DLL 字符串提取与导入:简化软件本地化流程
2024-12-20 23:32:03
DLL 文件字符串提取与导入
面对软件本地化工作时,从 DLL 文件中提取和导入字符串是关键环节。特别是在进行软件更新后,再次从DLL中导出文本,并与先前的文本比较工作,往往耗时耗力。本文介绍一些实用技巧。使用自动化脚本简化此流程。结合实例来更好地理解它们。
一、DLL 文件字符串资源提取方案
处理 DLL 文件的本地化问题时,主要需求是从中提取出可翻译的字符串。这些字符串可能存在于菜单、对话框等资源里。下面介绍几种方法:
方案1:利用 Resource Hacker 命令行提取字符串表
Resource Hacker 提供了一种通过命令行提取资源的简易方式。其基本思路是直接导出指定类型的资源。
-
提取命令 :
"C:\Program Files (x86)\Resource Hacker\ResourceHacker.exe" -open "待处理的.dll" -save "资源导出目录.rc" -action extract -mask STRINGTABLE,,
该命令可以将所有 STRINGTABLE 导出。其中
-mask
可以控制导出的资源种类,STRINGTABLE 即字符串表。这里也可以使用对话框或者菜单栏作为关键词,更换它导出需要的文件种类。但是,这些种类的名称不好记忆,使用 STRINGTABLE 是最快捷的方式。之后,需要自己删掉非文本数据即可。 -
操作步骤 :
- 打开命令行窗口。
- 执行命令,指定目标 DLL 文件路径、保存路径以及要提取的资源类型(这里是字符串表)。
- 检查输出文件,验证字符串是否被成功提取。
这个方法适用于提取大量资源字符串。但是也有明显的缺点:导出格式不够方便后续处理。比如,这个方案产生大量 .bin 文件。因此,还需要继续操作提取 bin 文件内的字符,或者删除非 STRINGTABLE 文件,或者想办法把非 STRINGTABLE 的资源数据从总 rc 文件里去除掉。这不适合新手,会带来很大的操作负担。所以我们还要探索另一种方法。
方案2: 使用 PowerShell 脚本和 ResGen.exe
工具提取特定字符串
这个方法依赖于 .NET Framework
的 ResGen.exe
。这要求使用者系统中必须装有开发工具 Visual Studio 或者对应版本的 Windows SDK。利用 PowerShell 调用 ResGen.exe
,可以将 RC 文件转换为易于处理的 .resx
或 .txt
文件。
-
使用 Resource Hacker 保存资源 :
运行 Resource Hacker,打开 DLL 文件,手动导出指定菜单,保存成 rc 格式,需要注意保存编码为 unicode 格式。
-
执行脚本 :
$rcFilePath = "目标文件.rc" $outputFilePath = "输出的字符串文件.txt" # 假定路径已经在系统变量内,或您记得具体位置。 $resGenPath = "ResGen.exe" # RC 文件先转为 RESX 格式 & $resGenPath $rcFilePath $outputFilePath.Replace(".txt", ".resx") # 再转为 TXT 格式。当然了,ResX 格式也是很方便继续处理的。只是我们要求是 txt,方便后面对比新旧版 & $resGenPath /str:csharp, $outputFilePath.Replace(".txt", ".resx"), $outputFilePath Write-Host "字符串已提取至 $($outputFilePath)"
-
脚本解析 :
- 定义变量存储输入文件和输出文件的路径。
- 执行
ResGen.exe
,将.rc
转换到.resx
文件,方便机器识别处理,然后再转为字符串文件。 - 输出字符串文件的路径。
该方法可以准确提取字符串,并且结果更方便使用脚本做差异比较。如果需要使用其他编码(默认是 utf-16 格式),需要自己处理格式转换问题。
注意:这些操作都具有侵入性,建议对原始 DLL 文件做好备份。不要忘记操作安全准则,始终优先处理副本而不是原始数据 。
二、字符串对比及翻译处理
提取出字符串后,接下来的步骤是将其与已有的翻译版本对比。进而确定需要更新的字符串。并调用翻译服务。
改进版对比脚本 :
针对之前的 python 对比脚本的逻辑优化与改进如下:
import csv
import subprocess
def read_resource_file(file_path):
"""读取资源文件并返回字典,键为 ID,值为字符串。"""
# 因为之前使用的方法对引号的处理问题可能造成解析错误。所以改用下面这段:
result = {}
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
i = 0
while i < len(lines):
line = lines[i].strip()
if line.startswith('BEGIN'):
# 遇到 "BEGIN",向下查找直到 "END" 的块
block_lines = []
i += 1
while i < len(lines) and not lines[i].strip().startswith('END'):
block_lines.append(lines[i].strip())
i += 1
# 在这里处理找到的块
for block_line in block_lines:
if len(block_line) < 2: # 遇到非标准格式内容
continue
if block_line[0] == '"' and block_line[-1] == '"':
id_, value = block_line.split(',', 1)
# 这一行解决了原脚本的报错问题:对那些带有双引号的数据也能处理了
result[id_.strip().strip('"')] = value.strip().strip('"')
i += 1
return result
def translate_text_with_bing(text, target_language):
"""调用必应翻译 API 进行翻译。需要配置好客户端环境。"""
# 此处使用示例命令,实际操作需根据具体 API 文档调整。
# 参考:https://learn.microsoft.com/zh-cn/azure/ai-services/translator/reference/v3-0-reference
# 要先确保安装了相应的开发环境,并获取认证参数
try:
result = subprocess.run([
'curl', '-X', 'POST',
'https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=' + target_language,
'-H', 'Ocp-Apim-Subscription-Key: <你的密钥>',
'-H', 'Ocp-Apim-Subscription-Region: <你的区域>',
'-H', 'Content-Type: application/json',
'-d', '[{"Text": "' + text + '"}]'
], capture_output=True, text=True, check=True)
# 解析返回的 JSON 数据,并获取翻译后的文本。
return result.stdout
except subprocess.CalledProcessError as e:
print(f"翻译失败: {e}")
return ""
def compare_and_update_strings(old_file, new_file, old_translated_file, target_language="zh"):
"""对比新旧版本的字符串,生成更新后的翻译文件。"""
old_strings = read_resource_file(old_file)
new_strings = read_resource_file(new_file)
old_translated_strings = read_resource_file(old_translated_file)
updated_content = ""
log_entries = []
with open(new_file, "r", encoding="utf-8") as f:
# 对原文件内的字符也同样进行操作
# 这样处理后,输出内容能保留原始 rc 文件的大部分信息,可以方便写回 rc
content = f.read()
for string_id, new_string in new_strings.items():
old_string = old_strings.get(string_id)
old_translation = old_translated_strings.get(string_id, "")
if old_string == new_string and old_translation:
# 旧版本存在且翻译内容存在则替换新字符串
content = content.replace('"' + new_string + '"', '"' + old_translation + '"')
updated_content += f'"{string_id}", "{old_translation}"\n'
else:
# 需要进行翻译的情况。旧字符串在新字符串文件中,需要重写此句。
if old_string is not None and old_string in content:
translated_text = translate_text_with_bing(new_string, target_language)
content = content.replace('"' + old_string + '"', '"' + translated_text + '"')
updated_content += f'"{string_id}", "{translated_text}"\n'
elif new_string in content:
translated_text = translate_text_with_bing(new_string, target_language)
content = content.replace('"' + new_string + '"', '"' + translated_text + '"')
updated_content += f'"{string_id}", "{translated_text}"\n'
log_entries.append([string_id, new_string, old_string, old_translation])
# 生成新的已翻译的字符串数据。
with open("updated_translation.txt", "w", encoding="utf-8") as f:
f.write(updated_content)
# 生成新的带有翻译后的完整资源。
with open("new_translated_file.rc", "w", encoding="utf-8") as f:
f.write(content)
# 生成差异比较日志
with open("translation_log.csv", "w", encoding="utf-8", newline="") as log_file:
csv_writer = csv.writer(log_file)
csv_writer.writerow(["ID", "新字符串", "旧字符串", "旧翻译"])
csv_writer.writerows(log_entries)
# 使用示例,直接修改成所需文件即可,不要把4个文件设置成相同的。
compare_and_update_strings("old_strings.txt", "new_strings.txt", "old_translated_strings.txt", "目标语言代码,如 zh-hans,日语 ja 等")
这个脚本做了如下优化:
- 读取并解析了新的文件格式。
- 调用 Bing 翻译 API 对比得到的新字符做初步翻译(如有需求)。当然了,要配置好自己的账号和区域才可以使用此功能。如果不用,请将调用翻译功能的几行删掉。
- 生成更新的翻译后字符串。
- 把字符串按格式重写了新文件内。保证了rc格式原样。
- 创建日志,详细记录每次处理的结果,是继续沿用,还是需要人工校对,都提供数据参考。
- 更详细的出错信息和安全操作,降低出 bug 的概率,让使用者自己排除代码故障。
- 改进了原脚本中的多处运行逻辑问题。
通过该脚本可以批量对比更新需要人工处理的内容,简化更新工作量。注意要根据机器翻译 API 的使用规则处理身份认证和请求频率。或者也可以改用其他已有的服务接口替换掉这里的。如果软件规模很庞大,一定要谨慎设置这些调用频率,防止账号受影响。