跳到主要内容

ETN Website i18n 国际化设计文档

目标

为 ETN 网站(Docusaurus 3.9.2)添加中英双语支持,面向海外用户扩展。中文保持现有 URL,英文走 /en/ 前缀,SEO 双语收录,首次访问自动检测语言。


架构

URL 结构

LocaleURL构建输出
中文(默认)everythingto.site/website/build/website/
Englisheverythingto.site/en/website/build/en/website/

技术选型

  • 翻译方式:Docusaurus 官方 <Translate> 组件 + code.json 翻译文件
  • 构建方式npm run build 静态生成两套完整 HTML,SEO 可索引
  • 语言检测navigator.language + localStorage("etn-locale") 客户端模块
  • hreflang:Docusaurus 自动注入,告知搜索引擎中英页面关系

文件结构

etn-website/
├── i18n/
│ └── en/
│ ├── code.json # 所有 <Translate> 字符串英文版
│ └── docusaurus-theme-classic/
│ ├── navbar.json # 导航标签英文
│ └── footer.json # Footer 英文
├── src/
│ └── clientModules/
│ └── autoLocale.ts # 首次访问语言自动检测
└── docusaurus.config.ts # 新增 en locale 配置

各模块设计

1. Docusaurus 配置变更(docusaurus.config.ts

i18n: {
defaultLocale: 'zh-Hans',
locales: ['zh-Hans', 'en'],
localeConfigs: {
'zh-Hans': { label: '中文', direction: 'ltr' },
'en': { label: 'English', direction: 'ltr' },
},
},

新增 clientModules

clientModules: ['./src/clientModules/autoLocale.ts'],

2. 自动语言检测(src/clientModules/autoLocale.ts

逻辑:

  1. localStorage.getItem('etn-locale')
  2. 有缓存 → 按缓存跳转(避免重复检测)
  3. 无缓存 → 读 navigator.language
    • zh 开头 → 不跳转(已在默认中文路径)
    • 其他 → 跳转 /en/website/ + 写缓存 en
export default function autoLocale(): void {
if (typeof window === 'undefined') return;

const stored = localStorage.getItem('etn-locale');
const currentPath = window.location.pathname;
const isEnPath = currentPath.startsWith('/en/');

if (stored) {
if (stored === 'en' && !isEnPath) {
window.location.replace('/en' + currentPath);
}
if (stored === 'zh-Hans' && isEnPath) {
window.location.replace(currentPath.replace(/^\/en/, ''));
}
return;
}

// 首次访问,无缓存
const lang = navigator.language || 'zh';
if (!lang.startsWith('zh') && !isEnPath) {
localStorage.setItem('etn-locale', 'en');
window.location.replace('/en' + currentPath);
} else {
localStorage.setItem('etn-locale', 'zh-Hans');
}
}

3. 语言切换器(src/theme/NavbarItem/NavbarLoginItem.tsx

现有的语言切换器已是视觉组件,改造为:

function switchLocale(locale: 'zh-Hans' | 'en') {
localStorage.setItem('etn-locale', locale);
const path = window.location.pathname;
if (locale === 'en') {
if (!path.startsWith('/en/')) {
window.location.assign('/en' + path);
}
} else {
if (path.startsWith('/en/')) {
window.location.assign(path.replace(/^\/en/, ''));
}
}
}

点击语言选项调用 switchLocale,替代原来只更新 state 的逻辑。

4. 页面文字翻译(<Translate> 组件)

所有页面(10 个)的 UI 文字用 <Translate> 包裹,key 命名规范:

{页面}.{区块}.{元素}

示例:

import Translate from '@docusaurus/Translate';

// 标题
<h1><Translate id="home.hero.title">把散落的内容,送回你的 Notion</Translate></h1>

// 按钮
<button><Translate id="home.hero.cta.install">免费安装扩展</Translate></button>

// 带变量的字符串(用 values prop)
<Translate id="home.stats.users" values={{ count: '12,000+' }}>
{'已有 {count} 用户在使用'}
</Translate>

Key 命名表(完整覆盖)

页面区块示例 key
homehero / clients / features / stats / pricing / ctahome.hero.title
pricingheader / plans / compare / faq / ctapricing.header.title
loginbrand / form / tabslogin.form.email.label
changelogheader / timelinechangelog.header.title
usercenterheader / stats / bento / recentusercenter.stats.today
clippingheader / filters / tableclipping.filter.platform.all
databaseheader / mapping / actionsdatabase.mapping.title
tagsheader / stats / filterstags.header.create
wereadheader / status / config / booksweread.config.frequency
pic-configheader / providers / form / actionspicconfig.provider.oss

5. 翻译文件(i18n/en/code.json

所有 key 对应的英文翻译,格式:

{
"home.hero.title": {
"message": "Capture anything, send it to Notion.",
"description": "Hero section main headline"
},
"home.hero.subtitle": {
"message": "Xiaohongshu, WeChat, Douban, Weibo, WeRead — one-click clip, auto-converted to Notion blocks, images uploaded to OSS.",
"description": "Hero section subtitle"
},
"home.hero.cta.install": {
"message": "Install Chrome Extension",
"description": "Primary CTA button"
}
}

i18n/en/docusaurus-theme-classic/navbar.json

{
"item.label.Library": { "message": "Library" },
"item.label.Archive": { "message": "Changelog" },
"item.label.Pricing": { "message": "Pricing" },
"item.label.WeRead": { "message": "WeRead" }
}

i18n/en/docusaurus-theme-classic/footer.json

{
"link.item.label.Documentation": { "message": "Documentation" },
"link.item.label.Changelog": { "message": "Changelog" },
"link.item.label.WeRead Sync": { "message": "WeRead Sync" },
"link.item.label.Database Config":{ "message": "Database Config" },
"link.item.label.Tag Management": { "message": "Tag Management" },
"link.item.label.Privacy": { "message": "Privacy" }
}

7. nginx 配置变更

在现有 server 块中新增 location:

# 现有(不动)
location /website/ {
root /var/www/etn;
try_files $uri $uri/ /website/index.html;
}

# 新增英文路径
location /en/website/ {
alias /var/www/etn/en/website/;
try_files $uri $uri/ /en/website/index.html;
}

部署时把 build/en/website/ 拷贝到服务器 /var/www/etn/en/website/


构建命令

# 构建全部语言版本
npm run build

# 仅构建英文版(开发调试)
npm run build -- --locale en

# 本地预览英文版
npm run serve
# 访问 http://localhost:3000/en/website/

翻译工作流(未来加新语言)

  1. docusaurus.config.tslocales 数组加新 locale(如 ja
  2. 运行 npx docusaurus write-translations --locale ja 生成空 JSON 模板
  3. 填写 i18n/ja/code.json 翻译内容
  4. npm run build 自动生成第三套静态 HTML
  5. nginx 加一行 location /ja/website/

不在本次范围内

  • 文档(/docs/)的翻译——文档内容较多,单独排期
  • Blog 翻译
  • 后端 API 响应多语言(错误信息等)