From 24cd84cda5522e4607e8e3fb3626f289d7e348f4 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Fri, 6 Feb 2026 11:30:40 -0600
Subject: feat(www): locale specific urls (#12508)
---
packages/console/app/src/routes/[...404].tsx | 10 +++---
packages/console/app/src/routes/black.tsx | 15 ++++----
packages/console/app/src/routes/black/index.tsx | 4 ++-
.../app/src/routes/black/subscribe/[plan].tsx | 4 ++-
.../console/app/src/routes/black/workspace.tsx | 6 ++--
packages/console/app/src/routes/brand/index.tsx | 6 ++--
.../console/app/src/routes/changelog/index.tsx | 9 ++---
packages/console/app/src/routes/docs/[...path].ts | 4 +--
packages/console/app/src/routes/docs/index.ts | 4 +--
packages/console/app/src/routes/download/index.tsx | 40 +++++++++++++---------
.../console/app/src/routes/enterprise/index.tsx | 6 ++--
packages/console/app/src/routes/index.tsx | 37 ++++++++++++--------
.../app/src/routes/legal/privacy-policy/index.tsx | 14 ++++----
.../src/routes/legal/terms-of-service/index.tsx | 23 +++++++------
packages/console/app/src/routes/s/[id].ts | 4 +--
packages/console/app/src/routes/t/[...path].tsx | 4 +--
packages/console/app/src/routes/temp.tsx | 14 ++++----
packages/console/app/src/routes/user-menu.tsx | 4 ++-
packages/console/app/src/routes/workspace.tsx | 4 ++-
.../workspace/[id]/members/member-section.tsx | 4 ++-
.../src/routes/workspace/[id]/model-section.tsx | 6 ++--
packages/console/app/src/routes/zen/index.tsx | 23 +++++++------
22 files changed, 143 insertions(+), 102 deletions(-)
(limited to 'packages/console/app/src/routes')
diff --git a/packages/console/app/src/routes/[...404].tsx b/packages/console/app/src/routes/[...404].tsx
index 7e7111ef6..414491f9d 100644
--- a/packages/console/app/src/routes/[...404].tsx
+++ b/packages/console/app/src/routes/[...404].tsx
@@ -4,16 +4,18 @@ import { HttpStatusCode } from "@solidjs/start"
import logoLight from "../asset/logo-ornate-light.svg"
import logoDark from "../asset/logo-ornate-dark.svg"
import { useI18n } from "~/context/i18n"
+import { useLanguage } from "~/context/language"
export default function NotFound() {
const i18n = useI18n()
+ const language = useLanguage()
return (
{i18n.t("notFound.title")}
-
+
@@ -22,16 +24,16 @@ export default function NotFound() {
diff --git a/packages/console/app/src/routes/black.tsx b/packages/console/app/src/routes/black.tsx
index 012b392e2..243ae687d 100644
--- a/packages/console/app/src/routes/black.tsx
+++ b/packages/console/app/src/routes/black.tsx
@@ -1,5 +1,5 @@
import { A, createAsync, RouteSectionProps } from "@solidjs/router"
-import { Title, Meta, Link } from "@solidjs/meta"
+import { Title, Meta } from "@solidjs/meta"
import { createMemo, createSignal } from "solid-js"
import { github } from "~/lib/github"
import { config } from "~/config"
@@ -7,6 +7,7 @@ import { useLanguage } from "~/context/language"
import { LanguagePicker } from "~/component/language-picker"
import { useI18n } from "~/context/i18n"
import Spotlight, { defaultConfig, type SpotlightAnimationState } from "~/component/spotlight"
+import { LocaleLinks } from "~/component/locale-links"
import "./black.css"
export default function BlackLayout(props: RouteSectionProps) {
@@ -70,9 +71,9 @@ export default function BlackLayout(props: RouteSectionProps) {
diff --git a/packages/console/app/src/routes/black/index.tsx b/packages/console/app/src/routes/black/index.tsx
index ad7a115d0..72b196f57 100644
--- a/packages/console/app/src/routes/black/index.tsx
+++ b/packages/console/app/src/routes/black/index.tsx
@@ -3,10 +3,12 @@ import { Title } from "@solidjs/meta"
import { createMemo, createSignal, For, Match, onMount, Show, Switch } from "solid-js"
import { PlanIcon, plans } from "./common"
import { useI18n } from "~/context/i18n"
+import { useLanguage } from "~/context/language"
export default function Black() {
const [params] = useSearchParams()
const i18n = useI18n()
+ const language = useLanguage()
const [selected, setSelected] = createSignal((params.plan as string) || null)
const [mounted, setMounted] = createSignal(false)
const selectedPlan = createMemo(() => plans.find((p) => p.id === selected()))
@@ -104,7 +106,7 @@ export default function Black() {
{i18n.t("black.finePrint.beforeTerms")} ·{" "}
- {i18n.t("black.finePrint.terms")}
+ {i18n.t("black.finePrint.terms")}
>
diff --git a/packages/console/app/src/routes/black/subscribe/[plan].tsx b/packages/console/app/src/routes/black/subscribe/[plan].tsx
index 347be7419..644d87d9b 100644
--- a/packages/console/app/src/routes/black/subscribe/[plan].tsx
+++ b/packages/console/app/src/routes/black/subscribe/[plan].tsx
@@ -15,6 +15,7 @@ import { Modal } from "~/component/modal"
import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { useI18n } from "~/context/i18n"
+import { useLanguage } from "~/context/language"
import { formError } from "~/lib/form-error"
const plansMap = Object.fromEntries(plans.map((p) => [p.id, p])) as Record
@@ -267,6 +268,7 @@ function IntentForm(props: { plan: PlanID; workspaceID: string; onSuccess: (data
export default function BlackSubscribe() {
const params = useParams()
const i18n = useI18n()
+ const language = useLanguage()
const planData = plansMap[(params.plan as PlanID) ?? "20"] ?? plansMap["20"]
const plan = planData.id
@@ -467,7 +469,7 @@ export default function BlackSubscribe() {
{i18n.t("black.finePrint.beforeTerms")} ·{" "}
- {i18n.t("black.finePrint.terms")}
+ {i18n.t("black.finePrint.terms")}
>
diff --git a/packages/console/app/src/routes/black/workspace.tsx b/packages/console/app/src/routes/black/workspace.tsx
index 03950a243..106e8a23e 100644
--- a/packages/console/app/src/routes/black/workspace.tsx
+++ b/packages/console/app/src/routes/black/workspace.tsx
@@ -220,13 +220,13 @@ export default function BlackWorkspace() {
{i18n.t("nav.github")} [{starCount()}]
- {i18n.t("nav.docs")}
+ {i18n.t("nav.docs")}
- {i18n.t("legal.privacy")}
+ {i18n.t("legal.privacy")}
- {i18n.t("legal.terms")}
+ {i18n.t("legal.terms")}
diff --git a/packages/console/app/src/routes/brand/index.tsx b/packages/console/app/src/routes/brand/index.tsx
index a1bc6c54c..eda3c8472 100644
--- a/packages/console/app/src/routes/brand/index.tsx
+++ b/packages/console/app/src/routes/brand/index.tsx
@@ -1,10 +1,10 @@
import "./index.css"
-import { Title, Meta, Link } from "@solidjs/meta"
+import { Title, Meta } from "@solidjs/meta"
import { Header } from "~/component/header"
-import { config } from "~/config"
import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
import { useI18n } from "~/context/i18n"
+import { LocaleLinks } from "~/component/locale-links"
import previewLogoLight from "../../asset/brand/preview-opencode-logo-light.png"
import previewLogoDark from "../../asset/brand/preview-opencode-logo-dark.png"
import previewWordmarkLight from "../../asset/brand/preview-opencode-wordmark-light.png"
@@ -56,7 +56,7 @@ export default function Brand() {
return (
{i18n.t("brand.title")}
-
+
diff --git a/packages/console/app/src/routes/changelog/index.tsx b/packages/console/app/src/routes/changelog/index.tsx
index 595ae9d1b..54f037479 100644
--- a/packages/console/app/src/routes/changelog/index.tsx
+++ b/packages/console/app/src/routes/changelog/index.tsx
@@ -1,15 +1,15 @@
import "./index.css"
-import { Title, Meta, Link } from "@solidjs/meta"
+import { Title, Meta } from "@solidjs/meta"
import { createAsync } from "@solidjs/router"
import { Header } from "~/component/header"
import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
-import { config } from "~/config"
import { changelog } from "~/lib/changelog"
import type { HighlightGroup } from "~/lib/changelog"
import { For, Show, createSignal } from "solid-js"
import { useI18n } from "~/context/i18n"
import { useLanguage } from "~/context/language"
+import { LocaleLinks } from "~/component/locale-links"
function formatDate(dateString: string, locale: string) {
const date = new Date(dateString)
@@ -107,7 +107,7 @@ export default function Changelog() {
return (
{i18n.t("changelog.title")}
-
+
@@ -122,7 +122,8 @@ export default function Changelog() {
- {i18n.t("changelog.empty")} {i18n.t("changelog.viewJson")}
+ {i18n.t("changelog.empty")}{" "}
+ {i18n.t("changelog.viewJson")}
diff --git a/packages/console/app/src/routes/docs/[...path].ts b/packages/console/app/src/routes/docs/[...path].ts
index 5493c31a6..0711b5ce0 100644
--- a/packages/console/app/src/routes/docs/[...path].ts
+++ b/packages/console/app/src/routes/docs/[...path].ts
@@ -1,5 +1,5 @@
import type { APIEvent } from "@solidjs/start/server"
-import { localeFromCookieHeader, tag } from "~/lib/language"
+import { LOCALE_HEADER, localeFromCookieHeader, parseLocale, tag } from "~/lib/language"
async function handler(evt: APIEvent) {
const req = evt.request.clone()
@@ -7,7 +7,7 @@ async function handler(evt: APIEvent) {
const targetUrl = `https://docs.opencode.ai${url.pathname}${url.search}`
const headers = new Headers(req.headers)
- const locale = localeFromCookieHeader(req.headers.get("cookie"))
+ const locale = parseLocale(req.headers.get(LOCALE_HEADER)) ?? localeFromCookieHeader(req.headers.get("cookie"))
if (locale) headers.set("accept-language", tag(locale))
const response = await fetch(targetUrl, {
diff --git a/packages/console/app/src/routes/docs/index.ts b/packages/console/app/src/routes/docs/index.ts
index 5493c31a6..0711b5ce0 100644
--- a/packages/console/app/src/routes/docs/index.ts
+++ b/packages/console/app/src/routes/docs/index.ts
@@ -1,5 +1,5 @@
import type { APIEvent } from "@solidjs/start/server"
-import { localeFromCookieHeader, tag } from "~/lib/language"
+import { LOCALE_HEADER, localeFromCookieHeader, parseLocale, tag } from "~/lib/language"
async function handler(evt: APIEvent) {
const req = evt.request.clone()
@@ -7,7 +7,7 @@ async function handler(evt: APIEvent) {
const targetUrl = `https://docs.opencode.ai${url.pathname}${url.search}`
const headers = new Headers(req.headers)
- const locale = localeFromCookieHeader(req.headers.get("cookie"))
+ const locale = parseLocale(req.headers.get(LOCALE_HEADER)) ?? localeFromCookieHeader(req.headers.get("cookie"))
if (locale) headers.set("accept-language", tag(locale))
const response = await fetch(targetUrl, {
diff --git a/packages/console/app/src/routes/download/index.tsx b/packages/console/app/src/routes/download/index.tsx
index 0c89e4abd..b5dbbd39a 100644
--- a/packages/console/app/src/routes/download/index.tsx
+++ b/packages/console/app/src/routes/download/index.tsx
@@ -1,5 +1,5 @@
import "./index.css"
-import { Title, Meta, Link } from "@solidjs/meta"
+import { Title, Meta } from "@solidjs/meta"
import { A, createAsync, query } from "@solidjs/router"
import { Header } from "~/component/header"
import { Footer } from "~/component/footer"
@@ -11,6 +11,8 @@ import { config } from "~/config"
import { createSignal, onMount, Show, JSX } from "solid-js"
import { DownloadPlatform } from "./types"
import { useI18n } from "~/context/i18n"
+import { useLanguage } from "~/context/language"
+import { LocaleLinks } from "~/component/locale-links"
type OS = "macOS" | "Windows" | "Linux" | null
@@ -66,6 +68,7 @@ function CopyStatus() {
export default function Download() {
const i18n = useI18n()
+ const language = useLanguage()
const [detectedOS, setDetectedOS] = createSignal(null)
onMount(() => {
@@ -83,7 +86,7 @@ export default function Download() {
return (
{i18n.t("download.title")}
-
+
@@ -97,7 +100,10 @@ export default function Download() {
{i18n.t("download.hero.title")}
{i18n.t("download.hero.subtitle")}
-
+
{i18n.t("download.hero.button", { os: detectedOS()! })}
@@ -169,7 +175,7 @@ export default function Download() {
{i18n.t("download.platform.macosAppleSilicon")}
-
+
{i18n.t("download.action.download")}
@@ -185,7 +191,7 @@ export default function Download() {
{i18n.t("download.platform.macosIntel")}
-
+
{i18n.t("download.action.download")}
@@ -208,7 +214,7 @@ export default function Download() {
{i18n.t("download.platform.windowsX64")}
-
+
{i18n.t("download.action.download")}
@@ -224,7 +230,7 @@ export default function Download() {
{i18n.t("download.platform.linuxDeb")}
-
+
{i18n.t("download.action.download")}
@@ -240,7 +246,7 @@ export default function Download() {
{i18n.t("download.platform.linuxRpm")}
-
+
{i18n.t("download.action.download")}
@@ -257,7 +263,7 @@ export default function Download() {
Linux (.AppImage)
-
+
Download
*/}
@@ -422,36 +428,38 @@ export default function Download() {
- {i18n.t("home.faq.a2.before")} {i18n.t("home.faq.a2.link")}.
+ {i18n.t("home.faq.a2.before")} {i18n.t("home.faq.a2.link")}.
{i18n.t("download.faq.a3.beforeLocal")}{" "}
-
+
{i18n.t("download.faq.a3.localLink")}
{" "}
- {i18n.t("download.faq.a3.afterLocal.beforeZen")} {i18n.t("nav.zen")}
+ {i18n.t("download.faq.a3.afterLocal.beforeZen")}{" "}
+ {i18n.t("nav.zen")}
{i18n.t("download.faq.a3.afterZen")}
- {i18n.t("home.faq.a5.beforeDesktop")} {i18n.t("home.faq.a5.desktop")}{" "}
- {i18n.t("home.faq.a5.and")} {i18n.t("home.faq.a5.web")}!
+ {i18n.t("home.faq.a5.beforeDesktop")}{" "}
+ {i18n.t("home.faq.a5.desktop")} {i18n.t("home.faq.a5.and")}{" "}
+ {i18n.t("home.faq.a5.web")}!
{i18n.t("download.faq.a5.p1")} {i18n.t("download.faq.a5.p2.beforeZen")}{" "}
- {i18n.t("nav.zen")}
+ {i18n.t("nav.zen")}
{i18n.t("download.faq.a5.p2.afterZen")}
{i18n.t("download.faq.a6.p1")} {i18n.t("download.faq.a6.p2.beforeShare")}{" "}
- {i18n.t("download.faq.a6.shareLink")}.
+ {i18n.t("download.faq.a6.shareLink")}.
diff --git a/packages/console/app/src/routes/enterprise/index.tsx b/packages/console/app/src/routes/enterprise/index.tsx
index c9f487490..ee323ff82 100644
--- a/packages/console/app/src/routes/enterprise/index.tsx
+++ b/packages/console/app/src/routes/enterprise/index.tsx
@@ -1,12 +1,12 @@
import "./index.css"
-import { Title, Meta, Link } from "@solidjs/meta"
+import { Title, Meta } from "@solidjs/meta"
import { createSignal, Show } from "solid-js"
-import { config } from "~/config"
import { Header } from "~/component/header"
import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
import { Faq } from "~/component/faq"
import { useI18n } from "~/context/i18n"
+import { LocaleLinks } from "~/component/locale-links"
export default function Enterprise() {
const i18n = useI18n()
@@ -57,7 +57,7 @@ export default function Enterprise() {
return (
{i18n.t("enterprise.title")}
-
+
diff --git a/packages/console/app/src/routes/index.tsx b/packages/console/app/src/routes/index.tsx
index a56b2ac62..e47134d2b 100644
--- a/packages/console/app/src/routes/index.tsx
+++ b/packages/console/app/src/routes/index.tsx
@@ -1,5 +1,5 @@
import "./index.css"
-import { Title, Meta, Link } from "@solidjs/meta"
+import { Title, Meta } from "@solidjs/meta"
//import { HttpHeader } from "@solidjs/start"
import video from "../asset/lander/opencode-min.mp4"
import videoPoster from "../asset/lander/opencode-poster.png"
@@ -15,6 +15,8 @@ import { github } from "~/lib/github"
import { createMemo } from "solid-js"
import { config } from "~/config"
import { useI18n } from "~/context/i18n"
+import { useLanguage } from "~/context/language"
+import { LocaleLinks } from "~/component/locale-links"
function CopyStatus() {
return (
@@ -27,6 +29,7 @@ function CopyStatus() {
export default function Home() {
const i18n = useI18n()
+ const language = useLanguage()
const githubData = createAsync(() => github())
const release = createMemo(() => githubData()?.release)
@@ -46,7 +49,7 @@ export default function Home() {
{/**/}
{i18n.t("home.title")}
-
+
@@ -217,7 +220,7 @@ export default function Home() {
-
+
{i18n.t("home.what.readDocs")}
@@ -673,14 +676,15 @@ export default function Home() {
- {i18n.t("home.faq.a2.before")} {i18n.t("home.faq.a2.link")}.
+ {i18n.t("home.faq.a2.before")} {i18n.t("home.faq.a2.link")}.
- {i18n.t("home.faq.a3.p1")} {i18n.t("home.faq.a3.p2.beforeZen")} {i18n.t("nav.zen")}
+ {i18n.t("home.faq.a3.p1")} {i18n.t("home.faq.a3.p2.beforeZen")}{" "}
+ {i18n.t("nav.zen")}
{i18n.t("home.faq.a3.p2.afterZen")} {i18n.t("home.faq.a3.p3")} {i18n.t("home.faq.a3.p4.beforeLocal")}{" "}
-
+
{i18n.t("home.faq.a3.p4.localLink")}
.
@@ -688,13 +692,15 @@ export default function Home() {
- {i18n.t("home.faq.a4.p1")} {i18n.t("common.learnMore")}.
+ {i18n.t("home.faq.a4.p1")}{" "}
+ {i18n.t("common.learnMore")}.
- {i18n.t("home.faq.a5.beforeDesktop")} {i18n.t("home.faq.a5.desktop")}{" "}
- {i18n.t("home.faq.a5.and")} {i18n.t("home.faq.a5.web")}!
+ {i18n.t("home.faq.a5.beforeDesktop")}{" "}
+ {i18n.t("home.faq.a5.desktop")} {i18n.t("home.faq.a5.and")}{" "}
+ {i18n.t("home.faq.a5.web")}!
@@ -703,8 +709,9 @@ export default function Home() {
{i18n.t("home.faq.a7.p1")} {i18n.t("home.faq.a7.p2.beforeModels")}{" "}
- {i18n.t("home.faq.a7.p2.modelsLink")} {i18n.t("home.faq.a7.p2.and")}{" "}
- {i18n.t("home.faq.a7.p2.shareLink")}.
+ {i18n.t("home.faq.a7.p2.modelsLink")}{" "}
+ {i18n.t("home.faq.a7.p2.and")}{" "}
+ {i18n.t("home.faq.a7.p2.shareLink")}.
@@ -808,7 +815,7 @@ export default function Home() {
-
+
{i18n.t("home.zenCta.link")}