
LLM 驱动的前端国际化方案从文本提取到多语言代码生成的工程实践一、前端国际化的工程痛点手动维护翻译文件的噩梦前端国际化i18n看似简单——把硬编码的中文替换为 i18n key再提供各语言的翻译文件。但在大型项目中这个流程迅速失控新增功能时遗漏翻译 key、翻译文件与代码不同步、翻译质量无法自动化验证、文案变更需要跨团队协调。更严重的是翻译文件往往成为无人认领的代码开发者在其中堆砌 key 却从不清理废弃条目。LLM 为国际化流程提供了新的可能性自动提取代码中的硬编码文本、根据上下文生成高质量翻译、检测翻译文件与代码的一致性。这并非替代专业翻译而是将机械性的提取和初译工作自动化让翻译人员专注于语义校准和文化适配。二、LLM 辅助国际化的架构设计2.1 国际化全流程自动化flowchart TB subgraph Extract[文本提取] E1[AST 扫描源码] -- E2[识别硬编码中文] E2 -- E3[生成 i18n key] E3 -- E4[替换源码引用] end subgraph Translate[翻译生成] T1[提取中文文案] -- T2[LLM 上下文翻译] T2 -- T3[术语表约束] T3 -- T4[翻译质量评分] end subgraph Validate[一致性校验] V1[代码引用扫描] -- V2[翻译文件扫描] V2 -- V3[缺失 key 检测] V3 -- V4[废弃 key 检测] end Extract -- Translate -- Validate2.2 i18n key 的命名规范规范的 key 命名是国际化可维护性的基础。推荐采用模块.页面.元素.状态的层级命名如user.profile.email.label、order.list.empty.hint。这种命名既便于定位代码位置也便于翻译人员理解上下文。三、LLM 辅助国际化的代码实现3.1 硬编码文本提取器import * as ts from typescript; import * as fs from fs; import * as path from path; interface ExtractedText { file: string; line: number; original: string; key: string; context: string; // 周围代码的上下文 } // 从 TypeScript/TSX 文件中提取硬编码中文 function extractChineseText(filePath: string): ExtractedText[] { const sourceCode fs.readFileSync(filePath, utf-8); const sourceFile ts.createSourceFile( filePath, sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX ); const results: ExtractedText[] []; function visit(node: ts.Node) { // 检测字符串字面量中的中文 if (ts.isStringLiteral(node) containsChinese(node.text)) { const line sourceFile.getLineAndCharacterOfPosition(node.getStart()).line 1; const context getContext(sourceCode, node.getStart(), 200); const key generateKey(filePath, node.text); results.push({ file: filePath, line, original: node.text, key, context, }); } // 检测 JSX 文本中的中文 if (ts.isJsxText(node) containsChinese(node.text)) { const text node.text.trim(); if (text) { const line sourceFile.getLineAndCharacterOfPosition(node.getStart()).line 1; const context getContext(sourceCode, node.getStart(), 200); const key generateKey(filePath, text); results.push({ file: filePath, line, original: text, key, context, }); } } ts.forEachChild(node, visit); } visit(sourceFile); return results; } function containsChinese(text: string): boolean { return /[\u4e00-\u9fa5]/.test(text); } // 根据文件路径和文本内容生成 i18n key function generateKey(filePath: string, text: string): string { const relativePath path.relative(process.cwd(), filePath); const parts relativePath .replace(/\.(tsx?|jsx?)$/, ) .split(path.sep) .filter(p p ! src p ! pages p ! components); // 取路径的最后两级作为 key 前缀 const prefix parts.slice(-2).join(.); // 取文本的前几个字作为语义标识 const suffix text.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, ).slice(0, 8); return ${prefix}.${suffix}; } function getContext(sourceCode: string, position: number, length: number): string { const start Math.max(0, position - length / 2); const end Math.min(sourceCode.length, position length / 2); return sourceCode.slice(start, end); }3.2 LLM 翻译生成器interface TranslationRequest { key: string; chinese: string; context: string; glossary: Recordstring, string; // 术语表 } interface TranslationResult { key: string; en: string; ja: string; ko: string; qualityScore: number; // 0-1 翻译质量评分 } // 使用 LLM 生成多语言翻译 async function generateTranslations( requests: TranslationRequest[] ): PromiseTranslationResult[] { const glossaryStr Object.entries(requests[0]?.glossary || {}) .map(([cn, en]) ${cn} → ${en}) .join(\n); const prompt 你是一个专业的前端翻译专家。请将以下中文文案翻译为英文、日文、韩文。 翻译规则 1. 保持 UI 文案的简洁性英文不超过中文长度的 1.5 倍 2. 遵循术语表中的固定翻译 3. 根据代码上下文推断文案的语义场景 4. 输出 JSON 数组格式 术语表 ${glossaryStr} 待翻译文案 ${requests.map(r Key: ${r.key}\n中文: ${r.chinese}\n上下文: ${r.context}).join(\n---\n)} 输出格式 [{key: ..., en: ..., ja: ..., ko: ...}]; const response await callLLM(prompt); const results JSON.parse(response); // 为每条翻译添加质量评分 return results.map(r ({ ...r, qualityScore: estimateQuality(r), })); } // 简单的翻译质量评估 function estimateQuality(result: TranslationResult): number { let score 1.0; // 英文过长扣分 if (result.en.length result.key.length * 3) score - 0.2; // 包含未翻译的中文扣分 if (/[\u4e00-\u9fa5]/.test(result.en)) score - 0.3; return Math.max(0, score); }3.3 一致性校验工具interface ValidationResult { missingInCode: string[]; // 翻译文件中有但代码中未引用的 key missingInTranslation: string[]; // 代码中引用但翻译文件中缺失的 key emptyTranslations: string[]; // 翻译值为空的 key } function validateI18nConsistency( codeKeys: Setstring, translationFiles: Recordstring, Recordstring, string ): ValidationResult { const allTranslationKeys new Setstring(); const missingInCode: string[] []; const missingInTranslation: string[] []; const emptyTranslations: string[] []; // 收集所有翻译文件中的 key for (const translations of Object.values(translationFiles)) { for (const key of Object.keys(translations)) { allTranslationKeys.add(key); } } // 检查翻译文件中有但代码中未引用的 key废弃 key for (const key of allTranslationKeys) { if (!codeKeys.has(key)) { missingInCode.push(key); } } // 检查代码中引用但翻译文件中缺失的 key for (const key of codeKeys) { if (!allTranslationKeys.has(key)) { missingInTranslation.push(key); } } // 检查翻译值为空的 key for (const [lang, translations] of Object.entries(translationFiles)) { for (const [key, value] of Object.entries(translations)) { if (!value.trim()) { emptyTranslations.push(${lang}/${key}); } } } return { missingInCode, missingInTranslation, emptyTranslations }; }四、LLM 辅助国际化的架构权衡4.1 LLM 翻译 vs 专业翻译LLM 翻译适合初译和批量翻译但在文化适配、品牌调性和法律合规方面仍需专业翻译人员审核。建议将 LLM 翻译作为初稿标记为status: draft经人工审核后才标记为status: approved。4.2 自动提取的准确率AST 扫描能准确识别字符串字面量和 JSX 文本中的中文但无法识别动态拼接的文案如欢迎 name。对于动态文案仍需人工标注。建议在代码规范中禁止字符串拼接统一使用模板函数如t(welcome, { name })。4.3 翻译文件的格式选择JSON 格式最常见但不支持注释YAML 支持注释但解析复杂。建议使用 JSON 格式将注释信息放在_comment字段中。对于大型项目按模块拆分翻译文件避免单文件过大。五、总结前端国际化的工程化需要覆盖文本提取、翻译生成和一致性校验三个环节。AST 扫描实现硬编码文本的自动提取LLM 根据代码上下文生成多语言初译一致性校验工具检测缺失和废弃的翻译 key。LLM 翻译的价值在于将初译的机械劳动自动化而非替代专业翻译。落地时建议从文本提取和一致性校验这两个确定性工具开始再逐步引入 LLM 翻译辅助确保每一步都有质量门禁。