跳到主要内容

ETN Website i18n 国际化实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 为 ETN 网站添加中英双语支持,URL 前缀方案(/website/ 中文,/en/website/ 英文),首次访问自动检测浏览器语言,静态构建 SEO 友好。

Architecture: Docusaurus 官方 i18n,<Translate> 组件包裹所有 UI 字符串,构建时生成两套完整静态 HTML。自动检测逻辑在客户端模块中运行,语言偏好写入 localStorage,语言切换器触发 URL 跳转。

Tech Stack: Docusaurus 3.9.2, React 18, TypeScript, @docusaurus/Translate


文件变更总览

新建:

  • src/clientModules/autoLocale.ts — 首次访问语言检测 + localStorage 缓存
  • i18n/en/code.json — 所有 <Translate> 字符串的英文翻译
  • i18n/en/docusaurus-theme-classic/navbar.json — 导航英文标签
  • i18n/en/docusaurus-theme-classic/footer.json — Footer 英文内容

修改:

  • docusaurus.config.ts — 新增 en locale,注册 clientModule
  • src/theme/NavbarItem/NavbarLoginItem.tsx — 语言切换器触发真实 URL 跳转
  • src/pages/index.tsx<Translate> 包裹所有中文字符串
  • src/pages/pricing/index.tsx
  • src/pages/login/index.tsx
  • src/pages/changelog/index.tsx
  • src/pages/usercenter/index.tsx
  • src/pages/clipping/index.tsx
  • src/pages/statistics/index.tsx
  • src/pages/database/index.tsx
  • src/pages/tags/index.tsx
  • src/pages/weread/index.tsx
  • src/pages/pic-config/index.tsx

Task 1: Docusaurus 配置 — 添加 en locale

Files:

  • Modify: docusaurus.config.ts

  • Step 1: 读取当前配置确认 i18n 块位置

grep -n "i18n" E:/work/ETN/etn-website/docusaurus.config.ts

预期输出:

151:  i18n: {
153: locales: ["zh-Hans"],
  • Step 2: 更新 i18n 配置块

docusaurus.config.ts 中的 i18n 配置替换为:

i18n: {
defaultLocale: "zh-Hans",
locales: ["zh-Hans", "en"],
localeConfigs: {
"zh-Hans": { label: "中文(简体)", direction: "ltr" },
en: { label: "English", direction: "ltr" },
},
},
  • Step 3: 在 config 对象中注册 clientModule

const config: Config = { 块内(plugins: [] 行之后)新增:

clientModules: [require.resolve('./src/clientModules/autoLocale')],
  • Step 4: 验证配置语法正确
cd E:/work/ETN/etn-website && npx tsc --noEmit 2>&1 | grep "docusaurus.config"

预期:无输出(无该文件错误)


Task 2: 创建自动语言检测客户端模块

Files:

  • Create: src/clientModules/autoLocale.ts

  • Step 1: 创建目录

mkdir -p E:/work/ETN/etn-website/src/clientModules
  • Step 2: 写入 autoLocale.ts
// src/clientModules/autoLocale.ts
// 在浏览器端执行,检测首次访问语言并跳转

export function onRouteDidUpdate(): void {
// SSR 环境跳过
if (typeof window === 'undefined') return;

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

if (stored) {
// 已有缓存,按缓存决定是否需要跳转
if (stored === 'en' && !isEnPath) {
window.location.replace('/en' + currentPath);
} else if (stored === 'zh-Hans' && isEnPath) {
window.location.replace(currentPath.replace(/^\/en/, ''));
}
return;
}

// 首次访问,无缓存,检测浏览器语言
const lang = (navigator.language || navigator.languages?.[0] || 'zh').toLowerCase();
if (lang.startsWith('zh')) {
localStorage.setItem(LOCALE_KEY, 'zh-Hans');
} else {
localStorage.setItem(LOCALE_KEY, 'en');
if (!isEnPath) {
window.location.replace('/en' + currentPath);
}
}
}
  • Step 3: 验证 TypeScript 无错误
cd E:/work/ETN/etn-website && npx tsc --noEmit 2>&1 | grep "autoLocale"

预期:无输出


Task 3: 语言切换器接入真实跳转

Files:

  • Modify: src/theme/NavbarItem/NavbarLoginItem.tsx

  • Step 1: 读取文件确认语言切换器位置

grep -n "setCurrentLang\|langOpen\|lang-dropdown" E:/work/ETN/etn-website/src/theme/NavbarItem/NavbarLoginItem.tsx | head -15
  • Step 2: 在文件顶部附近添加 switchLocale 函数

const NavbarLoginItem 函数定义之前插入:

const LOCALE_KEY = 'etn-locale';

function switchLocale(locale: 'zh-Hans' | 'en'): void {
if (typeof window === 'undefined') return;
localStorage.setItem(LOCALE_KEY, 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/, ''));
}
}
}
  • Step 3: 让每个语言选项点击时调用 switchLocale

找到 setCurrentLang(l.label); setLangOpen(false); 这一行(在 lang-option onClick 里),替换为:

onClick={() => {
setCurrentLang(l.label);
setLangOpen(false);
switchLocale(l.code as 'zh-Hans' | 'en');
}}

注意繁體中文code 目前还未实现,点击后调用 switchLocale('zh-Hans') 作为临时回退。

  • Step 4: 页面加载时从 localStorage 初始化当前语言显示

