国际化和本地化工作流

Claude Code 工作流专题 · 多语言项目开发自动化

专题:Claude Code 工作流系统学习

关键词:Claude Code, i18n, l10n, 国际化, 本地化, 翻译管理, react-intl, Gettext, RTL

一、国际化与本地化基础概念

国际化(Internationalization,常缩写为 i18n,因首字母 i 和末字母 n 之间恰好有 18 个字母)和本地化(Localization,缩写为 l10n)是现代软件工程中不可或缺的组成部分。随着业务覆盖全球市场,一个产品需要同时支持数十种语言、适应不同地区的文化习惯、处理多元的排版方向,这对开发和维护提出了极高的挑战。Claude Code 作为强大的 AI 编程助手,可以在国际化工作流的各个环节发挥关键作用:从翻译文件生成、代码自动化重构,到翻译质量审查、RTL 适配验证,大幅提升多语言项目交付效率。

本专题将系统梳理 i18n/l10n 的完整工作流,涵盖框架集成、翻译管理、自动化流水线、RTL 支持、SEO 国际化以及测试验证六大核心模块,辅以丰富的 Claude Code 实战示例。

核心定义:

国际化 (i18n):在设计阶段就将多语言支持作为架构的一部分,使代码能够无侵入地适配不同语言和区域。典型做法包括:外部化所有用户可见字符串、使用区域敏感 API(如 Intl 命名空间)、抽象文本方向等。

本地化 (l10n):在国际化的基础上,针对特定语言/地区翻译内容、调整格式偏好(如日期格式 "2026/05/08" 与 "May 8, 2026")、适配法律法规(如 GDPR 隐私声明)和本地文化(如颜色象征意义)。

二、i18n 框架集成

选择合适的 i18n 框架是全局化的第一步。不同技术栈有对应的成熟方案,Claude Code 可以辅助完成框架初始化、配置生成、现有代码迁移等工作。

2.1 React / React Native — react-intl 与 i18next

在 React 生态中,react-intl(基于 FormatJS)和 i18next(配合 react-i18next)是最主流的两个选择。react-intl 从 ICU Message 语法演化而来,天然支持复数选择、富文本插值;i18next 则强调插件化和运行时动态加载。

// react-intl 基础配置 — Claude Code 自动生成 import { IntlProvider, FormattedMessage, useIntl } from 'react-intl'; import messages_zhCN from './locales/zh-CN.json'; import messages_enUS from './locales/en-US.json'; import messages_jaJP from './locales/ja-JP.json'; const messages = { 'zh-CN': messages_zhCN, 'en-US': messages_enUS, 'ja-JP': messages_jaJP, }; function App({ locale }) { return ( <IntlProvider messages={messages[locale]} locale={locale} defaultLocale="zh-CN"> <OrderSummary /> </IntlProvider> ); } // 组件中使用 function OrderSummary() { const { formatMessage } = useIntl(); return ( <div> <h1><FormattedMessage id="order.title" defaultMessage="订单摘要" /></h1> <p> {formatMessage({ id: "order.total", defaultMessage: "总计: {amount}" }, { amount: 299.00 })} </p> <p> <FormattedMessage id="order.items" values={{ count: 3 }} defaultMessage={"共 {count} 件商品"} /> </p> </div> ); }

对于 i18next 方案,Claude Code 可以自动生成初始化脚手架和检测逻辑:

// i18next 初始化 — Claude Code 生成 import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import LanguageDetector from 'i18next-browser-languagedetector'; import Backend from 'i18next-http-backend'; i18n .use(Backend) // 懒加载翻译文件 .use(LanguageDetector) // 自动检测浏览器语言 .use(initReactI18next) // 绑定 React .init({ fallbackLng: 'zh-CN', debug: process.env.NODE_ENV === 'development', interpolation: { escapeValue: false }, ns: ['common', 'order', 'payment', 'error'], defaultNS: 'common', backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' }, }); export default i18n;

2.2 Vue I18n

Vue 生态的首选是 vue-i18n,支持 Composition API 和 Options API 两种用法。Claude Code 可以帮助将组件内硬编码文本快速提取为 \$t 调用。

<!-- Vue 3 + vue-i18n 基本使用 --> <template> <div class="profile"> <h1>{{ $t('profile.title') }}</h1> <p> {{ $t('profile.greeting', { name: user.displayName }) }} </p> <p> {{ $t('profile.memberSince', { date: formatDate(user.createdAt) }) }} </p> <p> {{ $tc('profile.items', cart.itemCount) }} </p> </div> </template> <script setup> import { useI18n } from 'vue-i18n'; import { useUser } from './composables/useUser'; const { t, tc, locale } = useI18n(); const { user } = useUser(); // 编程式切换语言 function switchLanguage(lang) { locale.value = lang; document.querySelector('html').setAttribute('lang', lang); } </script>

2.3 Django i18n

Django 拥有成熟的国际化框架,基于 Gettext/PO 文件体系。Claude Code 能自动运行 makemessages/compilemessages,并协助生成 django.po 翻译条目。

# Django settings 国际化配置 # settings/base.py — Claude Code 生成 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = True LANGUAGES = [ ('zh-hans', _('简体中文')), ('en', _('English')), ('ja', _('日本語')), ('ko', _('한국어')), ] LOCALE_PATHS = [BASE_DIR / 'locale'] # 模板中使用 {% load i18n %} <h1>{% trans "欢迎访问" %}</h1> <p>{% blocktrans with name=user.name %}{{ name }},您好!{% endblocktrans %}</p> # Claude Code CLI 自动执行翻译文件管理 # $ django-admin makemessages -l ja --ignore=venv # $ django-admin compilemessages

2.4 FastAPI Babel 与 Gettext

FastAPI 是新兴的 Python Web 框架,虽然没有内置 i18n,但可集成 Babel 和 Gettext 实现国际化。Claude Code 可以自动搭建中间件和翻译加载器。

