language.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import * as i18n from "@solid-primitives/i18n"
  2. import { createEffect, createMemo } from "solid-js"
  3. import { createStore } from "solid-js/store"
  4. import { createSimpleContext } from "@opencode-ai/ui/context"
  5. import { Persist, persisted } from "@/utils/persist"
  6. import { dict as en } from "@/i18n/en"
  7. import { dict as zh } from "@/i18n/zh"
  8. import { dict as zht } from "@/i18n/zht"
  9. import { dict as ko } from "@/i18n/ko"
  10. import { dict as de } from "@/i18n/de"
  11. import { dict as es } from "@/i18n/es"
  12. import { dict as fr } from "@/i18n/fr"
  13. import { dict as da } from "@/i18n/da"
  14. import { dict as ja } from "@/i18n/ja"
  15. import { dict as pl } from "@/i18n/pl"
  16. import { dict as ru } from "@/i18n/ru"
  17. import { dict as ar } from "@/i18n/ar"
  18. import { dict as no } from "@/i18n/no"
  19. import { dict as br } from "@/i18n/br"
  20. import { dict as th } from "@/i18n/th"
  21. import { dict as bs } from "@/i18n/bs"
  22. import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
  23. import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
  24. import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
  25. import { dict as uiKo } from "@opencode-ai/ui/i18n/ko"
  26. import { dict as uiDe } from "@opencode-ai/ui/i18n/de"
  27. import { dict as uiEs } from "@opencode-ai/ui/i18n/es"
  28. import { dict as uiFr } from "@opencode-ai/ui/i18n/fr"
  29. import { dict as uiDa } from "@opencode-ai/ui/i18n/da"
  30. import { dict as uiJa } from "@opencode-ai/ui/i18n/ja"
  31. import { dict as uiPl } from "@opencode-ai/ui/i18n/pl"
  32. import { dict as uiRu } from "@opencode-ai/ui/i18n/ru"
  33. import { dict as uiAr } from "@opencode-ai/ui/i18n/ar"
  34. import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
  35. import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
  36. import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
  37. import { dict as uiBs } from "@opencode-ai/ui/i18n/bs"
  38. export type Locale =
  39. | "en"
  40. | "zh"
  41. | "zht"
  42. | "ko"
  43. | "de"
  44. | "es"
  45. | "fr"
  46. | "da"
  47. | "ja"
  48. | "pl"
  49. | "ru"
  50. | "ar"
  51. | "no"
  52. | "br"
  53. | "th"
  54. | "bs"
  55. type RawDictionary = typeof en & typeof uiEn
  56. type Dictionary = i18n.Flatten<RawDictionary>
  57. const LOCALES: readonly Locale[] = [
  58. "en",
  59. "zh",
  60. "zht",
  61. "ko",
  62. "de",
  63. "es",
  64. "fr",
  65. "da",
  66. "ja",
  67. "pl",
  68. "ru",
  69. "bs",
  70. "ar",
  71. "no",
  72. "br",
  73. "th",
  74. ]
  75. type ParityKey = "command.session.previous.unseen" | "command.session.next.unseen"
  76. const PARITY_CHECK: Record<Exclude<Locale, "en">, Record<ParityKey, string>> = {
  77. zh,
  78. zht,
  79. ko,
  80. de,
  81. es,
  82. fr,
  83. da,
  84. ja,
  85. pl,
  86. ru,
  87. ar,
  88. no,
  89. br,
  90. th,
  91. bs,
  92. }
  93. void PARITY_CHECK
  94. function detectLocale(): Locale {
  95. if (typeof navigator !== "object") return "en"
  96. const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
  97. for (const language of languages) {
  98. if (!language) continue
  99. if (language.toLowerCase().startsWith("zh")) {
  100. if (language.toLowerCase().includes("hant")) return "zht"
  101. return "zh"
  102. }
  103. if (language.toLowerCase().startsWith("ko")) return "ko"
  104. if (language.toLowerCase().startsWith("de")) return "de"
  105. if (language.toLowerCase().startsWith("es")) return "es"
  106. if (language.toLowerCase().startsWith("fr")) return "fr"
  107. if (language.toLowerCase().startsWith("da")) return "da"
  108. if (language.toLowerCase().startsWith("ja")) return "ja"
  109. if (language.toLowerCase().startsWith("pl")) return "pl"
  110. if (language.toLowerCase().startsWith("ru")) return "ru"
  111. if (language.toLowerCase().startsWith("ar")) return "ar"
  112. if (
  113. language.toLowerCase().startsWith("no") ||
  114. language.toLowerCase().startsWith("nb") ||
  115. language.toLowerCase().startsWith("nn")
  116. )
  117. return "no"
  118. if (language.toLowerCase().startsWith("pt")) return "br"
  119. if (language.toLowerCase().startsWith("th")) return "th"
  120. if (language.toLowerCase().startsWith("bs")) return "bs"
  121. }
  122. return "en"
  123. }
  124. export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({
  125. name: "Language",
  126. init: () => {
  127. const [store, setStore, _, ready] = persisted(
  128. Persist.global("language", ["language.v1"]),
  129. createStore({
  130. locale: detectLocale() as Locale,
  131. }),
  132. )
  133. const locale = createMemo<Locale>(() => {
  134. if (store.locale === "zh") return "zh"
  135. if (store.locale === "zht") return "zht"
  136. if (store.locale === "ko") return "ko"
  137. if (store.locale === "de") return "de"
  138. if (store.locale === "es") return "es"
  139. if (store.locale === "fr") return "fr"
  140. if (store.locale === "da") return "da"
  141. if (store.locale === "ja") return "ja"
  142. if (store.locale === "pl") return "pl"
  143. if (store.locale === "ru") return "ru"
  144. if (store.locale === "ar") return "ar"
  145. if (store.locale === "no") return "no"
  146. if (store.locale === "br") return "br"
  147. if (store.locale === "th") return "th"
  148. if (store.locale === "bs") return "bs"
  149. return "en"
  150. })
  151. createEffect(() => {
  152. const current = locale()
  153. if (store.locale === current) return
  154. setStore("locale", current)
  155. })
  156. const base = i18n.flatten({ ...en, ...uiEn })
  157. const dict = createMemo<Dictionary>(() => {
  158. if (locale() === "en") return base
  159. if (locale() === "zh") return { ...base, ...i18n.flatten({ ...zh, ...uiZh }) }
  160. if (locale() === "zht") return { ...base, ...i18n.flatten({ ...zht, ...uiZht }) }
  161. if (locale() === "de") return { ...base, ...i18n.flatten({ ...de, ...uiDe }) }
  162. if (locale() === "es") return { ...base, ...i18n.flatten({ ...es, ...uiEs }) }
  163. if (locale() === "fr") return { ...base, ...i18n.flatten({ ...fr, ...uiFr }) }
  164. if (locale() === "da") return { ...base, ...i18n.flatten({ ...da, ...uiDa }) }
  165. if (locale() === "ja") return { ...base, ...i18n.flatten({ ...ja, ...uiJa }) }
  166. if (locale() === "pl") return { ...base, ...i18n.flatten({ ...pl, ...uiPl }) }
  167. if (locale() === "ru") return { ...base, ...i18n.flatten({ ...ru, ...uiRu }) }
  168. if (locale() === "ar") return { ...base, ...i18n.flatten({ ...ar, ...uiAr }) }
  169. if (locale() === "no") return { ...base, ...i18n.flatten({ ...no, ...uiNo }) }
  170. if (locale() === "br") return { ...base, ...i18n.flatten({ ...br, ...uiBr }) }
  171. if (locale() === "th") return { ...base, ...i18n.flatten({ ...th, ...uiTh }) }
  172. if (locale() === "bs") return { ...base, ...i18n.flatten({ ...bs, ...uiBs }) }
  173. return { ...base, ...i18n.flatten({ ...ko, ...uiKo }) }
  174. })
  175. const t = i18n.translator(dict, i18n.resolveTemplate)
  176. const labelKey: Record<Locale, keyof Dictionary> = {
  177. en: "language.en",
  178. zh: "language.zh",
  179. zht: "language.zht",
  180. ko: "language.ko",
  181. de: "language.de",
  182. es: "language.es",
  183. fr: "language.fr",
  184. da: "language.da",
  185. ja: "language.ja",
  186. pl: "language.pl",
  187. ru: "language.ru",
  188. ar: "language.ar",
  189. no: "language.no",
  190. br: "language.br",
  191. th: "language.th",
  192. bs: "language.bs",
  193. }
  194. const label = (value: Locale) => t(labelKey[value])
  195. createEffect(() => {
  196. if (typeof document !== "object") return
  197. document.documentElement.lang = locale()
  198. })
  199. return {
  200. ready,
  201. locale,
  202. locales: LOCALES,
  203. label,
  204. t,
  205. setLocale(next: Locale) {
  206. setStore("locale", next)
  207. },
  208. }
  209. },
  210. })