AI 生成前端国际化翻译:从 Key 到多语言文案的全流程
AI 生成前端国际化翻译从 Key 到多语言文案的全流程一、前端国际化翻译的工程化挑战前端国际化i18n需要维护多语言翻译文件传统方案依赖人工翻译或第三方翻译服务。随着产品功能迭代翻译文件同步成为负担容易出现遗漏或翻译不准确。AI 辅助翻译能够通过大语言模型LLM自动生成多语言翻译并保持术语一致性。核心挑战包括上下文理解、术语统一、复数形式处理、变量插值支持。// 传统国际化文件维护方式存在问题 // en-US.json { welcome.message: Welcome to our app, button.save: Save, user.profile.title: User Profile, notification.unread_count: You have {count} unread notifications } // zh-CN.json需要人工翻译容易遗漏或翻译不一致 { welcome.message: 欢迎使用我们的应用, button.save: 保存, user.profile.title: 用户资料, notification.unread_count: 您有 {count} 条未读通知 } // 新增键时需要手动添加到所有语言文件 // 删除键时需要手动从所有语言文件中删除 // 这导致维护成本高、容易出错二、AI 翻译的技术架构与流程AI 驱动的国际化翻译系统包含以下核心模块Key 提取模块从源代码中自动提取国际化 Key上下文分析模块理解 Key 的使用场景和语义翻译生成模块调用 LLM API 生成多语言翻译术语一致性检查确保同一术语在不同地方翻译一致翻译文件更新自动更新 JSON/YAML 翻译文件graph TD A[源代码扫描] -- B[提取 i18n Keys] B -- C{翻译文件是否存在} C --|否| D[创建新翻译条目] C --|是| E[检查是否需要更新] D -- F[上下文分析] E -- F F -- G[调用 LLM 生成翻译] G -- H[术语一致性检查] H -- I[人工审核可选] I -- J[更新翻译文件] J -- K[生成 TypeScript 类型] K -- L[集成到构建流程] style A fill:#e1f5fe style G fill:#fff3e0 style J fill:#e8f5e9 style L fill:#f3e5f5技术选型Key 提取使用 AST 分析工具Babel Parser扫描代码中的t()、i18n.t()等调用LLM 选择OpenAI GPT-4翻译质量高、Claude 3上下文理解强、或开源模型成本低术语库使用 JSON 或数据库存储产品特定术语的翻译映射质量控制使用 BLEU 评分、人工审核、A/B 测试场景描述在一次实际项目中我们用 Babel Parser 做 Key 提取时发现了一个隐蔽问题动态 Key 无法被 AST 静态分析捕获。比如t(\user.status.${status}) 这种模板字符串方式的调用AST 看到的不是一个 StringLiteral 而是一个 TemplateLiteral导致提取器漏掉了大量 Key。等到日语版本的翻译文件上线后大量未翻译的 Key 直接以英文原文显示在页面上。// Babel Parser 只能静态分析这种动态 key 会漏掉 const statusText t(user.status.${status}); // ❌ 不会被提取 // 解决方案使用枚举替换模板字符串 const statusMap { active: user.status.active, inactive: user.status.inactive, banned: user.status.banned } as const; const statusText t(statusMap[status]); // ✅ 可被静态分析这个案例的教训是AST 提取不能 100% 覆盖所有场景团队需要建立规范限制动态 Key 的使用。同时应该在 CI 中加入覆盖度检查——运行时收集所有实际调用过的t()key与静态提取的结果做 diff发现遗漏及时补充。// i18n Key 提取器实现 // services/i18nKeyExtractor.ts import * as babelParser from babel/parser; import * as t from babel/types; import traverse from babel/traverse; import * as fs from fs; import * as path from path; export interface I18nKeyInfo { key: string; defaultValue?: string; filePath: string; lineNumber: number; context?: string; // 周围的代码上下文 } export class I18nKeyExtractor { private patterns: Array{ name: string; detect: (node: t.Node) I18nKeyInfo | null; }; constructor() { this.patterns [ { name: react-i18next t(), detect: (node) { if ( t.isCallExpression(node) t.isIdentifier(node.callee) node.callee.name t node.arguments.length 1 t.isStringLiteral(node.arguments[0]) ) { return { key: node.arguments[0].value, defaultValue: node.arguments[1]?.type StringLiteral ? node.arguments[1].value : undefined, filePath: , // 需要在遍历时设置 lineNumber: node.loc?.start.line || 0 }; } return null; } }, { name: i18next t(), detect: (node) { if ( t.isCallExpression(node) t.isMemberExpression(node.callee) t.isIdentifier(node.callee.object) node.callee.object.name i18next t.isIdentifier(node.callee.property) node.callee.property.name t node.arguments.length 1 t.isStringLiteral(node.arguments[0]) ) { return { key: node.arguments[0].value, filePath: , lineNumber: node.loc?.start.line || 0 }; } return null; } }, { name: FormattedMessage (react-intl), detect: (node) { if ( t.isJSXElement(node) t.isJSXOpeningElement(node.openingElement) t.isJSXIdentifier(node.openingElement.name) node.openingElement.name.name FormattedMessage ) { const idAttr node.openingElement.attributes.find( attr t.isJSXAttribute(attr) t.isJSXIdentifier(attr.name) attr.name.name id ) as t.JSXAttribute | undefined; if (idAttr?.value t.isStringLiteral(idAttr.value)) { return { key: idAttr.value.value, filePath: , lineNumber: node.loc?.start.line || 0 }; } } return null; } } ]; } /** * 从文件中提取 i18n Keys */ extractFromFile(filePath: string): I18nKeyInfo[] { const code fs.readFileSync(filePath, utf-8); const keys: I18nKeyInfo[] []; try { const ast babelParser.parse(code, { sourceType: module, plugins: [jsx, typescript, decorators-legacy] }); traverse(ast, { enter: (path) { for (const pattern of this.patterns) { const result pattern.detect(path.node); if (result) { result.filePath filePath; // 获取更多上下文前 3 行代码 const lines code.split(\n); const startLine Math.max(0, result.lineNumber - 4); result.context lines.slice(startLine, result.lineNumber).join(\n); keys.push(result); break; // 一个节点只匹配一个模式 } } } }); } catch (error) { console.error(Failed to parse file ${filePath}:, error); } return keys; } /** * 扫描目录提取所有 i18n Keys */ extractFromDirectory(dirPath: string, extensions: string[] [.tsx, .ts, .jsx, .js]): I18nKeyInfo[] { const allKeys: I18nKeyInfo[] []; const scanDirectory (currentPath: string) { const items fs.readdirSync(currentPath); for (const item of items) { const itemPath path.join(currentPath, item); const stat fs.statSync(itemPath); if (stat.isDirectory()) { // 跳过 node_modules、dist 等目录 if (![node_modules, dist, build, .git].includes(item)) { scanDirectory(itemPath); } } else if (stat.isFile()) { const ext path.extname(itemPath); if (extensions.includes(ext)) { const keys this.extractFromFile(itemPath); allKeys.push(...keys); } } } }; scanDirectory(dirPath); return allKeys; } }三、实战AI 翻译生成与术语一致性保障以下实现完整的 AI 翻译生成流程包括术语库管理、LLM 调用、翻译质量控制。1. 术语库管理// services/terminologyManager.ts export interface TermEntry { term: string; // 源语言术语 translations: Recordstring, string; // 各语言的翻译 context?: string; // 使用场景说明 approved: boolean; // 是否已审核 } export class TerminologyManager { private terms: Mapstring, TermEntry new Map(); private filePath: string; constructor(filePath: string ./i18n/terminology.json) { this.filePath filePath; this.loadTerms(); } /** * 加载术语库 */ private loadTerms(): void { try { const content fs.readFileSync(this.filePath, utf-8); const data JSON.parse(content); for (const [term, entry] of Object.entries(data)) { this.terms.set(term, entry as TermEntry); } console.log(Loaded ${this.terms.size} terminology entries); } catch (error) { console.log(Terminology file not found, starting with empty terminology); } } /** * 保存术语库 */ private saveTerms(): void { const data Object.fromEntries(this.terms); fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), utf-8); } /** * 添加或更新术语 */ addTerm(term: string, translations: Recordstring, string, context?: string): void { const existing this.terms.get(term); this.terms.set(term, { term, translations: { ...existing?.translations, ...translations }, context: context || existing?.context, approved: false }); this.saveTerms(); } /** * 查找术语翻译 */ findTranslation(term: string, language: string): string | null { const entry this.terms.get(term); return entry?.translations[language] || null; } /** * 构建 LLM Prompt 的术语部分 */ buildTerminologyPrompt(languages: string[]): string { if (this.terms.size 0) { return ; } let prompt \n\n## Terminology (必须严格遵守)\n; for (const [term, entry] of this.terms) { prompt - ${term}:; for (const lang of languages) { const translation entry.translations[lang]; if (translation) { prompt [${lang}] ${translation}; } } if (entry.context) { prompt (上下文: ${entry.context}); } prompt \n; } return prompt; } }2. AI 翻译生成服务// services/aiTranslationService.ts import OpenAI from openai; export interface TranslationRequest { keys: Array{ key: string; defaultValue?: string; context?: string; }; sourceLanguage: string; targetLanguages: string[]; terminologyPrompt?: string; } export interface TranslationResult { [key: string]: { [language: string]: string; }; } export class AITranslationService { private openai: OpenAI; private terminologyManager: TerminologyManager; constructor(apiKey: string, terminologyManager: TerminologyManager) { this.openai new OpenAI({ apiKey }); this.terminologyManager terminologyManager; } /** * 批量生成翻译 */ async generateTranslations(request: TranslationRequest): PromiseTranslationResult { const { keys, sourceLanguage, targetLanguages, terminologyPrompt } request; // 构建 LLM Prompt const prompt this.buildPrompt( keys, sourceLanguage, targetLanguages, terminologyPrompt ); try { const response await this.openai.chat.completions.create({ model: gpt-4, messages: [ { role: system, content: You are a professional translator specializing in software UI translation. Provide accurate, concise, and contextually appropriate translations. }, { role: user, content: prompt } ], temperature: 0.3, // 低温度确保一致性 response_format: { type: json_object } }); const content response.choices[0]?.message?.content; if (!content) { throw new Error(Empty response from LLM); } const translations JSON.parse(content) as TranslationResult; // 验证翻译结果 this.validateTranslations(translations, keys, targetLanguages); return translations; } catch (error) { console.error(Failed to generate translations:, error); throw error; } } /** * 构建翻译 Prompt */ private buildPrompt( keys: TranslationRequest[keys], sourceLanguage: string, targetLanguages: string[], terminologyPrompt?: string ): string { let prompt # Task: Translate UI strings for a software application\n\n; prompt ## Source Language: ${sourceLanguage}\n; prompt ## Target Languages: ${targetLanguages.join(, )}\n\n; prompt ## Strings to Translate\n\n; for (const item of keys) { prompt ### Key: ${item.key}\n; if (item.defaultValue) { prompt Default Value: ${item.defaultValue}\n; } if (item.context) { prompt Context: ${item.context}\n; } prompt \n; } // 添加术语库约束 if (terminologyPrompt) { prompt terminologyPrompt; } prompt \n## Instructions\n; prompt 1. Provide translations for ALL target languages.\n; prompt 2. Keep translations concise and appropriate for UI elements.\n; prompt 3. Preserve variables in the format {variableName} unchanged.\n; prompt 4. For plural forms, use the format: one item | {count} items.\n; prompt 5. Return the result as a JSON object with the following structure:\n; prompt {\n; prompt key_name: {\n; prompt language_code: translation\n; prompt }\n; prompt }\n; return prompt; } /** * 验证翻译结果 */ private validateTranslations( translations: TranslationResult, keys: TranslationRequest[keys], targetLanguages: string[] ): void { const errors: string[] []; for (const keyInfo of keys) { const translation translations[keyInfo.key]; if (!translation) { errors.push(Missing translation for key: ${keyInfo.key}); continue; } for (const lang of targetLanguages) { if (!translation[lang]) { errors.push(Missing ${lang} translation for key: ${keyInfo.key}); } } // 检查变量是否保留 if (keyInfo.defaultValue) { const variables keyInfo.defaultValue.match(/\{(\w)\}/g) || []; for (const lang of targetLanguages) { const translated translation[lang]; if (translated) { for (const variable of variables) { if (!translated.includes(variable)) { errors.push(Variable ${variable} not preserved in ${lang} translation for key: ${keyInfo.key}); } } } } } } if (errors.length 0) { console.warn(Translation validation warnings:); errors.forEach(err console.warn( - ${err})); } } }3. 翻译文件自动更新场景描述自动更新翻译文件虽然方便但是 AI 翻译偶尔会把占位符{variable}翻译成目标语言的等价表达比如把{count} items的日语翻译写成{count} アイテム就对了但有时模型会把{count}当成原文的一部分翻译成{個数}导致运行时变量不再被识别为插值点直接显示为原始字符串。我们的做法是在updateTranslations方法中增加校验步骤检查翻译后占位符数量和原始值的占位符数量是否一致// 占位符完整性校验 function validatePlaceholders(original: string, translated: string): boolean { const regex /\{(\w)\}/g; const origMatches original.match(regex) || []; const transMatches translated.match(regex) || []; return origMatches.every(m transMatches.includes(m)); }如果校验失败对应的 key 标记为需要人工复审不直接写入文件。这个简单的校验避免了多次线上文案出现{個数}而不是实际数字的事故。// services/translationFileManager.ts export class TranslationFileManager { private basePath: string; constructor(basePath: string ./public/locales) { this.basePath basePath; } /** * 更新翻译文件 */ async updateTranslations( translations: TranslationResult, targetLanguages: string[] ): Promisevoid { for (const lang of targetLanguages) { const filePath path.join(this.basePath, lang, translation.json); // 读取现有翻译 let existing: Recordstring, string {}; try { const content fs.readFileSync(filePath, utf-8); existing JSON.parse(content); } catch (error) { console.log(Creating new translation file: ${filePath}); } // 更新翻译 let updated false; for (const [key, translation] of Object.entries(translations)) { if (translation[lang] existing[key] ! translation[lang]) { existing[key] translation[lang]; updated true; } } // 写回文件 if (updated) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, JSON.stringify(existing, null, 2), utf-8); console.log(Updated translation file: ${filePath}); } } } /** * 检测缺失的翻译 */ findMissingTranslations( keys: string[], targetLanguages: string[] ): Recordstring, string[] { const missing: Recordstring, string[] {}; for (const lang of targetLanguages) { const filePath path.join(this.basePath, lang, translation.json); try { const content fs.readFileSync(filePath, utf-8); const translations JSON.parse(content); const missingKeys keys.filter(key !translations[key]); if (missingKeys.length 0) { missing[lang] missingKeys; } } catch (error) { console.error(Failed to read translation file for ${lang}:, error); missing[lang] keys; // 文件不存在所有 key 都缺失 } } return missing; } }4. 完整工作流集成// scripts/generate-translations.ts - CLI 工具 import { I18nKeyExtractor } from ../services/i18nKeyExtractor; import { TerminologyManager } from ../services/terminologyManager; import { AITranslationService } from ../services/aiTranslationService; import { TranslationFileManager } from ../services/translationFileManager; async function main() { // 1. 提取 i18n Keys console.log(Extracting i18n keys...); const extractor new I18nKeyExtractor(); const keys extractor.extractFromDirectory(./src); console.log(Found ${keys.length} i18n keys); // 2. 加载术语库 console.log(Loading terminology...); const termManager new TerminologyManager(); const terminologyPrompt termManager.buildTerminologyPrompt([zh-CN, ja-JP, ko-KR]); // 3. 检测缺失的翻译 console.log(Checking for missing translations...); const fileManager new TranslationFileManager(); const missing fileManager.findMissingTranslations( keys.map(k k.key), [zh-CN, ja-JP, ko-KR] ); console.log(Missing translations:, missing); // 4. 生成翻译 if (Object.keys(missing).length 0) { console.log(Generating translations with AI...); const translationService new AITranslationService( process.env.OPENAI_API_KEY!, termManager ); const keysToTranslate keys.filter(k Object.values(missing).some(langKeys langKeys.includes(k.key)) ); const translations await translationService.generateTranslations({ keys: keysToTranslate.map(k ({ key: k.key, defaultValue: k.defaultValue, context: k.context })), sourceLanguage: en-US, targetLanguages: [zh-CN, ja-JP, ko-KR], terminologyPrompt }); // 5. 更新翻译文件 console.log(Updating translation files...); await fileManager.updateTranslations(translations, [zh-CN, ja-JP, ko-KR]); console.log(✅ Translation generation complete!); } else { console.log(✅ All translations are up to date!); } } main().catch(error { console.error(❌ Translation generation failed:, error); process.exit(1); });四、AI 翻译的工程化最佳实践1. 翻译质量评估使用 BLEU 评分和人工审核结合的方式确保质量。// 翻译质量评估 export async function evaluateTranslationQuality( sourceTexts: string[], translatedTexts: string[], referenceTranslations: string[] ): Promise{ bleuScore: number; suggestions: string[] } { // 简化版 BLEU 评分计算 let totalScore 0; const suggestions: string[] []; for (let i 0; i sourceTexts.length; i) { const translated translatedTexts[i]; const reference referenceTranslations[i]; // 计算 n-gram 重叠度简化逻辑 const translatedWords translated.split(/\s/); const referenceWords reference.split(/\s/); const commonWords translatedWords.filter(w referenceWords.includes(w)); const score commonWords.length / Math.max(translatedWords.length, 1); totalScore score; if (score 0.5) { suggestions.push(Low quality translation for: ${sourceTexts[i]}); } } const bleuScore totalScore / sourceTexts.length; return { bleuScore, suggestions }; }2. 增量翻译只翻译新增或修改的 Keys避免重复翻译。// 增量翻译支持 export class IncrementalTranslationManager { private cachePath: string; constructor(cachePath: string ./.i18n-cache.json) { this.cachePath cachePath; } /** * 计算需要翻译的 Keys新增或修改后的 */ async getKeysToTranslate(keys: I18nKeyInfo[]): PromiseI18nKeyInfo[] { const cache this.loadCache(); const keysToTranslate: I18nKeyInfo[] []; for (const key of keys) { const cached cache[key.key]; const currentHash this.hashCode(key.defaultValue || key.key); if (!cached || cached.hash ! currentHash) { keysToTranslate.push(key); } } return keysToTranslate; } /** * 更新缓存 */ async updateCache(keys: I18nKeyInfo[]): Promisevoid { const cache this.loadCache(); for (const key of keys) { cache[key.key] { hash: this.hashCode(key.defaultValue || key.key), lastTranslated: new Date().toISOString() }; } fs.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), utf-8); } private loadCache(): Recordstring, { hash: number; lastTranslated: string } { try { return JSON.parse(fs.readFileSync(this.cachePath, utf-8)); } catch { return {}; } } private hashCode(str: string): number { let hash 0; for (let i 0; i str.length; i) { const char str.charCodeAt(i); hash ((hash 5) - hash) char; hash hash hash; // Convert to 32bit integer } return hash; } }3. CI/CD 集成在 CI/CD 流程中自动检测并更新翻译。场景描述将 AI 翻译集成到 CI/CD 中有一个风险点值得注意——如果 CI 每次 push 都自动生成并提交翻译但 AI 翻译结果偶然出现了严重错误比如把否定句翻译成了肯定句这个错误会被自动合入代码库并可能随下一次部署上线。因此我们增加了一个缓冲区AI 翻译的 PR 只自动创建不自动合入。由人工或简单的规则检查如关键术语一致性校验通过后再合入。# 关键自动创建 PR 但不自动合入 - name: Create Translation PR uses: peter-evans/create-pull-requestv5 with: title: Auto-generated i18n translations body: AI 自动生成的翻译更新请检查后合入 branch: auto/i18n-update labels: i18n, auto-generated # 不自动合入等待人工或规则检查这样既保证了翻译同步的效率又避免了一次 AI 的幻觉直接把错误翻译推到线上。# .github/workflows/translations.yml name: Update Translations on: push: branches: [main] paths: - src/** - public/locales/** jobs: update-translations: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Setup Node.js uses: actions/setup-nodev3 with: node-version: 18 - name: Install dependencies run: npm ci - name: Extract i18n keys run: npm run i18n:extract - name: Generate translations (AI) env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: npm run i18n:translate - name: Commit updated translations uses: stefanzweifel/git-auto-commit-actionv4 with: commit_message: Auto-update i18n translations [skip ci] file_pattern: public/locales/**五、总结AI 生成前端国际化翻译能够显著降低多语言维护成本。通过自动提取 i18n Keys、调用 LLM 生成翻译、保障术语一致性可以实现翻译文件的自动更新。关键实践包括建立术语库确保一致性、增量翻译避免重复成本、翻译质量评估、CI/CD 自动化集成。这些手段能够确保翻译质量并减少人工介入。对于大型项目建议建立翻译审核流程AI 生成初稿后由人工审核关键翻译持续提升翻译质量。