引擎指南FAQPatreonDiscord下载
登录
RuneTranslate · 端到端翻译日式游戏
引擎指南对比图片文字存档编辑器作弊模式FAQ下载PatreonDiscordYouTube隐私政策服务条款联系我们
全部文章
unreal · guides · engine · localization

如何翻译 Unreal Engine 游戏

2026年8月18日·9 分钟阅读

Unreal 游戏把文本装在 .pak 或 IoStore 容器里编译好的 .locres 表中。本文讲清如何读取它们,以及为何输出是一个极小的覆盖 pak,而不是重新打包整个游戏。

一款 Unreal Engine 游戏看上去是你硬盘上最难翻译的东西。没有 data 文件夹,没有脚本文件,没有你能打开的 .json —— 只有一个可执行文件、一个装着一两个巨大 .pak 文件的 Content/Paks 目录,也许还有一对 .utoc/.ucas 容器。好消息是,你真正想要的那些文本,通常并不是被刻意藏在那些文件里的。Unreal 有一套一等公民的本地化系统,而大多数已发行的游戏都在用它。你要做的是找到那张表、把字符串换掉,然后递给引擎一个优先级高于原件的小文件。

最后那一点,正是 Unreal 一旦你搞懂之后会变得轻松的原因。你不用重新打包一个 60 GB 的游戏。你产出的是一个以 KB 计的覆盖归档,把它丢在原件旁边就行。

进入步骤之前先说一件事:RuneTranslate 里的 Unreal 支持是尽力而为的,也比 RPG Maker 或 Ren'Py 这类引擎更新。它已在真实的 UE4 作品上确认可玩,但这个格式的面太大,而且每家工作室的打包方式都不一样。在投入一次长时间的翻译运行之前,先在游戏里验证一次导出的构建。

一款 Unreal 游戏实际发布了什么

Unreal 把面向玩家的文本放在 `.locres` 文件里 —— 也就是 FTextLocalizationResource 的编译形态。每种文化对应一个,摆在类似 <Game>/Content/Localization/Game/ja/Game.locres 的路径上,旁边还有 en、zh-Hans 以及工作室发行的其他文化。哪怕一款游戏根本没有任何翻译,通常也仍有一个,对应它被创作时用的那种文化。

.locres 不是文本文件,也不是一份简单的键值列表。它是一张二进制表,按命名空间组织,每个命名空间装着键,每个键指向一个字符串。在较新的版本里,命名空间和键存的是哈希(版本 2 用 CRC32,版本 3 用 UTF-16 上的 CityHash64),而不是可读的名字,字符串本身则住在一张共享、去重的查找表里,还带引用计数。运行时从不按英文文本去查一个字符串 —— 它按那个命名空间/键对来查,而那一对在构建时就被编译进了游戏的蓝图和 C++ 里。

这个结构决定了编辑它的唯一安全做法。RuneTranslate 把每一个命名空间哈希、键哈希和源字符串哈希逐字节原样保留,只改值,然后重建那张去重字符串表和它的引用计数。没有任何东西去重算哈希,所以也就不可能产生一个游戏找不到的键。译文比原文更长或更短都无所谓。

这些 .locres 文件随后被打进 .pak 归档 —— 靠文件最末尾一段页脚里的魔数 0x5A6F12E1 识别 —— 或者,在较新的 UE5 作品上,被打进 IoStore 的 .utoc/.ucas 容器。

花上一小时之前先确认范围

.locres 覆盖 UI、菜单、系统消息、教程文本、物品和技能名,以及字幕 —— 在一款典型游戏里,大致算得上可见文本的 90%。那正是 RuneTranslate 读取和写入的部分。

它没覆盖的是:编译进 DataTable、或烘焙进已烹调(cooked)`.uasset` 包里的文本。有些工作室 —— 尤其是在较小或高度依赖蓝图的项目上 —— 把对话放在那里,而不是放进本地化系统。那些字符串在已烹调的包里按哈希寻址,没有可读的目录索引,要正确读出来需要一份已发行游戏并不附带的 .usmap 映射文件。如果你的游戏把剧本放在那里,提取回来的就会是菜单和按钮、一句对话也没有,而 RuneTranslate 会把这个明确报告为超出支持范围的结果,而不是假装成功了。

