欢迎光临
我们一直在努力

【Python实战】手把手教你基于Smartcat与Python制作完美格式的双语工单

文章目录

  • 【Python实战】手把手教你基于Smartcat与Python制作完美格式的双语工单
    • 一、 背景与痛点:为什么AI搞不定 Word 排版?
    • 二、 解决方案架构
      • 1. 为什么选择 Smartcat?
    • 三、 Python 实现逻辑:V6 版本核心功能
    • 四、 完整代码实现
      • Python 脚本 (`merge_v6.py`)
    • 五、 代码关键部分详解
      • 1. 为什么不用 `cell.text += …` ?
      • 2. 软回车技巧 (`\\n`)
      • 3. XML 指纹去重
    • 六、 总结

【Python实战】手把手教你基于Smartcat与Python制作完美格式的双语工单

摘要:部门面临国际化需求,急需将复杂的工单(Word格式)转换为中英文对照版本。尝试了市面上各种AI产品,发现它们无法完美保留Word中复杂的嵌套表格、红色警告等排版。本文介绍了一种“硬核”方案:利用 Smartcat 保证翻译与文档结构的稳定性,再利用 Python 进行像素级的双语合并,实现“中文原格式保留 + 英文Times New Roman字体 + 智能字号适配”。


一、 背景与痛点:为什么AI搞不定 Word 排版?

最近部门正在推进业务国际化,领导要求将现有的 航空维修工单(通常是格式极度严格的 Word 文档,包含大量嵌套表格、红色警告字体、签字栏等)全部升级为 中英文双语对照版。

