diff --git a/frontend/check-locales.cjs b/frontend/check-locales.cjs index 251b19c4b..1b02ce8ec 100644 --- a/frontend/check-locales.cjs +++ b/frontend/check-locales.cjs @@ -23,6 +23,7 @@ const allLocales = [ ["cs", "cs-CZ"], ["vi", "vi-VN"], ["zh", "zh-CN"], + ["zh-tw", "zh-TW"], ["ko", "ko-KR"], ["bg", "bg-BG"], ["id", "id-ID"], diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx index 671ef8ed5..108f75f78 100644 --- a/frontend/src/locale/IntlProvider.tsx +++ b/frontend/src/locale/IntlProvider.tsx @@ -18,6 +18,7 @@ import langSk from "./lang/sk.json"; import langCs from "./lang/cs.json"; import langVi from "./lang/vi.json"; import langZh from "./lang/zh.json"; +import langZhTw from "./lang/zh-tw.json"; import langTr from "./lang/tr.json"; import langHu from "./lang/hu.json"; import langNo from "./lang/no.json"; @@ -27,7 +28,7 @@ import langFa from "./lang/fa.json"; import langList from "./lang/lang-list.json"; // first item of each array should be the language code, -// not the country code +// or the full locale when multiple variants share a language // Remember when adding to this list, also update check-locales.js script const localeOptions = [ ["en", "en-US", langEn], @@ -46,6 +47,7 @@ const localeOptions = [ ["cs", "cs-CZ", langCs], ["vi", "vi-VN", langVi], ["zh", "zh-CN", langZh], + ["zh-TW", "zh-TW", langZhTw], ["ko", "ko-KR", langKo], ["bg", "bg-BG", langBg], ["id", "id-ID", langId], @@ -58,18 +60,26 @@ const localeOptions = [ ]; const loadMessages = (locale?: string): typeof langList & typeof langEn => { - const thisLocale = (locale || "en").slice(0, 2); + const requestedLocale = (locale || "en").toLowerCase(); + const shortLocale = requestedLocale.slice(0, 2); + const localeOption = + localeOptions.find((option) => String(option[1]).toLowerCase() === requestedLocale) ?? + localeOptions.find((option) => String(option[0]).toLowerCase() === shortLocale); // ensure this lang exists in localeOptions above, otherwise fallback to en - if (thisLocale === "en" || !localeOptions.some(([code]) => code === thisLocale)) { + if (!localeOption || localeOption[0] === "en") { return Object.assign({}, langList, langEn); } - return Object.assign({}, langList, langEn, localeOptions.find(([code]) => code === thisLocale)?.[2]); + return Object.assign({}, langList, langEn, localeOption[2]); }; const getFlagCodeForLocale = (locale?: string) => { - const thisLocale = (locale || "en").slice(0, 2); + const requestedLocale = (locale || "en").toLowerCase(); + if (requestedLocale === "zh-tw") { + return "TW"; + } + const thisLocale = requestedLocale.slice(0, 2); // only add to this if your flag is different from the locale code const specialCases: Record = { @@ -160,4 +170,4 @@ const T = ({ //console.log("L:", localeOptions); -export { localeOptions, getFlagCodeForLocale, getLocale, isRTLLocale, createIntl, changeLocale, intl, T }; +export { localeOptions, loadMessages, getFlagCodeForLocale, getLocale, isRTLLocale, createIntl, changeLocale, intl, T }; diff --git a/frontend/src/locale/Utils.test.tsx b/frontend/src/locale/Utils.test.tsx index 77db2b6b6..0111c242d 100644 --- a/frontend/src/locale/Utils.test.tsx +++ b/frontend/src/locale/Utils.test.tsx @@ -1,4 +1,6 @@ -import { formatDateTime, getFlagCodeForLocale } from "src/locale"; +import { changeLocale, formatDateTime, getFlagCodeForLocale, getLocale, loadMessages } from "src/locale"; +import { getHelpFile } from "src/locale/src/HelpDoc"; +import * as zhTwHelp from "src/locale/src/HelpDoc/zh-tw"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; describe("DateFormatter", () => { @@ -83,6 +85,9 @@ describe("getFlagCodeForLocale", () => { it("returns correct flag code for special-case locales", () => { expect(getFlagCodeForLocale("ja-JP")).toBe("JP"); expect(getFlagCodeForLocale("zh-CN")).toBe("CN"); + expect(getFlagCodeForLocale("zh")).toBe("CN"); + expect(getFlagCodeForLocale("zh-TW")).toBe("TW"); + expect(getFlagCodeForLocale("ZH-tw")).toBe("TW"); expect(getFlagCodeForLocale("vi-VN")).toBe("VN"); expect(getFlagCodeForLocale("ko-KR")).toBe("KR"); expect(getFlagCodeForLocale("cs-CZ")).toBe("CZ"); @@ -123,3 +128,40 @@ describe("formatDateTime Vietnamese locale", () => { } }); }); + +describe("Chinese locale variants", () => { + it("loads Traditional Chinese by full locale before falling back to Simplified Chinese", () => { + expect(loadMessages("ZH-tw")).toEqual(loadMessages("zh-TW")); + expect(loadMessages("zh-TW").save).toBe("儲存"); + expect(loadMessages("zh").save).toBe("保存"); + expect(loadMessages("zh-CN")).toEqual(loadMessages("zh")); + expect(loadMessages("zh-HK")).toEqual(loadMessages("zh")); + }); + + it("preserves the full locale and resets RTL layout when switching to Traditional Chinese", () => { + const originalLocale = getLocale(); + const savedLocale = window.localStorage.getItem("locale"); + try { + changeLocale("fa-IR"); + expect(document.documentElement.dir).toBe("rtl"); + changeLocale("zh-TW"); + expect(getLocale()).toBe("zh-TW"); + expect(window.localStorage.getItem("locale")).toBe("zh-TW"); + expect(document.documentElement.lang).toBe("zh-TW"); + expect(document.documentElement.dir).toBe("ltr"); + } finally { + changeLocale(originalLocale); + if (savedLocale === null) { + window.localStorage.removeItem("locale"); + } else { + window.localStorage.setItem("locale", savedLocale); + } + } + }); + + it("selects HelpDocs by full locale before falling back to the language code", () => { + expect(getHelpFile("ZH-tw", "AccessLists")).toBe(getHelpFile("zh-TW", "AccessLists")); + expect(getHelpFile("zh-TW", "AccessLists")).toBe(zhTwHelp.AccessLists.default); + expect(getHelpFile("zh-CN", "AccessLists")).toBe(getHelpFile("zh", "AccessLists")); + }); +}); diff --git a/frontend/src/locale/src/HelpDoc/index.ts b/frontend/src/locale/src/HelpDoc/index.ts index ceddf4c78..08f7618d8 100644 --- a/frontend/src/locale/src/HelpDoc/index.ts +++ b/frontend/src/locale/src/HelpDoc/index.ts @@ -17,19 +17,53 @@ import * as sk from "./sk/index"; import * as cs from "./cs/index"; import * as vi from "./vi/index"; import * as zh from "./zh/index"; +import * as zhTw from "./zh-tw/index"; import * as tr from "./tr/index"; import * as hu from "./hu/index"; import * as uk from "./uk/index"; import * as az from "./az/index"; import * as fa from "./fa/index"; -const items: any = { en, de, pt, es, et, ja, sk, cs, zh, pl, ru, it, vi, nl, bg, ko, ga, id, fr, tr, hu, uk, az, fa }; +const items: any = { + en, + de, + pt, + es, + et, + ja, + sk, + cs, + zh, + "zh-TW": zhTw, + pl, + ru, + it, + vi, + nl, + bg, + ko, + ga, + id, + fr, + tr, + hu, + uk, + az, + fa, +}; const fallbackLang = "en"; export const getHelpFile = (lang: string, section: string): string => { - if (typeof items[lang] !== "undefined" && typeof items[lang][section] !== "undefined") { - return items[lang][section].default; + const requestedLang = lang.toLowerCase(); + const exactLang = Object.keys(items).find((key) => key.toLowerCase() === requestedLang); + const shortLang = Object.keys(items).find((key) => key.toLowerCase() === requestedLang.slice(0, 2)); + + if (exactLang && typeof items[exactLang][section] !== "undefined") { + return items[exactLang][section].default; + } + if (shortLang && typeof items[shortLang][section] !== "undefined") { + return items[shortLang][section].default; } // Fallback to English if (typeof items[fallbackLang] !== "undefined" && typeof items[fallbackLang][section] !== "undefined") { diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md b/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md new file mode 100644 index 000000000..d8ba7ba2a --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/AccessLists.md @@ -0,0 +1,7 @@ +## 什麼是存取清單? + +存取清單可針對特定用戶端 IP 位址設定黑名單或白名單,並可為代理主機設定 HTTP 基本驗證。 + +您可以在單一存取清單中設定多項用戶端規則、使用者名稱與密碼,再將其套用至一個或多個 _代理主機_。 + +對於透過代理轉送、但本身未內建驗證機制的網路服務,或需要防止未知用戶端存取時,這項功能格外實用。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md b/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md new file mode 100644 index 000000000..4199c2867 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/Certificates.md @@ -0,0 +1,21 @@ +## 憑證說明 + +### HTTP 憑證 + +使用 HTTP 驗證方式申請憑證時,Let's Encrypt 伺服器會嘗試透過 HTTP(不是 HTTPS!)連線至您的網域;驗證成功後,便會核發憑證。 + +使用此方式前,您必須為網域建立一個可透過 HTTP 存取、且指向此 Nginx 安裝環境的 _代理主機_。憑證核發後,您可以修改該 _代理主機_,讓 HTTPS 連線也使用此憑證。不過,為了讓憑證能夠續期,_代理主機_ 仍須保留 HTTP 存取設定。 + +此流程不支援萬用字元網域。 + +### DNS 憑證 + +使用 DNS 驗證方式申請憑證時,您需要使用 DNS 服務供應商外掛程式。系統會透過該 DNS 服務供應商在您的網域中建立暫時性的 DNS 紀錄,接著 Let's Encrypt 會查詢這些紀錄,以確認您是網域所有者;驗證成功後,便會核發憑證。 + +申請此類憑證前,您不必先建立 _代理主機_,也不必將 _代理主機_ 設定為可透過 HTTP 存取。 + +此流程支援萬用字元網域。 + +### 自訂憑證 + +使用此選項,上傳由您自己的憑證授權單位提供的 SSL 憑證。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md new file mode 100644 index 000000000..1aa6b6457 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/DeadHosts.md @@ -0,0 +1,7 @@ +## 什麼是 404 主機? + +404 主機就是用來顯示 404 頁面的主機設定。 + +當您的網域已被搜尋引擎收錄,而您想提供更友善的錯誤頁面,或明確告知搜尋引擎的索引程式該網域的頁面已不存在時,這項功能會很實用。 + +使用 404 主機的另一項好處,是可以從記錄檔追蹤對該主機的存取,並查看來源網址。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md new file mode 100644 index 000000000..a6d27c361 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/ProxyHosts.md @@ -0,0 +1,7 @@ +## 什麼是代理主機? + +代理主機是接收連線的端點,會將連線轉送至您指定的網路服務。 + +它可選擇代為處理 SSL 連線,適用於未內建 SSL 支援的服務。 + +代理主機是 Nginx Proxy Manager 最常見的用途。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md b/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md new file mode 100644 index 000000000..3876a4cec --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/RedirectionHosts.md @@ -0,0 +1,5 @@ +## 什麼是重新導向主機? + +重新導向主機會將送往指定網域的請求重新導向,讓訪客前往另一個網域。 + +這類主機最常用於網站更換網域後,搜尋引擎或其他來源的連結仍指向舊網域的情況。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md b/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md new file mode 100644 index 000000000..b068f9b26 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-tw/Streams.md @@ -0,0 +1,5 @@ +## 什麼是連接埠轉送? + +連接埠轉送是 Nginx 相對較新的功能,可將 TCP/UDP 流量直接轉送至網路中的另一台電腦。 + +若您正在架設遊戲伺服器、FTP 伺服器或 SSH 伺服器,這項功能會很方便。 diff --git a/frontend/src/locale/src/HelpDoc/zh-tw/index.ts b/frontend/src/locale/src/HelpDoc/zh-tw/index.ts new file mode 100644 index 000000000..a9bb46ba7 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/zh-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"; diff --git a/frontend/src/locale/src/lang-list.json b/frontend/src/locale/src/lang-list.json index 9ba12915a..e71e61c79 100644 --- a/frontend/src/locale/src/lang-list.json +++ b/frontend/src/locale/src/lang-list.json @@ -36,7 +36,10 @@ "defaultMessage": "Čeština" }, "locale-zh-CN": { - "defaultMessage": "中文" + "defaultMessage": "简体中文" + }, + "locale-zh-TW": { + "defaultMessage": "繁體中文(台灣)" }, "locale-pl-PL": { "defaultMessage": "Polski" diff --git a/frontend/src/locale/src/zh-tw.json b/frontend/src/locale/src/zh-tw.json new file mode 100644 index 000000000..fb4c79525 --- /dev/null +++ b/frontend/src/locale/src/zh-tw.json @@ -0,0 +1,854 @@ +{ + "2fa.backup-codes-remaining": { + "defaultMessage": "剩餘備用碼:{count}" + }, + "2fa.backup-warning": { + "defaultMessage": "請將這些備用碼存放在安全的地方。每個備用碼都只能使用一次。" + }, + "2fa.disable": { + "defaultMessage": "停用雙因素驗證" + }, + "2fa.disable-confirm": { + "defaultMessage": "停用 2FA" + }, + "2fa.disable-warning": { + "defaultMessage": "停用雙因素驗證會降低帳號的安全性。" + }, + "2fa.disabled": { + "defaultMessage": "已停用" + }, + "2fa.done": { + "defaultMessage": "我已儲存備用碼" + }, + "2fa.enable": { + "defaultMessage": "啟用雙因素驗證" + }, + "2fa.enabled": { + "defaultMessage": "已啟用" + }, + "2fa.enter-code": { + "defaultMessage": "輸入驗證碼" + }, + "2fa.enter-code-disable": { + "defaultMessage": "輸入驗證碼以停用" + }, + "2fa.regenerate": { + "defaultMessage": "重新產生" + }, + "2fa.regenerate-backup": { + "defaultMessage": "重新產生備用碼" + }, + "2fa.regenerate-instructions": { + "defaultMessage": "請輸入驗證碼以產生新的備用碼。舊的備用碼將會失效。" + }, + "2fa.secret-key": { + "defaultMessage": "祕密金鑰" + }, + "2fa.setup-instructions": { + "defaultMessage": "請用驗證器 App 掃描此 QR Code,或手動輸入祕密金鑰。" + }, + "2fa.status": { + "defaultMessage": "狀態" + }, + "2fa.title": { + "defaultMessage": "雙因素驗證" + }, + "2fa.verify-enable": { + "defaultMessage": "驗證並啟用" + }, + "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": "允許與拒絕規則會依照設定順序套用。" + }, + "access-list.inherit": { + "defaultMessage": "沿用代理主機的設定" + }, + "access-list.inherit.subtitle": { + "defaultMessage": "使用代理主機的存取清單" + }, + "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} 位使用者,{rules} 條規則,建立於 {date}" + }, + "access-lists": { + "defaultMessage": "存取清單" + }, + "action.add": { + "defaultMessage": "新增" + }, + "action.add-location": { + "defaultMessage": "新增路徑" + }, + "action.allow": { + "defaultMessage": "允許" + }, + "action.clear": { + "defaultMessage": "清除" + }, + "action.close": { + "defaultMessage": "關閉" + }, + "action.delete": { + "defaultMessage": "刪除" + }, + "action.deny": { + "defaultMessage": "拒絕" + }, + "action.disable": { + "defaultMessage": "停用" + }, + "action.download": { + "defaultMessage": "下載" + }, + "action.edit": { + "defaultMessage": "編輯" + }, + "action.enable": { + "defaultMessage": "啟用" + }, + "action.logs": { + "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": "DNS 傳播時間(秒)" + }, + "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": "這些網域必須已設定為指向目前這套 Nginx Proxy Manager。" + }, + "certificates.key-type": { + "defaultMessage": "金鑰類型" + }, + "certificates.key-type-description": { + "defaultMessage": "RSA 相容性較廣;ECDSA 速度較快且更安全,但較舊的系統可能不支援。" + }, + "certificates.key-type-ecdsa": { + "defaultMessage": "ECDSA 256" + }, + "certificates.key-type-rsa": { + "defaultMessage": "RSA 2048" + }, + "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.error": { + "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} 個 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": "此憑證授權單位不支援萬用字元" + }, + "domains.advanced": { + "defaultMessage": "進階" + }, + "domains.force-ssl": { + "defaultMessage": "強制使用 SSL" + }, + "domains.hsts-enabled": { + "defaultMessage": "啟用 HSTS" + }, + "domains.hsts-subdomains": { + "defaultMessage": "HSTS 子網域" + }, + "domains.http2-support": { + "defaultMessage": "支援 HTTP/2" + }, + "domains.trust-forwarded-proto": { + "defaultMessage": "信任上游代理傳來的 X-Forwarded-Proto/X-Forwarded-Scheme 標頭" + }, + "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 本專案" + }, + "form.unsaved-changes": { + "defaultMessage": "您有尚未儲存的變更,確定要捨棄嗎?" + }, + "host.flags.block-exploits": { + "defaultMessage": "阻擋常見攻擊" + }, + "host.flags.cache-assets": { + "defaultMessage": "快取靜態資源" + }, + "host.flags.preserve-path": { + "defaultMessage": "保留路徑" + }, + "host.flags.protocols": { + "defaultMessage": "通訊協定" + }, + "host.flags.websockets-upgrade": { + "defaultMessage": "WebSocket 支援" + }, + "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": "載入中…" + }, + "location.advanced-config": { + "defaultMessage": "含有自訂 Nginx 設定" + }, + "location.filter": { + "defaultMessage": "依路徑或目的地篩選" + }, + "login.2fa-code": { + "defaultMessage": "驗證碼" + }, + "login.2fa-code-placeholder": { + "defaultMessage": "輸入驗證碼" + }, + "login.2fa-description": { + "defaultMessage": "請輸入驗證器 App 顯示的驗證碼" + }, + "login.2fa-title": { + "defaultMessage": "雙因素驗證" + }, + "login.2fa-verify": { + "defaultMessage": "驗證" + }, + "login.title": { + "defaultMessage": "登入您的帳號" + }, + "logs": { + "defaultMessage": "紀錄" + }, + "logs.channel.access": { + "defaultMessage": "存取" + }, + "logs.channel.error": { + "defaultMessage": "錯誤" + }, + "logs.download": { + "defaultMessage": "下載" + }, + "logs.empty": { + "defaultMessage": "尚無紀錄。" + }, + "logs.level": { + "defaultMessage": "等級" + }, + "logs.level.all": { + "defaultMessage": "所有等級" + }, + "logs.lines": { + "defaultMessage": "行數" + }, + "logs.live": { + "defaultMessage": "即時更新" + }, + "logs.paused": { + "defaultMessage": "已暫停" + }, + "logs.refresh": { + "defaultMessage": "重新整理" + }, + "logs.search-placeholder": { + "defaultMessage": "搜尋紀錄…" + }, + "logs.source": { + "defaultMessage": "來源" + }, + "logs.source.system": { + "defaultMessage": "系統" + }, + "logs.truncated-warning": { + "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.allowed-proxy-hosts": { + "defaultMessage": "允許存取的代理主機" + }, + "permissions.allowed-proxy-hosts-help": { + "defaultMessage": "另外開放存取其他使用者建立的指定代理主機(使用者自己建立的主機原本就能存取)。" + }, + "permissions.hidden": { + "defaultMessage": "隱藏" + }, + "permissions.manage": { + "defaultMessage": "管理" + }, + "permissions.no-proxy-hosts": { + "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} 個重新導向主機" + }, + "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} 組連接埠轉送" + }, + "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": "切換至淺色模式" + }, + "user.two-factor": { + "defaultMessage": "雙因素驗證" + }, + "username": { + "defaultMessage": "使用者名稱" + }, + "users": { + "defaultMessage": "使用者" + } +} diff --git a/frontend/src/modals/HelpModal.tsx b/frontend/src/modals/HelpModal.tsx index f05816142..1d395fc11 100644 --- a/frontend/src/modals/HelpModal.tsx +++ b/frontend/src/modals/HelpModal.tsx @@ -18,7 +18,7 @@ const showHelpModal = (section: string, color?: string) => { const HelpModal = EasyModal.create(({ section, color, visible, remove }: Props) => { const [markdownText, setMarkdownText] = useState(""); - const lang = getLocale(true); + const lang = getLocale(); useEffect(() => { try {