Back to MCP directory
publicPublicdnsLocal runtime

translator-ai

多AI提供商JSON翻译工具,支持智能缓存、批量处理和本地化翻译

article

README

🚀 translator-ai

translator-ai 是一款快速高效的 JSON 国际化(i18n)翻译工具,支持多种人工智能翻译提供商,如 Google Gemini、OpenAI 以及本地的 Ollama/DeepSeek。它具备智能缓存、多文件去重和 MCP 集成等特性,能显著提升翻译效率并节省成本。

🚀 快速开始

你可以通过以下命令全局或本地安装 translator-ai

全局安装(推荐)

npm install -g translator-ai

本地安装

npm install translator-ai

安装完成后,你可以使用以下基本命令进行文件翻译:

# 翻译单个文件
translator-ai source.json -l es -o spanish.json

# 翻译多个文件并去重
translator-ai src/locales/en/*.json -l es -o "{dir}/{name}.{lang}.json"

# 使用通配符模式
translator-ai "src/**/*.en.json" -l fr -o "{dir}/{name}.fr.json"

✨ 主要特性

  • 多 AI 提供商支持:你可以在 Google Gemini、OpenAI(云端)或 Ollama/DeepSeek(本地)之间选择翻译服务。
  • 多文件处理:自动处理多个文件并去重,节省 API 调用次数。
  • 增量缓存:仅翻译新的或修改过的字符串,大幅减少 API 调用。
  • 批量处理:智能批量翻译以优化性能。
  • 路径保留:保持 JSON 结构不变,包括嵌套对象和数组。
  • 跨平台支持:可在 Windows、macOS 和 Linux 上运行,并自动检测缓存目录。
  • 开发者友好:内置性能统计和进度指示器。
  • 成本效益高:通过智能缓存和去重最小化 API 使用。
  • 语言检测:自动检测源语言,无需假设为英语。
  • 多目标语言支持:可在单个命令中翻译到多种语言。
  • 翻译元数据:可选择在输出文件中包含翻译细节以便跟踪。
  • 试运行模式:预览翻译内容而不进行实际的 API 调用。
  • 格式保留:保持 URL、电子邮件、日期、数字和模板变量不变。

📦 安装指南

全局安装(推荐)

npm install -g translator-ai

本地安装

npm install translator-ai

💻 使用示例

基础用法

# 翻译单个文件
translator-ai source.json -l es -o spanish.json

# 翻译多个文件并去重
translator-ai src/locales/en/*.json -l es -o "{dir}/{name}.{lang}.json"

# 使用通配符模式
translator-ai "src/**/*.en.json" -l fr -o "{dir}/{name}.fr.json"

命令行选项

translator-ai <inputFiles...> [options]

参数:
  inputFiles                   源 JSON 文件的路径或通配符模式

选项:
  -l, --lang <langCodes>      目标语言代码,多个代码用逗号分隔
  -o, --output <pattern>      输出文件路径或模式
  --stdout                    输出到标准输出而不是文件
  --stats                     显示详细的性能统计信息
  --no-cache                  禁用增量翻译缓存
  --cache-file <path>         自定义缓存文件路径
  --provider <type>           翻译提供商:gemini、openai 或 ollama(默认:gemini)
  --ollama-url <url>          Ollama API URL(默认:http://localhost:11434)
  --ollama-model <model>      Ollama 模型名称(默认:deepseek-r1:latest)
  --gemini-model <model>      Gemini 模型名称(默认:gemini-2.0-flash-lite)
  --openai-model <model>      OpenAI 模型名称(默认:gpt-4o-mini)
  --list-providers            列出可用的翻译提供商
  --verbose                   启用详细输出以进行调试
  --detect-source             自动检测源语言而不是假设为英语
  --dry-run                   预览翻译内容而不进行实际的 API 调用
  --preserve-formats          保持 URL、电子邮件、数字、日期和其他格式不变
  --metadata                  在输出文件中添加翻译元数据(可能会破坏一些 i18n 解析器)
  --sort-keys                 按字母顺序对输出 JSON 键进行排序
  --check-keys                验证所有源键是否存在于输出中(如果缺少键则退出并报错)
  -h, --help                  显示帮助信息
  -V, --version               显示版本信息

输出模式变量(用于多个文件):
  {dir}   - 原始目录路径
  {name}  - 原始文件名(不包括扩展名)
  {lang}  - 目标语言代码

示例

翻译单个文件

translator-ai en.json -l es -o es.json

按模式翻译多个文件

# 目录中的所有 JSON 文件
translator-ai locales/en/*.json -l es -o "locales/es/{name}.json"

# 递归通配符模式
translator-ai "src/**/en.json" -l fr -o "{dir}/fr.json"

# 多个特定文件
translator-ai file1.json file2.json file3.json -l de -o "{name}.de.json"

去重翻译以节省成本

# 显示统计信息,包括节省的 API 调用次数
translator-ai src/i18n/*.json -l ja -o "{dir}/{name}.{lang}.json" --stats

输出到标准输出(便于管道操作)

translator-ai en.json -l de --stdout > de.json

使用 jq 解析输出

translator-ai en.json -l de --stdout | jq

禁用缓存以进行全新翻译

translator-ai en.json -l ja -o ja.json --no-cache

使用自定义缓存位置

translator-ai en.json -l ko -o ko.json --cache-file /path/to/cache.json

使用 Ollama 进行本地翻译

# 使用 Ollama 的基本用法
translator-ai en.json -l es -o es.json --provider ollama

# 使用不同的 Ollama 模型
translator-ai en.json -l fr -o fr.json --provider ollama --ollama-model llama2:latest

# 连接到远程 Ollama 实例
translator-ai en.json -l de -o de.json --provider ollama --ollama-url http://192.168.1.100:11434

# 检查可用的提供商
translator-ai --list-providers

高级特性

# 自动检测源语言
translator-ai content.json -l es -o spanish.json --detect-source

# 一次翻译到多种语言
translator-ai en.json -l es,fr,de,ja -o translations/{lang}.json

# 试运行 - 查看翻译内容而不进行实际的 API 调用
translator-ai en.json -l es -o es.json --dry-run

# 保持格式(URL、电子邮件、日期、数字、模板变量)
translator-ai app.json -l fr -o app-fr.json --preserve-formats

# 包含翻译元数据(默认禁用以确保兼容性)
translator-ai en.json -l fr -o fr.json --metadata

# 按字母顺序对键进行排序以确保输出一致
translator-ai en.json -l fr -o fr.json --sort-keys

# 验证所有键是否存在于翻译中
translator-ai en.json -l fr -o fr.json --check-keys

# 使用不同的 Gemini 模型
translator-ai en.json -l es -o es.json --gemini-model gemini-2.5-flash

# 组合特性
translator-ai src/**/*.json -l es,fr,de -o "{dir}/{name}.{lang}.json" \
  --detect-source --preserve-formats --stats --check-keys

可用的 Gemini 模型

--gemini-model 选项允许你选择不同的 Gemini 模型。常用选项包括:

  • gemini-2.0-flash-lite(默认) - 适用于大多数翻译,快速高效。
  • gemini-2.5-flash - 性能增强,具备新功能。
  • gemini-pro - 对复杂翻译有更深入的理解。
  • gemini-1.5-pro - 上一代专业模型。
  • gemini-1.5-flash - 上一代快速模型。

示例用法:

# 使用最新的快速模型
translator-ai en.json -l es -o es.json --gemini-model gemini-2.5-flash

# 使用默认的轻量级模型
translator-ai en.json -l fr -o fr.json --gemini-model gemini-2.0-flash-lite

可用的 OpenAI 模型

--openai-model 选项允许你选择不同的 OpenAI 模型。常用选项包括:

  • gpt-4o-mini(默认) - 大多数翻译场景下性价比高且速度快。
  • gpt-4o - 功能最强大,理解能力先进。
  • gpt-4-turbo - 上一代旗舰模型。
  • gpt-3.5-turbo - 简单翻译场景下快速高效。

示例用法:

# 使用 OpenAI 默认模型
translator-ai en.json -l es -o es.json --provider openai

# 使用 GPT-4o 进行复杂翻译
translator-ai en.json -l ja -o ja.json --provider openai --openai-model gpt-4o

# 使用 GPT-3.5-turbo 进行快速简单的翻译
translator-ai en.json -l fr -o fr.json --provider openai --openai-model gpt-3.5-turbo

翻译元数据

当使用 --metadata 标志启用时,translator-ai 会添加元数据以帮助跟踪翻译:

{
  "_translator_metadata": {
    "工具": "translator-ai v1.1.0",
    "仓库": "https://github.com/DatanoiseTV/translator-ai",
    "提供商": "Google Gemini",
    "源语言": "英语",
    "目标语言": "fr",
    "时间戳": "2025-06-20T12:34:56.789Z",
    "总字符串数": 42,
    "源文件": "en.json"
  },
  "问候语": "Bonjour",
  "告别语": "Au revoir"
}

元数据默认禁用,以确保与 i18n 解析器兼容。使用 --metadata 启用。

键排序

使用 --sort-keys 标志按字母顺序对输出中的所有 JSON 键进行排序:

translator-ai en.json -l es -o es.json --sort-keys

这确保了翻译之间的一致排序,并使差异比较更清晰。键的排序规则如下:

  • 不区分大小写(a, B, c,而不是 B, a, c)
  • 递归处理所有嵌套对象
  • 数组保持元素顺序不变

键验证

使用 --check-keys 标志确保翻译完整性:

translator-ai en.json -l es -o es.json --check-keys

此功能:

  • 验证所有源键是否存在于翻译输出中
  • 报告任何缺失的键及其完整路径
  • 如果缺少任何键,则以错误代码 1 退出
  • 有助于捕获翻译 API 失败或格式问题
  • 检查时忽略元数据键

📚 详细文档

支持的语言代码

它应该支持任何标准化的语言代码。

工作原理

  1. 解析:将你的 JSON 结构读取并展平为路径。
  2. 去重:处理多个文件时,识别共享字符串。
  3. 缓存:检查缓存中是否有之前翻译过的字符串。
  4. 差异检测:识别需要翻译的新字符串或修改过的字符串。
  5. 批量处理:将唯一字符串分组为最佳批量大小,以提高 API 效率。
  6. 翻译:将批量发送到选定的提供商(Gemini API 或本地 Ollama)。
  7. 重建:用翻译结果重建精确的 JSON 结构。
  8. 缓存更新:用新的翻译更新缓存,以备将来使用。

多文件去重

翻译多个文件时,translator-ai 会自动:

  • 识别文件间的重复字符串。
  • 仅翻译每个唯一字符串一次。
  • 在所有文件中一致应用相同的翻译。
  • 节省大量 API 调用并确保一致性。

示例:如果 10 个文件共享 50% 的字符串,你可以节省约 50% 的 API 调用!

缓存管理

默认缓存位置

  • Windows%APPDATA%\translator-ai\translation-cache.json
  • macOS~/Library/Caches/translator-ai/translation-cache.json
  • Linux~/.cache/translator-ai/translation-cache.json

缓存文件按以下方式索引存储翻译:

  • 源文件路径
  • 目标语言
  • 源字符串的 SHA - 256 哈希

这确保了:

  • 修改过的字符串会重新翻译。
  • 移除的字符串会从缓存中清除。
  • 多个项目可以共享同一缓存而不会冲突。

提供商比较

Google Gemini

  • 优点:快速、准确,能有效处理大批量翻译。
  • 缺点:需要 API 密钥,有使用成本。
  • 可用模型
    • gemini-2.0-flash-lite(默认) - 最快、最具成本效益。
    • gemini-pro - 性能均衡。
    • gemini-1.5-pro - 具备高级功能。
    • gemini-1.5-flash - 速度快且质量良好。
  • 适用场景:生产环境、大型项目,对准确性要求高时。

Ollama(本地)

  • 优点:免费,本地运行,无 API 限制,注重隐私。
  • 缺点:速度较慢,需要本地资源,需下载模型。
  • 适用场景:开发环境、对隐私敏感的数据、注重成本的项目。

性能提示

  1. 使用缓存(默认启用)以最小化 API 调用。
  2. 在同一会话中批量处理多个文件,以利用热缓存。
  3. 使用 --stats 标志 监控性能和优化机会。
  4. 保持源文件一致 以最大化缓存命中率。
  5. 对于 Ollama:使用性能强大的计算机以获得更好的性能。

API 限制和成本

Gemini API

  • 使用 Gemini 2.0 Flash Lite 模型以实现最佳速度和成本。
  • 根据输入键的数量动态选择最佳批量大小。
  • 每次 API 调用最多批量处理 100 个字符串。
  • 查看 Google 的定价 了解当前费率。

Ollama

  • 无 API 成本 - 完全在你的硬件上运行。
  • 性能取决于你的机器性能。
  • 支持不同速度/质量权衡的各种模型。

使用模型上下文协议(MCP)

translator-ai 可以用作 MCP 服务器,允许像 Claude Desktop 这样的 AI 助手直接翻译文件。

MCP 配置

添加到你的 Claude Desktop 配置中: macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "translator-ai": {
      "command": "npx",
      "args": [
        "-y",
        "translator-ai-mcp"
      ],
      "env": {
        "GEMINI_API_KEY": "your-gemini-api-key-here"
        // 或者对于 Ollama:
        // "TRANSLATOR_PROVIDER": "ollama"
      }
    }
  }
}