langOpen state 附近找到 const [currentLang, setCurrentLang] = useState('中文');,替换为:

const [currentLang, setCurrentLang] = useState(() => {
if (typeof window === 'undefined') return '中文';
const stored = localStorage.getItem('etn-locale');
return stored === 'en' ? 'EN' : '中文';
});
  • Step 5: 验证构建
cd E:/work/ETN/etn-website && npx tsc --noEmit 2>&1 | grep "NavbarLoginItem"

预期:无新增错误


Task 4: 生成翻译文件模板

Files:

  • Create: i18n/en/docusaurus-theme-classic/navbar.json

  • Create: i18n/en/docusaurus-theme-classic/footer.json

  • Create: i18n/en/code.json(空模板,后续填写)

  • Step 1: 运行官方翻译文件生成命令

cd E:/work/ETN/etn-website && npx docusaurus write-translations --locale en 2>&1

预期输出包含:Writing 20+ messages to i18n/en/...

  • Step 2: 确认文件已生成
ls E:/work/ETN/etn-website/i18n/en/docusaurus-theme-classic/

预期:navbar.json footer.json ...

  • Step 3: 如果 code.json 未生成,手动创建
echo '{}' > E:/work/ETN/etn-website/i18n/en/code.json

Task 5: 翻译 navbar.json 和 footer.json

Files:

  • Modify: i18n/en/docusaurus-theme-classic/navbar.json

  • Modify: i18n/en/docusaurus-theme-classic/footer.json

  • Step 1: 读取并更新 navbar.json

读取 i18n/en/docusaurus-theme-classic/navbar.json 查看生成的 key 结构,然后确保以下内容正确填写(key 以实际生成为准):

{
"item.label.Library": {
"message": "Library",
"description": "Navbar item label for Library"
},
"item.label.Archive": {
"message": "Changelog",
"description": "Navbar item label for Changelog"
},
"item.label.Pricing": {
"message": "Pricing",
"description": "Navbar item label for Pricing"
},
"item.label.WeRead": {
"message": "WeRead",
"description": "Navbar item label for WeRead"
}
}
  • Step 2: 读取并更新 footer.json

读取 i18n/en/docusaurus-theme-classic/footer.json,填写英文翻译:

{
"link.item.label.Documentation": { "message": "Documentation" },
"link.item.label.API Reference": { "message": "API Reference" },
"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" },
"link.item.label.Terms": { "message": "Terms" },
"copyright": {
"message": "© {year} EverythingToNotion. Precision in documentation.",
"description": "Footer copyright"
}
}
  • Step 3: 验证英文构建通过
cd E:/work/ETN/etn-website && npm run build -- --locale en 2>&1 | tail -5

预期:[SUCCESS] Generated static files(此时页面内容仍是中文,但构建不报错)


Task 6: 首页添加 <Translate> 包裹

Files:

  • Modify: src/pages/index.tsx

  • Modify: i18n/en/code.json(追加 key)

  • Step 1: 在 index.tsx 顶部引入 Translate

在现有 import 块末尾加一行:

import Translate from '@docusaurus/Translate';
  • Step 2: 包裹 HeroSection 文字

找到并替换以下字符串(用 Translate 包裹,保留中文作为默认值):

// Badge
<Translate id="home.hero.badge">支持 8 大中文内容平台</Translate>

// H1(拆成3个翻译单元)
<Translate id="home.hero.title.line1">把散落的内容,</Translate>
<em><Translate id="home.hero.title.line2">送回你的</Translate></em>
// "Notion。" 是品牌词,不翻译

// 副标题
<Translate id="home.hero.subtitle">
小红书、公众号、豆瓣、微博、微信读书——Chrome 扩展一键剪藏,图片上传 OSS,内容原样进 Notion。
</Translate>

// CTA 按钮
<Translate id="home.hero.cta.install">免费安装扩展</Translate>
<Translate id="home.hero.cta.demo">查看功能演示</Translate>

// 信任行
<Translate id="home.hero.trust">已有 12,000+ 用户在使用 · Chrome Web Store 4.8★</Translate>

// 浮动卡片
<Translate id="home.hero.badge.oss.title">图片已上传 OSS</Translate>
<Translate id="home.hero.badge.oss.desc">永久保存,不依赖原链接</Translate>
<Translate id="home.hero.badge.notion.title">已写入 Notion</Translate>
<Translate id="home.hero.badge.notion.desc">3个块 · 2张图片</Translate>
  • Step 3: 包裹 ClientsSection 文字
<Translate id="home.clients.label">多端 · 多平台</Translate>
<Translate id="home.clients.title.line1">随时随地,</Translate>
<Translate id="home.clients.title.line2">都能剪藏。</Translate>
<Translate id="home.clients.subtitle">五种客户端覆盖你的全部使用场景,八大内容平台一网打尽。</Translate>

