diff --git a/src/islands/legacy/LegacyLetter.tsx b/src/islands/legacy/LegacyLetter.tsx index 282a4e7..3979f7a 100644 --- a/src/islands/legacy/LegacyLetter.tsx +++ b/src/islands/legacy/LegacyLetter.tsx @@ -187,6 +187,71 @@ function HowItWorks({ lang }: { lang: Lang }) { ); } +// Create/Open form strings, per language. (Deep crypto-lib error messages — e.g. +// "Wrong password" — remain English for now.) +const F: Record string; sharesDesc: (k: number) => string; + keepTitle: string; keepBody: (hasShares: boolean, k: number) => string; editLetter: string; + yourMessage: string; messagePh: string; accounts: string; + svcPh: string; userPh: string; passPh: string; sitePh: string; notesPh: string; + removeAria: string; addAccount: string; unlockHeading: string; passwordPh: string; confirmPh: string; + alsoSplit: string; alsoSplitHint: string; anyPre: string; ofMid: string; sharesSuf: string; + atLeastOne: string; encrypt: string; encrypting: string; + errNoMethod: string; errMismatch: string; errShort: string; errShares: string; errCreate: string; + dropTitle: string; dropDesc: string; openWithPw: string; openBtn: string; openWithShares: string; + pasteShares: (k: number, n: number) => string; combineOpen: string; chooseDifferent: string; + print: string; messageLabel: string; untitled: string; + lblUsername: string; lblPassword: string; lblWebsite: string; lblNotes: string; + copy: string; revealAria: string; written: (d: string) => string; + errRead: string; errOpen: string; +}> = { + en: { + back: '← Back', ready: 'Your encrypted letter is ready. Download it and keep it safe.', download: 'Download the letter', + sharesHead: (k, n) => `Family shares — any ${k} of these ${n} open the letter`, + sharesDesc: k => `Give each share to a different trusted person (a relative — and consider giving one to your lawyer/executor). No single person can open the letter alone; any ${k} together can.`, + keepTitle: 'Keep it recoverable', + keepBody: (hasShares, k) => `This letter can be opened only with its password${hasShares ? ` or ${k} family shares` : ''}. If those are lost, the contents are gone forever — by design, so no one else can read them. Store the file and the way to unlock it separately.`, + editLetter: 'Edit the letter', + yourMessage: 'Your message', messagePh: 'Final words for your family…', accounts: 'Accounts & secrets', + svcPh: 'Service (e.g. Instagram)', userPh: 'Username / email', passPh: 'Password / PIN', sitePh: 'Website (optional)', notesPh: 'Notes (e.g. 2FA on my phone)', + removeAria: 'Remove', addAccount: 'Add account', unlockHeading: 'How family will unlock it', passwordPh: 'Password', confirmPh: 'Confirm password', + alsoSplit: 'Also split into family shares', alsoSplitHint: '— so no single person can open it alone', anyPre: 'Any', ofMid: 'of', sharesSuf: 'shares', + atLeastOne: 'Set a password, family shares, or both. At least one is required.', encrypt: 'Encrypt & create letter', encrypting: 'Encrypting…', + errNoMethod: 'Choose at least one way to unlock: a password or family shares.', errMismatch: 'The passwords do not match.', errShort: 'Use a password of at least 8 characters.', + errShares: 'Check the family-share numbers (threshold ≥ 2, and total ≥ threshold).', errCreate: 'Could not create the letter.', + dropTitle: 'Drop the letter file', dropDesc: 'It is decrypted here on your device — nothing is uploaded.', + openWithPw: 'Open with password', openBtn: 'Open', openWithShares: 'Open with family shares', + pasteShares: (k, n) => `Paste ${k} of the ${n} shares, one per line.`, combineOpen: 'Combine & open', chooseDifferent: 'Choose a different file', + print: 'Print / Save PDF', messageLabel: 'Message', untitled: '(untitled)', + lblUsername: 'Username', lblPassword: 'Password', lblWebsite: 'Website', lblNotes: 'Notes', + copy: 'Copy', revealAria: 'Reveal', written: d => `Written ${d}.`, + errRead: 'Could not read this file.', errOpen: 'Could not open the letter.', + }, + id: { + back: '← Kembali', ready: 'Surat terenkripsi Anda sudah siap. Unduh dan simpan dengan aman.', download: 'Unduh surat', + sharesHead: (k, n) => `Bagian keluarga — ${k} mana pun dari ${n} ini membuka surat`, + sharesDesc: k => `Berikan tiap bagian kepada orang tepercaya yang berbeda (kerabat — dan pertimbangkan memberikan satu kepada notaris/pelaksana wasiat Anda). Tidak ada satu orang pun yang bisa membuka surat sendirian; ${k} mana pun bersama-sama bisa.`, + keepTitle: 'Jaga agar tetap bisa dibuka', + keepBody: (hasShares, k) => `Surat ini hanya bisa dibuka dengan kata sandinya${hasShares ? ` atau ${k} bagian keluarga` : ''}. Jika itu hilang, isinya lenyap selamanya — memang dirancang begitu, agar tidak ada orang lain yang bisa membacanya. Simpan berkas dan cara membukanya secara terpisah.`, + editLetter: 'Edit surat', + yourMessage: 'Pesan Anda', messagePh: 'Kata-kata terakhir untuk keluarga Anda…', accounts: 'Akun & rahasia', + svcPh: 'Layanan (mis. Instagram)', userPh: 'Nama pengguna / email', passPh: 'Kata sandi / PIN', sitePh: 'Situs web (opsional)', notesPh: 'Catatan (mis. 2FA di ponsel saya)', + removeAria: 'Hapus', addAccount: 'Tambah akun', unlockHeading: 'Cara keluarga membukanya', passwordPh: 'Kata sandi', confirmPh: 'Konfirmasi kata sandi', + alsoSplit: 'Bagi juga menjadi bagian keluarga', alsoSplitHint: '— agar tidak ada satu orang pun yang bisa membukanya sendirian', anyPre: 'Butuh', ofMid: 'dari', sharesSuf: 'bagian', + atLeastOne: 'Tetapkan kata sandi, bagian keluarga, atau keduanya. Minimal satu diperlukan.', encrypt: 'Enkripsi & buat surat', encrypting: 'Mengenkripsi…', + errNoMethod: 'Pilih setidaknya satu cara membuka: kata sandi atau bagian keluarga.', errMismatch: 'Kata sandi tidak cocok.', errShort: 'Gunakan kata sandi minimal 8 karakter.', + errShares: 'Periksa angka bagian keluarga (ambang ≥ 2, dan total ≥ ambang).', errCreate: 'Tidak dapat membuat surat.', + dropTitle: 'Letakkan berkas surat', dropDesc: 'Didekripsi di sini di perangkat Anda — tidak ada yang diunggah.', + openWithPw: 'Buka dengan kata sandi', openBtn: 'Buka', openWithShares: 'Buka dengan bagian keluarga', + pasteShares: (k, n) => `Tempel ${k} dari ${n} bagian, satu per baris.`, combineOpen: 'Gabungkan & buka', chooseDifferent: 'Pilih berkas lain', + print: 'Cetak / Simpan PDF', messageLabel: 'Pesan', untitled: '(tanpa judul)', + lblUsername: 'Nama pengguna', lblPassword: 'Kata sandi', lblWebsite: 'Situs web', lblNotes: 'Catatan', + copy: 'Salin', revealAria: 'Tampilkan', written: d => `Ditulis ${d}.`, + errRead: 'Tidak dapat membaca berkas ini.', errOpen: 'Tidak dapat membuka surat.', + }, +}; + export default function LegacyLetter({ lang = 'en' }: { lang?: Lang }) { const [mode, setMode] = useState(null); const tr = T[lang] ?? T.en; @@ -202,13 +267,14 @@ export default function LegacyLetter({ lang = 'en' }: { lang?: Lang }) { )} - {mode === 'create' && setMode(null)} />} - {mode === 'open' && setMode(null)} />} + {mode === 'create' && setMode(null)} lang={lang} />} + {mode === 'open' && setMode(null)} lang={lang} />} ); } -function CreateView({ onBack }: { onBack: () => void }) { +function CreateView({ onBack, lang }: { onBack: () => void; lang: Lang }) { + const f = F[lang] ?? F.en; const [message, setMessage] = useState(''); const [accounts, setAccounts] = useState([emptyAccount()]); const [password, setPassword] = useState(''); @@ -226,10 +292,10 @@ function CreateView({ onBack }: { onBack: () => void }) { const generate = async () => { setError(''); const usePw = password.length > 0; - if (!usePw && !useShares) { setError('Choose at least one way to unlock: a password or family shares.'); return; } - if (usePw && password !== confirm) { setError('The passwords do not match.'); return; } - if (usePw && password.length < 8) { setError('Use a password of at least 8 characters.'); return; } - if (useShares && (k < 2 || n < k || n > 20)) { setError('Check the family-share numbers (threshold ≥ 2, and total ≥ threshold).'); return; } + if (!usePw && !useShares) { setError(f.errNoMethod); return; } + if (usePw && password !== confirm) { setError(f.errMismatch); return; } + if (usePw && password.length < 8) { setError(f.errShort); return; } + if (useShares && (k < 2 || n < k || n > 20)) { setError(f.errShares); return; } const content: LegacyContent = { message, accounts: accounts.filter(a => a.service.trim() || a.username?.trim() || a.password?.trim()), @@ -240,7 +306,7 @@ function CreateView({ onBack }: { onBack: () => void }) { const res = await createVault(content, { password: usePw ? password : undefined, shares: useShares ? { n, k } : undefined }); setResult(res); } catch (e) { - setError(e instanceof Error ? e.message : 'Could not create the letter.'); + setError(e instanceof Error ? e.message : f.errCreate); } finally { setBusy(false); } @@ -249,16 +315,16 @@ function CreateView({ onBack }: { onBack: () => void }) { if (result) { return (
- - Your encrypted letter is ready. Download it and keep it safe. + + {f.ready} {result.shares.length > 0 && (
-

Family shares — hand out {k} of these {result.shares.length} are needed

-

Give each share to a different trusted person (a relative — and consider giving one to your lawyer/executor). No single person can open the letter alone; any {k} together can.

+

{f.sharesHead(k, result.shares.length)}

+

{f.sharesDesc(k)}

{result.shares.map((s, i) => (
{s} @@ -269,73 +335,70 @@ function CreateView({ onBack }: { onBack: () => void }) { )}
-

Keep it recoverable

-

- This letter can be opened only with its password{result.shares.length > 0 ? ` or ${k} family shares` : ''}. - If those are lost, the contents are gone forever — by design, so no one else can read them. Store the - file and the way to unlock it separately. -

+

{f.keepTitle}

+

{f.keepBody(result.shares.length > 0, k)}

- +
); } return (
- +
- -