summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context/language.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/app/src/context/language.tsx')
-rw-r--r--packages/app/src/context/language.tsx144
1 files changed, 78 insertions, 66 deletions
diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx
index 51dc09cd7..b1edd541c 100644
--- a/packages/app/src/context/language.tsx
+++ b/packages/app/src/context/language.tsx
@@ -1,10 +1,42 @@
import * as i18n from "@solid-primitives/i18n"
-import { createEffect, createMemo, createResource } from "solid-js"
+import { createEffect, createMemo } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { Persist, persisted } from "@/utils/persist"
import { dict as en } from "@/i18n/en"
+import { dict as zh } from "@/i18n/zh"
+import { dict as zht } from "@/i18n/zht"
+import { dict as ko } from "@/i18n/ko"
+import { dict as de } from "@/i18n/de"
+import { dict as es } from "@/i18n/es"
+import { dict as fr } from "@/i18n/fr"
+import { dict as da } from "@/i18n/da"
+import { dict as ja } from "@/i18n/ja"
+import { dict as pl } from "@/i18n/pl"
+import { dict as ru } from "@/i18n/ru"
+import { dict as ar } from "@/i18n/ar"
+import { dict as no } from "@/i18n/no"
+import { dict as br } from "@/i18n/br"
+import { dict as th } from "@/i18n/th"
+import { dict as bs } from "@/i18n/bs"
+import { dict as tr } from "@/i18n/tr"
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
+import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
+import { dict as uiZht } from "@opencode-ai/ui/i18n/zht"
+import { dict as uiKo } from "@opencode-ai/ui/i18n/ko"
+import { dict as uiDe } from "@opencode-ai/ui/i18n/de"
+import { dict as uiEs } from "@opencode-ai/ui/i18n/es"
+import { dict as uiFr } from "@opencode-ai/ui/i18n/fr"
+import { dict as uiDa } from "@opencode-ai/ui/i18n/da"
+import { dict as uiJa } from "@opencode-ai/ui/i18n/ja"
+import { dict as uiPl } from "@opencode-ai/ui/i18n/pl"
+import { dict as uiRu } from "@opencode-ai/ui/i18n/ru"
+import { dict as uiAr } from "@opencode-ai/ui/i18n/ar"
+import { dict as uiNo } from "@opencode-ai/ui/i18n/no"
+import { dict as uiBr } from "@opencode-ai/ui/i18n/br"
+import { dict as uiTh } from "@opencode-ai/ui/i18n/th"
+import { dict as uiBs } from "@opencode-ai/ui/i18n/bs"
+import { dict as uiTr } from "@opencode-ai/ui/i18n/tr"
export type Locale =
| "en"
@@ -27,7 +59,6 @@ export type Locale =
type RawDictionary = typeof en & typeof uiEn
type Dictionary = i18n.Flatten<RawDictionary>
-type Source = { dict: Record<string, string> }
function cookie(locale: Locale) {
return `oc_locale=${encodeURIComponent(locale)}; Path=/; Max-Age=31536000; SameSite=Lax`
@@ -94,43 +125,24 @@ const LABEL_KEY: Record<Locale, keyof Dictionary> = {
}
const base = i18n.flatten({ ...en, ...uiEn })
-const dicts = new Map<Locale, Dictionary>([["en", base]])
-
-const merge = (app: Promise<Source>, ui: Promise<Source>) =>
- Promise.all([app, ui]).then(([a, b]) => ({ ...base, ...i18n.flatten({ ...a.dict, ...b.dict }) }) as Dictionary)
-
-const loaders: Record<Exclude<Locale, "en">, () => Promise<Dictionary>> = {
- zh: () => merge(import("@/i18n/zh"), import("@opencode-ai/ui/i18n/zh")),
- zht: () => merge(import("@/i18n/zht"), import("@opencode-ai/ui/i18n/zht")),
- ko: () => merge(import("@/i18n/ko"), import("@opencode-ai/ui/i18n/ko")),
- de: () => merge(import("@/i18n/de"), import("@opencode-ai/ui/i18n/de")),
- es: () => merge(import("@/i18n/es"), import("@opencode-ai/ui/i18n/es")),
- fr: () => merge(import("@/i18n/fr"), import("@opencode-ai/ui/i18n/fr")),
- da: () => merge(import("@/i18n/da"), import("@opencode-ai/ui/i18n/da")),
- ja: () => merge(import("@/i18n/ja"), import("@opencode-ai/ui/i18n/ja")),
- pl: () => merge(import("@/i18n/pl"), import("@opencode-ai/ui/i18n/pl")),
- ru: () => merge(import("@/i18n/ru"), import("@opencode-ai/ui/i18n/ru")),
- ar: () => merge(import("@/i18n/ar"), import("@opencode-ai/ui/i18n/ar")),
- no: () => merge(import("@/i18n/no"), import("@opencode-ai/ui/i18n/no")),
- br: () => merge(import("@/i18n/br"), import("@opencode-ai/ui/i18n/br")),
- th: () => merge(import("@/i18n/th"), import("@opencode-ai/ui/i18n/th")),
- bs: () => merge(import("@/i18n/bs"), import("@opencode-ai/ui/i18n/bs")),
- tr: () => merge(import("@/i18n/tr"), import("@opencode-ai/ui/i18n/tr")),
-}
-
-function loadDict(locale: Locale) {
- const hit = dicts.get(locale)
- if (hit) return Promise.resolve(hit)
- if (locale === "en") return Promise.resolve(base)
- const load = loaders[locale]
- return load().then((next: Dictionary) => {
- dicts.set(locale, next)
- return next
- })
-}
-
-export function loadLocaleDict(locale: Locale) {
- return loadDict(locale).then(() => undefined)
+const DICT: Record<Locale, Dictionary> = {
+ en: base,
+ zh: { ...base, ...i18n.flatten({ ...zh, ...uiZh }) },
+ zht: { ...base, ...i18n.flatten({ ...zht, ...uiZht }) },
+ ko: { ...base, ...i18n.flatten({ ...ko, ...uiKo }) },
+ de: { ...base, ...i18n.flatten({ ...de, ...uiDe }) },
+ es: { ...base, ...i18n.flatten({ ...es, ...uiEs }) },
+ fr: { ...base, ...i18n.flatten({ ...fr, ...uiFr }) },
+ da: { ...base, ...i18n.flatten({ ...da, ...uiDa }) },
+ ja: { ...base, ...i18n.flatten({ ...ja, ...uiJa }) },
+ pl: { ...base, ...i18n.flatten({ ...pl, ...uiPl }) },
+ ru: { ...base, ...i18n.flatten({ ...ru, ...uiRu }) },
+ ar: { ...base, ...i18n.flatten({ ...ar, ...uiAr }) },
+ no: { ...base, ...i18n.flatten({ ...no, ...uiNo }) },
+ br: { ...base, ...i18n.flatten({ ...br, ...uiBr }) },
+ th: { ...base, ...i18n.flatten({ ...th, ...uiTh }) },
+ bs: { ...base, ...i18n.flatten({ ...bs, ...uiBs }) },
+ tr: { ...base, ...i18n.flatten({ ...tr, ...uiTr }) },
}
const localeMatchers: Array<{ locale: Locale; match: (language: string) => boolean }> = [
@@ -156,6 +168,27 @@ const localeMatchers: Array<{ locale: Locale; match: (language: string) => boole
{ locale: "tr", match: (language) => language.startsWith("tr") },
]
+type ParityKey = "command.session.previous.unseen" | "command.session.next.unseen"
+const PARITY_CHECK: Record<Exclude<Locale, "en">, Record<ParityKey, string>> = {
+ zh,
+ zht,
+ ko,
+ de,
+ es,
+ fr,
+ da,
+ ja,
+ pl,
+ ru,
+ ar,
+ no,
+ br,
+ th,
+ bs,
+ tr,
+}
+void PARITY_CHECK
+
function detectLocale(): Locale {
if (typeof navigator !== "object") return "en"
@@ -170,48 +203,27 @@ function detectLocale(): Locale {
return "en"
}
-export function normalizeLocale(value: string): Locale {
+function normalizeLocale(value: string): Locale {
return LOCALES.includes(value as Locale) ? (value as Locale) : "en"
}
-function readStoredLocale() {
- if (typeof localStorage !== "object") return
- try {
- const raw = localStorage.getItem("opencode.global.dat:language")
- if (!raw) return
- const next = JSON.parse(raw) as { locale?: string }
- if (typeof next?.locale !== "string") return
- return normalizeLocale(next.locale)
- } catch {
- return
- }
-}
-
-const warm = readStoredLocale() ?? detectLocale()
-if (warm !== "en") void loadDict(warm)
-
export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({
name: "Language",
- init: (props: { locale?: Locale }) => {
- const initial = props.locale ?? readStoredLocale() ?? detectLocale()
+ init: () => {
const [store, setStore, _, ready] = persisted(
Persist.global("language", ["language.v1"]),
createStore({
- locale: initial,
+ locale: detectLocale() as Locale,
}),
)
const locale = createMemo<Locale>(() => normalizeLocale(store.locale))
+ console.log("locale", locale())
const intl = createMemo(() => INTL[locale()])
- const [dict] = createResource(locale, loadDict, {
- initialValue: dicts.get(initial) ?? base,
- })
+ const dict = createMemo<Dictionary>(() => DICT[locale()])
- const t = i18n.translator(() => dict() ?? base, i18n.resolveTemplate) as (
- key: keyof Dictionary,
- params?: Record<string, string | number | boolean>,
- ) => string
+ const t = i18n.translator(dict, i18n.resolveTemplate)
const label = (value: Locale) => t(LABEL_KEY[value])