// 5个客户端卡片标题和描述
<Translate id="home.clients.extension.title">Chrome 扩展</Translate>
<Translate id="home.clients.extension.desc">浏览器右上角一键触发,无需切换 App。支持全部内容平台,批量操作、标签设置、实时同步状态一目了然。</Translate>
<Translate id="home.clients.miniapp.title">微信小程序</Translate>
<Translate id="home.clients.miniapp.desc">在微信里直接分享链接到 ETN 小程序,无需切换浏览器。随手看到随手存。</Translate>
<Translate id="home.clients.web.title">网页应用</Translate>
<Translate id="home.clients.web.desc">无需安装任何软件,浏览器直接打开使用。管理历史记录、配置数据库、查看统计。</Translate>
<Translate id="home.clients.shortcut.title">快捷指令</Translate>
<Translate id="home.clients.shortcut.desc">配置一条 iOS 快捷指令,分享菜单里直接存进 Notion。也支持 Siri 语音触发。</Translate>
<Translate id="home.clients.bot.title">聊天机器人</Translate>
<Translate id="home.clients.bot.desc">把链接发给 ETN Bot,自动解析、打标、同步 Notion,一条消息搞定。</Translate>
// 平台标签(品牌词不翻译,保留中文)
  • Step 4: 包裹 FeaturesSection、StatsSection、HowSection、PricingSection、CTASection
// Features
<Translate id="home.features.label">核心能力</Translate>
<Translate id="home.features.title.line1">精确、完整、</Translate>
<Translate id="home.features.title.line2">可信赖。</Translate>
<Translate id="home.features.subtitle">每一次剪藏都是一次完整的保存——不是截图,不是书签,是真正可检索、可编辑的 Notion 内容。</Translate>
// 6个功能卡片 title + desc(key 格式:home.features.{slug}.title / .desc)

// Stats
<Translate id="home.stats.users.label">活跃用户</Translate>
<Translate id="home.stats.clips.label">已同步内容</Translate>
<Translate id="home.stats.platforms.label">支持平台</Translate>

// How
<Translate id="home.how.label">使用流程</Translate>
<Translate id="home.how.title">三步开始同步</Translate>
<Translate id="home.how.step1.title">安装 Chrome 扩展</Translate>
<Translate id="home.how.step1.desc">在 Chrome Web Store 搜索 ETN,一键安装。免费,无需注册信用卡。</Translate>
<Translate id="home.how.step2.title">打开任意支持页面</Translate>
<Translate id="home.how.step2.desc">浏览小红书、公众号、豆瓣等平台,点击浏览器右上角 ETN 图标。</Translate>
<Translate id="home.how.step3.title">选库,一键同步</Translate>
<Translate id="home.how.step3.desc">选择目标 Notion 数据库,点击同步。内容、图片、标签全部到位。</Translate>

// CTA
<Translate id="home.cta.title.line1">开始整理你的</Translate>
<Translate id="home.cta.title.line2">知识库。</Translate>
<Translate id="home.cta.desc">12,000+ 用户已在用 ETN 把互联网上的内容,变成真正属于自己的知识档案。</Translate>
<Translate id="home.cta.button">免费安装 Chrome 扩展</Translate>
<Translate id="home.cta.trust1">无需信用卡</Translate>
<Translate id="home.cta.trust2">Notion 官方合作</Translate>
<Translate id="home.cta.trust3">隐私优先</Translate>
  • Step 5: 在 i18n/en/code.json 追加首页所有英文翻译
{
"home.hero.badge": { "message": "Supports 8 Chinese Content Platforms" },
"home.hero.title.line1": { "message": "Capture anything," },
"home.hero.title.line2": { "message": "send it to your" },
"home.hero.subtitle": { "message": "Xiaohongshu, WeChat, Douban, Weibo, WeRead — one-click clip, images uploaded to OSS, content preserved in Notion blocks." },
"home.hero.cta.install": { "message": "Install Chrome Extension" },
"home.hero.cta.demo": { "message": "See it in action" },
"home.hero.trust": { "message": "12,000+ active users · Chrome Web Store 4.8★" },
"home.hero.badge.oss.title": { "message": "Image uploaded to OSS" },
"home.hero.badge.oss.desc": { "message": "Permanent storage, no broken links" },
"home.hero.badge.notion.title": { "message": "Written to Notion" },
"home.hero.badge.notion.desc": { "message": "3 blocks · 2 images" },
"home.clients.label": { "message": "Multi-platform · Multi-client" },
"home.clients.title.line1": { "message": "Clip from anywhere," },
"home.clients.title.line2": { "message": "anytime." },
"home.clients.subtitle": { "message": "5 clients cover every scenario. 8 content sources, all in one place." },
"home.clients.extension.title": { "message": "Chrome Extension" },
"home.clients.extension.desc": { "message": "One click from the browser toolbar. Works on all supported platforms with batch sync, tag assignment, and real-time status." },
"home.clients.miniapp.title": { "message": "WeChat Mini Program" },
"home.clients.miniapp.desc": { "message": "Share links directly to ETN from WeChat. No app switching needed." },
"home.clients.web.title": { "message": "Web App" },
"home.clients.web.desc": { "message": "No install needed. Open in any browser to manage history, configure databases, and view stats." },
"home.clients.shortcut.title": { "message": "iOS Shortcuts" },
"home.clients.shortcut.desc": { "message": "Save to Notion from the iOS share sheet. Siri voice trigger supported." },
"home.clients.bot.title": { "message": "Chat Bot" },
"home.clients.bot.desc": { "message": "Send a link to ETN Bot. It auto-parses, tags, and syncs to Notion. One message, done." },
"home.features.label": { "message": "Core Features" },
"home.features.title.line1": { "message": "Precise, complete," },
"home.features.title.line2": { "message": "reliable." },
"home.features.subtitle": { "message": "Every clip is a complete save — not a screenshot, not a bookmark. Real searchable, editable Notion content." },
"home.features.content.title": { "message": "Content Preserved" },
"home.features.content.desc": { "message": "Images, code blocks, highlights — all converted to native Notion blocks. Structure retained, not flattened." },
"home.features.oss.title": { "message": "Images on OSS" },
"home.features.oss.desc": { "message": "Images uploaded to Aliyun OSS before inserting into Notion. No broken links — ever." },
"home.features.batch.title": { "message": "Batch Queue Sync" },
"home.features.batch.desc": { "message": "Multi-article queue, server-guaranteed delivery. Auto-retry on failure." },
"home.features.tags.title": { "message": "Tags & Properties" },
"home.features.tags.desc": { "message": "Set Notion database properties and tags at clip time. Land in the right place, automatically." },
"home.features.weread.title": { "message": "WeRead Sync" },
"home.features.weread.desc": { "message": "Highlights and notes auto-synced to Notion with book title, chapter, and color label." },
"home.features.log.title": { "message": "Operation Log" },
"home.features.log.desc": { "message": "Full sync history with status, time, and target database. One-click retry on failure." },
"home.stats.users.label": { "message": "Active Users" },
"home.stats.clips.label": { "message": "Clips Synced" },
"home.stats.platforms.label": { "message": "Platforms" },
"home.how.label": { "message": "How It Works" },
"home.how.title": { "message": "Three steps to start syncing" },
"home.how.step1.title": { "message": "Install Chrome Extension" },
"home.how.step1.desc": { "message": "Search ETN on the Chrome Web Store and install in one click. Free, no credit card." },
"home.how.step2.title": { "message": "Open any supported page" },
"home.how.step2.desc": { "message": "Browse Xiaohongshu, WeChat, Douban, and more. Click the ETN icon in your browser toolbar." },
"home.how.step3.title": { "message": "Pick a database, sync" },
"home.how.step3.desc": { "message": "Select your Notion database, hit sync. Content, images, and tags land in the right place." },
"home.cta.title.line1": { "message": "Start building your" },
"home.cta.title.line2": { "message": "knowledge base." },
"home.cta.desc": { "message": "12,000+ users are already turning web content into a personal knowledge archive with ETN." },
"home.cta.button": { "message": "Install Chrome Extension Free" },
"home.cta.trust1": { "message": "No credit card required" },
"home.cta.trust2": { "message": "Official Notion Partner" },
"home.cta.trust3": { "message": "Privacy First" }
}
  • Step 6: 验证构建(中文 + 英文)