早点知道会有用:最快的确认办法就是跑一遍创建项目这一步,看看出来了什么。几百条短字符串、看着全是 Continue、Options、Are you sure?,意味着 UI 做了本地化而剧本没有。好几千条、还包含完整句子,那你的处境不错。

AES 密钥,以及它为何不是一堵墙

已发行的 pak 里有很大一部分是 AES-256 加密的 —— 具体说是不带 IV 的 ECB 模式,作用在归档的索引上,往往也作用在单个条目上。没有密钥,归档就是不透明的:你连里面有什么都列不出来。

密钥能恢复出来,理由很直白。游戏必须在运行时读自己的归档,而且是在一台没有网络、没有授权服务器的机器上,所以密钥就作为普通数据存在于发行的二进制里。RuneTranslate 会扫描可执行文件及其 DLL 里的 32 字节窗口,用一道熵的门槛把它们筛掉(一把真正的 AES 密钥,32 字节里至少有 25 个互不相同),再做一次廉价的合理性检查 —— 解密索引的第一个块,看开头那个值读起来像不像一个说得通的字符串长度 —— 然后对幸存者做正经的验证:完整解密索引,把它的 SHA-1 和 pak 自家页脚里记着的哈希比对。最后这一步,正是它和外面那些按熵排序的找密钥工具的区别 —— 一个候选要么复现出归档自己记录的哈希,要么被拒绝。一把错的密钥永远不可能被接受。

有些游戏把密钥存成文本而不是原始字节,有些则把它拆散在二进制里的不同指令中;这两种情况都处理了。如果全都不奏效,新建项目对话框接受粘贴的十六进制密钥(0x… 或 64 个十六进制字符)或 base64,而且密钥一旦恢复就会按项目缓存,你不必每次导出都去扫描一个 85 MB 的可执行文件。

UE5 的 IoStore 不一样,提取是有针对性的

UE5 作品越来越多地发布 IoStore 容器而非普通 pak:一个 .utoc 目录配一个 .ucas 数据块,用 Oodle 压缩和它自己的一套加密。这里一种想当然的做法是把整个东西拆开,而在一款 105 GB 的游戏上,那根本算不上一种做法。

RuneTranslate 捆绑了 retoc(MIT 许可),让它把容器转换成传统 pak,并且只过滤出本地化文件 —— 等价于 to-legacy -f locres --no-shaders --no-script-objects。一款 105 GB 的 IoStore 作品会被过滤到几 KB 的 .locres,耗时以秒计而非以小时计。retoc 也处理容器自己的 AES 和 Oodle,这就是为什么 IoStore 游戏绕开了松散 pak 可能撞上的 Oodle 问题。

这件事重要的另一半是:IoStore 只让读取变复杂。写入是通用的,那正是下一节的内容。

输出是一个覆盖 pak,不是重新打包的游戏

这是没有哪份竞品指南讲清楚过的部分,也是 Unreal 一旦跑通、翻译起来就很便宜的原因。

Unreal 按一个确定的顺序挂载 pak 文件,而任何名字以 `_P` 结尾的归档都最后挂载。后挂载的赢。所以一个除了你改过的 .locres 之外什么都不装、并且放在基础游戏所用的同一虚拟路径上的小归档,就那么把原件盖住了 —— 本地化管理器加载的是你的那份,根本看不到随游戏发行的那份。

  • 不用重新签名。 pak 签名是一套完全独立的 RSA 机制,在已发行的消费级作品里很少强制启用;覆盖不碰它。
  • 输出不需要 AES 密钥。 覆盖归档写出来是不加密、不压缩的。密钥从头到尾只是读原件时才需要。
  • 不用重新打包游戏。 基础归档原封不动。一次 60 GB 的安装仍是 60 GB;你的翻译是一个以 KB 计的文件。
  • 对 IoStore 游戏一样有效。 .locres 是通过 pak 文件系统读取的,而非 Zen 包加载器,所以哪怕一款 UE5 游戏的资源全住在 .utoc/.ucas 里,一个普通的松散 _P.pak 照样会被拾取。

