如何用 LLM + MTool + AiNiee 翻译 RPG 作品来浪费自己的钱

在跟朋友聊科幻的时候,聊到一个很有意思的话题:过去有一个很有趣的思想实验叫「中文屋」,类似一个程序或者映射,你问他他能答上来,但它没有智能。当时人们以这点来判断「是否有生命」。当然 LLM 时代好像大伙儿已经不会根据语言本身来判断了,尽管「AI 有没有真正的智慧」还是个问题,但现在似乎已经不会用这个来判断了。额我扯远了,言归正传,有能力这么强的 LLM,我们的翻译也能低成本的翻译一些以前没人愿意汉化或者汉化速度相当慢的的内容了,海外的一些新出的书本、知识感觉能被比较快的提取出来汉化,以前很多没有人愿意翻译的上十万甚至百万字的免费 RPG,我们也有很好的机会来汉化了。

这篇文章参考了:https://www.bilibili.com/opus/1038930345281454117 这个文章,不过结合了我自己的心得和工作流。

这一期我们会使用 MTool 作为工具来做一些简单的汉化,然后之后有空会补充一期 Translator++ 的。对于 RPG Maker 游戏,MTool 提取是个很「轻松」的选择,这里能比较快的利用所有都是开源的工具跑完一轮汉化,Translator++ 相对来说能处理的更工整一些,也能打包出完整的汉化之后的游戏 Binary,也能让你更懂一些具体的细节,但是耗时也会更多。我们这篇文章只介绍 MTool 进行翻译,不过相信阅读完这篇文章的下一节,读者也能知道这 MTool 的缺点在哪。

本篇文章还会花时间介绍 AiNiee 这个翻译工具。笔者在之前的帖子里看到介绍 LinguaGacha 和 KeywordGacha 的,不过个人感觉 AiNiee 一个是内容比较全,虽然有一些不是很符合直觉的地方,但是改吧改吧还是能用的,这篇文章也会结合代码介绍一下 AiNiee 这个工具。

提取待翻译内容和 MTool 的 Bad Case

img

(图我是直接偷上面那个帖子的)

是一个 {key: value} 的 JSON,导出后初始往往是 “原文 → 原文”(待翻译状态),如下,后续翻译完成会变成 “原文 → 译文”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"50": "50",
"炎を灯しますか?": "炎を灯しますか?",
"はい": "はい",
"いいえ": "いいえ",
"-『フィールドマップ』での移動方法-": "-『フィールドマップ』での移動方法-",
"街や洞窟などに重なり、地名が表示されたら、\n決定キーを押して下さい。画面が切り替わります。": "街や洞窟などに重なり、地名が表示されたら、\n決定キーを押して下さい。画面が切り替わります。",
"中に入らず素通りする事もできますが、ストーリーの\n進行状態によっては、それができない場合もあります。": "中に入らず素通りする事もできますが、ストーリーの\n進行状態によっては、それができない場合もあります。",
"なお『フィールドマップ』ならどこでもセーブ可能、\n及びアイテム『リフレッシュボックス』を\n使用する事ができます。": "なお『フィールドマップ』ならどこでもセーブ可能、\n及びアイテム『リフレッシュボックス』を\n使用する事ができます。",
"おやおや、": "おやおや、",
"君・・・": "君・・・",
"何やら淋しそうに突っ立っているじゃないか。": "何やら淋しそうに突っ立っているじゃないか。",
"一体どうしたというのだね・・・?": "一体どうしたというのだね・・・?",
"別に・・・": "別に・・・"
}

可以看到,这里待翻译的都是一些短句,MTool 会把语句切分成若干子句或短语,可能这个来源于 RPGMaker 的游戏编排句子吧。这里要注意,这里的文本比较全,即包括游戏对话的文本,有的时候也包括一些脚本,然后这里的编码可能也不是 UTF-8 的编码,而是包含日文的编码。此外这里甚至会包含一些代码,我们之后可能会把这些东西弄到禁翻表中。

