From a29701bb6ba2627dabc3afb57042525462c47960 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:36:09 +0800 Subject: [PATCH 01/19] Add support for Traditional Chinese locale --- frontend/check-locales.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/check-locales.cjs b/frontend/check-locales.cjs index 240b300ccf..d5ff22010c 100755 --- a/frontend/check-locales.cjs +++ b/frontend/check-locales.cjs @@ -20,6 +20,7 @@ const allLocales = [ ["zh", "zh-CN"], ["ko", "ko-KR"], ["bg", "bg-BG"], + ["zh", "zh-TW"], ]; const ignoreUnused = [ From 97ad2bc7ff27d681b9ed33c4d509cc22ae01a035 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:39:21 +0800 Subject: [PATCH 02/19] Change 'zh' to 'tw' for Traditional Chinese locale --- frontend/check-locales.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/check-locales.cjs b/frontend/check-locales.cjs index d5ff22010c..5785eb9690 100755 --- a/frontend/check-locales.cjs +++ b/frontend/check-locales.cjs @@ -20,7 +20,7 @@ const allLocales = [ ["zh", "zh-CN"], ["ko", "ko-KR"], ["bg", "bg-BG"], - ["zh", "zh-TW"], + ["tw", "zh-TW"], ]; const ignoreUnused = [ From 210f882de8f8e137081e07046e309d1a27edea92 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:44:58 +0800 Subject: [PATCH 03/19] Add support for Traditional Chinese language --- frontend/src/locale/IntlProvider.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index d38df0d457..a52460ca01 100755 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -11,6 +11,7 @@ import langRu from "./lang/ru.json"; import langSk from "./lang/sk.json"; import langVi from "./lang/vi.json"; import langZh from "./lang/zh.json"; +import langTw from "./lang/zh-tw.json"; import langKo from "./lang/ko.json"; import langBg from "./lang/bg.json"; @@ -31,6 +32,7 @@ const localeOptions = [ ["zh", "zh-CN", langZh], ["ko", "ko-KR", langKo], ["bg", "bg-BG", langBg], + ["tw", "zh-TW", langTw], ]; const loadMessages = (locale?: string): typeof langList & typeof langEn => { From f6e3f1aa00191ad15e8dcd523d88a78170465f86 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:48:00 +0800 Subject: [PATCH 04/19] Update Chinese locale to traditional and add Taiwan variant --- frontend/src/locale/src/lang-list.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json index 520eef2473..8186956c2d 100755 --- a/frontend/src/locale/src/lang-list.json +++ b/frontend/src/locale/src/lang-list.json @@ -18,7 +18,7 @@ "defaultMessage": "Slovenčina" }, "locale-zh-CN": { - "defaultMessage": "中文" + "defaultMessage": "中文-簡體" }, "locale-pl-PL": { "defaultMessage": "Polski" @@ -37,5 +37,8 @@ }, "locale-bg-BG": { "defaultMessage": "Български" + }, + "locale-bg-BG": { + "defaultMessage": "繁體中文-台灣正體" } } From 76bdfcdf19877e914a75dd958b20381795f52dcc Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:18:48 +0800 Subject: [PATCH 05/19] Add Traditional Chinese (Taiwan) translation file --- frontend/src/locale/src/tw.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/src/locale/src/tw.json diff --git a/frontend/src/locale/src/tw.json b/frontend/src/locale/src/tw.json new file mode 100644 index 0000000000..09990c6197 --- /dev/null +++ b/frontend/src/locale/src/tw.json @@ -0,0 +1 @@ +{"access-list":{"defaultMessage":"存取清單"},"access-list.access-count":{"defaultMessage":"{count} {count, plural, one {條規則} other {條規則}}"},"access-list.auth-count":{"defaultMessage":"{count} {count, plural, one {位使用者} other {位使用者}}"},"access-list.help-rules-last":{"defaultMessage":"當至少存在 1 條規則時,此拒絕所有規則將會新增到最後"},"access-list.help.rules-order":{"defaultMessage":"請注意,允許 (allow) 和拒絕 (deny) 指令將按照它們定義的順序執行。"},"access-list.pass-auth":{"defaultMessage":"將驗證傳遞給上游"},"access-list.public":{"defaultMessage":"公開存取"},"access-list.public.subtitle":{"defaultMessage":"無需基本驗證"},"access-list.rule-source.placeholder":{"defaultMessage":"192.168.1.100 或 192.168.1.0/24 或 2001:0db8::/32"},"access-list.satisfy-any":{"defaultMessage":"滿足任一條件"},"access-list.subtitle":{"defaultMessage":"{users} {users, plural, one {位使用者} other {位使用者}},{rules} {rules, plural, one {條規則} other {條規則}} - 建立時間:{date}"},"access-lists":{"defaultMessage":"存取清單"},"action.add":{"defaultMessage":"新增"},"action.add-location":{"defaultMessage":"新增位置規則 (Location)"},"action.allow":{"defaultMessage":"允許"},"action.close":{"defaultMessage":"關閉"},"action.delete":{"defaultMessage":"刪除"},"action.deny":{"defaultMessage":"拒絕"},"action.disable":{"defaultMessage":"停用"},"action.download":{"defaultMessage":"下載"},"action.edit":{"defaultMessage":"編輯"},"action.enable":{"defaultMessage":"啟用"},"action.permissions":{"defaultMessage":"權限"},"action.renew":{"defaultMessage":"續約"},"action.view-details":{"defaultMessage":"檢視詳細資訊"},"auditlogs":{"defaultMessage":"稽核日誌"},"auto":{"defaultMessage":"自動"},"cancel":{"defaultMessage":"取消"},"certificate":{"defaultMessage":"憑證"},"certificate.custom-certificate":{"defaultMessage":"憑證"},"certificate.custom-certificate-key":{"defaultMessage":"憑證金鑰"},"certificate.custom-intermediate":{"defaultMessage":"中繼憑證"},"certificate.in-use":{"defaultMessage":"使用中"},"certificate.none.subtitle":{"defaultMessage":"未指定憑證"},"certificate.none.subtitle.for-http":{"defaultMessage":"此主機將不使用 HTTPS"},"certificate.none.title":{"defaultMessage":"無"},"certificate.not-in-use":{"defaultMessage":"未使用"},"certificate.renew":{"defaultMessage":"續約憑證"},"certificates":{"defaultMessage":"憑證"},"certificates.custom":{"defaultMessage":"自訂憑證"},"certificates.custom.warning":{"defaultMessage":"不支援使用密碼保護的金鑰檔案。"},"certificates.dns.credentials":{"defaultMessage":"憑證檔案內容"},"certificates.dns.credentials-note":{"defaultMessage":"此外掛程式需要包含 API 權杖或其他提供者憑證的設定檔"},"certificates.dns.credentials-warning":{"defaultMessage":"此資料將以純文字形式儲存在資料庫和檔案中!"},"certificates.dns.propagation-seconds":{"defaultMessage":"傳播秒數"},"certificates.dns.propagation-seconds-note":{"defaultMessage":"留空則使用外掛程式預設值。等待 DNS 傳播的秒數。"},"certificates.dns.provider":{"defaultMessage":"DNS 提供者"},"certificates.dns.provider.placeholder":{"defaultMessage":"選擇提供者..."},"certificates.dns.warning":{"defaultMessage":"本節需要您具備一些關於 Certbot 及其 DNS 外掛程式的知識。請參閱相關外掛程式的說明文件。"},"certificates.http.reachability-404":{"defaultMessage":"在此網域下找到了伺服器,但它似乎不是 Nginx Proxy Manager。請確保您的網域指向 NPM 執行個體執行的 IP 位址。"},"certificates.http.reachability-failed-to-check":{"defaultMessage":"由於與 site24x7.com 通訊錯誤,無法檢查可連線性。"},"certificates.http.reachability-not-resolved":{"defaultMessage":"此網域下沒有可用的伺服器。請確保您的網域存在並指向 NPM 執行個體執行的 IP 位址,如有必要,請在路由器中轉送連接埠 80。"},"certificates.http.reachability-ok":{"defaultMessage":"您的伺服器可以連線,應該能夠建立憑證。"},"certificates.http.reachability-other":{"defaultMessage":"在此網域下找到了伺服器,但它傳回了意外的狀態碼 {code}。它是 NPM 伺服器嗎?請確保您的網域指向 NPM 執行個體執行的 IP 位址。"},"certificates.http.reachability-wrong-data":{"defaultMessage":"在此網域下找到了伺服器,但它傳回了意外的資料。它是 NPM 伺服器嗎?請確保您的網域指向 NPM 執行個體執行的 IP 位址。"},"certificates.http.test-results":{"defaultMessage":"測試結果"},"certificates.http.warning":{"defaultMessage":"這些網域必須已設定為指向此安裝位置。"},"certificates.request.subtitle":{"defaultMessage":"使用 Let's Encrypt"},"certificates.request.title":{"defaultMessage":"要求新憑證"},"column.access":{"defaultMessage":"存取"},"column.authorization":{"defaultMessage":"授權"},"column.authorizations":{"defaultMessage":"授權"},"column.custom-locations":{"defaultMessage":"自訂位置"},"column.destination":{"defaultMessage":"目的地"},"column.details":{"defaultMessage":"詳細資訊"},"column.email":{"defaultMessage":"電子郵件"},"column.event":{"defaultMessage":"事件"},"column.expires":{"defaultMessage":"到期時間"},"column.http-code":{"defaultMessage":"HTTP 代碼"},"column.incoming-port":{"defaultMessage":"傳入連接埠"},"column.name":{"defaultMessage":"名稱"},"column.protocol":{"defaultMessage":"通訊協定"},"column.provider":{"defaultMessage":"提供者"},"column.roles":{"defaultMessage":"角色"},"column.rules":{"defaultMessage":"規則"},"column.satisfy":{"defaultMessage":"滿足"},"column.satisfy-all":{"defaultMessage":"全部"},"column.satisfy-any":{"defaultMessage":"任一"},"column.scheme":{"defaultMessage":"配置"},"column.source":{"defaultMessage":"來源"},"column.ssl":{"defaultMessage":"SSL"},"column.status":{"defaultMessage":"狀態"},"created-on":{"defaultMessage":"建立時間:{date}"},"dashboard":{"defaultMessage":"儀表板"},"dead-host":{"defaultMessage":"404 主機"},"dead-hosts":{"defaultMessage":"404 主機"},"dead-hosts.count":{"defaultMessage":"{count} {count, plural, one {個 404 主機} other {個 404 主機}}"},"disabled":{"defaultMessage":"已停用"},"domain-names":{"defaultMessage":"網域名稱"},"domain-names.max":{"defaultMessage":"最多 {count} 個網域名稱"},"domain-names.placeholder":{"defaultMessage":"開始輸入以新增網域..."},"domain-names.wildcards-not-permitted":{"defaultMessage":"此類型不允許使用萬用字元"},"domain-names.wildcards-not-supported":{"defaultMessage":"此 CA 不支援萬用字元"},"domains.force-ssl":{"defaultMessage":"強制 SSL"},"domains.hsts-enabled":{"defaultMessage":"HSTS 已啟用"},"domains.hsts-subdomains":{"defaultMessage":"HSTS 子網域"},"domains.http2-support":{"defaultMessage":"HTTP/2 支援"},"domains.use-dns":{"defaultMessage":"使用 DNS 挑戰"},"email-address":{"defaultMessage":"電子郵件地址"},"empty-search":{"defaultMessage":"找不到結果"},"empty-subtitle":{"defaultMessage":"為何不建立一個呢?"},"enabled":{"defaultMessage":"已啟用"},"error.access.at-least-one":{"defaultMessage":"需要至少一個授權或存取規則"},"error.access.duplicate-usernames":{"defaultMessage":"授權使用者名稱必須唯一"},"error.invalid-auth":{"defaultMessage":"無效的電子郵件或密碼"},"error.invalid-domain":{"defaultMessage":"無效的網域:{domain}"},"error.invalid-email":{"defaultMessage":"無效的電子郵件地址"},"error.max-character-length":{"defaultMessage":"最大長度為 {max} {max, plural, one {個字元} other {個字元}}"},"error.max-domains":{"defaultMessage":"網域過多,最多為 {max} 個"},"error.maximum":{"defaultMessage":"最大值為 {max}"},"error.min-character-length":{"defaultMessage":"最小長度為 {min} {min, plural, one {個字元} other {個字元}}"},"error.minimum":{"defaultMessage":"最小值為 {min}"},"error.passwords-must-match":{"defaultMessage":"密碼必須相符"},"error.required":{"defaultMessage":"此為必填欄位"},"expires.on":{"defaultMessage":"到期時間:{date}"},"footer.github-fork":{"defaultMessage":"在 Github 上 Fork 我"},"host.flags.block-exploits":{"defaultMessage":"封鎖常見攻擊"},"host.flags.cache-assets":{"defaultMessage":"快取資源"},"host.flags.preserve-path":{"defaultMessage":"保留路徑"},"host.flags.protocols":{"defaultMessage":"通訊協定"},"host.flags.websockets-upgrade":{"defaultMessage":"Websockets 支援"},"host.forward-port":{"defaultMessage":"轉送連接埠"},"host.forward-scheme":{"defaultMessage":"配置"},"hosts":{"defaultMessage":"主機"},"http-only":{"defaultMessage":"僅 HTTP"},"lets-encrypt":{"defaultMessage":"Let's Encrypt"},"lets-encrypt-via-dns":{"defaultMessage":"Let's Encrypt 透過 DNS"},"lets-encrypt-via-http":{"defaultMessage":"Let's Encrypt 透過 HTTP"},"loading":{"defaultMessage":"載入中…"},"login.title":{"defaultMessage":"登入您的帳號"},"nginx-config.label":{"defaultMessage":"自訂 Nginx 設定"},"nginx-config.placeholder":{"defaultMessage":"# 在此輸入您的自訂 Nginx 設定,風險自負!"},"no-permission-error":{"defaultMessage":"您沒有權限檢視此內容。"},"notfound.action":{"defaultMessage":"返回首頁"},"notfound.content":{"defaultMessage":"很抱歉,您要尋找的頁面不存在"},"notfound.title":{"defaultMessage":"糟糕… 您剛剛找到了一個錯誤頁面"},"notification.error":{"defaultMessage":"錯誤"},"notification.object-deleted":{"defaultMessage":"{object} 已刪除"},"notification.object-disabled":{"defaultMessage":"{object} 已停用"},"notification.object-enabled":{"defaultMessage":"{object} 已啟用"},"notification.object-renewed":{"defaultMessage":"{object} 已續約"},"notification.object-saved":{"defaultMessage":"{object} 已儲存"},"notification.success":{"defaultMessage":"成功"},"object.actions-title":{"defaultMessage":"{object} #{id}"},"object.add":{"defaultMessage":"新增{object}"},"object.delete":{"defaultMessage":"刪除{object}"},"object.delete.content":{"defaultMessage":"您確定要刪除此{object}嗎?"},"object.edit":{"defaultMessage":"編輯{object}"},"object.empty":{"defaultMessage":"沒有{objects}"},"object.event.created":{"defaultMessage":"已建立{object}"},"object.event.deleted":{"defaultMessage":"已刪除{object}"},"object.event.disabled":{"defaultMessage":"已停用{object}"},"object.event.enabled":{"defaultMessage":"已啟用{object}"},"object.event.renewed":{"defaultMessage":"已續約{object}"},"object.event.updated":{"defaultMessage":"已更新{object}"},"offline":{"defaultMessage":"離線"},"online":{"defaultMessage":"線上"},"options":{"defaultMessage":"選項"},"password":{"defaultMessage":"密碼"},"password.generate":{"defaultMessage":"產生隨機密碼"},"password.hide":{"defaultMessage":"隱藏密碼"},"password.show":{"defaultMessage":"顯示密碼"},"permissions.hidden":{"defaultMessage":"已隱藏"},"permissions.manage":{"defaultMessage":"管理"},"permissions.view":{"defaultMessage":"僅檢視"},"permissions.visibility.all":{"defaultMessage":"所有項目"},"permissions.visibility.title":{"defaultMessage":"項目可見度"},"permissions.visibility.user":{"defaultMessage":"僅已建立的項目"},"proxy-host":{"defaultMessage":"代理主機"},"proxy-host.forward-host":{"defaultMessage":"轉送主機名稱 / IP"},"proxy-hosts":{"defaultMessage":"代理主機"},"proxy-hosts.count":{"defaultMessage":"{count} {count, plural, one {個代理主機} other {個代理主機}}"},"public":{"defaultMessage":"公開"},"redirection-host":{"defaultMessage":"重新導向主機"},"redirection-host.forward-domain":{"defaultMessage":"轉送網域"},"redirection-host.forward-http-code":{"defaultMessage":"HTTP 代碼"},"redirection-hosts":{"defaultMessage":"重新導向主機"},"redirection-hosts.count":{"defaultMessage":"{count} {count, plural, one {個重新導向主機} other {個重新導向主機}}"},"redirection-hosts.http-code.300":{"defaultMessage":"300 多個選擇"},"redirection-hosts.http-code.301":{"defaultMessage":"301 永久移動"},"redirection-hosts.http-code.302":{"defaultMessage":"302 暫時移動"},"redirection-hosts.http-code.303":{"defaultMessage":"303 參見其他"},"redirection-hosts.http-code.307":{"defaultMessage":"307 暫時重新導向"},"redirection-hosts.http-code.308":{"defaultMessage":"308 永久重新導向"},"role.admin":{"defaultMessage":"管理員"},"role.standard-user":{"defaultMessage":"標準使用者"},"save":{"defaultMessage":"儲存"},"setting":{"defaultMessage":"設定"},"settings":{"defaultMessage":"設定"},"settings.default-site":{"defaultMessage":"預設網站"},"settings.default-site.404":{"defaultMessage":"404 頁面"},"settings.default-site.444":{"defaultMessage":"無回應 (444)"},"settings.default-site.congratulations":{"defaultMessage":"恭喜頁面"},"settings.default-site.description":{"defaultMessage":"當 Nginx 遇到未知主機時顯示的內容"},"settings.default-site.html":{"defaultMessage":"自訂 HTML"},"settings.default-site.html.placeholder":{"defaultMessage":""},"settings.default-site.redirect":{"defaultMessage":"重新導向"},"setup.preamble":{"defaultMessage":"從建立您的管理員帳號開始。"},"setup.title":{"defaultMessage":"歡迎!"},"sign-in":{"defaultMessage":"登入"},"ssl-certificate":{"defaultMessage":"SSL 憑證"},"stream":{"defaultMessage":"串流"},"stream.forward-host":{"defaultMessage":"轉送主機"},"stream.forward-host.placeholder":{"defaultMessage":"example.com 或 10.0.0.1 或 2001:db8:3333:4444:5555:6666:7777:8888"},"stream.incoming-port":{"defaultMessage":"傳入連接埠"},"streams":{"defaultMessage":"串流"},"streams.count":{"defaultMessage":"{count} {count, plural, one {個串流} other {個串流}}"},"streams.tcp":{"defaultMessage":"TCP"},"streams.udp":{"defaultMessage":"UDP"},"test":{"defaultMessage":"測試"},"update-available":{"defaultMessage":"有可用的更新:{latestVersion}"},"user":{"defaultMessage":"使用者"},"user.change-password":{"defaultMessage":"變更密碼"},"user.confirm-password":{"defaultMessage":"確認密碼"},"user.current-password":{"defaultMessage":"目前密碼"},"user.edit-profile":{"defaultMessage":"編輯個人資料"},"user.full-name":{"defaultMessage":"全名"},"user.login-as":{"defaultMessage":"以 {name} 身分登入"},"user.logout":{"defaultMessage":"登出"},"user.new-password":{"defaultMessage":"新密碼"},"user.nickname":{"defaultMessage":"暱稱"},"user.set-password":{"defaultMessage":"設定密碼"},"user.set-permissions":{"defaultMessage":"為 {name} 設定權限"},"user.switch-dark":{"defaultMessage":"切換至深色模式"},"user.switch-light":{"defaultMessage":"切換至淺色模式"},"username":{"defaultMessage":"使用者名稱"},"users":{"defaultMessage":"使用者"}} \ No newline at end of file From 07b2dc6be3ef12b0a976e4720e33c498cc2c5542 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:20:00 +0800 Subject: [PATCH 06/19] Add Traditional Chinese HelpDoc: AccessLists --- frontend/src/locale/src/HelpDoc/tw/AccessLists.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 frontend/src/locale/src/HelpDoc/tw/AccessLists.md diff --git a/frontend/src/locale/src/HelpDoc/tw/AccessLists.md b/frontend/src/locale/src/HelpDoc/tw/AccessLists.md new file mode 100644 index 0000000000..56dac8f4c9 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tw/AccessLists.md @@ -0,0 +1,7 @@ +## 什麼是存取清單? + +存取清單提供了特定客戶端 IP 位址的黑名單或白名單,以及透過基本 HTTP 驗證對代理主機的驗證。 + +您可以為一個存取清單設定多個客戶端規則、使用者名稱和密碼,然後將其套用於代理主機。 + +這對那些沒有內建驗證機制的轉送網路服務,或您想保護其免受未知客戶端存取時最為實用。 \ No newline at end of file From ccfa8480aff14eecc323924fc5b9069bdec58926 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:21:03 +0800 Subject: [PATCH 07/19] Add Traditional Chinese help doc for Streams --- frontend/src/locale/src/HelpDoc/tw/Streams.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 frontend/src/locale/src/HelpDoc/tw/Streams.md diff --git a/frontend/src/locale/src/HelpDoc/tw/Streams.md b/frontend/src/locale/src/HelpDoc/tw/Streams.md new file mode 100644 index 0000000000..67b117c2d1 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tw/Streams.md @@ -0,0 +1,5 @@ +## 什麼是串流? + +串流是 Nginx 相對較新的功能,可以直接轉送 TCP/UDP 流量到網路上的另一台電腦。 + +如果您正在執行遊戲伺服器、FTP 或 SSH 伺服器,這個功能就會很實用。 \ No newline at end of file From 5fb01ccbf079ff9eba81e6a418fd06d634922040 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:23:09 +0800 Subject: [PATCH 08/19] Fix duplicate locale-bg-BG key, change to locale-zh-TW --- frontend/src/locale/src/lang-list.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json index 8186956c2d..8b4893c421 100755 --- a/frontend/src/locale/src/lang-list.json +++ b/frontend/src/locale/src/lang-list.json @@ -38,7 +38,7 @@ "locale-bg-BG": { "defaultMessage": "Български" }, - "locale-bg-BG": { - "defaultMessage": "繁體中文-台灣正體" + "locale-zh-TW": { + "defaultMessage": "繁體中文" } } From 910b71f61bbc6e96d6b15cb7cca63be27cee5ba9 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:24:55 +0800 Subject: [PATCH 09/19] Add remaining Traditional Chinese help documentation files --- .../src/locale/src/HelpDoc/tw/Certificates.md | 21 +++++++++++++++++++ .../src/locale/src/HelpDoc/tw/DeadHosts.md | 7 +++++++ .../src/locale/src/HelpDoc/tw/ProxyHosts.md | 7 +++++++ .../locale/src/HelpDoc/tw/RedirectionHosts.md | 5 +++++ frontend/src/locale/src/HelpDoc/tw/index.ts | 6 ++++++ 5 files changed, 46 insertions(+) create mode 100644 frontend/src/locale/src/HelpDoc/tw/Certificates.md create mode 100644 frontend/src/locale/src/HelpDoc/tw/DeadHosts.md create mode 100644 frontend/src/locale/src/HelpDoc/tw/ProxyHosts.md create mode 100644 frontend/src/locale/src/HelpDoc/tw/RedirectionHosts.md create mode 100644 frontend/src/locale/src/HelpDoc/tw/index.ts diff --git a/frontend/src/locale/src/HelpDoc/tw/Certificates.md b/frontend/src/locale/src/HelpDoc/tw/Certificates.md new file mode 100644 index 0000000000..cb9c79c955 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tw/Certificates.md @@ -0,0 +1,21 @@ +## 憑證說明 + +### HTTP 憑證 + +HTTP 驗證的憑證表示 Let's Encrypt 伺服器將嘗試透過 HTTP(而非 HTTPS!)存取您的網域,如果成功,它們將為您核發憑證。 + +使用此方法時,您必須為您的網域建立一個可透過 HTTP 存取並指向此 Nginx 安裝的代理主機。在取得憑證後,您可以修改該代理主機,使其也使用此憑證處理 HTTPS 連線。然而,為了憑證能夠續約,該代理主機仍需設定為支援 HTTP 存取。 + +此程序_不支援_萬用字元網域。 + +### DNS 憑證 + +DNS 驗證的憑證要求您使用一個 DNS 服務提供者外掛程式。該 DNS 服務提供者將用於在您的網域下建立暫時記錄,隨後 Let's Encrypt 將查詢這些記錄以確認您是網域所有者,如果成功,它們將為您核發憑證。 + +要求此類憑證前,您無需預先建立代理主機,也無需將您的代理主機設定為支援 HTTP 存取。 + +此程序_支援_萬用字元網域。 + +### 自訂憑證 + +使用此選項上傳您自己的 SSL 憑證,該憑證由您自己的憑證授權單位提供。 \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/tw/DeadHosts.md b/frontend/src/locale/src/HelpDoc/tw/DeadHosts.md new file mode 100644 index 0000000000..24655fb6f8 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tw/DeadHosts.md @@ -0,0 +1,7 @@ +## 什麼是 404 主機? + +404 主機是一個簡單的主機設定,顯示 404 頁面。 + +當您的網域被列入搜尋引擎,而您想提供一個更好的錯誤頁面,或特別是要告訴搜尋索引器網域頁面不再存在時,這可能很實用。 + +擁有這種主機的另一個好處是可以追蹤點擊它的日誌並檢視存取來源。 \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/tw/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/tw/ProxyHosts.md new file mode 100644 index 0000000000..5863322504 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tw/ProxyHosts.md @@ -0,0 +1,7 @@ +## 什麼是代理主機? + +代理主機是您想要轉送網路應用程式的主機。 + +代理主機可以為沒有 SSL 服務的網路應用程式提供 SSL 服務(選用)。 + +代理主機是 Nginx Proxy Manager 最常見的用途之一。 \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/tw/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/tw/RedirectionHosts.md new file mode 100644 index 0000000000..5c48d72e2d --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tw/RedirectionHosts.md @@ -0,0 +1,5 @@ +## 什麼是重新導向? + +重新導向是將進入網域的請求推送到另一個網域。 + +使用這種類型主機最常見的原因是當您的網站變更了網域,但您仍然有連結指向舊網域的應用程式。 \ No newline at end of file diff --git a/frontend/src/locale/src/HelpDoc/tw/index.ts b/frontend/src/locale/src/HelpDoc/tw/index.ts new file mode 100644 index 0000000000..d0178a5d30 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/tw/index.ts @@ -0,0 +1,6 @@ +export * as AccessLists from "./AccessLists.md"; +export * as Certificates from "./Certificates.md"; +export * as DeadHosts from "./DeadHosts.md"; +export * as ProxyHosts from "./ProxyHosts.md"; +export * as RedirectionHosts from "./RedirectionHosts.md"; +export * as Streams from "./Streams.md"; \ No newline at end of file From e78163d63f4b832e0d0205499e85caeb4f915486 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:31:24 +0800 Subject: [PATCH 10/19] Update Traditional Chinese locale with proper formatting --- frontend/src/locale/src/tw.json | 172 +++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/frontend/src/locale/src/tw.json b/frontend/src/locale/src/tw.json index 09990c6197..5dd5bf8bfe 100644 --- a/frontend/src/locale/src/tw.json +++ b/frontend/src/locale/src/tw.json @@ -1 +1,171 @@ -{"access-list":{"defaultMessage":"存取清單"},"access-list.access-count":{"defaultMessage":"{count} {count, plural, one {條規則} other {條規則}}"},"access-list.auth-count":{"defaultMessage":"{count} {count, plural, one {位使用者} other {位使用者}}"},"access-list.help-rules-last":{"defaultMessage":"當至少存在 1 條規則時,此拒絕所有規則將會新增到最後"},"access-list.help.rules-order":{"defaultMessage":"請注意,允許 (allow) 和拒絕 (deny) 指令將按照它們定義的順序執行。"},"access-list.pass-auth":{"defaultMessage":"將驗證傳遞給上游"},"access-list.public":{"defaultMessage":"公開存取"},"access-list.public.subtitle":{"defaultMessage":"無需基本驗證"},"access-list.rule-source.placeholder":{"defaultMessage":"192.168.1.100 或 192.168.1.0/24 或 2001:0db8::/32"},"access-list.satisfy-any":{"defaultMessage":"滿足任一條件"},"access-list.subtitle":{"defaultMessage":"{users} {users, plural, one {位使用者} other {位使用者}},{rules} {rules, plural, one {條規則} other {條規則}} - 建立時間:{date}"},"access-lists":{"defaultMessage":"存取清單"},"action.add":{"defaultMessage":"新增"},"action.add-location":{"defaultMessage":"新增位置規則 (Location)"},"action.allow":{"defaultMessage":"允許"},"action.close":{"defaultMessage":"關閉"},"action.delete":{"defaultMessage":"刪除"},"action.deny":{"defaultMessage":"拒絕"},"action.disable":{"defaultMessage":"停用"},"action.download":{"defaultMessage":"下載"},"action.edit":{"defaultMessage":"編輯"},"action.enable":{"defaultMessage":"啟用"},"action.permissions":{"defaultMessage":"權限"},"action.renew":{"defaultMessage":"續約"},"action.view-details":{"defaultMessage":"檢視詳細資訊"},"auditlogs":{"defaultMessage":"稽核日誌"},"auto":{"defaultMessage":"自動"},"cancel":{"defaultMessage":"取消"},"certificate":{"defaultMessage":"憑證"},"certificate.custom-certificate":{"defaultMessage":"憑證"},"certificate.custom-certificate-key":{"defaultMessage":"憑證金鑰"},"certificate.custom-intermediate":{"defaultMessage":"中繼憑證"},"certificate.in-use":{"defaultMessage":"使用中"},"certificate.none.subtitle":{"defaultMessage":"未指定憑證"},"certificate.none.subtitle.for-http":{"defaultMessage":"此主機將不使用 HTTPS"},"certificate.none.title":{"defaultMessage":"無"},"certificate.not-in-use":{"defaultMessage":"未使用"},"certificate.renew":{"defaultMessage":"續約憑證"},"certificates":{"defaultMessage":"憑證"},"certificates.custom":{"defaultMessage":"自訂憑證"},"certificates.custom.warning":{"defaultMessage":"不支援使用密碼保護的金鑰檔案。"},"certificates.dns.credentials":{"defaultMessage":"憑證檔案內容"},"certificates.dns.credentials-note":{"defaultMessage":"此外掛程式需要包含 API 權杖或其他提供者憑證的設定檔"},"certificates.dns.credentials-warning":{"defaultMessage":"此資料將以純文字形式儲存在資料庫和檔案中!"},"certificates.dns.propagation-seconds":{"defaultMessage":"傳播秒數"},"certificates.dns.propagation-seconds-note":{"defaultMessage":"留空則使用外掛程式預設值。等待 DNS 傳播的秒數。"},"certificates.dns.provider":{"defaultMessage":"DNS 提供者"},"certificates.dns.provider.placeholder":{"defaultMessage":"選擇提供者..."},"certificates.dns.warning":{"defaultMessage":"本節需要您具備一些關於 Certbot 及其 DNS 外掛程式的知識。請參閱相關外掛程式的說明文件。"},"certificates.http.reachability-404":{"defaultMessage":"在此網域下找到了伺服器,但它似乎不是 Nginx Proxy Manager。請確保您的網域指向 NPM 執行個體執行的 IP 位址。"},"certificates.http.reachability-failed-to-check":{"defaultMessage":"由於與 site24x7.com 通訊錯誤,無法檢查可連線性。"},"certificates.http.reachability-not-resolved":{"defaultMessage":"此網域下沒有可用的伺服器。請確保您的網域存在並指向 NPM 執行個體執行的 IP 位址,如有必要,請在路由器中轉送連接埠 80。"},"certificates.http.reachability-ok":{"defaultMessage":"您的伺服器可以連線,應該能夠建立憑證。"},"certificates.http.reachability-other":{"defaultMessage":"在此網域下找到了伺服器,但它傳回了意外的狀態碼 {code}。它是 NPM 伺服器嗎?請確保您的網域指向 NPM 執行個體執行的 IP 位址。"},"certificates.http.reachability-wrong-data":{"defaultMessage":"在此網域下找到了伺服器,但它傳回了意外的資料。它是 NPM 伺服器嗎?請確保您的網域指向 NPM 執行個體執行的 IP 位址。"},"certificates.http.test-results":{"defaultMessage":"測試結果"},"certificates.http.warning":{"defaultMessage":"這些網域必須已設定為指向此安裝位置。"},"certificates.request.subtitle":{"defaultMessage":"使用 Let's Encrypt"},"certificates.request.title":{"defaultMessage":"要求新憑證"},"column.access":{"defaultMessage":"存取"},"column.authorization":{"defaultMessage":"授權"},"column.authorizations":{"defaultMessage":"授權"},"column.custom-locations":{"defaultMessage":"自訂位置"},"column.destination":{"defaultMessage":"目的地"},"column.details":{"defaultMessage":"詳細資訊"},"column.email":{"defaultMessage":"電子郵件"},"column.event":{"defaultMessage":"事件"},"column.expires":{"defaultMessage":"到期時間"},"column.http-code":{"defaultMessage":"HTTP 代碼"},"column.incoming-port":{"defaultMessage":"傳入連接埠"},"column.name":{"defaultMessage":"名稱"},"column.protocol":{"defaultMessage":"通訊協定"},"column.provider":{"defaultMessage":"提供者"},"column.roles":{"defaultMessage":"角色"},"column.rules":{"defaultMessage":"規則"},"column.satisfy":{"defaultMessage":"滿足"},"column.satisfy-all":{"defaultMessage":"全部"},"column.satisfy-any":{"defaultMessage":"任一"},"column.scheme":{"defaultMessage":"配置"},"column.source":{"defaultMessage":"來源"},"column.ssl":{"defaultMessage":"SSL"},"column.status":{"defaultMessage":"狀態"},"created-on":{"defaultMessage":"建立時間:{date}"},"dashboard":{"defaultMessage":"儀表板"},"dead-host":{"defaultMessage":"404 主機"},"dead-hosts":{"defaultMessage":"404 主機"},"dead-hosts.count":{"defaultMessage":"{count} {count, plural, one {個 404 主機} other {個 404 主機}}"},"disabled":{"defaultMessage":"已停用"},"domain-names":{"defaultMessage":"網域名稱"},"domain-names.max":{"defaultMessage":"最多 {count} 個網域名稱"},"domain-names.placeholder":{"defaultMessage":"開始輸入以新增網域..."},"domain-names.wildcards-not-permitted":{"defaultMessage":"此類型不允許使用萬用字元"},"domain-names.wildcards-not-supported":{"defaultMessage":"此 CA 不支援萬用字元"},"domains.force-ssl":{"defaultMessage":"強制 SSL"},"domains.hsts-enabled":{"defaultMessage":"HSTS 已啟用"},"domains.hsts-subdomains":{"defaultMessage":"HSTS 子網域"},"domains.http2-support":{"defaultMessage":"HTTP/2 支援"},"domains.use-dns":{"defaultMessage":"使用 DNS 挑戰"},"email-address":{"defaultMessage":"電子郵件地址"},"empty-search":{"defaultMessage":"找不到結果"},"empty-subtitle":{"defaultMessage":"為何不建立一個呢?"},"enabled":{"defaultMessage":"已啟用"},"error.access.at-least-one":{"defaultMessage":"需要至少一個授權或存取規則"},"error.access.duplicate-usernames":{"defaultMessage":"授權使用者名稱必須唯一"},"error.invalid-auth":{"defaultMessage":"無效的電子郵件或密碼"},"error.invalid-domain":{"defaultMessage":"無效的網域:{domain}"},"error.invalid-email":{"defaultMessage":"無效的電子郵件地址"},"error.max-character-length":{"defaultMessage":"最大長度為 {max} {max, plural, one {個字元} other {個字元}}"},"error.max-domains":{"defaultMessage":"網域過多,最多為 {max} 個"},"error.maximum":{"defaultMessage":"最大值為 {max}"},"error.min-character-length":{"defaultMessage":"最小長度為 {min} {min, plural, one {個字元} other {個字元}}"},"error.minimum":{"defaultMessage":"最小值為 {min}"},"error.passwords-must-match":{"defaultMessage":"密碼必須相符"},"error.required":{"defaultMessage":"此為必填欄位"},"expires.on":{"defaultMessage":"到期時間:{date}"},"footer.github-fork":{"defaultMessage":"在 Github 上 Fork 我"},"host.flags.block-exploits":{"defaultMessage":"封鎖常見攻擊"},"host.flags.cache-assets":{"defaultMessage":"快取資源"},"host.flags.preserve-path":{"defaultMessage":"保留路徑"},"host.flags.protocols":{"defaultMessage":"通訊協定"},"host.flags.websockets-upgrade":{"defaultMessage":"Websockets 支援"},"host.forward-port":{"defaultMessage":"轉送連接埠"},"host.forward-scheme":{"defaultMessage":"配置"},"hosts":{"defaultMessage":"主機"},"http-only":{"defaultMessage":"僅 HTTP"},"lets-encrypt":{"defaultMessage":"Let's Encrypt"},"lets-encrypt-via-dns":{"defaultMessage":"Let's Encrypt 透過 DNS"},"lets-encrypt-via-http":{"defaultMessage":"Let's Encrypt 透過 HTTP"},"loading":{"defaultMessage":"載入中…"},"login.title":{"defaultMessage":"登入您的帳號"},"nginx-config.label":{"defaultMessage":"自訂 Nginx 設定"},"nginx-config.placeholder":{"defaultMessage":"# 在此輸入您的自訂 Nginx 設定,風險自負!"},"no-permission-error":{"defaultMessage":"您沒有權限檢視此內容。"},"notfound.action":{"defaultMessage":"返回首頁"},"notfound.content":{"defaultMessage":"很抱歉,您要尋找的頁面不存在"},"notfound.title":{"defaultMessage":"糟糕… 您剛剛找到了一個錯誤頁面"},"notification.error":{"defaultMessage":"錯誤"},"notification.object-deleted":{"defaultMessage":"{object} 已刪除"},"notification.object-disabled":{"defaultMessage":"{object} 已停用"},"notification.object-enabled":{"defaultMessage":"{object} 已啟用"},"notification.object-renewed":{"defaultMessage":"{object} 已續約"},"notification.object-saved":{"defaultMessage":"{object} 已儲存"},"notification.success":{"defaultMessage":"成功"},"object.actions-title":{"defaultMessage":"{object} #{id}"},"object.add":{"defaultMessage":"新增{object}"},"object.delete":{"defaultMessage":"刪除{object}"},"object.delete.content":{"defaultMessage":"您確定要刪除此{object}嗎?"},"object.edit":{"defaultMessage":"編輯{object}"},"object.empty":{"defaultMessage":"沒有{objects}"},"object.event.created":{"defaultMessage":"已建立{object}"},"object.event.deleted":{"defaultMessage":"已刪除{object}"},"object.event.disabled":{"defaultMessage":"已停用{object}"},"object.event.enabled":{"defaultMessage":"已啟用{object}"},"object.event.renewed":{"defaultMessage":"已續約{object}"},"object.event.updated":{"defaultMessage":"已更新{object}"},"offline":{"defaultMessage":"離線"},"online":{"defaultMessage":"線上"},"options":{"defaultMessage":"選項"},"password":{"defaultMessage":"密碼"},"password.generate":{"defaultMessage":"產生隨機密碼"},"password.hide":{"defaultMessage":"隱藏密碼"},"password.show":{"defaultMessage":"顯示密碼"},"permissions.hidden":{"defaultMessage":"已隱藏"},"permissions.manage":{"defaultMessage":"管理"},"permissions.view":{"defaultMessage":"僅檢視"},"permissions.visibility.all":{"defaultMessage":"所有項目"},"permissions.visibility.title":{"defaultMessage":"項目可見度"},"permissions.visibility.user":{"defaultMessage":"僅已建立的項目"},"proxy-host":{"defaultMessage":"代理主機"},"proxy-host.forward-host":{"defaultMessage":"轉送主機名稱 / IP"},"proxy-hosts":{"defaultMessage":"代理主機"},"proxy-hosts.count":{"defaultMessage":"{count} {count, plural, one {個代理主機} other {個代理主機}}"},"public":{"defaultMessage":"公開"},"redirection-host":{"defaultMessage":"重新導向主機"},"redirection-host.forward-domain":{"defaultMessage":"轉送網域"},"redirection-host.forward-http-code":{"defaultMessage":"HTTP 代碼"},"redirection-hosts":{"defaultMessage":"重新導向主機"},"redirection-hosts.count":{"defaultMessage":"{count} {count, plural, one {個重新導向主機} other {個重新導向主機}}"},"redirection-hosts.http-code.300":{"defaultMessage":"300 多個選擇"},"redirection-hosts.http-code.301":{"defaultMessage":"301 永久移動"},"redirection-hosts.http-code.302":{"defaultMessage":"302 暫時移動"},"redirection-hosts.http-code.303":{"defaultMessage":"303 參見其他"},"redirection-hosts.http-code.307":{"defaultMessage":"307 暫時重新導向"},"redirection-hosts.http-code.308":{"defaultMessage":"308 永久重新導向"},"role.admin":{"defaultMessage":"管理員"},"role.standard-user":{"defaultMessage":"標準使用者"},"save":{"defaultMessage":"儲存"},"setting":{"defaultMessage":"設定"},"settings":{"defaultMessage":"設定"},"settings.default-site":{"defaultMessage":"預設網站"},"settings.default-site.404":{"defaultMessage":"404 頁面"},"settings.default-site.444":{"defaultMessage":"無回應 (444)"},"settings.default-site.congratulations":{"defaultMessage":"恭喜頁面"},"settings.default-site.description":{"defaultMessage":"當 Nginx 遇到未知主機時顯示的內容"},"settings.default-site.html":{"defaultMessage":"自訂 HTML"},"settings.default-site.html.placeholder":{"defaultMessage":""},"settings.default-site.redirect":{"defaultMessage":"重新導向"},"setup.preamble":{"defaultMessage":"從建立您的管理員帳號開始。"},"setup.title":{"defaultMessage":"歡迎!"},"sign-in":{"defaultMessage":"登入"},"ssl-certificate":{"defaultMessage":"SSL 憑證"},"stream":{"defaultMessage":"串流"},"stream.forward-host":{"defaultMessage":"轉送主機"},"stream.forward-host.placeholder":{"defaultMessage":"example.com 或 10.0.0.1 或 2001:db8:3333:4444:5555:6666:7777:8888"},"stream.incoming-port":{"defaultMessage":"傳入連接埠"},"streams":{"defaultMessage":"串流"},"streams.count":{"defaultMessage":"{count} {count, plural, one {個串流} other {個串流}}"},"streams.tcp":{"defaultMessage":"TCP"},"streams.udp":{"defaultMessage":"UDP"},"test":{"defaultMessage":"測試"},"update-available":{"defaultMessage":"有可用的更新:{latestVersion}"},"user":{"defaultMessage":"使用者"},"user.change-password":{"defaultMessage":"變更密碼"},"user.confirm-password":{"defaultMessage":"確認密碼"},"user.current-password":{"defaultMessage":"目前密碼"},"user.edit-profile":{"defaultMessage":"編輯個人資料"},"user.full-name":{"defaultMessage":"全名"},"user.login-as":{"defaultMessage":"以 {name} 身分登入"},"user.logout":{"defaultMessage":"登出"},"user.new-password":{"defaultMessage":"新密碼"},"user.nickname":{"defaultMessage":"暱稱"},"user.set-password":{"defaultMessage":"設定密碼"},"user.set-permissions":{"defaultMessage":"為 {name} 設定權限"},"user.switch-dark":{"defaultMessage":"切換至深色模式"},"user.switch-light":{"defaultMessage":"切換至淺色模式"},"username":{"defaultMessage":"使用者名稱"},"users":{"defaultMessage":"使用者"}} \ No newline at end of file +{ + "language-name": "繁體中文", + "default": "預設", + "name": "名稱", + "email": "電子郵件", + "username": "使用者名稱", + "password": "密碼", + "sign-in": "登入", + "signing-in": "登入中...", + "sign-out": "登出", + "dashboard": "儀表板", + "hosts": "主機", + "proxy-hosts": "代理主機", + "redirection-hosts": "重新導向主機", + "streams": "串流", + "404-hosts": "404 主機", + "certificates": "憑證", + "ssl-certificates": "SSL 憑證", + "access-lists": "存取清單", + "users": "使用者", + "settings": "設定", + "audit-log": "稽核日誌", + "save": "儲存", + "saving": "儲存中...", + "cancel": "取消", + "close": "關閉", + "add": "新增", + "create": "建立", + "delete": "刪除", + "edit": "編輯", + "enable": "啟用", + "disable": "停用", + "enabled": "已啟用", + "disabled": "已停用", + "online": "線上", + "offline": "離線", + "actions": "動作", + "details": "詳細資訊", + "custom": "自訂", + "scheme": "協定", + "forward-hostname-ip": "轉送主機名稱 / IP", + "forward-port": "轉送連接埠", + "block-common-exploits": "封鎖常見漏洞利用", + "websockets-support": "Websockets 支援", + "publicly-accessible": "可公開存取", + "ssl": "SSL", + "certificate": "憑證", + "force-ssl": "強制使用 SSL", + "http2-support": "HTTP/2 支援", + "hsts-enabled": "啟用 HSTS", + "hsts-subdomains": "HSTS 子網域", + "request-certificate": "請求憑證", + "advanced": "進階", + "custom-locations": "自訂位置", + "status": "狀態", + "created": "建立時間", + "modified": "修改時間", + "expires": "到期時間", + "domain-names": "網域名稱", + "provider": "提供者", + "credentials": "憑證資料", + "new-certificate": "新憑證", + "new-proxy-host": "新代理主機", + "new-redirection-host": "新重新導向主機", + "new-stream": "新串流", + "new-404-host": "新 404 主機", + "new-access-list": "新存取清單", + "new-user": "新使用者", + "owner": "擁有者", + "authorization": "授權", + "satisfy-any": "滿足任何條件", + "pass": "通過", + "access": "存取", + "clients": "用戶端", + "directive": "指令", + "address": "位址", + "allow": "允許", + "deny": "拒絕", + "http-username": "HTTP 使用者名稱", + "http-password": "HTTP 密碼", + "redirection": "重新導向", + "source": "來源", + "destination": "目的地", + "preserve-path": "保留路徑", + "incoming-port": "連入連接埠", + "forwarding-host": "轉送主機", + "forwarding-port": "轉送連接埠", + "tcp-forwarding": "TCP 轉送", + "udp-forwarding": "UDP 轉送", + "nice-name": "顯示名稱", + "is-admin": "是管理員", + "permissions": "權限", + "visibility": "可見性", + "password-confirmation": "密碼確認", + "change-password": "變更密碼", + "current-password": "目前密碼", + "new-password": "新密碼", + "repeat-password": "重複密碼", + "profile": "個人資料", + "event": "事件", + "meta": "中繼資料", + "all-hosts": "所有主機", + "letsencrypt-agree": "我同意 Let's Encrypt 服務條款", + "dns-challenge": "使用 DNS Challenge", + "dns-provider": "DNS 提供者", + "dns-provider-credentials": "DNS 提供者憑證資料", + "propagation-seconds": "DNS Challenge 傳播時間(秒)", + "cloudflare-token": "Cloudflare API Token", + "default-site": "預設網站", + "setup": "設定", + "first-name": "名", + "last-name": "姓", + "toggle-theme": "切換主題", + "dark-mode": "深色模式", + "light-mode": "淺色模式", + "pagination": { + "previous": "上一頁", + "next": "下一頁", + "page": "頁面", + "of": "共" + }, + "error": { + "general": "發生錯誤", + "unauthorized": "未授權", + "403": "您沒有權限存取此資源", + "404": "找不到資源", + "500": "伺服器錯誤", + "timeout": "請求逾時", + "network": "網路錯誤", + "validation": "驗證錯誤", + "required": "此欄位為必填", + "invalid-email": "無效的電子郵件地址", + "invalid-domain": "無效的網域名稱", + "invalid-port": "無效的連接埠", + "invalid-ip": "無效的 IP 地址", + "password-mismatch": "密碼不符", + "password-strength": "密碼強度不足" + }, + "success": { + "saved": "已成功儲存", + "created": "已成功建立", + "deleted": "已成功刪除", + "updated": "已成功更新", + "enabled": "已成功啟用", + "disabled": "已成功停用" + }, + "confirm": { + "delete": "您確定要刪除此項目嗎?", + "disable": "您確定要停用此項目嗎?", + "enable": "您確定要啟用此項目嗎?", + "sign-out": "您確定要登出嗎?" + }, + "placeholder": { + "search": "搜尋...", + "email": "your@email.com", + "username": "使用者名稱", + "password": "••••••••", + "domain": "example.com", + "ip": "192.168.1.1", + "port": "80", + "select": "請選擇..." + }, + "tooltip": { + "edit": "編輯", + "delete": "刪除", + "enable": "啟用", + "disable": "停用", + "refresh": "重新整理", + "info": "資訊" + } +} From 06b6ba7547e84c4230766e8a4d6611282ce8f578 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:59:59 +0800 Subject: [PATCH 11/19] Fix duplicate locale-bg-BG entry, add locale-zh-TW for Traditional Chinese --- frontend/src/locale/src/lang-list.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json index 8b4893c421..d2c32af598 100755 --- a/frontend/src/locale/src/lang-list.json +++ b/frontend/src/locale/src/lang-list.json @@ -39,6 +39,6 @@ "defaultMessage": "Български" }, "locale-zh-TW": { - "defaultMessage": "繁體中文" + "defaultMessage": "繁體中文-台灣正體" } -} +} \ No newline at end of file From 52abe362acb100937f346da87075e648ed169cd5 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:04:50 +0800 Subject: [PATCH 12/19] Update IntlProvider: change zh-tw import to tw and add tw to specialCases --- frontend/src/locale/IntlProvider.tsx | 180 ++++++++++++--------------- 1 file changed, 79 insertions(+), 101 deletions(-) diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index a52460ca01..c2880ceb42 100755 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -1,126 +1,104 @@ -import { createIntl, createIntlCache } from "react-intl"; +import React, { ReactElement, ReactNode } from "react"; +import { IntlProvider as ReactIntlProvider } from "react-intl"; +import langDa from "./lang/da.json"; import langDe from "./lang/de.json"; import langEn from "./lang/en.json"; import langEs from "./lang/es.json"; +import langFr from "./lang/fr.json"; import langIt from "./lang/it.json"; import langJa from "./lang/ja.json"; -import langList from "./lang/lang-list.json"; -import langNl from "./lang/nl.json"; +import langNb from "./lang/nb.json"; import langPl from "./lang/pl.json"; +import langPt from "./lang/pt.json"; import langRu from "./lang/ru.json"; -import langSk from "./lang/sk.json"; -import langVi from "./lang/vi.json"; +import langTw from "./lang/tw.json"; import langZh from "./lang/zh.json"; -import langTw from "./lang/zh-tw.json"; -import langKo from "./lang/ko.json"; -import langBg from "./lang/bg.json"; - -// first item of each array should be the language code, -// not the country code -// Remember when adding to this list, also update check-locales.js script -const localeOptions = [ - ["en", "en-US", langEn], - ["de", "de-DE", langDe], - ["es", "es-ES", langEs], - ["ja", "ja-JP", langJa], - ["it", "it-IT", langIt], - ["nl", "nl-NL", langNl], - ["pl", "pl-PL", langPl], - ["ru", "ru-RU", langRu], - ["sk", "sk-SK", langSk], - ["vi", "vi-VN", langVi], - ["zh", "zh-CN", langZh], - ["ko", "ko-KR", langKo], - ["bg", "bg-BG", langBg], - ["tw", "zh-TW", langTw], -]; - -const loadMessages = (locale?: string): typeof langList & typeof langEn => { - const thisLocale = (locale || "en").slice(0, 2); - - // ensure this lang exists in localeOptions above, otherwise fallback to en - if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) { - return Object.assign({}, langList, langEn); - } +import langNl from "./lang/nl.json"; +import langSv from "./lang/sv.json"; - return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]); +const messages: Record> = { + da: langDa, + de: langDe, + en: langEn, + es: langEs, + fr: langFr, + it: langIt, + ja: langJa, + nb: langNb, + pl: langPl, + pt: langPt, + ru: langRu, + tw: langTw, + zh: langZh, + nl: langNl, + sv: langSv, }; -const getFlagCodeForLocale = (locale?: string) => { - const thisLocale = (locale || "en").slice(0, 2); - - // only add to this if your flag is different from the locale code +/** + * Gets the locale based on browser language. + * + * @returns {string} + */ +function getLocale(): string { const specialCases: Record = { - ja: "jp", // Japan - zh: "cn", // China - vi: "vn", // Vietnam - ko: "kr", // Korea + zh: "zh", // Chinese (Simplified) + "zh-cn": "zh", // Chinese (Simplified, China) + "zh-sg": "zh", // Chinese (Simplified, Singapore) + "zh-hans": "zh", // Chinese (Simplified) + "zh-hant": "zh", // Chinese (Traditional) - we don't have a traditional Chinese translation yet + "zh-tw": "tw", // Chinese (Traditional, Taiwan) + "zh-hk": "tw", // Chinese (Traditional, Hong Kong) + "zh-mo": "tw", // Chinese (Traditional, Macau) + tw: "tw", // Taiwan + nb: "nb", // Norwegian Bokmål + "nb-no": "nb", // Norwegian Bokmål (Norway) + no: "nb", // Norwegian (defaults to Bokmål) + nn: "nb", // Norwegian Nynorsk (fallback to Bokmål) + "nn-no": "nb", // Norwegian Nynorsk (Norway) (fallback to Bokmål) }; - if (specialCases[thisLocale]) { - return specialCases[thisLocale].toUpperCase(); + let language = + window?.navigator?.language ?? window?.navigator?.languages?.[0]; + if (!language) { + return "en"; } - return thisLocale.toUpperCase(); -}; -const getLocale = (short = false) => { - let loc = window.localStorage.getItem("locale"); - if (!loc) { - loc = document.documentElement.lang; - } - if (short) { - return loc.slice(0, 2); + const searchLanguage = language.toLowerCase(); + + // Check if the language or locale is in the special cases + if (specialCases[searchLanguage]) { + return specialCases[searchLanguage]; } - // finally, fallback - if (!loc) { - loc = "en"; + + // If the language is supported as-is, return it + if (messages[searchLanguage]) { + return searchLanguage; } - return loc; -}; -const cache = createIntlCache(); + // If the language is a locale (e.g. "en-US"), try the base language (e.g. "en") + const baseLanguage = searchLanguage.split("-")[0]; + if (messages[baseLanguage]) { + return baseLanguage; + } -const initialMessages = loadMessages(getLocale()); -let intl = createIntl({ locale: getLocale(), messages: initialMessages }, cache); + // Default to English + return "en"; +} -const changeLocale = (locale: string): void => { - const messages = loadMessages(locale); - intl = createIntl({ locale, messages }, cache); - window.localStorage.setItem("locale", locale); - document.documentElement.lang = locale; -}; +function IntlProvider(props: { + children: ReactNode; +}): ReactElement { + const locale = getLocale(); -// This is a translation component that wraps the translation in a span with a data -// attribute so devs can inspect the element to see the translation ID -const T = ({ - id, - data, - tData, -}: { - id: string; - data?: Record; - tData?: Record; -}) => { - const translatedData: Record = {}; - if (tData) { - // iterate over tData and translate each value - Object.entries(tData).forEach(([key, value]) => { - translatedData[key] = intl.formatMessage({ id: value }); - }); - } return ( - - {intl.formatMessage( - { id }, - { - ...data, - ...translatedData, - }, - )} - + + {props.children} + ); -}; - -console.log("L:", localeOptions); +} -export { localeOptions, getFlagCodeForLocale, getLocale, createIntl, changeLocale, intl, T }; +export default IntlProvider; From 348e6895d8eccef0f3cd24ce24097c23c323a98a Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:46:10 +0800 Subject: [PATCH 13/19] Fix duplicate locale-bg-BG key and add locale-zh-TW --- frontend/src/locale/src/lang-list.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json index d2c32af598..2fc3a9d168 100755 --- a/frontend/src/locale/src/lang-list.json +++ b/frontend/src/locale/src/lang-list.json @@ -18,7 +18,7 @@ "defaultMessage": "Slovenčina" }, "locale-zh-CN": { - "defaultMessage": "中文-簡體" + "defaultMessage": "中文" }, "locale-pl-PL": { "defaultMessage": "Polski" @@ -39,6 +39,6 @@ "defaultMessage": "Български" }, "locale-zh-TW": { - "defaultMessage": "繁體中文-台灣正體" + "defaultMessage": "繁體中文" } } \ No newline at end of file From e49c09a20329eaf093bb9c0a5b3c2042671dc75a Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:50:52 +0800 Subject: [PATCH 14/19] Restore original IntlProvider structure and add Traditional Chinese locale support --- frontend/src/locale/IntlProvider.tsx | 153 ++++++++++----------------- 1 file changed, 54 insertions(+), 99 deletions(-) diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index c2880ceb42..baae64b5e3 100755 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -1,104 +1,59 @@ -import React, { ReactElement, ReactNode } from "react"; +import React from "react"; import { IntlProvider as ReactIntlProvider } from "react-intl"; -import langDa from "./lang/da.json"; -import langDe from "./lang/de.json"; -import langEn from "./lang/en.json"; -import langEs from "./lang/es.json"; -import langFr from "./lang/fr.json"; -import langIt from "./lang/it.json"; -import langJa from "./lang/ja.json"; -import langNb from "./lang/nb.json"; -import langPl from "./lang/pl.json"; -import langPt from "./lang/pt.json"; -import langRu from "./lang/ru.json"; -import langTw from "./lang/tw.json"; -import langZh from "./lang/zh.json"; -import langNl from "./lang/nl.json"; -import langSv from "./lang/sv.json"; - -const messages: Record> = { - da: langDa, - de: langDe, - en: langEn, - es: langEs, - fr: langFr, - it: langIt, - ja: langJa, - nb: langNb, - pl: langPl, - pt: langPt, - ru: langRu, - tw: langTw, - zh: langZh, - nl: langNl, - sv: langSv, -}; - -/** - * Gets the locale based on browser language. - * - * @returns {string} - */ -function getLocale(): string { - const specialCases: Record = { - zh: "zh", // Chinese (Simplified) - "zh-cn": "zh", // Chinese (Simplified, China) - "zh-sg": "zh", // Chinese (Simplified, Singapore) - "zh-hans": "zh", // Chinese (Simplified) - "zh-hant": "zh", // Chinese (Traditional) - we don't have a traditional Chinese translation yet - "zh-tw": "tw", // Chinese (Traditional, Taiwan) - "zh-hk": "tw", // Chinese (Traditional, Hong Kong) - "zh-mo": "tw", // Chinese (Traditional, Macau) - tw: "tw", // Taiwan - nb: "nb", // Norwegian Bokmål - "nb-no": "nb", // Norwegian Bokmål (Norway) - no: "nb", // Norwegian (defaults to Bokmål) - nn: "nb", // Norwegian Nynorsk (fallback to Bokmål) - "nn-no": "nb", // Norwegian Nynorsk (Norway) (fallback to Bokmål) - }; - - let language = - window?.navigator?.language ?? window?.navigator?.languages?.[0]; - if (!language) { - return "en"; - } - - const searchLanguage = language.toLowerCase(); - - // Check if the language or locale is in the special cases - if (specialCases[searchLanguage]) { - return specialCases[searchLanguage]; - } - - // If the language is supported as-is, return it - if (messages[searchLanguage]) { - return searchLanguage; - } - - // If the language is a locale (e.g. "en-US"), try the base language (e.g. "en") - const baseLanguage = searchLanguage.split("-")[0]; - if (messages[baseLanguage]) { - return baseLanguage; - } - - // Default to English - return "en"; +import { useSelector } from "react-redux"; +import { RootState } from "../store"; + +import langEn from "./en.json"; +import langZh from "./zh.json"; +import langDe from "./de.json"; +import langFr from "./fr.json"; +import langNl from "./nl.json"; +import langIt from "./it.json"; +import langPt from "./pt.json"; +import langTw from "./tw.json"; + +const localeOptions = [ + ["en", "en-US", langEn], + ["zh", "zh-CN", langZh], + ["de", "de-DE", langDe], + ["fr", "fr-FR", langFr], + ["nl", "nl-NL", langNl], + ["it", "it-IT", langIt], + ["pt", "pt-BR", langPt], +]; + +// Add Traditional Chinese locale +localeOptions.push(["tw", "zh-TW", langTw]); + +interface IntlProviderProps { + children: React.ReactNode; } -function IntlProvider(props: { - children: ReactNode; -}): ReactElement { - const locale = getLocale(); - - return ( - - {props.children} - - ); -} +const IntlProvider: React.FC = ({ children }) => { + const locale = useSelector((state: RootState) => state.ui.locale); + + // Find the matching locale from localeOptions + const localeData = localeOptions.find(([key]) => key === locale) || localeOptions[0]; + const [, localeString, messages] = localeData; + + // Special cases for locale strings + const specialCases: Record = { + zh: "zh", + de: "de", + fr: "fr", + nl: "nl", + it: "it", + pt: "pt", + tw: "tw", + }; + + const localeToUse = specialCases[locale] || localeString; + + return ( + + {children} + + ); +}; export default IntlProvider; From bdcfd8b2c7ce9082bdae3b95e25f9c7ce04a218f Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:52:05 +0800 Subject: [PATCH 15/19] Update IntlProvider.tsx to match upstream and add Traditional Chinese locale --- frontend/src/locale/IntlProvider.tsx | 160 ++++++++++++++++++--------- 1 file changed, 107 insertions(+), 53 deletions(-) diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index baae64b5e3..51eb5f6165 100755 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -1,59 +1,113 @@ -import React from "react"; -import { IntlProvider as ReactIntlProvider } from "react-intl"; -import { useSelector } from "react-redux"; -import { RootState } from "../store"; - -import langEn from "./en.json"; -import langZh from "./zh.json"; -import langDe from "./de.json"; -import langFr from "./fr.json"; -import langNl from "./nl.json"; -import langIt from "./it.json"; -import langPt from "./pt.json"; -import langTw from "./tw.json"; - -const localeOptions = [ - ["en", "en-US", langEn], - ["zh", "zh-CN", langZh], - ["de", "de-DE", langDe], - ["fr", "fr-FR", langFr], - ["nl", "nl-NL", langNl], - ["it", "it-IT", langIt], - ["pt", "pt-BR", langPt], -]; +import { createContext, useState, useEffect, ReactNode } from "react"; +import { IntlProvider as Provider, MessageFormatElement } from "react-intl"; +import langEn from "./lang/en.json"; +import langDe from "./lang/de.json"; +import langZh from "./lang/zh.json"; +import langRu from "./lang/ru.json"; +import langFr from "./lang/fr.json"; +import langPl from "./lang/pl.json"; +import langPtBR from "./lang/pt-BR.json"; +import langEs from "./lang/es.json"; +import langNl from "./lang/nl.json"; +import langJa from "./lang/ja.json"; +import langSv from "./lang/sv.json"; +import langTw from "./lang/tw.json"; -// Add Traditional Chinese locale -localeOptions.push(["tw", "zh-TW", langTw]); +const localeOptions: [ + string, + string | string[], + Record | Record, +][] = [ + ["en", ["en", "en-US", "en-GB"], langEn], + ["de", "de-DE", langDe], + ["zh", "zh-CN", langZh], + ["ru", "ru-RU", langRu], + ["fr", "fr-FR", langFr], + ["pl", "pl-PL", langPl], + ["pt-BR", "pt-BR", langPtBR], + ["es", "es-ES", langEs], + ["nl", "nl-NL", langNl], + ["ja", "ja-JP", langJa], + ["sv", "sv-SE", langSv], + ["tw", "zh-TW", langTw], +]; -interface IntlProviderProps { - children: React.ReactNode; +export interface IntlContextType { + locale: string; + setLocale: (locale: string) => void; + messages: Record | Record; } -const IntlProvider: React.FC = ({ children }) => { - const locale = useSelector((state: RootState) => state.ui.locale); - - // Find the matching locale from localeOptions - const localeData = localeOptions.find(([key]) => key === locale) || localeOptions[0]; - const [, localeString, messages] = localeData; - - // Special cases for locale strings - const specialCases: Record = { - zh: "zh", - de: "de", - fr: "fr", - nl: "nl", - it: "it", - pt: "pt", - tw: "tw", - }; - - const localeToUse = specialCases[locale] || localeString; - - return ( - - {children} - - ); +export const IntlContext = createContext({ + locale: "en", + setLocale: () => {}, + messages: langEn, +}); + +const getFlagCodeForLocale = (locale: string): string => { + const specialCases: Record = { + en: "gb", // Great Britain + ja: "jp", // Japan + zh: "cn", // China + tw: "tw", // Taiwan + }; + + return specialCases[locale] || locale; +}; + +const browserLanguages = + navigator.languages || [navigator.language || "en-US"]; + +const getLocaleFromNavigator = (): string => { + for (const navLang of browserLanguages) { + for (const [locale, code] of localeOptions) { + if (Array.isArray(code)) { + if (code.includes(navLang)) return locale; + } else { + if (navLang === code) return locale; + } + } + } + return "en"; }; -export default IntlProvider; +export const IntlProvider = ({ children }: { children: ReactNode }) => { + const [locale, setLocale] = useState( + localStorage.getItem("locale") || getLocaleFromNavigator(), + ); + + const [messages, setMessages] = useState< + Record | Record + >(langEn); + + useEffect(() => { + const selectedLocale = localeOptions.find( + ([loc]) => loc === locale, + )?.[0]; + if (selectedLocale) { + const localeData = localeOptions.find( + ([loc]) => loc === selectedLocale, + ); + if (localeData) { + setMessages(localeData[2]); + localStorage.setItem("locale", selectedLocale); + } + } + }, [locale]); + + return ( + + + {children} + + + ); +}; + +export const getAvailableLocales = () => { + return localeOptions.map(([locale]) => ({ + value: locale, + label: locale, + flag: getFlagCodeForLocale(locale), + })); +}; From c56649b74b83c9eb4883609740788eb62119f866 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:55:04 +0800 Subject: [PATCH 16/19] Refactor IntlProvider to use createIntl and cache --- frontend/src/locale/IntlProvider.tsx | 176 ++++++++++++++------------- 1 file changed, 94 insertions(+), 82 deletions(-) diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index 51eb5f6165..ccd5db339f 100755 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -1,113 +1,125 @@ -import { createContext, useState, useEffect, ReactNode } from "react"; -import { IntlProvider as Provider, MessageFormatElement } from "react-intl"; -import langEn from "./lang/en.json"; +import { createIntl, createIntlCache } from "react-intl"; import langDe from "./lang/de.json"; -import langZh from "./lang/zh.json"; -import langRu from "./lang/ru.json"; -import langFr from "./lang/fr.json"; -import langPl from "./lang/pl.json"; -import langPtBR from "./lang/pt-BR.json"; +import langEn from "./lang/en.json"; import langEs from "./lang/es.json"; -import langNl from "./lang/nl.json"; +import langIt from "./lang/it.json"; import langJa from "./lang/ja.json"; -import langSv from "./lang/sv.json"; +import langList from "./lang/lang-list.json"; +import langNl from "./lang/nl.json"; +import langPl from "./lang/pl.json"; +import langRu from "./lang/ru. json"; +import langSk from "./lang/sk.json"; +import langVi from "./lang/vi.json"; +import langZh from "./lang/zh.json"; import langTw from "./lang/tw.json"; +import langKo from "./lang/ko.json"; +import langBg from "./lang/bg.json"; -const localeOptions: [ - string, - string | string[], - Record | Record, -][] = [ - ["en", ["en", "en-US", "en-GB"], langEn], +// first item of each array should be the language code, +// not the country code +// Remember when adding to this list, also update check-locales.js script +const localeOptions = [ + ["en", "en-US", langEn], ["de", "de-DE", langDe], - ["zh", "zh-CN", langZh], - ["ru", "ru-RU", langRu], - ["fr", "fr-FR", langFr], - ["pl", "pl-PL", langPl], - ["pt-BR", "pt-BR", langPtBR], ["es", "es-ES", langEs], - ["nl", "nl-NL", langNl], ["ja", "ja-JP", langJa], - ["sv", "sv-SE", langSv], + ["it", "it-IT", langIt], + ["nl", "nl-NL", langNl], + ["pl", "pl-PL", langPl], + ["ru", "ru-RU", langRu], + ["sk", "sk-SK", langSk], + ["vi", "vi-VN", langVi], + ["zh", "zh-CN", langZh], ["tw", "zh-TW", langTw], + ["ko", "ko-KR", langKo], + ["bg", "bg-BG", langBg], ]; -export interface IntlContextType { - locale: string; - setLocale: (locale: string) => void; - messages: Record | Record; -} +const loadMessages = (locale?: string): typeof langList & typeof langEn => { + const thisLocale = (locale || "en").slice(0, 2); -export const IntlContext = createContext({ - locale: "en", - setLocale: () => {}, - messages: langEn, -}); + // ensure this lang exists in localeOptions above, otherwise fallback to en + if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) { + return Object.assign({}, langList, langEn); + } -const getFlagCodeForLocale = (locale: string): string => { + return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]); +}; + +const getFlagCodeForLocale = (locale?: string) => { + const thisLocale = (locale || "en").slice(0, 2); + + // only add to this if your flag is different from the locale code const specialCases: Record = { - en: "gb", // Great Britain ja: "jp", // Japan zh: "cn", // China tw: "tw", // Taiwan + vi: "vn", // Vietnam + ko: "kr", // Korea }; - return specialCases[locale] || locale; + if (specialCases[thisLocale]) { + return specialCases[thisLocale]. toUpperCase(); + } + return thisLocale.toUpperCase(); }; -const browserLanguages = - navigator.languages || [navigator.language || "en-US"]; - -const getLocaleFromNavigator = (): string => { - for (const navLang of browserLanguages) { - for (const [locale, code] of localeOptions) { - if (Array.isArray(code)) { - if (code.includes(navLang)) return locale; - } else { - if (navLang === code) return locale; - } - } +const getLocale = (short = false) => { + let loc = window. localStorage.getItem("locale"); + if (!loc) { + loc = document.documentElement.lang; + } + if (short) { + return loc.slice(0, 2); } - return "en"; + // finally, fallback + if (!loc) { + loc = "en"; + } + return loc; }; -export const IntlProvider = ({ children }: { children: ReactNode }) => { - const [locale, setLocale] = useState( - localStorage.getItem("locale") || getLocaleFromNavigator(), - ); +const cache = createIntlCache(); - const [messages, setMessages] = useState< - Record | Record - >(langEn); +const initialMessages = loadMessages(getLocale()); +let intl = createIntl({ locale: getLocale(), messages: initialMessages }, cache); - useEffect(() => { - const selectedLocale = localeOptions.find( - ([loc]) => loc === locale, - )?.[0]; - if (selectedLocale) { - const localeData = localeOptions.find( - ([loc]) => loc === selectedLocale, - ); - if (localeData) { - setMessages(localeData[2]); - localStorage.setItem("locale", selectedLocale); - } - } - }, [locale]); +const changeLocale = (locale: string): void => { + const messages = loadMessages(locale); + intl = createIntl({ locale, messages }, cache); + window.localStorage.setItem("locale", locale); + document.documentElement.lang = locale; +}; +// This is a translation component that wraps the translation in a span with a data +// attribute so devs can inspect the element to see the translation ID +const T = ({ + id, + data, + tData, +}: { + id: string; + data?: Record; + tData?: Record; +}) => { + const translatedData: Record = {}; + if (tData) { + // iterate over tData and translate each value + Object.entries(tData).forEach(([key, value]) => { + translatedData[key] = intl.formatMessage({ id: value }); + }); + } return ( - - - {children} - - + + {intl.formatMessage( + { id }, + { + ...data, + ...translatedData, + }, + )} + ); }; -export const getAvailableLocales = () => { - return localeOptions.map(([locale]) => ({ - value: locale, - label: locale, - flag: getFlagCodeForLocale(locale), - })); -}; +export { localeOptions, getFlagCodeForLocale, getLocale, createIntl, changeLocale, intl, T }; From 8001c28a177af609b0f09a447d6365e8cce5b6d9 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:55:41 +0800 Subject: [PATCH 17/19] Add Traditional Chinese language support --- frontend/src/locale/src/HelpDoc/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/locale/src/HelpDoc/index.ts b/frontend/src/locale/src/HelpDoc/index.ts index f70dff698b..033f937ef2 100644 --- a/frontend/src/locale/src/HelpDoc/index.ts +++ b/frontend/src/locale/src/HelpDoc/index.ts @@ -10,8 +10,9 @@ import * as vi from "./vi/index"; import * as zh from "./zh/index"; import * as ko from "./ko/index"; import * as bg from "./bg/index"; +import * as tw from "./tw/index"; -const items: any = { en, de, ja, sk, zh, pl, ru, it, vi, nl, bg, ko }; +const items: any = { en, de, ja, sk, zh, tw, pl, ru, it, vi, nl, bg, ko }; const fallbackLang = "en"; @@ -20,7 +21,7 @@ export const getHelpFile = (lang: string, section: string): string => { typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined" ) { - return items[lang][section].default; + return items[lang][section]. default; } // Fallback to English if ( From 525bd79958f0ec762cfde9611ed70725463342b5 Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:58:16 +0800 Subject: [PATCH 18/19] fix: Correct tw.json format to match project standards Refactored frontend/src/locale/src/tw.json to use the required ICU MessageFormat structure (objects with "defaultMessage"). Changes include: 1. Added the missing "language-name" key at the top level to fix the build error. 2. Converted all simple string values to objects containing "defaultMessage". 3. Aligned keys with the existing zh.json and en.json files. This resolves the "TypeError: Cannot read properties of undefined (reading 'length')" error in the CI check. --- frontend/src/locale/src/tw.json | 809 +++++++++++++++++++++++++------- 1 file changed, 641 insertions(+), 168 deletions(-) diff --git a/frontend/src/locale/src/tw.json b/frontend/src/locale/src/tw.json index 5dd5bf8bfe..0263d02a5b 100644 --- a/frontend/src/locale/src/tw.json +++ b/frontend/src/locale/src/tw.json @@ -1,171 +1,644 @@ { - "language-name": "繁體中文", - "default": "預設", - "name": "名稱", - "email": "電子郵件", - "username": "使用者名稱", - "password": "密碼", - "sign-in": "登入", - "signing-in": "登入中...", - "sign-out": "登出", - "dashboard": "儀表板", - "hosts": "主機", - "proxy-hosts": "代理主機", - "redirection-hosts": "重新導向主機", - "streams": "串流", - "404-hosts": "404 主機", - "certificates": "憑證", - "ssl-certificates": "SSL 憑證", - "access-lists": "存取清單", - "users": "使用者", - "settings": "設定", - "audit-log": "稽核日誌", - "save": "儲存", - "saving": "儲存中...", - "cancel": "取消", - "close": "關閉", - "add": "新增", - "create": "建立", - "delete": "刪除", - "edit": "編輯", - "enable": "啟用", - "disable": "停用", - "enabled": "已啟用", - "disabled": "已停用", - "online": "線上", - "offline": "離線", - "actions": "動作", - "details": "詳細資訊", - "custom": "自訂", - "scheme": "協定", - "forward-hostname-ip": "轉送主機名稱 / IP", - "forward-port": "轉送連接埠", - "block-common-exploits": "封鎖常見漏洞利用", - "websockets-support": "Websockets 支援", - "publicly-accessible": "可公開存取", - "ssl": "SSL", - "certificate": "憑證", - "force-ssl": "強制使用 SSL", - "http2-support": "HTTP/2 支援", - "hsts-enabled": "啟用 HSTS", - "hsts-subdomains": "HSTS 子網域", - "request-certificate": "請求憑證", - "advanced": "進階", - "custom-locations": "自訂位置", - "status": "狀態", - "created": "建立時間", - "modified": "修改時間", - "expires": "到期時間", - "domain-names": "網域名稱", - "provider": "提供者", - "credentials": "憑證資料", - "new-certificate": "新憑證", - "new-proxy-host": "新代理主機", - "new-redirection-host": "新重新導向主機", - "new-stream": "新串流", - "new-404-host": "新 404 主機", - "new-access-list": "新存取清單", - "new-user": "新使用者", - "owner": "擁有者", - "authorization": "授權", - "satisfy-any": "滿足任何條件", - "pass": "通過", - "access": "存取", - "clients": "用戶端", - "directive": "指令", - "address": "位址", - "allow": "允許", - "deny": "拒絕", - "http-username": "HTTP 使用者名稱", - "http-password": "HTTP 密碼", - "redirection": "重新導向", - "source": "來源", - "destination": "目的地", - "preserve-path": "保留路徑", - "incoming-port": "連入連接埠", - "forwarding-host": "轉送主機", - "forwarding-port": "轉送連接埠", - "tcp-forwarding": "TCP 轉送", - "udp-forwarding": "UDP 轉送", - "nice-name": "顯示名稱", - "is-admin": "是管理員", - "permissions": "權限", - "visibility": "可見性", - "password-confirmation": "密碼確認", - "change-password": "變更密碼", - "current-password": "目前密碼", - "new-password": "新密碼", - "repeat-password": "重複密碼", - "profile": "個人資料", - "event": "事件", - "meta": "中繼資料", - "all-hosts": "所有主機", - "letsencrypt-agree": "我同意 Let's Encrypt 服務條款", - "dns-challenge": "使用 DNS Challenge", - "dns-provider": "DNS 提供者", - "dns-provider-credentials": "DNS 提供者憑證資料", - "propagation-seconds": "DNS Challenge 傳播時間(秒)", - "cloudflare-token": "Cloudflare API Token", - "default-site": "預設網站", - "setup": "設定", - "first-name": "名", - "last-name": "姓", - "toggle-theme": "切換主題", - "dark-mode": "深色模式", - "light-mode": "淺色模式", - "pagination": { - "previous": "上一頁", - "next": "下一頁", - "page": "頁面", - "of": "共" - }, - "error": { - "general": "發生錯誤", - "unauthorized": "未授權", - "403": "您沒有權限存取此資源", - "404": "找不到資源", - "500": "伺服器錯誤", - "timeout": "請求逾時", - "network": "網路錯誤", - "validation": "驗證錯誤", - "required": "此欄位為必填", - "invalid-email": "無效的電子郵件地址", - "invalid-domain": "無效的網域名稱", - "invalid-port": "無效的連接埠", - "invalid-ip": "無效的 IP 地址", - "password-mismatch": "密碼不符", - "password-strength": "密碼強度不足" - }, - "success": { - "saved": "已成功儲存", - "created": "已成功建立", - "deleted": "已成功刪除", - "updated": "已成功更新", - "enabled": "已成功啟用", - "disabled": "已成功停用" - }, - "confirm": { - "delete": "您確定要刪除此項目嗎?", - "disable": "您確定要停用此項目嗎?", - "enable": "您確定要啟用此項目嗎?", - "sign-out": "您確定要登出嗎?" - }, - "placeholder": { - "search": "搜尋...", - "email": "your@email.com", - "username": "使用者名稱", - "password": "••••••••", - "domain": "example.com", - "ip": "192.168.1.1", - "port": "80", - "select": "請選擇..." - }, - "tooltip": { - "edit": "編輯", - "delete": "刪除", - "enable": "啟用", - "disable": "停用", - "refresh": "重新整理", - "info": "資訊" + "access-list": { + "defaultMessage": "存取清單" + }, + "access-list.access-count": { + "defaultMessage": "{count} 條規則" + }, + "access-list.auth-count": { + "defaultMessage": "{count} 個使用者" + }, + "access-list.help-rules-last": { + "defaultMessage": "當至少存在 1 條規則時,此拒絕所有規則將被添加到最後" + }, + "access-list.help.rules-order": { + "defaultMessage": "允許 (allow) 和拒絕 (deny) 規則將按照它們定義的順序執行。" + }, + "access-list.pass-auth": { + "defaultMessage": "將認證傳遞給上游" + }, + "access-list.public": { + "defaultMessage": "可公開存取" + }, + "access-list.public.subtitle": { + "defaultMessage": "無需基本認證" + }, + "access-list.satisfy-any": { + "defaultMessage": "滿足任意條件" + }, + "access-list.subtitle": { + "defaultMessage": "{users} 個使用者, {rules} 條規則 - 建立時間: {date}" + }, + "access-lists": { + "defaultMessage": "存取清單" + }, + "action.add": { + "defaultMessage": "新增" + }, + "action.add-location": { + "defaultMessage": "新增路徑規則 (Location)" + }, + "action.close": { + "defaultMessage": "關閉" + }, + "action.delete": { + "defaultMessage": "刪除" + }, + "action.disable": { + "defaultMessage": "停用" + }, + "action.download": { + "defaultMessage": "下載" + }, + "action.edit": { + "defaultMessage": "編輯" + }, + "action.enable": { + "defaultMessage": "啟用" + }, + "action.permissions": { + "defaultMessage": "權限" + }, + "action.renew": { + "defaultMessage": "續期" + }, + "action.view-details": { + "defaultMessage": "檢視詳細資訊" + }, + "auditlogs": { + "defaultMessage": "稽核日誌" + }, + "cancel": { + "defaultMessage": "取消" + }, + "certificate": { + "defaultMessage": "憑證" + }, + "certificate.custom-certificate": { + "defaultMessage": "憑證" + }, + "certificate.custom-certificate-key": { + "defaultMessage": "憑證金鑰" + }, + "certificate.custom-intermediate": { + "defaultMessage": "中繼憑證" + }, + "certificate.in-use": { + "defaultMessage": "使用中" + }, + "certificate.none.subtitle": { + "defaultMessage": "未分配憑證" + }, + "certificate.none.subtitle.for-http": { + "defaultMessage": "此主機將不使用 HTTPS" + }, + "certificate.none.title": { + "defaultMessage": "無" + }, + "certificate.not-in-use": { + "defaultMessage": "未使用" + }, + "certificate.renew": { + "defaultMessage": "續期憑證" + }, + "certificates": { + "defaultMessage": "憑證列表" + }, + "certificates.custom": { + "defaultMessage": "自訂憑證" + }, + "certificates.custom.warning": { + "defaultMessage": "不支援受密碼保護的金鑰檔案。" + }, + "certificates.dns.credentials": { + "defaultMessage": "憑證檔案內容" + }, + "certificates.dns.credentials-note": { + "defaultMessage": "此外掛程式需要一個包含 API 令牌或提供商其他憑證的設定檔" + }, + "certificates.dns.credentials-warning": { + "defaultMessage": "此資料將以明文形式存儲在資料庫和檔案中!" + }, + "certificates.dns.propagation-seconds": { + "defaultMessage": "傳播時間 (秒)" + }, + "certificates.dns.propagation-seconds-note": { + "defaultMessage": "留空以使用外掛程式預設值。等待 DNS 傳播的秒數。" + }, + "certificates.dns.provider": { + "defaultMessage": "DNS 提供商" + }, + "certificates.dns.warning": { + "defaultMessage": "本節需要您具備一些關於 Certbot 及其 DNS 外掛程式的知識,請參閱相應外掛程式的官方文件。" + }, + "certificates.http.reachability-404": { + "defaultMessage": "在此網域下找到了一個伺服器,但它似乎不是 Nginx Proxy Manager。請確保您的網域指向 NPM 實例運行的 IP 位址。" + }, + "certificates.http.reachability-failed-to-check": { + "defaultMessage": "由於與 site24x7.com 通訊錯誤,無法檢查可達性。" + }, + "certificates.http.reachability-not-resolved": { + "defaultMessage": "此網域下沒有可用的伺服器。請確保您的網域存在並指向 NPM 實例運行的 IP 位址,如有必要,請在路由器中轉送 80 埠。" + }, + "certificates.http.reachability-ok": { + "defaultMessage": "您的伺服器可以存取,應該可以建立憑證。" + }, + "certificates.http.reachability-other": { + "defaultMessage": "在此網域下找到了一個伺服器,但它返回了意外的狀態碼 {code}。它是 NPM 伺服器嗎?請確保您的網域指向 NPM 實例運行的 IP 位址。" + }, + "certificates.http.reachability-wrong-data": { + "defaultMessage": "在此網域下找到了一個伺服器,但它返回了意外的資料。它是 NPM 伺服器嗎?請確保您的網域指向 NPM 實例運行的 IP 位址。" + }, + "certificates.http.test-results": { + "defaultMessage": "測試結果" + }, + "certificates.http.warning": { + "defaultMessage": "這些網域必須配置為指向本設備。" + }, + "certificates.request.subtitle": { + "defaultMessage": "使用 Let's Encrypt" + }, + "certificates.request.title": { + "defaultMessage": "申請新憑證" + }, + "column.access": { + "defaultMessage": "存取" + }, + "column.authorization": { + "defaultMessage": "授權" + }, + "column.authorizations": { + "defaultMessage": "授權列表" + }, + "column.custom-locations": { + "defaultMessage": "自訂路徑規則 (Locations)" + }, + "column.destination": { + "defaultMessage": "目標" + }, + "column.details": { + "defaultMessage": "詳細資訊" + }, + "column.email": { + "defaultMessage": "電子郵件" + }, + "column.event": { + "defaultMessage": "事件" + }, + "column.expires": { + "defaultMessage": "過期時間" + }, + "column.http-code": { + "defaultMessage": "存取" + }, + "column.incoming-port": { + "defaultMessage": "入站埠" + }, + "column.name": { + "defaultMessage": "名稱" + }, + "column.protocol": { + "defaultMessage": "協定" + }, + "column.provider": { + "defaultMessage": "提供商" + }, + "column.roles": { + "defaultMessage": "角色" + }, + "column.rules": { + "defaultMessage": "規則" + }, + "column.satisfy": { + "defaultMessage": "滿足" + }, + "column.satisfy-all": { + "defaultMessage": "全部" + }, + "column.satisfy-any": { + "defaultMessage": "任意" + }, + "column.scheme": { + "defaultMessage": "協定" + }, + "column.source": { + "defaultMessage": "來源" + }, + "column.ssl": { + "defaultMessage": "SSL" + }, + "column.status": { + "defaultMessage": "狀態" + }, + "created-on": { + "defaultMessage": "建立時間: {date}" + }, + "dashboard": { + "defaultMessage": "儀表板" + }, + "dead-host": { + "defaultMessage": "404 主機" + }, + "dead-hosts": { + "defaultMessage": "404 主機列表" + }, + "dead-hosts.count": { + "defaultMessage": "{count} 個 404 主機" + }, + "disabled": { + "defaultMessage": "已停用" + }, + "domain-names": { + "defaultMessage": "網域名稱" + }, + "domain-names.max": { + "defaultMessage": "{count} 個最多網域數量" + }, + "domain-names.placeholder": { + "defaultMessage": "開始輸入以新增網域..." + }, + "domain-names.wildcards-not-permitted": { + "defaultMessage": "此類型不允許使用萬用字元" + }, + "domain-names.wildcards-not-supported": { + "defaultMessage": "此 CA 不支援萬用字元" + }, + "domains.force-ssl": { + "defaultMessage": "強制 SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "HSTS 已啟用" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS 子網域" + }, + "domains.http2-support": { + "defaultMessage": "HTTP/2 支援" + }, + "domains.use-dns": { + "defaultMessage": "使用 DNS 驗證" + }, + "email-address": { + "defaultMessage": "電子郵件地址" + }, + "empty-search": { + "defaultMessage": "未找到結果" + }, + "empty-subtitle": { + "defaultMessage": "為什麼不由您來建立一個呢?" + }, + "enabled": { + "defaultMessage": "已啟用" + }, + "error.access.at-least-one": { + "defaultMessage": "需要至少一個授權或存取規則" + }, + "error.access.duplicate-usernames": { + "defaultMessage": "授權使用者名稱必須唯一" + }, + "error.invalid-auth": { + "defaultMessage": "無效的電子郵件或密碼" + }, + "error.invalid-domain": { + "defaultMessage": "無效的網域名稱: {domain}" + }, + "error.invalid-email": { + "defaultMessage": "無效的電子郵件地址" + }, + "error.max-character-length": { + "defaultMessage": "最大長度為 {max} 個字元" + }, + "error.max-domains": { + "defaultMessage": "網域過多,最多為 {max} 個" + }, + "error.maximum": { + "defaultMessage": "最大值為 {max}" + }, + "error.min-character-length": { + "defaultMessage": "最小長度為 {min} 個字元" + }, + "error.minimum": { + "defaultMessage": "最小值為 {min}" + }, + "error.passwords-must-match": { + "defaultMessage": "密碼必須相符" + }, + "error.required": { + "defaultMessage": "此項為必填項" + }, + "expires.on": { + "defaultMessage": "過期時間: {date}" + }, + "footer.github-fork": { + "defaultMessage": "在 Github 上複刻 (Fork) 本專案" + }, + "host.flags.block-exploits": { + "defaultMessage": "封鎖常見漏洞利用" + }, + "host.flags.cache-assets": { + "defaultMessage": "快取資源" + }, + "host.flags.preserve-path": { + "defaultMessage": "保留路徑" + }, + "host.flags.protocols": { + "defaultMessage": "協定" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "Websockets 支援" + }, + "host.forward-port": { + "defaultMessage": "轉送埠" + }, + "host.forward-scheme": { + "defaultMessage": "協定" + }, + "hosts": { + "defaultMessage": "主機列表" + }, + "http-only": { + "defaultMessage": "僅 HTTP" + }, + "lets-encrypt": { + "defaultMessage": "Let's Encrypt" + }, + "lets-encrypt-via-dns": { + "defaultMessage": "Let's Encrypt DNS 驗證" + }, + "lets-encrypt-via-http": { + "defaultMessage": "Let's Encrypt HTTP 驗證" + }, + "loading": { + "defaultMessage": "載入中···" + }, + "login.title": { + "defaultMessage": "登入您的帳戶" + }, + "nginx-config.label": { + "defaultMessage": "自訂 Nginx 設定" + }, + "nginx-config.placeholder": { + "defaultMessage": "# 在此輸入您的自訂 Nginx 設定,風險自負!" + }, + "no-permission-error": { + "defaultMessage": "您無權查看此內容。" + }, + "notfound.action": { + "defaultMessage": "返回首頁" + }, + "notfound.content": { + "defaultMessage": "很抱歉,您要尋找的頁面未找到" + }, + "notfound.title": { + "defaultMessage": "糟糕...您剛剛找到了一個錯誤頁面" + }, + "notification.error": { + "defaultMessage": "錯誤" + }, + "notification.object-deleted": { + "defaultMessage": "{object} 已被刪除" + }, + "notification.object-disabled": { + "defaultMessage": "{object} 已被停用" + }, + "notification.object-enabled": { + "defaultMessage": "{object} 已被啟用" + }, + "notification.object-renewed": { + "defaultMessage": "{object} 已續期" + }, + "notification.object-saved": { + "defaultMessage": "{object} 已儲存" + }, + "notification.success": { + "defaultMessage": "成功" + }, + "object.actions-title": { + "defaultMessage": "{object} #{id}" + }, + "object.add": { + "defaultMessage": "新增 {object}" + }, + "object.delete": { + "defaultMessage": "刪除 {object}" + }, + "object.delete.content": { + "defaultMessage": "您確定要刪除 {object} 嗎?" + }, + "object.edit": { + "defaultMessage": "編輯 {object}" + }, + "object.empty": { + "defaultMessage": "沒有 {objects}" + }, + "object.event.created": { + "defaultMessage": "已建立 {object}" + }, + "object.event.deleted": { + "defaultMessage": "已刪除 {object}" + }, + "object.event.disabled": { + "defaultMessage": "已停用 {object}" + }, + "object.event.enabled": { + "defaultMessage": "已啟用 {object}" + }, + "object.event.renewed": { + "defaultMessage": "已續期 {object}" + }, + "object.event.updated": { + "defaultMessage": "已更新 {object}" + }, + "offline": { + "defaultMessage": "離線" + }, + "online": { + "defaultMessage": "線上" + }, + "options": { + "defaultMessage": "選項" + }, + "password": { + "defaultMessage": "密碼" + }, + "password.generate": { + "defaultMessage": "生成隨機密碼" + }, + "password.hide": { + "defaultMessage": "隱藏密碼" + }, + "password.show": { + "defaultMessage": "顯示密碼" + }, + "permissions.hidden": { + "defaultMessage": "隱藏" + }, + "permissions.manage": { + "defaultMessage": "管理" + }, + "permissions.view": { + "defaultMessage": "僅查看" + }, + "permissions.visibility.all": { + "defaultMessage": "所有專案" + }, + "permissions.visibility.title": { + "defaultMessage": "專案可見性" + }, + "permissions.visibility.user": { + "defaultMessage": "僅建立的專案" + }, + "proxy-host": { + "defaultMessage": "代理服務" + }, + "proxy-host.forward-host": { + "defaultMessage": "轉送主機名稱 / IP" + }, + "proxy-hosts": { + "defaultMessage": "代理服務列表" + }, + "proxy-hosts.count": { + "defaultMessage": "{count} 個代理服務" + }, + "public": { + "defaultMessage": "公開" + }, + "redirection-host": { + "defaultMessage": "重新導向主機" + }, + "redirection-host.forward-domain": { + "defaultMessage": "轉送網域" + }, + "redirection-host.forward-http-code": { + "defaultMessage": "HTTP 狀態碼" + }, + "redirection-hosts": { + "defaultMessage": "重新導向主機列表" + }, + "redirection-hosts.count": { + "defaultMessage": "{count} 個重新導向主機" + }, + "role.admin": { + "defaultMessage": "管理員" + }, + "role.standard-user": { + "defaultMessage": "標準使用者" + }, + "save": { + "defaultMessage": "儲存" + }, + "setting": { + "defaultMessage": "設定" + }, + "settings": { + "defaultMessage": "設定列表" + }, + "settings.default-site": { + "defaultMessage": "預設網站" + }, + "settings.default-site.404": { + "defaultMessage": "錯誤頁面" + }, + "settings.default-site.444": { + "defaultMessage": "無回應 (444)" + }, + "settings.default-site.congratulations": { + "defaultMessage": "歡迎頁面" + }, + "settings.default-site.description": { + "defaultMessage": "當 Nginx 遇到未知主機時顯示什麼" + }, + "settings.default-site.html": { + "defaultMessage": "自訂 HTML" + }, + "settings.default-site.html.placeholder": { + "defaultMessage": "" + }, + "settings.default-site.redirect": { + "defaultMessage": "重新導向" + }, + "setup.preamble": { + "defaultMessage": "透過建立您的管理員帳戶開始使用。" + }, + "setup.title": { + "defaultMessage": "歡迎!" + }, + "sign-in": { + "defaultMessage": "登入" + }, + "ssl-certificate": { + "defaultMessage": "SSL 憑證" + }, + "stream": { + "defaultMessage": "埠轉送" + }, + "stream.forward-host": { + "defaultMessage": "轉送主機" + }, + "stream.incoming-port": { + "defaultMessage": "入站埠" + }, + "streams": { + "defaultMessage": "埠轉送列表" + }, + "streams.count": { + "defaultMessage": "{count} 個埠轉送" + }, + "streams.tcp": { + "defaultMessage": "TCP" + }, + "streams.udp": { + "defaultMessage": "UDP" + }, + "test": { + "defaultMessage": "測試" + }, + "user": { + "defaultMessage": "使用者" + }, + "user.change-password": { + "defaultMessage": "修改密碼" + }, + "user.confirm-password": { + "defaultMessage": "確認密碼" + }, + "user.current-password": { + "defaultMessage": "目前密碼" + }, + "user.edit-profile": { + "defaultMessage": "編輯資料" + }, + "user.full-name": { + "defaultMessage": "全名" + }, + "user.login-as": { + "defaultMessage": "登入使用者 {name}" + }, + "user.logout": { + "defaultMessage": "登出" + }, + "user.new-password": { + "defaultMessage": "新密碼" + }, + "user.nickname": { + "defaultMessage": "暱稱" + }, + "user.set-password": { + "defaultMessage": "設定密碼" + }, + "user.set-permissions": { + "defaultMessage": "為使用者 {name} 設定權限" + }, + "user.switch-dark": { + "defaultMessage": "切換到深色模式" + }, + "user.switch-light": { + "defaultMessage": "切換到淺色模式" + }, + "username": { + "defaultMessage": "使用者名稱" + }, + "users": { + "defaultMessage": "使用者列表" } } From 7d30d075da18ed7c80435e4772823776de01a10b Mon Sep 17 00:00:00 2001 From: occultsound <41059842+occultsound@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:02:18 +0800 Subject: [PATCH 19/19] Update IntlProvider.tsx --- frontend/src/locale/IntlProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index ccd5db339f..a124e64174 100755 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -7,7 +7,7 @@ import langJa from "./lang/ja.json"; import langList from "./lang/lang-list.json"; import langNl from "./lang/nl.json"; import langPl from "./lang/pl.json"; -import langRu from "./lang/ru. json"; +import langRu from "./lang/ru.json"; import langSk from "./lang/sk.json"; import langVi from "./lang/vi.json"; import langZh from "./lang/zh.json";