# FastAPI + Babel i18n 中间件 — Claude Code 生成 from pathlib import Path from typing import Optional from fastapi import FastAPI, Request from starlette.middleware.base import BaseHTTPMiddleware from babel.support import Translations from starlette.datastructures import Headers app = FastAPI() LOCALE_DIR = Path(__file__).parent / "locale" class I18nMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): lang = self.get_preferred_language(request.headers) translations = Translations.load(LOCALE_DIR, [lang]) request.state.translations = translations request.state.locale = lang response = await call_next(request) return response def get_preferred_language(self, headers: Headers) -> str: accept = headers.get("accept-language", "zh-CN") # 简化的优先级解析逻辑 for lang_code in ["zh-CN", "en", "ja"]: if lang_code in accept: return lang_code return "en" app.add_middleware(I18nMiddleware) # 辅助函数 def _(request: Request, text: str) -> str: return request.state.translations.ugettext(text) # Babel 配置文件 babel.cfg # [python: **.py] # [jinja2: **.j2] # extensions = jinja2.ext.autoescape,jinja2.ext.with_

2.5 语言文件格式对比

不同框架采用不同的翻译文件格式,Claude Code 可以自动进行格式转换和合并。以下是三种主流格式的对比:

格式典型框架优点缺点
JSONreact-intl, i18next前端原生解析,简单直观,Webpack/Rollup 原生支持不支持注释,大文件性能下降,无法表达复数规则
YAMLRuby on Rails, Hugo可读性强,支持注释和锚点引用缩进敏感,嵌套过深时易出错
PODjango, WordPress, GNU Gettext支持复数形式、上下文(msgctxt)、开发者注释、模糊标记格式冗长,工具链绑定 Gettext
// JSON 格式(react-intl) { "order.title": "订单摘要", "order.total": "总计: {amount} 元", "order.items": "共 {count} 件商品", "order.status": "订单状态: {status}" } # YAML 格式(Rails) zh-CN: order: title: 订单摘要 total: 总计: %{amount} 元 items: one: 1 件商品 other: 共 %{count} 件商品 # PO 格式(Gettext) msgid "" msgstr "" "Language: zh-CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" msgid "Order Summary" msgstr "订单摘要" msgid "Total: {amount}" msgstr "总计: {amount} 元" #: models/order.py:42 msgid "Pending" msgid_plural "Pending" msgstr[0] "待处理" msgstr[1] "待处理"

三、翻译管理

翻译管理是 i18n 工作流的核心环节。良好的翻译键命名策略、命名空间划分、以及对复数、性别、日期、数字、货币、时区的正确处理,决定了最终本地化质量。

3.1 翻译键命名规范

翻译键(translation key)是引用翻译内容的唯一标识符。推荐采用点分隔命名法(dot-notation),遵循「模块.页面.元素.属性」的原则。Claude Code 可以扫描整个代码库自动生成键名建议并检测命名冲突。

// 推荐的翻译键命名规范 { // 公共区域 "common.button.submit": "提交", "common.button.cancel": "取消", "common.button.confirm": "确认", "common.loading": "加载中…", "common.error.network": "网络错误,请稍后重试", // 导航 "nav.home": "首页", "nav.products": "产品", "nav.about": "关于我们", "nav.contact": "联系我们", // 订单模块 "order.list.title": "我的订单", "order.detail.title": "订单详情", "order.detail.orderNo": "订单编号: {no}", "order.detail.shipTo": "收货地址", // 支付模块 "payment.method.wechat": "微信支付", "payment.method.alipay": "支付宝", "payment.method.creditCard": "信用卡", "payment.success": "支付成功", "payment.failed": "支付失败" }

3.2 命名空间策略

对于大型项目,将所有翻译放在一个 JSON 文件中会导致维护灾难。命名空间(namespace)机制将翻译文件按功能模块拆分,按需加载。i18next 原生支持命名空间,react-intl 可借助 code-splitting 实现类似效果。

// 命名空间目录结构 — Claude Code 生成 locales/ ├── zh-CN/ │ ├── common.json // 公共 UI 元素 │ ├── order.json // 订单模块 │ ├── payment.json // 支付模块 │ ├── product.json // 商品模块 │ ├── user.json // 用户模块 │ ├── error.json // 错误信息 │ └── validation.json // 表单校验 ├── en-US/ │ ├── common.json │ ├── order.json │ └── ... └── ja-JP/ ├── common.json └── ... // Claude Code 检查命名空间引用的脚本 function validateNamespaceUsage(componentCode, namespaceName) { const tCalls = componentCode.matchAll(/\bt\(['"`]([^'"`]+\.[^'"`]+)['"`]/g); const allKeys = [...tCalls].map(m => m[1]); const nsMismatches = allKeys.filter(key => !key.startsWith(namespaceName)); if (nsMismatches.length > 0) { console.warn(`[警告] 发现跨命名空间引用: ${nsMismatches.join(', ')}`); } }

3.3 复数形式与性别处理

不同语言有截然不同的复数规则。英语有单数/复数两种形式(one/other);阿拉伯语有六种(zero/one/two/few/many/other);中文几乎不做复数变化。ICU Message 语法通过 selectordinal 和 plural 关键字处理复数;Gettext 通过 nplurals 声明。性别处理(如法语中的形容词性数配合)也是类似模式。

// ICU Message 复数+性别 — react-intl import { FormattedMessage } from 'react-intl'; // 翻译 JSON { "notifications": "{count, plural, =0 {无新通知} one {# 条新通知} other {# 条新通知}}", "likes": "{count, plural, =0 {尚未有人点赞} one {# 人点赞了} other {# 人点赞了}}", "friendRequest": "{gender, select, male {他} female {她} other {TA}} 请求添加你为好友" } // Claude Code 自动根据语言生成复数规则配置文件 const pluralRules = { 'zh-CN': { nplurals: 1, expr: '0' }, 'en': { nplurals: 2, expr: 'n != 1' }, 'ar': { nplurals: 6, expr: 'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5' }, 'pl': { nplurals: 3, expr: 'n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2' }, 'ru': { nplurals: 3, expr: 'n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2' }, 'ja': { nplurals: 1, expr: '0' }, };

