Unity I18N 小探

2022-01-08 indienova

前略

軟體要分發給使用不同語言和處於不同地區的人員,需要從多個方面處理從而使這些人能以自己文化背景下熟知的方式來使用。這包括了最基本的文本的翻譯,也有一些通用習俗和約定的問題(如不同國家和地區的日期時間的表示等)。而本地化(L10N)和國際化(I18N)就是專門來解決這些問題的。

要很好的解決這一問題,往往需要多方面的人員參與,既有軟體設計者,有程序開發人員,也需要翻譯人員甚至翻譯測試人員,而要把這些人組織起來完成工作,需要的不是只面向一兩方的 API 和代碼,良好的本地化團隊往往需要面面俱到。

傳統的解決方案

最知名的解決這些問題的一套系統,是 GNU Gettext。這是一套上世紀九十年提出的用以解決自由軟體的國際化和本地化問題的工具套件。在 gettext 裡,制定了翻譯文本的組織方式(po/pot),提供了從程序源碼提取字符串並生成翻譯模板的工具(xgettext)。在這些之外,gettext 和其社區也發展出了面向翻譯人員的翻譯內容管理系統(Weblate),以及有用於完成翻譯工作的專門軟體(Poedit)。Gettext 也提供 DIff/Merge 工具來解決翻譯內容的漸進式更新的需求。

遊戲具有其特殊性

遊戲作為最特殊的軟體之一,對於本地化和國際化也有著屬於它的苛刻要求:

傳統方案在遊戲開發上的擱淺

GNU Gettext 天生不適合遊戲開發中的多語言問題,它只處理文本資源,原版的 gettext 甚至只能從源碼中收集多語言數據,這顯然只適合普通軟體的需求,卻很難滿足遊戲開發的需要。

對於遊戲而言,只有處於 UI 界面裡的數據,會比較切近 Gettext 的需求場景,然而在很多成熟引擎下,這些數據也往往位於特定的 UI 聲明文件內(在 Unity 裡這些數據處於 scene/prefab 文件中)而不是原始碼中。至於佔遊戲內比重更大的其他需要多語言的數據,更是會分散在各種特定格式的數據之中。在 Unity 下,關卡數據可能存放在 prefab 中,角色/物品這類的設定性的文檔數據可能處於 ScriptableObject,甚至這些數據都保存在別的序列化文件內(諸如最常見的 csv/xls 或者 xml/json,再或者自定義的二進位數據文件)。

一點牢騷

誠然,遊戲的多語言問題在如今看起來似乎並不算多麼麻煩的事情,即使是考慮多語言輸入的問題等,在遊戲編程精粹系列中,很早就有論證這些問題的文章[1]。而如今面臨的問題似乎同前輩們沒什麼兩樣。相比遊戲開發中的其他領域(尤其是圖形學),這個話題好像沒什麼可談的。即使我在文章開頭的前幾段論述了這個問題多麼棘手,但貌似現成的解決方案存在著一大堆。而在之前,我在一個遊戲開發的群裡提到這個話題時,被人指出是在浪費時間,對方建議我應該多研究研究圖像學之類的如何讓遊戲畫面看起來漂亮的技術或者去鑽研鑽研遊戲敘事來讓遊戲內容變得好玩。:-)

但事實真的如此了嗎?

從 key-value 表到 I2 Localization

實現一個簡單的多語言系統是十分容易。一個最小可用的多語言系統大體需要如下的部分:

字典表,記錄了所有需要多語言處理的字符串,key 往往是對應內容的英文,而 value 則是對應語言的翻譯內容。這些內容在文件系統裡通常以 json/csv/xml 這樣的形式存在。

一個形似 Localization.Get(key, lang) 的函數,用來在 Runtime 返回指定 key 和語言所對應的翻譯內容

一套加載機制,用來在對應語言環境下加載對應的多語言文件

一套通知機制,使得系統能得知語言被切換過。這個系統往往是通過訂閱對應的事件來完成通知。

實現這些東西的代碼花費不了幾個小時就能完成。然而仔細思考就會發現要拿這套系統來用的舒爽,就十分的困難了。

一些待解決的問題:

字體

缺乏對其他多媒體資源的支持

少數語言對文字排版的需求

翻譯人員對接

就字體問題而言,對於歐美不少語言,一個字體文件往往很容易就能解決大部分語言的字體問題,但是一旦遇到中日韓,就會變的十分頭疼。所幸很多系統和引擎都提供字體的 fallback 機制,只要合理利用這套機制,使得字體問題也能輕鬆得以解決。再或者乾脆再弄個字體文件的字典表,替換文本的同時也替換對應的字體。

其他媒體資源的支持問題,在別的本地化系統裡出場率遠遠低於遊戲。這是因為遊戲裡很容易產生那些需要多語言處理的多媒體資源。一個藝術字標題或者一個圖片按鈕,亦或是人物對話的配音。不過我們照貓畫虎,按照如何處理文本,也同樣來處理多媒體資源就行。這裡比較棘手的問題是,相比文本這種往往一個語言的所有多語言資源都塞到一個文件內就能解決,多媒體資源在文件系統方面就會變得十分複雜。而一旦這些問題和引擎的資源打包系統耦合在一起,要設計出一套好用又節省內存的方案並不簡單。

少數語言文字排版需求通常是指阿拉伯文字這種特有的從右往左排版的情況。相關資料十分豐富,我也不再贅述。不過要注意的是如果涉及到多語言混排,基本很難做對。如果需要相關信息, W3C i18n 小組下的內容對此會十分有幫助[2]。

和翻譯人員對接部分,目前來看,業界主要以通過翻譯外包的形式將本地化任務託管給其他公司。這些公司往往要求提供 key-value 形式的多語言數據文件(如 xml/csv/xls)給他們。如果你在第一步的時候就已經決定用這樣的方式來組織你的多語言文本,正好可以一勞永逸。但第一步選擇了別的方案可能就需要提供導入導出功能。

Unity 下有現成的處理好這些問題的框架嗎?當然,小節標題後邊的部分說的就是它 —— I2 Localization,Unity 資源商店裡頂頂有名的國際化插件。

關於它的介紹我不打算多花筆墨,讀者可以直接閱讀它的文檔來一窺究竟。I2 Localization 基本上很好的解決了上邊提到的問題。而且他還有不少十分有用的特性。

如果你覺得你的問題已經解決了,那麼你可以停止閱讀趕緊去購買插件/自己實現類似系統了。但是,假如你仍然抱有困惑,或者好奇多語言系統還有哪些問題和改進的地方,那麼我將在下邊兩個小節內盡力給你一個滿意的答案。

Rosetta - 關於本地化問題的反思和實現

雖然上一節提到的 I2 Localization 十分優秀,但是依然有不少問題有待解決:

key-value 形式的多語言系統天然違反遊戲開發的流程和直覺

繁雜而未經設計的翻譯文本組織文件進一步導致了功能上的缺失和解決這一問題的困難

對於漸進式開發和需更新維護的遊戲缺乏合適的 Merge 機制

和翻譯人員對接的部分過於的粗暴反而埋下多語言問題的禍根

在 key-value 模式下,你需要為每一個多語言文本分配一個 key,這個 key 往往是一個英文單詞或者短語。這對於通常的軟體來說十分夠用。本來大部分需要多語言處理的文本都是按鈕上的文本或者窗體的標題。但是對於遊戲來說,這類的數據佔比十分低,而更多的多語言文本是遊戲內各種物品技能裝備等的說明和關卡裡的人物對白。這些文字全都篇幅不短。如果你的遊戲內有書籍系統,那麼更是意味著對應的是一篇短文。這時候為這些數量眾多的大段文字去分配一個個不會衝突的 key 是十分頭痛而又麻煩的。可能最後這些 key 變成了一個個奇怪的縮寫標記(比如一個 l10e2s2 這樣的神秘字符串代表著關卡十中第二個事件上的第二段對話)。而開發人員在瀏覽遊戲工程文件時,看著充滿神秘字符串的界面,還要去回頭查找對應的是什麼文本。

