恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Express.js 官网国际化(i18n)机制深度解析:Astro 路由、TypeScript 工具链与 Crowdin 的多语言协作实践
首页
资讯中心
/
Express.js 官网国际化(i18n)机制深度解析:Astro 路由、TypeScript 工具链与 Crowdin 的多语言协作实践
Express.js 官网国际化(i18n)机制深度解析:Astro 路由、TypeScript 工具链与 Crowdin 的多语言协作实践
发布时间:2026/10/13 1:40:39
文档教程技术博客【免费下载链接】expressjs.comThe Express.js Website项目地址https://gitcode.com/gh_mirrors/ex/expressjs.com点击查看免费下载本文以 docs/i18n.md 为核心骨架结合 expressjs.com 仓库源码src/i18n/、src/pages/[lang]/、src/plugins/、crowdin.yml等展开。本文面向简体中文开发者将系统梳理该网站如何通过 Astro 动态路由、TypeScript 类型安全的翻译工具函数与 Crowdin 自动化流程支撑 10 种语言的站点内容管理。读完你将掌握语言选择器与翻译函数的底层实现、语言无关内部链接的构建期本地化机制、如何新增一种语言、以及社区翻译贡献的标准工作流。一、多语言体系全景三大支柱expressjs.com 的多语言支持并非单一的翻译文件替换而是由三层机制协同完成Astro 动态路由通过[lang]路径参数实现/en/、/fr/、/de/等语言前缀路由TypeScript 工具链位于src/i18n/的配置与工具函数提供类型安全的语言识别与翻译查询Crowdin 集成crowdin.yml定义的自动化翻译管线负责翻译文件同步与 Pull Request 自动创建。这一组合让机器翻译 社区人工校对的协作模式成为可能Crowdin 支持 AI 自动翻译但由于 AI 翻译可能存在不准确之处项目欢迎社区贡献者通过 Crowdin 平台人工改进翻译质量。二、支持的语言与配置中心2.1 官方支持语言清单当前网站支持以下 10 种语言含语言代码与本地化名称代码语言本地名称en英语默认Englishde德语Deutsches西班牙语Españolfr法语Françaisit意大利语Italianoja日语日本語ko韩语한국어pt-br巴西葡萄牙语Portuguêszh-cn简体中文简体中文zh-tw繁体中文繁體中文注意zh-cn与zh-tw是两个独立的语言条目分别对应简体与繁体中文的翻译文件与语言前缀。2.2 语言配置的单一事实来源所有语言元数据集中在 src/i18n/locales.tsimport en from ./ui/en.json; import fr from ./ui/fr.json; // ... 其余语言 JSON 导入 export const languages { en: { label: English, direction: ltr }, de: { label: Deutsch, direction: ltr }, // ... pt-br: { label: Português, direction: ltr }, zh-cn: { label: 简体中文, direction: ltr }, zh-tw: { label: 繁體中文, direction: ltr }, } as const; export type LanguageCode keyof typeof languages; export const languageCodes Object.keys(languages); export const languagesArray Object.entries(languages).map(([code, obj]) ({ code, label: obj.label, })); export const defaultLang en; export const ui { en, fr, de, es, it, ja, ko, pt-br: ptBr, zh-cn: zhCn, zh-tw: zhTw, } as const;该文件导出的核心内容languages每种语言的元数据对象包含label显示名称与direction文本方向当前全部为ltr为将来可能的 RTL 语言预留LanguageCode由keyof typeof languages推导出的联合类型实现全项目类型安全languageCodes语言代码数组注释明确建议全项目优先使用它而不是重复Object.keys(languages)languagesArray{ code, label }结构数组直接供语言选择器等 UI 组件消费defaultLang默认语言en英语页面同时以根路径/与/en/形式服务详见下文路由章节ui语言代码到 JSON 翻译对象的映射是useTranslations的数据来源。从源码结构看新增语言时必须同步维护import语句、languages对象与ui映射三处这正是 docs/i18n.md 中更新 locales.ts步骤的实现细节。三、i18n 工具函数从 URL 到翻译文本src/i18n/utils.ts 是整个多语言体系的运行时大脑提供了 5 个核心函数import { ui, defaultLang, languageCodes } from ./locales; /** 从 URL 中提取语言代码/fr/api/ 的 pathname 第二段即语言 */ export function getLangFromUrl(url: URL) { const [, lang] url.pathname.split(/); if (lang in ui) return lang as keyof typeof ui; return defaultLang; // 未命中时回退到 en } /** 递归读取嵌套 JSON 键如 menu.main.docs */ function getNestedValue(obj, path: string): string | undefined { const value path.split(.).reduce((acc, part) { if (acc typeof acc object part in acc) { return (acc as Recordstring, unknown)[part]; } return undefined; }, obj); return typeof value string ? value : undefined; } /** 返回当前语言的翻译函数 t(key) */ export function useTranslations(lang: keyof typeof ui) { return function t(key: string): string { return getNestedValue(ui[lang], key) ?? getNestedValue(ui[defaultLang], key) ?? key; }; } /** 语言首页路径英语在根路径 /其余语言为 /{lang}/ */ export function getHomePath(lang: string): string { return lang defaultLang ? / : /${lang}/; } /** 生成匹配语言前缀的正则如 ^/(en|de|...)/ */ export function createLanguagePathRegex(): RegExp { const codes languageCodes.join(|); return new RegExp(^/(${codes})/); } /** 将路径中的语言前缀替换为新语言无前缀则直接添加 */ export function replaceLanguageInPath(path: string, newLang: string): string { const langRegex createLanguagePathRegex(); if (langRegex.test(path)) { return path.replace(langRegex, /${newLang}/); } else { return /${newLang}${path}; } }这些函数的设计要点getLangFromUrl利用URL对象直接解析pathname第二段就是语言代码若该段不在ui键集合中如无语言前缀的/api/...或根路径回退为defaultLanguseTranslations返回柯里化翻译函数t(key)采用当前语言 → 英语 → 原始 key三级回退策略——当某语言缺少某个 UI 字符串时自动降级显示英文避免页面出现空白getNestedValue支持点分路径如menu.main.docs在 JSON 对象中递归取值这正是翻译键的组织方式replaceLanguageInPath为语言切换器提供 URL 改写能力英语主页有特殊处理见下文组件章节。四、路由架构[lang]动态参数与多级回退4.1 路由入口多语言页面的核心路由是 src/pages/[lang]/[...slug].astro它通过[lang]与[...slug]两个动态参数组合出所有语言 × 文档路径的页面。同时存在三个配套入口src/pages/index.astro根路径/首页英语src/pages/[lang]/index.astro各语言首页/fr/、/de/等其getStaticPaths()直接遍历languageCodes生成src/pages/[...path].astro无语言前缀的兜底重定向路由。支持形如/en/api/、/fr/api/、/de/api/的 URL也支持/en/5x/guide/routing这样的语言 版本 路径三层结构。4.2 静态路径生成与英语回退[...slug].astro的getStaticPaths()是路由逻辑最复杂的部分从源码结构看它按优先级依次构建路径docs 集合src/content/docs/{lang}/{version}/...按语言 版本展开pages 集合src/content/pages/{lang}/...全局非版本页面api 集合API 参考是跨语言共享内容仅英语维护对每个语言代码生成/api路径默认版本别名为默认版本5x内容生成不带版本前缀的 URL 别名英语回退fallback当某语言缺少某个文档页时直接用英语内容在该语言路径下渲染——这意味着用户访问/fr/5x/guide/routing即使法语翻译尚未完成也能看到该页面的英语版本保证链接永不 404。构建路径时还用existingContent集合记录已有内容避免重复路径。此外共享集合api、blog在render()时通过Content components{{ a: LocalizedLink }} /注入链接本地化组件详见第六节。五、UI 翻译与组件中的使用方式5.1 翻译文件的组织结构UI 字符串与文档正文分离管理UI 字符串存放在 src/i18n/ui/ 下的 JSON 文件如en.json、fr.json、zh-cn.json包含导航标签、按钮文案、ARIA 无障碍标签等文档内容存放在src/content/docs/[lang-code]/[version]/版本化文档页与src/content/pages/[lang-code]/非版本化全局页面API 参考与博客文章目前不参与翻译仅以英语维护。以 src/i18n/ui/en.json 为例翻译键按语义分块组织{ common: { home: Home, download: Download }, nav: { mainMenu: Main menu, toggleMenu: Toggle menu, home: home }, menu: { main: { docs: Docs, api: API, resources: Resources }, sections: { gettingStarted: Getting started, guide: Guide }, items: { installing: Installing, routing: Routing } }, search: { placeholder: Start typing..., ariaLabel: Start typing to search }, theme: { toggle: Toggle theme }, language: { selectLabel: Select language }, page: { editOnGitHub: Edit on GitHub, translateThis: Translate this page }, doc: { previousPage: Previous, nextPage: Next } }各语言的 JSON 保持完全相同的键结构如 src/i18n/ui/fr.json 中menu: { main: { docs: Documents } }、src/i18n/ui/zh-cn.json 中menu: { main: { docs: 文档 } }。这种同构结构正是getNestedValue点分路径查询能够成立的前提。5.2 在 Astro 组件中使用翻译src/components/patterns/Header/Header.astro 展示了标准用法——先取语言、再取翻译函数--- import { getLangFromUrl, useTranslations } from /i18n/utils; const currentLang getLangFromUrl(Astro.url); const t useTranslations(currentLang); const homeHref getHomePath(currentLang); --- header classheader a href{homeHref} classlogo-link aria-label{t(nav.home)} !-- 站点 Logo -- /a SearchBox lang{currentLang} placeholder{t(search.placeholder)} ariaLabel{t(search.ariaLabel)} / LanguageSelect languages{languagesArray} currentLanguage{currentLang} / /headerAstro.url对象自动提供当前页面 URL无需额外解析即可完成语言提取与翻译绑定。这一模式在全项目被广泛复用如Sidebar、Breadcrumbs、Hero、Searchbox等组件均导入useTranslations/getLangFromUrl。5.3 语言切换器组件的运行时逻辑src/components/patterns/LanguageSelect/LanguageSelect.astro 基于Select原语组件构建核心交互逻辑在script中import { replaceLanguageInPath } from /i18n/utils; select.addEventListener(select-change, ((e: CustomEvent) { const code e.detail.value; const currentPath window.location.pathname; const newPath replaceLanguageInPath(currentPath, code); // 英语首页以 / 为规范路径而非 /en/ window.location.href newPath /en/ ? / : newPath; }) as EventListener);它监听从/fr/5x/guide/routing这样的路径调用replaceLanguageInPath把/fr/替换为/de/并特殊处理英语首页跳转到/。组件还通过document.addEventListener(astro:page-load, handleLanguageChange)保证 Astro 视图切换后事件重新绑定。六、内容组织与语言无关的内部链接6.1 内容集合的划分依据 src/content.config.ts内容分为四个集合docs版本化文档按{lang}/{version}/组织、api版本化 API 参考共享内容仅英语、pages非版本化全局页面按{lang}/组织、blog博客共享内容仅英语。翻译后的文档正是分布在docs与pages集合的各语言子目录下。6.2 内部链接的黄金法则永远不写死语言前缀这是多语言站点最容易踩坑的地方docs/i18n.md 与 docs/content.md 都重点强调内部链接必须写语言无关形式/guide/routing、/api/express构建期会自动本地化为当前页面语言。因为 Crowdin 会逐字复制链接到翻译文件中一旦写死/en/...翻译后的页面就会链接回英语版破坏本地化体验。6.3 链接本地化的构建期实现语言无关链接的本地化由两个插件在构建期完成其共享逻辑位于 src/plugins/rewrite-localized-links-core.mjssrc/plugins/remark-rewrite-localized-links.mjs处理 Markdown 链接text与引用式定义节点src/plugins/rehype-rewrite-localized-links.mjs处理 MDX 中原始 HTML/JSX 锚点a href/...例如包裹Card/组件的卡片链接。两个插件在 astro.config.mjs 的markdown.processor中注册无需额外配置默认参数即可工作。核心重写规则来自rewriteUrl函数均有测试用例佐证写法渲染结果以西班牙语 5.x 页面为例规则说明/api/express/es/5x/api/express/版本化区块api/starter/guide/advanced自动注入源文件版本/guide/routing/es/5x/guide/routing/同上/guide/migrating-5/es/guide/migrating-5/全局页面位于非版本化pages集合保持无版本/4x/api/express/es/4x/api/express/显式版本被保留跨版本链接/resources/middleware/es/resources/middleware/resources/support/blog永不携带版本插件还具备以下防护逻辑跳过已带语言前缀的链接/en/...、外链、相对路径./x、纯锚点#x与图片等非目标区块路径测试用例/images/express-mw.png保持原样保留查询字符串与 hash 片段如/5x/api/express#expressstaticroot-options共享集合api、blog源文件路径无语言段语言回退到默认en渲染时再通过LocalizedLink组件按当前页面语言二次改写。配套测试位于 tests/unit/remark-rewrite-localized-links.test.mjs 与 tests/unit/rehype-rewrite-localized-links.test.mjs覆盖跨版本链接保留、全局页面去版本、前缀跳过、外部链接不处理等 30 余条断言是理解该机制的权威参考。6.4 LocalizedLink 组件的运行时补充src/components/patterns/LocalizedLink/LocalizedLink.astro 解决的是共享集合api、blog的特殊问题这类内容没有分语言源文件MDX 编译一次后链接已被插件烘焙为默认语言/en/...。因此渲染时以a元素覆盖的形式Content components{{ a: LocalizedLink }} /重新指向当前语言裸路径则从零本地化。锚点、外链与相对链接保持不动。七、Crowdin 集成翻译协作与自动化同步7.1 配置文件解读根目录 crowdin.yml 定义了翻译管线的完整映射files: - source: /src/content/pages/en/**/*.* translation: /src/content/pages/%two_letters_code%/**/%original_file_name% - source: /src/content/docs/en/**/*.* translation: /src/content/docs/%two_letters_code%/**/%original_file_name% - source: /src/i18n/ui/en.json translation: /src/i18n/ui/%two_letters_code%.json preserve_hierarchy: true project_id_env: CROWDIN_PROJECT_ID base_url: https://express.api.crowdin.com api_token_env: CROWDIN_PERSONAL_TOKEN要点三组源文件英语文档pages与docs两个集合以及 UI 字符串en.json翻译目标%two_letters_code%占位符按语言代码展开如fr、depreserve_hierarchy: true保持目录结构环境变量CROWDIN_PROJECT_ID与CROWDIN_PERSONAL_TOKEN从环境读取用于 API 自动化不在仓库中硬编码凭据。7.2 自动化工作流当 Crowdin 中的翻译完成时翻译文件自动同步回仓库即crowdin.yml映射的目标路径机器人自动创建包含翻译更新的 Pull Request。这一闭环意味着仓库中的翻译文件是 Crowdin 的产物而不是人工维护的源。八、操作指南新增语言、贡献翻译与添加 UI 字符串8.1 新增一种语言按照 docs/i18n.md 的流程共三步申请语言支持联系网站负责人website captain在 Crowdin 项目中请求新语言——只有网站维护者能在 Crowdin 项目中添加语言等待 Crowdin 生成文件审批通过后自动生成src/i18n/ui/[lang-code].jsonUI 字符串与src/content/docs/[lang-code]/文档内容目录更新src/i18n/locales.ts在languages对象中添加该语言条目并导入对应的 JSON 文件如前文第 2.2 节所示需同步修改 import、languages与ui三处。8.2 社区翻译贡献流程申请加入 Express.js Website 的 Crowdin 项目在 Crowdin 中选择要翻译的语言在 Crowdin 在线编辑器中开始翻译。重要约束翻译贡献必须通过 Crowdin 完成不能直接修改仓库中的翻译文件——直接编辑会被 Crowdin 同步覆盖。8.3 添加新的 UI 字符串先在 src/i18n/ui/en.json 中添加该字符串结构会同步到 Crowdin翻译人员在 Crowdin 中完成翻译更新后的翻译自动同步回仓库。九、维护红线翻译文件是托管产物docs/i18n.md 最后给出明确警告也是本仓库多语言维护最重要的一条纪律不要手动创建或编辑翻译文件src/i18n/ui/*.json和src/content/docs/[lang-code]/*.*。这些文件由 Crowdin 自动管理任何手动修改都会被覆盖。所有翻译更新必须走 Crowdin 工作流。同理docs/content.md 强调链接规则时也指出因为 Crowdin 逐字复制链接写死语言前缀会让翻译页面指向英语。这两条规则共同保障了自动化管线与人工内容之间不会产生冲突。十、小结从 URL 到翻译文本的完整链路将上述机制串起来一次多语言页面请求的完整链路是路由匹配[lang]/[...slug].astro依据 URL 语言段选中页面语言提取getLangFromUrl(Astro.url)解析当前语言UI 翻译useTranslations(lang)返回t(key)按当前语言 → 英语 → 原始键三级回退取词内容渲染docs/pages 集合按语言加载对应 MDX缺失时回退英语内容链接本地化构建期 remark/rehype 插件 渲染期LocalizedLink组件确保所有内部链接指向当前语言与版本用户切换LanguageSelect通过replaceLanguageInPath完成跨语言 URL 改写翻译回流Crowdin 同步新翻译并自动创建 PR。这套Astro 路由 TypeScript 工具 Crowdin 自动化的组合将机器翻译的规模化能力与社区人工校对的准确性结合是静态文档站点多语言运营的一个完整参考实现。开发者若在自己的 Astro 站点中复用此模式可重点关注locales.ts的单一事实来源设计、useTranslations的三级回退策略以及语言无关内部链接 构建期本地化的插件化思路——三者分别解决了配置集中、容错取值与链接一致性问题。赞分享文档教程技术博客【免费下载链接】expressjs.comThe Express.js Website项目地址https://gitcode.com/gh_mirrors/ex/expressjs.com点击查看免费下载相关推荐Astro国际化i18n多语言路由与内容管理Astro国际化i18n多语言路由与内容管理 痛点全球化时代的网站建设挑战 在当今全球化时代网站需要服务来自不同语言和文化背景的用户。传统多语言网站开发面前端Web框架SSR前端构建Lit多语言路由国际化路由的最佳实践Lit多语言路由国际化路由的最佳实践 引言全球化Web应用的新挑战 在当今全球化的互联网环境中构建支持多语言的Web应用已成为基本需求。然而传统的国际化前端UI组件Express.js国际化(i18n)实现终极指南快速构建多语言网站Express.js国际化 i18n 实现终极指南快速构建多语言网站 在全球化时代构建支持多语言的网站已成为提升用户体验的关键。Express.js作为No文档教程技术博客上一篇终极Vagas合规性指南10个招聘数据保护与隐私政策实施技巧下一篇**深度解析与体验——user-status项目打造实时在线用户管理的完美工具**创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考