安装它的方法,就是把产出的 _P.pak 复制进 <Game>/Content/Paks/ —— 原件所在的同一个文件夹。有些游戏还会挂载一个 ~mods/ 子文件夹,效果一样好。撤销翻译就是删掉那一个文件,这让回滚变得毫不费力,也让早早就试一试变得安全。

步骤

  1. 从下载页面安装 RuneTranslate 并登录。首次启动需要一次免费的 Patreon 登录;免费档位解锁了每一种引擎和每一种翻译服务 —— 它限制的是吞吐量、同时只保留一个项目。
  2. 新建一个项目,把它指向游戏的根文件夹 —— 也就是含有可执行文件和 <Game> 目录的那个。检测会去找 Content/Paks 和 pak 的页脚魔数,或者 .utoc/.ucas。
  3. 如果归档是加密的,让密钥恢复跑起来。它会扫描发行的二进制,并拿 pak 自家的索引哈希校验每一个候选。如果什么也没找到,就在对话框里粘一把密钥进去。
  4. 让提取跑完。你会拿到源文化的 .locres 条目,按本地化目标分好组,可以开始审阅了。
  5. 选一个翻译服务并翻译。如果你想只在要紧的地方花钱,短 UI 字符串和长对话可以分流给不同的翻译服务。
  6. 审阅回来的结果。菜单字符串是机器翻译最弱的地方 —— 一个光秃秃的 Save 或 Load 没有上下文,而一个两词按钮要是回来变成一整句,它会把控件撑爆。
  7. 导出。RuneTranslate 会写出单独一个不加密的 _P.pak,里面是你改过的 .locres,放在正确的引擎相对路径上。
  8. 把那个文件复制进 <Game>/Content/Paks/ 并启动游戏。

在把整个游戏丢给翻译服务之前,先只翻几条字符串走完第 8 步测一次。如果覆盖注定会被忽略 —— 文件夹放错、文化不对、挂载方式特殊 —— 你会希望在五分钟内就发现,而不是在为 9,000 条字符串付完钱之后。

占位符与富文本

Unreal 的 FText 格式化用 {0}、{1} 表示位置参数,用 {PlayerName}、{Count} 表示具名参数,还有配 </> 结束标记的 <RichText> 标记来做内联样式。如果一个翻译服务把其中之一丢掉或搞坏了,游戏显示的不会是一句略微不对的话 —— 而是一个坏掉的格式字符串,或者那个参数从这一行里悄无声息地消失。

RuneTranslate 会在翻译服务看到字符串之前,把它们每一个都屏蔽成一个中性占位符,之后再还原。翻译服务是绕着这些标记译,而不是穿过它们译,所以一个参数不可能被某个觉得花括号像错字的模型删掉。如果你想更进一步、把特定字符串整个挡在翻译之外,正则排除过滤器在任何引擎上都能用,而一份术语表能让专有名词在整款游戏里保持稳定。

重音符号陷阱:这是编码 bug,不是字体问题

这一条值得单独记下来,因为它的症状指向的方向恰恰是错的。Unreal 序列化字符串时带一个长度前缀:正的长度表示每字符一个字节,负的长度表示 UTF-16。引擎只有在每一个字符都是 7 位 ASCII 时才选窄的那种形态 —— 它的检查会拒绝任何高于 0x7F 的东西。它的加载器也是照这个思路写的,所以如果递给它一个含有高字节的窄字符串,它会把每一个都替换成一个字面的 ?。

实际后果是:一句以窄形态写下的、含有 poción mágica 的译文,到玩家眼里变成 poci?n m?gica。而受影响的区段是 U+0080–U+00FF,那恰好是西班牙语、法语、德语、葡萄牙语、意大利语和北欧诸语言的重音字母。波兰语、土耳其语、捷克语、希腊语、西里尔字母、日语和中文全都在 U+00FF 之上,自动走宽的那条分支,从不受影响。

所以它的指纹是:西班牙语的重音坏掉,而俄语没事。你要是看到这个,那是写文件那一方的编码 bug,不是游戏字体缺字形 —— 缺字形渲染出来是方块或者什么也没有,绝不会是问号。RuneTranslate 曾经就有这个 bug,在 0.49.4 修掉了;现在编码器只在一个地方,凡是超出 ASCII 的一律写宽形态。这件事值得知道,因为这个领域里别的工具至今仍会弄错。

