Skip to content

Instantly share code, notes, and snippets.

@SiongSng
Last active April 5, 2022 05:48
Show Gist options
  • Save SiongSng/a65579f2a6a1a1ccd47436d7133e91cf to your computer and use it in GitHub Desktop.
Save SiongSng/a65579f2a6a1a1ccd47436d7133e91cf to your computer and use it in GitHub Desktop.
RPMTW Homemade Minecraft Translation System Technical Note

RPMTW 自製 Minecraft 翻譯系統 技術說明

目標

專為 Minecraft 設計的翻譯系統,支援多種語言,打造出一個對使用者以及譯者皆有善的平台。

進度

此專案從 2022/3/7 正式動工,理想是在 2023/1/1 能將所有功能全數完工。

總進度:約 16%

基礎設計:100%
API:100%
伺服器腳本:5% 函式庫 (Dart/Kotlin): 76.4%
翻譯器端 (UI):1%
遊戲端: 0%

翻譯格式

  • 一般格式

最普遍的翻譯格式,使用資源包載入,檔案路徑為 assets/<namespace>/lang/<langCode>.json

namespace -> 命名空間 langCode -> 要載入的語言代碼,例如繁體中文 (台灣) 為 zh_tw

Patchouli 官方文檔
有些模組作者會開啟 i18n 的選項,可直接透過一般格式來載入翻譯,但仍有許多作者是直接寫死在 json 裡面,如果直接覆蓋很容易遇到因版本、翻譯錯誤等因素造成的衝突問題,因此 RPMTranslator 預計會使用自訂的格式載入,再透過模組去將這些內容顯示到遊戲內 (mixin)。

預計的翻譯鍵格式為
patchouli.<namespace>.<bookName>.content.<fileFolder>.<fileName>.<field>

參數介紹:

  • namespace
    模組的 namespace
  • bookName
    書本名稱,例如 Botania 模組中的 lexicon (植物學)
  • fileFolder 該檔案所在的資料夾,常見的有 entriescategoriestemplates
  • fileName 該條目/類別的檔案名稱,例如 test_entry.json 的 fileName 就是 test_entry
  • field
    原先在手冊內使用的 key ,例如該手冊的內容長這樣,那 field 就是 namedescription,非文字的內容或不可翻譯不算
{ 
    "name": "Test Category",
    "description": "This is a test category for testing!",
    "icon": "<iconPath>"
}

例外的格式
譬如說該手冊的標題、子標題... 例如標題的翻譯鍵格式是 patchouli.<namespace>.<bookName>.addon.title

  • 自訂資源

例如圖檔、模組自訂的檔案、自訂的手冊都會使用此方式載入,檔案路徑會在 assets/<filePath>,若模組無法使用以上這些方式載入,預計會透過模組端去 mixin 模組來載入內容,或自己開發一個透過 java class/field 的名稱去辨別的特殊載入方式。

filePath -> 資源所在檔案路徑

資料庫儲存方式

  • ModSourceInfo 模組語系資訊

代表一個模組所有文字格式的原始語系資訊。
儲存了檔案所屬的模組資訊 (namespace、RPMTW 資料庫的模組 UUID),此模組包含的原始語系檔案。
範例格式:

{
    "uuid": "<uuid>",
    "namespace": "mod_a",
    "modUUID": "<所屬模組的 UUID (該模組必須收錄於 RPMTW 資料庫,可以為 null)>",
    "patchouliAddons": ["title", "landingText..."]
}
  • SourceFile 原始語系檔案

代表一個文字格式的原始語系檔案。
儲存了檔案路徑、檔案格式、檔案中包含的原文條目。

  • path
    檔案路徑 對應於 repository 儲存的原文路徑
    Patchouli 手冊的路徑類似這樣 assets/<namespace>/patchouli_books/<bookName>/.../....json
  • type
    檔案格式

gsonLang -> 1.13 以上版本所使用的格式
minecraftLang -> 1.12 以下(含)版本所使用的格式)
patchouli -> Patchouli 手冊的格式
plainText -> 純文字格式,每行文字為一個原文條目,原文條目中的 key 則使用原文內容的 md5 雜湊值

  • sources
    檔案中包含的原文條目

範例格式:

{
    "uuid": "<uuid>",
    "sourceInfoUUID": "<此檔案所屬的模組的原始語系資訊UUID>",
    "path": "assets/<namespace>/lang/en_us.json",
    "type": "gsonLang",
    "sources": ["SourceText1","SourceText2..."]
}
  • Translation 譯文

儲存了譯文,該譯文的譯者、投票資訊。
範例格式:

{
    "uuid": "<uuid>",
    "sourceUUID": "<此翻譯所屬的原文條目>",
    "content": "你好,世界!",
    "translatorUUID": "<譯者的UUID>",
    "language": "zh-TW"
}
  • SourceText 原文

代表一個翻譯的條目
儲存了原文內容、翻譯鍵、翻譯內容、用到此原文的遊戲版本。
範例格式:

{
    "uuid": "<uuid>",
    "source": "Hello, World!",
    "key": "gui.mod_a.title",
    "gameVersions":  ["1.16", "1.18"],
    "type": "general"
}
  • TranslationVote 翻譯投票

代表對譯文的投票
其中 type 中的 up 代表贊成,down 代表不贊成。
範例格式:

{
    "uuid": "<uuid>",
    "type": "up",
    "translationUUID": "<要投票的譯文的UUID>",
    "userUUID": "<投票者的UUID>",
}
  • Glossary 詞彙表

用於儲存專有名詞、用語、Minecraft 標準化譯名...,例如 Creeper 是指苦力怕而非爬行者
參數說明:

  • term 詞語名稱 (原文)
  • translation 詞語譯文
  • description 關於此詞彙的描述
  • language 此詞彙所屬的語言
  • modUUID 此詞彙所屬的模組 UUID,若為 null 則代表全域詞彙(任何地方都可使用)
    範例格式:
{
    "uuid": "<uuid>",
    "term": "Creeper",
    "translation": "苦力怕",
    "description": "常見的敵對生物之一,它們悄無聲息地接近玩家,當距離目標3格內就會開始爆炸。由於它們獨特的外表和殺傷力以及破壞地形的能力,苦力怕成為了 Minecraft 的象徵標誌之一,遊戲內外都很受歡迎。",
    "language": "zh-TW",
    "modUUID": null
}
  • Comment 評論/留言

用於評論翻譯內容、討論翻譯...
參數說明:

  • content 評論的內容,可使用 Markdown 格式,可以使用 @ <userUUID> 來標記別人 (在 UI 中不會顯示 UUID,而是 username)
  • type 評論的類型,如果是評論翻譯則為 translate
  • userUUID 發送此評論的使用者 UUID
  • parentUUID 如果評論類型是 translate 則它的父項為 SourceText, 如果類型是 wiki 則父項為 MinecraftMod.
  • createdAt 此評論第一次發送的時間
  • updatedAt 此評論最後編輯的時間
  • isHidden 是否隱藏此評論,當評論被使用者按下刪除按鍵,就會被隱藏而非從資料庫完全移除資料,將會於一週後完全移除資料
  • replyCommentUUID 是否有回覆其他評論,若有則返回該評論的 UUID,否則為 null 範例格式:
{
    "uuid": "<uuid>",
    "content": "@<tagUserUUID>\n不錯的翻譯!\n但有個字打錯了,不是 `醬魂` 應該是 `匠魂`,麻煩修正一下! 謝謝",
    "type": "translate",
    "userUUID" : "<addCommentUserUUID>",
    "parentUUID": "<SourceTextUUID>",
    "createdAt": "<timestamp>",
    "updatedAt": "<timestamp>",
    "isHidden": false,
    "replyCommentUUID": null
}
  • TranslateStatus 翻譯狀態

{
    "modSourceInfoUUID" : null,
    "totalWords": 1000,
    "translatedWords": {"zh-TW": 10, "zh-CN": 7},
    "lastUpdated": <lastUpdatedTime>
}
  • TranslatorInfo 譯者資訊

{
    "uuid": "<uuid>",
    "userUUID": "<userUUID>",
    "translatedCount": 1050,
    "votedCount": 10
}
  • TranslateReport 翻譯報告

{
    "translators": [<a>, <b>, <c>...],
    "start": <startTime>,
    "end": <endTime>,
    "limit": 50,
    "skip": 0,
    "total": 32
}

