diff options
Diffstat (limited to 'packages/app/src/context/language.tsx')
| -rw-r--r-- | packages/app/src/context/language.tsx | 144 |
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]) |
