diff --git a/internal/core/wordlists.go b/internal/core/wordlists.go index 0587d6c..4a8a89d 100644 --- a/internal/core/wordlists.go +++ b/internal/core/wordlists.go @@ -19,17 +19,18 @@ var wordlistFS embed.FS type Lang string const ( - LangEN Lang = "en" - LangES Lang = "es" - LangFR Lang = "fr" - LangDE Lang = "de" - LangSL Lang = "sl" - LangPT Lang = "pt" + LangEN Lang = "en" + LangES Lang = "es" + LangFR Lang = "fr" + LangDE Lang = "de" + LangSL Lang = "sl" + LangPT Lang = "pt" + LangZH_TW Lang = "zh-TW" ) // AllLangs returns all supported word list languages. func AllLangs() []Lang { - return []Lang{LangEN, LangES, LangFR, LangDE, LangSL, LangPT} + return []Lang{LangEN, LangES, LangFR, LangDE, LangSL, LangPT, LangZH_TW} } // WordListInfo describes a BIP39 word list: its source, expected hash, and words. @@ -60,6 +61,7 @@ var wordListSpecs = []wordListSpec{ {LangDE, "wordlists/german.txt", "https://github.com/dys2p/wordlists-de/blob/43553378b71ac06467e4654372ac249f15e16f4d/de-2048-v1.txt", "7965dc8c6b413ccb635d3021043365e18df0367bf5413a50a069a98addfe4e1d"}, {LangSL, "wordlists/slovenian.txt", "https://github.com/StellarStoic/BIP39_Exotica/8a5c0d93be825fab837dd293c94c635d6a39aa70/main/WRDL/nonStandard/slovenian.txt", "bdc73f14501843be9ae38fea61d6070298df4a83c67a8710e9755c557880467a"}, {LangPT, "wordlists/portuguese.txt", "https://github.com/bitcoin/bips/blob/ed7af6ae7e80c90bcfc69b3936073505e2fc2503/bip-0039/portuguese.txt", "2685e9c194c82ae67e10ba59d9ea5345a23dc093e92276fc5361f6667d79cd3f"}, + {LangZH_TW, "wordlists/chinese_traditional.txt", "https://github.com/bitcoin/bips/blob/7d77befd2b14359b9386fc1f9fb15f82d418fb34/bip-0039/chinese_traditional.txt", "417b26b3d8500a4ae3d59717d7011952db6fc2fb84b807f3f94ac734e89c1b5f"}, } var ( diff --git a/internal/core/wordlists/chinese_traditional.txt b/internal/core/wordlists/chinese_traditional.txt new file mode 100644 index 0000000..9b02047 --- /dev/null +++ b/internal/core/wordlists/chinese_traditional.txt @@ -0,0 +1,2048 @@ +的 +一 +是 +在 +不 +了 +有 +和 +人 +這 +中 +大 +為 +上 +個 +國 +我 +以 +要 +他 +時 +來 +用 +們 +生 +到 +作 +地 +於 +出 +就 +分 +對 +成 +會 +可 +主 +發 +年 +動 +同 +工 +也 +能 +下 +過 +子 +說 +產 +種 +面 +而 +方 +後 +多 +定 +行 +學 +法 +所 +民 +得 +經 +十 +三 +之 +進 +著 +等 +部 +度 +家 +電 +力 +裡 +如 +水 +化 +高 +自 +二 +理 +起 +小 +物 +現 +實 +加 +量 +都 +兩 +體 +制 +機 +當 +使 +點 +從 +業 +本 +去 +把 +性 +好 +應 +開 +它 +合 +還 +因 +由 +其 +些 +然 +前 +外 +天 +政 +四 +日 +那 +社 +義 +事 +平 +形 +相 +全 +表 +間 +樣 +與 +關 +各 +重 +新 +線 +內 +數 +正 +心 +反 +你 +明 +看 +原 +又 +麼 +利 +比 +或 +但 +質 +氣 +第 +向 +道 +命 +此 +變 +條 +只 +沒 +結 +解 +問 +意 +建 +月 +公 +無 +系 +軍 +很 +情 +者 +最 +立 +代 +想 +已 +通 +並 +提 +直 +題 +黨 +程 +展 +五 +果 +料 +象 +員 +革 +位 +入 +常 +文 +總 +次 +品 +式 +活 +設 +及 +管 +特 +件 +長 +求 +老 +頭 +基 +資 +邊 +流 +路 +級 +少 +圖 +山 +統 +接 +知 +較 +將 +組 +見 +計 +別 +她 +手 +角 +期 +根 +論 +運 +農 +指 +幾 +九 +區 +強 +放 +決 +西 +被 +幹 +做 +必 +戰 +先 +回 +則 +任 +取 +據 +處 +隊 +南 +給 +色 +光 +門 +即 +保 +治 +北 +造 +百 +規 +熱 +領 +七 +海 +口 +東 +導 +器 +壓 +志 +世 +金 +增 +爭 +濟 +階 +油 +思 +術 +極 +交 +受 +聯 +什 +認 +六 +共 +權 +收 +證 +改 +清 +美 +再 +採 +轉 +更 +單 +風 +切 +打 +白 +教 +速 +花 +帶 +安 +場 +身 +車 +例 +真 +務 +具 +萬 +每 +目 +至 +達 +走 +積 +示 +議 +聲 +報 +鬥 +完 +類 +八 +離 +華 +名 +確 +才 +科 +張 +信 +馬 +節 +話 +米 +整 +空 +元 +況 +今 +集 +溫 +傳 +土 +許 +步 +群 +廣 +石 +記 +需 +段 +研 +界 +拉 +林 +律 +叫 +且 +究 +觀 +越 +織 +裝 +影 +算 +低 +持 +音 +眾 +書 +布 +复 +容 +兒 +須 +際 +商 +非 +驗 +連 +斷 +深 +難 +近 +礦 +千 +週 +委 +素 +技 +備 +半 +辦 +青 +省 +列 +習 +響 +約 +支 +般 +史 +感 +勞 +便 +團 +往 +酸 +歷 +市 +克 +何 +除 +消 +構 +府 +稱 +太 +準 +精 +值 +號 +率 +族 +維 +劃 +選 +標 +寫 +存 +候 +毛 +親 +快 +效 +斯 +院 +查 +江 +型 +眼 +王 +按 +格 +養 +易 +置 +派 +層 +片 +始 +卻 +專 +狀 +育 +廠 +京 +識 +適 +屬 +圓 +包 +火 +住 +調 +滿 +縣 +局 +照 +參 +紅 +細 +引 +聽 +該 +鐵 +價 +嚴 +首 +底 +液 +官 +德 +隨 +病 +蘇 +失 +爾 +死 +講 +配 +女 +黃 +推 +顯 +談 +罪 +神 +藝 +呢 +席 +含 +企 +望 +密 +批 +營 +項 +防 +舉 +球 +英 +氧 +勢 +告 +李 +台 +落 +木 +幫 +輪 +破 +亞 +師 +圍 +注 +遠 +字 +材 +排 +供 +河 +態 +封 +另 +施 +減 +樹 +溶 +怎 +止 +案 +言 +士 +均 +武 +固 +葉 +魚 +波 +視 +僅 +費 +緊 +愛 +左 +章 +早 +朝 +害 +續 +輕 +服 +試 +食 +充 +兵 +源 +判 +護 +司 +足 +某 +練 +差 +致 +板 +田 +降 +黑 +犯 +負 +擊 +范 +繼 +興 +似 +餘 +堅 +曲 +輸 +修 +故 +城 +夫 +夠 +送 +筆 +船 +佔 +右 +財 +吃 +富 +春 +職 +覺 +漢 +畫 +功 +巴 +跟 +雖 +雜 +飛 +檢 +吸 +助 +昇 +陽 +互 +初 +創 +抗 +考 +投 +壞 +策 +古 +徑 +換 +未 +跑 +留 +鋼 +曾 +端 +責 +站 +簡 +述 +錢 +副 +盡 +帝 +射 +草 +衝 +承 +獨 +令 +限 +阿 +宣 +環 +雙 +請 +超 +微 +讓 +控 +州 +良 +軸 +找 +否 +紀 +益 +依 +優 +頂 +礎 +載 +倒 +房 +突 +坐 +粉 +敵 +略 +客 +袁 +冷 +勝 +絕 +析 +塊 +劑 +測 +絲 +協 +訴 +念 +陳 +仍 +羅 +鹽 +友 +洋 +錯 +苦 +夜 +刑 +移 +頻 +逐 +靠 +混 +母 +短 +皮 +終 +聚 +汽 +村 +雲 +哪 +既 +距 +衛 +停 +烈 +央 +察 +燒 +迅 +境 +若 +印 +洲 +刻 +括 +激 +孔 +搞 +甚 +室 +待 +核 +校 +散 +侵 +吧 +甲 +遊 +久 +菜 +味 +舊 +模 +湖 +貨 +損 +預 +阻 +毫 +普 +穩 +乙 +媽 +植 +息 +擴 +銀 +語 +揮 +酒 +守 +拿 +序 +紙 +醫 +缺 +雨 +嗎 +針 +劉 +啊 +急 +唱 +誤 +訓 +願 +審 +附 +獲 +茶 +鮮 +糧 +斤 +孩 +脫 +硫 +肥 +善 +龍 +演 +父 +漸 +血 +歡 +械 +掌 +歌 +沙 +剛 +攻 +謂 +盾 +討 +晚 +粒 +亂 +燃 +矛 +乎 +殺 +藥 +寧 +魯 +貴 +鐘 +煤 +讀 +班 +伯 +香 +介 +迫 +句 +豐 +培 +握 +蘭 +擔 +弦 +蛋 +沉 +假 +穿 +執 +答 +樂 +誰 +順 +煙 +縮 +徵 +臉 +喜 +松 +腳 +困 +異 +免 +背 +星 +福 +買 +染 +井 +概 +慢 +怕 +磁 +倍 +祖 +皇 +促 +靜 +補 +評 +翻 +肉 +踐 +尼 +衣 +寬 +揚 +棉 +希 +傷 +操 +垂 +秋 +宜 +氫 +套 +督 +振 +架 +亮 +末 +憲 +慶 +編 +牛 +觸 +映 +雷 +銷 +詩 +座 +居 +抓 +裂 +胞 +呼 +娘 +景 +威 +綠 +晶 +厚 +盟 +衡 +雞 +孫 +延 +危 +膠 +屋 +鄉 +臨 +陸 +顧 +掉 +呀 +燈 +歲 +措 +束 +耐 +劇 +玉 +趙 +跳 +哥 +季 +課 +凱 +胡 +額 +款 +紹 +卷 +齊 +偉 +蒸 +殖 +永 +宗 +苗 +川 +爐 +岩 +弱 +零 +楊 +奏 +沿 +露 +桿 +探 +滑 +鎮 +飯 +濃 +航 +懷 +趕 +庫 +奪 +伊 +靈 +稅 +途 +滅 +賽 +歸 +召 +鼓 +播 +盤 +裁 +險 +康 +唯 +錄 +菌 +純 +借 +糖 +蓋 +橫 +符 +私 +努 +堂 +域 +槍 +潤 +幅 +哈 +竟 +熟 +蟲 +澤 +腦 +壤 +碳 +歐 +遍 +側 +寨 +敢 +徹 +慮 +斜 +薄 +庭 +納 +彈 +飼 +伸 +折 +麥 +濕 +暗 +荷 +瓦 +塞 +床 +築 +惡 +戶 +訪 +塔 +奇 +透 +梁 +刀 +旋 +跡 +卡 +氯 +遇 +份 +毒 +泥 +退 +洗 +擺 +灰 +彩 +賣 +耗 +夏 +擇 +忙 +銅 +獻 +硬 +予 +繁 +圈 +雪 +函 +亦 +抽 +篇 +陣 +陰 +丁 +尺 +追 +堆 +雄 +迎 +泛 +爸 +樓 +避 +謀 +噸 +野 +豬 +旗 +累 +偏 +典 +館 +索 +秦 +脂 +潮 +爺 +豆 +忽 +托 +驚 +塑 +遺 +愈 +朱 +替 +纖 +粗 +傾 +尚 +痛 +楚 +謝 +奮 +購 +磨 +君 +池 +旁 +碎 +骨 +監 +捕 +弟 +暴 +割 +貫 +殊 +釋 +詞 +亡 +壁 +頓 +寶 +午 +塵 +聞 +揭 +炮 +殘 +冬 +橋 +婦 +警 +綜 +招 +吳 +付 +浮 +遭 +徐 +您 +搖 +谷 +贊 +箱 +隔 +訂 +男 +吹 +園 +紛 +唐 +敗 +宋 +玻 +巨 +耕 +坦 +榮 +閉 +灣 +鍵 +凡 +駐 +鍋 +救 +恩 +剝 +凝 +鹼 +齒 +截 +煉 +麻 +紡 +禁 +廢 +盛 +版 +緩 +淨 +睛 +昌 +婚 +涉 +筒 +嘴 +插 +岸 +朗 +莊 +街 +藏 +姑 +貿 +腐 +奴 +啦 +慣 +乘 +夥 +恢 +勻 +紗 +扎 +辯 +耳 +彪 +臣 +億 +璃 +抵 +脈 +秀 +薩 +俄 +網 +舞 +店 +噴 +縱 +寸 +汗 +掛 +洪 +賀 +閃 +柬 +爆 +烯 +津 +稻 +牆 +軟 +勇 +像 +滾 +厘 +蒙 +芳 +肯 +坡 +柱 +盪 +腿 +儀 +旅 +尾 +軋 +冰 +貢 +登 +黎 +削 +鑽 +勒 +逃 +障 +氨 +郭 +峰 +幣 +港 +伏 +軌 +畝 +畢 +擦 +莫 +刺 +浪 +秘 +援 +株 +健 +售 +股 +島 +甘 +泡 +睡 +童 +鑄 +湯 +閥 +休 +匯 +舍 +牧 +繞 +炸 +哲 +磷 +績 +朋 +淡 +尖 +啟 +陷 +柴 +呈 +徒 +顏 +淚 +稍 +忘 +泵 +藍 +拖 +洞 +授 +鏡 +辛 +壯 +鋒 +貧 +虛 +彎 +摩 +泰 +幼 +廷 +尊 +窗 +綱 +弄 +隸 +疑 +氏 +宮 +姐 +震 +瑞 +怪 +尤 +琴 +循 +描 +膜 +違 +夾 +腰 +緣 +珠 +窮 +森 +枝 +竹 +溝 +催 +繩 +憶 +邦 +剩 +幸 +漿 +欄 +擁 +牙 +貯 +禮 +濾 +鈉 +紋 +罷 +拍 +咱 +喊 +袖 +埃 +勤 +罰 +焦 +潛 +伍 +墨 +欲 +縫 +姓 +刊 +飽 +仿 +獎 +鋁 +鬼 +麗 +跨 +默 +挖 +鏈 +掃 +喝 +袋 +炭 +污 +幕 +諸 +弧 +勵 +梅 +奶 +潔 +災 +舟 +鑑 +苯 +訟 +抱 +毀 +懂 +寒 +智 +埔 +寄 +屆 +躍 +渡 +挑 +丹 +艱 +貝 +碰 +拔 +爹 +戴 +碼 +夢 +芽 +熔 +赤 +漁 +哭 +敬 +顆 +奔 +鉛 +仲 +虎 +稀 +妹 +乏 +珍 +申 +桌 +遵 +允 +隆 +螺 +倉 +魏 +銳 +曉 +氮 +兼 +隱 +礙 +赫 +撥 +忠 +肅 +缸 +牽 +搶 +博 +巧 +殼 +兄 +杜 +訊 +誠 +碧 +祥 +柯 +頁 +巡 +矩 +悲 +灌 +齡 +倫 +票 +尋 +桂 +鋪 +聖 +恐 +恰 +鄭 +趣 +抬 +荒 +騰 +貼 +柔 +滴 +猛 +闊 +輛 +妻 +填 +撤 +儲 +簽 +鬧 +擾 +紫 +砂 +遞 +戲 +吊 +陶 +伐 +餵 +療 +瓶 +婆 +撫 +臂 +摸 +忍 +蝦 +蠟 +鄰 +胸 +鞏 +擠 +偶 +棄 +槽 +勁 +乳 +鄧 +吉 +仁 +爛 +磚 +租 +烏 +艦 +伴 +瓜 +淺 +丙 +暫 +燥 +橡 +柳 +迷 +暖 +牌 +秧 +膽 +詳 +簧 +踏 +瓷 +譜 +呆 +賓 +糊 +洛 +輝 +憤 +競 +隙 +怒 +粘 +乃 +緒 +肩 +籍 +敏 +塗 +熙 +皆 +偵 +懸 +掘 +享 +糾 +醒 +狂 +鎖 +淀 +恨 +牲 +霸 +爬 +賞 +逆 +玩 +陵 +祝 +秒 +浙 +貌 +役 +彼 +悉 +鴨 +趨 +鳳 +晨 +畜 +輩 +秩 +卵 +署 +梯 +炎 +灘 +棋 +驅 +篩 +峽 +冒 +啥 +壽 +譯 +浸 +泉 +帽 +遲 +矽 +疆 +貸 +漏 +稿 +冠 +嫩 +脅 +芯 +牢 +叛 +蝕 +奧 +鳴 +嶺 +羊 +憑 +串 +塘 +繪 +酵 +融 +盆 +錫 +廟 +籌 +凍 +輔 +攝 +襲 +筋 +拒 +僚 +旱 +鉀 +鳥 +漆 +沈 +眉 +疏 +添 +棒 +穗 +硝 +韓 +逼 +扭 +僑 +涼 +挺 +碗 +栽 +炒 +杯 +患 +餾 +勸 +豪 +遼 +勃 +鴻 +旦 +吏 +拜 +狗 +埋 +輥 +掩 +飲 +搬 +罵 +辭 +勾 +扣 +估 +蔣 +絨 +霧 +丈 +朵 +姆 +擬 +宇 +輯 +陝 +雕 +償 +蓄 +崇 +剪 +倡 +廳 +咬 +駛 +薯 +刷 +斥 +番 +賦 +奉 +佛 +澆 +漫 +曼 +扇 +鈣 +桃 +扶 +仔 +返 +俗 +虧 +腔 +鞋 +棱 +覆 +框 +悄 +叔 +撞 +騙 +勘 +旺 +沸 +孤 +吐 +孟 +渠 +屈 +疾 +妙 +惜 +仰 +狠 +脹 +諧 +拋 +黴 +桑 +崗 +嘛 +衰 +盜 +滲 +臟 +賴 +湧 +甜 +曹 +閱 +肌 +哩 +厲 +烴 +緯 +毅 +昨 +偽 +症 +煮 +嘆 +釘 +搭 +莖 +籠 +酷 +偷 +弓 +錐 +恆 +傑 +坑 +鼻 +翼 +綸 +敘 +獄 +逮 +罐 +絡 +棚 +抑 +膨 +蔬 +寺 +驟 +穆 +冶 +枯 +冊 +屍 +凸 +紳 +坯 +犧 +焰 +轟 +欣 +晉 +瘦 +禦 +錠 +錦 +喪 +旬 +鍛 +壟 +搜 +撲 +邀 +亭 +酯 +邁 +舒 +脆 +酶 +閒 +憂 +酚 +頑 +羽 +漲 +卸 +仗 +陪 +闢 +懲 +杭 +姚 +肚 +捉 +飄 +漂 +昆 +欺 +吾 +郎 +烷 +汁 +呵 +飾 +蕭 +雅 +郵 +遷 +燕 +撒 +姻 +赴 +宴 +煩 +債 +帳 +斑 +鈴 +旨 +醇 +董 +餅 +雛 +姿 +拌 +傅 +腹 +妥 +揉 +賢 +拆 +歪 +葡 +胺 +丟 +浩 +徽 +昂 +墊 +擋 +覽 +貪 +慰 +繳 +汪 +慌 +馮 +諾 +姜 +誼 +兇 +劣 +誣 +耀 +昏 +躺 +盈 +騎 +喬 +溪 +叢 +盧 +抹 +悶 +諮 +刮 +駕 +纜 +悟 +摘 +鉺 +擲 +頗 +幻 +柄 +惠 +慘 +佳 +仇 +臘 +窩 +滌 +劍 +瞧 +堡 +潑 +蔥 +罩 +霍 +撈 +胎 +蒼 +濱 +倆 +捅 +湘 +砍 +霞 +邵 +萄 +瘋 +淮 +遂 +熊 +糞 +烘 +宿 +檔 +戈 +駁 +嫂 +裕 +徙 +箭 +捐 +腸 +撐 +曬 +辨 +殿 +蓮 +攤 +攪 +醬 +屏 +疫 +哀 +蔡 +堵 +沫 +皺 +暢 +疊 +閣 +萊 +敲 +轄 +鉤 +痕 +壩 +巷 +餓 +禍 +丘 +玄 +溜 +曰 +邏 +彭 +嘗 +卿 +妨 +艇 +吞 +韋 +怨 +矮 +歇 diff --git a/internal/html/assets/docs.html b/internal/html/assets/docs.html index af5e9e5..dbf510e 100644 --- a/internal/html/assets/docs.html +++ b/internal/html/assets/docs.html @@ -855,7 +855,7 @@

