i18n.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. // Package i18n translates the backend's user-facing Chinese strings into
  2. // English when the persisted UI language is English.
  3. //
  4. // The SPA keeps its own Chinese-keyed dictionary (web/src/lib/i18n-en.ts) for
  5. // static UI copy. This package covers the strings the Go backend generates as
  6. // DATA — status text, error messages, probe hints, and country names — which
  7. // the SPA renders verbatim and therefore cannot translate client-side.
  8. //
  9. // The product serves a single administrator whose language is persisted in the
  10. // settings store, so a process-level language value is sufficient; the server
  11. // refreshes it on startup and whenever the preference is read or written.
  12. package i18n
  13. import (
  14. "fmt"
  15. "sync/atomic"
  16. )
  17. // current holds the active UI language: "en" or "zh". It defaults to Chinese so
  18. // the backend behaves exactly as it always has unless the persisted preference
  19. // is explicitly English — the server syncs it from the settings store on
  20. // startup and on every preferences read/write.
  21. var current atomic.Value // stores string
  22. func init() {
  23. current.Store("zh")
  24. }
  25. // Set records the active UI language. Anything other than "zh" is treated as
  26. // English, matching the SPA's fallback.
  27. func Set(language string) {
  28. if language == "zh" {
  29. current.Store("zh")
  30. return
  31. }
  32. current.Store("en")
  33. }
  34. // Lang reports the active UI language ("en" or "zh").
  35. func Lang() string {
  36. if language, ok := current.Load().(string); ok {
  37. return language
  38. }
  39. return "zh"
  40. }
  41. // T translates a Chinese string to English when the active language is
  42. // English; otherwise it returns the input unchanged. Strings with no entry are
  43. // returned as-is (Chinese), mirroring the SPA dictionary's fallback.
  44. func T(zh string) string {
  45. if Lang() != "en" {
  46. return zh
  47. }
  48. if en, ok := zhToEn[zh]; ok {
  49. return en
  50. }
  51. return zh
  52. }
  53. // Tf translates a Chinese printf-style template and then formats it with args,
  54. // so interpolated values land inside the translated sentence.
  55. func Tf(template string, args ...any) string {
  56. return fmt.Sprintf(T(template), args...)
  57. }
  58. // zhToEn maps every user-facing Chinese string the backend emits onto its
  59. // English equivalent. Keys must match the source string byte-for-byte.
  60. var zhToEn = map[string]string{
  61. // ---- eSIM ----
  62. "已启用": "Enabled",
  63. "已禁用": "Disabled",
  64. "该 eSIM 操作暂未实现:当前仅支持列出 Profile 与切卡(启用已安装的 Profile),不支持下载/删除/改名": "This eSIM operation is not available: only listing profiles and switching (enabling an already-installed profile) are supported; download, delete, and rename are not.",
  65. // ---- devices ----
  66. "设备数量已达上限,最多只能添加 %d 台设备": "Device limit reached; at most %d devices can be added.",
  67. "SIM 卡归属地为%s(MCC %s),本服务不向该地区卡片提供数据/短信/VoWiFi": "The SIM's home region is %s (MCC %s); this service does not provide data, SMS, or VoWiFi to cards from that region.",
  68. // ---- settings / update ----
  69. "未配置受信任的软件更新源;不会从未知地址下载或执行文件。": "No trusted update source is configured; no files will be downloaded or executed from unknown addresses.",
  70. "未配置受信任的软件更新源;未执行任何更新。": "No trusted update source is configured; no update was performed.",
  71. // ---- proxy probe / save ----
  72. "检查地址、端口、防火墙与上游代理监听状态。": "Check the address, port, firewall, and that the upstream proxy is listening.",
  73. "该代理不能承载 ePDG 所需的 UDP;启用上游 SOCKS5 UDP 转发后重试。": "This proxy cannot carry the UDP that ePDG requires; enable upstream SOCKS5 UDP forwarding and retry.",
  74. "TCP 握手、认证和 UDP ASSOCIATE 均通过。": "TCP handshake, authentication, and UDP ASSOCIATE all passed.",
  75. "代理已保存;UDP ASSOCIATE 尚未通过。": "Proxy saved; UDP ASSOCIATE has not passed yet.",
  76. "代理已保存,SOCKS5 认证与 UDP ASSOCIATE 均通过。": "Proxy saved; SOCKS5 authentication and UDP ASSOCIATE both passed.",
  77. "代理不能承载 VoWiFi 所需的 UDP。": "The proxy cannot carry the UDP that VoWiFi requires.",
  78. "SOCKS5 认证与 UDP ASSOCIATE 探测通过。": "SOCKS5 authentication and UDP ASSOCIATE probe passed.",
  79. // ---- country names (upstream proxy country rules) ----
  80. "中国": "China",
  81. "中国香港": "Hong Kong (China)",
  82. "中国澳门": "Macau (China)",
  83. "中国台湾": "Taiwan (China)",
  84. "美国": "United States",
  85. "加拿大": "Canada",
  86. "英国": "United Kingdom",
  87. "德国": "Germany",
  88. "法国": "France",
  89. "意大利": "Italy",
  90. "西班牙": "Spain",
  91. "葡萄牙": "Portugal",
  92. "荷兰": "Netherlands",
  93. "比利时": "Belgium",
  94. "瑞士": "Switzerland",
  95. "奥地利": "Austria",
  96. "爱尔兰": "Ireland",
  97. "丹麦": "Denmark",
  98. "瑞典": "Sweden",
  99. "挪威": "Norway",
  100. "芬兰": "Finland",
  101. "波兰": "Poland",
  102. "捷克": "Czechia",
  103. "希腊": "Greece",
  104. "罗马尼亚": "Romania",
  105. "匈牙利": "Hungary",
  106. "乌克兰": "Ukraine",
  107. "俄罗斯": "Russia",
  108. "土耳其": "Türkiye",
  109. "日本": "Japan",
  110. "韩国": "South Korea",
  111. "新加坡": "Singapore",
  112. "马来西亚": "Malaysia",
  113. "泰国": "Thailand",
  114. "越南": "Vietnam",
  115. "菲律宾": "Philippines",
  116. "印度尼西亚": "Indonesia",
  117. "印度": "India",
  118. "巴基斯坦": "Pakistan",
  119. "阿联酋": "United Arab Emirates",
  120. "沙特阿拉伯": "Saudi Arabia",
  121. "以色列": "Israel",
  122. "澳大利亚": "Australia",
  123. "新西兰": "New Zealand",
  124. "巴西": "Brazil",
  125. "墨西哥": "Mexico",
  126. "阿根廷": "Argentina",
  127. "智利": "Chile",
  128. "哥伦比亚": "Colombia",
  129. "南非": "South Africa",
  130. "埃及": "Egypt",
  131. "尼日利亚": "Nigeria",
  132. "肯尼亚": "Kenya",
  133. }