什么是 MCP 服务器?
MCP 服务器是一种特殊的服务,可以让 AI 助手(如 Claude)直接翻译 JSON 文件。它通过特定的接口与 AI 模型通信,实现文件的自动翻译。如何使用 MCP 服务器?
使用 MCP 服务器需要先配置好 AI 提供商(如 Google Gemini 或 Ollama)。然后在 AI 助手(如 Claude)中调用这个服务,就可以直接翻译 JSON 文件了。适用场景
MCP 服务器非常适合需要频繁翻译多语言 JSON 文件的开发人员和团队。它可以节省大量手动翻译时间,提高工作效率。主要功能
优势与局限性
如何使用
使用案例
常见问题
相关资源
🚀 translator-ai
translator-ai 是一款快速高效的 JSON 国际化(i18n)翻译工具,支持多种 AI 翻译提供商,如 Google Gemini、OpenAI 以及 Ollama/DeepSeek。它具备智能缓存、多文件去重和 MCP 集成等功能,能帮助开发者更高效地完成翻译任务。
🚀 快速开始
在使用 translator-ai 之前,你需要进行安装和配置。安装完成后,你可以根据需求使用不同的命令进行翻译操作。
✨ 主要特性
- 多 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
# 包含翻译元数据(默认禁用,以确保与 i18n 解析器兼容)
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
- 上一代快速模型。
示例用法:
# 使用最新的 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",
"源语言": "英语",
"目标语言": "法语",
"时间戳": "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 失败或格式问题。
- 检查时忽略元数据键。
支持的语言代码
它应该支持任何标准化的语言代码。
📚 详细文档
工作原理
- 解析:将你的 JSON 结构读取并展平为路径。
- 去重:处理多个文件时,识别共享字符串。
- 缓存:检查缓存中是否有以前翻译过的字符串。
- 差异检测:识别需要翻译的新字符串或修改过的字符串。
- 批量处理:将唯一字符串分组为最佳批量大小,以提高 API 效率。
- 翻译:将批量数据发送到选定的提供商(Gemini API 或本地 Ollama)。
- 重建:使用翻译结果重建精确的 JSON 结构。
- 缓存更新:用新的翻译更新缓存,以备将来使用。
多文件去重
翻译多个文件时,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 限制、注重隐私。
- 缺点:速度较慢,需要本地资源,需要下载模型。
- 适用场景:开发环境、对隐私敏感的数据以及对成本敏感的项目。
性能提示
- 使用缓存(默认启用)以减少 API 调用。
- 在同一会话中批量处理多个文件,以利用热缓存。
- 使用
--stats
标志 监控性能并发现优化机会。 - 保持源文件一致,以最大化缓存命中率。
- 对于 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": {
"命令": "npx",
"参数": [
"-y",
"translator-ai-mcp"
],
"环境变量": {
"GEMINI_API_KEY": "your-gemini-api-key-here"
// 或者对于 Ollama:
// "TRANSLATOR_PROVIDER": "ollama"
}
}
}
}
MCP 使用示例
配置完成后,你可以要求 Claude 翻译文件:
用户:你能把我的英语本地化文件翻译成西班牙语吗?
Claude:我将使用 translator-ai 把你的英语本地化文件翻译成西班牙语。
<use_tool name="translate_json">
{
"输入文件": "locales/en.json",
"目标语言": "西班牙语",
"输出文件": "locales/es.json"
}
</use_tool>
翻译成功!文件已保存到 locales/es.json。
对于多个文件的去重翻译:
用户:把我 locales 文件夹中的所有英语 JSON 文件翻译成德语。
Claude:我将使用去重功能把你所有的英语 JSON 文件翻译成德语。
<use_tool name="translate_multiple">
{
"输入文件模式": "locales/en/*.json",
"目标语言": "德语",
"输出文件模式": "locales/de/{name}.json",
"显示统计信息": true
}
</use_tool>
翻译完成!处理了 5 个文件,节省了 23% 的 API 调用。
MCP 可用工具
- translate_json:翻译单个 JSON 文件
inputFile
:源文件路径targetLanguage
:目标语言代码outputFile
:输出文件路径
- 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 转换
- 创建翻译脚本 (
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
# 将 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
- 基于 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"
# 将翻译后的 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:按内容目录翻译
- 设置 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
# 其余配置...
- 创建翻译脚本 (
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 文件翻译
- 安装依赖:
npm install -g translator-ai js-yaml
- 创建 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 转换提示
- 保持格式:使用
js-yaml
并设置适当的选项以保持 YAML 结构。 - 处理特殊字符:确保整个过程中使用正确的编码(UTF-8)。
- 验证输出:某些 YAML 特性(锚点、别名)可能需要特殊处理。
- 考虑 TOML:对于 Hugo,你可能还需要处理 TOML 配置文件。
替代方案:直接支持 YAML(功能请求)
如果你经常处理 YAML 文件,可以考虑创建一个包装脚本,自动处理转换,或者请求 translator-ai 增加 YAML 支持功能。
📄 许可证
本项目无论商业或非商业使用都需要注明来源。详情请参阅 LICENSE 文件。
贡献
欢迎贡献代码!请随时提交拉取请求。
支持
如果有问题、疑问或建议,请在 GitHub 上创建一个问题。
如果你觉得这个工具有用,考虑支持开发:













