gettext · po · mo · engine · tutorial
Cách dịch file .po và .mo của game (gettext)
.po là bản nguồn để chỉnh sửa; .mo là danh mục đã biên dịch mà game thực sự nạp, nên chỉ sửa `.po` thì không đổi gì cả. RuneTranslate đọc cả hai, bảo vệ placeholder %s và {count}, chỉnh dạng số nhiều cho ngôn ngữ của bạn, và ghi lại một file .mo chạy được.
Nếu bạn mở thư mục game và thấy một thư mục locale đầy file .po và .mo, game được bản địa hóa bằng gettext, và giờ RuneTranslate có thể dịch nó trọn vẹn, từ lúc tạo dự án tới bản dựng chơi được.
Hướng dẫn này giải thích hai loại file đó thực sự là gì, vì sao sửa cái bạn đọc được thường không đổi gì trong game, và cách dịch chúng đúng cách.
.po và .mo: điểm khiến ai cũng vấp
Chúng là hai dạng của cùng một danh mục:
- Portable Object, tức
.po. Một file văn bản thuần mà bạn mở được bằng bất kỳ trình soạn thảo nào. Mỗi mục gồm mộtmsgid(chuỗi gốc) và mộtmsgstr(bản dịch của nó). Đây là file mà người dịch làm việc. - Machine Object, tức
.mo. Một phiên bản nhị phân đã biên dịch của cùng dữ liệu, sắp xếp để game tra một chuỗi tức thì. Đây là file mà game đang chạy đọc.
Điểm cuối cùng đó là thứ làm mất của người ta cả buổi chiều. Bạn có thể dịch từng dòng của .po, lưu, mở game và không thấy thay đổi gì, vì game không bao giờ mở .po. Nó nạp file .mo nằm cạnh, vốn vẫn chứa văn bản cũ. Thông thường bạn biên dịch cái này sang cái kia bằng msgfmt trong bộ công cụ GNU gettext. RuneTranslate ghi cả hai, nên không có bước biên dịch riêng.
Thư mục game gettext trông như thế nào
Bố cục chuẩn là một thư mục locale đặt theo mã ngôn ngữ:
game.exelocale/en/LC_MESSAGES/mygame.molocale/fr/LC_MESSAGES/mygame.molocale/de/LC_MESSAGES/mygame.mo
Thư mục LC_MESSAGES là một phần của quy ước, không phải lỗi, và mygame là text domain, tức cái tên game yêu cầu khi nạp chuỗi của nó. Nhiều game dùng dạng phẳng hơn, như lang/fr.po, và cách đó cũng chạy được.
Game nào dùng gettext?
Gettext khởi nguồn từ thế giới Unix và trở thành cách bản địa hóa mặc định cho phần mềm viết bằng C, Python và mọi thứ dựa trên GTK. Với game, điều đó nghĩa là:
- Game Python và pygame
- Solarus, SDL và các engine C/C++ khác
- Game bản địa hóa qua Poedit, Weblate hoặc Crowdin, đều nói PO một cách tự nhiên
- Một số dự án Godot, hỗ trợ PO làm nguồn dịch
Nó phổ biến hơn nhiều ở game indie phương Tây so với game Nhật: các engine Nhật như Kirikiri và RPG Maker giữ văn bản trong định dạng riêng của chúng.
Dịch game gettext bằng RuneTranslate
- Trỏ RuneTranslate vào thư mục game. Thư mục có
locale/,lang/hoặci18n/bên trong. Việc phát hiện đọc các byte thực chứ không tin đuôi file, nên một file.mothật ra là file cảnh 3D sẽ bị bỏ qua thay vì tạo ra dự án hỏng. - Xem phần tóm tắt. Trước khi tạo dự án, bạn thấy số danh mục, tỷ lệ giữa
.mođã biên dịch và.ponguồn, những ngôn ngữ game đã có sẵn, và tổng số chuỗi. - Chọn ngôn ngữ và nhà cung cấp. Google hoặc DeepL miễn phí, hoặc khóa DeepL, OpenAI, Claude hay DeepSeek của riêng bạn, xem so sánh nhà cung cấp.
- Xem lại, rồi xuất. Bạn nhận một bản sao đầy đủ của game với bản dịch đã đặt vào.
Hai thứ âm thầm làm hỏng bản dịch gettext
Placeholder. Một msgid là một chuỗi định dạng, không phải văn xuôi hoàn chỉnh. Hello, %s! có tên người chơi được thay vào lúc chạy. Trình dịch máy hay làm rơi %s, tách nó bằng khoảng trắng, hoặc, với dạng đặt tên của Python %(player)s, dịch luôn từ bên trong. Hậu quả không chỉ là thẩm mỹ: một %s bị rơi làm lệch mọi đối số phía sau, và hầu hết engine sập hẳn. RuneTranslate che mọi placeholder nó tìm thấy (%s, %d, %(player)s, {count}, {0}) trước khi văn bản tới nhà cung cấp, rồi khôi phục sau đó.
Dạng số nhiều. Tiếng Anh có hai (“1 coin” / “5 coins”). Tiếng Nhật và Thổ Nhĩ Kỳ có một. Tiếng Nga và Ba Lan có ba. Danh mục khai báo số dạng nó dùng trong phần header, và nếu con số đó không khớp với ngôn ngữ, gettext tra vượt quá cuối danh sách và âm thầm quay về bản gốc chưa dịch. RuneTranslate chỉnh các dạng cho khớp ngôn ngữ đích của bạn và ghi lại header cho tương ứng.
Vì sao bản xuất ghi danh mục hai lần
Xuất ra cho bạn cả một locale mới đúng chuẩn, locale/<ngôn ngữ của bạn>/LC_MESSAGES/, lẫn một bản cập nhật của danh mục mà game đã nạp sẵn.
Cái thứ hai mới là thứ khiến bản dịch thực sự hiện ra. Game quyết định nạp locale nào theo những cách mà không thứ gì trong thư mục tiết lộ: nó có thể đọc ngôn ngữ hệ điều hành của bạn, một file cấu hình, một cờ launcher, hoặc đơn giản là viết cứng một cái. Thêm danh mục tiếng Thổ Nhĩ Kỳ vào game chỉ bao giờ yêu cầu tiếng Anh sẽ để bạn ngồi nhìn một bản dựng dường như không đổi. Ghi đè danh mục mà nó thật sự nạp sẽ xóa bỏ chuyện đoán mò. Cả hai chỉ xảy ra trong bản sao đã xuất, còn thư mục game gốc của bạn không bị chỉnh sửa.
Chuỗi bạn chưa dịch giữ nguyên bản dịch mà game đã có sẵn, nên một dự án dịch dở không bao giờ xóa trắng phần bản địa hóa của chính nhà phát triển.
Những gì cách này không làm được
Chỉ văn bản đã nằm trong danh mục mới dịch được. Nếu nhà phát triển viết cứng một chuỗi thay vì cho đi qua gettext, nó không có trong .po hay .mo, và không công cụ PO nào tìm thấy nó ở đó. Đó là giới hạn của định dạng, không phải của công cụ.
RuneTranslate cũng coi msgid là văn bản nguồn, đúng như chính gettext quy định. Một vài dự án để msgid tiếng Anh ở đó còn ngôn ngữ gốc thật nằm trong msgstr; những dự án đó sẽ được dịch từ tiếng Anh.
Cuối cùng, nếu game chạy trên một engine mà RuneTranslate hỗ trợ trực tiếp, như Godot, Unity, Ren’Py, thì engine đó xử lý nó, nên phần văn bản còn lại của game cũng với tới được. Danh mục .po của game Godot là ngoại lệ: đường Godot đọc các bảng dịch CSV của nó nhưng chưa đọc .po.
Trước khi chia sẻ
Mở bản dựng đã xuất và đọc thử vài màn hình. Menu và nút là cách kiểm tra nhanh nhất, vì đó là nơi phần lớn văn bản danh mục nằm. Nếu có gì sai, như game không được phát hiện, bản xuất không chạy, hoặc văn bản vẫn là ngôn ngữ gốc, hãy báo từ menu Trợ giúp của ứng dụng hoặc trên Discord. Tải RuneTranslate và mở danh mục đầu tiên của bạn.
Bài liên quan
Sẵn sàng thử RuneTranslate?
Gói miễn phí mở khóa mọi engine và mọi nhà cung cấp dịch trừ RuneLingua. Supporter ($3.99/tháng) mở khóa tốc độ tối đa.
Tải cho Windows & Linux
