什麼是 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 上創建一個問題。
如果你覺得這個工具有用,考慮支持開發:













