场景/问题

在游戏开发过程中,需要本地化的文本(如UI、对话、物品描述)通常散落在代码文件、配置文件、Excel表格或XML/JSON数据文件中。手动查找和提取这些文本不仅效率低下,而且极易出错和遗漏,导致后续翻译流程受阻或产生“硬编码”文本问题。我们需要一种自动化的方法,能够根据项目特点,从源代码中精准地提取出待翻译的字符串。

思路

核心思路是使用正则表达式进行模式匹配,因为需要本地化的文本在代码中通常以特定的函数调用或格式出现(例如 Localization.Get("key")_("text") 或 TEXT("dialog"))。我们将编写一个脚本,遍历指定的项目目录,读取源代码文件,使用预定义的正则表达式模式匹配出所有目标字符串,并将其输出到一个结构化的文件中(如CSV或JSON),以供后续的翻译流程使用。

代码

#!/usr/bin/env python3
import os
import re
import csv
import argparse
from pathlib import Path

def extract_localized_text(project_root, output_file, pattern):
    """
    从项目目录中提取需要本地化的文本。

    Args:
        project_root (str): 项目根目录路径。
        output_file (str): 输出CSV文件路径。
        pattern (str): 用于匹配本地化文本的正则表达式模式。
    """
    extracted_data = []
    regex = re.compile(pattern)

    # 支持的文件扩展名
    code_extensions = {'.cs', '.cpp', '.h', '.java', '.py', '.lua', '.json', '.xml'}

    for root, dirs, files in os.walk(project_root):
        # 可选:跳过某些目录,如第三方库
        dirs[:] = [d for d in dirs if d not in ['Library', 'ThirdParty']]

        for file in files:
            if Path(file).suffix in code_extensions:
                file_path = os.path.join(root, file)
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        content = f.read()
                        matches = regex.findall(content)
                        for match in matches:
                            # 假设模式中第一个捕获组是文本内容
                            # 根据实际正则表达式调整
                            text = match if isinstance(match, str) else match[0]
                            extracted_data.append({
                                'source_file': file_path,
                                'text': text.strip('"\'')
                            })
                except (UnicodeDecodeError, IOError) as e:
                    print(f"警告:无法读取文件 {file_path}: {e}")

    # 写入CSV文件
    with open(output_file, 'w', newline='', encoding='utf-8-sig') as csvfile:
        fieldnames = ['source_file', 'text']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(extracted_data)

    print(f"提取完成!共找到 {len(extracted_data)} 条文本。结果已保存至:{output_file}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='提取游戏项目中的可本地化文本。')
    parser.add_argument('project_root', help='游戏项目根目录的路径')
    parser.add_argument('-o', '--output', default='extracted_texts.csv', help='输出CSV文件路径(默认:extracted_texts.csv)')
    parser.add_argument('-p', '--pattern', default=r'Localization\.Get\("([^"]+)"\)',
                        help='正则表达式模式,用于匹配代码中的本地化调用(默认匹配 C# 风格 Localization.Get("key"))')

    args = parser.parse_args()

    extract_localized_text(args.project_root, args.output, args.pattern)

解释

  1. 函数 extract_localized_text:这是脚本的核心函数。它接收项目根目录、输出文件路径和正则表达式模式作为输入。
  2. 遍历文件:使用 os.walk 递归遍历项目目录。通过 code_extensions 集合过滤出目标源代码文件类型,避免处理二进制文件。
  3. 模式匹配:使用 re.compile 编译传入的正则表达式模式,然后在每个文件的内容中查找所有匹配项 (regex.findall)。
    • 默认模式 r'Localization\.Get\("([^"]+)"\)':这是一个示例,匹配 C# 中类似 Localization.Get("PLAYER_NAME") 的调用,并捕获引号内的键(PLAYER_NAME)。
  4. 数据收集:将匹配到的文本(或捕获组)与它所在的源文件路径一起存储到一个字典列表中。
  5. 输出结果:将收集到的数据写入一个 CSV 文件,包含“源文件”和“文本”两列,方便后续导入 CAT 工具或翻译管理系统。
  6. 命令行接口:使用 argparse 库提供命令行参数,使脚本更灵活,可以指定不同的项目路径、输出文件和匹配模式。

边界条件

  • 编码问题:脚本假设源代码文件为 UTF-8 编码。对于其他编码(如 GBK、Shift-JIS)的项目,需要修改 open 函数的 encoding 参数或增加编码检测逻辑。
  • 复杂字符串:简单的正则表达式可能无法正确处理字符串拼接(如 "Hello" + playerName)、多行字符串或包含转义引号("He said, \"Hi\"")的文本。对于复杂情况,需要更精细的模式或使用语法分析器。
  • 误匹配:正则表达式可能匹配到注释中的字符串或非本地化用途的字符串。需要根据项目代码规范仔细设计模式,或进行后处理过滤。
  • 性能:对于超大型项目,遍历所有文件可能较慢。可以考虑增量提取或仅扫描特定模块。
  • 键值对提取:示例脚本提取的是“键”或直接文本。如果代码中直接写的是待翻译的原文(如 _("欢迎来到游戏世界!")),则需要调整模式以捕获原文本身。

可扩展方向

  1. 多模式支持:可以预定义多个正则表达式模式(如针对 T()NSLocalizedString 等不同函数),并在一个脚本中运行,合并结果。
  2. 上下文提取:除了文本本身,还可以尝试提取其周围的代码行作为上下文,帮助翻译人员理解用法。
  3. 增量提取与去重:记录已提取文本的哈希值或位置,仅提取新增或修改的文本,并与现有翻译记忆库对比去重。
  4. 集成到构建流程:将脚本作为 CI/CD 流水线(如 Jenkins, GitHub Actions)的一部分,在每次构建前自动运行,确保没有新的硬编码文本被引入。
  5. 输出格式多样化:除了 CSV,还可以支持直接输出为 XLIFF、JSON 或直接导入到在线本地化管理平台(如 Crowdin、Lokalise)的格式。
  6. GUI 工具:为不熟悉命令行的团队成员开发一个简单的图形界面,方便选择目录和配置模式。