diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md
index e53720d..ecd1063 100644
--- a/docs/docs/CHANGELOG.md
+++ b/docs/docs/CHANGELOG.md
@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add translation for Turkish language ([#751](https://github.com/TecharoHQ/anubis/pull/751))
- Allow [Common Crawl](https://commoncrawl.org/) by default so scrapers have less incentive to scrape
- The [bbolt storage backend](./admin/policies.mdx#bbolt) now runs its cleanup every hour instead of every five minutes.
+- Add translation for Traditional Chinese ([#759](https://github.com/TecharoHQ/anubis/pull/759))
### Potentially breaking changes
diff --git a/lib/localization/locales/manifest.json b/lib/localization/locales/manifest.json
index 7e64673..2138d2d 100644
--- a/lib/localization/locales/manifest.json
+++ b/lib/localization/locales/manifest.json
@@ -1,3 +1,3 @@
{
- "supportedLanguages": ["en", "fr", "es", "pt-BR", "de", "tr"]
+ "supportedLanguages": ["en", "fr", "es", "pt-BR", "de", "tr", "zh-TW"]
}
diff --git a/lib/localization/locales/zh-TW.json b/lib/localization/locales/zh-TW.json
new file mode 100644
index 0000000..10e7e78
--- /dev/null
+++ b/lib/localization/locales/zh-TW.json
@@ -0,0 +1,63 @@
+{
+ "loading": "載入中...",
+ "why_am_i_seeing": "為什麼我看到這個?",
+ "protected_by": "保護由",
+ "made_with": "在 🇨🇦 用 ❤️ 製作",
+ "mascot_design": "吉祥物由",
+ "ai_companies_explanation": "您會看到這個畫面,是因為網站管理員啟用了 Anubis 來保護伺服器,避免 AI 公司大量爬取網站內容。這類行為會導致網站當機,讓所有使用者都無法正常存取資源。",
+ "anubis_compromise": "Anubis 是一種折衷做法。它採用了類似 Hashcash 的工作量證明機制(Proof-of-Work),該機制最初是為了減少垃圾郵件而提出。其核心概念是:對個別使用者而言,額外的運算負擔可以忽略,但對大規模爬蟲來說,累積起來的成本將大幅增加,從而讓爬取行為變得更困難。",
+ "hack_purpose": "本質上,這是一種權宜的解法,目的是提供一個「夠用」的暫時性防護措施,好讓開發者有更多時間針對無頭瀏覽器進行指紋特徵辨識(例如:分析其字型渲染方式),以便未來不再需要對可能為合法使用者的訪客展示工作量證明頁面。",
+ "jshelter_note": "請注意,Anubis 需要使用現代 JavaScript 功能,而像 JShelter 這類外掛可能會阻擋這些功能。請為此網域停用 JShelter 或類似的插件。",
+ "version_info": "這個網站正在運行 Anubis 版本",
+ "try_again": "再試一次",
+ "go_home": "回首頁",
+ "contact_webmaster": "或者您覺得您不應該被封鎖,請聯絡站點管理員於",
+ "connection_security": "請稍等,我們需要在繼續之前檢閱您的連線安全性。",
+ "javascript_required": "很遺憾,您必須啟用 JavaScript 才能通過這項驗證。這是因為 AI 公司已經改變了網站託管的社會契約,因此我們必須採取這樣的保護機制。無需 JavaScript 的解法仍在開發中。",
+ "benchmark_requires_js": "執行基準測試工具需要啟用 JavaScript。",
+ "difficulty": "難度:",
+ "algorithm": "演算法:",
+ "compare": "比較:",
+ "time": "時間",
+ "iters": "迭代",
+ "time_a": "時間 A",
+ "iters_a": "迭代 A",
+ "time_b": "時間 B",
+ "iters_b": "迭代 B",
+ "static_check_endpoint": "這是提供給您的反向代理伺服器使用的檢查端點。",
+ "authorization_required": "需要認證",
+ "cookies_disabled": "您的瀏覽器目前已停用 Cookie,為了確認您是合法使用者,Anubis 需要啟用 Cookie。 請為此網域啟用 Cookie",
+ "access_denied": "拒絕存取:錯誤代碼",
+ "dronebl_entry": "DroneBL 回報了一筆紀錄",
+ "see_dronebl_lookup": "見",
+ "internal_server_error": "內部伺服器錯誤:管理員錯誤地配置了 Anubis。 請聯絡管理員要求他們檢閱日誌",
+ "invalid_redirect": "無效的重新導向",
+ "redirect_not_parseable": "重新導向 URL 無法解析",
+ "redirect_domain_not_allowed": "重新導向的網域並不允許",
+ "failed_to_sign_jwt": "簽署 JWT 失敗",
+ "invalid_invocation": "無效的 MakeChallenge 呼叫",
+ "client_error_browser": "客戶端錯誤:請確保您的瀏覽器是最新版本並稍候再試。",
+ "oh_noes": "哎呀糟糕了!",
+ "benchmarking_anubis": "正在進行 Anubis 效能測試!",
+ "you_are_not_a_bot": "你不是機器人!",
+ "making_sure_not_bot": "正在確認你是不是機器人!",
+ "celphase": "CELPHASE 設計",
+ "js_web_crypto_error": "您的瀏覽器無法正常使用 web.crypto 元件。您是否透過安全連線(HTTPS)檢視此網站?",
+ "js_web_workers_error": "您的瀏覽器並不支援 Web workers (Anubis 使用這個來避免凍結您的瀏覽器 )您有安裝像是 JShelter 之類的插件嗎?",
+ "js_cookies_error": "您的瀏覽器無法儲存 Cookie。 Anubis 會使用 Cookie 儲存簽署的憑證,以判斷使用者是否已通過驗證。請為此網域啟用 Cookie 儲存功能。 請注意,Anubis 儲存的 Cookie 名稱可能會變動,且其名稱與內容不屬於公開 API 的一部分。",
+ "js_context_not_secure": "您的內容並不安全",
+ "js_context_not_secure_msg": "請嘗試使用 HTTPS 連線,或聯繫網站管理員設定 HTTPS。更多資訊請參見 MDN。",
+ "js_calculating": "計算中...",
+ "js_missing_feature": "缺少功能",
+ "js_challenge_error": "挑戰錯誤!",
+ "js_challenge_error_msg": "解決檢查演算法失敗。 您可能會想要重整頁面。",
+ "js_calculating_difficulty": "計算中...
難度:",
+ "js_speed": "速度:",
+ "js_verification_longer": "驗證所花的時間高於預期。 請不要重整頁面。",
+ "js_success": "成功!",
+ "js_done_took": "完成! 花費",
+ "js_iterations": "迭代",
+ "js_finished_reading": "我讀完了,繼續 →",
+ "js_calculation_error": "計算錯誤!",
+ "js_calculation_error_msg": "計算挑戰失敗:"
+}
\ No newline at end of file
diff --git a/lib/localization/localization_test.go b/lib/localization/localization_test.go
index 840d837..cdfdf5b 100644
--- a/lib/localization/localization_test.go
+++ b/lib/localization/localization_test.go
@@ -43,6 +43,14 @@ func TestLocalizationService(t *testing.T) {
}
})
+ t.Run("Traditional Chinese localization", func(t *testing.T) {
+ localizer := service.GetLocalizer("zh-TW")
+ result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
+ if result != "載入中..." {
+ t.Errorf("Expected '載入中...', got '%s'", result)
+ }
+ })
+
t.Run("All required keys exist in English", func(t *testing.T) {
localizer := service.GetLocalizer("en")
requiredKeys := []string{
@@ -87,6 +95,21 @@ func TestLocalizationService(t *testing.T) {
}
}
})
+
+ t.Run("All required keys exist in Traditional Chinese", func(t *testing.T) {
+ localizer := service.GetLocalizer("zh-TW")
+ requiredKeys := []string{
+ "loading", "why_am_i_seeing", "protected_by", "made_with",
+ "mascot_design", "try_again", "go_home", "javascript_required",
+ }
+
+ for _, key := range requiredKeys {
+ result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
+ if result == "" {
+ t.Errorf("Key '%s' returned empty string", key)
+ }
+ }
+ })
}
type manifest struct {