MCP 使用示例

配置完成后,你可以要求 Claude 翻译文件:

用户:你能把我的英语本地化文件翻译成西班牙语吗?

Claude:我将使用 translator-ai 把你的英语本地化文件翻译成西班牙语。

<使用工具 name="translate_json">
{
  "输入文件": "locales/en.json",
  "目标语言": "es",
  "输出文件": "locales/es.json"
}
</使用工具>

翻译成功!文件已保存到 locales/es.json。

对于多个文件的去重翻译:

用户:把我 locales 文件夹中的所有英语 JSON 文件翻译成德语。

Claude:我将对所有英语 JSON 文件进行去重翻译并翻译成德语。

<使用工具 name="translate_multiple">
{
  "模式": "locales/en/*.json",
  "目标语言": "de",
  "输出模式": "locales/de/{name}.json",
  "显示统计信息": true
}
</使用工具>

翻译完成!处理了 5 个文件,去重节省了 23%。

MCP 可用工具

  1. translate_json:翻译单个 JSON 文件
    • inputFile:源文件路径
    • targetLanguage:目标语言代码
    • outputFile:输出文件路径
  2. translate_multiple:翻译多个文件并去重
    • pattern:文件模式或路径
    • targetLanguage:目标语言代码
    • outputPattern:输出模式,包含 {dir}、{name}、{lang} 变量
    • showStats:显示去重统计信息(可选)

