From 812597bb8b101896a8988493d37261ff851ae502 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Fri, 6 Feb 2026 08:54:51 -0600 Subject: feat(web): i18n (#12471) --- packages/console/app/src/context/i18n.tsx | 27 +++++++++++ packages/console/app/src/context/language.tsx | 68 +++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 packages/console/app/src/context/i18n.tsx create mode 100644 packages/console/app/src/context/language.tsx (limited to 'packages/console/app/src/context') 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) { + 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) { + 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() + }, + } + }, +}) -- cgit v1.2.3