aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/features/messages.js137
-rw-r--r--src/i18n.js8
-rw-r--r--src/locales/ar.json10
-rw-r--r--src/locales/bn.json10
-rw-r--r--src/locales/de.json10
-rw-r--r--src/locales/es.json10
-rw-r--r--src/locales/fr.json10
-rw-r--r--src/locales/hi.json10
-rw-r--r--src/locales/id.json10
-rw-r--r--src/locales/ja.json10
-rw-r--r--src/locales/ko.json10
-rw-r--r--src/locales/mr.json10
-rw-r--r--src/locales/pt.json10
-rw-r--r--src/locales/ru.json10
-rw-r--r--src/locales/sw.json10
-rw-r--r--src/locales/ta.json10
-rw-r--r--src/locales/te.json10
-rw-r--r--src/locales/tr.json10
-rw-r--r--src/locales/ur.json10
-rw-r--r--src/locales/vi.json10
-rw-r--r--src/locales/zh.json10
21 files changed, 306 insertions, 29 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 3e8fce5..765df01 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -3709,15 +3709,40 @@ export function messagesFeature(ctx) {
}
const isFollowing = (pk) => followsNow().set.has(pk);
+ // Everywhere someone's follow list might be: our relays, the profile
+ // indexes, the archives, their own outbox relays, and Primal's cache (its
+ // copy is the signed event, checked here like any other). A kind 3 is
+ // REPLACEABLE: publishing one built on a list we failed to find wipes the
+ // real one everywhere it lands. 2026-10-02: an Amethyst user's 398 follows,
+ // kept only on her own relays, became our two starter follows the first
+ // time she signed in here. Returns the newest list (or null) and whether
+ // this key has any nostr life at all (a list or a profile).
+ async function deepContactList(pk) {
+ const relays = [...new Set([...zapRelays(), ...PROFILE_INDEX_RELAYS, ...PROFILE_ARCHIVE_RELAYS,
+ ...(await relaysOf(pk).catch(() => []))])];
+ const [relayEvs, primalEvs] = await Promise.all([
+ queryOn(relays, { kinds: [0, 3], authors: [pk] }, 7000).catch(() => []),
+ primalCache('contact_list', { pubkey: pk }, 7000)
+ .then((evs) => verifiedPrimal((evs || []).filter((e) => e.pubkey === pk), [0, 3])).catch(() => []),
+ ]);
+ const all = [...(relayEvs || []), ...primalEvs].filter((e) => e.pubkey === pk);
+ const list = all.filter((e) => e.kind === 3).sort((a, b) => b.created_at - a.created_at)[0] || null;
+ return { list, existing: all.length > 0 };
+ }
+
// Fetch the newest list from the relays. Older than what we hold is
// ignored: a relay that missed our last publish must not un-follow people.
- async function syncFollows({ force = false } = {}) {
+ // `deep` widens the search to everywhere above — required before
+ // publishing on top of a list we have never seen.
+ async function syncFollows({ force = false, deep = false } = {}) {
const me = mePk();
if (!me) return followsNow();
if (!force && Date.now() - followsAt < 10 * 60_000) return followsNow();
followsAt = Date.now();
try {
- const evs = await queryOn(zapRelays(), { kinds: [3], authors: [me] }, 5000);
+ const evs = deep
+ ? [(await deepContactList(me)).list].filter(Boolean)
+ : await queryOn(zapRelays(), { kinds: [3], authors: [me] }, 5000);
const newest = (evs || []).sort((a, b) => b.created_at - a.created_at)[0];
const cur = followsNow();
if (newest && newest.created_at > cur.at) {
@@ -3758,8 +3783,14 @@ export function messagesFeature(ctx) {
for (const f of feeds) if (!s.feeds.some((x) => x.id === f.id)) s.feeds.push(f);
save(s);
if (onlyIfNoFollows) {
- await syncFollows({ force: true }).catch(() => {});
- if (followsNow().set.size) return;
+ // "New" here means new to coinos, not new to nostr: anyone with a
+ // profile or a follow list anywhere already has a following, and a
+ // starter list would replace it. Only a key with no trace gets one.
+ const me = mePk();
+ if (!me) return;
+ let found;
+ try { found = await deepContactList(me); } catch { return; }
+ if (found.existing || followsNow().set.size) { syncFollows({ force: true, deep: true }).catch(() => {}); return; }
}
followMany(STARTER_FOLLOWS).catch(() => {});
}
@@ -3773,7 +3804,8 @@ export function messagesFeature(ctx) {
followsPub = true;
const before = followsNow();
try {
- await syncFollows({ force: true }).catch(() => {});
+ // never seen this person's list: look everywhere before writing one
+ await syncFollows({ force: true, deep: !before.at }).catch(() => {});
const fetched = followsNow();
const base = fetched.at > before.at ? fetched : before;
const add = want.filter((pk) => !base.set.has(pk));
@@ -3801,7 +3833,8 @@ export function messagesFeature(ctx) {
saveFollows({ ...before, set: new Set(before.set.has(pk) ? [...before.set].filter((x) => x !== pk) : [...before.set, pk]) });
render();
try {
- await syncFollows({ force: true });
+ // never seen this person's list: look everywhere before writing one
+ await syncFollows({ force: true, deep: !before.at });
// The list to publish is the relays' copy if it's genuinely newer than
// what we held, and otherwise what we held BEFORE the optimistic paint
// — reading our own paint back as fact made a follow publish a list
@@ -6647,13 +6680,18 @@ export function messagesFeature(ctx) {
return Array.isArray(tags) ? tags.filter((x) => Array.isArray(x) && x[0] && x[1]) : [];
} catch { return []; } // nip04 from an older client, or not ours to read
}
- async function syncMutes({ force = false } = {}) {
+ async function syncMutes({ force = false, deep = false } = {}) {
const me = mePk();
if (!me) return mutesNow();
if (!force && Date.now() - mutesAt < 10 * 60_000) return mutesNow();
mutesAt = Date.now();
try {
- const evs = await queryOn(zapRelays(), { kinds: [10000], authors: [me] }, 5000);
+ // replaceable like the follow list: never written over one we haven't
+ // looked for everywhere (see deepContactList)
+ const relays = deep
+ ? [...new Set([...zapRelays(), ...PROFILE_INDEX_RELAYS, ...PROFILE_ARCHIVE_RELAYS, ...(await relaysOf(me).catch(() => []))])]
+ : zapRelays();
+ const evs = await queryOn(relays, { kinds: [10000], authors: [me] }, deep ? 7000 : 5000);
const newest = (evs || []).sort((a, b) => b.created_at - a.created_at)[0];
const cur = mutesNow();
if (newest && newest.created_at > cur.at) {
@@ -6675,7 +6713,7 @@ export function messagesFeature(ctx) {
saveMutes({ ...before, ...paint });
render();
try {
- await syncMutes({ force: true });
+ await syncMutes({ force: true, deep: !before.at });
const fetched = mutesNow();
const base = fetched.at > before.at ? fetched : before;
const next = change(base) || {};
@@ -6973,6 +7011,85 @@ export function messagesFeature(ctx) {
h('button', { class: 'btn-ghost btn-block', onClick: close }, t('back'))));
}
// Settings → Nostr: the mute list laid out, and the two spam rules.
+ // ---- follow list history: undo a client that replaced the list ---------
+ // A kind 3 is replaceable, so relays that hold the newest copy have dropped
+ // the old one; the archives often haven't. Restoring is a union: everyone
+ // in the old copy goes back into the current list, nobody is taken out.
+ const FOLLOW_HISTORY_RELAYS = ['wss://nostr21.com', 'wss://relay.ditto.pub', 'wss://offchain.pub', 'wss://nostr.mom',
+ 'wss://nostr.oxtr.dev', 'wss://relay.nostrplebs.com', 'wss://relay.damus.io', 'wss://nos.lol', 'wss://relay.primal.net',
+ 'wss://relay.snort.social', 'wss://nostr.wine', 'wss://nostr.bitcoiner.social'];
+ async function findFollowHistory() {
+ const me = mePk();
+ if (!me) return [];
+ const relays = [...new Set([...zapRelays(), ...PROFILE_INDEX_RELAYS, ...PROFILE_ARCHIVE_RELAYS, ...FOLLOW_HISTORY_RELAYS,
+ ...(await relaysOf(me).catch(() => []))])];
+ const evs = await queryOn(relays, { kinds: [3], authors: [me], limit: 50 }, 8000).catch(() => []);
+ const byId = new Map();
+ for (const e of evs || []) if (e.pubkey === me) byId.set(e.id, e);
+ return [...byId.values()].sort((a, b) => b.created_at - a.created_at);
+ }
+ async function restoreFollows(old) {
+ if (followsPub) return;
+ const id = await requireIdentity();
+ followsPub = true;
+ try {
+ const before = followsNow();
+ await syncFollows({ force: true, deep: true });
+ const fetched = followsNow();
+ const base = fetched.at > before.at ? fetched : before;
+ const have = new Set(pTags(base.tags).map((x) => x[1]));
+ const add = pTags(old.tags || []).filter((x) => !have.has(x[1]) && x[1] !== id.pubkey && (have.add(x[1]), true));
+ if (!add.length) { toast(t('followHistNothing')); return; }
+ const tags = [...base.tags, ...add];
+ const created_at = Math.max(Math.floor(Date.now() / 1000), base.at + 1);
+ const partial = { kind: 3, content: base.content || '', created_at, tags };
+ const evt = id.signer instanceof Uint8Array ? finalizeEvent(partial, id.signer) : await id.signer.signEvent(partial);
+ const ok = await publishOn([...new Set([...zapRelays(), ...(await relaysOf(id.pubkey).catch(() => []))])], evt);
+ if (!ok) throw new Error(t('msgSendFailed'));
+ saveFollows({ set: new Set(pTags(tags).map((x) => x[1])), tags, content: base.content || '', at: created_at });
+ feedAuthorsChanged();
+ syncInbox({ force: true }).catch(() => {});
+ toast(t('followHistRestored', { n: add.length }));
+ if (ui.followHist) ui.followHist.done = true;
+ } catch (e) {
+ if (!(e instanceof NoIdentity)) toast(e.message || String(e));
+ } finally {
+ followsPub = false;
+ render();
+ }
+ }
+ function followHistoryCard() {
+ if (!mePk()) return null;
+ const cur = followsNow();
+ const fh = ui.followHist;
+ const look = () => {
+ ui.followHist = { loading: true };
+ render();
+ findFollowHistory().then((vs) => { ui.followHist = { versions: vs }; render(); })
+ .catch(() => { ui.followHist = { versions: [] }; render(); });
+ };
+ const versions = (fh && fh.versions) || [];
+ // worth offering: a copy holding people the current list doesn't
+ const offers = versions.map((e) => ({ e, extra: pTags(e.tags).filter((x) => !cur.set.has(x[1])).length }))
+ .filter((v) => v.extra > 0);
+ const clientOf = (e) => (e.tags.find((x) => x[0] === 'client') || [])[1];
+ return h('div', { class: 'card col follow-hist-card', style: 'gap:10px' },
+ h('h3', {}, t('followHistTitle')),
+ h('p', { class: 'small muted', style: 'margin:0' }, t('followHistDesc', { n: cur.set.size })),
+ !fh || fh.done
+ ? h('button', { class: 'btn-block', onClick: look }, t('followHistLook'))
+ : fh.loading
+ ? h('div', { class: 'row', style: 'justify-content:center;padding:6px' }, h('span', { class: 'spinner sm' }))
+ : offers.length
+ ? h('div', { class: 'col', style: 'gap:8px' }, ...offers.slice(0, 6).map(({ e, extra }) => h('div', { class: 'row between gap6', style: 'align-items:center' },
+ h('div', { class: 'col', style: 'gap:1px;min-width:0' },
+ h('div', {}, t('followHistPeople', { n: pTags(e.tags).length })),
+ h('div', { class: 'small faint' }, new Date(e.created_at * 1000).toLocaleDateString()
+ + (clientOf(e) ? ' · ' + clientOf(e) : ''))),
+ h('button', { class: 'btn-sm', disabled: followsPub, onClick: () => restoreFollows(e) }, t('followHistRestore', { n: extra })))))
+ : h('div', { class: 'small faint' }, t('followHistNone')));
+ }
+
function moderationCard() {
const m = mutesNow();
const e = ui.modEdit || (ui.modEdit = { word: '' });
@@ -10061,7 +10178,7 @@ export function messagesFeature(ctx) {
return {
id: 'messages',
- nostrSettingsCards() { return [moderationCard()]; },
+ nostrSettingsCards() { return [followHistoryCard(), moderationCard()]; },
// The app came back after being backgrounded. A phone freezes a hidden
// tab: the relay sockets are cut and every post made in the meantime is
// simply missing, which is why the feed used to sit there looking stale
diff --git a/src/i18n.js b/src/i18n.js
index 9ab7c53..9b258b1 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -104,6 +104,14 @@ const STR = {
profUploadPic: 'Upload photo',
profDisplayName: 'Display name',
profPublicKey: 'Nostr public key',
+ followHistTitle: 'Follow list',
+ followHistDesc: 'You follow {n} people. If another app replaced your follow list, an older copy may still be on a relay that keeps history. Restoring adds everyone from it back; nobody is removed.',
+ followHistLook: 'Look for older copies',
+ followHistPeople: '{n} people',
+ followHistRestore: 'Restore {n}',
+ followHistNone: 'No older copy has anyone your list is missing.',
+ followHistNothing: 'Everyone in that copy is already in your list.',
+ followHistRestored: 'Restored {n} follows.',
profFollowing: 'Following',
profFollowers: 'Followers',
profFollowsNone: 'Not following anyone yet.',
diff --git a/src/locales/ar.json b/src/locales/ar.json
index c5af5fb..09b789e 100644
--- a/src/locales/ar.json
+++ b/src/locales/ar.json
@@ -870,5 +870,13 @@
"profFollowersNone": "لم يُعثر على متابعين.",
"profFollowersPartial": "عرض {n} من {total} متابع.",
"showMore": "عرض المزيد",
- "arkZapNoPostZap": "لا يمكن إرسال zap لهذا المنشور: ليس لدى كاتبه عنوان Lightning ولا عنوان Ark."
+ "arkZapNoPostZap": "لا يمكن إرسال zap لهذا المنشور: ليس لدى كاتبه عنوان Lightning ولا عنوان Ark.",
+ "followHistTitle": "قائمة المتابعة",
+ "followHistDesc": "أنت تتابع {n} شخصًا. إذا استبدل تطبيق آخر قائمة متابعتك، فقد تبقى نسخة أقدم على مرحّل يحتفظ بالسجل. الاستعادة تعيد كل من فيها؛ لا يُحذف أحد.",
+ "followHistLook": "ابحث عن نسخ أقدم",
+ "followHistPeople": "{n} شخصًا",
+ "followHistRestore": "استعادة {n}",
+ "followHistNone": "لا توجد نسخة أقدم فيها أحد تفتقده قائمتك.",
+ "followHistNothing": "كل من في تلك النسخة موجود في قائمتك بالفعل.",
+ "followHistRestored": "تمت استعادة {n} متابَعًا."
}
diff --git a/src/locales/bn.json b/src/locales/bn.json
index b2f8b5f..c5fc411 100644
--- a/src/locales/bn.json
+++ b/src/locales/bn.json
@@ -870,5 +870,13 @@
"profFollowersNone": "কোনো অনুসারী পাওয়া যায়নি।",
"profFollowersPartial": "{total} জন অনুসারীর মধ্যে {n} জন দেখানো হচ্ছে।",
"showMore": "আরও দেখুন",
- "arkZapNoPostZap": "এই পোস্টে zap করা যাবে না: লেখকের কোনো Lightning ঠিকানা বা Ark ঠিকানা নেই।"
+ "arkZapNoPostZap": "এই পোস্টে zap করা যাবে না: লেখকের কোনো Lightning ঠিকানা বা Ark ঠিকানা নেই।",
+ "followHistTitle": "ফলো তালিকা",
+ "followHistDesc": "আপনি {n} জনকে ফলো করেন। অন্য কোনো অ্যাপ আপনার ফলো তালিকা বদলে দিলে, পুরোনো একটি কপি ইতিহাস রাখা কোনো রিলেতে থাকতে পারে। পুনরুদ্ধার করলে সবাই ফিরে আসে; কাউকে সরানো হয় না।",
+ "followHistLook": "পুরোনো কপি খুঁজুন",
+ "followHistPeople": "{n} জন",
+ "followHistRestore": "{n} জন ফিরিয়ে আনুন",
+ "followHistNone": "কোনো পুরোনো কপিতে এমন কেউ নেই যিনি আপনার তালিকায় নেই।",
+ "followHistNothing": "ওই কপির সবাই ইতিমধ্যে আপনার তালিকায় আছেন।",
+ "followHistRestored": "{n} জনকে আবার ফলো করা হয়েছে।"
}
diff --git a/src/locales/de.json b/src/locales/de.json
index 5311a5b..626d548 100644
--- a/src/locales/de.json
+++ b/src/locales/de.json
@@ -870,5 +870,13 @@
"profFollowersNone": "Keine Follower gefunden.",
"profFollowersPartial": "{n} von {total} Followern angezeigt.",
"showMore": "Mehr anzeigen",
- "arkZapNoPostZap": "Dieser Beitrag kann nicht gezappt werden: Der Autor hat weder eine Lightning- noch eine Ark-Adresse."
+ "arkZapNoPostZap": "Dieser Beitrag kann nicht gezappt werden: Der Autor hat weder eine Lightning- noch eine Ark-Adresse.",
+ "followHistTitle": "Folgeliste",
+ "followHistDesc": "Du folgst {n} Personen. Hat eine andere App deine Folgeliste ersetzt, liegt vielleicht noch eine ältere Kopie auf einem Relay, das den Verlauf aufbewahrt. Wiederherstellen fügt alle daraus wieder hinzu; niemand wird entfernt.",
+ "followHistLook": "Nach älteren Kopien suchen",
+ "followHistPeople": "{n} Personen",
+ "followHistRestore": "{n} wiederherstellen",
+ "followHistNone": "Keine ältere Kopie enthält jemanden, der in deiner Liste fehlt.",
+ "followHistNothing": "Alle aus dieser Kopie sind schon in deiner Liste.",
+ "followHistRestored": "{n} Follows wiederhergestellt."
}
diff --git a/src/locales/es.json b/src/locales/es.json
index 4084357..db30441 100644
--- a/src/locales/es.json
+++ b/src/locales/es.json
@@ -870,5 +870,13 @@
"profFollowersNone": "No se encontraron seguidores.",
"profFollowersPartial": "Mostrando {n} de {total} seguidores.",
"showMore": "Mostrar más",
- "arkZapNoPostZap": "No se puede hacer zap a esta publicación: su autor no tiene dirección Lightning ni dirección Ark."
+ "arkZapNoPostZap": "No se puede hacer zap a esta publicación: su autor no tiene dirección Lightning ni dirección Ark.",
+ "followHistTitle": "Lista de seguidos",
+ "followHistDesc": "Sigues a {n} personas. Si otra app reemplazó tu lista, puede que quede una copia anterior en un relay que guarda historial. Restaurar vuelve a añadir a todos los de esa copia; no se quita a nadie.",
+ "followHistLook": "Buscar copias anteriores",
+ "followHistPeople": "{n} personas",
+ "followHistRestore": "Restaurar {n}",
+ "followHistNone": "Ninguna copia anterior tiene a alguien que falte en tu lista.",
+ "followHistNothing": "Todos los de esa copia ya están en tu lista.",
+ "followHistRestored": "Se restauraron {n} seguidos."
}
diff --git a/src/locales/fr.json b/src/locales/fr.json
index f8ca6a9..aeccf9c 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -870,5 +870,13 @@
"profFollowersNone": "Aucun abonné trouvé.",
"profFollowersPartial": "{n} abonnés affichés sur {total}.",
"showMore": "Afficher plus",
- "arkZapNoPostZap": "Impossible de zapper cette publication : son auteur n’a ni adresse Lightning ni adresse Ark."
+ "arkZapNoPostZap": "Impossible de zapper cette publication : son auteur n’a ni adresse Lightning ni adresse Ark.",
+ "followHistTitle": "Liste d’abonnements",
+ "followHistDesc": "Vous suivez {n} personnes. Si une autre app a remplacé votre liste, une copie plus ancienne peut subsister sur un relais qui garde l’historique. La restauration rajoute tout le monde ; personne n’est retiré.",
+ "followHistLook": "Chercher des copies plus anciennes",
+ "followHistPeople": "{n} personnes",
+ "followHistRestore": "Restaurer {n}",
+ "followHistNone": "Aucune copie plus ancienne ne contient quelqu’un qui manque à votre liste.",
+ "followHistNothing": "Tout le monde dans cette copie est déjà dans votre liste.",
+ "followHistRestored": "{n} abonnements restaurés."
}
diff --git a/src/locales/hi.json b/src/locales/hi.json
index 013be87..e683ef2 100644
--- a/src/locales/hi.json
+++ b/src/locales/hi.json
@@ -870,5 +870,13 @@
"profFollowersNone": "कोई फ़ॉलोअर नहीं मिला।",
"profFollowersPartial": "{total} में से {n} फ़ॉलोअर्स दिखाए जा रहे हैं।",
"showMore": "और दिखाएँ",
- "arkZapNoPostZap": "इस पोस्ट पर zap नहीं कर सकते: लेखक के पास न Lightning पता है, न Ark पता।"
+ "arkZapNoPostZap": "इस पोस्ट पर zap नहीं कर सकते: लेखक के पास न Lightning पता है, न Ark पता।",
+ "followHistTitle": "फ़ॉलो सूची",
+ "followHistDesc": "आप {n} लोगों को फ़ॉलो करते हैं। अगर किसी दूसरे ऐप ने आपकी फ़ॉलो सूची बदल दी, तो इतिहास रखने वाले किसी रिले पर पुरानी कॉपी हो सकती है। बहाल करने पर सब वापस जुड़ जाते हैं; कोई हटाया नहीं जाता।",
+ "followHistLook": "पुरानी कॉपियाँ खोजें",
+ "followHistPeople": "{n} लोग",
+ "followHistRestore": "{n} बहाल करें",
+ "followHistNone": "किसी पुरानी कॉपी में ऐसा कोई नहीं जो आपकी सूची में न हो।",
+ "followHistNothing": "उस कॉपी के सभी लोग पहले से आपकी सूची में हैं।",
+ "followHistRestored": "{n} फ़ॉलो बहाल किए गए।"
}
diff --git a/src/locales/id.json b/src/locales/id.json
index 64bf633..beebc86 100644
--- a/src/locales/id.json
+++ b/src/locales/id.json
@@ -206,5 +206,13 @@
"profFollowersNone": "Tidak ada pengikut yang ditemukan.",
"profFollowersPartial": "Menampilkan {n} dari {total} pengikut.",
"showMore": "Tampilkan lebih banyak",
- "arkZapNoPostZap": "Tidak bisa zap postingan ini: penulisnya tidak punya alamat Lightning maupun alamat Ark."
+ "arkZapNoPostZap": "Tidak bisa zap postingan ini: penulisnya tidak punya alamat Lightning maupun alamat Ark.",
+ "followHistTitle": "Daftar ikuti",
+ "followHistDesc": "Kamu mengikuti {n} orang. Jika aplikasi lain mengganti daftar ikutimu, salinan lama mungkin masih ada di relay yang menyimpan riwayat. Memulihkan menambahkan semua orang dari salinan itu; tak ada yang dihapus.",
+ "followHistLook": "Cari salinan lama",
+ "followHistPeople": "{n} orang",
+ "followHistRestore": "Pulihkan {n}",
+ "followHistNone": "Tidak ada salinan lama yang berisi orang yang hilang dari daftarmu.",
+ "followHistNothing": "Semua orang di salinan itu sudah ada di daftarmu.",
+ "followHistRestored": "{n} ikutan dipulihkan."
}
diff --git a/src/locales/ja.json b/src/locales/ja.json
index ddd6c53..9745063 100644
--- a/src/locales/ja.json
+++ b/src/locales/ja.json
@@ -870,5 +870,13 @@
"profFollowersNone": "フォロワーが見つかりません。",
"profFollowersPartial": "{total}人中{n}人のフォロワーを表示しています。",
"showMore": "もっと見る",
- "arkZapNoPostZap": "この投稿にはZapできません:投稿者にLightningアドレスもArkアドレスもありません。"
+ "arkZapNoPostZap": "この投稿にはZapできません:投稿者にLightningアドレスもArkアドレスもありません。",
+ "followHistTitle": "フォローリスト",
+ "followHistDesc": "{n}人をフォロー中です。別のアプリがフォローリストを置き換えた場合、履歴を保存するリレーに古いコピーが残っているかもしれません。復元するとその全員が戻り、誰も外されません。",
+ "followHistLook": "古いコピーを探す",
+ "followHistPeople": "{n}人",
+ "followHistRestore": "{n}人を復元",
+ "followHistNone": "あなたのリストに欠けている人を含む古いコピーはありません。",
+ "followHistNothing": "そのコピーの全員がすでにリストにいます。",
+ "followHistRestored": "{n}人のフォローを復元しました。"
}
diff --git a/src/locales/ko.json b/src/locales/ko.json
index cf252d2..098bb7e 100644
--- a/src/locales/ko.json
+++ b/src/locales/ko.json
@@ -869,5 +869,13 @@
"profFollowersNone": "팔로워를 찾을 수 없습니다.",
"profFollowersPartial": "팔로워 {total}명 중 {n}명을 표시합니다.",
"showMore": "더 보기",
- "arkZapNoPostZap": "이 게시물에 zap할 수 없습니다: 작성자에게 Lightning 주소도 Ark 주소도 없습니다."
+ "arkZapNoPostZap": "이 게시물에 zap할 수 없습니다: 작성자에게 Lightning 주소도 Ark 주소도 없습니다.",
+ "followHistTitle": "팔로우 목록",
+ "followHistDesc": "{n}명을 팔로우하고 있습니다. 다른 앱이 팔로우 목록을 덮어썼다면, 기록을 보관하는 릴레이에 예전 사본이 남아 있을 수 있습니다. 복원하면 그 사람들이 모두 다시 추가되며, 아무도 빠지지 않습니다.",
+ "followHistLook": "예전 사본 찾기",
+ "followHistPeople": "{n}명",
+ "followHistRestore": "{n}명 복원",
+ "followHistNone": "목록에 빠진 사람이 들어 있는 예전 사본이 없습니다.",
+ "followHistNothing": "그 사본의 모든 사람이 이미 목록에 있습니다.",
+ "followHistRestored": "팔로우 {n}명을 복원했습니다."
}
diff --git a/src/locales/mr.json b/src/locales/mr.json
index 63c17cf..fa63503 100644
--- a/src/locales/mr.json
+++ b/src/locales/mr.json
@@ -206,5 +206,13 @@
"profFollowersNone": "कोणतेही फॉलोअर्स आढळले नाहीत.",
"profFollowersPartial": "{total} पैकी {n} फॉलोअर्स दाखवत आहे.",
"showMore": "अधिक दाखवा",
- "arkZapNoPostZap": "या पोस्टला zap करता येत नाही: लेखकाकडे Lightning पत्ता किंवा Ark पत्ता नाही."
+ "arkZapNoPostZap": "या पोस्टला zap करता येत नाही: लेखकाकडे Lightning पत्ता किंवा Ark पत्ता नाही.",
+ "followHistTitle": "फॉलो यादी",
+ "followHistDesc": "तुम्ही {n} लोकांना फॉलो करता. दुसऱ्या ॲपने तुमची फॉलो यादी बदलली असल्यास, इतिहास ठेवणाऱ्या रिलेवर जुनी प्रत असू शकते. पुनर्संचयित केल्याने सर्वजण परत जोडले जातात; कोणालाही काढले जात नाही.",
+ "followHistLook": "जुन्या प्रती शोधा",
+ "followHistPeople": "{n} लोक",
+ "followHistRestore": "{n} पुनर्संचयित करा",
+ "followHistNone": "तुमच्या यादीत नसलेले कोणी असलेली जुनी प्रत नाही.",
+ "followHistNothing": "त्या प्रतीतील सर्वजण आधीच तुमच्या यादीत आहेत.",
+ "followHistRestored": "{n} फॉलो पुनर्संचयित केले."
}
diff --git a/src/locales/pt.json b/src/locales/pt.json
index ce07aba..e9e0ef8 100644
--- a/src/locales/pt.json
+++ b/src/locales/pt.json
@@ -870,5 +870,13 @@
"profFollowersNone": "Nenhum seguidor encontrado.",
"profFollowersPartial": "Mostrando {n} de {total} seguidores.",
"showMore": "Mostrar mais",
- "arkZapNoPostZap": "Não é possível dar zap neste post: o autor não tem endereço Lightning nem endereço Ark."
+ "arkZapNoPostZap": "Não é possível dar zap neste post: o autor não tem endereço Lightning nem endereço Ark.",
+ "followHistTitle": "Lista de seguidos",
+ "followHistDesc": "Você segue {n} pessoas. Se outro app substituiu sua lista, uma cópia antiga pode estar num relay que guarda histórico. Restaurar adiciona todos de volta; ninguém é removido.",
+ "followHistLook": "Procurar cópias antigas",
+ "followHistPeople": "{n} pessoas",
+ "followHistRestore": "Restaurar {n}",
+ "followHistNone": "Nenhuma cópia antiga tem alguém que falte na sua lista.",
+ "followHistNothing": "Todos dessa cópia já estão na sua lista.",
+ "followHistRestored": "{n} seguidos restaurados."
}
diff --git a/src/locales/ru.json b/src/locales/ru.json
index 5d2fc07..b89fbe0 100644
--- a/src/locales/ru.json
+++ b/src/locales/ru.json
@@ -870,5 +870,13 @@
"profFollowersNone": "Подписчики не найдены.",
"profFollowersPartial": "Показано {n} из {total} подписчиков.",
"showMore": "Показать ещё",
- "arkZapNoPostZap": "Нельзя отправить zap на этот пост: у автора нет ни Lightning-адреса, ни Ark-адреса."
+ "arkZapNoPostZap": "Нельзя отправить zap на этот пост: у автора нет ни Lightning-адреса, ни Ark-адреса.",
+ "followHistTitle": "Список подписок",
+ "followHistDesc": "Вы подписаны на {n} человек. Если другое приложение заменило ваш список, старая копия может остаться на реле, хранящем историю. Восстановление вернёт всех из неё; никто не будет удалён.",
+ "followHistLook": "Найти старые копии",
+ "followHistPeople": "{n} человек",
+ "followHistRestore": "Восстановить {n}",
+ "followHistNone": "Нет старых копий с людьми, которых не хватает в вашем списке.",
+ "followHistNothing": "Все из этой копии уже есть в вашем списке.",
+ "followHistRestored": "Восстановлено подписок: {n}."
}
diff --git a/src/locales/sw.json b/src/locales/sw.json
index d41ee46..75f5a8b 100644
--- a/src/locales/sw.json
+++ b/src/locales/sw.json
@@ -206,5 +206,13 @@
"profFollowersNone": "Hakuna wafuasi waliopatikana.",
"profFollowersPartial": "Inaonyesha wafuasi {n} kati ya {total}.",
"showMore": "Onyesha zaidi",
- "arkZapNoPostZap": "Huwezi kutuma zap kwa chapisho hili: mwandishi wake hana anwani ya Lightning wala anwani ya Ark."
+ "arkZapNoPostZap": "Huwezi kutuma zap kwa chapisho hili: mwandishi wake hana anwani ya Lightning wala anwani ya Ark.",
+ "followHistTitle": "Orodha ya unaowafuata",
+ "followHistDesc": "Unawafuata watu {n}. Ikiwa programu nyingine ilibadilisha orodha yako, nakala ya zamani huenda bado iko kwenye relay inayohifadhi historia. Kurejesha huwaongeza wote tena; hakuna anayeondolewa.",
+ "followHistLook": "Tafuta nakala za zamani",
+ "followHistPeople": "watu {n}",
+ "followHistRestore": "Rejesha {n}",
+ "followHistNone": "Hakuna nakala ya zamani yenye mtu anayekosekana kwenye orodha yako.",
+ "followHistNothing": "Wote kwenye nakala hiyo tayari wamo kwenye orodha yako.",
+ "followHistRestored": "Umerejesha unaowafuata {n}."
}
diff --git a/src/locales/ta.json b/src/locales/ta.json
index 3fc58b8..5007c0a 100644
--- a/src/locales/ta.json
+++ b/src/locales/ta.json
@@ -206,5 +206,13 @@
"profFollowersNone": "பின்தொடர்பவர்கள் யாரும் இல்லை.",
"profFollowersPartial": "{total} பின்தொடர்பவர்களில் {n} பேர் காட்டப்படுகின்றனர்.",
"showMore": "மேலும் காட்டு",
- "arkZapNoPostZap": "இந்தப் பதிவுக்கு zap செய்ய முடியாது: ஆசிரியரிடம் Lightning முகவரியோ Ark முகவரியோ இல்லை."
+ "arkZapNoPostZap": "இந்தப் பதிவுக்கு zap செய்ய முடியாது: ஆசிரியரிடம் Lightning முகவரியோ Ark முகவரியோ இல்லை.",
+ "followHistTitle": "பின்தொடர்வு பட்டியல்",
+ "followHistDesc": "நீங்கள் {n} பேரைப் பின்தொடர்கிறீர்கள். வேறொரு ஆப் உங்கள் பட்டியலை மாற்றியிருந்தால், வரலாற்றைச் சேமிக்கும் ரிலேயில் பழைய நகல் இருக்கலாம். மீட்டெடுத்தால் அனைவரும் மீண்டும் சேர்க்கப்படுவர்; யாரும் நீக்கப்படமாட்டார்கள்.",
+ "followHistLook": "பழைய நகல்களைத் தேடு",
+ "followHistPeople": "{n} பேர்",
+ "followHistRestore": "{n} மீட்டெடு",
+ "followHistNone": "உங்கள் பட்டியலில் இல்லாதவர் உள்ள பழைய நகல் இல்லை.",
+ "followHistNothing": "அந்த நகலில் உள்ள அனைவரும் ஏற்கனவே உங்கள் பட்டியலில் உள்ளனர்.",
+ "followHistRestored": "{n} பின்தொடர்வுகள் மீட்டெடுக்கப்பட்டன."
}
diff --git a/src/locales/te.json b/src/locales/te.json
index 5b2a31e..a138c26 100644
--- a/src/locales/te.json
+++ b/src/locales/te.json
@@ -204,5 +204,13 @@
"profFollowersNone": "ఫాలోవర్లు కనిపించలేదు.",
"profFollowersPartial": "{total} ఫాలోవర్లలో {n} మందిని చూపిస్తోంది.",
"showMore": "మరిన్ని చూపించు",
- "arkZapNoPostZap": "ఈ పోస్ట్‌కు zap చేయలేరు: రచయితకు Lightning చిరునామా లేదా Ark చిరునామా లేదు."
+ "arkZapNoPostZap": "ఈ పోస్ట్‌కు zap చేయలేరు: రచయితకు Lightning చిరునామా లేదా Ark చిరునామా లేదు.",
+ "followHistTitle": "ఫాలో జాబితా",
+ "followHistDesc": "మీరు {n} మందిని ఫాలో అవుతున్నారు. మరో యాప్ మీ ఫాలో జాబితాను మార్చేసి ఉంటే, చరిత్రను ఉంచే రిలేలో పాత కాపీ ఉండవచ్చు. పునరుద్ధరిస్తే అందరూ మళ్లీ చేరతారు; ఎవరినీ తీసివేయరు.",
+ "followHistLook": "పాత కాపీల కోసం వెతకండి",
+ "followHistPeople": "{n} మంది",
+ "followHistRestore": "{n} పునరుద్ధరించండి",
+ "followHistNone": "మీ జాబితాలో లేని వ్యక్తి ఉన్న పాత కాపీ ఏదీ లేదు.",
+ "followHistNothing": "ఆ కాపీలోని అందరూ ఇప్పటికే మీ జాబితాలో ఉన్నారు.",
+ "followHistRestored": "{n} ఫాలోలు పునరుద్ధరించబడ్డాయి."
}
diff --git a/src/locales/tr.json b/src/locales/tr.json
index 62e9049..d8fcc50 100644
--- a/src/locales/tr.json
+++ b/src/locales/tr.json
@@ -869,5 +869,13 @@
"profFollowersNone": "Takipçi bulunamadı.",
"profFollowersPartial": "{total} takipçiden {n} tanesi gösteriliyor.",
"showMore": "Daha fazla göster",
- "arkZapNoPostZap": "Bu gönderiye zap yapılamaz: yazarının ne Lightning adresi ne de Ark adresi var."
+ "arkZapNoPostZap": "Bu gönderiye zap yapılamaz: yazarının ne Lightning adresi ne de Ark adresi var.",
+ "followHistTitle": "Takip listesi",
+ "followHistDesc": "{n} kişiyi takip ediyorsun. Başka bir uygulama takip listeni değiştirdiyse, geçmişi saklayan bir röle üzerinde eski bir kopya kalmış olabilir. Geri yüklemek o kopyadaki herkesi geri ekler; kimse çıkarılmaz.",
+ "followHistLook": "Eski kopyaları ara",
+ "followHistPeople": "{n} kişi",
+ "followHistRestore": "{n} kişiyi geri yükle",
+ "followHistNone": "Hiçbir eski kopyada listende eksik olan biri yok.",
+ "followHistNothing": "O kopyadaki herkes zaten listende.",
+ "followHistRestored": "{n} takip geri yüklendi."
}
diff --git a/src/locales/ur.json b/src/locales/ur.json
index 734df85..5059007 100644
--- a/src/locales/ur.json
+++ b/src/locales/ur.json
@@ -206,5 +206,13 @@
"profFollowersNone": "کوئی فالوور نہیں ملا۔",
"profFollowersPartial": "{total} میں سے {n} فالوورز دکھائے جا رہے ہیں۔",
"showMore": "مزید دکھائیں",
- "arkZapNoPostZap": "اس پوسٹ پر zap نہیں ہو سکتا: مصنف کے پاس نہ Lightning پتہ ہے نہ Ark پتہ۔"
+ "arkZapNoPostZap": "اس پوسٹ پر zap نہیں ہو سکتا: مصنف کے پاس نہ Lightning پتہ ہے نہ Ark پتہ۔",
+ "followHistTitle": "فالو فہرست",
+ "followHistDesc": "آپ {n} لوگوں کو فالو کرتے ہیں۔ اگر کسی اور ایپ نے آپ کی فالو فہرست بدل دی، تو تاریخ رکھنے والے کسی ریلے پر پرانی کاپی ہو سکتی ہے۔ بحال کرنے سے سب واپس شامل ہو جاتے ہیں؛ کسی کو ہٹایا نہیں جاتا۔",
+ "followHistLook": "پرانی کاپیاں تلاش کریں",
+ "followHistPeople": "{n} لوگ",
+ "followHistRestore": "{n} بحال کریں",
+ "followHistNone": "کسی پرانی کاپی میں ایسا کوئی نہیں جو آپ کی فہرست میں نہ ہو۔",
+ "followHistNothing": "اس کاپی کے سب لوگ پہلے سے آپ کی فہرست میں ہیں۔",
+ "followHistRestored": "{n} فالو بحال ہو گئے۔"
}
diff --git a/src/locales/vi.json b/src/locales/vi.json
index afce28e..91ae1e4 100644
--- a/src/locales/vi.json
+++ b/src/locales/vi.json
@@ -206,5 +206,13 @@
"profFollowersNone": "Không tìm thấy người theo dõi.",
"profFollowersPartial": "Đang hiển thị {n} trên {total} người theo dõi.",
"showMore": "Xem thêm",
- "arkZapNoPostZap": "Không thể zap bài này: tác giả không có địa chỉ Lightning hay địa chỉ Ark."
+ "arkZapNoPostZap": "Không thể zap bài này: tác giả không có địa chỉ Lightning hay địa chỉ Ark.",
+ "followHistTitle": "Danh sách theo dõi",
+ "followHistDesc": "Bạn đang theo dõi {n} người. Nếu một ứng dụng khác đã thay danh sách theo dõi của bạn, bản cũ có thể vẫn còn trên relay lưu lịch sử. Khôi phục sẽ thêm lại tất cả; không ai bị xóa.",
+ "followHistLook": "Tìm bản cũ",
+ "followHistPeople": "{n} người",
+ "followHistRestore": "Khôi phục {n}",
+ "followHistNone": "Không bản cũ nào có người mà danh sách của bạn đang thiếu.",
+ "followHistNothing": "Mọi người trong bản đó đã có trong danh sách của bạn.",
+ "followHistRestored": "Đã khôi phục {n} người theo dõi."
}
diff --git a/src/locales/zh.json b/src/locales/zh.json
index ee2234e..6b4c3a4 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -870,5 +870,13 @@
"profFollowersNone": "未找到粉丝。",
"profFollowersPartial": "显示 {total} 位粉丝中的 {n} 位。",
"showMore": "显示更多",
- "arkZapNoPostZap": "无法打赏此帖子:作者既没有 Lightning 地址也没有 Ark 地址。"
+ "arkZapNoPostZap": "无法打赏此帖子:作者既没有 Lightning 地址也没有 Ark 地址。",
+ "followHistTitle": "关注列表",
+ "followHistDesc": "你关注了 {n} 人。如果其他应用替换了你的关注列表,保存历史的中继上可能还有旧副本。恢复会把其中所有人加回来,不会移除任何人。",
+ "followHistLook": "查找旧副本",
+ "followHistPeople": "{n} 人",
+ "followHistRestore": "恢复 {n} 人",
+ "followHistNone": "没有旧副本包含你列表中缺少的人。",
+ "followHistNothing": "该副本中的所有人都已在你的列表中。",
+ "followHistRestored": "已恢复 {n} 个关注。"
}