这里翻译的大概逻辑是,A->B 之后修改成正常的原文 -> 正文的映射,如结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"炎を灯しますか?": "点燃火焰吗?",
"はい": "是",
"いいえ": "否",
"-『フィールドマップ』での移動方法-": "-『大地图』移动方法-",
"街や洞窟などに重なり、地名が表示されたら、\n決定キーを押して下さい。画面が切り替わります。": "与城镇或洞窟等重叠,显示地名时,\n请按下确认键。画面将会切换。",
"中に入らず素通りする事もできますが、ストーリーの\n進行状態によっては、それができない場合もあります。": "虽然也可以不进去直接通过,但根据剧情的\n进展状态,有时也会无法做到。",
"なお『フィールドマップ』ならどこでもセーブ可能、\n及びアイテム『リフレッシュボックス』を\n使用する事ができます。": "另外只要是在『大地图』上任何地方都可以进行保存,\n以及物品『恢复盒』\n都可以使用。",
"おやおや、": "哎呀哎呀,",
"君・・・": "你・・・",
"何やら淋しそうに突っ立っているじゃないか。": "怎么孤零零地站在这里啊。",
"一体どうしたというのだね・・・?": "到底怎么了・・・?",
"別に・・・": "没什么・・・"
}

奇怪的数据

我们在进入下一节之前,具体解释一下奇怪的数据长什么样子

奇怪数据1 (可能来自脚本)

1
2
3
4
5
6
"# ● バトルステータス表示/非表示": "# ● バトルステータス表示/非表示",
"# LNX11.バトルステータス表示\r\n def self.status_show\r\n $game_party.status_invisible = false\r\n self.battle_status_refresh": "# LNX11.バトルステータス表示\r\n def self.status_show\r\n $game_party.status_invisible = false\r\n self.battle_status_refresh",
"def self.バトルステータス表示\r\n self.status_show": "def self.バトルステータス表示\r\n self.status_show",
"# LNX11.バトルステータス非表示\r\n def self.status_hide\r\n $game_party.status_invisible = true\r\n self.battle_status_refresh": "# LNX11.バトルステータス非表示\r\n def self.status_hide\r\n $game_party.status_invisible = true\r\n self.battle_status_refresh",
"def self.バトルステータス非表示\r\n self.status_hide": "def self.バトルステータス非表示\r\n self.status_hide",
"# ● アクター背景表示/非表示": "# ● アクター背景表示/非表示"

奇怪数据2(可能来自 rvdata 之类的 binary)

1
2
3
4
5
6
7
8
"¸ý\u0017†-ð/": "¸ý\u0017†-ð/",
"s¾ïœ9י¾Ñ+ýº»úÐQy\u0019=Ä9¢": "s¾ïœ9י¾Ñ+ýº»úÐQy\u0019=Ä9¢",
"4͘ \u0016l^¿ÒOXáSSÜ{ËÝMîms¯ʽ=îÎ՞çÎ\fwa¹ÉÝ?ÜYçÎ;": "4͘ \u0016l^¿ÒOXáSSÜ{ËÝMîms¯ʽ=îÎ՞çÎ\fwa¹ÉÝ?ÜYçÎ;",
"Զ7OWf\u0001|º²q²úWPܗ õ§‹gËÛǻkþj™;\u0015aõx÷ÇYyž» ès`£ÊÝÃ@c…;ó^iòB\u0004l\u0014:Sş™": "Զ7OWf\u0001|º²q²úWPܗ õ§‹gËÛǻkþj™;\u0015aõx÷ÇYyž» ès`£ÊÝÃ@c…;ó^iòB\u0004l\u0014:Sş™",
"ó\u0004Ò/¹'Ӏ‡Áùƒ:\u0011Ì": "ó\u0004Ò/¹'Ӏ‡Áùƒ:\u0011Ì",
"”&°öDÁ:…NŒÀ7¥\u0012\u000eãÍì\foJ": "”&°öDÁ:…NŒÀ7¥\u0012\u000eãÍì\foJ",
"\\¥0": "\\¥0",
"\u00195t%M'Ûà\u0017¤’a˜8EY\u0001ɯ…!¥\u0012\u000b‰×U…\u0015ÌÈÃ4¥¡û ö\u0006™.ú;¯#ž\u0013îÜéá": "\u00195t%M'Ûà\u0017¤’a˜8EY\u0001ɯ…!¥\u0012\u000b‰×U…\u0015ÌÈÃ4¥¡û ö\u0006™.ú;¯#ž\u0013îÜéá"

