wip(app): i18n
This commit is contained in:
@@ -4,10 +4,72 @@ import { Font } from "@opencode-ai/ui/font"
|
||||
import { MetaProvider } from "@solidjs/meta"
|
||||
import { MarkedProvider } from "@opencode-ai/ui/context/marked"
|
||||
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
|
||||
import { Suspense } from "solid-js"
|
||||
import { I18nProvider, type UiI18nParams } from "@opencode-ai/ui/context"
|
||||
import { dict as uiEn } from "@opencode-ai/ui/i18n/en"
|
||||
import { dict as uiZh } from "@opencode-ai/ui/i18n/zh"
|
||||
import { createEffect, createMemo, Suspense, type ParentProps } from "solid-js"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import "./app.css"
|
||||
import { Favicon } from "@opencode-ai/ui/favicon"
|
||||
|
||||
function resolveTemplate(text: string, params?: UiI18nParams) {
|
||||
if (!params) return text
|
||||
return text.replace(/{{\s*([^}]+?)\s*}}/g, (_, rawKey) => {
|
||||
const key = String(rawKey)
|
||||
const value = params[key]
|
||||
return value === undefined ? "" : String(value)
|
||||
})
|
||||
}
|
||||
|
||||
function detectLocaleFromHeader(header: string | null | undefined) {
|
||||
if (!header) return
|
||||
for (const item of header.split(",")) {
|
||||
const value = item.trim().split(";")[0]?.toLowerCase()
|
||||
if (!value) continue
|
||||
if (value.startsWith("zh")) return "zh" as const
|
||||
if (value.startsWith("en")) return "en" as const
|
||||
}
|
||||
}
|
||||
|
||||
function detectLocale() {
|
||||
const event = getRequestEvent()
|
||||
const header = event?.request.headers.get("accept-language")
|
||||
const headerLocale = detectLocaleFromHeader(header)
|
||||
if (headerLocale) return headerLocale
|
||||
|
||||
if (typeof document === "object") {
|
||||
const value = document.documentElement.lang?.toLowerCase() ?? ""
|
||||
if (value.startsWith("zh")) return "zh" as const
|
||||
if (value.startsWith("en")) return "en" as const
|
||||
}
|
||||
|
||||
if (typeof navigator === "object") {
|
||||
const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
|
||||
for (const language of languages) {
|
||||
if (!language) continue
|
||||
if (language.toLowerCase().startsWith("zh")) return "zh" as const
|
||||
}
|
||||
}
|
||||
|
||||
return "en" as const
|
||||
}
|
||||
|
||||
function UiI18nBridge(props: ParentProps) {
|
||||
const locale = createMemo(() => detectLocale())
|
||||
const t = (key: keyof typeof uiEn, params?: UiI18nParams) => {
|
||||
const value = locale() === "zh" ? uiZh[key] ?? uiEn[key] : uiEn[key]
|
||||
const text = value ?? String(key)
|
||||
return resolveTemplate(text, params)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (typeof document !== "object") return
|
||||
document.documentElement.lang = locale()
|
||||
})
|
||||
|
||||
return <I18nProvider value={{ locale, t }}>{props.children}</I18nProvider>
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Router
|
||||
@@ -17,7 +79,9 @@ export default function App() {
|
||||
<MarkedProvider>
|
||||
<Favicon />
|
||||
<Font />
|
||||
<Suspense>{props.children}</Suspense>
|
||||
<UiI18nBridge>
|
||||
<Suspense>{props.children}</Suspense>
|
||||
</UiI18nBridge>
|
||||
</MarkedProvider>
|
||||
</DialogProvider>
|
||||
</MetaProvider>
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
// @refresh reload
|
||||
import { createHandler, StartServer } from "@solidjs/start/server"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
|
||||
export default createHandler(() => (
|
||||
<StartServer
|
||||
document={({ assets, children, scripts }) => (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OpenCode</title>
|
||||
<meta name="theme-color" content="#F8F7F7" />
|
||||
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
|
||||
{assets}
|
||||
</head>
|
||||
<body class="antialiased overscroll-none text-12-regular">
|
||||
<div id="app">{children}</div>
|
||||
{scripts}
|
||||
</body>
|
||||
</html>
|
||||
)}
|
||||
document={({ assets, children, scripts }) => {
|
||||
const lang = (() => {
|
||||
const event = getRequestEvent()
|
||||
const header = event?.request.headers.get("accept-language")
|
||||
if (!header) return "en"
|
||||
for (const item of header.split(",")) {
|
||||
const value = item.trim().split(";")[0]?.toLowerCase()
|
||||
if (!value) continue
|
||||
if (value.startsWith("zh")) return "zh"
|
||||
if (value.startsWith("en")) return "en"
|
||||
}
|
||||
return "en"
|
||||
})()
|
||||
|
||||
return (
|
||||
<html lang={lang}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>OpenCode</title>
|
||||
<meta name="theme-color" content="#F8F7F7" />
|
||||
<meta name="theme-color" content="#131010" media="(prefers-color-scheme: dark)" />
|
||||
{assets}
|
||||
</head>
|
||||
<body class="antialiased overscroll-none text-12-regular">
|
||||
<div id="app">{children}</div>
|
||||
{scripts}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user