cd E:/work/ETN/etn-website && npm run build 2>&1 | tail -5

预期:[SUCCESS] Generated static files


Task 7: 营销页翻译 — pricing + login + changelog

Files:

  • Modify: src/pages/pricing/index.tsx
  • Modify: src/pages/login/index.tsx
  • Modify: src/pages/changelog/index.tsx
  • Modify: i18n/en/code.json(追加)

对每个文件:

  • Step 1: 在文件顶部添加 Translate import
import Translate from '@docusaurus/Translate';
  • Step 2: 包裹 pricing/index.tsx 所有静态中文字符串

关键 key:

<Translate id="pricing.header.label">会员方案</Translate>
<Translate id="pricing.header.title">找到适合你的方案</Translate>
<Translate id="pricing.header.subtitle">免费起步,随时升级。无隐藏费用。</Translate>
<Translate id="pricing.toggle.monthly">月付</Translate>
<Translate id="pricing.toggle.annual">年付</Translate>
<Translate id="pricing.toggle.save">省 34%</Translate>
<Translate id="pricing.free.name">免费版</Translate>
<Translate id="pricing.free.price">¥0</Translate>
<Translate id="pricing.free.period">/ 永久</Translate>
<Translate id="pricing.free.cta">免费开始使用</Translate>
<Translate id="pricing.pro.name">Pro 年付</Translate>
<Translate id="pricing.pro.badge">最受欢迎</Translate>
<Translate id="pricing.pro.period">/ 年</Translate>
<Translate id="pricing.pro.cta">立即订阅</Translate>
<Translate id="pricing.lifetime.name">买断版</Translate>
<Translate id="pricing.lifetime.period">/ 永久</Translate>
<Translate id="pricing.lifetime.cta">一次买断</Translate>
<Translate id="pricing.faq.title">常见问题</Translate>
  • Step 3: 包裹 login/index.tsx 所有静态中文字符串

关键 key:

