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 驱动的国际化翻译系统包含以下核心模块:

  1. Key 提取模块:从源代码中自动提取国际化 Key
  2. 上下文分析模块:理解 Key 的使用场景和语义
  3. 翻译生成模块:调用 LLM API 生成多语言翻译
  4. 术语一致性检查:确保同一术语在不同地方翻译一致
  5. 翻译文件更新:自动更新 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: Record<string, string>;  // 各语言的翻译
  context?: string;       // 使用场景说明
  approved: boolean;     // 是否已审核
}

export class TerminologyManager {
  private terms: Map<string, 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: Record<string, 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): Promise<TranslationResult> {
    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[]
  ): Promise<void> {
    for (const lang of targetLanguages) {
      const filePath = path.join(this.basePath, lang, 'translation.json');
      
      // 读取现有翻译
      let existing: Record<string, 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[]
  ): Record<string, string[]> {
    const missing: Record<string, 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[]): Promise<I18nKeyInfo[]> {
    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[]): Promise<void> {
    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(): Record<string, { 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-request@v5
  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/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        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-action@v4
        with:
          commit_message: 'Auto-update i18n translations [skip ci]'
          file_pattern: 'public/locales/**'

五、总结

AI 生成前端国际化翻译能够显著降低多语言维护成本。通过自动提取 i18n Keys、调用 LLM 生成翻译、保障术语一致性,可以实现翻译文件的自动更新。

关键实践包括:建立术语库确保一致性、增量翻译避免重复成本、翻译质量评估、CI/CD 自动化集成。这些手段能够确保翻译质量,并减少人工介入。

对于大型项目,建议建立翻译审核流程,AI 生成初稿后由人工审核关键翻译,持续提升翻译质量。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值