场景/问题
在游戏开发过程中,需要本地化的文本(如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)解释
- 函数
extract_localized_text:这是脚本的核心函数。它接收项目根目录、输出文件路径和正则表达式模式作为输入。 - 遍历文件:使用
os.walk递归遍历项目目录。通过code_extensions集合过滤出目标源代码文件类型,避免处理二进制文件。 - 模式匹配:使用
re.compile编译传入的正则表达式模式,然后在每个文件的内容中查找所有匹配项 (regex.findall)。- 默认模式
r'Localization\.Get\("([^"]+)"\)':这是一个示例,匹配 C# 中类似Localization.Get("PLAYER_NAME")的调用,并捕获引号内的键(PLAYER_NAME)。
- 默认模式
- 数据收集:将匹配到的文本(或捕获组)与它所在的源文件路径一起存储到一个字典列表中。
- 输出结果:将收集到的数据写入一个 CSV 文件,包含“源文件”和“文本”两列,方便后续导入 CAT 工具或翻译管理系统。
- 命令行接口:使用
argparse库提供命令行参数,使脚本更灵活,可以指定不同的项目路径、输出文件和匹配模式。
边界条件
- 编码问题:脚本假设源代码文件为 UTF-8 编码。对于其他编码(如 GBK、Shift-JIS)的项目,需要修改
open函数的encoding参数或增加编码检测逻辑。 - 复杂字符串:简单的正则表达式可能无法正确处理字符串拼接(如
"Hello" + playerName)、多行字符串或包含转义引号("He said, \"Hi\"")的文本。对于复杂情况,需要更精细的模式或使用语法分析器。 - 误匹配:正则表达式可能匹配到注释中的字符串或非本地化用途的字符串。需要根据项目代码规范仔细设计模式,或进行后处理过滤。
- 性能:对于超大型项目,遍历所有文件可能较慢。可以考虑增量提取或仅扫描特定模块。
- 键值对提取:示例脚本提取的是“键”或直接文本。如果代码中直接写的是待翻译的原文(如
_("欢迎来到游戏世界!")),则需要调整模式以捕获原文本身。
可扩展方向
- 多模式支持:可以预定义多个正则表达式模式(如针对
T()、NSLocalizedString等不同函数),并在一个脚本中运行,合并结果。 - 上下文提取:除了文本本身,还可以尝试提取其周围的代码行作为上下文,帮助翻译人员理解用法。
- 增量提取与去重:记录已提取文本的哈希值或位置,仅提取新增或修改的文本,并与现有翻译记忆库对比去重。
- 集成到构建流程:将脚本作为 CI/CD 流水线(如 Jenkins, GitHub Actions)的一部分,在每次构建前自动运行,确保没有新的硬编码文本被引入。
- 输出格式多样化:除了 CSV,还可以支持直接输出为 XLIFF、JSON 或直接导入到在线本地化管理平台(如 Crowdin、Lokalise)的格式。
- GUI 工具:为不熟悉命令行的团队成员开发一个简单的图形界面,方便选择目录和配置模式。
评论区