<Translate id="login.brand.tagline">把互联网的精彩,收进你的 Notion</Translate>
<Translate id="login.brand.desc">一键剪藏小红书、微博、微信公众号等平台内容,自动结构化存入你的知识库。</Translate>
<Translate id="login.brand.feature1.title">一键剪藏</Translate>
<Translate id="login.brand.feature1.desc">支持小红书、微博、豆瓣、公众号等平台</Translate>
<Translate id="login.brand.feature2.title">10+ 平台支持</Translate>
<Translate id="login.brand.feature2.desc">中文内容平台全覆盖</Translate>
<Translate id="login.brand.feature3.title">AI 智能标签</Translate>
<Translate id="login.brand.feature3.desc">内容自动分类与关联</Translate>
<Translate id="login.form.title">欢迎回来</Translate>
<Translate id="login.form.subtitle">登录你的 ETN 账号,继续整理知识库</Translate>
<Translate id="login.tab.email">邮箱登录</Translate>
<Translate id="login.tab.uuid">UUID 登录</Translate>
<Translate id="login.email.label">邮箱地址</Translate>
<Translate id="login.email.placeholder">user@example.com</Translate>
<Translate id="login.code.label">验证码</Translate>
<Translate id="login.code.placeholder">输入 6 位验证码</Translate>
<Translate id="login.code.send">发送验证码</Translate>
<Translate id="login.submit">登录</Translate>
<Translate id="login.uuid.label">UUID</Translate>
<Translate id="login.uuid.placeholder">输入你的 UUID</Translate>
<Translate id="login.footer.noAccount">还没有账号?</Translate>
<Translate id="login.footer.register">免费注册</Translate>
  • Step 4: 包裹 changelog/index.tsx 所有静态中文字符串

关键 key:

<Translate id="changelog.header.label">版本历史</Translate>
<Translate id="changelog.header.title">更新日志</Translate>
<Translate id="changelog.header.subtitle">持续迭代,每一次更新都让剪藏体验更顺畅。订阅更新通知,第一时间获取新功能。</Translate>
<Translate id="changelog.sidebar.jump">快速跳转</Translate>
<Translate id="changelog.sidebar.subscribe.label">订阅版本更新</Translate>
<Translate id="changelog.sidebar.subscribe.placeholder">your@email.com</Translate>
<Translate id="changelog.sidebar.subscribe.button">订阅通知</Translate>
<Translate id="changelog.badge.new">最新</Translate>
<Translate id="changelog.badge.feature">新功能</Translate>
<Translate id="changelog.badge.fix">修复</Translate>
<Translate id="changelog.badge.improve">优化</Translate>
<Translate id="changelog.loading">加载中...</Translate>
<Translate id="changelog.empty">暂无更新日志</Translate>
  • Step 5: 追加英文翻译到 i18n/en/code.json
{
"pricing.header.label": { "message": "Plans" },
"pricing.header.title": { "message": "Find the plan that's right for you" },
"pricing.header.subtitle": { "message": "Start free, upgrade anytime. No hidden fees." },
"pricing.toggle.monthly": { "message": "Monthly" },
"pricing.toggle.annual": { "message": "Annual" },
"pricing.toggle.save": { "message": "Save 34%" },
"pricing.free.name": { "message": "Free" },
"pricing.free.price": { "message": "$0" },
"pricing.free.period": { "message": "/ forever" },
"pricing.free.cta": { "message": "Get started free" },
"pricing.pro.name": { "message": "Pro Annual" },
"pricing.pro.badge": { "message": "Most Popular" },
"pricing.pro.period": { "message": "/ year" },
"pricing.pro.cta": { "message": "Subscribe now" },
"pricing.lifetime.name": { "message": "Lifetime" },
"pricing.lifetime.period": { "message": "/ one-time" },
"pricing.lifetime.cta": { "message": "Buy once, own forever" },
"pricing.faq.title": { "message": "Frequently Asked Questions" },
"login.brand.tagline": { "message": "Capture the best of the internet into your Notion" },
"login.brand.desc": { "message": "One-click clip from Xiaohongshu, Weibo, WeChat and more. Auto-structured into your knowledge base." },
"login.brand.feature1.title": { "message": "One-Click Clip" },
"login.brand.feature1.desc": { "message": "Supports Xiaohongshu, Weibo, Douban, WeChat" },
"login.brand.feature2.title": { "message": "10+ Platforms" },
"login.brand.feature2.desc": { "message": "Full coverage of Chinese content platforms" },
"login.brand.feature3.title": { "message": "AI Smart Tags" },
"login.brand.feature3.desc": { "message": "Auto-classify and link content" },
"login.form.title": { "message": "Welcome back" },
"login.form.subtitle": { "message": "Sign in to your ETN account" },
"login.tab.email": { "message": "Email Login" },
"login.tab.uuid": { "message": "UUID Login" },
"login.email.label": { "message": "Email Address" },
"login.email.placeholder": { "message": "user@example.com" },
"login.code.label": { "message": "Verification Code" },
"login.code.placeholder": { "message": "Enter 6-digit code" },
"login.code.send": { "message": "Send Code" },
"login.submit": { "message": "Sign In" },
"login.uuid.label": { "message": "UUID" },
"login.uuid.placeholder": { "message": "Enter your UUID" },
"login.footer.noAccount": { "message": "Don't have an account?" },
"login.footer.register": { "message": "Sign up free" },
"changelog.header.label": { "message": "Release History" },
"changelog.header.title": { "message": "Changelog" },
"changelog.header.subtitle": { "message": "Continuously improving. Subscribe for release notifications." },
"changelog.sidebar.jump": { "message": "Jump to version" },
"changelog.sidebar.subscribe.label": { "message": "Subscribe to updates" },
"changelog.sidebar.subscribe.placeholder": { "message": "your@email.com" },
"changelog.sidebar.subscribe.button": { "message": "Subscribe" },
"changelog.badge.new": { "message": "New" },
"changelog.badge.feature": { "message": "Feature" },
"changelog.badge.fix": { "message": "Fix" },
"changelog.badge.improve": { "message": "Improvement" },
"changelog.loading": { "message": "Loading..." },
"changelog.empty": { "message": "No changelog entries yet" }
}
  • Step 6: 验证构建
