summaryrefslogtreecommitdiffhomepage
path: root/packages/console/app/src/context
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-06 08:54:51 -0600
committerGitHub <[email protected]>2026-02-06 08:54:51 -0600
commit812597bb8b101896a8988493d37261ff851ae502 (patch)
tree3b713c13f49c65665e6a7296ed483a3f6f8befbf /packages/console/app/src/context
parent0ec5f6608bdfea5be62dbbdc4c04a61de6d3e67c (diff)
downloadopencode-812597bb8b101896a8988493d37261ff851ae502.tar.gz
opencode-812597bb8b101896a8988493d37261ff851ae502.zip
feat(web): i18n (#12471)
Diffstat (limited to 'packages/console/app/src/context')
-rw-r--r--packages/console/app/src/context/i18n.tsx27
-rw-r--r--packages/console/app/src/context/language.tsx68
2 files changed, 95 insertions, 0 deletions
diff --git a/packages/console/app/src/context/i18n.tsx b/packages/console/app/src/context/i18n.tsx
new file mode 100644
index 000000000..5d178c8b8
--- /dev/null
+++ b/packages/console/app/src/context/i18n.tsx
@@ -0,0 +1,27 @@
+import { createMemo } from "solid-js"
+import { createSimpleContext } from "@opencode-ai/ui/context"
+import { i18n, type Key } from "~/i18n"
+import { useLanguage } from "~/context/language"
+
+function resolve(text: string, params?: Record<string, string | number>) {
+ if (!params) return text
+ return text.replace(/\{\{(\w+)\}\}/g, (raw, key) => {
+ const value = params[key]
+ if (value === undefined || value === null) return raw
+ return String(value)
+ })
+}
+
+export const { use: useI18n, provider: I18nProvider } = createSimpleContext({
+ name: "I18n",
+ init: () => {
+ const language = useLanguage()
+ const dict = createMemo(() => i18n(language.locale()))
+
+ return {
+ t(key: Key, params?: Record<string, string | number>) {
+ return resolve(dict()[key], params)
+ },
+ }
+ },
+})
diff --git a/packages/console/app/src/context/language.tsx b/packages/console/app/src/context/language.tsx
new file mode 100644
index 000000000..7e3e5286c
--- /dev/null
+++ b/packages/console/app/src/context/language.tsx
@@ -0,0 +1,68 @@
+import { createEffect } from "solid-js"
+import { createStore } from "solid-js/store"
+import { getRequestEvent } from "solid-js/web"
+import { createSimpleContext } from "@opencode-ai/ui/context"
+import {
+ LOCALES,
+ type Locale,
+ clearCookie,
+ cookie,
+ detectFromLanguages,
+ dir as localeDir,
+ label as localeLabel,
+ localeFromCookieHeader,
+ localeFromRequest,
+ parseLocale,
+ tag as localeTag,
+} from "~/lib/language"
+
+function initial() {
+ const evt = getRequestEvent()
+ if (evt) return localeFromRequest(evt.request)
+
+ if (typeof document === "object") {
+ const fromDom = parseLocale(document.documentElement.dataset.locale)
+ if (fromDom) return fromDom
+
+ const fromCookie = localeFromCookieHeader(document.cookie)
+ if (fromCookie) return fromCookie
+ }
+
+ if (typeof navigator !== "object") return "en" satisfies Locale
+
+ const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
+ return detectFromLanguages(languages)
+}
+
+export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({
+ name: "Language",
+ init: () => {
+ const [store, setStore] = createStore({
+ locale: initial(),
+ })
+
+ createEffect(() => {
+ if (typeof document !== "object") return
+ document.documentElement.lang = localeTag(store.locale)
+ document.documentElement.dir = localeDir(store.locale)
+ document.documentElement.dataset.locale = store.locale
+ })
+
+ return {
+ locale: () => store.locale,
+ locales: LOCALES,
+ label: localeLabel,
+ tag: localeTag,
+ dir: localeDir,
+ setLocale(next: Locale) {
+ setStore("locale", next)
+ if (typeof document !== "object") return
+ document.cookie = cookie(next)
+ },
+ clear() {
+ if (typeof document !== "object") return
+ document.cookie = clearCookie()
+ },
+ }
+ },
+})