3.4 日期时间格式

不同地区对日期时间的展示习惯差异巨大:美国用 MM/dd/yyyy(05/08/2026),欧洲用 dd/MM/yyyy(08/05/2026),中国用 yyyy-MM-dd(2026-05-08)。借助 Intl.DateTimeFormat 或 moment/luxon 可以轻松处理。

// 区域感知的日期时间格式化 — Claude Code 辅助生成 // 使用原生 Intl API,零依赖 const locales = { 'zh-CN': 'zh-CN', 'en-US': 'en-US', 'ja-JP': 'ja-JP', 'ar-SA': 'ar-SA', 'de-DE': 'de-DE', }; function formatDate(date, locale, options = {}) { const defaults = { year: 'numeric', month: 'long', day: 'numeric', }; return new Intl.DateTimeFormat(locales[locale] || 'en-US', { ...defaults, ...options }).format(date); } function formatTime(date, locale) { return new Intl.DateTimeFormat(locales[locale] || 'en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: locale === 'en-US' // 英语用12小时制 }).format(date); } // 使用示例 console.log(formatDate(new Date(), 'zh-CN')); // → 2026年5月8日 console.log(formatDate(new Date(), 'en-US')); // → May 8, 2026 console.log(formatDate(new Date(), 'ja-JP')); // → 2026年5月8日 console.log(formatDate(new Date(), 'de-DE')); // → 8. Mai 2026

3.5 数字与货币格式

数字格式的差异包括:千位分隔符(1,000 vs 1 000 vs 1.000)、小数点符号(1.5 vs 1,5)、货币符号位置($100 vs 100 $ vs 100€)、负数表示(-100 vs (100))。Intl.NumberFormat 提供了统一解决方案。

// 区域感知的数字、货币和百分比格式化 function formatCurrency(amount, currency, locale) { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency, minimumFractionDigits: 2, }).format(amount); } function formatNumber(num, locale, options = {}) { return new Intl.NumberFormat(locale, options).format(num); } function formatPercent(value, locale) { return new Intl.NumberFormat(locale, { style: 'percent', minimumFractionDigits: 1, }).format(value); } // 测试用例 — Claude Code 自动生成 console.log(formatCurrency(1234567.89, 'USD', 'en-US')); // → $1,234,567.89 console.log(formatCurrency(1234567.89, 'EUR', 'de-DE')); // → 1.234.567,89 € console.log(formatCurrency(1234567.89, 'JPY', 'ja-JP')); // → ¥1,234,568(日元无小数) console.log(formatCurrency(1234567.89, 'CNY', 'zh-CN')); // → ¥1,234,567.89 console.log(formatNumber(0.85, 'en-US', { style: 'percent' })); // → 85%

3.6 时区处理

全球化应用必须正确处理时区。存储始终使用 UTC,展示时转换为用户本地时间。ICU 的 timeZoneName 选项可以显示时区名称。Claude Code 可以协助编写时区转换工具函数。

// 时区感知的日期格式化 — Claude Code 生成 function formatDateTimeWithTimezone(utcDate, locale, timeZone) { return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: timeZone, timeZoneName: 'short', }).format(new Date(utcDate)); } // 获取用户时区 const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // 可能的返回: "Asia/Shanghai", "America/New_York", "Europe/London" 等 // 日期在时区间转换 function convertTimeZone(dateStr, fromZone, toZone) { // 使用 luxon 库示例 const { DateTime } = require('luxon'); const dt = DateTime.fromISO(dateStr, { zone: fromZone }); return dt.setZone(toZone).toFormat('yyyy-MM-dd HH:mm ZZZZ'); } // Claude Code 时区处理辅助提示 // 1. 所有时间戳在数据库中统一存储为 UTC // 2. 前端在 API 响应拦截器中自动转换为用户时区 // 3. 发送到后端的日期参数先转回 UTC // 4. 不要信赖客户端时间,重要时间计算由服务端完成

四、本地化自动化

Claude Code 在本地化自动化方面的核心价值在于:自动提取源文本生成翻译文件、协调翻译流程、自动进行质量检查、以及检测翻译过时和上下文缺失。

4.1 翻译文件自动生成与提取

手动从代码中收集待翻译字符串效率低且容易遗漏。Claude Code 可以扫描全项目,基于正则或 AST 分析提取所有国际化调用中的默认文本,自动生成初始 JSON/PO/YAML 文件。

# Claude Code CLI 工作流:从 React 项目提取翻译 # 场景:项目已使用 react-intl,需要生成初始翻译文件 # 1. 用 Claude Code 分析项目结构 claude "扫描 src/ 下所有使用了 FormattedMessage 或 useIntl().formatMessage 的文件,提取 id 和 defaultMessage,生成 locales/zh-CN.json" # 2. Claude Code 会运行类似以下逻辑的脚本 # 自动生成的提取脚本 extract.js const fs = require('fs'); const path = require('path'); const glob = require('glob'); function extractTranslations(rootDir) { const translations = {}; const files = glob.sync('src/**/*.{js,jsx,ts,tsx}', { cwd: rootDir }); for (const file of files) { const content = fs.readFileSync(path.join(rootDir, file), 'utf-8'); // 匹配 FormattedMessage id="..." defaultMessage="..." const pattern = /FormattedMessage\s+[^/]*?id="([^"]+)"[^/]*?defaultMessage="([^"]+)"/g; let match; while ((match = pattern.exec(content)) !== null) { translations[match[1]] = match[2]; } } fs.writeFileSync( path.join(rootDir, 'locales/zh-CN.json'), JSON.stringify(translations, null, 2) ); console.log(`提取完成,共 ${Object.keys(translations).length} 条翻译`); } extractTranslations(process.cwd());