cd E:/work/ETN/etn-website && npm run build 2>&1 | tail -5

预期:[SUCCESS]


Task 8: 仪表盘页翻译 — usercenter + statistics + clipping

Files:

  • Modify: src/pages/usercenter/index.tsx

  • Modify: src/pages/statistics/index.tsx

  • Modify: src/pages/clipping/index.tsx

  • Modify: src/components/DashboardSidebar.tsx

  • Modify: i18n/en/code.json(追加)

  • Step 1: 每个文件加 Translate import

import Translate from '@docusaurus/Translate';
  • Step 2: DashboardSidebar.tsx — 包裹导航标签
// 每个导航项标签
<Translate id="sidebar.overview">概览</Translate>
<Translate id="sidebar.database">数据库配置</Translate>
<Translate id="sidebar.picConfig">图片配置</Translate>
<Translate id="sidebar.clipping">剪藏记录</Translate>
<Translate id="sidebar.statistics">使用统计</Translate>
<Translate id="sidebar.weread">微信读书</Translate>
<Translate id="sidebar.tags">标签管理</Translate>
<Translate id="sidebar.help">帮助文档</Translate>
<Translate id="sidebar.settings">设置</Translate>
  • Step 3: usercenter/index.tsx — 包裹关键字符串
<Translate id="usercenter.greeting" values={{ name: userName }}>{'你好,{name}'}</Translate>
<Translate id="usercenter.stats.today.label">今日剪藏</Translate>
<Translate id="usercenter.stats.month.label">本月剪藏</Translate>
<Translate id="usercenter.stats.total.label">累计剪藏</Translate>
<Translate id="usercenter.stats.rate.label">成功率</Translate>
<Translate id="usercenter.bento.extension.title">Chrome 扩展</Translate>
<Translate id="usercenter.bento.extension.desc">一键剪藏任意网页</Translate>
<Translate id="usercenter.bento.miniapp.title">微信小程序</Translate>
<Translate id="usercenter.bento.web.title">网页剪藏</Translate>
<Translate id="usercenter.bento.shortcut.title">iOS 快捷指令</Translate>
<Translate id="usercenter.bento.weread.title">微信读书同步</Translate>
<Translate id="usercenter.bento.bot.title">聊天机器人</Translate>
<Translate id="usercenter.recent.title">近期同步</Translate>
<Translate id="usercenter.account.title">账户信息</Translate>
<Translate id="usercenter.account.uuid.label">UUID</Translate>
<Translate id="usercenter.account.plan.label">会员状态</Translate>
<Translate id="usercenter.account.expires.label">到期时间</Translate>
  • Step 4: statistics/index.tsx — 包裹关键字符串
<Translate id="stats.header.title">使用统计</Translate>
<Translate id="stats.range.today">今日</Translate>
<Translate id="stats.range.week">本周</Translate>
<Translate id="stats.range.month">本月</Translate>
<Translate id="stats.range.custom">自定义</Translate>
<Translate id="stats.card.total">总剪藏</Translate>
<Translate id="stats.card.success">同步成功</Translate>
<Translate id="stats.card.fail">同步失败</Translate>
<Translate id="stats.card.rate">成功率</Translate>
<Translate id="stats.chart.title">每日剪藏量</Translate>
<Translate id="stats.platform.title">平台分布</Translate>
<Translate id="stats.status.title">同步状态分布</Translate>
  • Step 5: clipping/index.tsx — 包裹关键字符串
