前言
在 DevOps 实践中,文档往往是被自动化流水线遗忘的一环。代码有 CI/CD,但文档翻译还靠手动上传下载。本文分享如何将 PDF 文档翻译集成到 Jenkins 流水线中,实现代码提交后自动翻译技术文档。
场景描述
团队维护一个多语言技术文档仓库,英文文档为源文件。每次更新英文文档后,需要同步生成中文、日文、西班牙文版本。手动操作耗时且容易遗漏。
目标:git push 后 Jenkins 自动触发翻译流水线,翻译完成后提交到对应语言的分支。
环境准备
- Jenkins 2.400+
- Python 3.10+(Jenkins agent 上安装)
- 依赖:pip install requests pyyaml
- Git plugin for Jenkins
- PDFTranslator API 访问权限
实现步骤
Step 1: 编写翻译脚本
#!/usr/bin/env python3
"""
doc_translator.py – CI/CD 流水线文档翻译脚本
读取配置文件,批量翻译指定目录下的PDF文档
"""
import os
import sys
import time
import json
import requests
from pathlib import Path
from typing import List, Dict
class DocTranslator:
"""PDF文档翻译器,适配CI/CD流水线"""
def __init__(self, config_path: str = "translate_config.yml"):
self.config = self._load_config(config_path)
self.api_base = "https://pdftranslator.org/api"
self.timeout = 300 # 单文件超时5分钟
def _load_config(self, path: str) –> Dict:
"""加载翻译配置"""
import yaml
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def translate_file(self, pdf_path: str, source_lang: str,
target_lang: str) –> str:
"""翻译单个PDF文件,返回下载URL
Args:
pdf_path: PDF文件路径
source_lang: 源语言(auto为自动检测)
target_lang: 目标语言代码
Returns:
翻译结果下载URL
Raises:
Exception: 翻译失败时抛出异常
"""
# Step 1: 上传文件
with open(pdf_path, "rb") as f:
resp = requests.post(
f"{self.api_base}/upload",
files={"file": f},
data={"source_lang": source_lang, "target_lang": target_lang},
timeout=60
)
resp.raise_for_status()
job_id = resp.json()["job_id"]
# Step 2: 轮询翻译状态
start_time = time.time()
while time.time() – start_time < self.timeout:
time.sleep(5)
status_resp = requests.get(
f"{self.api_base}/status/{job_id}",
timeout=30
)
status_resp.raise_for_status()
result = status_resp.json()
if result["status"] == "completed":
return result["download_url"]
elif result["status"] == "failed":
raise Exception(f"Translation failed: {result.get('error')}")
raise TimeoutError(f"Translation timeout for {pdf_path}")
def download_file(self, url: str, output_path: str):
"""下载翻译结果"""
resp = requests.get(url, timeout=120)
resp.raise_for_status()
with open(output_path, "wb") as f:
f.write(resp.content)
def run(self) –> Dict:
"""执行翻译流水线
Returns:
执行结果摘要
"""
source_dir = self.config["source_dir"]
target_langs = self.config["target_languages"]
output_base = self.config.get("output_dir", "./translated")
pdf_files = list(Path(source_dir).glob("**/*.pdf"))
if not pdf_files:
return {"status": "skipped", "reason": "No PDF files found"}
results = {"total": 0, "success": 0, "failed": 0, "details": []}
for lang in target_langs:
lang_dir = os.path.join(output_base, lang)
os.makedirs(lang_dir, exist_ok=True)
for pdf_file in pdf_files:
results["total"] += 1
rel_path = pdf_file.relative_to(source_dir)
output_path = os.path.join(lang_dir, str(rel_path))
os.makedirs(os.path.dirname(output_path), exist_ok=True)
try:
print(f"[INFO] Translating {rel_path} -> {lang}")
download_url = self.translate_file(
str(pdf_file), "auto", lang
)
self.download_file(download_url, output_path)
results["success"] += 1
results["details"].append({
"file": str(rel_path),
"lang": lang,
"status": "success"
})
print(f"[OK] {rel_path} -> {lang}")
except Exception as e:
results["failed"] += 1
results["details"].append({
"file": str(rel_path),
"lang": lang,
"status": "failed",
"error": str(e)
})
print(f"[FAIL] {rel_path} -> {lang}: {e}")
return results
if __name__ == "__main__":
translator = DocTranslator()
result = translator.run()
print(json.dumps(result, indent=2, ensure_ascii=False))
# CI/CD 退出码:有失败则返回1
if result["failed"] > 0:
sys.exit(1)
Step 2: 创建配置文件
# translate_config.yml
source_dir: "./docs/en" # 英文源文档目录
output_dir: "./docs" # 输出根目录
target_languages: # 目标语言列表
– "zh" # 中文
– "ja" # 日文
– "es" # 西班牙文
Step 3: 编写 Jenkinsfile
pipeline {
agent any
environment {
PYTHON_PATH = '/usr/bin/python3'
VENV_DIR = '.venv'
}
triggers {
// 监听 docs/ 目录的变更
pollSCM('H/5 * * * *')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Setup Python') {
steps {
sh '''
${PYTHON_PATH} -m venv ${VENV_DIR}
. ${VENV_DIR}/bin/activate
pip install requests pyyaml
'''
}
}
stage('Detect Changes') {
steps {
script {
// 检查 docs/en/ 目录是否有变更
def changes = sh(
script: "git diff –name-only HEAD~1 HEAD — docs/en/",
returnStdout: true
).trim()
if (!changes) {
echo "No document changes detected. Skipping."
currentBuild.result = 'NOT_BUILT'
return
}
echo "Changed files:\\n${changes}"
}
}
}
stage('Translate Documents') {
steps {
sh '''
. ${VENV_DIR}/bin/activate
python doc_translator.py
'''
}
}
stage('Commit Translations') {
steps {
sh '''
git config user.name "Jenkins CI"
git config user.email "jenkins@company.com"
git add docs/
git commit -m "Auto-translate documents [skip ci]" || echo "No changes to commit"
git push origin ${env.BRANCH_NAME}
'''
}
}
}
post {
success {
echo "Document translation pipeline completed successfully."
}
failure {
echo "Document translation pipeline failed!"
// 发送通知
emailext(
subject: "Translation Pipeline Failed: ${env.JOB_NAME}",
body: "Check: ${env.BUILD_URL}",
to: "${env.TEAM_EMAIL}"
)
}
}
}
Step 4: 配置 Jenkins Job
运行效果
Jenkins 构建日志示例:
[INFO] Translating api-guide.pdf -> zh
[OK] api-guide.pdf -> zh
[INFO] Translating api-guide.pdf -> ja
[OK] api-guide.pdf -> ja
[INFO] Translating api-guide.pdf -> es
[OK] api-guide.pdf -> es
[INFO] Translating architecture.pdf -> zh
[OK] architecture.pdf -> zh
…
{
"total": 9,
"success": 9,
"failed": 0,
"details": […]
}
3个文件 × 3种语言 = 9个翻译任务,总耗时约15分钟。翻译完成后自动提交到仓库。
优化建议
1. 并发翻译
在 doc_translator.py 中引入 ThreadPoolExecutor:
from concurrent.futures import ThreadPoolExecutor, as_completed
# 在 run() 方法中替换串行循环
with ThreadPoolExecutor(max_workers=3) as executor:
futures = []
for lang in target_langs:
for pdf_file in pdf_files:
future = executor.submit(self._translate_one, pdf_file, lang)
futures.append(future)
for future in as_completed(futures):
result = future.result()
# 处理结果
3线程并发可将9个任务的总耗时从15分钟压缩到5分钟。
2. 增量翻译
只翻译有变更的文件,避免重复翻译:
import hashlib
def get_file_hash(filepath: str) –> str:
"""计算文件MD5,用于判断是否需要重新翻译"""
with open(filepath, "rb") as f:
return hashlib.md5(f.read()).hexdigest()
# 在翻译前检查文件是否变更
hash_file = ".translation_hashes.json"
stored_hashes = json.load(open(hash_file)) if os.path.exists(hash_file) else {}
current_hash = get_file_hash(str(pdf_file))
key = f"{rel_path}_{lang}"
if stored_hashes.get(key) == current_hash:
print(f"[SKIP] {key} unchanged")
continue
3. 失败重试与通知
在 Jenkinsfile 中添加重试逻辑:
stage('Translate Documents') {
steps {
retry(3) {
sh '''
. ${VENV_DIR}/bin/activate
python doc_translator.py
'''
}
}
}
总结
将文档翻译集成到CI/CD流水线后,团队不再需要手动翻译文档。开发者更新英文文档后,Jenkins自动翻译并提交多语言版本,整个过程无需人工干预。
核心收益:
- 自动化:git push 触发,零手动操作
- 多语言:一次配置,同步生成多种语言版本
- 格式保留:PDFTranslator保留原文档排版,翻译后直接可用
- 可追溯:翻译记录在Git历史中,方便回溯
标签:CI/CD、Jenkins、自动化、PDF翻译、DevOps






