與混亂之中起舞:模組漢化相關的小小心得
前不久在討論寫文章的時候,當麻提到應該把 GitHub 上面寫的一些文章分享出來,畢竟 GitHub 看得人太少,難以覆蓋到更多的用戶。我覺得說的很對,把我的一些經歷和故事分享給大家,還能夠讓後來者了解更多事情,避免踩坑,倒也是一件功德圓滿的事情,所以你們就看到了這篇文章。
一. 前言
Minecraft 自 2009 年 5 月由瑞典人馬庫斯·阿列克謝·佩爾松(Markus Alexej Persson)開發以來,彷彿如龍捲風一般狂襲世界各地。沙盒遊戲本身的創造性,再加上世界各地各種開發者的貢獻,一次又一次構建了遊戲史上的奇蹟,而模組就是其中一個極為重要的分支。
模組(英文常稱呼 Mod,即 Modifications 的縮寫)大多由遊戲愛好者自發製作,用來改變遊戲原本內容的東西。有的能夠優化遊戲本身的體驗;有的修復了官方未能修復的 bug;有的完全推倒重來,構建了一個新的世界觀。正因為開發者人數的眾多,也導致了創意方面的無窮性、質量方面的良莠不齊、標準方面的混亂不堪。
Minecraft 模組最早可以追溯到遊戲的 beta 版本,如今很多耳熟能詳的模組(比如工業、建築、林業)都基本在這一時期誕生。伴隨著 Minecraft 名氣越來越大,相關模組也逐漸從歐美國家擴散到世界各地,廣為人熟知,帶來的就是亘古不變的問題——翻譯。
Minecraft 官方在正式版 1.1 即加入了世界各國的語言文件,到了正式版 1.6.1,又修改了語言文件載入方式,能夠直接通過資源包來覆蓋語言文件。而模組對多語言的支持則要曲折的多,直到正式版 1.6.1 之前,模組還沒有標準的多語言支持,除了個別有心的模組作者可能提供了語言文件介面,更多時候國內翻譯者只能通過修改源代碼方式強行翻譯漢化。
即使到了之後,相關標準的確立,也不能完全解決翻譯中帶來的各種問題。編碼、文件格式、載入方式、分行等等五花八門,各種標準層出不窮。這也是我想寫這樣一本小小心得的初衷,希望能夠將我自己的翻譯經驗分享給大家,在之後的道路上避免後人踩坑。
為了能夠讓更多的人理解,對於一些專有名詞也儘可能都解釋的一下。由於作者水平有限,可能給會存在各種錯誤或者考據不嚴謹。如果對這些心得有意見或者建議,歡迎多批評指正,如果你喜歡這篇文章,也請隨意轉載演繹。(原創部分採用 MIT 協議)
二. 了解語言文件
1. 語言文件發展歷史
語言文件(這裡特指模組相關語言文件)的發展是一個極為曲折的過程。
① Minecraft 1.6.1 之前
在 Minecraft 1.6.1 之前官方還沒有指定相關規範,模組本身缺乏對多語言的支持。諸如工業、建築這樣的模組,可能還給提供語言文件,但其他大部分模組直接都是硬編碼語言文件。
硬編碼:將可變變數用一個固定數值表示。由於可變變數是固定不變的,一旦編譯後很難修改。
軟編碼:和硬編碼相對,將可變變數用特殊標記來表示。這樣在編譯後,只需要修改這個特殊標記即可。
為了能夠翻譯相關模組,當時的翻譯者往往需要 jd-gui 這樣的反編譯工具來反編譯模組,而後修改模組的常量池來參與翻譯。這樣翻譯不但效率低下,一旦翻譯錯誤,整個模組文件就報廢了。
關於早期模組翻譯,可以參見:個人漢化mod的教程,歡迎來討論(無JAVA編譯)[求指正] - Minecraft(我的世界)中文論壇 -② Minecraft 1.6.1 ~ Minecraft 1.10.2 之間
1.6.1 之後官方相關規範的出現,大大簡化了翻譯的難度。官方採用了簡單的語言文件格式來載入本地化,語言文件甚至可以被資源包所替換。大致規則如下:
- 語言文件都位於
assets/modid/lang/
下,其中 modid 是隨意的,依據具體的模組不同,名字可能不同。比如林業模組的語言文件都位於assets/forestry/lang
文件夾下。 - 可以通過資源包覆蓋語言文件,覆蓋機理和材質一致。
- 語言文件命名參照了 ISO639 標準:英文(美國)就是
en_US.lang
,中文(中國大陸)就是zh_CN.lang
- 語言文件格式是簡單的
key=value
方式書寫:
item.apple.name=蘋果 tile.stone.name=石頭
在上述案例中
item.apple.name
就是key
,而蘋果
就是value
。
- 以
#
開頭的段落識別為注釋:
# 注釋 # 注釋中可以隨便書寫文字,均不會載入 item.apple.name=蘋果 tile.stone.name=石頭
- 語言文件編碼為
utf-8 無 BOM 編碼
BOM
位元組順序標記(byte-order mark,BOM),即放置在文件開頭的字元 uFEFF,它常被用來當做標示文件是以 UTF-8、UTF-16 或 UTF-32 編碼的記號。
③ Minecraft 1.11 以後
1.11 版本更新後,官方對部分文件大小寫做了嚴格規範,資源相關的文件名需要通過資源包的 pack.mcmeta
中的 pack_format
參數來決定。
一個只包含 pack_format 和 description 參數的 pack.mcmeta 文件,可以看到書寫格式和 JSON 一致。
{ "pack": { "pack_format": 3, "description": "模組資源文件" }}
- 當設定為
2
時,語言文件名採用舊版本書寫方式,英文(美國)就是en_US.lang
,中文(中國大陸)就是zh_CN.lang
。 - 當設定為
3
時,語言文件名需要全小寫。比如英文(美國)就是en_us.lang
,中文(中國大陸)就是zh_cn.lang
。
除此之外其他規則一致。
2. 模組語言文件載入機理
① 普通語言文件載入
這一塊參考 Minecraft 1.12.x 版本的 Forge 代碼。 源代碼:【1】 【2】
Minecraft 載入語言文件的機理很簡單:
- 遊戲讀取所有模組的
assets/modid/lang/
下的語言文件; - 將語言文件處理成映射表;
我們剛剛在第一章中有簡單說過 Minecraft 的語言文件,遊戲將會按照條目中的第一個等號來拆分成映射表。
比如
a=b=c
在遊戲中會拆分成a
和b=c
兩個條目,形成一對映射;
- 遊戲中依據映射表、具體選擇的語言來載入翻譯條目;
- 如果指定語言找不到映射,則開始在
en_us.lang
中尋找映射; - 如果在
en_us.lang
中還是找不到對應的映射,直接顯示key
;
映射
兩個非空集合 A 與 B 間存在著對應關係,而且對於 A 中的每一個元素,B 中總有有唯一的一個元素與它對應,就這種對應稱為從 A 到 B 的映射。
② 帶有 #PARSE_ESCAPE
注釋語言文件載入
Forge 新增了一個特殊讀取:如果注釋中存在 #PARSE_ESCAPE
注釋,那麼處理將會嚴格按照 Java Properties 格式來讀取,並處理成映射表;
Properties 格式很像 Minecraft 語言文件格式,但是要更加靈活強大:
- Properties 採用
:
或=
來書寫映射,也是按照第一個:
或=
來拆分成映射表。下面兩種書寫方式均是正確的:
#PARSE_ESCAPEitem.apple.name:蘋果item.apple.name=蘋果
- Properties 可以採用轉義字元來轉義
:
或者=
,轉義過的字元不會被識別。
#PARSE_ESCAPEitem.apple:name:蘋果item.apple=name=蘋果
- Properties 可以寫成多行的,通過每行行尾的
符號來識別:
#PARSE_ESCAPEitem.apple.name:蘋果挺好吃的item.apple.name=蘋果不咋好吃真的確實不好吃
- Properties 採用
#
或者!
來書寫注釋:
#PARSE_ESCAPE! 這是一條注釋item.apple.name:蘋果# 這也是一條注釋item.apple.name=蘋果
③ 映射重名問題
由於遊戲是將所有模組的語言文件放在一起處理,一些粗心大意的模組作者在書寫語言文件方面如果考慮不周,就會罕見的存在映射重名問題。
比如經典的 ShetiPhianCore 譯名衝突,在 ShetiPhianCore 模組中存在著這幾個翻譯條目:
item.dyePowder.green.name=綠色染料item.dyePowder.red.name=紅色染料item.dyePowder.yellow.name=黃色染料
而類似的條目在 Minecraft 原版中恰好也存在,它們的 key 完全相同:
item.dyePowder.green.name=仙人掌綠item.dyePowder.red.name=玫瑰紅item.dyePowder.yellow.name=蒲公英黃
結果導致 ShetiPhianCore 模組的這幾個條目直接覆蓋了原版的語言文件,導致玩家在遊戲中獲取了錯誤的翻譯。
除此之外諸如額外星系模組的 岩漿水
與原版的 岩漿塊
名稱相互衝突的等等問題,不一而具。
所幸的是這些問題很少見,而且出現後僅僅是對遊戲內翻譯有所影響,不會帶來任何其他方面的崩潰或者衝突。如果你在翻譯過程中發現了這些問題,請及時向模組的原作者反饋(但是額外星系的作者就表示不會修復這個問題,雖然實際修復起來很簡單)。
三. 編碼問題
1. 字元編碼
計算機只能識別二進位代碼,如果想要讓計算機存儲或者識別字元,就需要將字元轉變為二進位代碼。隨著時代、用途等諸多因素的影響,字元編碼的標準也各不相同。
這部分東西會非常多而亂,初學者可能會各種懵逼。沒辦法……誰讓這是計算機呢……
① ASCII 碼
1967 年美國國家標準學會制定了第一個字元編碼規範,這就是赫赫有名的 ASCII 碼。
ASCII 碼使用 7 位二進位數(剩下的1位二進位為0)來表示所有的大寫和小寫字母、數字 0 到 9、標點符號, 以及在美式英語中使用的特殊控制字元。
然而 ASCII 碼支持的字元太少,而後世界各國又獨自演化出了適應本國使用的各種編碼。諸如中國大陸的 GB2312 編碼,日本的 Shift-JIS 編碼。
② ISO-8859-1 編碼
也常被稱為 Latin-1
編碼,在 ASCII 的基礎上額外加入 96 個字母及符號,使其能夠廣泛支持西歐各國語言。
特意提一下這個編碼,部分歐美作者在書寫語言文件時會偶然忘記改變文件編碼,系統便自動使用 ISO-8859-1 編碼書寫語言文件。如果不慎語言文件中出現了非 ASCII 字元,那麼這部分字元就會亂碼。更有甚者強制使用 ISO-8859-1 編碼中文文件,導致翻譯者提交的正確翻譯,在編譯後亂碼。
典型代表:
- Dungeon Tactics 模組,
en_US.lang
採用 ISO-8859-1 編碼。讀取其 1.12.2 版本模組en_US.lang
第 245 行字元ü
,存在亂碼:
item.dungeontactics:terrible_feather.name=Hühnerfeder
- SuperMiner 模組,
en_US.lang
採用 ISO-8859-1 編碼。讀取其 1.12.2 版本模組en_US.lang
第 46 行字元§
,存在亂碼:
superminer.excavator.goditems=§aCONGRATULATIONS: §7Youve Unlocked the §6God Items§7!
③ GB 編碼
- 中國國家標準總局 1980 發布的適合中文的編碼,第一版為 GB2312 編碼,共收入 6763個漢字、682 個非漢字圖形字元。由於中文字元的龐雜性,所以採用了兩個位元組來表示,向下兼容 ASCII 編碼。
值得特殊說明的是,在 ASCII 里本來就有的數字、標點、字母,在 GB 編碼中又重編了兩個位元組長的編碼,這就是全形字元。
# 半形數字、標點、字母1234567890,.abcdefg# 全形數字、標點、字母1234567890,.abcdefg
- 1995 年又制定了 GBK 編碼,GBK 共收入 21886 個漢字和圖形符號,兼容 GB2312 編碼。
- 2000 年又制定了 GB18030 編碼,這次收錄了 70244 個中文字元,兼容 GBK 編碼。GB18030 編採用一二四位元組的變長編碼。
值得一提的是,Windows 操作系統默認的文件編碼方式是 ANSI 編碼,在簡體中文系統就指的 GB 編碼。在日文系統上就指的是 Shift-JIS 編碼。至於你問為什麼非要用這個名字……去問微軟爸爸啊。
④ Unicode 編碼
世界各國的編碼太多太亂了,我們來指定一個全新的通用標準吧,於是 Unicode 編碼應運而生。Unicode 編碼支持全世界各國文字,採用統一的編碼,兼容 ISO 8859-1 編碼。Unicode 編碼依據不同的長度,具體實現方面又分為 UTF-8、UTF-16、UTF-32。最常用的就是 UTF-8 編碼。
MacOS 和 Linux 都還不錯,操作系統默認都是 UTF-8 無 BOM 編碼;
Windows 系統則不然,採用 ANSI 編碼,依據不同國家採用不同國家的國標碼。但對 UTF-8 編碼也有支持,我們在存儲文件的時候還是可以將編碼選擇為 UTF-8 編碼。
但是!Windows 下存儲的 UTF-8 編碼文件,其開頭加入了位元組順序標記(byte-order mark,簡稱 BOM,即放置在文件開頭的字元 uFEFF
),用來當做標示文件是以 UTF-8、UTF-16 或 UTF-32 編碼的記號。部分程序並不支持 BOM,導致處理該字元時候會報錯。
我們日常使用的 Java 7/8 以及 Python 3 這些計算機語言,內部實現默認都使用無 BOM 的 UTF-8 編碼。但是在沒有指定語言編碼的情況下讀取系統文件,仍舊是沿用系統編碼。這在下一章的存儲格式中會有詳細的說明,這也是很多令人頭疼問題的導火索。
2. 文件格式
這一章我們將介紹模組翻譯中會遇見的各種文件格式,以及各種形形色色的問題。
① 原版語言文件
幸運的是,原版語言文件制定了較為嚴格的規定,同時也提供了方便易用的 I18n 方法。除了極少數模組,大部分模組都較好的遵循了原版語言文件。對幾個特立獨行的模組簡單介紹下:
- 工業模組:至今仍然採用
Java Properties
格式書寫語言文件。 - 格雷科技:採用程序生成的配置文件來書寫語言文件,啟動時需要將語言文件放置在遊戲主文件夾下。
- McJty:一個模組作者,寫過很多知名的模組,比如 RFTools 三件套,The One Probe 等。除了官方強制要求添加本地化的部分(方塊、物品、成就、標籤頁),其他部分全部採用硬編碼。不過所幸他認識到了這個問題,在比較新的 MeeCreeps 模組中已經開始書寫較為全面的語言文件了。本人也表示會在之後逐步在其他模組中添加更多語言文件。
- Extreme Reactors:屬於舊版本遺留問題,所有的 GUI 目前仍舊採用硬編碼。
原版語言文件的書寫與格式在第一章中都詳細敘述了,此處不再過多介紹。
② JSON 語言文件
(1)JSON 基本介紹
全稱 JavaScript 對象表示法(JavaScript Object Notation),是一種輕量級的數據交換格式,完全獨立於編程語言的文本格式來存儲和表示數據。JSON 不允許寫入注釋,整體規則很簡單,總結如下:
- 數據由逗號分隔;
- 花括弧保存映射;
- 方括弧保存數組;
下面我們先書寫一個最簡單的 JSON 文件,包含一對映射,一個存有兩個數據的數組:
{ "item.apple.name": "蘋果", "item.list.name": [ "蘋果", "香蕉" ]}
JSON 有很多在線的格式化工具,能夠檢查我們書寫的語法是否有錯誤,以及對數據進行重新排版。
- JSON.cn
- BeJSON
(2)編碼問題
按照標準,JSON 文件應當使用 UTF-8 無 BOM 編碼,但是部分作者未強制指定編碼,導致遊戲中中文翻譯亂碼,或者更甚者無法啟動遊戲。
經典代表就是 FTB Achievements 模組,它在很多 FTB 整合包中均有使用。在 FTB Skyfactory Challenges 整合包中,其所有的挑戰任務均使用 FTB Achievements 書寫,任務文件在遊戲主目錄下的 config 文件夾中,採用 JSON 格式書寫。其所有的任務描述由配置中的 tagCompound
標籤書寫,但是作者似乎並沒有限定編碼,導致其強制以 ASCII 編碼進行解析,如果不慎寫入中文字元,啟動過程中遊戲會直接崩潰。
所幸還是有辦法解決,通過轉義字元書寫 Unicode 編碼,即可正常載入。但是轉義字元書寫的 Unicode 編碼畢竟不適合閱讀,在翻譯過程中檢查排錯都很困難,如果出現了這些編碼問題,請及時向原作者反饋問題。
轉義書寫的 Unicode 編碼字元
採用四位 16 進位,開頭輔以u
字元來轉義;比如正常的中文
二字,Unicode 編碼為u4e2du6587
。當程序讀取到這些轉義字元時,便會以 Unicode 編碼來進行解析,從而在其他編碼環境的文件中愉快的插入 Unicode 字元。網路上有很多相關工具可以將普通字元轉換為 Unicode 轉義字元,比如站長工具。
(3)傳參崩潰問題
一般 JSON 書寫的文件除了語言文件之後,還有許多功能性參數。如果我們在錯誤的地方進行了翻譯,而作者代碼又不夠健壯,就會導致遊戲崩潰。這種崩潰可能是啟動中崩潰,也可能是遊戲中觸發某個事件後崩潰。
舉個例子,匠魂模組 2.5 版本手冊中曾經存在一個翻譯錯誤,翻譯者~~酒石酸~~錯誤的翻譯了 shulking.json
文件的modifier
欄位,此處欄位應當為工具強化的 id,不能夠翻譯。該錯誤導致匠魂手冊在長達半年的時間,中文玩家一打開就會遊戲崩潰。
如何判定當前欄位是否應當被翻譯,需要翻譯者查看具體源碼,或者多次試錯才能夠發現,難度較大。
③ XML 語言文件
可擴展標記語言(EXtensible Markup Language,簡稱 XML)是一種標記語言,很像 HTML 語言,不過所有的標籤均可自定義。XML 可讀性較 JSON 稍差,除了早期模組,現在極少有模組使用這種語言。
XML 語言大致結構如下,整個文件主體部分必須要被標籤所囊括,可以添加註釋:
<note> <!--注釋部分,注釋可以書寫在標籤的外面--> <to>George</to> <from>John</from> <heading>Reminder</heading> <body>Dont forget the meeting!</body></note>
關於更多 XML 知識,可以查看 W3school 中文教程。
目前所知還在使用 XML 語言的模組僅有 InGame Info XML 模組。如同 JSON 語言一樣,XML 也可能包含很多功能性參數,如果書寫位置錯誤,也可能會導致遊戲崩潰。好在 InGame Info XML 作者代碼健壯性還不錯,只會導致無法載入,遊戲並不會崩潰。
這裡截取了部分 InGame Info XML 配置文件源碼。
<line> <str>Day {day}, {mctime} (</str> <if> <var>daytime</var> <str>$eDay</str> <str>$8Night</str> </if> <str> time$f)</str></line>
圖中只有 <str>
標籤圍起來的部分可以被翻譯。
④ YAML 語言文件
YAML 英文原名為 YAML Aint Markup Language,YAML 可以方便的表達映射、數組之類的數據,而且採用縮進表示層級關係,閱讀性極高。
YAML 格式一般如下:
house: family: name: Doe parents: - John - Jane # 可以隨意插入注釋 address: number: 34 street: Main Street "city": "Nowheretown" zipcode: 12345
- 縮進並沒有強制要求,只需要保證同級數據縮進相等即可,不過不可以使用 tab 縮進;
- 數據可以依據個人習慣選擇是否用引號圈住;
#
後插入注釋;- 數組內元素採用
-
符號,相同縮進來表示;
採用 YAML 書寫配置或者語言文件的模組極為罕見,但在插件的配置、語言文件中幾乎全部採用此語言書寫。
⑤ HOCON 格式
HOCON 英文原名為 Human-Optimized Config Object Notation,一種極為類似於 JSON 和 Properties 的語言,結構清晰簡單,書寫靈活。目前 Sponge 服務端的默認配置文件都是採用此語法書寫,模組中的配置文件格式也和這個語法有點像。
play.http.secret.key = "changeme"play.modules { # Disable built-in i18n module disabled += play.api.i18n.I18nModule # Enable Hocon module enabled += com.marcospereira.play.i18n.HoconI18nModule}play.i18n { langs = [ "en" ]}
⑥ 其他格式
然而總是會有模組作者另闢蹊徑,選擇自定義的格式來書寫語言文件或者配置,典型代表就是格雷科技和 Simple Achievements 模組。
格雷科技採用了原版配置文件格式來載入語言文件,且載入位置放置在遊戲主目錄下。下圖為部分語言文件截取:
languagefile { S:"Dirty Water.name"=髒水 S:bc.trigger.gregapi.energy.air.capacity.empty=空的 (ENERGY.AIR) S:bc.trigger.gregapi.energy.air.capacity.full=滿的 (ENERGY.AIR)}
其次是 Simple Achievements 模組書寫的任務書,這部分配置同樣沒有限定編碼,需要轉換成 Unicode 字元,或者採用 GB 編碼書寫才可以正常使用。
下圖為其部分截取,作者並未為此添加 try catch
結構,如果書寫錯誤,遊戲會啟動崩潰。
--== 神秘農業 ==--| |資源始終是有限的,作物才能滿足你的需求::1合成一個鐵種子::0合成一個鑽石種子::0使用離魂塊獲得一個怪物靈魂碎片::0使用生長水晶和生長加速器::0合成一個大師級的注魔水晶::0合成一個終極蘋果::0合成一個終極防具::0::1::1::1
未完待續
順便吐槽下,知乎居然是我所見過唯一一個把 Java Properties 語法渲染正確的地方,但只限編輯時候的預覽,發布以後又……變回去了……
無論我用 GitHub,還是編輯器 Atom,都渲染是錯的……
推薦閱讀:
※漢譯英中的那些「大坑」,你掉進去過嗎?
※簡潔譯文
※歌詞翻譯——約翰·丹佛《Annies Song》
※AI 翻譯能夠取代人類?微軟黃學東:我們仍在感知智能和認知智能的過渡期
※[翻譯] 一生之盟