与静态网站生成器集成

处理 YAML 文件(Hugo、Jekyll 等)

由于 translator-ai 处理 JSON 文件,你需要将 YAML 转换为 JSON 再转换回来。以下是一个实用的工作流程:

设置 YAML 转换工具
# 安装 yaml 转换工具
npm install -g js-yaml
# 或者
pip install pyyaml
Hugo 示例(YAML 转换)
  1. 创建翻译脚本 (translate-hugo.sh):
#!/bin/bash
# translate-hugo.sh - 翻译 Hugo YAML i18n 文件

# 翻译 YAML 文件的函数
translate_yaml() {
  local input_file=$1
  local lang=$2
  local output_file=$3
  
  echo "将 $input_file 翻译成 $lang..."
  
  # 将 YAML 转换为 JSON
  npx js-yaml $input_file > temp_input.json
  
  # 翻译 JSON
  translator-ai temp_input.json -l $lang -o temp_output.json
  
  # 转换回 YAML
  npx js-yaml temp_output.json > $output_file
  
  # 清理临时文件
  rm temp_input.json temp_output.json
}

# 翻译 Hugo i18n 文件
translate_yaml themes/your-theme/i18n/en.yaml es themes/your-theme/i18n/es.yaml
translate_yaml themes/your-theme/i18n/en.yaml fr themes/your-theme/i18n/fr.yaml
translate_yaml themes/your-theme/i18n/en.yaml de themes/your-theme/i18n/de.yaml
  1. 基于 Python 的转换器 用于更复杂的场景:
#!/usr/bin/env python3
# hugo-translate.py

import yaml
import json
import subprocess
import sys
import os

def yaml_to_json(yaml_file):
    """将 YAML 转换为 JSON"""
    with open(yaml_file, 'r', encoding='utf-8') as f:
        data = yaml.safe_load(f)
    return json.dumps(data, ensure_ascii=False, indent=2)

def json_to_yaml(json_str):
    """将 JSON 转换回 YAML"""
    data = json.loads(json_str)
    return yaml.dump(data, allow_unicode=True, default_flow_style=False)

def translate_yaml_file(input_yaml, target_lang, output_yaml):
    """使用 translator-ai 翻译 YAML 文件"""
    
    # 创建临时 JSON 文件
    temp_json_in = 'temp_in.json'
    temp_json_out = f'temp_out_{target_lang}.json'
    
    try:
        # 将 YAML 转换为 JSON
        json_content = yaml_to_json(input_yaml)
        with open(temp_json_in, 'w', encoding='utf-8') as f:
            f.write(json_content)
        
        # 运行 translator-ai
        cmd = [
            'translator-ai',
            temp_json_in,
            '-l', target_lang,
            '-o', temp_json_out
        ]
        subprocess.run(cmd, check=True)
        
        # 读取翻译后的 JSON 并转换回 YAML
        with open(temp_json_out, 'r', encoding='utf-8') as f:
            translated_json = f.read()
        
        yaml_content = json_to_yaml(translated_json)
        
        # 写入翻译后的 YAML 输出
        with open(output_yaml, 'w', encoding='utf-8') as f:
            f.write(yaml_content)
        
        print(f"✓ 已将 {input_yaml} 翻译成 {output_yaml}")
        
    finally:
        # 清理临时文件
        for f in [temp_json_in, temp_json_out]:
            if os.path.exists(f):
                os.remove(f)

# 使用示例
if __name__ == "__main__":
    languages = ['es', 'fr', 'de', 'ja']
    
    for lang in languages:
        translate_yaml_file(
            'i18n/en.yaml',
            lang,
            f'i18n/{lang}.yaml'
        )
Node.js 解决方案(正确处理 YAML)

创建 translate-yaml.js

#!/usr/bin/env node
const fs = require('fs');
const yaml = require('js-yaml');
const { execSync } = require('child_process');
const path = require('path');