4.2 Claude Code 自动翻译

在生成初始翻译文件后,Claude Code 可以利用自身的多语言能力进行机器翻译初翻,生成目标语言文件。翻译时保持占位符完整性(如 {name}、%s、{count} 等)是核心要求。

# Claude Code 提示词:自动翻译并校验占位符 # 场景:已有 zh-CN.json,需要生成 en-US.json 和 ja-JP.json claude " 读取 locales/zh-CN.json,对每个 value 做以下处理: 1. 识别其中的占位符({xxx} 格式) 2. 分别翻译为英语和日语 3. 确保目标语言的翻译值中包含所有原始占位符(位置可调整) 4. 输出 en-US.json 和 ja-JP.json 示例对照: 原始: '共 {count} 件商品' 英语: '{count} items total' 日语: '合計 {count} 点' 检查项: - 所有 {xxx} 占位符都在翻译中保留 - 翻译后的文件结构与源文件完全一致"

最佳实践:Claude Code 自动翻译后,建议使用专门的占位符一致性检查脚本进行二次验证,防止因翻译过程中的疏忽导致运行时崩溃。

4.3 翻译同步与增量更新

当源码新增或修改了字符串后,需要同步更新翻译文件而不覆盖已有翻译。Claude Code 可以执行智能合并:保留已有翻译、添加新条目、标记已删除条目为废弃(而非直接删除)。

// 翻译文件同步工具 — Claude Code 生成 const fs = require('fs'); const path = require('path'); const glob = require('glob'); function syncTranslations(sourceLang, targetLangs) { const sourcePath = path.join('locales', `${sourceLang}.json`); const source = JSON.parse(fs.readFileSync(sourcePath, 'utf-8')); const sourceKeys = new Set(Object.keys(source)); for (const lang of targetLangs) { const targetPath = path.join('locales', `${lang}.json`); const existing = fs.existsSync(targetPath) ? JSON.parse(fs.readFileSync(targetPath, 'utf-8')) : {}; const existingKeys = new Set(Object.keys(existing)); const result = {}; // 新建条目(有默认文本但无翻译) for (const key of sourceKeys) { if (existingKeys.has(key)) { result[key] = existing[key]; // 保留已有翻译 } else { result[key] = `[待翻译] ${source[key]}`; // 标记新条目 } } // 废弃条目标记(存在于目标但不存在于源) for (const key of existingKeys) { if (!sourceKeys.has(key)) { result[`__deprecated__${key}`] = existing[key]; } } fs.writeFileSync(targetPath, JSON.stringify(result, null, 2), 'utf-8'); console.log(`[同步完成] ${lang}: ${sourceKeys.size} 源条目 → ${Object.keys(result).length} 目标条目`); } } syncTranslations('zh-CN', ['en-US', 'ja-JP', 'ko-KR', 'ar-SA']);

4.4 翻译质量检查

翻译质量检查包含多个维度,Claude Code 可以自动执行以下检测:

// 翻译质量检查脚本 — Claude Code 生成 const fs = require('fs'); function checkPlaceholderConsistency(sourceFile, targetFile) { const source = JSON.parse(fs.readFileSync(sourceFile, 'utf-8')); const target = JSON.parse(fs.readFileSync(targetFile, 'utf-8')); const placeholderPattern = /\{(\w+)\}/g; const errors = []; for (const [key, sourceText] of Object.entries(source)) { const targetText = target[key]; if (!targetText) { errors.push(`[缺失] ${key} — 目标文件缺少此条目`); continue; } const sourcePlaceholders = [...sourceText.matchAll(placeholderPattern)] .map(m => m[1]).sort(); const targetPlaceholders = [...targetText.matchAll(placeholderPattern)] .map(m => m[1]).sort(); if (JSON.stringify(sourcePlaceholders) !== JSON.stringify(targetPlaceholders)) { errors.push(`[占位符不一致] ${key} 源文: ${sourceText} → 占位符: [${sourcePlaceholders.join(', ')}] 译文: ${targetText} → 占位符: [${targetPlaceholders.join(', ')}]`); } } if (errors.length === 0) { console.log(`[通过] ${targetFile} — 占位符一致性检查全部通过`); } else { console.error(`[失败] ${targetFile} — 发现 ${errors.length} 个问题:\n`); errors.forEach(e => console.error(e + '\n')); process.exitCode = 1; } } checkPlaceholderConsistency('locales/zh-CN.json', 'locales/en-US.json');

常见陷阱:翻译时被修改的占位符是线上事故的常见原因。例如将 {count} 条结果 翻译为 {number} results 而非 {count} results,会导致页面显示空白或 "undefined results"。务必通过自动化脚本强制检查。

4.5 上下文缺失与翻译过时检测

很多翻译错误源于缺乏上下文。例如英语的 "Save" 可能是「保存」(动词) 或「救球」(名词)。Claude Code 可以通过分析邻近代码和注释来推断上下文。过时检测则对比源代码中的 defaultMessage 与翻译文件中的原始文本,发现不一致即发出警告。

// 翻译过时检测 — Claude Code 生成 const fs = require('fs'); const path = require('path'); const glob = require('glob'); function detectStaleTranslations(srcDir, localeDir, sourceLang) { const sourcePath = path.join(localeDir, `${sourceLang}.json`); const sourceData = JSON.parse(fs.readFileSync(sourcePath, 'utf-8')); // 从源代码提取最新的 defaultMessage const freshData = {}; const files = glob.sync('**/*.{js,jsx,ts,tsx}', { cwd: srcDir, ignore: 'node_modules/**' }); for (const file of files) { const content = fs.readFileSync(path.join(srcDir, file), 'utf-8'); const matches = content.matchAll(/id="([^"]+)"[^>]*defaultMessage="([^"]+)"/g); for (const match of matches) { freshData[match[1]] = match[2]; } } // 对比发现过时条目 for (const [key, freshText] of Object.entries(freshData)) { if (sourceData[key] && sourceData[key] !== freshText) { console.warn(`[翻译过时] ${key} 源代码最新: "${freshText}" 翻译文件: "${sourceData[key]}" 建议: 更新翻译以匹配新的源文本`); } } } detectStaleTranslations('src', 'locales', 'zh-CN');