<Translate id="clipping.header.title">剪藏记录</Translate>
<Translate id="clipping.header.resyncAll">全部重新同步</Translate>
<Translate id="clipping.filter.all">全部</Translate>
<Translate id="clipping.filter.success">成功</Translate>
<Translate id="clipping.filter.fail">失败</Translate>
<Translate id="clipping.filter.pending">等待</Translate>
<Translate id="clipping.table.title">标题</Translate>
<Translate id="clipping.table.platform">平台</Translate>
<Translate id="clipping.table.time">时间</Translate>
<Translate id="clipping.table.status">状态</Translate>
<Translate id="clipping.table.actions">操作</Translate>
<Translate id="clipping.status.synced">已同步</Translate>
<Translate id="clipping.status.failed">失败</Translate>
<Translate id="clipping.status.pending">等待中</Translate>
<Translate id="clipping.action.retry">重试</Translate>
<Translate id="clipping.action.delete">删除</Translate>
  • Step 6: 追加英文翻译到 code.json
{
"sidebar.overview": { "message": "Overview" },
"sidebar.database": { "message": "Database Config" },
"sidebar.picConfig": { "message": "Image Config" },
"sidebar.clipping": { "message": "Clip History" },
"sidebar.statistics": { "message": "Statistics" },
"sidebar.weread": { "message": "WeRead" },
"sidebar.tags": { "message": "Tag Management" },
"sidebar.help": { "message": "Help" },
"sidebar.settings": { "message": "Settings" },
"usercenter.greeting": { "message": "Hello, {name}" },
"usercenter.stats.today.label": { "message": "Today" },
"usercenter.stats.month.label": { "message": "This Month" },
"usercenter.stats.total.label": { "message": "Total" },
"usercenter.stats.rate.label": { "message": "Success Rate" },
"usercenter.bento.extension.title": { "message": "Chrome Extension" },
"usercenter.bento.extension.desc": { "message": "One-click clip from any page" },
"usercenter.bento.miniapp.title": { "message": "WeChat Mini Program" },
"usercenter.bento.web.title": { "message": "Web App" },
"usercenter.bento.shortcut.title": { "message": "iOS Shortcuts" },
"usercenter.bento.weread.title": { "message": "WeRead Sync" },
"usercenter.bento.bot.title": { "message": "Chat Bot" },
"usercenter.recent.title": { "message": "Recent Clips" },
"usercenter.account.title": { "message": "Account" },
"usercenter.account.uuid.label": { "message": "UUID" },
"usercenter.account.plan.label": { "message": "Plan" },
"usercenter.account.expires.label": { "message": "Expires" },
"stats.header.title": { "message": "Statistics" },
"stats.range.today": { "message": "Today" },
"stats.range.week": { "message": "This Week" },
"stats.range.month": { "message": "This Month" },
"stats.range.custom": { "message": "Custom" },
"stats.card.total": { "message": "Total Clips" },
"stats.card.success": { "message": "Successful" },
"stats.card.fail": { "message": "Failed" },
"stats.card.rate": { "message": "Success Rate" },
"stats.chart.title": { "message": "Daily Clips" },
"stats.platform.title": { "message": "By Platform" },
"stats.status.title": { "message": "Sync Status" },
"clipping.header.title": { "message": "Clip History" },
"clipping.header.resyncAll": { "message": "Retry All Failed" },
"clipping.filter.all": { "message": "All" },
"clipping.filter.success": { "message": "Success" },
"clipping.filter.fail": { "message": "Failed" },
"clipping.filter.pending": { "message": "Pending" },
"clipping.table.title": { "message": "Title" },
"clipping.table.platform": { "message": "Platform" },
"clipping.table.time": { "message": "Time" },
"clipping.table.status": { "message": "Status" },
"clipping.table.actions": { "message": "Actions" },
"clipping.status.synced": { "message": "Synced" },
"clipping.status.failed": { "message": "Failed" },
"clipping.status.pending": { "message": "Pending" },
"clipping.action.retry": { "message": "Retry" },
"clipping.action.delete": { "message": "Delete" }
}
  • Step 7: 验证构建
cd E:/work/ETN/etn-website && npm run build 2>&1 | tail -5

Task 9: 配置页翻译 — database + tags + weread + pic-config

Files:

  • Modify: src/pages/database/index.tsx

  • Modify: src/pages/tags/index.tsx

  • Modify: src/pages/weread/index.tsx

  • Modify: src/pages/pic-config/index.tsx

  • Modify: i18n/en/code.json(追加)

  • Step 1: 每个文件加 Translate import

import Translate from '@docusaurus/Translate';
  • Step 2: database/index.tsx 关键字符串
<Translate id="db.header.title">数据库配置</Translate>
<Translate id="db.header.desc">将 Notion 数据库字段与 ETN 内容属性对应起来,确保剪藏内容正确写入</Translate>
<Translate id="db.auth.connected">已连接</Translate>
<Translate id="db.auth.reauth">重新授权</Translate>
<Translate id="db.auth.disconnect">断开连接</Translate>
<Translate id="db.mapping.title">字段映射</Translate>
<Translate id="db.mapping.autoMatch">自动匹配字段</Translate>
<Translate id="db.mapping.col.field">ETN 字段</Translate>
<Translate id="db.mapping.col.type">类型</Translate>
<Translate id="db.mapping.col.notion">Notion 字段名</Translate>
<Translate id="db.mapping.col.status">状态</Translate>
<Translate id="db.action.save">保存映射</Translate>
<Translate id="db.default.label">默认同步数据库</Translate>
  • Step 3: tags/index.tsx 关键字符串
<Translate id="tags.header.title">标签管理</Translate>
<Translate id="tags.stats.available">可用标签</Translate>
<Translate id="tags.stats.categories">分类</Translate>
<Translate id="tags.stats.archived">已归档</Translate>
<Translate id="tags.action.create">新建标签</Translate>
<Translate id="tags.search.placeholder">搜索标签名称...</Translate>
<Translate id="tags.filter.all">全部</Translate>
<Translate id="tags.filter.used">已使用</Translate>
<Translate id="tags.filter.unused">未使用</Translate>
<Translate id="tags.filter.archived">已归档</Translate>
  • Step 4: weread/index.tsx 关键字符串
<Translate id="weread.header.title">微信读书同步</Translate>
<Translate id="weread.notion.title">Notion 授权</Translate>
<Translate id="weread.weread.title">微信读书账号</Translate>
<Translate id="weread.config.title">同步配置</Translate>
<Translate id="weread.config.frequency.label">同步频率</Translate>
<Translate id="weread.config.content.label">同步内容</Translate>
<Translate id="weread.config.highlights">划线笔记</Translate>
<Translate id="weread.config.quotes">书摘</Translate>
<Translate id="weread.config.thoughts">想法</Translate>
<Translate id="weread.config.database.label">目标数据库</Translate>
<Translate id="weread.config.save">保存配置</Translate>
<Translate id="weread.books.title">书籍同步状态</Translate>
<Translate id="weread.books.col.name">书名</Translate>
<Translate id="weread.books.col.status">状态</Translate>
<Translate id="weread.books.col.lastSync">上次同步</Translate>
<Translate id="weread.books.col.highlights">划线数</Translate>
<Translate id="weread.books.col.actions">操作</Translate>
  • Step 5: pic-config/index.tsx 关键字符串