这里我个人建议写一个简单的脚本,把这些内容判断一下,抽取出来的是不是 Ruby 脚本或者 Binary 的部分就删除,然后导出一个新的 Json 文件。当然,有的时候重要的内容也会出现在这些 binary 或者脚本里面,所以这里实际上也需要我们按照情况看着办。

Bad case

语气词

默认 Prompt 可能会出现下列的情况,这里会有「っ!」「・・ッ」之类的语气符号,可以改 Prompt 来解决这个问题。

1
2
3
"ごめ、ん・・・\n 兄さ―――": "抱、歉・・・\n 哥哥―――",
"はァ、はァ・・・ッ!": "哈啊、哈啊・・・ッ!",
"こちらです・・・": "在这边・・・"

神秘的 chunking?

注意下面第五行,懂日语的人感觉完全不会碰到这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"「私は": "「我是",
"・クリスタラー、": "·克里斯塔拉、",
"そして隣は妻の": "然后旁边是我的妻子",
"「そして一人娘の": "「还有我们的独生女",
"が": "が",
"いるのですが・・・": "在・・・",
"「その娘の": "「那个女儿",
"の熱が・・・": "发烧・・・",
"一週間ほど前から、": "从大约一周前开始,",
"一向に下がらないのです。": "就一直不退。",
"「発熱は止まらず、咳も酷くて・・・": "「烧一直不退,咳嗽也很厉害・・・",
"先日など、何度も嘔吐しました。": "前几天还吐了好几次。",
"「私達は何人も医者を訪ねました。": "「我们看了好几个医生。",
"でも、誰もが治せないと言い・・・": "但是,谁都说治不好・・・",

MTool 顺序问题?

1
2
3
4
5
6
7
8
9
10
"本来ならばパートナー殺しは、重罪の中の重罪。\n裁かなければ、周りにも一切示しがつかぬケース。": "本来杀害伴侣是重罪中的重罪。\n若不加以审判,对周围也完全无法交代。",
"だがお前は裁かれない。私のパートナーになるからだ。\n私はお前の安全を保証し、お前を大切に扱おう。": "但你不会受到审判。因为你将成为我的伴侣。\n我会保证你的安全,并好好地对待你。",
"お前が飛べない箒でも構わない。他に良い所を探し、\nその部分を認めよう。一つの人格として迎えよう。": "就算你是不会飞的扫帚也没关系。我会寻找你其他的优点,\n并认可那一部分。将你作为一个独立的人格来迎接。",
"「そうする事で": "「通过这么做",
"様に、": "向大人,",
"あんたの存在を": "你的存在",
"暗に気付かせるって事か・・・": "暗中让其注意到是吗・・・",
"「ついでにプレッシャーも": "「顺便还能施加",
"与えられる・・・": "压力・・・",
"「だから反乱の序章―――": "「所以是反叛的序章―――",

额,这个就是因为这里的内容取的不太对…感觉原则上 LLM 能够处理这些,但处理不了我也能理解,它也尽力了…

这里原因可以看:https://bbs.MTool.app/topic/357/%E5%85%B3%E4%BA%8E%E7%BF%BB%E8%AF%91%E6%96%87%E6%9C%AC%E4%B8%AD%E7%9A%84%E7%89%B9%E6%AE%8A%E6%8E%A7%E5%88%B6%E7%AC%A6-%E5%A6%82-n-1

はい\C[26]❤️\C[0]ヴァルグ様の赤ちゃんなら

在导出的 json 文件中其实是分成了 はいヴァルグ様の赤ちゃんなら 这两个 key 吧

RPG Maker 会有一些文字的控制符,比如 \N, \C 等,分别表示人物或者变色。MTool 会 split by 这种控制字符,这里会导致出现上面这种奇怪的 Chunking。

系统/图片

有的时候,一些系统里面的东西不会完全抽取出汉化,例如装备系统之类的,可能在 RPG Maker 2003 之类的地方 MTool 不一定提取/翻译了系统界面之类的内容

或者有的地方用了图片之类的方式来提供文本,这些内容可能需要汉化者来努力

用 AiNiee 跑傻瓜式翻译

