diff options
| author | Adam <[email protected]> | 2026-03-25 06:25:57 -0500 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-03-25 06:25:57 -0500 |
| commit | 1041ae91d1a39401fe099747e3bc093bdcdaa079 (patch) | |
| tree | ca5515910ad01f76639577ef8e3a991b644a5ade /packages/app/src/context/language.tsx | |
| parent | 898456a25cf2edbfc4ae4961b37424f633419dd6 (diff) | |
| download | opencode-1041ae91d1a39401fe099747e3bc093bdcdaa079.tar.gz opencode-1041ae91d1a39401fe099747e3bc093bdcdaa079.zip | |
Reapply "fix(app): startup efficiency"
This reverts commit 898456a25cf2edbfc4ae4961b37424f633419dd6.
Diffstat (limited to 'packages/app/src/context/language.tsx')
| -rw-r--r-- | packages/app/src/context/language.tsx | 144 |
1 files changed, 66 insertions, 78 deletions
diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx index b1edd541c..51dc09cd7 100644 --- a/packages/app/src/context/language.tsx +++ b/packages/app/src/context/language.tsx @@ -1,42 +1,10 @@ import * as i18n from "@solid-primitives/i18n" -import { createEffect, createMemo } from "solid-js" +import { createEffect, createMemo, createResource } 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" @@ -59,6 +27,7 @@ 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` @@ -125,24 +94,43 @@ const LABEL_KEY: Record<Locale, keyof Dictionary> = { } const base = i18n.flatten({ ...en, ...uiEn }) -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 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 localeMatchers: Array<{ locale: Locale; match: (language: string) => boolean }> = [ @@ -168,27 +156,6 @@ 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" @@ -203,27 +170,48 @@ function detectLocale(): Locale { return "en" } -function normalizeLocale(value: string): Locale { +export 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: () => { + init: (props: { locale?: Locale }) => { + const initial = props.locale ?? readStoredLocale() ?? detectLocale() const [store, setStore, _, ready] = persisted( Persist.global("language", ["language.v1"]), createStore({ - locale: detectLocale() as Locale, + locale: initial, }), ) const locale = createMemo<Locale>(() => normalizeLocale(store.locale)) - console.log("locale", locale()) const intl = createMemo(() => INTL[locale()]) - const dict = createMemo<Dictionary>(() => DICT[locale()]) + const [dict] = createResource(locale, loadDict, { + initialValue: dicts.get(initial) ?? base, + }) - const t = i18n.translator(dict, i18n.resolveTemplate) + const t = i18n.translator(() => dict() ?? base, i18n.resolveTemplate) as ( + key: keyof Dictionary, + params?: Record<string, string | number | boolean>, + ) => string const label = (value: Locale) => t(LABEL_KEY[value]) |