你可能會說,既然如此我乾脆用需要翻譯的文本本身來作為 key 好了。噔噔噔!恭喜你,想出了幾十年前那些自由軟體黑客們就已經構思並實現了的點子—— GNU Gettext。GNU Gettext 的一大特點就是用文本本身做為 key。整個軟體選取一個開發語言,開發時所有的文本均按該語言撰寫。然後這些文本數據通過特定的標記(往往是通過 Gettext 的獲取多語言文本的函數),在軟體編寫完成後,通過特定的程序掃描整個軟體的代碼,從中將這些被標記的靜態文本挨個收集起來。

除此之外,GNU Gettext 所定義的用於組織多語言文本的特定文件格式 po/pot(po 文件用來組織翻譯文本,而 pot 文件則是翻譯文本模板文件),也解決了上邊的問題二。相比大部分情況下使用某種現成的序列化文本格式來組織多語言文本, po/pot 文件考慮了更多多語言工作中需要解決的問題,比如 pot 文件裡支持針對單個文本片段的多種形式的注釋:

1). #後緊接著空格符的注釋內容,是翻譯者添加的注釋;
2). #後緊接著.的注釋內容,是 xgettext 從原始碼中提取出的注釋內容(通過 xgettext --add-comments 選項);
3). #後緊接著:的注釋是待翻譯語句在原始碼中的位置信息;
4). #後緊接著,的注釋是 msgfmt 程序專用的 flag;
5). #後緊接著|的注釋是這條待翻譯語句之前的相關翻譯信息;

而大部分使用通用序列化文本格式的本地化系統,基本不會去考慮這些情況。更何況這些部分還需要各種軟體和工具的支持,而繁雜的翻譯文本組織方式更是讓想支持這些也變得困難,除此之外,對於問題三,大部分使用通用的序列化文本格式的系統也幾乎不曾考慮,而 po/pot 文件很早就開始考慮這些問題並提供支持。GNU Gettext 作為一個存在幾十年的系統,期間積攢了數量眾多的處理這些問題的工具和軟體。

問題四要指出的是,目前的外包形式的解決辦法,遊戲開發商難以較為積極的參與到多語言工作中,其次對於遊戲來說往往存在著大量需要多語言處理的多媒體資源,這些資源通常還涉及到美術和配音人員。最後的結果就是要麼把這些也一同丟給本地化外包公司,要麼就組織兩套系統然後花費額外精力來維護兩個系統間的周轉。其次完全交給外包公司也讓遊戲本地化工作問題的反饋難以及時跟進(實際上大部分遊戲根本不會提供多語言測試版或者僱傭多語言測試人員)。這一結果使得遊戲開發和多語言工作過分割裂,最後很容易產生各種翻譯問題並很難解決(比如被遊戲視頻 UP 主神奇陸夫人常常念叨的 Artifex 公司作品的翻譯迫擊炮問題)。

我個人認為解決這一問題的辦法是遊戲開發公司應該把多語言工作的主動性掌握在自己手裡,通過自己部署翻譯管理系統,將多語言工作涉及到的人員全部聚集在一起。同時遊戲應該提供多語言測試工具並提供多語言測試版本,然後讓多語言測試人員(可以是公司招聘的人員,也可以是通過發布測試版來讓對應語言的玩家充當)將多語言問題通過這個系統提交到翻譯內容管理系統上。在這一體制下,翻譯人員不再是外包給翻譯公司,由公司內部分配然後運作在其內部系統上,而是翻譯公司外派人員在遊戲開發公司的系統上來工作。這樣一來無論是監管還是進度追蹤抑或是翻譯問題的追蹤和處理都會變得簡單而透明。同時也可以將配音和美術人員也統一在這一框架下。而且如果是大公司的話,這裡邊的翻譯工作人員也可以是公司自己內部的員工。當然,這些構思只是出於一個技術人員在軟體開發角度的考量。可能涉及到商業和管理方面的問題並不一定會達成(比如翻譯公司拒絕外派人員而堅持使用自己的內部系統)。另外不少獨立遊戲公司的本地化工作都是通過 Mod 系統然後讓對應語言的玩家社區來完成。如果有這樣的工具來提供給玩家社區也是很不錯的。

從上邊討論可知, GNU Gettext 的解決方法優於 key-value 方案,並且 gettext 還有配套的開源的翻譯內容管理系統(比如 Weblate),這意味著問題四也可以方便的解決。