该选哪个翻译服务

九种翻译服务里有三种完全不需要 API 密钥 —— Google、免费的 DeepL,以及 DeepL 的 Classic/Next-gen 模型 —— 而免费档位就把它们全都给你。对一款主要是 UI 的游戏,免费翻译服务真的够用。对话量大的作品则是 LLM 翻译服务对上下文的把握好得多;OpenAI、Anthropic、DeepSeek、任意 OpenAI 兼容端点,以及通过 Ollama 或 LM Studio 跑的本地模型,都可以用。翻译服务对比逐个讲了它们各自擅长什么、不擅长什么,并附上真实的成本数字。

疑难排查

游戏能跑,但什么都没被翻译

几乎总是三件事之一。文件不在 <Game>/Content/Paks/ 里 —— 把它放在可执行文件旁边毫无作用。文件名丢了 _P 后缀,那样它就按普通的字母顺序挂载,基础 pak 可能会赢。再不然就是游戏跑在一种你没翻译的文化上:它加载的是启动时解析出的那种文化对应的 .locres,所以一款强制用 en 的游戏会无视一个只为 ja 写的覆盖。先检查游戏的语言设置。

在一个 Oodle 压缩的松散 pak 上提取失败

一个用 Oodle 压缩的松散 .pak 需要一个无法随程序捆绑的解压器。如果这款游戏同时也发布了 IoStore 容器,retoc 那条路会处理 Oodle,你就没事。如果只有松散 pak,目前这是一个硬性的止步,而且会被明确报告出来,而不是变成一个悄无声息的空结果。

游戏里哪儿都没有 .locres

那它的文本就是编译进已烹调资源里的,这条路够不到它。最清楚的例子是一款大型的线上运营 UE5 作品,它的 IoStore 索引被剥成了内容哈希、连文件名都没有 —— 几十万个分块,.locres 一个也没有。RuneTranslate 会直说,而不是把整个容器磨一遍。

检测根本不触发

把它指向含有可执行文件的那个文件夹,不要指向 Content/Paks 本身,也不要指向一个塞满其他游戏的上级文件夹。如果还是检测不到,那说明打包方式不寻常,值得报告 —— pak 格式有随版本而异的页脚布局,而一种还没人见过的方言,正是那种会被加进来的东西。

诚实的边界

Unreal 支持是尽力而为的。松散 pak、加密 pak 以及 UE5 IoStore 里的 .locres 是已经跑通的路子,在一款 UE4 作品上以西班牙语确认过在游戏里可玩。DataTable 和已烹调 .uasset 里的文本不在覆盖范围内。没有 IoStore 对应物的 Oodle 压缩松散 pak 不支持。强制签名校验的 pak 无法被覆盖,尽管实践中很少见。而且正因为这个格式在引擎版本和工作室之间差异极大,请把第一次导出当作一次测试,而不是当作成品。

你最后得到的是一个留在自己机器上、能玩的译好构建:原始游戏原封不动,外加一个你随时可以删掉、把一切撤销的小文件。如果你在对比引擎、或者想确认自己的游戏到底在不在支持范围内,Unreal 引擎页面有技术细节,全部引擎列出了支持的十七种格式,包括 Unity 和 RPG Maker。如果你对整个流程还很陌生,先从通用教程开始,再回到这里看 Unreal 特有的部分。

相关阅读
01

如何翻译 RPG Maker 游戏

rpg-makerhow-toenginecontrol-codes2026年8月18日 · 10 分钟
阅读 →
02

如何翻译游戏的 .po 和 .mo 文件(gettext)

gettextpomoenginetutorial2026年8月4日 · 7 分钟
阅读 →
03

如何翻译 AliceSoft System 游戏(Rance、Evenicle……)

alicesoftranceengine2026年6月26日 · 6 分钟
阅读 →

准备好试用 RuneTranslate 了吗?

免费版即可解锁全部引擎 + 全部翻译服务商。支持者($3/mo)解锁全速翻译。

下载 Windows 版