function translateYamlFile(inputPath, targetLang, outputPath) {
  console.log(`将 ${inputPath} 翻译成 ${targetLang}...`);
  
  // 读取并解析 YAML
  const yamlContent = fs.readFileSync(inputPath, 'utf8');
  const data = yaml.load(yamlContent);
  
  // 写入临时 JSON
  const tempJsonIn = `temp_${path.basename(inputPath)}.json`;
  const tempJsonOut = `temp_${path.basename(inputPath)}_${targetLang}.json`;
  
  fs.writeFileSync(tempJsonIn, JSON.stringify(data, null, 2));
  
  try {
    // 使用 translator-ai 进行翻译
    execSync(`translator-ai ${tempJsonIn} -l ${targetLang} -o ${tempJsonOut}`);
    
    // 读取翻译后的 JSON
    const translatedData = JSON.parse(fs.readFileSync(tempJsonOut, 'utf8'));
    
    // 转换回 YAML
    const translatedYaml = yaml.dump(translatedData, {
      indent: 2,
      lineWidth: -1,
      noRefs: true
    });
    
    // 写入输出 YAML
    fs.writeFileSync(outputPath, translatedYaml);
    console.log(`✓ 已创建 ${outputPath}`);
    
  } finally {
    // 清理临时文件
    [tempJsonIn, tempJsonOut].forEach(f => {
      if (fs.existsSync(f)) fs.unlinkSync(f);
    });
  }
}

// 示例用法
const languages = ['es', 'fr', 'de'];
languages.forEach(lang => {
  translateYamlFile(
    'i18n/en.yaml',
    lang,
    `i18n/${lang}.yaml`
  );
});

实际的 Hugo 工作流程

Hugo 支持两种翻译方法:按文件名 (about.en.md, about.fr.md) 或按内容目录 (content/en/, content/fr/)。以下是如何自动化这两种方法:

方法 1:按文件名翻译

创建 hugo-translate-files.sh

#!/bin/bash
# 使用文件名约定翻译 Hugo 内容文件

SOURCE_LANG="en"
TARGET_LANGS=("es" "fr" "de" "ja")

# 查找所有英语内容文件
find content -name "*.${SOURCE_LANG}.md" | while read -r file; do
  # 提取不带语言后缀的基本文件名
  base_name="${file%.${SOURCE_LANG}.md}"
  
  for lang in "${TARGET_LANGS[@]}"; do
    output_file="${base_name}.${lang}.md"
    
    # 如果翻译文件已存在则跳过
    if [ -f "$output_file" ]; then
      echo "跳过 $output_file(已存在)"
      continue
    fi
    
    # 提取前置元数据
    awk '/^---$/{p=1; next} p&&/^---$/{exit} p' "$file" > temp_frontmatter.yaml
    
    # 将前置元数据转换为 JSON
    npx js-yaml temp_frontmatter.yaml > temp_frontmatter.json
    
    # 翻译前置元数据
    translator-ai temp_frontmatter.json -l "$lang" -o "temp_translated.json"
    
    # 转换回 YAML
    echo "---" > "$output_file"
    npx js-yaml temp_translated.json >> "$output_file"
    echo "---" >> "$output_file"
    
    # 复制内容(你可能也想翻译这部分)
    awk '/^---$/{p++} p==2{print}' "$file" | tail -n +2 >> "$output_file"
    
    echo "已创建 $output_file"
  done
  
  # 清理临时文件
  rm -f temp_frontmatter.yaml temp_frontmatter.json temp_translated.json
done
方法 2:按内容目录翻译
  1. 设置 Hugo 配置 (config.yaml):
defaultContentLanguage: en
defaultContentLanguageInSubdir: false

languages:
  en:
    contentDir: content/en
    languageName: English
    weight: 1
  es:
    contentDir: content/es
    languageName: Español
    weight: 2
  fr:
    contentDir: content/fr
    languageName: Français
    weight: 3

# 其余配置...
  1. 创建翻译脚本 (hugo-translate-dirs.js):
#!/usr/bin/env node
const fs = require('fs-extra');
const path = require('path');
const yaml = require('js-yaml');
const { execSync } = require('child_process');
const glob = require('glob');

const SOURCE_LANG = 'en';
const TARGET_LANGS = ['es', 'fr', 'de'];

async function translateHugoContent() {
  // 确保目标目录存在
  for (const lang of TARGET_LANGS) {
    await fs.ensureDir(`content/${lang}`);
  }
  
  // 查找源语言中的所有内容文件
  const files = glob.sync(`content/${SOURCE_LANG}/**/*.md`);
  
  for (const file of files) {
    const relativePath = path.relative(`content/${SOURCE_LANG}`, file);
    
    for (const lang of TARGET_LANGS) {
      const targetFile = path.join(`content/${lang}`, relativePath);
      
      // 如果已经翻译则跳过
      if (await fs.pathExists(targetFile)) {
        console.log(`跳过 ${targetFile}(已存在)`);
        continue;
      }
      
      await translateFile(file, targetFile, lang);
    }
  }
}