然而原本的 GNU Gettext 並不十分符合遊戲開發。比如遊戲的多語言數據基本上不會存在於原始碼中。其次遊戲存在眾多的需要多語言處理的多媒體資源,而那些配合 gettext 的 TMS 往往只支持文本內容的處理,前略裡已經完整的講過這個問題了。如此一來,直接把 GNU Gettext 搬過來只會水土不服。我們需要一個適用於 Unity 和遊戲開發的使用和 GNU Gettext 類似理念的全新的本地化框架。

簡單來說,我們需要:

設計良好的組織多語言數據的文件格式

通過簡單標記就可以自動化收集多語言資源的工具

一個運行時的加載這些多語言數據文件,並在恰當的時機將資源替換成對應語言的內容的系統

對翻譯人員提供符合他們工作習慣的工具來完成翻譯工作


這一想法最後誕生的結果就是 Rosetta。

Rosetta 採取了和 GNU Gettext 相同的組織翻譯文本的方式。這意味著我們可以在文本資源上充分利用 GNU Gettext 現成的工具鏈。除此之外還處理了多媒體資源的多語言問題。

而在問題二上 Rosetta 充分利用了 C# 的 attribute 和 reference 這兩個語言特性,通過 i18nAttribute 來標記需要多語言處理的資源,然後通過反射來搜集這些數據並生成對應的多語言模板文件。

對於問題四 來說,Rosetta 暫時只能依託於 GNU Gettext 現有的工具來解決。目前我正計劃在 Rosetta 1.0.0 版本的時候,提供一個和 Rosetta 搭配的適用於遊戲開發的翻譯內容管理系統:RosettaServer。

只有這些了嗎

在遊戲本地化問題上,是否還有其他前瞻性的東西,我想很多人和我一樣好奇。而思考這些問題則花去了我十分多的時間(實際上這些問題在一年多以前就以各種片段的形式存在於我腦海而直到最近 Rosetta 的第一個可運行版本完成開發,我才開始著筆寫下這篇文章)。有幸於我對小眾遊戲的關注和經常看到好玩的但沒有中文的遊戲/軟體會動漢化他的念頭的緣故。我得以見到很多遊戲提供的翻譯系統,也接觸到了一些處於這個領域十分前沿的問題。

AVG.js 和 huozi.js

AVG.js 是我的一位朋友 Icemic 在 HTML5 平臺上實現的 galgame 開發套件。這套工具裡最讓我印象深刻的是專為遊戲內中日韓文字排版而實現的排版引擎 huozi.js。

實際上遊戲開發對文本顯示的要求僅僅處於能看這一階段。而去思考並實現對應語言的規範排版需求的系統的情況基本不存在。雖然 W3C 的 i18n 小組一直在做這方面的工作(huozi.js 的開發也有參考他們的中文排版需求),但是遊戲行業對這些毫無興趣。這一方面是因為這個問題本身十分複雜且和多個系統耦合(排版系統和 I18N),另一方面遊戲內往往並不會出現大篇幅的密集文本。然而隨著遊戲的發展,我相信以後不少遊戲都會遇到類似的需求(如上古捲軸5 裡的書籍系統)。

Wayward : 從語義到語法

大部分情況下,我們漢化一段文本的時候,只是在理解它的語義,然後用另一種語言來表達它。但在一些特殊情況下,翻譯工作會涉及到把一門語言的語法翻譯到另一門語言上的問題。

最簡單的情況是單複數的處理。在中文中單複數很少會導致一個名詞發生變化,然而在英語等語言裡,單複數往往會導致名詞發生變動,而在一些語言裡甚至一個兩個三個和複數個都對應著不同的形式(在 I2 Localization 裡有處理這個情況,並且支持最多為七的情況)。

然而名詞的單複數只是這個問題下最簡單的一種情況。在 Wayward 中,翻譯需要處理更為複雜的語法問題,除了名詞單複數外,還有代詞和介詞等。整個遊戲翻譯模板裡有一個很複雜的表格來表述這種轉換:


之所以會存在這種情況是因為,Wayward 是一個 RougeLike 類遊戲,而遊戲內有不少文本來自程序生成。在這時候就自然而然的涉及到語法的問題。翻譯工作也不僅僅是翻譯語義,而要考慮對應語言的語法。