五、RTL 支持

从右到左(Right-to-Left, RTL)语言如阿拉伯语(Arabic)、希伯来语(Hebrew)、波斯语(Farsi)、乌尔都语(Urdu)等覆盖了全球超过 4 亿用户。RTL 支持是国际化中最容易被忽视但也最具挑战性的部分。Claude Code 可以从 CSS 逻辑属性迁移、布局镜像、混合内容处理三个维度辅助 RTL 适配。

5.1 CSS 逻辑属性

传统 CSS 使用物理方向属性(left/right/top/bottom),在 RTL 环境下需要手动交换。CSS 逻辑属性(Logical Properties)使用 block/inline/start/end 等相对方向,浏览器会自动按文本方向映射。

/* 物理属性 → 逻辑属性 对照示例 — Claude Code 自动重构 */ /* 旧代码(物理属性,RTL 下失效) */ .card { margin-left: 16px; padding-right: 12px; border-left: 3px solid #6b4c2a; text-align: left; } /* 新代码(逻辑属性,自动适应 RTL) */ .card { margin-inline-start: 16px; padding-inline-end: 12px; border-inline-start: 3px solid #6b4c2a; text-align: start; } /* Claude Code 批量替换提示词 */ // "扫描所有 CSS/SCSS 文件,将 left 替换为 inline-start、right 替换为 inline-end, // 但仅限于方向相关属性(margin-/padding-/border-/text-align/float/position 的 left/right)"

5.2 HTML dir 属性与镜像布局

RTL 适配的起点是在 `` 标签上设置 `dir="rtl"`。现代 CSS 框架(如 Tailwind CSS)通过 `rtl:` 和 `ltr:` 前缀支持条件化样式。Claude Code 可以协助生成双向样式和布局模板。

<!-- RTL 基础 HTML 结构 --> <!DOCTYPE html> <html lang="ar" dir="rtl"> <head> <meta charset="UTF-8"> <title>مرحبًا بكم</title> </head> <body> <nav> <!-- RTL 下 logo 在右侧,菜单在左侧 --> <div class="navbar"> <img src="logo.png" class="logo" alt="شعار"> <ul class="nav-links"> <li><a href="#">الرئيسية</a></li> <li><a href="#">المنتجات</a></li> <li><a href="#">اتصل بنا</a></li> </ul> </div> </nav> <div class="article"> <h1>مرحبًا بكم في موقعنا</h1> <p> هذا النص هو مثال لنص يمكن أن يستبدل في نفس المساحة. </p> <!-- 内嵌 LTR 内容(如英文关键词、代码片段)--> <p> يمكنك استخدام الكود <bdi dir="ltr">printf("Hello World");</bdi> في الأمثلة البرمجية. </p> </div> </body> </html>
/* Tailwind CSS RTL 支持 — Claude Code 生成 */ /* tailwind.config.js */ module.exports = { theme: { extend: {}, }, plugins: [ require('@tailwindcss/typography'), ], }; /* 组件中使用 rtl: 前缀 */ <div class=" flex rtl:flex-row-reverse ltr:space-x-4 rtl:space-x-reverse p-4 "> <div class="w-16 h-16 bg-blue-500"></div> <p class="rtl:text-right ltr:text-left"> محتوى مع مسافات مناسبة </p> </div> /* 图标翻转 */ .icon-arrow { transition: transform 0.3s; } [dir="rtl"] .icon-arrow { transform: scaleX(-1); }

5.3 Arabic/Hebrew 特殊适配

RTL 语言除了文本方向反转外,还涉及数字方向(Arabic 中的 Hindi 数字 vs. Western 数字)、连字(Ligature)渲染、字体选择(Cairo/Noto Naskh Arabic for Arabic,Noto Sans Hebrew for Hebrew)、以及表单元素的光标位置。Claude Code 可以在项目初始化时自动注入这些适配规则。

/* Arabic/Hebrew 字体和排版优化 */ /* 阿拉伯语字体栈 */ body[lang="ar"] { font-family: 'Noto Naskh Arabic', 'Cairo', 'Traditional Arabic', serif; line-height: 1.9; /* RTL 文字通常需要更大的行高 */ font-size: 1.05em; /* Arabic 同字号下视觉偏小,可略微放大 */ } /* 希伯来语字体栈 */ body[lang="he"] { font-family: 'Noto Sans Hebrew', 'Frank Ruehl CLM', 'David', sans-serif; } /* RTL 表单元素调整 */ [dir="rtl"] input, [dir="rtl"] textarea, [dir="rtl"] select { text-align: right; /* RTL 输入框文字右对齐 */ direction: rtl; } [dir="rtl"] input[type="email"], [dir="rtl"] input[type="url"] { direction: ltr; /* 邮箱/URL 始终 LTR 输入 */ text-align: left; unicode-bidi: embed; }

5.4 混合内容处理

RTL 页面中嵌入 LTR 内容(如英文代码、数学公式、产品型号)时,需要使用 <bdi>(Bi-Directional Isolation)标签隔离双向文本,防止方向溢出破坏整体排版。

// 混合内容处理的 React 组件 — Claude Code 推荐方案 function LocalizedDescription({ text, code, number }) { return ( <div lang="ar" dir="rtl"> <p>{text}</p> // <bdi> 确保内嵌 LTR 内容不影响周围 RTL 流 <p> رمز المنتج: <bdi dir="ltr" className="code-snippet">{code}</bdi> </p> <p> الكمية: <bdi>{number}</bdi> </p> </div> ); } // Claude Code RTL 验证检查项 const rtlChecklist = [ 'HTML 标签设置了正确的 dir 属性', 'CSS 使用逻辑属性替代物理方向属性', '内嵌 LTR 内容使用了 <bdi> 包裹', '图标/箭头在 RTL 下正确翻转', '导航栏项目顺序已反转', '文本对齐方向与 dir 一致', '表单输入框的光标位置正确', '滚动条位置在 RTL 下在左侧', '视频播放器控制按钮已镜像', '第三方嵌入内容(地图、图表)正常显示', ];

六、SEO 国际化

多语言 SEO 的目标是让搜索引擎正确索引并展示每个语言版本的页面,同时避免重复内容惩罚。Claude Code 可以自动生成 hreflang 标签、多语言站点地图、以及语言切换逻辑。

6.1 hreflang 标签

hreflang 标签告诉搜索引擎当前页面的不同语言/地区版本,防止被判定为重复内容,并将用户导向正确语言版本。

<!-- hreflang 标签 — Claude Code 自动生成 --> <!-- 每个语言/地区页面必须在 <head> 中包含所有版本链接 --> <link rel="alternate" href="https://example.com/zh-CN/products" hreflang="zh-CN" /> <link rel="alternate" href="https://example.com/en-US/products" hreflang="en-US" /> <link rel="alternate" href="https://example.com/ja-JP/products" hreflang="ja-JP" /> <link rel="alternate" href="https://example.com/ar-SA/products" hreflang="ar-SA" /> <link rel="alternate" href="https://example.com/products" hreflang="x-default" /> // React 中动态生成 hreflang — Claude Code 生成 function HreflangTags({ path, locales }) { const baseUrl = 'https://example.com'; return ( <> {locales.map(locale => ( <link key={locale} rel="alternate" href={`${baseUrl}/${locale}${path}`} hreflang={locale} /> ))} <link rel="alternate" href={`${baseUrl}${path}`} hreflang="x-default" /> </> ); } // Claude Code 验证脚本:确保每个页面都有完整 hreflang async function validateHreflang(url) { const response = await fetch(url); const html = await response.text(); const hreflangs = [...html.matchAll(/hreflang="([^"]+)"/g)]; if (hreflangs.length < 2) { console.warn(`[SEO警告] ${url} — hreflang 标签不足`); } }

6.2 多语言 URL 策略

三种主流的 URL 结构方案:子域名(de.example.com)、子目录(example.com/de/)、查询参数(example.com?lang=de)。Google 官方推荐子目录方案,因为子域名可能被视为独立站点而丢失权重。

// 子目录 URL 策略的路由配置 — Claude Code 自动生成 import { createBrowserRouter } from 'react-router-dom'; const supportedLocales = ['zh-CN', 'en-US', 'ja-JP', 'ar-SA']; const router = createBrowserRouter([ { path: '/:locale', element: <LocaleLayout />, loader: localeLoader, children: [ { index: true, element: <HomePage /> }, { path: 'products', element: <ProductsPage /> }, { path: 'products/:id', element: <ProductDetail /> }, { path: 'about', element: <AboutPage /> }, { path: 'contact', element: <ContactPage /> }, ], }, ]); async function localeLoader({ params }) { const { locale } = params; if (!supportedLocales.includes(locale)) { // 语言不支持时,根据 Accept-Language 重定向 throw redirect(`/en-US${location.pathname.replace(`/${locale}`, '')}`); } return { locale }; } // Nginx 区域重定向配置 — Claude Code 生成 # 根据 Accept-Language 自动重定向 map $http_accept_language $lang { default en-US; ~zh-CN zh-CN; ~zh zh-CN; ~ja ja-JP; ~ar ar-SA; } server { listen 80; server_name example.com; # 根路径重定向到推荐语言版本 location = / { if ($lang != 'en-US') { return 302 /$lang$request_uri; } return 302 /en-US/; } }

6.3 语言切换组件

语言切换器(Language Switcher)需要考虑三件事:显示语言名称(用各语言的本地名称而非英文)、保留当前页面路径、SEO 友好(用 <a> 而非 JavaScript 跳转)。

// SEO 友好的语言切换器 — Claude Code 生成 function LanguageSwitcher({ currentLocale, currentPath }) { const languages = { 'zh-CN': { label: '简体中文', flag: '🇨🇳' }, 'en-US': { label: 'English', flag: '🇺🇸' }, 'ja-JP': { label: '日本語', flag: '🇯🇵' }, 'ar-SA': { label: 'العربية', flag: '🇸🇦' }, }; return ( <nav className="language-switcher" aria-label="语言切换"> {Object.entries(languages).map(([locale, { label, flag }]) => ( <a key={locale} href={`/${locale}${currentPath}`} hrefLang={locale} className={locale === currentLocale ? 'active' : ''} rel="alternate" aria-current={locale === currentLocale ? 'true' : undefined} > <span aria-hidden="true">{flag}</span> {label} </a> ))} </nav> ); }

6.4 多语言站点地图

多语言站点地图需要在 `` 内使用 `` 标记各语言版本的对应关系。Claude Code 可以自动生成符合 Google 标准的站点地图。

<!-- 多语言站点地图示例 — Claude Code 自动生成 --> <?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"> <url> <loc>https://example.com/zh-CN/products</loc> <xhtml:link rel="alternate" hreflang="en-US" href="https://example.com/en-US/products" /> <xhtml:link rel="alternate" hreflang="ja-JP" href="https://example.com/ja-JP/products" /> <xhtml:link rel="alternate" hreflang="ar-SA" href="https://example.com/ar-SA/products" /> <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products" /> <lastmod>2026-05-08</lastmod> <changefreq>weekly</changefreq> <priority>0.8</priority> </url> <url> <loc>https://example.com/en-US/products</loc> <xhtml:link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-CN/products" /> <xhtml:link rel="alternate" hreflang="ja-JP" href="https://example.com/ja-JP/products" /> <xhtml:link rel="alternate" hreflang="ar-SA" href="https://example.com/ar-SA/products" /> <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products" /> <lastmod>2026-05-08</lastmod> <changefreq>weekly</changefreq> <priority>0.8</priority> </url> </urlset>

6.5 本地化关键词策略

直接翻译关键词通常效果不佳。Claude Code 可以根据目标市场的搜索习惯,建议本地化的关键词变体,而非字对字翻译。

// 本地化关键词策略 — Claude Code 分析建议 // 场景:电商平台关键词本地化 // 英语源关键词 "buy smartphone online" // 直译(不推荐) // 中文: "买智能手机在线" — 不符合中文搜索习惯 // 日语: "スマートフォンをオンラインで買う" — 生硬 // 本地化优化(推荐) // 中文: "手机网购" / "网上买手机" / "正品手机商城" // 日语: "スマホ 通販" / "スマートフォン オンラインショップ" // 阿拉伯语: "شراء هاتف ذكي عبر الإنترنت" // Claude Code 本地化关键词生成提示 // "为以下英语关键词生成中文、日语、阿拉伯语的本地化版本。 // 要求:符合当地常见搜索习惯,考虑百度/Google/Yandex 的不同排名因素。"

七、测试与验证

国际化测试涉及的功能点多、覆盖面广。Claude Code 可以编写并执行自动化测试脚本,覆盖语言切换、布局完整性、字符编码、翻译覆盖率、截图对比和本地化回归测试。

7.1 语言切换测试

自动测试每个语言页面的基本功能,验证所有页面元素是否已正确翻译、语言切换后 URL 是否正确更新、页面标题和 meta 标签是否对应当前语言。

// 语言切换端到端测试 — Playwright + Claude Code 生成 import { test, expect } from '@playwright/test'; const locales = ['zh-CN', 'en-US', 'ja-JP', 'ar-SA']; test.describe('国际化语言切换测试', () => { locales.forEach(locale => { test(`页面标题和 meta 标签正确 — ${locale}`, async ({ page }) => { await page.goto(`/${locale}/products`); await page.waitForLoadState('networkidle'); // 验证 HTML lang 属性 const htmlLang = await page.getAttribute('html', 'lang'); expect(htmlLang).toBe(locale); // 验证 dir 属性(RTL 语言) if (locale === 'ar-SA') { const dir = await page.getAttribute('html', 'dir'); expect(dir).toBe('rtl'); } // 验证 h1 标签已翻译 const h1 = await page.textContent('h1'); expect(h1).not.toContain('[待翻译]'); expect(h1?.trim().length).toBeGreaterThan(0); }); test(`语言切换后 URL 正确更新 — ${locale}`, async ({ page }) => { await page.goto(`/${locale}/products`); // 点击每个语言切换链接并验证 URL for (const targetLocale of locales) { if (targetLocale === locale) continue; await page.click(`a[href="/${targetLocale}/products"]`); await page.waitForLoadState('networkidle'); expect(page.url()).toContain(`/${targetLocale}/products`); await page.goBack(); } }); }); });

7.2 布局完整性测试

不同语言的文本长度差异会导致布局偏移。例如德语的 "Password" 是 "Kennwort" (8 字符 vs 8 字符问题不大),但 "Terms and Conditions" → "Nutzungsbedingungen" (22 字符 vs 19 字符) 或者 "I accept" → "Ik ga akkoord" (8 字符 vs 14 字符)。Claude Code 可以生成视觉回归测试脚本。

// 布局完整性测试 — Claude Code 生成 test.describe('布局完整性测试', () => { const longTextLocales = ['de-DE', 'ru-RU', 'ar-SA']; longTextLocales.forEach(locale => { test(`按钮和标签无文本溢出 — ${locale}`, async ({ page }) => { await page.goto(`/${locale}/checkout`); await page.waitForLoadState('networkidle'); // 检查所有按钮文本是否可见且未被截断 const buttons = page.locator('button, .btn, [role="button"]'); const count = await buttons.count(); for (let i = 0; i < count; i++) { const btn = buttons.nth(i); const box = await btn.boundingBox(); const text = await btn.textContent(); // 检查文本是否包含省略号(溢出截断标志) expect(text).not.toContain('…'); expect(text).not.toContain('...'); // 检查元素未超出父容器 const parent = page.locator('#root'); const parentBox = await parent.boundingBox(); expect(box?.x + (box?.width || 0)).toBeLessThanOrEqual( (parentBox?.x || 0) + (parentBox?.width || 0) + 2 ); } }); }); test(`无重叠元素 — ar-SA`, async ({ page }) => { await page.goto('/ar-SA/products'); await page.waitForLoadState('networkidle'); // 检查元素是否重叠 const elements = page.locator('*'); const count = await elements.count(); const rects = []; for (let i = 0; i < count; i++) { const box = await elements.nth(i).boundingBox(); if (box) rects.push(box); } // 检测重叠(简化版) for (let i = 0; i < rects.length; i++) { for (let j = i + 1; j < rects.length; j++) { const a = rects[i], b = rects[j]; const overlap = !(a.x + a.width < b.x || b.x + b.width < a.x || a.y + a.height < b.y || b.y + b.height < a.y); if (overlap) { // 忽略页面级容器间的正常包含关系 const isContained = (a.x <= b.x && a.y <= b.y && a.x + a.width >= b.x + b.width && a.y + a.height >= b.y + b.height); if (!isContained) { console.warn(`[重叠警告] 元素 ${i} 和 ${j}`); } } } } }); });

7.3 字符编码与翻译覆盖率

字符编码问题可能导致乱码(Mojibake),例如将 UTF-8 编码的日语读作 ISO-8859-1 时会出现「文字化け」。翻译覆盖率检查确保每个翻译键在目标语言文件中都有对应值,且不存在未经翻译的默认回退。

// 字符编码检查 — Claude Code 生成 const fs = require('fs'); const path = require('path'); function checkEncoding(localeDir) { const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.json')); const replacementChar = '�'; // 替换字符 (�) for (const file of files) { const content = fs.readFileSync(path.join(localeDir, file), 'utf-8'); if (content.includes(replacementChar)) { console.error(`[编码错误] ${file} 包含非法字符 �`); } // 检查 BOM 头 if (content.charCodeAt(0) === 0xFEFF) { console.warn(`[BOM警告] ${file} 包含 UTF-8 BOM,建议移除`); } } } // 翻译覆盖率报告 function coverageReport(sourceFile, targetFiles) { const source = JSON.parse(fs.readFileSync(sourceFile, 'utf-8')); const totalKeys = Object.keys(source).length; for (const targetFile of targetFiles) { const target = JSON.parse(fs.readFileSync(targetFile, 'utf-8')); const translated = Object.keys(target).filter(k => { return target[k] && !target[k].startsWith('[待翻译]') && !k.startsWith('__deprecated__'); }).length; const coverage = ((translated / totalKeys) * 100).toFixed(1); const status = coverage === '100.0' ? '✅' : '⚠️'; console.log(`${status} ${targetFile}: ${translated}/${totalKeys} (${coverage}%)`); } } coverageReport('locales/zh-CN.json', [ 'locales/en-US.json', 'locales/ja-JP.json', 'locales/ar-SA.json', ]);

7.4 截图对比与本地化回归测试

截图对比(Visual Regression Testing)是检测本地化回归最有效的手段。对不同语言的同一页面截图,并与基线图像进行像素级对比。Claude Code 可以配置 Percy/Chromatic 或 Playwright 截图对比测试。

// Playwright 截图对比测试 — Claude Code 生成 test.describe('本地化视觉回归测试', () => { const pages = [ { path: '/zh-CN/products', name: 'products-zh-CN' }, { path: '/en-US/products', name: 'products-en-US' }, { path: '/ja-JP/products', name: 'products-ja-JP' }, { path: '/ar-SA/products', name: 'products-ar-SA' }, ]; pages.forEach(({ path, name }) => { test(`截图对比: ${name}`, async ({ page }) => { await page.setViewportSize({ width: 1280, height: 800 }); await page.goto(path); await page.waitForLoadState('networkidle'); // 等待字体加载完成 await page.evaluate(async () => { await document.fonts.ready; }); await expect(page).toHaveScreenshot(name, { fullPage: true, maxDiffPixelRatio: 0.01, // 允许 1% 像素差异 }); }); }); test('移动端 RTL 布局测试', async ({ page }) => { await page.setViewportSize({ width: 375, height: 812 }); // iPhone X await page.goto('/ar-SA/products'); await page.waitForLoadState('networkidle'); await page.evaluate(async () => { await document.fonts.ready; }); await expect(page).toHaveScreenshot('products-ar-SA-mobile', { fullPage: true, maxDiffPixelRatio: 0.02, }); }); });

CI/CD 集成建议:

将国际化测试纳入 CI/CD 流水线,在每次 PR 合并前自动执行:

1. 翻译占位符一致性检查 → 阻塞合并(硬门槛)

2. 翻译覆盖率报告 → 不阻塞,但评论到 PR 上

3. Playwright 语言切换测试 → 阻塞合并

4. 截图对比测试 → 阻塞合并(基线存储在 git LFS)

5. 编码检测 → 阻塞合并(防止乱码进入生产)

7.5 本地化回归测试矩阵

测试类型测试范围自动化程度推荐工具
翻译键完整性所有语言的所有 JSON/PO 文件全自动自定义脚本 (Node.js / Python)
占位符一致性每个翻译键的源文 vs 译文全自动Claude Code + 正则校验
UI 布局完整性德语/俄语/阿拉伯语等文本较长语言全自动Playwright / Cypress
RTL 视觉验证阿拉伯语/希伯来语半自动Playwright 截图 + Percy
功能正确性每种语言的核心用户流全自动Playwright 端到端
字符编码所有翻译文件全自动chardet / isUTF8
SEO 标签验证所有语言版本的 hreflang/canonical全自动自定义爬虫 + Claude Code 审查
人工审查关键页面文案、品牌 slogan人工(Claude Code 辅助)Claude Code 对比 + 抄送翻译审核员

八、总结与工作流全景

国际化和本地化是一项持续的工程,而非一次性任务。Claude Code 在整个 i18n/l10n 生命周期中扮演着多重角色:代码分析者(扫描提取待翻译字符串)、自动化助手(生成翻译文件和配置)、质量把关者(校验占位符一致性和翻译覆盖率)、以及适配顾问(RTL/CSS 逻辑属性/SEO 策略)。

推荐 CI/CD 集成工作流全景:

① 开发阶段 → Claude Code 自动提取源字符串 → 生成/更新翻译骨架文件

② 翻译阶段 → Claude Code 机翻初稿 → 人工审校 → 回写翻译文件

③ 构建阶段 → 占位符一致性校验 → 编码检测 → 翻译覆盖率报告

④ 测试阶段 → 语言切换 E2E → RTL 布局截图对比 → 多语言视觉回归

⑤ 部署阶段 → hreflang 标签验证 → 多语言站点地图生成 → SEO 分析

⑥ 持续监控 → 翻译过时检测 → 新增字符串提醒 → 自动化 PR 建议

通过将 Claude Code 深度集成到 i18n/l10n 工作流中,团队可以节省 60%-80% 的重复性劳动时间,将更多精力投入到本地化质量和用户体验优化上。同时,自动化检查屏障可以有效杜绝因翻译错误导致的线上事故,保障全球用户的统一体验。

记住:

好的国际化代码,用户不会注意到。糟糕的本地化,用户一眼就能发现。将 Claude Code 融入你的 i18n 管道,让 AI 处理重复劳动,让人工专注于文化和创意层面的本地化决策。