async function translateFile(sourceFile, targetFile, targetLang) {
  console.log(`将 ${sourceFile} 翻译成 ${targetLang}...`);
  
  const content = await fs.readFile(sourceFile, 'utf8');
  const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  
  if (!frontMatterMatch) {
    // 没有前置元数据,直接复制
    await fs.ensureDir(path.dirname(targetFile));
    await fs.copyFile(sourceFile, targetFile);
    return;
  }
  
  // 解析前置元数据
  const frontMatter = yaml.load(frontMatterMatch[1]);
  const body = content.substring(frontMatterMatch[0].length);
  
  // 提取可翻译字段
  const translatable = {
    title: frontMatter.title || '',
    description: frontMatter.description || '',
    summary: frontMatter.summary || '',
    keywords: frontMatter.keywords || []
  };
  
  // 保存为 JSON 进行翻译
  await fs.writeJson('temp_meta.json', translatable);
  
  // 翻译
  execSync(`translator-ai temp_meta.json -l ${targetLang} -o temp_translated.json`);
  
  // 读取翻译结果
  const translated = await fs.readJson('temp_translated.json');
  
  // 更新前置元数据
  Object.assign(frontMatter, translated);
  
  // 写入翻译后的文件
  await fs.ensureDir(path.dirname(targetFile));
  const newContent = `---\n${yaml.dump(frontMatter)}---${body}`;
  await fs.writeFile(targetFile, newContent);
  
  // 清理临时文件
  await fs.remove('temp_meta.json');
  await fs.remove('temp_translated.json');
  
  console.log(`✓ 已创建 ${targetFile}`);
}

// 运行翻译
translateHugoContent().catch(console.error);

Hugo i18n 文件翻译

  1. 安装依赖
npm install -g translator-ai js-yaml
  1. 创建 Makefile 以便轻松翻译:
# Makefile for Hugo translations
LANGUAGES := es fr de ja zh
SOURCE_YAML := i18n/en.yaml
THEME_DIR := themes/your-theme

.PHONY: translate
translate: $(foreach lang,$(LANGUAGES),translate-$(lang))

translate-%:
	@echo "正在翻译到 $*..."
	@npx js-yaml $(SOURCE_YAML) > temp.json
	@translator-ai temp.json -l $* -o temp_$*.json
	@npx js-yaml temp_$*.json > i18n/$*.yaml
	@rm temp.json temp_$*.json
	@echo "✓ 已创建 i18n/$*.yaml"

.PHONY: translate-theme
translate-theme:
	@for lang in $(LANGUAGES); do \
		make translate-theme-$$lang; \
	done

translate-theme-%:
	@echo "正在将主题翻译到 $*..."
	@npx js-yaml $(THEME_DIR)/i18n/en.yaml > temp_theme.json
	@translator-ai temp_theme.json -l $* -o temp_theme_$*.json
	@npx js-yaml temp_theme_$*.json > $(THEME_DIR)/i18n/$*.yaml
	@rm temp_theme.json temp_theme_$*.json

.PHONY: clean
clean:
	@rm -f temp*.json

# 翻译所有内容
.PHONY: all
all: translate translate-theme

用法:

# 翻译到所有语言
make all

# 翻译到特定语言
make translate-es

# 翻译主题文件
make translate-theme

完整的 Hugo 翻译工作流程

以下是一个全面的脚本,处理内容和 i18n 翻译:

#!/usr/bin/env node
// hugo-complete-translator.js
const fs = require('fs-extra');
const path = require('path');
const yaml = require('js-yaml');
const { execSync } = require('child_process');
const glob = require('glob');

class HugoTranslator {
  constructor(targetLanguages = ['es', 'fr', 'de']) {
    this.targetLanguages = targetLanguages;
    this.tempFiles = [];
  }

  async translateSite() {
    console.log('开始翻译 Hugo 站点...\n');
    
    // 1. 翻译 i18n 文件
    await this.translateI18nFiles();
    
    // 2. 翻译内容
    await this.translateContent();
    
    // 3. 更新配置
    await this.updateConfig();
    
    console.log('\n翻译完成!');
  }

  async translateI18nFiles() {
    console.log('正在翻译 i18n 文件...');
    const i18nFiles = glob.sync('i18n/en.{yaml,yml,toml}');
    
    for (const file of i18nFiles) {
      const ext = path.extname(file);
      
      for (const lang of this.targetLanguages) {
        const outputFile = `i18n/${lang}${ext}`;
        
        if (await fs.pathExists(outputFile)) {
          console.log(`  跳过 ${outputFile}(已存在)`);
          continue;
        }
        
        // 转换为 JSON
        const tempJson = `temp_i18n_${lang}.json`;
        await this.convertToJson(file, tempJson);
        
        // 翻译
        const translatedJson = `temp_i18n_${lang}_translated.json`;
        execSync(`translator-ai ${tempJson} -l ${lang} -o ${translatedJson}`);
        
        // 转换回
        await this.convertFromJson(translatedJson, outputFile, ext);
        
        // 清理
        await fs.remove(tempJson);
        await fs.remove(translatedJson);
        
        console.log(`  ✓ 已创建 ${outputFile}`);
      }
    }
  }

  async translateContent() {
    console.log('\n正在翻译内容...');
    
    // 检测翻译方法
    const useContentDirs = await fs.pathExists('content/en');
    
    if (useContentDirs) {
      await this.translateContentByDirectory();
    } else {
      await this.translateContentByFilename();
    }
  }

  async translateContentByDirectory() {
    const files = glob.sync('content/en/**/*.md');
    
    for (const file of files) {
      const relativePath = path.relative('content/en', file);
      
      for (const lang of this.targetLanguages) {
        const targetFile = path.join('content', lang, relativePath);
        
        if (await fs.pathExists(targetFile)) continue;
        
        await this.translateMarkdownFile(file, targetFile, lang);
      }
    }
  }

  async translateContentByFilename() {
    const files = glob.sync('content/**/*.en.md');
    
    for (const file of files) {
      const baseName = file.replace('.en.md', '');
      
      for (const lang of this.targetLanguages) {
        const targetFile = `${baseName}.${lang}.md`;
        
        if (await fs.pathExists(targetFile)) continue;
        
        await this.translateMarkdownFile(file, targetFile, lang);
      }
    }
  }

  async translateMarkdownFile(sourceFile, targetFile, targetLang) {
    const content = await fs.readFile(sourceFile, 'utf8');
    const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
    
    if (!frontMatterMatch) {
      await fs.copy(sourceFile, targetFile);
      return;
    }
    
    const frontMatter = yaml.load(frontMatterMatch[1]);
    const body = content.substring(frontMatterMatch[0].length);
    
    // 翻译前置元数据
    const translatable = this.extractTranslatableFields(frontMatter);
    const tempJson = `temp_content_${path.basename(sourceFile)}.json`;
    const translatedJson = `${tempJson}.translated`;
    
    await fs.writeJson(tempJson, translatable);
    execSync(`translator-ai ${tempJson} -l ${targetLang} -o ${translatedJson}`);
    
    const translated = await fs.readJson(translatedJson);
    Object.assign(frontMatter, translated);
    
    // 写入翻译后的文件
    await fs.ensureDir(path.dirname(targetFile));
    const newContent = `---\n${yaml.dump(frontMatter)}---${body}`;
    await fs.writeFile(targetFile, newContent);
    
    // 清理
    await fs.remove(tempJson);
    await fs.remove(translatedJson);
    
    console.log(`  ✓ ${targetFile}`);
  }

  extractTranslatableFields(frontMatter) {
    const fields = ['title', 'description', 'summary', 'keywords', 'tags'];
    const translatable = {};
    
    fields.forEach(field => {
      if (frontMatter[field]) {
        translatable[field] = frontMatter[field];
      }
    });
    
    return translatable;
  }

  async convertToJson(inputFile, outputFile) {
    const ext = path.extname(inputFile);
    const content = await fs.readFile(inputFile, 'utf8');
    let data;
    
    if (ext === '.yaml' || ext === '.yml') {
      data = yaml.load(content);
    } else if (ext === '.toml') {
      // 这里需要一个 TOML 解析器
      throw new Error('本示例未实现 TOML 支持');
    }
    
    await fs.writeJson(outputFile, data, { spaces: 2 });
  }

  async convertFromJson(inputFile, outputFile, format) {
    const data = await fs.readJson(inputFile);
    let content;
    
    if (format === '.yaml' || format === '.yml') {
      content = yaml.dump(data, { 
        indent: 2, 
        lineWidth: -1,
        noRefs: true 
      });
    } else if (format === '.toml') {
      throw new Error('本示例未实现 TOML 支持');
    }
    
    await fs.writeFile(outputFile, content);
  }

  async updateConfig() {
    console.log('\n正在更新 Hugo 配置...');
    
    const configFile = glob.sync('config.{yaml,yml,toml,json}')[0];
    if (!configFile) return;
    
    // 这是一个简化的示例 - 你需要正确解析和更新
    console.log('  ! 记得在你的 config.yaml 中更新语言设置');
  }
}

// 运行翻译器
if (require.main === module) {
  const translator = new HugoTranslator(['es', 'fr', 'de']);
  translator.translateSite().catch(console.error);
}

module.exports = HugoTranslator;

在 Hugo 模块中使用

如果你使用 Hugo 模块,你可以创建一个翻译模块:

// go.mod
module github.com/yourusername/hugo-translator

go 1.19

require (
    github.com/yourusername/your-theme v1.0.0
)

然后在你的 package.json 中:

{
  "scripts": {
    "translate": "node hugo-complete-translator.js",
    "translate:content": "node hugo-complete-translator.js --content-only",
    "translate:i18n": "node hugo-complete-translator.js --i18n-only",
    "build": "npm run translate && hugo"
  }
}

Jekyll(带 YAML 前置元数据)

对于带有 YAML 前置元数据的 Jekyll 文章:

#!/usr/bin/env python3
# translate-jekyll-posts.py

import os
import yaml
import json
import subprocess
import frontmatter

def translate_jekyll_post(post_path, target_lang, output_dir):
    """翻译 Jekyll 文章,包括前置元数据"""
    
    # 加载带有前置元数据的文章
    post = frontmatter.load(post_path)
    
    # 提取可翻译的前置元数据字段
    translatable = {
        'title': post.metadata.get('title', ''),
        'description': post.metadata.get('description', ''),
        'excerpt': post.metadata.get('excerpt', '')
    }
    
    # 保存为 JSON 进行翻译
    with open('temp_meta.json', 'w', encoding='utf-8') as f:
        json.dump(translatable, f, ensure_ascii=False, indent=2)
    
    # 翻译
    subprocess.run([
        'translator-ai',
        'temp_meta.json',
        '-l', target_lang,
        '-o', f'temp_meta_{target_lang}.json'
    ])
    
    # 加载翻译结果
    with open(f'temp_meta_{target_lang}.json', 'r', encoding='utf-8') as f:
        translations = json.load(f)
    
    # 更新文章元数据
    for key, value in translations.items():
        if value:  # 仅在有翻译结果时更新
            post.metadata[key] = value
    
    # 在元数据中添加语言信息
    post.metadata['lang'] = target_lang
    
    # 保存翻译后的文章
    output_path = os.path.join(output_dir, os.path.basename(post_path))
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(frontmatter.dumps(post))
    
    # 清理临时文件
    os.remove('temp_meta.json')
    os.remove(f'temp_meta_{target_lang}.json')

# 翻译所有文章
for lang in ['es', 'fr', 'de']:
    os.makedirs(f'_posts/{lang}', exist_ok=True)
    for post in os.listdir('_posts/en'):
        if post.endswith('.md'):
            translate_jekyll_post(
                f'_posts/en/{post}',
                lang,
                f'_posts/{lang}'
            )

YAML/JSON 转换提示

  1. 保持格式:使用 js-yaml 并设置适当的选项以保持 YAML 结构。
  2. 处理特殊字符:确保整个过程中使用正确的编码(UTF - 8)。
  3. 验证输出:某些 YAML 特性(锚点、别名)可能需要特殊处理。
  4. 考虑 TOML:对于 Hugo,你可能还需要处理 TOML 配置文件。

替代方案:直接支持 YAML(功能请求)

如果你经常处理 YAML 文件,可以考虑创建一个包装脚本自动处理转换,或者请求 translator-ai 添加对 YAML 的支持。

🔧 技术细节

工作原理

  1. 解析:读取并将 JSON 结构展平为路径。
  2. 去重:处理多个文件时,识别共享字符串。
  3. 缓存:检查缓存中是否有之前翻译过的字符串。
  4. 差异检测:识别需要翻译的新字符串或修改过的字符串。
  5. 批量处理:将唯一字符串分组为最佳批量大小,以提高 API 效率。
  6. 翻译:将批量发送到选定的提供商(Gemini API 或本地 Ollama)。
  7. 重建:用翻译结果重建精确的 JSON 结构。
  8. 缓存更新:用新的翻译更新缓存,以备将来使用。

多文件去重

翻译多个文件时,translator-ai 会自动:

  • 识别文件间的重复字符串。
  • 仅翻译每个唯一字符串一次。
  • 在所有文件中一致应用相同的翻译。
  • 节省大量 API 调用并确保一致性。

示例:如果 10 个文件共享 50% 的字符串,你可以节省约 50% 的 API 调用!

缓存管理

默认缓存位置

  • Windows%APPDATA%\translator-ai\translation-cache.json
  • macOS~/Library/Caches/translator-ai/translation-cache.json
  • Linux~/.cache/translator-ai/translation-cache.json

缓存文件按以下方式索引存储翻译:

  • 源文件路径
  • 目标语言
  • 源字符串的 SHA - 256 哈希

这确保了:

  • 修改过的字符串会重新翻译。
  • 移除的字符串会从缓存中清除。
  • 多个项目可以共享同一缓存而不会冲突。

📄 许可证

本项目的商业和非商业使用都需要注明出处。详情请参阅 LICENSE 文件。

如果你发现这个工具有用,请考虑支持开发: Buy Me A Coffee

贡献

欢迎贡献代码!请随时提交拉取请求。

支持

如果有问题、疑问或建议,请在 GitHub 上创建一个问题。

help

Runtime guide

cloud

Hosted runtime

Hosted servers run from a provider-managed environment. You usually connect the MCP client to the hosted endpoint or follow the provider's authorization flow, without keeping a local process alive

  1. Open provider connection page
  2. Authorize or copy endpoint
  3. Connect from your MCP client
terminal

Local runtime / other methods

Local servers run on your own machine or infrastructure. You normally copy the server_config into your MCP client, install the required package, and provide env variables from env_schema when needed

  1. Copy server_config
  2. Install required package
  3. Fill env variables and restart client