gettext --- 多語種國際化服務(wù)?
源代碼: Lib/gettext.py
gettext 模塊為 Python 模塊和應(yīng)用程序提供國際化 (Internationalization, I18N) 和本地化 (Localization, L10N) 服務(wù)。它同時(shí)支持 GNU gettext 消息編目 API 和更高級(jí)的、基于類的 API,后者可能更適合于 Python 文件。下方描述的接口允許用戶使用一種自然語言編寫模塊和應(yīng)用程序消息,并提供翻譯后的消息編目,以便在不同的自然語言下運(yùn)行。
同時(shí)還給出一些本地化 Python 模塊及應(yīng)用程序的小技巧。
GNU gettext API?
模塊 gettext 定義了下列 API,這與 gettext API 類似。如果你使用該 API,將會(huì)對整個(gè)應(yīng)用程序產(chǎn)生全局的影響。如果你的應(yīng)用程序支持多語種,而語言選擇取決于用戶的區(qū)域設(shè)置,這通常正是你所想要的。而如果你正在本地化某個(gè) Python 模塊,或者你的應(yīng)用程序需要在運(yùn)行時(shí)切換語言,相反你或許想用基于類的API。
-
gettext.bindtextdomain(domain, localedir=None)? Bind the domain to the locale directory localedir. More concretely,
gettextwill look for binary.mofiles for the given domain using the path (on Unix):localedir/language/LC_MESSAGES/domain.mo, where languages is searched for in the environment variablesLANGUAGE,LC_ALL,LC_MESSAGES, andLANGrespectively.如果遺漏了 localedir 或者設(shè)置為
None,那么將返回當(dāng)前 domain 所綁定的值 1
-
gettext.bind_textdomain_codeset(domain, codeset=None)? 將 domain 綁定到 codeset,修改
lgettext(),ldgettext(),lngettext()和ldngettext()函數(shù)返回的字節(jié)串的字符編碼。如果省略了 codeset,則返回當(dāng)前綁定的編碼集。
-
gettext.textdomain(domain=None)? 修改或查詢當(dāng)前的全局域。如果 domain 為
None,則返回當(dāng)前的全局域,不為None則將全局域設(shè)置為 domain,并返回它。
-
gettext.gettext(message)? 返回 message 的本地化翻譯,依據(jù)包括當(dāng)前的全局域、語言和區(qū)域目錄。本函數(shù)在本地命名空間中通常有別名
_()(參考下面的示例)。
-
gettext.ngettext(singular, plural, n)? 與
gettext()類似,但考慮了復(fù)數(shù)形式。如果找到了翻譯,則將 n 代入復(fù)數(shù)公式,然后返回得出的消息(某些語言具有兩種以上的復(fù)數(shù)形式)。如果未找到翻譯,則 n 為 1 時(shí)返回 singular,為其他數(shù)時(shí)返回 plural。復(fù)數(shù)公式取自編目頭文件。它是 C 或 Python 表達(dá)式,有一個(gè)自變量 n,該表達(dá)式計(jì)算的是所需復(fù)數(shù)形式在編目中的索引號(hào)。關(guān)于在
.po文件中使用的確切語法和各種語言的公式,請參閱 GNU gettext 文檔 。
-
gettext.dngettext(domain, singular, plural, n)? 與
ngettext()類似,但在指定的 domain 中查找 message。
-
gettext.lgettext(message)?
-
gettext.ldgettext(domain, message)?
-
gettext.lngettext(singular, plural, n)?
-
gettext.ldngettext(domain, singular, plural, n)? 與前綴中沒有
l的相應(yīng)函數(shù)等效(gettext(),dgettext(),ngettext()和dngettext()),但是如果沒有用bind_textdomain_codeset()顯式設(shè)置其他編碼,則返回的翻譯將以首選系統(tǒng)編碼來編碼字節(jié)串。警告
These functions should be avoided in Python 3, because they return encoded bytes. It's much better to use alternatives which return Unicode strings instead, since most Python applications will want to manipulate human readable text as strings instead of bytes. Further, it's possible that you may get unexpected Unicode-related exceptions if there are encoding problems with the translated strings. It is possible that the
l*()functions will be deprecated in future Python versions due to their inherent problems and limitations.
注意,GNU gettext 還定義了 dcgettext() 方法,但它被認(rèn)為不實(shí)用,因此目前沒有實(shí)現(xiàn)它。
這是該 API 的典型用法示例:
import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print(_('This is a translatable string.'))
基于類的 API?
與 GNU gettext API 相比,gettext 模塊的基于類的 API 提供了更多的靈活性和更強(qiáng)的便利性。這是本地化 Python 應(yīng)用程序和模塊的推薦方法。gettext 定義了一個(gè) GNUTranslations 類,該類實(shí)現(xiàn)了 GNU .mo 格式文件的解析,并且具有用于返回字符串的方法。本類的實(shí)例也可以將自身作為函數(shù) _() 安裝到內(nèi)建命名空間中。
-
gettext.find(domain, localedir=None, languages=None, all=False)? 本函數(shù)實(shí)現(xiàn)了標(biāo)準(zhǔn)的
.mo文件搜索算法。它接受一個(gè) domain,它與textdomain()接受的域相同。可選參數(shù) localedir 與bindtextdomain()中的相同。可選參數(shù) languages 是多條字符串的列表,其中每條字符串都是一種語言代碼。如果沒有傳入 localedir,則使用默認(rèn)的系統(tǒng)區(qū)域設(shè)置目錄。 2 如果沒有傳入 languages,則搜索以下環(huán)境變量:
LANGUAGE、LC_ALL、LC_MESSAGES和LANG。從這些變量返回的第一個(gè)非空值將用作 languages 變量。環(huán)境變量應(yīng)包含一個(gè)語言列表,由冒號(hào)分隔,該列表會(huì)被按冒號(hào)拆分,以產(chǎn)生所需的語言代碼字符串列表。find()將擴(kuò)展并規(guī)范化 language,然后遍歷它們,搜索由這些組件構(gòu)建的現(xiàn)有文件:localedir/language/LC_MESSAGES/domain.mofind()返回找到類似的第一個(gè)文件名。如果找不到這樣的文件,則返回None。如果傳入了 all,它將返回一個(gè)列表,包含所有文件名,并按它們在語言列表或環(huán)境變量中出現(xiàn)的順序排列。
-
gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False, codeset=None)? 根據(jù) domain、localedir 和 languages,返回
*Translations實(shí)例,首先應(yīng)將前述參數(shù)傳入find()以獲取關(guān)聯(lián)的.mo文件路徑的列表。名字與.mo文件名相同的實(shí)例將被緩存。如果傳入 class_,它將是實(shí)際被實(shí)例化的類,否則實(shí)例化GNUTranslations。類的構(gòu)造函數(shù)必須只接受一個(gè) 文件對象 參數(shù)。如果傳入 codeset,那么在lgettext()和lngettext()方法中,對翻譯后的字符串進(jìn)行編碼的字符集將被改變。如果找到多個(gè)文件,后找到的文件將用作先前文件的替補(bǔ)。為了設(shè)置替補(bǔ),將使用
copy.copy()從緩存中克隆每個(gè) translation 對象。實(shí)際的實(shí)例數(shù)據(jù)仍在緩存中共享。如果
.mo文件未找到,且 fallback 為 false(默認(rèn)值),則本函數(shù)引發(fā)OSError異常,如果 fallback 為 true,則返回一個(gè)NullTranslations實(shí)例。
-
gettext.install(domain, localedir=None, codeset=None, names=None)? 根據(jù)傳入
translation()函數(shù)的 domain、localedir 和 codeset,在 Python 內(nèi)建命名空間中安裝_()函數(shù)。names 參數(shù)的信息請參閱 translation 對象的
install()方法的描述。如下所示,通常將字符串包括在
_()?函數(shù)的調(diào)用中,以標(biāo)記應(yīng)用程序中待翻譯的字符串,就像這樣:print(_('This string will be translated.'))
為了方便,一般將
_()函數(shù)安裝在 Python 內(nèi)建命名空間中,以便在應(yīng)用程序的所有模塊中輕松訪問它。
NullTranslations 類?
translation 類實(shí)際實(shí)現(xiàn)的是,將原始源文件消息字符串轉(zhuǎn)換為已翻譯的消息字符串。所有 translation 類使用的基類為 NullTranslations,它提供了基本的接口,可用于編寫自己定制的 translation 類。以下是 NullTranslations 的方法:
-
class
gettext.NullTranslations(fp=None)? 接受一個(gè)可選參數(shù) 文件對象 fp,該參數(shù)會(huì)被基類忽略。初始化由派生類設(shè)置的 "protected" (受保護(hù)的)實(shí)例變量 _info 和 _charset,與 _fallback 類似,但它是通過
add_fallback()來設(shè)置的。如果 fp 不為None,就會(huì)調(diào)用self._parse(fp)。-
_parse(fp)? 在基類中沒有操作,本方法接受文件對象 fp,從該文件讀取數(shù)據(jù),用來初始化消息編目。如果你手頭的消息編目文件的格式不受支持,則應(yīng)重寫本方法來解析你的格式。
-
add_fallback(fallback)? 添加 fallback 為當(dāng)前 translation 對象的替補(bǔ)對象。如果 translation 對象無法為指定消息提供翻譯,則應(yīng)向替補(bǔ)查詢。
-
gettext(message)? 如果設(shè)置了替補(bǔ),則轉(zhuǎn)發(fā)
gettext()?給替補(bǔ)。否則返回 message。須在派生類中重寫。
-
ngettext(singular, plural, n)? 如果設(shè)置了替補(bǔ),則轉(zhuǎn)發(fā)
ngettext()給替補(bǔ)。否則,n 為 1 時(shí)返回 singular,為其他時(shí)返回 plural。須在派生類中重寫。
-
lgettext(message)?
-
lngettext(singular, plural, n)? 與
gettext()和ngettext()等效,但是如果沒有用set_output_charset()顯式設(shè)置編碼,則返回的翻譯將以首選系統(tǒng)編碼來編碼字節(jié)串。在派生類中被重寫。警告
應(yīng)避免在 Python 3 中使用這些方法。請參閱
lgettext()函數(shù)的警告。
-
info()? 返回 "protected"(受保護(hù)的)?
_info變量,它是一個(gè)字典,包含在消息編目文件中找到的元數(shù)據(jù)。
-
charset()? 返回消息編目文件的編碼。
-
output_charset()? 返回由
lgettext()和lngettext()翻譯的消息的編碼。
-
set_output_charset(charset)? 更改翻譯出的消息的編碼。
-
install(names=None)? 本方法將
gettext()安裝至內(nèi)建命名空間,并綁定為_。If the names parameter is given, it must be a sequence containing the names of functions you want to install in the builtins namespace in addition to
_(). Supported names are'gettext','ngettext','lgettext'and'lngettext'.注意,這僅僅是使
_()函數(shù)在應(yīng)用程序中生效的一種方法,盡管也是最方便的方法。由于它會(huì)影響整個(gè)應(yīng)用程序全局,特別是內(nèi)建命名空間,因此已經(jīng)本地化的模塊不應(yīng)該安裝_(),而是應(yīng)該用下列代碼使_()在各自模塊中生效:import gettext t = gettext.translation('mymodule', ...) _ = t.gettext
這只將
_()放在其模塊的全局命名空間中,所以只影響其模塊內(nèi)的調(diào)用。
-
GNUTranslations 類?
gettext 模塊提供了一個(gè)派生自 NullTranslations 的其他類:GNUTranslations。該類重寫了 _parse(),同時(shí)能以大端序和小端序格式讀取 GNU gettext 格式的 .mo 文件。
GNUTranslations 從翻譯編目中解析出可選的元數(shù)據(jù)。GNU gettext 約定,將元數(shù)據(jù)包括在空字符串的翻譯中。該元數(shù)據(jù)采用 RFC 822 樣式的 key: value 鍵值對,且應(yīng)該包含 Project-Id-Version 鍵。如果找到 Content-Type 鍵,那么將用 charset 屬性去初始化 "protected"(受保護(hù)的) _charset 實(shí)例變量,而該變量在未找到鍵的情況下默認(rèn)為 None。如果指定了字符編碼,那么從編目中讀取的所有消息 ID 和消息字符串都將使用此編碼轉(zhuǎn)換為 Unicode,若未指定編碼,則假定編碼為 ASCII。
由于消息 ID 也是作為 Unicode 字符串讀取的,因此所有 *gettext() 方法都假定消息 ID 為 Unicode 字符串,而不是字節(jié)串。
整個(gè)鍵/值對集合將被放入一個(gè)字典,并設(shè)置為 "protected"(受保護(hù)的) _info 實(shí)例變量。
如果 .mo 文件的魔法值 (magic number) 無效,或遇到意外的主版本號(hào),或在讀取文件時(shí)發(fā)生其他問題,則實(shí)例化 GNUTranslations 類會(huì)引發(fā) OSError。
-
class
gettext.GNUTranslations? 下列方法是根據(jù)基類實(shí)現(xiàn)重寫的:
-
gettext(message)? 在編目中查找 message ID,并以 Unicode 字符串形式返回相應(yīng)的消息字符串。如果在編目中沒有 message ID 條目,且配置了替補(bǔ),則查找請求將被轉(zhuǎn)發(fā)到替補(bǔ)的
gettext()方法。否則,返回 message ID。
-
ngettext(singular, plural, n)? 查找消息 ID 的復(fù)數(shù)形式。singular 用作消息 ID,用于在編目中查找,同時(shí) n 用于確定使用哪種復(fù)數(shù)形式。返回的消息字符串是 Unicode 字符串。
如果在編目中沒有找到消息 ID,且配置了替補(bǔ),則查找請求將被轉(zhuǎn)發(fā)到替補(bǔ)的
ngettext()方法。否則,當(dāng) n 為 1 時(shí)返回 singular,其他情況返回 plural。例如:
n = len(os.listdir('.')) cat = GNUTranslations(somefile) message = cat.ngettext( 'There is %(num)d file in this directory', 'There are %(num)d files in this directory', n) % {'num': n}
-
lgettext(message)?
-
lngettext(singular, plural, n)? 與
gettext()和ngettext()等效,但是如果沒有用set_output_charset()顯式設(shè)置編碼,則返回的翻譯將以首選系統(tǒng)編碼來編碼字節(jié)串。警告
應(yīng)避免在 Python 3 中使用這些方法。請參閱
lgettext()函數(shù)的警告。
-
Solaris 消息編目支持?
Solaris 操作系統(tǒng)定義了自己的二進(jìn)制 .mo 文件格式,但由于找不到該格式的文檔,因此目前不支持該格式。
編目構(gòu)造器?
GNOME 用的?gettext 模塊是 James Henstridge 寫的版本,但該版本的 API 略有不同。它文檔中的用法是:
import gettext
cat = gettext.Catalog(domain, localedir)
_ = cat.gettext
print(_('hello world'))
為了與本模塊的舊版本兼容,Catalog() 函數(shù)是上述 translation() 函數(shù)的別名。
本模塊與 Henstridge 的模塊有一個(gè)區(qū)別:他的編目對象支持通過映射 API 進(jìn)行訪問,但是該特性似乎從未使用過,因此目前不支持該特性。
國際化 (I18N) 你的程序和模塊?
國際化 (I18N) 是指使程序可切換多種語言的操作。本地化 (L10N) 是指程序的適配能力,一旦程序被國際化,就能適配當(dāng)?shù)氐恼Z言和文化習(xí)慣。為了向 Python 程序提供不同語言的消息,需要執(zhí)行以下步驟:
準(zhǔn)備程序或模塊,將可翻譯的字符串特別標(biāo)記起來
在已標(biāo)記的文件上運(yùn)行一套工具,用來生成原始消息編目
創(chuàng)建不同語言的消息編目翻譯
使用
gettext模塊,以便正確翻譯消息字符串
為了準(zhǔn)備代碼以達(dá)成 I18N,需要巡視文件中的所有字符串。所有要翻譯的字符串都應(yīng)包括在 _('...') 內(nèi),以此打上標(biāo)記——也就是調(diào)用 _() 函數(shù)。例如:
filename = 'mylog.txt'
message = _('writing a log message')
with open(filename, 'w') as fp:
fp.write(message)
在這個(gè)例子中,字符串 'writing a log message' 被標(biāo)記為待翻譯,而字符串 'mylog.txt' 和 'w' 沒有被標(biāo)記。
有一些工具可以將待翻譯的字符串提取出來。原版的 GNU gettext 僅支持 C 或 C++ 源代碼,但其擴(kuò)展版 xgettext 可以掃描多種語言的代碼(包括 Python),來找出標(biāo)記為可翻譯的字符串。Babel 是一個(gè) Python 國際化庫,其中包含一個(gè) pybabel 腳本,用于提取并編譯消息編目。Fran?ois Pinard 寫的稱為 xpot 的程序也能完成類似的工作,可從他的 po-utils 軟件包 中獲取。
(Python 還包括了這些程序的純 Python 版本,稱為 pygettext.py 和 msgfmt.py,某些 Python 發(fā)行版已經(jīng)安裝了它們。pygettext.py 類似于 xgettext,但只能理解 Python 源代碼,無法處理諸如 C 或 C++ 的其他編程語言。pygettext.py 支持的命令行界面類似于 xgettext,查看其詳細(xì)用法請運(yùn)行 pygettext.py --help。msgfmt.py 與 GNU msgfmt 是二進(jìn)制兼容的。有了這兩個(gè)程序,可以不需要 GNU gettext 包來國際化 Python 應(yīng)用程序。)
xgettext、pygettext 或類似工具生成的 .po 文件就是消息編目。它們是結(jié)構(gòu)化的人類可讀文件,包含源代碼中所有被標(biāo)記的字符串,以及這些字符串的翻譯的占位符。
然后把這些 .po 文件的副本交給各個(gè)人工譯者,他們?yōu)樗С值拿糠N自然語言編寫翻譯。譯者以 <語言名稱>.po 文件的形式發(fā)送回翻譯完的某個(gè)語言的版本,將該文件用 msgfmt 程序編譯為機(jī)器可讀的 .mo 二進(jìn)制編目文件。gettext 模塊使用 .mo 文件在運(yùn)行時(shí)進(jìn)行實(shí)際的翻譯處理。
如何在代碼中使用 gettext 模塊取決于國際化單個(gè)模塊還是整個(gè)應(yīng)用程序。接下來的兩節(jié)將討論每種情況。
本地化你的模塊?
如果要本地化模塊,則切忌進(jìn)行全局性的更改,如更改內(nèi)建命名空間。不應(yīng)使用 GNU gettext API,而應(yīng)使用基于類的 API。
假設(shè)你的模塊叫做 "spam",并且該模塊的各種自然語言翻譯 .mo 文件存放于 /usr/share/locale,為 GNU gettext 格式。以下內(nèi)容應(yīng)放在模塊頂部:
import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.gettext
本地化你的應(yīng)用程序?
如果正在本地化應(yīng)用程序,可以將 _() 函數(shù)全局安裝到內(nèi)建命名空間中,通常在應(yīng)用程序的主文件中安裝。這將使某個(gè)應(yīng)用程序的所有文件都能使用 _('...'),而不必在每個(gè)文件中顯式安裝它。
最簡單的情況,就只需將以下代碼添加到應(yīng)用程序的主程序文件中:
import gettext
gettext.install('myapplication')
如果需要設(shè)置語言環(huán)境目錄,可以將其傳遞給 install() 函數(shù):
import gettext
gettext.install('myapplication', '/usr/share/locale')
即時(shí)更改語言?
如果程序需要同時(shí)支持多種語言,則可能需要?jiǎng)?chuàng)建多個(gè)翻譯實(shí)例,然后在它們之間進(jìn)行顯式切換,如下所示:
import gettext
lang1 = gettext.translation('myapplication', languages=['en'])
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])
# start by using language1
lang1.install()
# ... time goes by, user selects language 2
lang2.install()
# ... more time goes by, user selects language 3
lang3.install()
延遲翻譯?
在大多數(shù)代碼中,字符串會(huì)在編寫位置進(jìn)行翻譯。但偶爾需要將字符串標(biāo)記為待翻譯,實(shí)際翻譯卻推遲到后面。一個(gè)典型的例子是:
animals = ['mollusk',
'albatross',
'rat',
'penguin',
'python', ]
# ...
for a in animals:
print(a)
此處希望將 animals 列表中的字符串標(biāo)記為可翻譯,但不希望在打印之前對它們進(jìn)行翻譯。
這是處理該情況的一種方式:
def _(message): return message
animals = [_('mollusk'),
_('albatross'),
_('rat'),
_('penguin'),
_('python'), ]
del _
# ...
for a in animals:
print(_(a))
該方法之所以可行,是因?yàn)?_() 的虛定義只是簡單地返回了原本的字符串。并且該虛定義將臨時(shí)覆蓋內(nèi)建命名空間中 _() 的定義(直到 del 命令)。即使先前在本地命名空間中已經(jīng)有了 _() 的定義也請注意。
注意,第二次使用 _() 不會(huì)認(rèn)為 "a" 可以傳遞給 gettext 程序去翻譯,因?yàn)樵搮?shù)不是字符串文字。
解決該問題的另一種方法是下面這個(gè)例子:
def N_(message): return message
animals = [N_('mollusk'),
N_('albatross'),
N_('rat'),
N_('penguin'),
N_('python'), ]
# ...
for a in animals:
print(_(a))
這種情況下標(biāo)記可翻譯的字符串使用的是函數(shù) N_(),該函數(shù)不會(huì)與 _() 的任何定義沖突。但是,需要教會(huì)消息提取程序去尋找用 N_() 標(biāo)記的可翻譯字符串。xgettext, pygettext, pybabel extract 和 xpot 都支持此功能,使用 -k 命令行開關(guān)即可。這里選擇 N_() 為名稱完全是任意的,它也能輕易改為 MarkThisStringForTranslation()。
致謝?
以下人員為創(chuàng)建此模塊貢獻(xiàn)了代碼、反饋、設(shè)計(jì)建議、早期實(shí)現(xiàn)和寶貴的經(jīng)驗(yàn):
Peter Funk
James Henstridge
Juan David Ibá?ez Palomar
Marc-André Lemburg
Martin von L?wis
Fran?ois Pinard
Barry Warsaw
Gustavo Niemeyer
備注
- 1
不同系統(tǒng)的默認(rèn)語言環(huán)境目錄是不同的;比如在 RedHat Linux 上是
/usr/share/locale,在 Solaris 上是/usr/lib/locale。gettext模塊不會(huì)支持這些基于不同系統(tǒng)的默認(rèn)值;而它的默認(rèn)值為sys.base_prefix/share/locale(請參閱sys.base_prefix)。基于上述原因,最好每次都在應(yīng)用程序的開頭使用明確的絕對路徑來調(diào)用bindtextdomain()。- 2
參閱上方
bindtextdomain()的腳注。
