From 0cf57f02a31db59a4f5d83aba3496aaaddc7c35a Mon Sep 17 00:00:00 2001 From: Liuzhaoliang Date: Mon, 19 Jan 2026 22:26:22 +0800 Subject: [PATCH] chore: add i18n zh-CN --- bun.lock | 1 + .../console/app/script/generate-sitemap.ts | 52 +- packages/web/astro.config.mjs | 147 +- packages/web/package.json | 3 +- packages/web/src/components/Header.astro | 160 +- packages/web/src/components/Lander.astro | 136 +- packages/web/src/content.config.ts | 26 +- .../web/src/content/docs/{ => en}/1-0.mdx | 0 .../web/src/content/docs/{ => en}/acp.mdx | 0 .../web/src/content/docs/{ => en}/agents.mdx | 0 .../web/src/content/docs/{ => en}/cli.mdx | 0 .../src/content/docs/{ => en}/commands.mdx | 0 .../web/src/content/docs/{ => en}/config.mdx | 0 .../content/docs/{ => en}/custom-tools.mdx | 0 .../src/content/docs/{ => en}/ecosystem.mdx | 0 .../src/content/docs/{ => en}/enterprise.mdx | 2 +- .../src/content/docs/{ => en}/formatters.mdx | 0 .../web/src/content/docs/{ => en}/github.mdx | 0 .../web/src/content/docs/{ => en}/gitlab.mdx | 0 .../web/src/content/docs/{ => en}/ide.mdx | 0 .../web/src/content/docs/{ => en}/index.mdx | 4 +- .../src/content/docs/{ => en}/keybinds.mdx | 0 .../web/src/content/docs/{ => en}/lsp.mdx | 0 .../src/content/docs/{ => en}/mcp-servers.mdx | 0 .../web/src/content/docs/{ => en}/models.mdx | 0 .../web/src/content/docs/{ => en}/modes.mdx | 0 .../web/src/content/docs/{ => en}/network.mdx | 0 .../src/content/docs/{ => en}/permissions.mdx | 2 +- .../web/src/content/docs/{ => en}/plugins.mdx | 0 .../src/content/docs/{ => en}/providers.mdx | 2 +- .../web/src/content/docs/{ => en}/rules.mdx | 0 .../web/src/content/docs/{ => en}/sdk.mdx | 2 +- .../web/src/content/docs/{ => en}/server.mdx | 2 +- .../web/src/content/docs/{ => en}/share.mdx | 0 .../web/src/content/docs/{ => en}/skills.mdx | 0 .../web/src/content/docs/{ => en}/themes.mdx | 0 .../web/src/content/docs/{ => en}/tools.mdx | 0 .../content/docs/{ => en}/troubleshooting.mdx | 0 .../web/src/content/docs/{ => en}/tui.mdx | 0 .../web/src/content/docs/{ => en}/web.mdx | 6 +- .../web/src/content/docs/{ => en}/zen.mdx | 2 +- packages/web/src/content/docs/zh-cn/1-0.mdx | 66 + packages/web/src/content/docs/zh-cn/acp.mdx | 156 ++ .../web/src/content/docs/zh-cn/agents.mdx | 679 +++++++ packages/web/src/content/docs/zh-cn/cli.mdx | 593 ++++++ .../web/src/content/docs/zh-cn/commands.mdx | 322 +++ .../web/src/content/docs/zh-cn/config.mdx | 678 +++++++ .../src/content/docs/zh-cn/custom-tools.mdx | 165 ++ .../web/src/content/docs/zh-cn/ecosystem.mdx | 69 + .../web/src/content/docs/zh-cn/enterprise.mdx | 165 ++ .../web/src/content/docs/zh-cn/formatters.mdx | 128 ++ .../web/src/content/docs/zh-cn/github.mdx | 321 +++ .../web/src/content/docs/zh-cn/gitlab.mdx | 195 ++ packages/web/src/content/docs/zh-cn/ide.mdx | 48 + packages/web/src/content/docs/zh-cn/index.mdx | 340 ++++ .../web/src/content/docs/zh-cn/keybinds.mdx | 191 ++ packages/web/src/content/docs/zh-cn/lsp.mdx | 143 ++ .../src/content/docs/zh-cn/mcp-servers.mdx | 511 +++++ .../web/src/content/docs/zh-cn/models.mdx | 221 ++ packages/web/src/content/docs/zh-cn/modes.mdx | 328 +++ .../web/src/content/docs/zh-cn/network.mdx | 57 + .../src/content/docs/zh-cn/permissions.mdx | 192 ++ .../web/src/content/docs/zh-cn/plugins.mdx | 360 ++++ .../web/src/content/docs/zh-cn/providers.mdx | 1796 +++++++++++++++++ packages/web/src/content/docs/zh-cn/rules.mdx | 180 ++ packages/web/src/content/docs/zh-cn/sdk.mdx | 391 ++++ .../web/src/content/docs/zh-cn/server.mdx | 283 +++ packages/web/src/content/docs/zh-cn/share.mdx | 127 ++ .../web/src/content/docs/zh-cn/skills.mdx | 220 ++ .../web/src/content/docs/zh-cn/test-page.mdx | 6 + .../web/src/content/docs/zh-cn/themes.mdx | 369 ++++ packages/web/src/content/docs/zh-cn/tools.mdx | 345 ++++ .../content/docs/zh-cn/troubleshooting.mdx | 163 ++ packages/web/src/content/docs/zh-cn/tui.mdx | 388 ++++ packages/web/src/content/docs/zh-cn/web.mdx | 132 ++ packages/web/src/content/docs/zh-cn/zen.mdx | 227 +++ packages/web/src/pages/[...slug].md.ts | 10 +- packages/web/src/pages/en/[...slug].ts | 8 + packages/web/src/pages/en/index.ts | 6 + 79 files changed, 11065 insertions(+), 61 deletions(-) rename packages/web/src/content/docs/{ => en}/1-0.mdx (100%) rename packages/web/src/content/docs/{ => en}/acp.mdx (100%) rename packages/web/src/content/docs/{ => en}/agents.mdx (100%) rename packages/web/src/content/docs/{ => en}/cli.mdx (100%) rename packages/web/src/content/docs/{ => en}/commands.mdx (100%) rename packages/web/src/content/docs/{ => en}/config.mdx (100%) rename packages/web/src/content/docs/{ => en}/custom-tools.mdx (100%) rename packages/web/src/content/docs/{ => en}/ecosystem.mdx (100%) rename packages/web/src/content/docs/{ => en}/enterprise.mdx (99%) rename packages/web/src/content/docs/{ => en}/formatters.mdx (100%) rename packages/web/src/content/docs/{ => en}/github.mdx (100%) rename packages/web/src/content/docs/{ => en}/gitlab.mdx (100%) rename packages/web/src/content/docs/{ => en}/ide.mdx (100%) rename packages/web/src/content/docs/{ => en}/index.mdx (98%) rename packages/web/src/content/docs/{ => en}/keybinds.mdx (100%) rename packages/web/src/content/docs/{ => en}/lsp.mdx (100%) rename packages/web/src/content/docs/{ => en}/mcp-servers.mdx (100%) rename packages/web/src/content/docs/{ => en}/models.mdx (100%) rename packages/web/src/content/docs/{ => en}/modes.mdx (100%) rename packages/web/src/content/docs/{ => en}/network.mdx (100%) rename packages/web/src/content/docs/{ => en}/permissions.mdx (98%) rename packages/web/src/content/docs/{ => en}/plugins.mdx (100%) rename packages/web/src/content/docs/{ => en}/providers.mdx (99%) rename packages/web/src/content/docs/{ => en}/rules.mdx (100%) rename packages/web/src/content/docs/{ => en}/sdk.mdx (99%) rename packages/web/src/content/docs/{ => en}/server.mdx (99%) rename packages/web/src/content/docs/{ => en}/share.mdx (100%) rename packages/web/src/content/docs/{ => en}/skills.mdx (100%) rename packages/web/src/content/docs/{ => en}/themes.mdx (100%) rename packages/web/src/content/docs/{ => en}/tools.mdx (100%) rename packages/web/src/content/docs/{ => en}/troubleshooting.mdx (100%) rename packages/web/src/content/docs/{ => en}/tui.mdx (100%) rename packages/web/src/content/docs/{ => en}/web.mdx (91%) rename packages/web/src/content/docs/{ => en}/zen.mdx (99%) create mode 100644 packages/web/src/content/docs/zh-cn/1-0.mdx create mode 100644 packages/web/src/content/docs/zh-cn/acp.mdx create mode 100644 packages/web/src/content/docs/zh-cn/agents.mdx create mode 100644 packages/web/src/content/docs/zh-cn/cli.mdx create mode 100644 packages/web/src/content/docs/zh-cn/commands.mdx create mode 100644 packages/web/src/content/docs/zh-cn/config.mdx create mode 100644 packages/web/src/content/docs/zh-cn/custom-tools.mdx create mode 100644 packages/web/src/content/docs/zh-cn/ecosystem.mdx create mode 100644 packages/web/src/content/docs/zh-cn/enterprise.mdx create mode 100644 packages/web/src/content/docs/zh-cn/formatters.mdx create mode 100644 packages/web/src/content/docs/zh-cn/github.mdx create mode 100644 packages/web/src/content/docs/zh-cn/gitlab.mdx create mode 100644 packages/web/src/content/docs/zh-cn/ide.mdx create mode 100644 packages/web/src/content/docs/zh-cn/index.mdx create mode 100644 packages/web/src/content/docs/zh-cn/keybinds.mdx create mode 100644 packages/web/src/content/docs/zh-cn/lsp.mdx create mode 100644 packages/web/src/content/docs/zh-cn/mcp-servers.mdx create mode 100644 packages/web/src/content/docs/zh-cn/models.mdx create mode 100644 packages/web/src/content/docs/zh-cn/modes.mdx create mode 100644 packages/web/src/content/docs/zh-cn/network.mdx create mode 100644 packages/web/src/content/docs/zh-cn/permissions.mdx create mode 100644 packages/web/src/content/docs/zh-cn/plugins.mdx create mode 100644 packages/web/src/content/docs/zh-cn/providers.mdx create mode 100644 packages/web/src/content/docs/zh-cn/rules.mdx create mode 100644 packages/web/src/content/docs/zh-cn/sdk.mdx create mode 100644 packages/web/src/content/docs/zh-cn/server.mdx create mode 100644 packages/web/src/content/docs/zh-cn/share.mdx create mode 100644 packages/web/src/content/docs/zh-cn/skills.mdx create mode 100644 packages/web/src/content/docs/zh-cn/test-page.mdx create mode 100644 packages/web/src/content/docs/zh-cn/themes.mdx create mode 100644 packages/web/src/content/docs/zh-cn/tools.mdx create mode 100644 packages/web/src/content/docs/zh-cn/troubleshooting.mdx create mode 100644 packages/web/src/content/docs/zh-cn/tui.mdx create mode 100644 packages/web/src/content/docs/zh-cn/web.mdx create mode 100644 packages/web/src/content/docs/zh-cn/zen.mdx create mode 100644 packages/web/src/pages/en/[...slug].ts create mode 100644 packages/web/src/pages/en/index.ts diff --git a/bun.lock b/bun.lock index a9cabb31114..d8ee468c50b 100644 --- a/bun.lock +++ b/bun.lock @@ -475,6 +475,7 @@ "shiki": "catalog:", "solid-js": "catalog:", "toolbeam-docs-theme": "0.4.8", + "unist-util-visit": "5.0.0", }, "devDependencies": { "@types/node": "catalog:", diff --git a/packages/console/app/script/generate-sitemap.ts b/packages/console/app/script/generate-sitemap.ts index 6cbffcb8516..9edcf36c8e7 100755 --- a/packages/console/app/script/generate-sitemap.ts +++ b/packages/console/app/script/generate-sitemap.ts @@ -42,19 +42,47 @@ async function getDocsRoutes(): Promise { const routes: SitemapEntry[] = [] try { - const files = await readdir(DOCS_DIR) - - for (const file of files) { - if (!file.endsWith(".mdx")) continue - - const slug = file.replace(".mdx", "") - const path = slug === "index" ? "/docs/" : `/docs/${slug}` + async function walkMdxFiles(rootDir: string, currentDir = rootDir): Promise { + const entries = await readdir(currentDir, { withFileTypes: true }) + const results: string[] = [] + + for (const entry of entries) { + const absolute = join(currentDir, entry.name) + if (entry.isDirectory()) { + results.push(...(await walkMdxFiles(rootDir, absolute))) + continue + } + if (!entry.isFile()) continue + if (!entry.name.endsWith(".mdx")) continue + results.push(absolute) + } + + return results + } - routes.push({ - url: `${BASE_URL}${path}`, - priority: slug === "index" ? 0.9 : 0.7, - changefreq: "weekly", - }) + const localeDirs = (await readdir(DOCS_DIR, { withFileTypes: true })) + .filter((d) => d.isDirectory()) + .map((d) => d.name) + + for (const docsDirName of localeDirs) { + const routeLocale = docsDirName === "en" ? "root" : docsDirName + const localeDir = join(DOCS_DIR, docsDirName) + const files = await walkMdxFiles(localeDir) + + for (const absolutePath of files) { + const relative = absolutePath.slice(localeDir.length + 1) + const slug = relative.replace(/\.mdx$/, "").replace(/\\/g, "/") + const normalizedSlug = + slug === "index" ? "" : slug.endsWith("/index") ? slug.slice(0, -6) : slug + const basePath = routeLocale === "root" ? "/docs/" : `/docs/${routeLocale}/` + const path = normalizedSlug ? `${basePath}${normalizedSlug}/` : basePath + + routes.push({ + url: `${BASE_URL}${path}`, + priority: slug === "index" ? 0.9 : 0.7, + changefreq: "weekly", + }) + } } } catch (error) { console.error("Error reading docs directory:", error) diff --git a/packages/web/astro.config.mjs b/packages/web/astro.config.mjs index 99a1c3bd80c..1d3467befdc 100644 --- a/packages/web/astro.config.mjs +++ b/packages/web/astro.config.mjs @@ -8,6 +8,7 @@ import config from "./config.mjs" import { rehypeHeadingIds } from "@astrojs/markdown-remark" import rehypeAutolinkHeadings from "rehype-autolink-headings" import { spawnSync } from "child_process" +import { visit } from "unist-util-visit" // https://astro.build/config export default defineConfig({ @@ -24,7 +25,33 @@ export default defineConfig({ host: "0.0.0.0", }, markdown: { - rehypePlugins: [rehypeHeadingIds, [rehypeAutolinkHeadings, { behavior: "wrap" }]], + rehypePlugins: [ + rehypeHeadingIds, + [rehypeAutolinkHeadings, { behavior: "wrap" }], + () => (tree, file) => { + const filePath = typeof file?.path === "string" ? file.path : "" + const normalizedPath = filePath.replace(/\\/g, "/") + const docsMarker = "/src/content/docs/" + const docsIndex = normalizedPath.lastIndexOf(docsMarker) + const localeDir = + docsIndex === -1 + ? "en" + : normalizedPath.slice(docsIndex + docsMarker.length).split("/")[0] || "en" + const locale = localeDir === "en" ? "root" : localeDir + const base = locale === "root" ? "/docs" : `/docs/${locale}` + + visit(tree, "element", (node) => { + if (node.tagName !== "a") return + const href = node.properties?.href + if (typeof href !== "string") return + if (!href.startsWith("/docs")) return + const isRoot = href === "/docs" + const isRootHash = href.startsWith("/docs#") + const normalized = isRoot ? base : isRootHash ? `${base}${href.slice(5)}` : href.replace("/docs/", `${base}/`) + node.properties.href = normalized + }) + }, + ], }, build: {}, integrations: [ @@ -32,6 +59,11 @@ export default defineConfig({ solidJs(), starlight({ title: "OpenCode", + locales: { + root: { label: "English", lang: "en" }, + "zh-cn": { label: "中文", lang: "zh-CN" }, + }, + defaultLocale: "root", lastUpdated: true, expressiveCode: { themes: ["github-light", "github-dark"] }, social: [ @@ -50,44 +82,88 @@ export default defineConfig({ dark: "./src/assets/logo-dark.svg", replacesTitle: true, }, - sidebar: [ - "", - "config", - "providers", - "network", - "enterprise", - "troubleshooting", - "1-0", + // @ts-ignore + sidebar: withTranslations([ + { + label: "Intro", + translations: { "zh-cn": "介绍" }, + link: "", + }, + { + label: "Config", + translations: { "zh-cn": "配置" }, + link: "config", + }, + { + label: "Providers", + translations: { "zh-cn": "提供商" }, + link: "providers", + }, + { + label: "Network", + translations: { "zh-cn": "网络" }, + link: "network", + }, + { + label: "Enterprise", + translations: { "zh-cn": "企业" }, + link: "enterprise", + }, + { + label: "Troubleshooting", + translations: { "zh-cn": "故障排查" }, + link: "troubleshooting", + }, + { + label: "1-0", + translations: { "zh-cn": "1-0 版本" }, + link: "1-0", + }, { label: "Usage", - items: ["tui", "cli", "web", "ide", "zen", "share", "github", "gitlab"], + translations: { "zh-cn": "使用" }, + items: [ + { label: "TUI", translations: { "zh-cn": "TUI" }, link: "tui" }, + { label: "CLI", translations: { "zh-cn": "CLI" }, link: "cli" }, + { label: "Web", translations: { "zh-cn": "Web" }, link: "web" }, + { label: "IDE", translations: { "zh-cn": "IDE" }, link: "ide" }, + { label: "Zen", translations: { "zh-cn": "Zen" }, link: "zen" }, + { label: "Share", translations: { "zh-cn": "分享" }, link: "share" }, + { label: "GitHub", translations: { "zh-cn": "GitHub" }, link: "github" }, + { label: "GitLab", translations: { "zh-cn": "GitLab" }, link: "gitlab" }, + ], }, - { label: "Configure", + translations: { "zh-cn": "配置" }, items: [ - "tools", - "rules", - "agents", - "models", - "themes", - "keybinds", - "commands", - "formatters", - "permissions", - "lsp", - "mcp-servers", - "acp", - "skills", - "custom-tools", + { label: "tools", translations: { "zh-cn": "tools" }, link: "tools" }, + { label: "rules", translations: { "zh-cn": "规则" }, link: "rules" }, + { label: "agents", translations: { "zh-cn": "agents" }, link: "agents" }, + { label: "models", translations: { "zh-cn": "模型" }, link: "models" }, + { label: "themes", translations: { "zh-cn": "主题" }, link: "themes" }, + { label: "keybinds", translations: { "zh-cn": "快捷键" }, link: "keybinds" }, + { label: "commands", translations: { "zh-cn": "命令" }, link: "commands" }, + { label: "formatters", translations: { "zh-cn": "格式化" }, link: "formatters" }, + { label: "permissions", translations: { "zh-cn": "权限" }, link: "permissions" }, + { label: "lsp", translations: { "zh-cn": "LSP" }, link: "lsp" }, + { label: "mcp-servers", translations: { "zh-cn": "mcp servers" }, link: "mcp-servers" }, + { label: "acp", translations: { "zh-cn": "ACP" }, link: "acp" }, + { label: "skills", translations: { "zh-cn": "skills" }, link: "skills" }, + { label: "custom tools", translations: { "zh-cn": "custom tools" }, link: "custom-tools" }, ], }, - { label: "Develop", - items: ["sdk", "server", "plugins", "ecosystem"], + translations: { "zh-cn": "开发" }, + items: [ + { label: "SDK", translations: { "zh-cn": "SDK" }, link: "sdk" }, + { label: "Server", translations: { "zh-cn": "服务器" }, link: "server" }, + { label: "Plugins", translations: { "zh-cn": "插件" }, link: "plugins" }, + { label: "Ecosystem", translations: { "zh-cn": "生态" }, link: "ecosystem" }, + ], }, - ], + ]), components: { Hero: "./src/components/Hero.astro", Head: "./src/components/Head.astro", @@ -114,3 +190,18 @@ function configSchema() { }, } } + +/** + * @param {any[]} sidebar + */ +function withTranslations(sidebar) { + return sidebar.map((item) => { + if (item.translations && item.translations["zh-cn"]) { + item.translations["zh-CN"] = item.translations["zh-cn"] + } + if (item.items) { + item.items = withTranslations(item.items) + } + return item + }) +} diff --git a/packages/web/package.json b/packages/web/package.json index aef7c0c706d..9415b59209a 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -31,7 +31,8 @@ "remeda": "catalog:", "shiki": "catalog:", "solid-js": "catalog:", - "toolbeam-docs-theme": "0.4.8" + "toolbeam-docs-theme": "0.4.8", + "unist-util-visit": "5.0.0" }, "devDependencies": { "opencode": "workspace:*", diff --git a/packages/web/src/components/Header.astro b/packages/web/src/components/Header.astro index 396200a3eb1..6164165c521 100644 --- a/packages/web/src/components/Header.astro +++ b/packages/web/src/components/Header.astro @@ -3,15 +3,50 @@ import config from '../../config.mjs'; import astroConfig from 'virtual:starlight/user-config'; import { Icon } from '@astrojs/starlight/components'; import { HeaderLinks } from 'toolbeam-docs-theme/components'; -import Default from 'toolbeam-docs-theme/overrides/Header.astro'; import SocialIcons from 'virtual:starlight/components/SocialIcons'; import SiteTitle from '@astrojs/starlight/components/SiteTitle.astro'; +import Search from "virtual:starlight/components/Search"; const path = Astro.url.pathname; const links = astroConfig.social || []; const headerLinks = config.headerLinks; +let relativePath = path.replace(/\/$/, ""); +if (relativePath.startsWith("/docs")) relativePath = relativePath.slice(5); +if (relativePath.startsWith("/")) relativePath = relativePath.slice(1); + +const locales = astroConfig.locales || {}; +const localeKeys = Object.keys(locales); + +const segments = relativePath.split("/").filter(Boolean); +const firstLower = (segments[0] || "").toLowerCase(); + +let locale = "root"; +if (firstLower === "en") { + segments.shift(); +} else { + const key = localeKeys.find((k) => k.toLowerCase() === firstLower); + if (key && key !== "root") { + locale = key; + segments.shift(); + } +} + +const slug = segments.join("/"); +const localeLabel = locales[locale]?.label ?? (locale === "root" ? "English" : locale); +const localeLinks = localeKeys.map((key) => { + const isRoot = key === "root"; + const href = isRoot ? (slug ? `/docs/${slug}` : "/docs") : (slug ? `/docs/${key}/${slug}` : `/docs/${key}`); + return { key, href, label: locales[key]?.label ?? key }; +}); + +const isDocs = path.startsWith("/docs"); + +const shouldRenderSearch = + astroConfig.pagefind || + astroConfig.components.Search !== "@astrojs/starlight/components/Search.astro"; + --- { path.startsWith("/s") @@ -27,6 +62,21 @@ const headerLinks = config.headerLinks; }
+ {isDocs && ( +
+ +
+ {localeLinks.map((l) => ( + {l.label} + ))} +
+
+ )} { links.length > 0 && (
- : + :
+
+ +
+
+ +
+
+
+ +
+ {localeLinks.map((l) => ( + {l.label} + ))} +
+
+ + { + links.length > 0 && ( + + ) + } + {shouldRenderSearch && } +
+
}