而可以遇見的未來是,由於過程生成技術在遊戲裡越來越廣泛的使用,遊戲文本的過程生成也必然會越來越盛行。以後涉及到語法的本地化工作的情況也會越來越常見。

配音和口型生成

幾乎所有的 3A 遊戲都提供配音,而這時候又涉及到 3D 遊戲使用對應語言的配音時人物模型口型的問題。很早以前遊戲可能會考慮採取捕捉或者人工調整來生成口型數據,而如今更多的是使用算法通過文本來生成對應的口型數據。這樣一來又引入了一種全新的需要多語言處理的數據。

結語

以上提到的諸多問題,很多都處於多個領域交叉的情況,甚至你很難界定某些具體問題屬於那個領域。但是你要完成一個遊戲的多語言工作,往往必然會遇到他們。

另一方面,從最早的普通軟體到如今的 3A 遊戲,隨著遊戲技術不停的發展,也會引入越來越多的問題。最早我們可能只需要翻譯文本,之後引入了圖像文件,再後來遊戲配音又引入新的問題(配音和口型)。而可以預見的未來是,隨著過程生成技術流行,很多傳統的對多語言資源的處理也會面臨著翻譯內容變為翻譯規則。而只要遊戲技術不停的發展,遊戲本地化工作也會出現新的待解決的問題,這是本地化和國際化做為一個同遊戲內多個方面耦合的系統的必然結局。

參考

遊戲編程精粹3 - 1.12 1.13

《阿拉伯文字文本布局需求》- w3c 國際化興趣組 阿拉伯文布局特別任務組 


* 本文源自作者的知乎專欄投稿。

