Adam 2 недель назад
Родитель
Сommit
83708c295c

+ 75 - 1
packages/console/app/src/lib/language.ts

@@ -68,6 +68,80 @@ const TAG = {
   tr: "tr",
 } satisfies Record<Locale, string>
 
+const DOCS = {
+  en: "root",
+  zh: "zh-cn",
+  zht: "zh-tw",
+  ko: "ko",
+  de: "de",
+  es: "es",
+  fr: "fr",
+  it: "it",
+  da: "da",
+  ja: "ja",
+  pl: "pl",
+  ru: "ru",
+  ar: "ar",
+  no: "nb",
+  br: "pt-br",
+  th: "th",
+  tr: "tr",
+} satisfies Record<Locale, string>
+
+const DOCS_SEGMENT = new Set([
+  "ar",
+  "bs",
+  "da",
+  "de",
+  "es",
+  "fr",
+  "it",
+  "ja",
+  "ko",
+  "nb",
+  "pl",
+  "pt-br",
+  "ru",
+  "th",
+  "tr",
+  "zh-cn",
+  "zh-tw",
+])
+
+function suffix(pathname: string) {
+  const index = pathname.search(/[?#]/)
+  if (index === -1) {
+    return {
+      path: fix(pathname),
+      suffix: "",
+    }
+  }
+
+  return {
+    path: fix(pathname.slice(0, index)),
+    suffix: pathname.slice(index),
+  }
+}
+
+export function docs(locale: Locale, pathname: string) {
+  const value = DOCS[locale]
+  const next = suffix(pathname)
+  if (next.path !== "/docs" && next.path !== "/docs/" && !next.path.startsWith("/docs/")) {
+    return `${next.path}${next.suffix}`
+  }
+
+  if (value === "root") return `${next.path}${next.suffix}`
+
+  const hit = /^\/docs\/([^/]+)(\/.*)?$/.exec(next.path)
+  if (hit && DOCS_SEGMENT.has(hit[1] ?? "")) {
+    return `${next.path}${next.suffix}`
+  }
+
+  if (next.path === "/docs") return `/docs/${value}${next.suffix}`
+  if (next.path === "/docs/") return `/docs/${value}/${next.suffix}`
+  return `/docs/${value}${next.path.slice("/docs".length)}${next.suffix}`
+}
+
 export function parseLocale(value: unknown): Locale | null {
   if (typeof value !== "string") return null
   if ((LOCALES as readonly string[]).includes(value)) return value as Locale
@@ -90,7 +164,7 @@ export function strip(pathname: string) {
 
 export function route(locale: Locale, pathname: string) {
   const next = strip(pathname)
-  if (next.startsWith("/docs")) return next
+  if (next.startsWith("/docs")) return docs(locale, next)
   if (next.startsWith("/auth")) return next
   if (next.startsWith("/workspace")) return next
   if (locale === "en") return next

+ 4 - 3
packages/console/app/src/routes/docs/[...path].ts

@@ -1,13 +1,14 @@
 import type { APIEvent } from "@solidjs/start/server"
-import { localeFromRequest, tag } from "~/lib/language"
+import { docs, localeFromRequest, tag } from "~/lib/language"
 
 async function handler(evt: APIEvent) {
   const req = evt.request.clone()
   const url = new URL(req.url)
-  const targetUrl = `https://docs.opencode.ai${url.pathname}${url.search}`
+  const locale = localeFromRequest(req)
+  const targetUrl = `https://docs.opencode.ai${docs(locale, url.pathname)}${url.search}`
 
   const headers = new Headers(req.headers)
-  headers.set("accept-language", tag(localeFromRequest(req)))
+  headers.set("accept-language", tag(locale))
 
   const response = await fetch(targetUrl, {
     method: req.method,

+ 4 - 3
packages/console/app/src/routes/docs/index.ts

@@ -1,13 +1,14 @@
 import type { APIEvent } from "@solidjs/start/server"
-import { localeFromRequest, tag } from "~/lib/language"
+import { docs, localeFromRequest, tag } from "~/lib/language"
 
 async function handler(evt: APIEvent) {
   const req = evt.request.clone()
   const url = new URL(req.url)
-  const targetUrl = `https://docs.opencode.ai${url.pathname}${url.search}`
+  const locale = localeFromRequest(req)
+  const targetUrl = `https://docs.opencode.ai${docs(locale, url.pathname)}${url.search}`
 
   const headers = new Headers(req.headers)
-  headers.set("accept-language", tag(localeFromRequest(req)))
+  headers.set("accept-language", tag(locale))
 
   const response = await fetch(targetUrl, {
     method: req.method,

+ 7 - 7
packages/console/app/src/routes/download/index.tsx

@@ -294,7 +294,7 @@ export default function Download() {
                   </span>
                   <span>VS Code</span>
                 </div>
-                <a href="/docs/ide/" data-component="action-button">
+                <a href={language.route("/docs/ide/")} data-component="action-button">
                   {i18n.t("download.action.install")}
                 </a>
               </div>
@@ -318,7 +318,7 @@ export default function Download() {
                   </span>
                   <span>Cursor</span>
                 </div>
-                <a href="/docs/ide/" data-component="action-button">
+                <a href={language.route("/docs/ide/")} data-component="action-button">
                   {i18n.t("download.action.install")}
                 </a>
               </div>
@@ -335,7 +335,7 @@ export default function Download() {
                   </span>
                   <span>Zed</span>
                 </div>
-                <a href="/docs/ide/" data-component="action-button">
+                <a href={language.route("/docs/ide/")} data-component="action-button">
                   {i18n.t("download.action.install")}
                 </a>
               </div>
@@ -352,7 +352,7 @@ export default function Download() {
                   </span>
                   <span>Windsurf</span>
                 </div>
-                <a href="/docs/ide/" data-component="action-button">
+                <a href={language.route("/docs/ide/")} data-component="action-button">
                   {i18n.t("download.action.install")}
                 </a>
               </div>
@@ -369,7 +369,7 @@ export default function Download() {
                   </span>
                   <span>VSCodium</span>
                 </div>
-                <a href="/docs/ide/" data-component="action-button">
+                <a href={language.route("/docs/ide/")} data-component="action-button">
                   {i18n.t("download.action.install")}
                 </a>
               </div>
@@ -393,7 +393,7 @@ export default function Download() {
                   </span>
                   <span>GitHub</span>
                 </div>
-                <a href="/docs/github/" data-component="action-button">
+                <a href={language.route("/docs/github/")} data-component="action-button">
                   {i18n.t("download.action.install")}
                 </a>
               </div>
@@ -410,7 +410,7 @@ export default function Download() {
                   </span>
                   <span>GitLab</span>
                 </div>
-                <a href="/docs/gitlab/" data-component="action-button">
+                <a href={language.route("/docs/gitlab/")} data-component="action-button">
                   {i18n.t("download.action.install")}
                 </a>
               </div>

+ 4 - 3
packages/console/app/src/routes/s/[id].ts

@@ -1,13 +1,14 @@
 import type { APIEvent } from "@solidjs/start/server"
-import { localeFromRequest, tag } from "~/lib/language"
+import { docs, localeFromRequest, tag } from "~/lib/language"
 
 async function handler(evt: APIEvent) {
   const req = evt.request.clone()
   const url = new URL(req.url)
-  const targetUrl = `https://docs.opencode.ai/docs${url.pathname}${url.search}`
+  const locale = localeFromRequest(req)
+  const targetUrl = `https://docs.opencode.ai${docs(locale, `/docs${url.pathname}`)}${url.search}`
 
   const headers = new Headers(req.headers)
-  headers.set("accept-language", tag(localeFromRequest(req)))
+  headers.set("accept-language", tag(locale))
 
   const response = await fetch(targetUrl, {
     method: req.method,

+ 3 - 1
packages/console/app/src/routes/workspace/[id]/index.tsx

@@ -9,10 +9,12 @@ import { GraphSection } from "./graph-section"
 import { IconLogo } from "~/component/icon"
 import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance } from "../common"
 import { useI18n } from "~/context/i18n"
+import { useLanguage } from "~/context/language"
 
 export default function () {
   const params = useParams()
   const i18n = useI18n()
+  const language = useLanguage()
   const userInfo = createAsync(() => querySessionInfo(params.id!))
   const billingInfo = createAsync(() => queryBillingInfo(params.id!))
   const checkoutAction = useAction(createCheckoutUrl)
@@ -38,7 +40,7 @@ export default function () {
         <p>
           <span>
             {i18n.t("workspace.home.banner.beforeLink")}{" "}
-            <a target="_blank" href="/docs/zen">
+            <a target="_blank" href={language.route("/docs/zen")}>
               {i18n.t("common.learnMore")}
             </a>
             .