文章目錄
- 1.簡介
- 2.核心概念
- 國際化 (i18n)
- 本地化 (l10n)
- POT 文件
- PO 文件
- MO 文件
- 文本域
- 翻譯函數
- 3.主要組件
- 4.使用示例
- 參考文獻
1.簡介
GNU gettext 是一套用于軟件國際化(internationalization,i18n)和本地化(localization,l10n)的工具集,它幫助開發者創建多語言應用程序。
下面大致翻譯自 GetText 主頁的介紹 :
“通常,程序及其文檔信息都是用英語語言寫的,程序運行時同用戶交 互的信息也是英語。這是一個事實,不僅僅 GNU 的軟件是這樣,其他大 部分私有軟件或自由軟件也是這樣。一方面,對于來自所有國家的開 發者、維護者和用戶來說,相互溝通中使用一種通用的語言非常的方便。另一方面,相對于母語來說大多數人并不適應使用英語,而且他 們的日常工作都是盡可能的使用他們自己的母語。多數人都會喜歡他 們的計算機屏幕顯示的英語更少,顯示的母語更多。"
GNU 的 ‘gettext’ 是 GNU 翻譯項目的一個重要步驟,我們依賴于它 來作很多其他的步驟。該軟件包為程序員、翻譯人員甚至用戶提供了一套集成良好的工具和文檔。詳細地說,GNU gettext 提供了一套工具, 能讓其他 GNU 軟件創建多語言信息。這些工具包括一組關于如何編寫程序以支持消息目錄的約定,消息目錄本身的目錄和文件命名組織,支持檢索已翻譯消息的運行時庫,以及一些以各種方式處理可翻譯字符串集或已翻譯字符串集的獨立程序。一個特殊的 GNU Emacs 模式也可以幫助有興趣的各方準備這些集合,或者使它們更新。
gettext 的工作流程是這樣的:比如我們寫一個 C 程序,通常 printf 等輸 出信息都是 English 的。如果我們在程序中加入 gettext 支持,在需要交互的字符串上用 gettext 函數,程序運行就可以先調用 gettext 函數處理字符串,替換當前的字符串。注意是運行時替換。
2.核心概念
國際化 (i18n)
國際化(Internationalization)簡寫為 i18n,其中 i 和 n 分別代表單詞的第一個和最后一個字母,中間的"18"代表省略的18個字母。
國際化是設計和開發軟件應用的過程,使其架構能夠支持多種語言和地區設置,而無需進行重大修改。
國際化的關鍵任務包括:
- 將所有用戶界面元素(如文本、圖標)從代碼中分離出來,使其可以獨立于代碼進行更改。
- 使用通用的日期、時間和數字格式,可以根據用戶的地區設置進行調整。
- 支持多種字符編碼,如 UTF-8,確保可以處理各種語言的輸入和顯示。
- 設計靈活的布局,可以適應從右到左的閱讀順序等不同的文本方向。
本地化 (l10n)
本地化(Localization)簡寫為 l10n,其中 l 和 n 分別代表單詞的第一個和最后一個字母,中間的"10"代表省略的10個字母。
本地化是將國際化的軟件適配到特定地區或語言的過程,涉及翻譯文本和調整功能以符合特定文化和語言的需求。
本地化的關鍵任務包括:
- 翻譯軟件界面、幫助文檔以及其他用戶面向的文本。
- 調整圖形元素,使其符合地區文化的期望和標準。
- 根據目標市場的習慣和法律要求調整軟件功能,如貨幣、稅率計算等。
- 適應地區特有的日期、時間、地址和電話號碼格式。
POT 文件
POT 文件(Portable Object Template)是 GNU gettext 工具鏈中的翻譯模板文件,用于軟件國際化和本地化(i18n/l10n)。
它作為翻譯模板,存儲源代碼中提取的原始字符串(通常是英文),供后續生成多語言 PO(Portable Object)文件使用。
文件頭:元數據(如語言、字符集、復數形式規則等)
msgid ""
msgstr ""
"Project-Id-Version: MyApp 1.0\n" # 項目名稱和版本
"POT-Creation-Date: 2025-04-28 12:00\n" # 創建時間
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" # PO文件修訂時間(模板中通常留空)
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" # 最后譯者(PO文件填充)
"Language-Team: LANGUAGE <LL@li.org>\n" # 翻譯團隊(PO文件填充)
"Language: \n" # 語言代碼(PO文件填充,POT中為空)
"MIME-Version: 1.0\n" # MIME版本
"Content-Type: text/plain; charset=UTF-8\n" # 字符集(通常UTF-8)
"Content-Transfer-Encoding: 8bit\n" # 編碼方式
"Plural-Forms: nplurals=2; plural=(n != 1);\n" # 復數規則(示例為英語)
不同語言的復數形式不同(如俄語有3種復數形式,阿拉伯語有6種),需根據語言調整 nplurals 和 plural 表達式。
PO 文件
PO(Portable Object)特定語言的翻譯文件,基于 POT 文件生成,由翻譯人員填充具體語言的譯文。
示例:
msgid "Hello, world!"
msgstr "你好,世界!"
MO 文件
MO(Machine Object)文件,PO 文件的二進制編譯版本,供程序運行時高效加載。
特點:
- 二進制格式,不可直接編輯。
- 文件擴展名:.mo。
- 由 msgfmt 工具從 PO 文件編譯生成。
- 程序通過 gettext() 函數讀取 MO 文件中的翻譯。
文本域
文本域(Text Domain)是 gettext 中用來管理和隔離翻譯資源的一個重要機制。
文本域允許程序將其翻譯文件組織在不同的命名空間內,使同一程序可以同時加載多個獨立的翻譯文件(MO 文件)。
在代碼中通過 textdomain() 函數設置當前文本域:
#include <libintl.h>
textdomain("myapp"); // 設置文本域為 "myapp"
在翻譯文件中,MO 文件需放在特定路徑:
./locale/<語言>/LC_MESSAGES/<文本域>.mo# 例如
./locale/zh_CN/LC_MESSAGES/myapp.mo
若程序需要同時使用多個文本域(如主程序和插件):
// 臨時切換文本域(保存舊域)
char* old_domain = textdomain("plugin1");
printf("%s", gettext("Delete")); // 使用 plugin1 的翻譯
textdomain(old_domain); // 恢復原文本域
注意,MO 文件必須與文本域同名:
文本域 "myapp" -> MO 文件名為 myapp.mo
文本域 "gtk" -> MO 文件名為 gtk.mo
標準存放路徑:
/usr/share/locale/zh_CN/LC_MESSAGES/myapp.mo # 系統級
./locale/zh_CN/LC_MESSAGES/myapp.mo # 項目內
翻譯函數
函數 | 作用 | 示例 |
---|---|---|
textdomain() | 設置/獲取當前文本域 | textdomain(“myapp”); |
bindtextdomain() | 指定文本域的 MO 文件搜索路徑 | bindtextdomain(“myapp”, “./locale”); |
gettext() | 根據當前文本域獲取翻譯 | gettext(“Hello”); |
dgettext() | 顯式指定文本域獲取翻譯 | dgettext(“plugin1”, “Save”); |
ngettext() | 根據給定的數量(n)選擇正確的單數或復數翻譯字符串 | ngettext(“%d file”, “%d files”, n); |
3.主要組件
- 開發庫
libintl:提供 gettext() 等國際化函數
支持多種編程語言:C, C++, Python, Java, Perl, PHP 等。
- 工具集
xgettext:從源代碼提取可翻譯字符串,生成 POT 文件(翻譯模板文件)。
msginit:根據 POT 文件創建新的 PO 文件(翻譯文件)。
msgmerge:合并新舊 PO 文件。
msgfmt:將 PO 文件編譯為 MO 文件。
- 運行時組件
gettext:在運行時根據當前 locale 加載適當的翻譯。
4.使用示例
下面以 C 語言給出使用示例。
- 標記源代碼。
#include <stdio.h>
#include <locale.h>
#include <libintl.h> // gettext 頭文件// 定義簡化宏(約定俗成)
#define _(STRING) gettext(STRING)
#define LOCALEDIR "/usr/share/locale" // 翻譯文件存放目錄int main() {// 1.設置本地化環境采用系統默認設置。// 必須調用,否則 gettext 無法正確加載翻譯。setlocale(LC_ALL, "");// 2.綁定文本域(指定翻譯文件位置和名稱)bindtextdomain("hello", LOCALEDIR);textdomain("hello"); // 設置當前文本域// 3.使用可翻譯字符串printf(_("Hello, World!\n"));printf(_("This is a demo of gettext.\n"));// 4.帶變量的翻譯int count = 3;printf(_("You have %d new message.\n"), count);return 0;
}
- 提取字符串。
一旦您具有了使用 gettext 調用的代碼,您可以使用 xgettext 從中提取消息,并將它們存儲在 .pot 中
xgettext -d hello --keyword=_ -o hello.pot main.c
-d hello
指定生成的翻譯模板文件(.pot)關聯的文本域(text domain)為 “hello”。
文本域是 gettext 系統中用于區分不同模塊或應用程序翻譯的命名空間。
--keyword
查找被指定關鍵詞包裹的字符串。
生成的翻譯模板文件內容如下:
cat hello.pot# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-30 18:31+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"#: main.c:18
#, c-format
msgid "Hello, World!\n"
msgstr ""#: main.c:19
#, c-format
msgid "This is a demo of gettext.\n"
msgstr ""#: main.c:23
#, c-format
msgid "You have %d new message.\n"
msgstr ""
- 創建翻譯文件。
這里創建中文簡體的翻譯文件 zh_CN.po。
msginit --input=hello.pot --locale=zh_CN -o hello.po
-i, --input
參數指定輸入的模板文件,包含了所有待翻譯的字符串。這個文件通常是通過 xgettext 命令從源代碼中提取所有標記為翻譯的字符串得到的。
-l, --locale
參數指定了目標語言的語言代碼,這里使用 zh_CN 表示簡體中文。語言代碼通常由兩部分組成:語言(zh)和國家/地區(CN),這里 CN 代表中國。這個參數告訴 msginit 創建一個針對中文簡體的 .po 文件。
- 編輯翻譯文件進行翻譯。
msgid "Hello, World!"
msgstr "你好,世界!"msgid "This is a demo of gettext."
msgstr "這是一個gettext的演示。"msgid "You have %d new message.\n"
msgstr "你有 %d 條新消息。\n"
- 編譯翻譯文件。
使用工具 msgfmt 將 PO 文件編譯為程序使用的二進制 MO 文件。
msgfmt -o hello.mo hello.po
如果出現如下錯誤:
zh_CN.po:22:11: invalid multibyte sequence
zh_CN.po:22:16: invalid multibyte sequence
zh_CN.po:22:17: invalid multibyte sequence
...
說明生成的 PO 文件頭中的元信息 Content-Type 不對,需要修改成正確的字符編碼。
# 原來 GB2312
"Content-Type: text/plain; charset=UTF-8\n"# 改成 UTF-8
"Content-Type: text/plain; charset=UTF-8\n"
為什么使用 msginit 生成的 PO 文件字符編碼為 GB2312 呢?
注意: MO 文件必須與文本域同名。
因為代碼中使用的文本域為 hello,所以這里生成的 MO 文件需要命名為 hello.mo。
將生成的 MO 文件拷貝至代碼中使用的文本域目錄:
cp hello.mo /usr/share/locale/zh_CN/LC_MESSAGES/
- 驗證多語言是否生效。
編譯上面的示例程序。
gcc main.c -o hello.out
執行 hello.out 輸出如下結果:
LANG=zh_CN.UTF-8 ./hello.out你好,世界!
是一個gettext的演示。
你有 3 條新消息。
注意,執行程序前需要臨時設置環境變量 LANG 為 zh_CN.UTF-8,表明程序的語言環境和字符編碼為簡體中文和 UTF-8,這樣程序就可以去 locale 目錄找到特定語言的文本域對應的翻譯文件(MO 文件),這里是 hello.mo 文件且字符編碼為 UTF-8。這樣程序就可以將 gettext 包裹的內容翻譯成簡體中文了。
參考文獻
deepseek.com
gettext - GNU Project - Free Software Foundation
使用GNU gettext 來翻譯軟件 - Weblate