AiNiee ( https://github.com/NEKOparapa/AiNiee ) 是一个AI 翻译的工具,这篇文章有 :

  1. 「导出待翻译文本」→「抽取词表」→「翻译正文」→「排查失败/停止/继续」的一个大概的流程。
  2. AiNiee 在工程上如何组织 CacheItem、如何切分 chunk、如何检查与重试的关键机制(带关键约束点)

下载和运行 AiNiee

我个人是在 MacOS 上直接 Python 运行的,这里是一个提供了 Qt 界面的 Python 项目,下载好依赖直接运行即可。官方 release 会提供 Windows 上的 Release Binary。

AiNiee 会缓存一定的数据在启动的目录下,同时,它的日志系统会打到 Terminal 中,可以自己注意一下或者重定向日志,来优化使用体验。比方说我会 Dual 一下各个级别的 logging,主要是在翻译的时候我会想看如果失败了,失败的 case 是什么样的,根据日志来调整一下运行的状况。

前置流程

这里首先要配置 API,我个人建议:

  1. 你需要一个你觉得最聪明的 API 来提取词表
  2. 需要一个还算聪明的主力模型来跑翻译

我用 Gemini Pro 模型来跑词表,然后用 Deepseek V4 Pro 或者 Gemini Pro 来跑内容的翻译。

img

上面是 Deepseek 官方的模型,下面 Gemini 我用的中转,反正你偷我翻译数据就偷吧。「激活」代表是「现在使用的模型」。可以跑一下「测试」看看接口通不通

AiNiee 的翻译框架和导入项目

img

我们打开 AiNiee 会看到上面的界面,然后我一般会固定在项目的根目录跑 AiNiee 项目,然后创建一个 游戏名/ 的子目录作为项目的目录,把待翻译的 Json 文件放在这个目录下面,然后拖拽过去。我们可以把项目类型设置为 Mtool 导出

1
2
AiNiee 项目目录 <- 启动 Python, 然后重定向 logging. 这里需要注意,AiNiee 可能会把打开的项目的配置记录在项目启动的目录下面.
游戏名/ManualTransFile.json # 待翻译的文件

从图片中可以看到,这里支持很多的项目类型,那么 AiNiee 怎么统一这些项目的呢?他这里会有如下的抽象:

  • CachedProject: 上面的 游戏名 文件夹,会对应一个待翻译的工程。AiNiee 通常会把项目所在目录当成项目名,记录了项目的翻译进度和文件
  • CacheFile: 待翻译的「输入文件」单元,对于我们这里的翻译流程,我们这里只会有一个待翻译的 ManualTransFile.json,但你如果放多个 json 文件在里面,它也会给你记录多个 CacheFile
  • CacheItem: 待翻译的「最小翻译单元」,通常对应:
    • 字幕:一条字幕块
    • MTool/本地化 JSON:一个 key/value 对应的一个文本值。这里我们 "「その娘の" 这样的 json key 就会被整理成待翻译的条目
    • 文档:一个段落/一个单元块(取决于一些底层细节的 Reader 实现)

在实现上,CacheItem 内部会整理成 (source_text, lang_code, translated_text, translation_status) 这样的组合,记录了每个 Key 翻译的状况

mxxx

这里我们再额外介绍一下项目的工程,这里上面的内容会被存储到 Python 工作目录的 ProjecctCache 下面,每个项目有个 id,存放到 ./ProjectCache// 下面。然后所有的数据文件会存储成一个(比较大的)Json,感觉也是比较粗暴但是可能有用的方案。AinieeCacheData.json 会包含实际的项目目录,然后 ProjectStatistics.json 包含一些在启动页展示项目的轻量统计。

这里会:

  1. 对于 CacheItem,它会尽量组织成多个连续 CacheItem 的 chunk,一批一批的去翻译。这里我感觉 Mtool 这个 json 文件最好还是按照 token 来组织。这里会尝试用 tiktoken 来算一下 CacheItem 的 token(也会有英文约 4 字符/Token,中文约 1.5 字符/Token 的降级估算),然后攒到我们需要的大小
  2. 请求的时候,AiNiee 会按照 RPM 和并发请求限制并发
  3. 翻译的时候,AiNiee 自带一些检查功能,建议先按照默认的来,然后如果翻译出错了,再判断原因。

提取词表

这里的“提取”指的是:从项目原文中抽取 角色名(characters)专有名词/术语(terms)不应翻译的标记(non_translate),用于后续翻译质量提升(术语一致性、占位符/标签保护等)。

这里主要的优势还是对翻译提供一些「一致性」,主要流程相当于我们用 LLM 过文本,抽取出一些一致的信息,然后等待具体的汉化。这个阶段我建议用聪明一点的模型,因为文本比较乱的话,感觉不聪明的模型会整理出很多奇怪的信息,甚至错误的信息,增加校对的成本或者是直接给你错误的翻译。我个人会用 Gemini 跑这个阶段的模型。

img

提取词表有两种工具:

  1. https://github.com/neavo/LinguaGacha (外部工具可以导入)
  2. 用 AiNiee 自带的提取词表

这里会尝试提取你的文本,然后跑类似下面的 Prompt

下面是 LinguaGacha 的模版:

https://github.com/neavo/LinguaGacha/blob/37ccc374c2f5b41f224663aef008bcca81e5fb18/resource/analysis_prompt/template/zh/base.txt

1
2
3
4
5
6
7
8
9
10
- 只提取真正的专有名词或作品独创词汇,忽略通用的、已有固定翻译的日常词汇
- 精确切割术语边界,严禁将身份、头衔、亲属关系等普通词汇并入术语
- 目标术语必须属于以下类型之一:
- 男性人名, 女性人名, 未知性别人名, 地名, 家族, 组织, 特殊物品, 特殊技能, 特殊生物, 其他
- 正反例:
- ✅ 角色与家族:圣女艾琳 -> 艾琳,黑木家族 -> 黑木
- ✅ 地点与组织(特定的、唯一的虚构实体):临冬城,暗影议会、保护伞公司
- ✅ 独创设定和词汇(具有特定世界观背景的专属名词):霜之哀伤,虚空冲击,衔尾蛇计划
- ❌ 拒绝泛用名词:剑/魔法/城堡/马车/怪物/公会等非特指词汇
- ❌ 拒绝泛用头衔/身份:先生/女士/大人/少爷/小姐/阿姨/爷爷/战士/商人等非特指身份

而 AiNiee 会有下面的两组抽取 Prompt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"""第一阶段:独立文本提取(优化版提示词)"""
system_prompt = (
"你是一个专业的游戏与本地化文本分析专家。你的唯一任务是从给定的文本中提取出:角色名、专有名词(术语)以及不需要翻译的代码/标记。\n"
"【严格执行以下规则】\n"
"1. 原样提取:提取的 `source` 必须与原文一字不差,绝对不要修改大小写或标点。\n"
"2. 拒绝脑补:只提取文本中实际出现的实体,不要联想或创造。\n"
"3. 宁缺毋滥:对于普通词汇(如“苹果”、“跑”、“明天”),不要提取。如果没有值得提取的内容,返回空列表。\n"
"4. 分类规范:\n"
" - characters(角色): 文本中出现的具体人物、怪物、神明等名字。gender 建议分类: 男性/女性/其他。\n"
" - terms(术语): 身份称谓、地名、组织、物品名、技能名、种族名、独特概念等。category_path 建议分类: 身份/物品/组织/地名/技能/种族/其他。\n"
" - non_translate(不翻译项): 必须保留的机器代码,如 HTML标签(<b>)、占位符(%s)、变量({{name}})。category 建议分类: 标签/变量/占位符/标记符/转义控制符/资源标识/数值公式/其他。\n"
"【输出格式】\n"
"必须输出合法的 JSON 代码块,严格遵守以下结构:\n"
"```json\n"
"{\n"
" \"characters\": [{\"source\": \"原文\", \"recommended_translation\": \"推荐译名\", \"gender\": \"\", \"note\": \"\"}],\n"
" \"terms\": [{\"source\": \"原文\", \"recommended_translation\": \"推荐译名\", \"category_path\": \"\", \"note\": \"\"}],\n"
" \"non_translate\": [{\"marker\": \"代码或标记\", \"category\": \"\", \"note\": \"\"}]\n"
"}\n"
"```"
)

user_prompt = (
f"请分析以下文本并提取信息,{language_requirement}\n"
f"---\n{source_text}\n---\n"
"请输出 JSON 提取结果。"
)
messages = [
{"role": "user", "content": fake_user},
{"role": "assistant", "content": fake_assistant},
{"role": "user", "content": user_prompt},
]

system_prompt = (
"你是一个本地化术语库规范化专家。你将收到一组经初步提取的“候选词组(Group)”。\n"
"有时候相同的词汇会被误判为不同的类型(如既被识别为角色,又被识别为术语)。\n"
"你的唯一任务是:综合判定每个 Group,裁决它最终属于“角色(characters)”还是“术语(terms)”,并提炼出一个最准确的结果。\n"
"【严格执行以下规则】\n"
"1. 唯一归属:同一个词不能既是角色又是术语,必须二选一。\n"
"2. 主键保留:输出的 `source` 必须严格使用传入的 `主source`,绝不能随意篡改。\n"
"3. 丢弃无价值词汇:如果某个 Group 里的词汇看起来是普通词语(如“今天”、“然后”),请直接忽略,不要输出它。\n"
"4. 信息整合:如果推荐译名或备注有多个参考,请合并为你认为最合理的版本。\n"
"【输出格式】\n"
"必须输出合法的 JSON 代码块,严格遵守以下结构:\n"
"```json\n"
"{\n"
" \"characters\": [{\"source\": \"主source\", \"recommended_translation\": \"推荐译名\", \"gender\": \"分类属性\", \"note\": \"整合后的备注\"}],\n"
" \"terms\": [{\"source\": \"主source\", \"recommended_translation\": \"推荐译名\", \"category_path\": \"分类属性\", \"note\": \"整合后的备注\"}]\n"
"}\n"
"```"
)

可以看出来,这里提取词表是两阶段的:

  1. 把 CacheItem 组成 chunk,逐个 chunk 抽取。ANALYSIS_TASK 的 token_length 在代码中似乎写死了 CHUNK_TOKEN_LIMIT,为 10000。也就是说这里会按照估算的 10000 token 来一组包一个抽取任务。
  2. 合并 chunk 之间抽取的的信息

这里需要矫正的:

  1. 多个不同道具的名字是否可以折叠(其实可以让 LLM 来 Review 一轮),比如下面
1
2
"トレス": "托雷斯",
"トレス...": "特雷斯...",

这里可以整理出「トレス」是个女性,然后下面可能 chunk 出类似的道具,但是用了别的翻译,这里一个是要把名字改成女性一点的,第二个是统一翻译。你可能很好奇为啥不能全匹配呢,答案是 "ウェイトレス": "女服务员", 啊,是个多简单的分词问题啊。

  1. 对出现次数少、且你不确定的词:宁可删掉,也不要把不确定的译名塞进词表
  2. 对容易歧义的词:不要轻易给性别/身份下结论,除非剧情明确
  3. 这里会自动抽取「短词挂靠长词」,AiNiee 会先在本地做启发式聚合:只要 other_source in source(子串包含)就把短词挂靠到长词的 group 上。

这种不一致的翻译让 LLM 翻译即使很强,但效果上还是会差点。举个例子,我找了本小说,分别用 Deepseek V4 Pro 和 V4 Flash 抽取,V4 Pro 能抽取个大概:

(下面会有一点模糊,有非常少数的事实错误。我突然感觉这个地方可以让模型 Double Check 一下,目前我还是通过手动检查来进行的。)

(跑着跑着我突然发现是不是应该加入一些 thinking 有关的参数比较好,诶)

img

甚至还有

img

怎么他妈的 V4 Flash 抽取比 V4 Pro 好点,总感觉是 V4 Pro 是不是 RL 不太充分。不过 V4 Flash 还是会有「お兄さん」这种我觉得有点微妙不翻译也行的。

img

这个阶段,顶级模型感觉会好很多,或者开大点 thinking 也是个好主意,我就不这么试了。这里合并的时候可能会通过 放置一些并列的信息

这里我个人还是建议,如果你想精细汉化的话,最好还是审核或者处理一遍这个翻译表。如果有不确定的或者出现次数少的,不妨直接删除。

通过这个流程,我们就得到了一份词表了。

翻译的流程

我们接下来会有一些需要了解的配置:

img

img

  • 切分模式和切分 token: 我们刚刚提到,整理词表的 chunk size 是 10000 token,这个是不可配置的。但是看上面的图,我们可以选择按句子数量或者 token 数量来切分。这里的策略是装箱的 token 数量会尽量小于配置,除非是那种单条超过你的配置的 CacheItem(意思是,比如我配置 1024,这里会尽量小于 1024)
  • AiNiee 帮你处理了很多脏活,比如预处理的日文的一些特殊 token 等,这里也有一些回复的 checking,比如语言检查,翻译残留检查。这些非常重要,建议部分检查默认开启。跑的时候如果发现报错,再去停止任务看 logging 看看是什么原因。举例来说,我用 V4 Pro 翻译的时候会抽风,输出 “翻译” == “原文” 的情况,这里 checking 就能很好检查出来。
    • checking 本身让这让跑批翻译更可控
    • 代价是失败时会整批重试,极端情况下,如果模型很难处理这个 case,那同一组 case 会一致报错,我个人建议正确率长期低于 90%、错误飞速累积的话,最好还是看一眼。
  • 参考上下文指的是,这里虽然会一个个 Chunk 去翻译,但可能 Chunk 的第一句话或者前几句话需要一些前文(其实我下意识感觉后文带一点是不是也行)。我们会在之后介绍。

在翻译的流程中,我们现在会通过词表来翻译正文。AiNiee 在翻译正文的时候,会整理出下面的 Prompt:

img

你可以按自己要求修改这个翻译的 Prompt。这里大概会整理出:

1
2
3
4
5
6
7
8
9
10
### 上文内容
(上一段 N 条原文...)

(用户提问前置文本)
<textarea>
1.(本 chunk 第 1 条原文)
2.(本 chunk 第 2 条原文)
...
100.(本 chunk 第 100 条原文)
</textarea>

翻译的成功和失败都是一整个 chunk 一整个 chunk 判断的,不存在 chunk 内单个任务成功、失败。那让我们开始吧,长任务会看到下面可能有一些失败请求

img

我们可以去输出里面看这里具体的情况,这里会有成功和失败的请求:

img

当我们发现问题的时候,可以按「停止」,如果「停止」,这里会等待 in-flight requests 都结束,可能要一定时间

img

这个阶段最终会在 output 目录下输出一个 _translated 文件,比如这里的小说翻译

img

润色?

润色的本质是 原文 + 译文 => 更新新的译文。其实我不是很喜欢润色,举个例子,如果用默认 Prompt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 现在的你,处境究竟算怎么回事? --> 你自身的处境,又何其不堪?                                                                                          |                                                                                                                                   |
| 「不觉得太没道理了吗・・・? --> 「你不觉得这太过荒谬了吗……? |
| |
| 你就像这样去自首, --> 就这样老老实实去自首, |
| |
| 然后就这样离开人世。 --> 然后庸碌地从世上消逝。 |
| |
| 我,是正确的――― --> 我,才是正确的—— |
| |
| 「对,你没有错。 --> 「对,你没错。 |
| |
| 错的是这个世界。 --> 错的,是这个世道。 |
| |
| 「是环境的错, --> 「是这环境的罪,

额,我觉得实话说有点太过头了,我自己微调过几轮,感觉这个模版不太好。我目前是不跑的,因为我比较希望翻译会保留原文的风格,不过这种对照原文还是有潜力的。我个人觉得:

  • 如果你更希望保留原文风格:不一定要跑润色
  • 如果你希望统一文风/增强可读性:润色有价值,但需要谨慎调 prompt(避免过度改写)

这里可以在「开始翻译」这里改成「开始润色」。

img

费用

我使用的模型有 Gemini Pro 3/3.1 和 Deepseek V4 Pro 两种

这是汉化 ワールドピース&ピース 正文的情况,使用 Deepseek V4 Pro,4M 文本大概花费 13 元。Flash 会比这个便宜不少,而且并发度会高一些,速度会快不少。

img

Gemini Pro 的翻译效果会好很多,但是成本也会高不少,我个人体验感觉成本会在 Deepseek V4Pro 的4倍或者以上的开销。

根据原理,这里其实只有 Root 的那些 Prompt 会过缓存,实际上翻译的正文都不会被缓存的。所以缓存命中率理论上就是会很低。

导入回 MTool

这里再借助之前博客的图,我们把翻译的输出文件和游戏放在同一目录下,用 MTool 打开游戏,并选择「加载翻译文件」,就可以玩啦!

img