起初我尝试了各种 AI 文档工具,结果令人崩溃:

  • 格式错乱:AI 翻译后,表格边框丢失、单元格合并失效。
  • 样式丢失:原文档中红色的“警告”二字,AI 生成后变成了普通的黑色字体,这在航空维修中属于严重安全隐患。
  • 排版丑陋:中英文挤在一起,字体字号混乱,无法阅读。
  • 结论:截止目前,还没有一款 AI 产品能实现“完美读取 Word 复杂格式 -> 翻译 -> 在原单元格内输出双语 -> 导出 Word”。

    于是,我设计了一套“专业分工”的自动化工作流。


    二、 解决方案架构

    既然无法一步到位,我们将任务拆解为两步:

    #mermaid-svg-E6EbDBP2i9OWnxFm{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-E6EbDBP2i9OWnxFm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-E6EbDBP2i9OWnxFm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-E6EbDBP2i9OWnxFm .error-icon{fill:#552222;}#mermaid-svg-E6EbDBP2i9OWnxFm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-E6EbDBP2i9OWnxFm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-E6EbDBP2i9OWnxFm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-E6EbDBP2i9OWnxFm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-E6EbDBP2i9OWnxFm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-E6EbDBP2i9OWnxFm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-E6EbDBP2i9OWnxFm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-E6EbDBP2i9OWnxFm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-E6EbDBP2i9OWnxFm .marker.cross{stroke:#333333;}#mermaid-svg-E6EbDBP2i9OWnxFm svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-E6EbDBP2i9OWnxFm p{margin:0;}#mermaid-svg-E6EbDBP2i9OWnxFm .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-E6EbDBP2i9OWnxFm .cluster-label text{fill:#333;}#mermaid-svg-E6EbDBP2i9OWnxFm .cluster-label span{color:#333;}#mermaid-svg-E6EbDBP2i9OWnxFm .cluster-label span p{background-color:transparent;}#mermaid-svg-E6EbDBP2i9OWnxFm .label text,#mermaid-svg-E6EbDBP2i9OWnxFm span{fill:#333;color:#333;}#mermaid-svg-E6EbDBP2i9OWnxFm .node rect,#mermaid-svg-E6EbDBP2i9OWnxFm .node circle,#mermaid-svg-E6EbDBP2i9OWnxFm .node ellipse,#mermaid-svg-E6EbDBP2i9OWnxFm .node polygon,#mermaid-svg-E6EbDBP2i9OWnxFm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-E6EbDBP2i9OWnxFm .rough-node .label text,#mermaid-svg-E6EbDBP2i9OWnxFm .node .label text,#mermaid-svg-E6EbDBP2i9OWnxFm .image-shape .label,#mermaid-svg-E6EbDBP2i9OWnxFm .icon-shape .label{text-anchor:middle;}#mermaid-svg-E6EbDBP2i9OWnxFm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-E6EbDBP2i9OWnxFm .rough-node .label,#mermaid-svg-E6EbDBP2i9OWnxFm .node .label,#mermaid-svg-E6EbDBP2i9OWnxFm .image-shape .label,#mermaid-svg-E6EbDBP2i9OWnxFm .icon-shape .label{text-align:center;}#mermaid-svg-E6EbDBP2i9OWnxFm .node.clickable{cursor:pointer;}#mermaid-svg-E6EbDBP2i9OWnxFm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-E6EbDBP2i9OWnxFm .arrowheadPath{fill:#333333;}#mermaid-svg-E6EbDBP2i9OWnxFm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-E6EbDBP2i9OWnxFm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-E6EbDBP2i9OWnxFm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-E6EbDBP2i9OWnxFm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-E6EbDBP2i9OWnxFm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-E6EbDBP2i9OWnxFm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-E6EbDBP2i9OWnxFm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-E6EbDBP2i9OWnxFm .cluster text{fill:#333;}#mermaid-svg-E6EbDBP2i9OWnxFm .cluster span{color:#333;}#mermaid-svg-E6EbDBP2i9OWnxFm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-E6EbDBP2i9OWnxFm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-E6EbDBP2i9OWnxFm rect.text{fill:none;stroke-width:0;}#mermaid-svg-E6EbDBP2i9OWnxFm .icon-shape,#mermaid-svg-E6EbDBP2i9OWnxFm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-E6EbDBP2i9OWnxFm .icon-shape p,#mermaid-svg-E6EbDBP2i9OWnxFm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-E6EbDBP2i9OWnxFm .icon-shape rect,#mermaid-svg-E6EbDBP2i9OWnxFm .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-E6EbDBP2i9OWnxFm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-E6EbDBP2i9OWnxFm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-E6EbDBP2i9OWnxFm :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    上传

    机器翻译+人工校对

    结构保持一致

    提供格式基准

    样式侦测与注入

    中文原稿 cn.docx

    Smartcat 翻译平台

    导出英文版 en.docx

    Python 自动化脚本

    双语对照版_V6.docx

    1. 为什么选择 Smartcat?

    Smartcat 是一款强大的云端翻译工具。它的核心优势在于对 Office 文档的解析能力:

    • 结构锁定:它能提取文档中的纯文本进行翻译,同时在后台严格锁定文档的 XML 结构标签。
    • 格式一致:从 Smartcat 导出的“英文版”文档,其表格数量、行数、单元格顺序与“中文原版”是完全一致的。
    • 术语库:航空术语(如 Check vs Inspect)容不得半点差错,Smartcat 可以挂载术语库保证准确性。

    操作流程:

  • 上传中文原稿 (cn.docx) 到 Smartcat。
  • 完成翻译。
  • 导出目标文件,重命名为 en.docx。
  • 此时,我们有了两个结构一模一样,但语言不同的文件。

  • 三、 Python 实现逻辑:V6 版本核心功能

    有了结构一致的“中文原版”和“英文译版”,接下来的任务就是把它们合并。为了达到出版级的效果,我编写了 V6 版本的脚本,实现了以下核心功能:

  • 🕵️‍♂️ 智能样式侦探 (analyze_style):
    • 脚本在处理每一个中文段落时,会像侦探一样分析:它是红色的吗?是加粗的吗?字号是多少?
  • 🎨 动态样式应用 (set_smart_style):
    • 字体统一:英文部分强制使用标准西文字体 Times New Roman。
    • 字号跟随:如果中文是大标题,英文自动变大;如果是小注脚,英文自动变小。
    • 颜色警示:如果中文是红色警告,英文也自动标红加粗;普通文字则显示为黑色。
  • 🔄 递归表格处理:
    • 支持表格套表格(嵌套表格),确保无死角覆盖。
  • ↩️ 无损追加:
    • 不动原有的中文,而是利用“软回车”,将英文优雅地追加在中文下方。

    四、 完整代码实现

    请确保本地已安装 python-docx 库:

    pip install python-docx

    Python 脚本 (merge_v6.py)

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-

    import os
    from docx import Document
    from docx.shared import Pt, RGBColor
    from docx.oxml.ns import qn

    # ================= 配置区 =================
    # 确保这两个文件在脚本同级目录下,且表格结构一致
    FILE_CN = 'cn.docx' # 中文原版 (Smartcat 上传前的原件)
    FILE_EN = 'en.docx' # 英文译版 (Smartcat 导出的纯译文件)
    FILE_OUT = '双语对照版_V6_TNR字体.docx' # 输出文件名

    # 字体设置
    ENGLISH_FONT_NAME = 'Times New Roman' # 国际通用英文衬线字体
    WARNING_COLOR = RGBColor(255, 0, 0) # 红色 (用于警告信息)
    NORMAL_COLOR = RGBColor(0, 0, 0) # 黑色 (用于普通正文)
    # =========================================

    def analyze_style(paragraph):
    """
    【核心功能 1:样式侦探】
    扫描中文段落,提取关键样式特征。

    参数: paragraph (docx段落对象)
    返回: (is_bold, is_red, ref_size)
    """
    is_bold = False
    is_red = False
    ref_size = None # 默认大小

    # 遍历段落里的所有 Run (文字块)
    for run in paragraph.runs:
    # 1. 检测加粗
    if run.bold:
    is_bold = True

    # 2. 检测红色 (RGB: 255,0,0)
    # 航空文档中红色通常代表警告,翻译后必须保留此特征
    if run.font.color and run.font.color.rgb == RGBColor(255, 0, 0):
    is_red = True

    # 3. 捕捉字号 (如果中文手动设置了字号,就抓取它)
    if run.font.size is not None:
    ref_size = run.font.size

    return is_bold, is_red, ref_size

    def set_smart_style(run, is_bold_context, is_red_context, ref_size):
    """
    【核心功能 2:样式应用师】
    根据侦探传回的线索,给英文设置正确的样式。
    规则:Times New Roman + 黑色(或红) + 字号跟随中文
    """

    # 1. 强制设置字体为 Times New Roman
    run.font.name = ENGLISH_FONT_NAME
    r = run._element
    # 这一步是为了确保 Word 将其识别为西文字体(w:eastAsia),防止回退到宋体
    r.rPr.rFonts.set(qn('w:eastAsia'), ENGLISH_FONT_NAME)

    # 2. 设置字号 (动态跟随中文)
    if ref_size is not None:
    run.font.size = ref_size
    else:
    # 如果中文没有独立字号(也就是跟随段落默认),英文也不设,让它自动继承
    pass

    # 3. 颜色与加粗逻辑
    if is_red_context:
    # 场景:警告/禁止事项 -> 英文也要醒目(红+粗)
    run.font.color.rgb = WARNING_COLOR
    run.bold = True
    else:
    # 场景:普通文本 -> 英文保持黑色,加粗状态跟随中文标题
    run.font.color.rgb = NORMAL_COLOR
    run.bold = is_bold_context

    def merge_text_lines(paras_cn, text_en_raw):
    """
    【核心功能 3:对齐合并】
    将英文文本按行拆分,并插空填入中文段落后。
    """

    # 将英文按换行符拆分,过滤空行
    lines_en = [line.strip() for line in text_en_raw.split('\\n') if line.strip()]

    if not lines_en:
    return 0

    count = 0
    # 按照段落数量进行循环 (防止索引越界)
    limit = min(len(paras_cn), len(lines_en))

    for i in range(limit):
    p_cn = paras_cn[i]
    line_en = lines_en[i]

    # 简单去重:如果中文格子里已经有了这段英文,就不重复添加
    if line_en in p_cn.text:
    continue

    # === A. 侦测中文样式 (侦探出动) ===
    is_bold, is_red, ref_size = analyze_style(p_cn)

    # === B. 写入英文 ===
    # 如果中文段落本身有字,则先换行(\\n);如果是空行,直接写
    # 使用 add_run 而不是 add_paragraph,是为了保持在同一个段落里,行距更紧凑
    prefix = '\\n' if p_cn.text.strip() else ''
    run = p_cn.add_run(prefix + line_en)

    # === C. 应用样式 (设计师出动) ===
    set_smart_style(run, is_bold, is_red, ref_size)

    count += 1

    # 处理溢出的行 (例如中文只有1行,英文翻译成了3行)
    # 将多余的英文全部追加到中文的最后一段后面
    if len(lines_en) > len(paras_cn) and len(paras_cn) > 0:
    last_p = paras_cn[1]
    is_bold, is_red, ref_size = analyze_style(last_p)

    for extra_line in lines_en[limit:]:
    if extra_line not in last_p.text:
    run = last_p.add_run('\\n' + extra_line)
    set_smart_style(run, is_bold, is_red, ref_size)
    count += 1
    return count

    def process_table_pair(tbl_cn, tbl_en, processed_cells):
    """
    【核心功能 4:递归表格处理器】
    支持处理表格嵌套(表格里的格子里还有表格)
    """

    # 确保不越界,取行数较小值
    row_limit = min(len(tbl_cn.rows), len(tbl_en.rows))

    for r in range(row_limit):
    row_cn = tbl_cn.rows[r]
    row_en = tbl_en.rows[r]

    # 确保列不越界
    cell_limit = min(len(row_cn.cells), len(row_en.cells))
    for c in range(cell_limit):
    cell_cn = row_cn.cells[c]
    cell_en = row_en.cells[c]

    # 使用 XML 元素 ID 进行去重,防止合并单元格被多次处理
    # 这是一个非常关键的技巧,否则大单元格会被重复写入多次
    if cell_cn._element in processed_cells: continue
    processed_cells.add(cell_cn._element)

    txt_cn = cell_cn.text.strip()
    txt_en = cell_en.text.strip()

    # 只有当英文有内容,且不等于中文时才合并
    if txt_en and txt_en != txt_cn:
    # 确保有段落对象
    if len(cell_cn.paragraphs) == 0: cell_cn.add_paragraph()
    # 执行合并
    merge_text_lines(cell_cn.paragraphs, txt_en)

    # 递归检查:如果单元格里还套着表格
    if len(cell_cn.tables) > 0 and len(cell_en.tables) > 0:
    nested_limit = min(len(cell_cn.tables), len(cell_en.tables))
    for n_i in range(nested_limit):
    process_table_pair(cell_cn.tables[n_i], cell_en.tables[n_i], processed_cells)

    def run_fix_v6():
    print(f"🚀 启动 V6.0 (TNR字体 + 动态字号)…\\n中文源: {FILE_CN}\\n英文源: {FILE_EN}")
    try:
    doc_cn = Document(FILE_CN)
    doc_en = Document(FILE_EN)
    except Exception as e:
    print(f"❌ 错误: {e}")
    return

    # 1. 处理正文 (表格之外的段落)
    paras_cn = doc_cn.paragraphs
    paras_en = doc_en.paragraphs
    p_cn_valid = [p for p in paras_cn if p.text.strip()]
    p_en_valid = [p for p in paras_en if p.text.strip()]

    limit_p = min(len(p_cn_valid), len(p_en_valid))
    for i in range(limit_p):
    merge_text_lines([p_cn_valid[i]], p_en_valid[i].text)

    # 2. 处理表格 (递归处理)
    tables_cn = doc_cn.tables
    tables_en = doc_en.tables
    processed_cells = set() # 记录处理过的单元格

    min_tables = min(len(tables_cn), len(tables_en))
    print(f"正在处理 {min_tables} 个表格…")

    for i in range(min_tables):
    process_table_pair(tables_cn[i], tables_en[i], processed_cells)

    print("-" * 30)
    print(f"✅ V6 处理完成!字体已统一为 Times New Roman,字号自适应。")
    doc_cn.save(FILE_OUT)
    print(f"📁 文件已保存为:{FILE_OUT}")

    if __name__ == '__main__':
    run_fix_v6()


    五、 代码关键部分详解

    1. 为什么不用 cell.text += … ?

    很多新手在操作 Word 时喜欢用 cell.text = cell.text + english。这是一种破坏性操作,它会清空单元格内原有的所有样式(加粗、颜色),导致排版崩坏。 **本代码采用 add_run**:它是在现有内容的屁股后面“追加”一个新的小尾巴。这样原有的中文格式完全不受影响。

    2. 软回车技巧 (\\n)

    我们在插入英文前,会判断 prefix = '\\n'。这相当于在 Word 里按下了 Shift+Enter(软回车)。

    • 好处:英文和中文属于同一个段落。
    • 效果:如果中文设置了“居中对齐”,追加的英文会自动继承居中;如果中文设置了“首行缩进”,英文也会缩进。这比创建新段落(add_paragraph)要紧凑、美观得多。

    3. XML 指纹去重

    在处理 Word 表格时,合并单元格是一个大坑。一个跨3行的单元格,在代码循环中会出现3次。如果不做处理,英文会被写入3次。 代码中使用了 if cell_cn._element in processed_cells: continue。 _element 是单元格在内存中的唯一 XML 地址。不管它跨了几行,只要地址一样,我们就只处理一次。


    六、 总结

    通过 Smartcat 解决翻译准确性与文档结构一致性的问题,再通过 Python 解决排版样式的自动化合并问题。这套方案不仅成本低(Smartcat 有免费版,Python 开源),而且产出的文档质量完全符合航空级标准。

    如果你也在做文档国际化,或者需要批量处理复杂的 Word 排版,不妨试试这个思路!

    赞(0)
    未经允许不得转载:171主机测评 » 【Python实战】手把手教你基于Smartcat与Python制作完美格式的双语工单
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址