Recovery in Anonymous Mode

Advanced: Multilingual Bundles

- Each person can receive their bundle in their preferred language. Six languages are supported: English, Spanish, German, French, Slovenian and Portuguese. + Each person can receive their bundle in their preferred language. Seven languages are supported: English, Spanish, German, French, Slovenian, Portuguese and Chinese (Taiwan).

How It Works

diff --git a/internal/html/assets/maker.html b/internal/html/assets/maker.html index 8e47c3f..ef8d40f 100644 --- a/internal/html/assets/maker.html +++ b/internal/html/assets/maker.html @@ -280,6 +280,7 @@ + @@ -461,8 +462,8 @@

3 Generate Bu // Set initial language immediately (function() { const saved = localStorage.getItem('rememory-lang'); - const browserLang = navigator.language.split('-')[0]; - currentLang = saved || (['es', 'de', 'fr', 'pt'].includes(browserLang) ? browserLang : 'en'); + const detected = navigator.languages.find((language) => ['es', 'de', 'fr', 'sl', 'pt', 'zh-TW'].includes(language)); + currentLang = saved || detected || 'en'; })(); // Initialize language toggle buttons after DOM is ready diff --git a/internal/html/assets/recover.html b/internal/html/assets/recover.html index abf60cd..4741067 100644 --- a/internal/html/assets/recover.html +++ b/internal/html/assets/recover.html @@ -51,6 +51,7 @@ + @@ -185,8 +186,8 @@

3 Recover the // Set initial language immediately (before app.js runs) (function() { const saved = localStorage.getItem('rememory-lang'); - const browserLang = navigator.language.split('-')[0]; - currentLang = saved || (['es', 'de', 'fr', 'sl', 'pt'].includes(browserLang) ? browserLang : 'en'); + const detected = navigator.languages.find((language) => ['es', 'de', 'fr', 'sl', 'pt', 'zh-TW'].includes(language)); + currentLang = saved || detected || 'en'; })(); // Initialize language toggle buttons after DOM is ready diff --git a/internal/html/assets/src/create-app.ts b/internal/html/assets/src/create-app.ts index b6a4594..bea545f 100644 --- a/internal/html/assets/src/create-app.ts +++ b/internal/html/assets/src/create-app.ts @@ -323,7 +323,8 @@ declare let currentLang: string; { code: 'de', label: 'Deutsch' }, { code: 'fr', label: 'Français' }, { code: 'sl', label: 'Slovenščina' }, - { code: 'pt', label: 'Português' } + { code: 'pt', label: 'Português' }, + { code: 'zh-TW', label: '正體中文' } ]; const langOptionsHtml = langOptions.map(o => `` @@ -409,7 +410,7 @@ declare let currentLang: string; for (let k = 2; k <= n; k++) { const option = document.createElement('option'); option.value = String(k); - option.textContent = `${k} of ${n}`; + option.textContent = t('threshold_option', k, n); elements.thresholdSelect.appendChild(option); } diff --git a/internal/project/project.go b/internal/project/project.go index 7ef52f0..76e932b 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -20,7 +20,7 @@ const ( type Friend struct { Name string `yaml:"name"` Contact string `yaml:"contact,omitempty"` - Language string `yaml:"language,omitempty"` // Bundle language override (e.g. "en", "es", "de", "fr", "sl", "pt") + Language string `yaml:"language,omitempty"` // Bundle language override (e.g. "en", "es", "de", "fr", "sl", "pt", "zh-TW") } // ShareInfo stores information about a generated share. @@ -44,7 +44,7 @@ type Project struct { Created string `yaml:"created"` Threshold int `yaml:"threshold"` Anonymous bool `yaml:"anonymous,omitempty"` - Language string `yaml:"language,omitempty"` // Default bundle language (e.g. "en", "es", "de", "fr", "sl", "pt") + Language string `yaml:"language,omitempty"` // Default bundle language (e.g. "en", "es", "de", "fr", "sl", "pt", "zh-TW") Friends []Friend `yaml:"friends"` Sealed *Sealed `yaml:"sealed,omitempty"` diff --git a/internal/translations/maker/de.json b/internal/translations/maker/de.json index aae8827..7143aca 100644 --- a/internal/translations/maker/de.json +++ b/internal/translations/maker/de.json @@ -11,6 +11,7 @@ "num_shares_label": "Anzahl der Teile:", "add_friend": "Freund hinzufügen", "threshold_label": "Schwelle:", + "threshold_option": "{0} of {1}", "threshold_desc": "müssen zustimmen, um wiederherzustellen", "name_label": "Name", "contact_label": "Kontakt (optional)", diff --git a/internal/translations/maker/en.json b/internal/translations/maker/en.json index 32c442a..9ccf3bc 100644 --- a/internal/translations/maker/en.json +++ b/internal/translations/maker/en.json @@ -11,6 +11,7 @@ "num_shares_label": "Number of shares:", "add_friend": "Add Friend", "threshold_label": "Threshold:", + "threshold_option": "{0} of {1}", "threshold_desc": "must agree to recover", "name_label": "Name", "contact_label": "Contact info (optional)", diff --git a/internal/translations/maker/es.json b/internal/translations/maker/es.json index 6fa8574..cb5db63 100644 --- a/internal/translations/maker/es.json +++ b/internal/translations/maker/es.json @@ -11,6 +11,7 @@ "num_shares_label": "Número de partes:", "add_friend": "Agregar amigo", "threshold_label": "Umbral:", + "threshold_option": "{0} of {1}", "threshold_desc": "deben estar de acuerdo para recuperar", "name_label": "Nombre", "contact_label": "Contacto (opcional)", diff --git a/internal/translations/maker/fr.json b/internal/translations/maker/fr.json index 37904f6..089ef97 100644 --- a/internal/translations/maker/fr.json +++ b/internal/translations/maker/fr.json @@ -11,6 +11,7 @@ "num_shares_label": "Nombre de parts :", "add_friend": "Ajouter un ami", "threshold_label": "Seuil :", + "threshold_option": "{0} of {1}", "threshold_desc": "doivent se mettre d'accord", "name_label": "Nom", "contact_label": "Contact (optionnel)", diff --git a/internal/translations/maker/pt.json b/internal/translations/maker/pt.json index 84ea82f..9e3f046 100644 --- a/internal/translations/maker/pt.json +++ b/internal/translations/maker/pt.json @@ -11,6 +11,7 @@ "num_shares_label": "Número de partes:", "add_friend": "Adicionar Amigo", "threshold_label": "Número mínimo de partes:", + "threshold_option": "{0} of {1}", "threshold_desc": "amigos necessários para recuperar", "name_label": "Nome", "contact_label": "Informações de contato (opcional)", diff --git a/internal/translations/maker/sl.json b/internal/translations/maker/sl.json index 3df798f..f0a17d9 100644 --- a/internal/translations/maker/sl.json +++ b/internal/translations/maker/sl.json @@ -11,6 +11,7 @@ "num_shares_label": "Število delov:", "add_friend": "Dodaj prijatelja", "threshold_label": "Prag:", + "threshold_option": "{0} of {1}", "threshold_desc": "morajo soglašati za obnovitev", "name_label": "Ime", "contact_label": "Kontakt (neobvezno)", diff --git a/internal/translations/maker/zh-TW.json b/internal/translations/maker/zh-TW.json new file mode 100644 index 0000000..98d83d5 --- /dev/null +++ b/internal/translations/maker/zh-TW.json @@ -0,0 +1,64 @@ +{ + "loading": "載入中……", + "title": "ReMemory - 建立復原包", + "page_title": "建立復原包", + "page_description": "加入你的檔案、選擇你信任的人,設定幾個人同意才能復原。每個人都會拿到一個獨立的復原包。所有操作都在你的裝置裡完成,不會離開瀏覽器。", + "subtitle": "確保你的重要文件觸手可及,即使當你無法自己取得時。", + "step1_title": "朋友", + "friends_hint": "每位朋友都會保管你的一部分復原金鑰", + "anonymous_label": "匿名", + "anonymous_hint": "拿到復原包的人不會知道彼此是誰", + "num_shares_label": "份數:", + "add_friend": "新增朋友", + "threshold_label": "門檻:", + "threshold_option": "{0}/{1}", + "threshold_desc": "位朋友同意才會開始復原", + "name_label": "姓名", + "contact_label": "聯絡方式(選填)", + "import_summary": "從現有的 project.yml 匯入聯絡人", + "import_placeholder": "在這裡貼上你的 project.yml 內容……", + "import_btn": "匯入聯絡人", + "step2_title": "需要保護的檔案", + "files_drop": "拖放一個資料夾到這裡,或點擊以選擇檔案", + "files_hint": "這些檔案會先加密,再分割交給每位朋友", + "files_summary": "{0} 個檔案,共 {1}", + "step3_title": "產生復原包", + "generate_btn": "產生復原包", + "download_all_btn": "下載所有復原包", + "download_yaml_btn": "儲存 project.yml", + "works_offline": "可完全離線使用", + "need_help": "需要幫助?", + "download_cli": "從 GitHub 下載命令列工具", + "generating": "產生中……", + "archiving": "封存中……", + "encrypting": "加密中……", + "splitting": "分割金鑰……", + + "complete": "所有復原包已準備好。", + "error": "錯誤:{0}", + "validation_min_friends": "需要至少 2 位朋友", + "validation_friend_name": "朋友 {0}:請提供姓名", + "validation_no_files": "請選擇至少 1 個檔案", + "download": "下載", + "bundle_for": "{0} 的復原包", + "import_success": "已匯入 {0} 個聯絡人", + "import_error": "解析 YAML 失敗:{0}", + "error_title": "出了點問題", + "error_not_ready_title": "還沒準備好", + "error_not_ready_message": "復原包建立工具還在載入中。", + "error_not_ready_guidance": "請稍等片刻,然後重試。", + "error_import_title": "匯入失敗", + "error_import_guidance": "請檢查 YAML 的格式、縮排正確,並確保每位朋友都有填寫姓名。", + "error_min_friends_title": "朋友人數不足", + "error_min_friends_guidance": "由於需要分割成至少 2 份,請加入更多朋友以繼續。", + "error_generate_title": "復原包建立失敗", + "error_generate_guidance": "請確認每位朋友都有姓名,並且已經加入至少 1 個檔案。", + "action_try_again": "再試一次", + "validation_title": "有些欄位需要填寫", + "validation_message": "請先填寫所有已標示的欄位,再開始產生復原包。", + "validation_guidance": "每位朋友都需要填寫姓名,並且需要加入至少 1 個檔案。", + "language_label": "復原包語言", + + "custom_language_label": "自訂語言", + "remove": "移除" +} diff --git a/internal/translations/readme/de.json b/internal/translations/readme/de.json index a52637a..756bb43 100644 --- a/internal/translations/readme/de.json +++ b/internal/translations/readme/de.json @@ -56,6 +56,7 @@ "lang_de": "Deutsch", "lang_sl": "Slowenisch", "lang_pt": "Portugiesisch (Brasilien)", + "lang_zh-TW": "Chinesisch (Taiwan)", "machine_readable": "MASCHINENLESBARES FORMAT (auf der Webseite einfügen):", "metadata_footer": "METADATEN-FUSSZEILE (maschinenlesbar)", "qr_caption": "Scanne mit deiner Handykamera, um deinen Teil zu importieren", diff --git a/internal/translations/readme/en.json b/internal/translations/readme/en.json index 554c78f..1110e94 100644 --- a/internal/translations/readme/en.json +++ b/internal/translations/readme/en.json @@ -56,6 +56,7 @@ "lang_de": "German", "lang_sl": "Slovenian", "lang_pt": "Portuguese (Brazil)", + "lang_zh-TW": "Chinese (Taiwan)", "machine_readable": "MACHINE-READABLE FORMAT (paste on website):", "metadata_footer": "METADATA FOOTER (machine-parseable)", "qr_caption": "Scan with your phone camera to import your share", diff --git a/internal/translations/readme/es.json b/internal/translations/readme/es.json index 6a7e2d6..4f44ac2 100644 --- a/internal/translations/readme/es.json +++ b/internal/translations/readme/es.json @@ -56,6 +56,7 @@ "lang_de": "alemán", "lang_sl": "esloveno", "lang_pt": "Portugués (Brasil)", + "lang_zh-TW": "Chino (Taiwán)", "machine_readable": "FORMATO DE COMPUTADOR (pega esto):", "metadata_footer": "PIE DE METADATOS (parseable por máquina)", "qr_caption": "Escanea con la cámara de tu teléfono para importar tu parte", diff --git a/internal/translations/readme/fr.json b/internal/translations/readme/fr.json index 9e803f5..a8e86ac 100644 --- a/internal/translations/readme/fr.json +++ b/internal/translations/readme/fr.json @@ -56,6 +56,7 @@ "lang_de": "allemand", "lang_sl": "slovène", "lang_pt": "Portugais (Brésil)", + "lang_zh-TW": "Chinois (Taïwan)", "machine_readable": "FORMAT LISIBLE PAR MACHINE (collez sur le site web) :", "metadata_footer": "PIED DE PAGE MÉTADONNÉES (analysable par machine)", "qr_caption": "Scannez avec l'appareil photo de votre téléphone pour importer votre part", diff --git a/internal/translations/readme/pt.json b/internal/translations/readme/pt.json index 10ad37b..cb67d59 100644 --- a/internal/translations/readme/pt.json +++ b/internal/translations/readme/pt.json @@ -56,6 +56,7 @@ "lang_de": "Alemão", "lang_sl": "Esloveno", "lang_pt": "Português (Brasil)", + "lang_zh-TW": "Chinês (Taiwan)", "machine_readable": "FORMATO LÍGIVEL POR MÁQUINA (cole no site):", "metadata_footer": "RODAPÉ DE METADADOS (legivel por máquina)", "qr_caption": "Escaneie isso com a câmera do seu telefone para importar sua parte", diff --git a/internal/translations/readme/sl.json b/internal/translations/readme/sl.json index 7e4107d..8b6eb51 100644 --- a/internal/translations/readme/sl.json +++ b/internal/translations/readme/sl.json @@ -56,6 +56,7 @@ "lang_de": "nemščina", "lang_sl": "slovenščina", "lang_pt": "Portugalščina (Brazilija)", + "lang_zh-TW": "kitajščina (Tajvan)", "machine_readable": "STROJNO BERLJIV FORMAT (prilepite na spletno stran):", "metadata_footer": "NOGA Z METAPODATKI (strojno razčlenljiva)", "qr_caption": "Skenirajte s kamero telefona za uvoz vašega dela", diff --git a/internal/translations/readme/zh-TW.json b/internal/translations/readme/zh-TW.json new file mode 100644 index 0000000..73ff08e --- /dev/null +++ b/internal/translations/readme/zh-TW.json @@ -0,0 +1,66 @@ +{ + "title": "REMEMORY 復原包", + "for": "持有人:{0}", + "warning_cannot_alone": "這個檔案不能單獨使用", + "warning_need_friends": "你將需要下列持有人的協助。", + "warning_need_shares": "你需要把這個檔案和其他人手上的金鑰片段放在一起使用。", + "warning_confidential": "機密:請勿分享本文件", + "warning_keep_safe": "本文件含有你持有的金鑰片段,請妥善保管。", + "what_is_this": "這是什麼?", + "what_bundle_for": "這個復原包讓你能協助解鎖「{0}」的檔案。", + "what_one_of": "你是 {0} 位被託付這些金鑰片段的人之一。", + "what_threshold": "你們需要至少 {0} 位合作以解鎖檔案。", + "other_holders": "其他金鑰片段持有人(請聯絡以配合復原)", + "contact_label": "聯絡方式:{0}", + "sharing_title": "有人要求我的金鑰片段,我應該怎樣做?", + "sharing_verify": "首先,請確認要求是真實的。如果可以的話,自己聯絡原始檔案的擁有者進一步確認。", + "sharing_easiest": "提供協助最簡單的方法是傳送整個 ZIP 檔給他們。", + "sharing_readme_only": "如果你無法傳送 ZIP 檔,他們只需要你的 README.txt(這個檔案)。", + "sharing_words_phone": "如果你無法傳送任何檔案,可透過電話讀出下列復原詞組。", + "sharing_qr_mail": "下面的 QR 碼也可以列印出來,以郵寄方式送出。", + "recover_browser": "如何復原(主要方式:瀏覽器)", + "recover_step1": "1. 使用任何現代瀏覽器(Chrome、Firefox、Safari、Edge)開啟 recover.html", + "recover_share_loaded": "本復原包的解鎖工具專為你設計,你保管的金鑰片段已被預先載入。", + "recover_no_html": "如果你沒有 recover.html,請瀏覽 https://eljojo.github.io/rememory/recover", + "recover_step2": "2. 由本復原包載入加密封存檔(MANIFEST.age):", + "recover_step2_drag": "- 拖放到封存檔區域;或", + "recover_step2_click": "- 點擊以瀏覽並選擇封存檔", + "recover_step2_embedded": "2. 加密封存檔已經預先載入,無須再手動載入。", + "recover_step2_embedded_hint": "如果你用的是別的復原工具,請把這個復原包裡的 recover.html 拖放到那個工具裡。", + "recover_step3_contact": "3. 你會看到一份聯絡人清單,列出其他金鑰片段持有人", + "recover_step3_ask": "聯絡並請求他們傳送他們的 README.txt 給你", + "recover_step4": "4. 當你收到他們的 README.txt:", + "recover_step4_drag": "- 拖放到網頁;或", + "recover_step4_paste": "- 點擊剪貼簿按鈕以貼上他們的金鑰片段", + "recover_step5_checkmarks": "5. 當你加入金鑰片段,對應持有人的姓名旁邊會標上 ✅", + "recover_step5_auto": "當你收集了 {0} 份金鑰片段,復原程式會自動開始", + "recover_step6": "6. 下載已復原的檔案", + "recover_anon_step3": "3. 加入你收集到的其他金鑰片段", + "recover_anon_step3_drag": "- 拖放 README.txt 到網頁;或", + "recover_anon_step3_paste": "- 點擊剪貼簿按鈕以貼上金鑰片段", + "recover_anon_step4_auto": "4. 當你收集到 {0} 份金鑰片段,復原程序會自動開始", + "recover_anon_step5": "5. 下載已復原的檔案", + "recover_offline": "可完全離線使用,無須網路。", + "recover_cli": "如何復原(後備方式:命令列)", + "recover_cli_hint": "如果 recover.html 無法運作,下載命令列工具:", + "recover_cli_usage": "用法:rememory recover share1.txt share2.txt ... --manifest recover.html", + "your_share": "你的金鑰片段", + "recovery_words_title": "你的 {0} 個復原詞組:", + "recovery_words_title_lang": "你的 {0} 個復原詞組({1}):", + "recovery_words_title_english": "你的 {0} 個復原詞組(英文):", + "recovery_words_hint": "向負責復原的人讀出這些字詞,或輸入到 recover.html 的復原工具。", + "recovery_words_dual_hint": "不同語言的詞組清單編碼相同的資料,任一均可用於復原檔案。", + "lang_en": "英文", + "lang_es": "西班牙文", + "lang_fr": "法文", + "lang_de": "德文", + "lang_sl": "斯洛維尼亞文", + "lang_pt": "葡萄牙語(巴西)", + "lang_zh-TW": "正體中文", + "machine_readable": "機器可讀格式(貼到網頁上):", + "metadata_footer": "中繼資料註腳(機器可解析)", + "qr_caption": "掃描以匯入金鑰片段", + "recovery_rule": "復原條件", + "recovery_rule_count": "需要 {0}/{1} 位持有人", + "readme_filename": "README" +} diff --git a/internal/translations/recover/zh-TW.json b/internal/translations/recover/zh-TW.json new file mode 100644 index 0000000..aa9af15 --- /dev/null +++ b/internal/translations/recover/zh-TW.json @@ -0,0 +1,89 @@ +{ + "loading": "載入中……", + "title": "復原檔案", + "subtitle": "將朋友妥善保管的金鑰片段收集起來。", + "page_description": "每位朋友都有收到一個含有一部分復原金鑰的復原包。收集足夠的金鑰片段、加入加密封存檔,然後你的檔案會在瀏覽器解鎖。所有資料都不會離開你的裝置。", + "step1_title": "收集金鑰片段", + "step1_drop": "拖放 README.txt 到這裡,或點擊以選擇檔案", + "step1_hint": "每個文件含有一個人持有的金鑰片段", + "step2_title": "加入加密封存檔", + "step2_drop": "拖放 recover.html 或 MANIFEST.age 到這裡,或點擊以選擇檔案", + "step2_hint": "使用任何一位朋友的復原包裡的 recover.html 或 MANIFEST.age", + "step3_title": "復原檔案", + "decrypt_btn": "解鎖及復原", + "download_btn": "下載封存檔(.tar.gz)", + "no_manifest": "未加入封存檔", + "works_offline": "可完全離線使用", + "need_help": "需要幫助?", + "download_cli": "從 GitHub 下載命令列工具", + "need_more": "還需 {0} 個金鑰片段", + "need_more_one": "還需最後一個金鑰片段", + "ready": "一切準備就緒", + "shares_of": "{1} 之 {0} 個", + "remove": "移除", + "loaded": "已載入", + "manifest_loaded_bundle": "已從復原包載入", + "manifest_loaded_embedded": "已預先載入", + "manifest_loaded_html": "已從 recover.html 抽出", + "combining": "正在合併金鑰片段……", + "decrypting": "解鎖中……", + "reading": "正在開啟封存檔……", + "complete": "完成。已復原 {0} 個檔案。", + "error": "錯誤:{0}", + "paste_btn": "貼上金鑰片段或輸入復原詞組", + "paste_placeholder": "貼上收到的文字或輸入復原詞組……", + "paste_submit": "加入金鑰片段", + "your_share": "你的金鑰片段", + "contact_list": "聯絡其他人", + "contact_list_hint": "聯絡這些朋友,請他們幫忙提供金鑰片段", + "pasted_content": "貼上的文字", + "scan_btn": "掃描 QR 碼", + "scan_title": "掃描 QR 碼", + "scan_hint": "將你的鏡頭指向朋友 PDF 的 QR 碼", + "scan_camera_error": "無法使用攝影機", + "error_invalid_words_title": "復原詞組無效", + "error_invalid_words_guidance": "請檢查詞組是否有錯字,每個字詞應該跟復原指引中列出的一致。", + "error_title": "出了點問題", + "error_wasm_title": "無法載入復原工具", + "error_wasm_message": "復原模組無法被載入。", + "error_wasm_guidance": "請嘗試重新載入網頁,或使用其他瀏覽器(Chrome 或 Firefox)。你也可以使用命令列工具。", + "error_not_ready_title": "還沒準備好", + "error_not_ready_message": "復原工具還在載入中。", + "error_not_ready_guidance": "請稍等片刻,然後重試。", + "error_invalid_share_title": "金鑰片段格式無效", + "error_invalid_share_message": "檔案「{0}」不包含有效的金鑰片段。", + "error_invalid_share_guidance": "請使用復原包的 README.txt。檔案中應包含被「BEGIN REMEMORY SHARE」及「END REMEMORY SHARE」標記包圍的文字段。", + "error_no_share_title": "找不到金鑰片段", + "error_no_share_message": "檔案「{0}」不包含金鑰片段。", + "error_no_share_guidance": "每位朋友都有收到復原包,當中的 README.txt 含有他的金鑰片段。請檢查你的檔案是否正確。", + "error_duplicate_title": "重複的金鑰片段", + "error_duplicate_message": "第 {0} 個金鑰片段已被加入。", + "error_duplicate_guidance": "每個金鑰片段只能被使用一次,請加入其他朋友的金鑰片段。", + "error_file_read_title": "無法讀取檔案", + "error_file_read_message": "無法讀取檔案「{0}」。", + "error_file_read_guidance": "檔案可能已損壞或無法讀取,請嘗試再次下載或要求你的朋友再次傳送他們的復原包。", + "error_bundle_extract_title": "復原包無效", + "error_bundle_extract_message": "無法解壓縮復原包「{0}」。", + "error_bundle_extract_guidance": "這個 ZIP 檔案似乎不是有效的 ReMemory 復原包,請使用最初交給你的 bundle.zip。", + "error_wrong_manifest_title": "錯誤的檔案類型", + "error_wrong_manifest_message": "檔案「{0}」不是加密封存檔。", + "error_wrong_manifest_guidance": "請拖放任一朋友保管的復原包的 recover.html 或 MANIFEST.age。", + "error_html_no_manifest_guidance": "這個 recover.html 沒有嵌入加密封存檔,請嘗試使用其他朋友復原包中的 recover.html 或使用 MANIFEST.age。", + "error_paste_no_share_title": "貼上的文字沒有金鑰片段", + "error_paste_no_share_message": "貼上的文字不含有效的金鑰片段。", + "error_paste_no_share_guidance": "請從朋友的 README.txt 貼上完整內容,包括「BEGIN REMEMORY SHARE」及「END REMEMORY SHARE」標記。你也可以輸入或貼上復原詞組。", + "error_decrypt_title": "解鎖失敗", + "error_decrypt_message": "封存檔無法由提供的金鑰片段解鎖。", + "error_decrypt_guidance": "金鑰片段可能數量不足或不是用於這個封存檔,請確保所有金鑰片段都屬於這個封存檔。", + "error_decrypt_status": "解鎖失敗,請檢查你的金鑰片段並再試一次。", + "error_extract_title": "封存檔解壓縮失敗", + "error_extract_message": "無法開啟已解鎖的封存檔。", + "error_extract_guidance": "封存檔可能已損壞。如果你有 MANIFEST.age 的其他複本,請使用其再試一次。", + "error_extract_status": "解壓縮失敗,封存檔可能已損壞。", + "error_recovery_title": "復原失敗", + "error_recovery_guidance": "請檢查你有正確的金鑰片段及 MANIFEST.age。你可以用不同的金鑰片段再嘗試一次。", + "action_reload": "重新載入網頁", + "action_use_cli": "使用命令列工具", + "action_try_again": "再試一次", + "action_try_different_shares": "嘗試不同的金鑰片段" +} diff --git a/internal/translations/translations.go b/internal/translations/translations.go index 9a3b782..76ea879 100644 --- a/internal/translations/translations.go +++ b/internal/translations/translations.go @@ -18,7 +18,7 @@ var makerFS embed.FS var readmeFS embed.FS // Languages lists all supported language codes. -var Languages = []string{"en", "es", "de", "fr", "sl", "pt"} +var Languages = []string{"en", "es", "de", "fr", "sl", "pt", "zh-TW"} // GetTranslationsJS builds the JavaScript translations object for injection into HTML templates. // component must be "recover", "maker", or "readme". @@ -53,7 +53,8 @@ func GetTranslationsJS(component string) string { valJSON, _ := json.Marshal(check[k]) entries = append(entries, fmt.Sprintf(" %s: %s", string(keyJSON), string(valJSON))) } - parts = append(parts, fmt.Sprintf(" %s: {\n%s\n }", lang, strings.Join(entries, ",\n"))) + langJSON, _ := json.Marshal(lang) + parts = append(parts, fmt.Sprintf(" %s: {\n%s\n }", langJSON, strings.Join(entries, ",\n"))) } return "{\n" + strings.Join(parts, ",\n\n") + "\n }" diff --git a/internal/translations/translations_test.go b/internal/translations/translations_test.go index e5ffc01..5855b24 100644 --- a/internal/translations/translations_test.go +++ b/internal/translations/translations_test.go @@ -81,7 +81,7 @@ func TestGetTranslationsJSProducesValidJS(t *testing.T) { } for _, lang := range Languages { - if !strings.Contains(js, lang+": {") { + if !strings.Contains(js, "\""+lang+"\": {") { t.Errorf("GetTranslationsJS(%s) missing language %s", component, lang) } }