From d17fc6a174503778f3ed69adc4babac8b1e14932 Mon Sep 17 00:00:00 2001
From: CXM <16154023+littlecxm@users.noreply.github.com>
Date: Wed, 9 Jul 2025 18:53:08 +0800
Subject: [PATCH] feat(localization): add Simplified Chinese (#774)
---
lib/localization/locales/manifest.json | 2 +-
lib/localization/locales/zh-CN.json | 63 +++++++++++++
lib/localization/localization_test.go | 126 +++++++------------------
3 files changed, 96 insertions(+), 95 deletions(-)
create mode 100644 lib/localization/locales/zh-CN.json
diff --git a/lib/localization/locales/manifest.json b/lib/localization/locales/manifest.json
index 2138d2d..e446484 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", "zh-TW"]
+ "supportedLanguages": ["en", "fr", "es", "pt-BR", "de", "tr", "zh-CN", "zh-TW"]
}
diff --git a/lib/localization/locales/zh-CN.json b/lib/localization/locales/zh-CN.json
new file mode 100644
index 0000000..11c7f31
--- /dev/null
+++ b/lib/localization/locales/zh-CN.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": "计算挑战失败:"
+}
diff --git a/lib/localization/localization_test.go b/lib/localization/localization_test.go
index cdfdf5b..ee9a113 100644
--- a/lib/localization/localization_test.go
+++ b/lib/localization/localization_test.go
@@ -2,6 +2,7 @@ package localization
import (
"encoding/json"
+ "fmt"
"sort"
"testing"
@@ -11,105 +12,42 @@ import (
func TestLocalizationService(t *testing.T) {
service := NewLocalizationService()
- t.Run("English localization", func(t *testing.T) {
- localizer := service.GetLocalizer("en")
- result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
- if result != "Loading..." {
- t.Errorf("Expected 'Loading...', got '%s'", result)
- }
- })
+ loadingStrMap := map[string]string{
+ "en": "Loading...",
+ "fr": "Chargement...",
+ "de": "Ladevorgang...",
+ "tr": "Yükleniyor...",
+ "zh-CN": "加载中...",
+ "zh-TW": "載入中...",
+ }
- t.Run("French localization", func(t *testing.T) {
- localizer := service.GetLocalizer("fr")
- result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
- if result != "Chargement..." {
- t.Errorf("Expected 'Chargement...', got '%s'", result)
- }
- })
-
- t.Run("German localization", func(t *testing.T) {
- localizer := service.GetLocalizer("de")
- result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
- if result != "Ladevorgang..." {
- t.Errorf("Expected 'Ladevorgang...', got '%s'", result)
- }
- })
-
- t.Run("Turkish localization", func(t *testing.T) {
- localizer := service.GetLocalizer("tr")
- result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
- if result != "Yükleniyor..." {
- t.Errorf("Expected 'Yükleniyor...', got '%s'", result)
- }
- })
-
- 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{
- "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)
+ for lang, expected := range loadingStrMap {
+ t.Run(fmt.Sprintf("%s localization", lang), func(t *testing.T) {
+ localizer := service.GetLocalizer(lang)
+ result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"})
+ if result != expected {
+ t.Errorf("Expected '%s', got '%s'", expected, result)
}
- }
- })
+ })
+ }
- t.Run("All required keys exist in French", func(t *testing.T) {
- localizer := service.GetLocalizer("fr")
- requiredKeys := []string{
- "loading", "why_am_i_seeing", "protected_by", "made_with",
- "mascot_design", "try_again", "go_home", "javascript_required",
- }
+ // Test for requiredKeys localization
+ 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)
+ for lang := range loadingStrMap {
+ t.Run(fmt.Sprintf("All required keys exist in %s", lang), func(t *testing.T) {
+ loc := service.GetLocalizer(lang)
+ for _, key := range requiredKeys {
+ result := loc.MustLocalize(&i18n.LocalizeConfig{MessageID: key})
+ if result == "" {
+ t.Errorf("Key '%s' returned empty string", key)
+ }
}
- }
- })
-
- t.Run("All required keys exist in Turkish", func(t *testing.T) {
- localizer := service.GetLocalizer("tr")
- 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)
- }
- }
- })
-
- 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 {