相關焦點

  • vue3-TS-使用i18n國際化
    vue3-TS-使用i18n國際化main.ts
  • unity2021遊戲引擎安裝激活並漢化
    的教程,越看越不可思議,unity居然能做這麼多好玩的東西,像槍戰類,模擬類,角色扮演,動作冒險都很震撼。如果你還不了解unity是什麼,可以先看unity介紹文章。讓我們開始搭建unity的環境吧!打開官網下載頁面 https://unity3d.com/cn/get-unity/download ,unity 和 unityHub 這兩個都要下載下來。file選擇個人版本,下載程序
  • Unity3d-C#入門基礎
    ://www.jetbrains.com/resharper/Visual studio開發插件[連接Unity調試]:http://unityvs.comMono編譯器支持什麼:http://www.mono-project.com/docs/about-mono/languages/csharp/你能在unity裡使用的.Net庫:http://docs.unity3d.com/410/Documentation/ScriptReference
  • Unity免費的優質場景資源
    下載地址:https://www.assetstore.unity3d.com/en/?stay#!/content/74965Adam Exterior Environment下載地址:https://www.assetstore.unity3d.com/en/?stay#!
  • 今天擼擼抖音小遊戲平臺上的unity遊戲!
    關於unity小遊戲的釋義:很多玩家小朋友可能不知道什麼是unity遊戲,unity是遊戲引擎的一種,絕大多數我們玩到的手遊都採用的是unity引擎研發。但是unity引擎在最早小遊戲誕生的時候,還無法適配小遊戲。所以小遊戲都是用laya、cocos,最早還有白鷺引擎重新開發的。
  • unity期末--3d卡牌(一)
    unity課程期末作業,要求是製作一款3d遊戲
  • 優化unity地形的曲面細分
    內置算法的問題unity的曲面細分,特別是surface
  • Unity3D 實用技巧 - 分享實時飄動動畫插件
    小編針對實現實時飄動動畫插件進行了簡單的資源收集調研,以及對比淺析。通過控制骨骼系統達到模型的物理飄動,包括頭髮、頭飾、裙子飄動、乳搖系統實現。 可支持平臺:Windows、MAC、iOS and Android Dynamic Bone 下載連結: https://assetstore.unity.com/packages/tools/animation/dynamic-bone-16743這是Unity Chan 官方模型專用插件
  • Unity中可以讓工作變得更輕鬆的技巧
    有很多工具可以拓展你的IDE,其中還有一些小技巧來輔助你。其中一個拓展工具就是JetBrains Resharper,這個軟體十分實用,你在做任何編程的時候都可以用到。這個工具會分解你的代碼,然後再編寫代碼。
  • Unity中BVH骨骼動畫驅動的可視化理論與實現
    比如在 unity 中,導入某個模型時,通常為 T-pose 的姿態,如 unity 娘模型剛導入的時候:這個 Tpose 在 CMU 提供的 BVH 骨骼動畫數據中,第一幀數據也是 T-pose,比如:
  • Guangxi’s ethnic unity and progress work makes marked effects
    Recently, it was learned from the news conference of ethnic unity and progress work in Guangxi that much progress has been made in the work.
  • Unity Android Plugin開發指南
    最後,工程的結構類似這樣:Assets└── Plugins    ├── Android    │   ├── AndroidManifest.xml    │   ├── android_sdk.jar    │   └── res    │       ├── values    │       └── drawable    └── unity_wrapper.dll
  • Unity 實用技巧 - 從實踐中總結經驗
    今天小編與大家共同淺析這些常見的實用技巧。所有的Asset都應該只有一個唯一的版本。如果當我們需要一個分支版本的Prefab、Scene或是Mesh,那麼我們需要制定一個非常清晰的流程,來確定哪個是正確的版本。錯誤的分支應該起一個特別的名字,例如雙下劃線前綴:__MainScene_Backup。Prefab版本分支需要一個特別的流程來保證安全 。
  • Unity shader樹的陰影技巧
    需求與痛點unity的樹有個痛點不論引擎默認還是speedtree shader都是強制 不支持烘焙的。
  • Unity粒子系統小技巧X1
    下面我們就來學幾個跳出去又跳回來的小技巧放心,不會被人打的哈哈哈哈首先我們來看看效果圖幾個方法都是用unity粒子系統裡面的Velocity over lifetime(不知道這是什麼的去學基礎課)第一個方法
  • unity完整項目之滾球小遊戲(完整)
    今天來寫一篇unity開發一個滾小球小遊戲的整體流程。新手操作,大佬莫笑(文章中的代碼可能會因為手誤等原因敲錯,一切以最後截圖為準)。首先打開U3D,創建一個3D新項目。創建之後先保存一下項目,然後新建一個plane,作為遊戲的地板。
  • 更優雅地編寫Unity Shader
    怎麼這麼醜啦!!VS插件嗯,不要慌,百度一下找個插件好了,果不其然很多人吐槽VS寫Unity的ShaderLab如何如何不便,然後順利找到了一個插件= null && strSublimeTextPath.Length > 0)            {                Process.Start(strSublimeTextPath, $"\"{strFileName}:{line}\"");                return true;            }
  • unity 實現物體破碎效果
    現在很多遊戲的效果做的很逼真,那麼如何使用unity相信很多做遊戲開發的想知道,為此下面就給大家介紹下unity 實現物體破碎效果的方法,一起來看看吧。    使用maya等3D工具製作碎塊組成的物體,遊戲中在物體被打碎的時候,首先銷毀原先的物體,然後再用碎片組成的pretab替換,形成的效果則是每個碎塊的加力方式不同,需要通過調整來實現。
  • 程序丨如何將你的Unity代碼整理到一個DLL中?
    …等等其他方面在unity中重命名腳本略微有些蛋疼:在重命名一個腳本之後,你需要在MonoDevelop中關閉它,然後重新打開它,(假設這是一個C#類)重命名才會在原始碼中生效。,你需要動用一點聰明才智去使用它(用谷歌搜索一下,這只是個小問題)。但是C#只是簡單的將interfaces當作一種解耦的編譯兼容方式提供給我們:你需要在設計類的開始階段就使用它們進行解耦。接著可以通過谷歌進行相關內容的擴展了解--因為這個話題太大了,我們這裡點到為止。
  • Unity 自定義Shader GUI
    Unity自帶的屬性繪製可以參考以下文章【Unity Shader】自定義材質面板的小技巧 //foldoutLevel 摺疊頁等級,摺疊頁最低等級為1級(默認1級摺疊頁) //foldoutStyle 摺疊頁外觀樣式(默認第一種),目前有3種 1 大摺疊頁樣式, 2 中摺疊頁樣式, 3 小摺疊頁樣式