<Translate id="pic.header.title">图片存储配置</Translate>
<Translate id="pic.header.desc">配置图片上传服务,将剪藏内容的图片存储到云端,避免 Notion 外链图片失效</Translate>
<Translate id="pic.provider.label">存储方式</Translate>
<Translate id="pic.test.button">测试连接</Translate>
<Translate id="pic.test.success">连接成功</Translate>
<Translate id="pic.action.save">保存配置</Translate>
  • Step 6: 追加英文翻译到 code.json
{
"db.header.title": { "message": "Database Configuration" },
"db.header.desc": { "message": "Map Notion database fields to ETN content properties to ensure clips land correctly" },
"db.auth.connected": { "message": "Connected" },
"db.auth.reauth": { "message": "Re-authorize" },
"db.auth.disconnect": { "message": "Disconnect" },
"db.mapping.title": { "message": "Field Mapping" },
"db.mapping.autoMatch": { "message": "Auto-match fields" },
"db.mapping.col.field": { "message": "ETN Field" },
"db.mapping.col.type": { "message": "Type" },
"db.mapping.col.notion": { "message": "Notion Field Name" },
"db.mapping.col.status": { "message": "Status" },
"db.action.save": { "message": "Save Mapping" },
"db.default.label": { "message": "Default sync database" },
"tags.header.title": { "message": "Tag Management" },
"tags.stats.available": { "message": "Available Tags" },
"tags.stats.categories": { "message": "Categories" },
"tags.stats.archived": { "message": "Archived" },
"tags.action.create": { "message": "New Tag" },
"tags.search.placeholder": { "message": "Search tags..." },
"tags.filter.all": { "message": "All" },
"tags.filter.used": { "message": "In Use" },
"tags.filter.unused": { "message": "Unused" },
"tags.filter.archived": { "message": "Archived" },
"weread.header.title": { "message": "WeRead Sync" },
"weread.notion.title": { "message": "Notion Authorization" },
"weread.weread.title": { "message": "WeRead Account" },
"weread.config.title": { "message": "Sync Settings" },
"weread.config.frequency.label": { "message": "Sync Frequency" },
"weread.config.content.label": { "message": "Sync Content" },
"weread.config.highlights": { "message": "Highlights" },
"weread.config.quotes": { "message": "Quotes" },
"weread.config.thoughts": { "message": "Notes" },
"weread.config.database.label": { "message": "Target Database" },
"weread.config.save": { "message": "Save Settings" },
"weread.books.title": { "message": "Book Sync Status" },
"weread.books.col.name": { "message": "Book" },
"weread.books.col.status": { "message": "Status" },
"weread.books.col.lastSync": { "message": "Last Synced" },
"weread.books.col.highlights": { "message": "Highlights" },
"weread.books.col.actions": { "message": "Actions" },
"pic.header.title": { "message": "Image Storage Config" },
"pic.header.desc": { "message": "Configure image upload service to store clipped images in the cloud, preventing broken Notion image links" },
"pic.provider.label": { "message": "Storage Provider" },
"pic.test.button": { "message": "Test Connection" },
"pic.test.success": { "message": "Connection successful" },
"pic.action.save": { "message": "Save Config" }
}
  • Step 7: 验证全站构建
cd E:/work/ETN/etn-website && npm run build 2>&1 | tail -8

预期:

✔ Client: Compiled successfully
✔ Server: Compiled successfully
[SUCCESS] Generated static files in "build".

Task 10: 最终验证

Files: 无新增改动,验证构建产物

  • Step 1: 确认双语静态文件均已生成
ls E:/work/ETN/etn-website/build/
ls E:/work/ETN/etn-website/build/en/

预期 build/ 下有 website/build/en/ 下也有 website/

  • Step 2: 本地预览中文版
cd E:/work/ETN/etn-website && npm run serve -- --port 4000 &

浏览器打开 http://localhost:4000/website/,页面应为中文

  • Step 3: 本地预览英文版

浏览器打开 http://localhost:4000/en/website/,页面应为英文

  • Step 4: 验证 hreflang 标签
curl -s http://localhost:4000/website/ | grep "hreflang"

预期输出包含:

<link rel="alternate" hreflang="zh-Hans" href="..."/>
<link rel="alternate" hreflang="en" href="..."/>
  • Step 5: 验证语言自动检测(模拟英文浏览器)

在浏览器开发者工具 Console 执行:

localStorage.removeItem('etn-locale');
Object.defineProperty(navigator, 'language', { value: 'en-US', configurable: true });
location.reload();

预期:页面跳转到 /en/website/


自检结果

需求对应 Task
en locale 配置Task 1
自动语言检测 + localStorageTask 2
语言切换器真实跳转Task 3
翻译文件模板生成Task 4
navbar/footer 英文Task 5
首页 <Translate>Task 6
营销页翻译Task 7
仪表盘页翻译Task 8
配置页翻译Task 9
双语构建验证Task 10
nginx 改动用户自行处理,不在本计划范围
文档/Blog 翻译后续单独排期