diff options
| author | Adam Soltys <[email protected]> | 2026-10-30 18:29:14 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-30 18:29:14 -0700 |
| commit | f675fef1321e69f434f076211d13438c0e1aff59 (patch) | |
| tree | e07ff6ba80faa9738dccf9bb2c1135d0c8e4c918 /src | |
| parent | e7961f2e0d9df26b846c2c7f976c0d567ffbcc15 (diff) | |
| download | coinosv3-f675fef1321e69f434f076211d13438c0e1aff59.tar.gz coinosv3-f675fef1321e69f434f076211d13438c0e1aff59.zip | |
A profile (kind 0) is never written over one we failed to find. 2026-10-30, a long-time nostr user signed in with his key for the first time; our relays had never seen his kind 0 (it lived on ditto, snort and oxtr), the 3 s lookup came back empty, and the payment-address publish went out as {lud16, nip05} alone: name, picture and banner gone from every relay (nicstazzema, shown as npub1yk2lkr7). An empty quick lookup now widens to the same search the follow list got after its own overwrite (indexes, archives, the key's own write relays, Primal's signed copy) before anything is written, once per key per session so a brand-new key's first publish waits once. For anyone already hit, the profile editor offers "Look for older copies": a kind 0 from a history-keeping relay fills only the fields still empty in the editor, and Save publishes it. tools/kind0-overwrite-test.js plays the incident against a fake relay network where only an archive holds the profile
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/messages.js | 95 | ||||
| -rw-r--r-- | src/i18n.js | 11 | ||||
| -rw-r--r-- | src/locales/ar.json | 11 | ||||
| -rw-r--r-- | src/locales/bn.json | 11 | ||||
| -rw-r--r-- | src/locales/de.json | 11 | ||||
| -rw-r--r-- | src/locales/es.json | 11 | ||||
| -rw-r--r-- | src/locales/fr.json | 11 | ||||
| -rw-r--r-- | src/locales/hi.json | 11 | ||||
| -rw-r--r-- | src/locales/id.json | 11 | ||||
| -rw-r--r-- | src/locales/it.json | 11 | ||||
| -rw-r--r-- | src/locales/ja.json | 11 | ||||
| -rw-r--r-- | src/locales/ko.json | 11 | ||||
| -rw-r--r-- | src/locales/mr.json | 11 | ||||
| -rw-r--r-- | src/locales/pt.json | 11 | ||||
| -rw-r--r-- | src/locales/ru.json | 11 | ||||
| -rw-r--r-- | src/locales/sw.json | 11 | ||||
| -rw-r--r-- | src/locales/ta.json | 11 | ||||
| -rw-r--r-- | src/locales/te.json | 11 | ||||
| -rw-r--r-- | src/locales/tr.json | 11 | ||||
| -rw-r--r-- | src/locales/ur.json | 11 | ||||
| -rw-r--r-- | src/locales/vi.json | 11 | ||||
| -rw-r--r-- | src/locales/zh.json | 11 |
22 files changed, 325 insertions, 1 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index cdb9aea..478e054 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -5185,7 +5185,8 @@ export function messagesFeature(ctx) { ]); 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 }; + const profile = all.filter((e) => e.kind === 0).sort((a, b) => b.created_at - a.created_at)[0] || null; + return { list, profile, existing: all.length > 0 }; } // Fetch the newest list from the relays. Older than what we hold is @@ -12801,9 +12802,24 @@ export function messagesFeature(ctx) { let kind0Chain = Promise.resolve(); let kind0Last = null; // { at, content } — the newest kind 0 we published const withKind0Lock = (fn) => { const p = kind0Chain.then(fn, fn); kind0Chain = p.catch(() => {}); return p; }; + // A kind 0 is replaceable too: one built on a profile we failed to find + // wipes the real one wherever it lands. 2026-10-30, a long-time nostr + // user signed in with his key for the first time; our relays had never + // seen his kind 0 (it lived on ditto, snort and oxtr), the lookup below + // came back empty, and the payment-address publish went out as + // {lud16, nip05} alone — name, picture and banner gone everywhere. So an + // empty quick lookup widens to the same search the follow list gets + // (indexes, archives, his own write relays, Primal's signed copy) before + // anything is written. Once per key per session: a key with no profile + // anywhere is new, and its first publish shouldn't wait twice. + const kind0DeepMiss = new Set(); async function newestKind0(pk) { const evs = await queryOn([...new Set([...PROFILE_RELAYS, ...DM_RELAYS])], { kinds: [0], authors: [pk] }, 3000); let newest = evs.sort((a, b) => b.created_at - a.created_at)[0] || null; + if (!newest && !(kind0Last && kind0Last.pk === pk) && !kind0DeepMiss.has(pk)) { + try { newest = (await deepContactList(pk)).profile || null; } catch {} + if (!newest) kind0DeepMiss.add(pk); + } if (kind0Last && kind0Last.pk === pk && (!newest || newest.created_at <= kind0Last.at)) newest = kind0Last; let base = {}; try { base = newest ? JSON.parse(newest.content) : {}; } catch {} @@ -12902,6 +12918,78 @@ export function messagesFeature(ctx) { fullProfiles.set(id.pubkey, merged); profiles.set(id.pubkey, { name: merged.name || null, picture: merged.picture || null }); } + // ---- profile history: undo a kind 0 that replaced the real one --------- + // Relays holding the newest copy dropped the older ones; the archives + // often haven't. An older copy fills the editor's EMPTY fields (name, + // bio, picture, banner) — nothing typed or already set is touched, and + // nothing is published until Save. + const PROFILE_HISTORY_FIELDS = ['name', 'about', 'picture', 'banner']; + async function findProfileHistory() { + 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: [0], 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); + } + const profileFieldsOf = (e) => { + let c = {}; + try { c = JSON.parse(e.content) || {}; } catch {} + const out = {}; + for (const k of PROFILE_HISTORY_FIELDS) { + const v = k === 'name' ? (c.display_name || c.name) : c[k]; + if (typeof v === 'string' && v.trim()) out[k] = v.trim(); + } + return out; + }; + // what an older copy has that the draft is missing + const profileHistoryExtra = (e, draft) => Object.keys(profileFieldsOf(e)).filter((k) => !(draft && String(draft[k] || '').trim())); + function fillProfileFrom(e) { + const draft = ui.profEdit; + if (!draft) return 0; + const old = profileFieldsOf(e); + let n = 0; + for (const k of profileHistoryExtra(e, draft)) { draft[k] = old[k]; (draft.touched ||= {})[k] = true; n++; } + if (n) { + if (old.picture && draft.picture === old.picture) preloadPicture({ picture: old.picture }); + toast(t('profHistFilled', { n })); + } else toast(t('profHistNothing')); + if (ui.profHist) ui.profHist.done = true; + render(); + return n; + } + function profileHistoryCard() { + const draft = ui.profEdit; + if (!draft || !mePk()) return null; + const ph = ui.profHist; + const look = () => { + ui.profHist = { loading: true }; + render(); + findProfileHistory().then((vs) => { ui.profHist = { versions: vs }; render(); }) + .catch(() => { ui.profHist = { versions: [] }; render(); }); + }; + const versions = (ph && ph.versions) || []; + const offers = versions.map((e) => ({ e, extra: profileHistoryExtra(e, draft) })).filter((v) => v.extra.length); + return h('div', { class: 'col prof-hist-card', style: 'gap:8px' }, + h('p', { class: 'small muted', style: 'margin:0' }, t('profHistDesc')), + !ph || ph.done + ? h('button', { class: 'btn-sm', type: 'button', style: 'align-self:flex-start', onClick: look }, t('profHistLook')) + : ph.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 }) => { + const f = profileFieldsOf(e); + return h('div', { class: 'row between gap6', style: 'align-items:center' }, + h('div', { class: 'col', style: 'gap:1px;min-width:0' }, + h('div', { class: 'break' }, f.name || t('profHistNoName')), + h('div', { class: 'small faint' }, new Date(e.created_at * 1000).toLocaleDateString() + + ' · ' + extra.map((k) => t('profHist_' + k)).join(', '))), + h('button', { class: 'btn-sm', type: 'button', onClick: () => fillProfileFrom(e) }, t('profHistUse'))); + })) + : h('div', { class: 'small faint' }, t('profHistNone'))); + } // A payment-address rename released the old name: repoint the kind 0's // lud16 and nip05 — but only where they pointed at the released address // (or were empty). Deliberately different values are not ours to touch. @@ -13231,6 +13319,7 @@ export function messagesFeature(ctx) { ui.profUploading = false; render(); }, })), + profileHistoryCard(), h('button', { class: 'btn-primary btn-block', disabled: ui.profSaving, onClick: saveProfile }, ui.profSaving ? h('span', { class: 'spinner sm' }) : t('save')), h('button', { class: 'btn-ghost btn-block', onClick: () => { ui.profEdit = null; ui.profEditFilled = false; render(); } }, t('cancel'))) @@ -18567,6 +18656,10 @@ export function messagesFeature(ctx) { // lud16 and nip05 — but only where they pointed at the released address // (or were empty). Deliberately different values are not ours to touch. addressRenamed(oldAddr, newAddr) { return repointAddress(oldAddr, newAddr).catch(() => {}); }, + // older copies of our kind 0 on history-keeping relays, and filling the + // open editor's empty fields from one (tests) + profileHistory() { return findProfileHistory(); }, + fillProfileFrom(e) { return fillProfileFrom(e); }, // the profile editor's Save, without the editor (tests) async saveProfileDraft(e) { const id = await identity(); return withKind0Lock(() => saveProfileNow(id, { name: '', about: '', picture: '', banner: '', ...e })); }, // opens the profile, unless the caller has a step to take first (the diff --git a/src/i18n.js b/src/i18n.js index 8190030..636362e 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -109,6 +109,17 @@ const STR = { profDisplayName: 'Display name', profPublicKey: 'Nostr public key', profKeyLink: 'Public key', + profHistDesc: 'If another app replaced your profile, an older copy may still be on a relay that keeps history. Using one fills only the fields that are empty here.', + profHistLook: 'Look for older copies', + profHistUse: 'Use', + profHistNoName: '(no name)', + profHistNone: 'No older copy has anything this one is missing.', + profHistNothing: 'Everything in that copy is already here.', + profHistFilled: 'Filled {n} fields from the older copy. Save to publish.', + profHist_name: 'name', + profHist_about: 'bio', + profHist_picture: 'picture', + profHist_banner: 'banner', 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', diff --git a/src/locales/ar.json b/src/locales/ar.json index a79178b..a60f6ba 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -886,6 +886,17 @@ "followHistNone": "لا توجد نسخة أقدم فيها أحد تفتقده قائمتك.", "followHistNothing": "كل من في تلك النسخة موجود في قائمتك بالفعل.", "followHistRestored": "تمت استعادة {n} متابَعًا.", + "profHistDesc": "إذا استبدل تطبيق آخر ملفك الشخصي، فقد تبقى نسخة أقدم على مرحّل يحتفظ بالسجل. استخدامها يملأ فقط الحقول الفارغة هنا.", + "profHistLook": "البحث عن نسخ أقدم", + "profHistUse": "استخدام", + "profHistNoName": "(بلا اسم)", + "profHistNone": "لا توجد نسخة أقدم فيها شيء ينقص هنا.", + "profHistNothing": "كل ما في تلك النسخة موجود هنا بالفعل.", + "profHistFilled": "تم ملء {n} حقول من النسخة الأقدم. احفظ للنشر.", + "profHist_name": "الاسم", + "profHist_about": "النبذة", + "profHist_picture": "الصورة", + "profHist_banner": "الغلاف", "securityTitle": "الأمان", "arkCoinsNone": "لا توجد عملات في الإنفاق بعد.", "profKeyLink": "المفتاح العام", diff --git a/src/locales/bn.json b/src/locales/bn.json index de06f4c..0f627b2 100644 --- a/src/locales/bn.json +++ b/src/locales/bn.json @@ -886,6 +886,17 @@ "followHistNone": "কোনো পুরোনো কপিতে এমন কেউ নেই যিনি আপনার তালিকায় নেই।", "followHistNothing": "ওই কপির সবাই ইতিমধ্যে আপনার তালিকায় আছেন।", "followHistRestored": "{n} জনকে আবার ফলো করা হয়েছে।", + "profHistDesc": "অন্য কোনো অ্যাপ আপনার প্রোফাইল বদলে দিলে, ইতিহাস রাখে এমন কোনো রিলেতে পুরোনো কপি এখনও থাকতে পারে। সেটি ব্যবহার করলে এখানে শুধু খালি ঘরগুলো পূরণ হয়।", + "profHistLook": "পুরোনো কপি খুঁজুন", + "profHistUse": "ব্যবহার করুন", + "profHistNoName": "(নাম নেই)", + "profHistNone": "কোনো পুরোনো কপিতে এমন কিছু নেই যা এখানে নেই।", + "profHistNothing": "ওই কপির সবকিছু এখানে আগে থেকেই আছে।", + "profHistFilled": "পুরোনো কপি থেকে {n}টি ঘর পূরণ হয়েছে। প্রকাশ করতে সংরক্ষণ করুন।", + "profHist_name": "নাম", + "profHist_about": "পরিচিতি", + "profHist_picture": "ছবি", + "profHist_banner": "ব্যানার", "securityTitle": "নিরাপত্তা", "arkCoinsNone": "স্পেন্ডিং-এ এখনও কোনো কয়েন নেই।", "profKeyLink": "পাবলিক কী", diff --git a/src/locales/de.json b/src/locales/de.json index 0adee29..11fde22 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -886,6 +886,17 @@ "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.", + "profHistDesc": "Hat eine andere App dein Profil ersetzt, liegt vielleicht noch eine ältere Kopie auf einem Relay, das den Verlauf aufbewahrt. Eine zu übernehmen füllt nur die Felder, die hier leer sind.", + "profHistLook": "Nach älteren Kopien suchen", + "profHistUse": "Übernehmen", + "profHistNoName": "(kein Name)", + "profHistNone": "Keine ältere Kopie enthält etwas, das hier fehlt.", + "profHistNothing": "Alles aus dieser Kopie ist schon hier.", + "profHistFilled": "{n} Felder aus der älteren Kopie gefüllt. Speichern, um zu veröffentlichen.", + "profHist_name": "Name", + "profHist_about": "Bio", + "profHist_picture": "Bild", + "profHist_banner": "Banner", "securityTitle": "Sicherheit", "arkCoinsNone": "Noch keine Coins in Ausgaben.", "profKeyLink": "Öffentlicher Schlüssel", diff --git a/src/locales/es.json b/src/locales/es.json index d5cc421..28e2489 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -886,6 +886,17 @@ "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.", + "profHistDesc": "Si otra app reemplazó tu perfil, puede que aún haya una copia anterior en un relay que guarda el historial. Usarla solo rellena los campos que aquí están vacíos.", + "profHistLook": "Buscar copias anteriores", + "profHistUse": "Usar", + "profHistNoName": "(sin nombre)", + "profHistNone": "Ninguna copia anterior tiene algo que falte aquí.", + "profHistNothing": "Todo lo de esa copia ya está aquí.", + "profHistFilled": "{n} campos rellenados desde la copia anterior. Guarda para publicar.", + "profHist_name": "nombre", + "profHist_about": "bio", + "profHist_picture": "foto", + "profHist_banner": "banner", "securityTitle": "Seguridad", "arkCoinsNone": "Aún no hay monedas en Gastos.", "profKeyLink": "Clave pública", diff --git a/src/locales/fr.json b/src/locales/fr.json index 7e7245b..54c6eda 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -886,6 +886,17 @@ "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.", + "profHistDesc": "Si une autre app a remplacé votre profil, une copie plus ancienne est peut-être encore sur un relais qui garde l’historique. En utiliser une ne remplit que les champs vides ici.", + "profHistLook": "Chercher des copies plus anciennes", + "profHistUse": "Utiliser", + "profHistNoName": "(sans nom)", + "profHistNone": "Aucune copie plus ancienne n’a quelque chose qui manque ici.", + "profHistNothing": "Tout ce que contient cette copie est déjà ici.", + "profHistFilled": "{n} champs remplis depuis la copie plus ancienne. Enregistrez pour publier.", + "profHist_name": "nom", + "profHist_about": "bio", + "profHist_picture": "photo", + "profHist_banner": "bannière", "securityTitle": "Sécurité", "arkCoinsNone": "Aucune pièce dans Dépenses pour l’instant.", "profKeyLink": "Clé publique", diff --git a/src/locales/hi.json b/src/locales/hi.json index 42c85e8..527eb27 100644 --- a/src/locales/hi.json +++ b/src/locales/hi.json @@ -886,6 +886,17 @@ "followHistNone": "किसी पुरानी कॉपी में ऐसा कोई नहीं जो आपकी सूची में न हो।", "followHistNothing": "उस कॉपी के सभी लोग पहले से आपकी सूची में हैं।", "followHistRestored": "{n} फ़ॉलो बहाल किए गए।", + "profHistDesc": "अगर किसी दूसरे ऐप ने आपकी प्रोफ़ाइल बदल दी है, तो इतिहास रखने वाले किसी रिले पर पुरानी प्रति अब भी हो सकती है। उसे इस्तेमाल करने से यहाँ सिर्फ़ खाली फ़ील्ड भरते हैं।", + "profHistLook": "पुरानी प्रतियाँ खोजें", + "profHistUse": "इस्तेमाल करें", + "profHistNoName": "(बिना नाम)", + "profHistNone": "किसी पुरानी प्रति में ऐसा कुछ नहीं जो यहाँ न हो।", + "profHistNothing": "उस प्रति की हर चीज़ यहाँ पहले से है।", + "profHistFilled": "पुरानी प्रति से {n} फ़ील्ड भरे गए। प्रकाशित करने के लिए सहेजें।", + "profHist_name": "नाम", + "profHist_about": "परिचय", + "profHist_picture": "फ़ोटो", + "profHist_banner": "बैनर", "securityTitle": "सुरक्षा", "arkCoinsNone": "स्पेंडिंग में अभी कोई कॉइन नहीं।", "profKeyLink": "पब्लिक की", diff --git a/src/locales/id.json b/src/locales/id.json index fc807ec..e2402eb 100644 --- a/src/locales/id.json +++ b/src/locales/id.json @@ -220,6 +220,17 @@ "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.", + "profHistDesc": "Jika aplikasi lain mengganti profil Anda, salinan lama mungkin masih ada di relay yang menyimpan riwayat. Memakainya hanya mengisi kolom yang kosong di sini.", + "profHistLook": "Cari salinan lama", + "profHistUse": "Pakai", + "profHistNoName": "(tanpa nama)", + "profHistNone": "Tidak ada salinan lama yang punya sesuatu yang kurang di sini.", + "profHistNothing": "Semua isi salinan itu sudah ada di sini.", + "profHistFilled": "{n} kolom diisi dari salinan lama. Simpan untuk menerbitkan.", + "profHist_name": "nama", + "profHist_about": "bio", + "profHist_picture": "foto", + "profHist_banner": "spanduk", "securityTitle": "Keamanan", "arkCoinsNone": "Belum ada koin di Belanja.", "profKeyLink": "Kunci publik", diff --git a/src/locales/it.json b/src/locales/it.json index 459e67b..a9998c1 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -96,6 +96,17 @@ "relaysReload": "Ricarica", "followHistNothing": "Tutti quelli presenti in quella copia sono già nella tua lista.", "followHistRestored": "Ripristinati {n} seguiti.", + "profHistDesc": "Se un’altra app ha sostituito il tuo profilo, una copia più vecchia potrebbe essere ancora su un relay che conserva la cronologia. Usarne una riempie solo i campi qui vuoti.", + "profHistLook": "Cerca copie più vecchie", + "profHistUse": "Usa", + "profHistNoName": "(senza nome)", + "profHistNone": "Nessuna copia più vecchia ha qualcosa che manca qui.", + "profHistNothing": "Tutto ciò che c’è in quella copia è già qui.", + "profHistFilled": "{n} campi riempiti dalla copia più vecchia. Salva per pubblicare.", + "profHist_name": "nome", + "profHist_about": "bio", + "profHist_picture": "foto", + "profHist_banner": "banner", "profFollowing": "Seguiti", "profFollowers": "Follower", "profFollowsNone": "Non segui ancora nessuno.", diff --git a/src/locales/ja.json b/src/locales/ja.json index 7386982..27970db 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -886,6 +886,17 @@ "followHistNone": "あなたのリストに欠けている人を含む古いコピーはありません。", "followHistNothing": "そのコピーの全員がすでにリストにいます。", "followHistRestored": "{n}人のフォローを復元しました。", + "profHistDesc": "別のアプリがプロフィールを置き換えた場合、履歴を保持するリレーに古いコピーが残っていることがあります。使うと、ここで空欄の項目だけが埋まります。", + "profHistLook": "古いコピーを探す", + "profHistUse": "使う", + "profHistNoName": "(名前なし)", + "profHistNone": "ここに足りないものを持つ古いコピーはありません。", + "profHistNothing": "そのコピーの内容はすべてここにあります。", + "profHistFilled": "古いコピーから{n}項目を埋めました。公開するには保存してください。", + "profHist_name": "名前", + "profHist_about": "自己紹介", + "profHist_picture": "写真", + "profHist_banner": "バナー", "securityTitle": "セキュリティ", "arkCoinsNone": "支出にはまだコインがありません。", "profKeyLink": "公開鍵", diff --git a/src/locales/ko.json b/src/locales/ko.json index a25a8f7..94f9f0e 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -885,6 +885,17 @@ "followHistNone": "목록에 빠진 사람이 들어 있는 예전 사본이 없습니다.", "followHistNothing": "그 사본의 모든 사람이 이미 목록에 있습니다.", "followHistRestored": "팔로우 {n}명을 복원했습니다.", + "profHistDesc": "다른 앱이 프로필을 덮어썼다면, 기록을 보관하는 릴레이에 이전 사본이 남아 있을 수 있습니다. 사용하면 여기서 비어 있는 항목만 채웁니다.", + "profHistLook": "이전 사본 찾기", + "profHistUse": "사용", + "profHistNoName": "(이름 없음)", + "profHistNone": "여기에 없는 내용을 가진 이전 사본이 없습니다.", + "profHistNothing": "그 사본의 내용은 모두 이미 여기에 있습니다.", + "profHistFilled": "이전 사본에서 {n}개 항목을 채웠습니다. 게시하려면 저장하세요.", + "profHist_name": "이름", + "profHist_about": "소개", + "profHist_picture": "사진", + "profHist_banner": "배너", "securityTitle": "보안", "arkCoinsNone": "지출에 아직 코인이 없습니다.", "profKeyLink": "공개 키", diff --git a/src/locales/mr.json b/src/locales/mr.json index 3bd67c1..a7ad2b4 100644 --- a/src/locales/mr.json +++ b/src/locales/mr.json @@ -220,6 +220,17 @@ "followHistNone": "तुमच्या यादीत नसलेले कोणी असलेली जुनी प्रत नाही.", "followHistNothing": "त्या प्रतीतील सर्वजण आधीच तुमच्या यादीत आहेत.", "followHistRestored": "{n} फॉलो पुनर्संचयित केले.", + "profHistDesc": "दुसऱ्या अॅपने तुमची प्रोफाइल बदलली असेल, तर इतिहास ठेवणाऱ्या रिलेवर जुनी प्रत अजूनही असू शकते. ती वापरल्यास येथे फक्त रिकामी फील्ड भरली जातात.", + "profHistLook": "जुन्या प्रती शोधा", + "profHistUse": "वापरा", + "profHistNoName": "(नाव नाही)", + "profHistNone": "कोणत्याही जुन्या प्रतीत येथे नसलेले काही नाही.", + "profHistNothing": "त्या प्रतीतील सर्व काही येथे आधीच आहे.", + "profHistFilled": "जुन्या प्रतीतून {n} फील्ड भरली. प्रकाशित करण्यासाठी जतन करा.", + "profHist_name": "नाव", + "profHist_about": "परिचय", + "profHist_picture": "फोटो", + "profHist_banner": "बॅनर", "securityTitle": "सुरक्षा", "arkCoinsNone": "स्पेंडिंगमध्ये अद्याप कोणतेही कॉइन नाहीत.", "profKeyLink": "सार्वजनिक की", diff --git a/src/locales/pt.json b/src/locales/pt.json index 1b2b6f8..56e8cda 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -886,6 +886,17 @@ "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.", + "profHistDesc": "Se outro app substituiu seu perfil, uma cópia mais antiga ainda pode estar em um relay que guarda histórico. Usá-la preenche apenas os campos que estão vazios aqui.", + "profHistLook": "Procurar cópias mais antigas", + "profHistUse": "Usar", + "profHistNoName": "(sem nome)", + "profHistNone": "Nenhuma cópia mais antiga tem algo que falte aqui.", + "profHistNothing": "Tudo dessa cópia já está aqui.", + "profHistFilled": "{n} campos preenchidos da cópia mais antiga. Salve para publicar.", + "profHist_name": "nome", + "profHist_about": "bio", + "profHist_picture": "foto", + "profHist_banner": "capa", "securityTitle": "Segurança", "arkCoinsNone": "Ainda não há moedas em Gastos.", "profKeyLink": "Chave pública", diff --git a/src/locales/ru.json b/src/locales/ru.json index b888c62..7ec23b3 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -886,6 +886,17 @@ "followHistNone": "Нет старых копий с людьми, которых не хватает в вашем списке.", "followHistNothing": "Все из этой копии уже есть в вашем списке.", "followHistRestored": "Восстановлено подписок: {n}.", + "profHistDesc": "Если другое приложение заменило ваш профиль, более старая копия может ещё храниться на релее с историей. Её использование заполняет только пустые здесь поля.", + "profHistLook": "Поискать старые копии", + "profHistUse": "Использовать", + "profHistNoName": "(без имени)", + "profHistNone": "Ни в одной старой копии нет того, чего здесь не хватает.", + "profHistNothing": "Всё из этой копии уже здесь.", + "profHistFilled": "Заполнено полей из старой копии: {n}. Сохраните, чтобы опубликовать.", + "profHist_name": "имя", + "profHist_about": "о себе", + "profHist_picture": "фото", + "profHist_banner": "баннер", "securityTitle": "Безопасность", "arkCoinsNone": "В расходах пока нет монет.", "profKeyLink": "Открытый ключ", diff --git a/src/locales/sw.json b/src/locales/sw.json index 6aecfe6..e5aba2e 100644 --- a/src/locales/sw.json +++ b/src/locales/sw.json @@ -220,6 +220,17 @@ "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}.", + "profHistDesc": "Ikiwa programu nyingine ilibadilisha wasifu wako, nakala ya zamani huenda bado iko kwenye relay inayohifadhi historia. Kuitumia hujaza tu sehemu zilizo tupu hapa.", + "profHistLook": "Tafuta nakala za zamani", + "profHistUse": "Tumia", + "profHistNoName": "(hakuna jina)", + "profHistNone": "Hakuna nakala ya zamani iliyo na kitu kinachokosekana hapa.", + "profHistNothing": "Kila kitu kwenye nakala hiyo tayari kipo hapa.", + "profHistFilled": "Sehemu {n} zimejazwa kutoka nakala ya zamani. Hifadhi ili kuchapisha.", + "profHist_name": "jina", + "profHist_about": "wasifu", + "profHist_picture": "picha", + "profHist_banner": "bango", "securityTitle": "Usalama", "arkCoinsNone": "Bado hakuna sarafu kwenye Matumizi.", "profKeyLink": "Ufunguo wa umma", diff --git a/src/locales/ta.json b/src/locales/ta.json index 066e275..a15043d 100644 --- a/src/locales/ta.json +++ b/src/locales/ta.json @@ -220,6 +220,17 @@ "followHistNone": "உங்கள் பட்டியலில் இல்லாதவர் உள்ள பழைய நகல் இல்லை.", "followHistNothing": "அந்த நகலில் உள்ள அனைவரும் ஏற்கனவே உங்கள் பட்டியலில் உள்ளனர்.", "followHistRestored": "{n} பின்தொடர்வுகள் மீட்டெடுக்கப்பட்டன.", + "profHistDesc": "வேறு ஒரு செயலி உங்கள் சுயவிவரத்தை மாற்றியிருந்தால், வரலாற்றை வைத்திருக்கும் ரிலேயில் பழைய நகல் இன்னும் இருக்கலாம். அதைப் பயன்படுத்தினால் இங்கே காலியாக உள்ள புலங்கள் மட்டும் நிரப்பப்படும்.", + "profHistLook": "பழைய நகல்களைத் தேடு", + "profHistUse": "பயன்படுத்து", + "profHistNoName": "(பெயர் இல்லை)", + "profHistNone": "இங்கே இல்லாத எதுவும் எந்தப் பழைய நகலிலும் இல்லை.", + "profHistNothing": "அந்த நகலில் உள்ள அனைத்தும் ஏற்கனவே இங்கே உள்ளன.", + "profHistFilled": "பழைய நகலிலிருந்து {n} புலங்கள் நிரப்பப்பட்டன. வெளியிட சேமிக்கவும்.", + "profHist_name": "பெயர்", + "profHist_about": "அறிமுகம்", + "profHist_picture": "படம்", + "profHist_banner": "பேனர்", "securityTitle": "பாதுகாப்பு", "arkCoinsNone": "செலவுக் கணக்கில் இன்னும் நாணயங்கள் இல்லை.", "profKeyLink": "பொது விசை", diff --git a/src/locales/te.json b/src/locales/te.json index 88389fd..2f6c298 100644 --- a/src/locales/te.json +++ b/src/locales/te.json @@ -218,6 +218,17 @@ "followHistNone": "మీ జాబితాలో లేని వ్యక్తి ఉన్న పాత కాపీ ఏదీ లేదు.", "followHistNothing": "ఆ కాపీలోని అందరూ ఇప్పటికే మీ జాబితాలో ఉన్నారు.", "followHistRestored": "{n} ఫాలోలు పునరుద్ధరించబడ్డాయి.", + "profHistDesc": "మరో యాప్ మీ ప్రొఫైల్ను మార్చేస్తే, చరిత్రను ఉంచే రిలేలో పాత కాపీ ఇంకా ఉండవచ్చు. దాన్ని వాడితే ఇక్కడ ఖాళీగా ఉన్న ఫీల్డ్లు మాత్రమే నిండుతాయి.", + "profHistLook": "పాత కాపీలను వెతకండి", + "profHistUse": "వాడండి", + "profHistNoName": "(పేరు లేదు)", + "profHistNone": "ఇక్కడ లేనిది ఏ పాత కాపీలోనూ లేదు.", + "profHistNothing": "ఆ కాపీలో ఉన్నదంతా ఇప్పటికే ఇక్కడ ఉంది.", + "profHistFilled": "పాత కాపీ నుండి {n} ఫీల్డ్లు నింపబడ్డాయి. ప్రచురించడానికి సేవ్ చేయండి.", + "profHist_name": "పేరు", + "profHist_about": "పరిచయం", + "profHist_picture": "ఫోటో", + "profHist_banner": "బ్యానర్", "securityTitle": "భద్రత", "arkCoinsNone": "ఖర్చు ఖాతాలో ఇంకా నాణేలు లేవు.", "profKeyLink": "పబ్లిక్ కీ", diff --git a/src/locales/tr.json b/src/locales/tr.json index ac41629..407bcc0 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -885,6 +885,17 @@ "followHistNone": "Hiçbir eski kopyada listende eksik olan biri yok.", "followHistNothing": "O kopyadaki herkes zaten listende.", "followHistRestored": "{n} takip geri yüklendi.", + "profHistDesc": "Başka bir uygulama profilinizi değiştirdiyse, geçmişi saklayan bir relayde daha eski bir kopya hâlâ olabilir. Birini kullanmak yalnızca burada boş olan alanları doldurur.", + "profHistLook": "Eski kopyaları ara", + "profHistUse": "Kullan", + "profHistNoName": "(adsız)", + "profHistNone": "Hiçbir eski kopyada burada eksik olan bir şey yok.", + "profHistNothing": "O kopyadaki her şey zaten burada.", + "profHistFilled": "Eski kopyadan {n} alan dolduruldu. Yayınlamak için kaydedin.", + "profHist_name": "ad", + "profHist_about": "hakkında", + "profHist_picture": "fotoğraf", + "profHist_banner": "kapak", "securityTitle": "Güvenlik", "arkCoinsNone": "Harcama’da henüz coin yok.", "profKeyLink": "Açık anahtar", diff --git a/src/locales/ur.json b/src/locales/ur.json index d579fc3..bdcfa7a 100644 --- a/src/locales/ur.json +++ b/src/locales/ur.json @@ -220,6 +220,17 @@ "followHistNone": "کسی پرانی کاپی میں ایسا کوئی نہیں جو آپ کی فہرست میں نہ ہو۔", "followHistNothing": "اس کاپی کے سب لوگ پہلے سے آپ کی فہرست میں ہیں۔", "followHistRestored": "{n} فالو بحال ہو گئے۔", + "profHistDesc": "اگر کسی اور ایپ نے آپ کا پروفائل بدل دیا ہے تو تاریخ محفوظ رکھنے والے کسی ریلے پر پرانی نقل اب بھی ہو سکتی ہے۔ اسے استعمال کرنے سے یہاں صرف خالی خانے بھرتے ہیں۔", + "profHistLook": "پرانی نقلیں تلاش کریں", + "profHistUse": "استعمال کریں", + "profHistNoName": "(بغیر نام)", + "profHistNone": "کسی پرانی نقل میں ایسا کچھ نہیں جو یہاں نہ ہو۔", + "profHistNothing": "اس نقل کی ہر چیز پہلے سے یہاں موجود ہے۔", + "profHistFilled": "پرانی نقل سے {n} خانے بھرے گئے۔ شائع کرنے کے لیے محفوظ کریں۔", + "profHist_name": "نام", + "profHist_about": "تعارف", + "profHist_picture": "تصویر", + "profHist_banner": "بینر", "securityTitle": "سیکیورٹی", "arkCoinsNone": "خرچ میں ابھی کوئی کوائن نہیں۔", "profKeyLink": "پبلک کی", diff --git a/src/locales/vi.json b/src/locales/vi.json index 1923062..5490c34 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -220,6 +220,17 @@ "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.", + "profHistDesc": "Nếu một ứng dụng khác đã thay thế hồ sơ của bạn, bản cũ hơn có thể vẫn còn trên relay lưu lịch sử. Dùng một bản chỉ điền vào các ô đang trống ở đây.", + "profHistLook": "Tìm bản cũ hơn", + "profHistUse": "Dùng", + "profHistNoName": "(không tên)", + "profHistNone": "Không bản cũ nào có thứ còn thiếu ở đây.", + "profHistNothing": "Mọi thứ trong bản đó đã có ở đây.", + "profHistFilled": "Đã điền {n} ô từ bản cũ hơn. Lưu để đăng.", + "profHist_name": "tên", + "profHist_about": "tiểu sử", + "profHist_picture": "ảnh", + "profHist_banner": "ảnh bìa", "securityTitle": "Bảo mật", "arkCoinsNone": "Chưa có coin nào trong Chi tiêu.", "profKeyLink": "Khóa công khai", diff --git a/src/locales/zh.json b/src/locales/zh.json index d934e2d..4fc2f4b 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -886,6 +886,17 @@ "followHistNone": "没有旧副本包含你列表中缺少的人。", "followHistNothing": "该副本中的所有人都已在你的列表中。", "followHistRestored": "已恢复 {n} 个关注。", + "profHistDesc": "如果其他应用替换了你的资料,保留历史的中继上可能还有旧副本。使用它只会填入这里为空的字段。", + "profHistLook": "查找旧副本", + "profHistUse": "使用", + "profHistNoName": "(无名称)", + "profHistNone": "没有旧副本包含这里缺少的内容。", + "profHistNothing": "那份副本里的内容这里都已有了。", + "profHistFilled": "已从旧副本填入 {n} 个字段。保存即可发布。", + "profHist_name": "名称", + "profHist_about": "简介", + "profHist_picture": "头像", + "profHist_banner": "横幅", "securityTitle": "安全", "arkCoinsNone": "支出账户中还没有币。", "profKeyLink": "公钥", |
