summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context/language.tsx
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-25 06:25:57 -0500
committerAdam <[email protected]>2026-03-25 06:25:57 -0500
commit1041ae91d1a39401fe099747e3bc093bdcdaa079 (patch)
treeca5515910ad01f76639577ef8e3a991b644a5ade /packages/app/src/context/language.tsx
parent898456a25cf2edbfc4ae4961b37424f633419dd6 (diff)
downloadopencode-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.tsx144
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])