打包檔案

  • 資源包

主要用於處理自訂格式的翻譯檔案,預計使用 Github Action 進行打包,每當譯文更新時就會自動打包一次,翻譯內容會由伺服器端自動推送到儲存自訂翻譯用的 repository。

  • API

API 參數說明

  • format 要匯出的格式
  • language 語言代碼
  • mods 要取得哪些模組的語系檔案,格式為該模組的 namespace

取得指定模組的所有一般格式語系翻譯檔案
範例: https://<apiHost>/translate/export?format=json&language=zh-TW&version=1.18&namespaces=<modA>,<modB...>

{
    "key1": "translation1",
    "key2": "translation2",
    "key3": "translation3"
}

取得指定模組的所有純文字格式語系翻譯檔案
範例: https://<apiHost>/translate/export?format=plainText&language=zh-TW&version=1.18&namespaces=<modA>,<modB...>

{
    "assetsPath1": "<translation>",
    "assetsPath2": "<translation>"
}

取得指定模組的所有Patchouli 手冊語系翻譯檔案,詳細定義可參考最上方的手冊翻譯格式
範例: https://<apiHost>/translate/export?format=patchouli&language=zh-TW&version=1.18&namespaces=<modA>,<modB...>

{
    "patchouli.<namespace>.<bookName>.content.<fileFolder>.<fileName>.<field>": "translation1",
    "patchouli.<namespace>.<bookName>.content.<fileFolder>.<fileName>.<field>": "translation2",
    "patchouli.<namespace>.<bookName>.content.<fileFolder>.<fileName>.<field>": "translation3"
}

串連版本管理 (git)

會有一個 repository 來儲存原文內容,一般格式與 Patchouli 手冊格式的檔案會透過腳本上傳,自訂資源則是透過人工手動上傳或實現特定類型的自動化處理。
伺服器會定時每一小時檢查是否有更動,若有更動將會開始同步新的原文

遊戲版本

若遇到一個模組有多個遊戲版本,將會採用相同的翻譯,判斷條件為

  • 屬於同一個模組(模組的 namespace 必須相同)
  • 翻譯鍵必須相同
  • 原文必須相同 在實際的翻譯界面中會直接忽視版本選項

爬蟲腳本

將會開發一個工具,可以自動在網路上爬取模組的原文、手冊檔案,並自動更新至最新版,預計在 Github Action 上執行,目標覆蓋模組平台上有上架的所有模組
因為怕 RPMTW 伺服器承受不住,偷微軟拔拔的資源來用

預計從 CurseForge、Modrinth 這兩個模組平台上擷取
平台第一開始運作的時候會優先爬取熱門模組的檔案,接著會定時從 CurseForge 與 Modrinth 爬取新建立模組的資料
由於 CurseForge 的新模組數量較多,因此預計每 12 小時(半天)從 CurseForge 爬取一次,Modrinth 則是每 24 小時 (一天)

備註:由於 CurseForge 將於 2022 四月份停止舊版 API 的運作,新版的 API 將不會讓所有模組可供下載,如果模組作者有關閉被 API 擷取的設定,變成說需要手動新增,使全面覆蓋增加難度。

遷移腳本

由於 RPMTW 第一開始翻譯內容在 Crowdin 上,因此此系統開發完成後,勢必要將之前的翻譯內容、貢獻紀錄全部遷移至此系統

TODO:實作方式

一般格式

Patchouli 手冊格式

自訂資源

載入翻譯

遊戲內翻譯

  • 物品類
  • 實體類
  • GUI 類
  • 手冊類

專有名詞

Class => 物件導向程式語言的一種構造,是建立物件的藍圖,詳情可參考網路上或維基百科的介紹 GUI => 圖形使用者介面,簡單講就是視覺化的界面 Mixin => 簡單來說就是混合 class,可以修改、替換 class 中的內容,達成一些模組/Minecraft沒有提供的功能,Minecraft 中的 Mixin Namespace -> 命名空間,模組 ID 也是命名空間的一種

相關議題

伺服器: RPMTW/RPMTW-Server#8
API 拉取請求:RPMTW/RPMTW-Server#9
遊戲端: RPMTW/RPMTW-Platform-Mod#145

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment