gettext
--- 多語(yǔ)種國(guó)際化服務(wù)?
源代碼: Lib/gettext.py
gettext
模塊為 Python 模塊和應(yīng)用程序提供國(guó)際化 (Internationalization, I18N) 和本地化 (Localization, L10N) 服務(wù)。它同時(shí)支持 GNU gettext 消息編目 API 和更高級(jí)的、基于類的 API,后者可能更適合于 Python 文件。下方描述的接口允許用戶使用一種自然語(yǔ)言編寫(xiě)模塊和應(yīng)用程序消息,并提供翻譯后的消息編目,以便在不同的自然語(yǔ)言下運(yùn)行。
同時(shí)還給出一些本地化 Python 模塊及應(yīng)用程序的小技巧。
GNU gettext API?
模塊 gettext
定義了下列 API,這與 gettext API 類似。如果你使用該 API,將會(huì)對(duì)整個(gè)應(yīng)用程序產(chǎn)生全局的影響。如果你的應(yīng)用程序支持多語(yǔ)種,而語(yǔ)言選擇取決于用戶的語(yǔ)言環(huán)境設(shè)置,這通常正是你所想要的。而如果你正在本地化某個(gè) Python 模塊,或者你的應(yīng)用程序需要在運(yùn)行時(shí)切換語(yǔ)言,相反你或許想用基于類的API。
- gettext.bindtextdomain(domain, localedir=None)?
將 domain 綁定到本地目錄 localedir。 更具體地來(lái)說(shuō),模塊
gettext
將使用路徑 (在 Unix 系統(tǒng)中):localedir/language/LC_MESSAGES/domain.mo
查找二進(jìn)制.mo
文件,此處對(duì)應(yīng)地查找 language 的位置是環(huán)境變量LANGUAGE
,LC_ALL
,LC_MESSAGES
和LANG
中。如果遺漏了 localedir 或者設(shè)置為
None
,那么將返回當(dāng)前 domain 所綁定的值 1
- gettext.textdomain(domain=None)?
修改或查詢當(dāng)前的全局域。如果 domain 為
None
,則返回當(dāng)前的全局域,不為None
則將全局域設(shè)置為 domain,并返回它。
- gettext.gettext(message)?
返回 message 的本地化翻譯,依據(jù)包括當(dāng)前的全局域、語(yǔ)言和語(yǔ)言環(huán)境目錄。本函數(shù)在本地命名空間中通常有別名
_()
(參考下面的示例)。
- gettext.ngettext(singular, plural, n)?
與
gettext()
類似,但考慮了復(fù)數(shù)形式。如果找到了翻譯,則將 n 代入復(fù)數(shù)公式,然后返回得出的消息(某些語(yǔ)言具有兩種以上的復(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
文件中使用的確切語(yǔ)法和各種語(yǔ)言的公式,請(qǐng)參閱 GNU gettext 文檔 。
- gettext.dngettext(domain, singular, plural, n)?
與
ngettext()
類似,但在指定的 domain 中查找 message。
- gettext.pgettext(context, message)?
- gettext.dpgettext(domain, context, message)?
- gettext.npgettext(context, singular, plural, n)?
- gettext.dnpgettext(domain, context, singular, plural, n)?
與前綴中沒(méi)有
p
的相應(yīng)函數(shù)類似(即gettext()
,dgettext()
,ngettext()
,dngettext()
),但是僅翻譯給定的 message context。3.8 新版功能.
注意,GNU gettext 還定義了 dcgettext()
方法,但它被認(rèn)為不實(shí)用,因此目前沒(méi)有實(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()
接受的域相同??蛇x參數(shù) localedir 與bindtextdomain()
中的相同??蛇x參數(shù) languages 是多條字符串的列表,其中每條字符串都是一種語(yǔ)言代碼。如果沒(méi)有傳入 localedir,則使用默認(rèn)的系統(tǒng)語(yǔ)言環(huán)境目錄。 2 如果沒(méi)有傳入 languages,則搜索以下環(huán)境變量:
LANGUAGE
、LC_ALL
、LC_MESSAGES
和LANG
。從這些變量返回的第一個(gè)非空值將用作 languages 變量。環(huán)境變量應(yīng)包含一個(gè)語(yǔ)言列表,由冒號(hào)分隔,該列表會(huì)被按冒號(hào)拆分,以產(chǎn)生所需的語(yǔ)言代碼字符串列表。find()
將擴(kuò)展并規(guī)范化 language,然后遍歷它們,搜索由這些組件構(gòu)建的現(xiàn)有文件:localedir/language/LC_MESSAGES/domain.mo
find()
返回找到類似的第一個(gè)文件名。如果找不到這樣的文件,則返回None
。如果傳入了 all,它將返回一個(gè)列表,包含所有文件名,并按它們?cè)谡Z(yǔ)言列表或環(huán)境變量中出現(xiàn)的順序排列。
- gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False)?
根據(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è) 文件對(duì)象 參數(shù)。如果傳入 codeset,那么在lgettext()
和lngettext()
方法中,對(duì)翻譯后的字符串進(jìn)行編碼的字符集將被改變。如果找到多個(gè)文件,后找到的文件將用作先前文件的替補(bǔ)。為了設(shè)置替補(bǔ),將使用
copy.copy()
從緩存中克隆每個(gè) translation 對(duì)象。實(shí)際的實(shí)例數(shù)據(jù)仍在緩存中共享。如果
.mo
文件未找到,且 fallback 為 false(默認(rèn)值),則本函數(shù)引發(fā)OSError
異常,如果 fallback 為 true,則返回一個(gè)NullTranslations
實(shí)例。在 3.11 版更改: codeset parameter is removed.
- gettext.install(domain, localedir=None, *, names=None)?
This installs the function
_()
in Python's builtins namespace, based on domain and localedir which are passed to the functiontranslation()
.names 參數(shù)的信息請(qǐng)參閱 translation 對(duì)象的
install()
方法的描述。如下所示,通常將字符串包括在
_()
函數(shù)的調(diào)用中,以標(biāo)記應(yīng)用程序中待翻譯的字符串,就像這樣:print(_('This string will be translated.'))
為了方便,一般將
_()
函數(shù)安裝在 Python 內(nèi)建命名空間中,以便在應(yīng)用程序的所有模塊中輕松訪問(wèn)它。在 3.11 版更改: names is now a keyword-only parameter.
NullTranslations
類?
translation 類實(shí)際實(shí)現(xiàn)的是,將原始源文件消息字符串轉(zhuǎn)換為已翻譯的消息字符串。所有 translation 類使用的基類為 NullTranslations
,它提供了基本的接口,可用于編寫(xiě)自己定制的 translation 類。以下是 NullTranslations
的方法:
- class gettext.NullTranslations(fp=None)?
接受一個(gè)可選參數(shù) 文件對(duì)象 fp,該參數(shù)會(huì)被基類忽略。初始化由派生類設(shè)置的 "protected" (受保護(hù)的)實(shí)例變量 _info 和 _charset,與 _fallback 類似,但它是通過(guò)
add_fallback()
來(lái)設(shè)置的。如果 fp 不為None
,就會(huì)調(diào)用self._parse(fp)
。- _parse(fp)?
在基類中沒(méi)有操作,本方法接受文件對(duì)象 fp,從該文件讀取數(shù)據(jù),用來(lái)初始化消息編目。如果你手頭的消息編目文件的格式不受支持,則應(yīng)重寫(xiě)本方法來(lái)解析你的格式。
- add_fallback(fallback)?
添加 fallback 為當(dāng)前 translation 對(duì)象的替補(bǔ)對(duì)象。如果 translation 對(duì)象無(wú)法為指定消息提供翻譯,則應(yīng)向替補(bǔ)查詢。
- gettext(message)?
如果設(shè)置了替補(bǔ),則轉(zhuǎn)發(fā)
gettext()
給替補(bǔ)。否則返回 message。在派生類中被重寫(xiě)。
- ngettext(singular, plural, n)?
如果設(shè)置了替補(bǔ),則轉(zhuǎn)發(fā)
ngettext()
給替補(bǔ)。否則,n 為 1 時(shí)返回 singular,為其他時(shí)返回 plural。在派生類中被重寫(xiě)。
- pgettext(context, message)?
如果設(shè)置了替補(bǔ),則轉(zhuǎn)發(fā)
pgettext()
給替補(bǔ)。否則返回已翻譯的消息。在派生類中被重寫(xiě)。3.8 新版功能.
- npgettext(context, singular, plural, n)?
如果設(shè)置了替補(bǔ),則轉(zhuǎn)發(fā)
npgettext()
給替補(bǔ)。否則返回已翻譯的消息。在派生類中被重寫(xiě)。3.8 新版功能.
- info()?
返回 "protected"(受保護(hù)的)
_info
變量,它是一個(gè)字典,包含在消息編目文件中找到的元數(shù)據(jù)。
- charset()?
返回消息編目文件的編碼。
- install(names=None)?
本方法將
gettext()
安裝至內(nèi)建命名空間,并綁定為_
。如果傳入 names 參數(shù),該參數(shù)必須是一個(gè)序列,包含除
_()
外其他要安裝在內(nèi)建命名空間中的函數(shù)的名稱。支持的名稱有'gettext'
,'ngettext'
,'pgettext'
,'npgettext'
,'lgettext'
和'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)用。在 3.8 版更改: 添加了
'pgettext'
和'npgettext'
。
GNUTranslations
類?
gettext
模塊提供了一個(gè)派生自 NullTranslations
的其他類:GNUTranslations
。該類重寫(xiě)了 _parse()
,同時(shí)能以大端序和小端序格式讀取 GNU gettext 格式的 .mo
文件。
GNUTranslations
從翻譯編目中解析出可選的元數(shù)據(jù)。GNU gettext 約定,將元數(shù)據(jù)包括在空字符串的翻譯中。該元數(shù)據(jù)采用 RFC 822 樣式的 key: value
鍵值對(duì),且應(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è)鍵/值對(duì)集合將被放入一個(gè)字典,并設(shè)置為 "protected"(受保護(hù)的) _info
實(shí)例變量。
如果 .mo
文件的魔法值 (magic number) 無(wú)效,或遇到意外的主版本號(hào),或在讀取文件時(shí)發(fā)生其他問(wèn)題,則實(shí)例化 GNUTranslations
類會(huì)引發(fā) OSError
。
- class gettext.GNUTranslations?
下列方法是根據(jù)基類實(shí)現(xiàn)重寫(xiě)的:
- gettext(message)?
在編目中查找 message ID,并以 Unicode 字符串形式返回相應(yīng)的消息字符串。如果在編目中沒(méi)有 message ID 條目,且配置了替補(bǔ),則查找請(qǐng)求將被轉(zhuǎn)發(fā)到替補(bǔ)的
gettext()
方法。否則,返回 message ID。
- ngettext(singular, plural, n)?
查找消息 ID 的復(fù)數(shù)形式。singular 用作消息 ID,用于在編目中查找,同時(shí) n 用于確定使用哪種復(fù)數(shù)形式。返回的消息字符串是 Unicode 字符串。
如果在編目中沒(méi)有找到消息 ID,且配置了替補(bǔ),則查找請(qǐng)求將被轉(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}
- pgettext(context, message)?
在編目中查找 context 和 message ID,并以 Unicode 字符串形式返回相應(yīng)的消息字符串。如果在編目中沒(méi)有 message ID 和 context 條目,且配置了替補(bǔ),則查找請(qǐng)求將被轉(zhuǎn)發(fā)到替補(bǔ)的
pgettext()
方法。否則,返回 message ID。3.8 新版功能.
- npgettext(context, singular, plural, n)?
查找消息 ID 的復(fù)數(shù)形式。singular 用作消息 ID,用于在編目中查找,同時(shí) n 用于確定使用哪種復(fù)數(shù)形式。
如果在編目中沒(méi)有找到 context 對(duì)應(yīng)的消息 ID,且配置了替補(bǔ),則查找請(qǐng)求將被轉(zhuǎn)發(fā)到替補(bǔ)的
npgettext()
方法。否則,當(dāng) n 為 1 時(shí)返回 singular,其他情況返回 plural。3.8 新版功能.
Solaris 消息編目支持?
Solaris 操作系統(tǒng)定義了自己的二進(jìn)制 .mo
文件格式,但由于找不到該格式的文檔,因此目前不支持該格式。
編目構(gòu)造器?
GNOME 用的 gettext
模塊是 James Henstridge 寫(xiě)的版本,但該版本的 API 略有不同。它文檔中的用法是:
import gettext
cat = gettext.Catalog(domain, localedir)
_ = cat.gettext
print(_('hello world'))
為了與本模塊的舊版本兼容,Catalog()
函數(shù)是上述 translation()
函數(shù)的別名。
本模塊與 Henstridge 的模塊有一個(gè)區(qū)別:他的編目對(duì)象支持通過(guò)映射 API 進(jìn)行訪問(wèn),但是該特性似乎從未使用過(guò),因此目前不支持該特性。
國(guó)際化 (I18N) 你的程序和模塊?
國(guó)際化 (I18N) 是指使程序可切換多種語(yǔ)言的操作。本地化 (L10N) 是指程序的適配能力,一旦程序被國(guó)際化,就能適配當(dāng)?shù)氐恼Z(yǔ)言和文化習(xí)慣。為了向 Python 程序提供不同語(yǔ)言的消息,需要執(zhí)行以下步驟:
準(zhǔn)備程序或模塊,將可翻譯的字符串特別標(biāo)記起來(lái)
在已標(biāo)記的文件上運(yùn)行一套工具,用來(lái)生成原始消息編目
創(chuàng)建消息編目的不同語(yǔ)言的翻譯
使用
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'
沒(méi)有被標(biāo)記。
有一些工具可以將待翻譯的字符串提取出來(lái)。原版的 GNU gettext 僅支持 C 或 C++ 源代碼,但其擴(kuò)展版 xgettext 可以掃描多種語(yǔ)言的代碼(包括 Python),來(lái)找出標(biāo)記為可翻譯的字符串。Babel 是一個(gè) Python 國(guó)際化庫(kù),其中包含一個(gè) pybabel
腳本,用于提取并編譯消息編目。Fran?ois Pinard 寫(xiě)的稱為 xpot 的程序也能完成類似的工作,可從他的 po-utils 軟件包 中獲取。
(Python 還包括了這些程序的純 Python 版本,稱為 pygettext.py 和 msgfmt.py,某些 Python 發(fā)行版已經(jīng)安裝了它們。pygettext.py 類似于 xgettext,但只能理解 Python 源代碼,無(wú)法處理諸如 C 或 C++ 的其他編程語(yǔ)言。pygettext.py 支持的命令行界面類似于 xgettext,查看其詳細(xì)用法請(qǐng)運(yùn)行 pygettext.py --help
。msgfmt.py 與 GNU msgfmt 是二進(jìn)制兼容的。有了這兩個(gè)程序,可以不需要 GNU gettext 包來(lái)國(guó)際化 Python 應(yīng)用程序。)
xgettext、pygettext 或類似工具生成的 .po
文件就是消息編目。它們是結(jié)構(gòu)化的人類可讀文件,包含源代碼中所有被標(biāo)記的字符串,以及這些字符串的翻譯的占位符。
然后把這些 .po
文件的副本交給各個(gè)人工譯者,他們?yōu)樗С值拿糠N自然語(yǔ)言編寫(xiě)翻譯。譯者以 <語(yǔ)言名稱>.po
文件的形式發(fā)送回翻譯完的某個(gè)語(yǔ)言的版本,將該文件用 msgfmt 程序編譯為機(jī)器可讀的 .mo
二進(jìn)制編目文件。gettext
模塊使用 .mo
文件在運(yùn)行時(shí)進(jìn)行實(shí)際的翻譯處理。
如何在代碼中使用 gettext
模塊取決于國(guó)際化單個(gè)模塊還是整個(gè)應(yīng)用程序。接下來(lái)的兩節(jié)將討論每種情況。
本地化你的模塊?
如果要本地化模塊,則切忌進(jìn)行全局性的更改,如更改內(nèi)建命名空間。不應(yīng)使用 GNU gettext API,而應(yīng)使用基于類的 API。
假設(shè)你的模塊叫做 "spam",并且該模塊的各種自然語(yǔ)言翻譯 .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è)文件中顯式安裝它。
最簡(jiǎn)單的情況,就只需將以下代碼添加到應(yīng)用程序的主程序文件中:
import gettext
gettext.install('myapplication')
如果需要設(shè)置語(yǔ)言環(huán)境目錄,可以將其傳遞給 install()
函數(shù):
import gettext
gettext.install('myapplication', '/usr/share/locale')
即時(shí)更改語(yǔ)言?
如果程序需要同時(shí)支持多種語(yǔ)言,則可能需要?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ì)在編寫(xiě)位置進(jìn)行翻譯。但偶爾需要將字符串標(biāo)記為待翻譯,實(shí)際翻譯卻推遲到后面。一個(gè)典型的例子是:
animals = ['mollusk',
'albatross',
'rat',
'penguin',
'python', ]
# ...
for a in animals:
print(a)
此處希望將 animals
列表中的字符串標(biāo)記為可翻譯,但不希望在打印之前對(duì)它們進(jìn)行翻譯。
這是處理該情況的一種方式:
def _(message): return message
animals = [_('mollusk'),
_('albatross'),
_('rat'),
_('penguin'),
_('python'), ]
del _
# ...
for a in animals:
print(_(a))
該方法之所以可行,是因?yàn)?_()
的虛定義只是簡(jiǎn)單地返回了原本的字符串。并且該虛定義將臨時(shí)覆蓋內(nèi)建命名空間中 _()
的定義(直到 del
命令)。即使先前在本地命名空間中已經(jīng)有了 _()
的定義也請(qǐng)注意。
注意,第二次使用 _()
不會(huì)認(rèn)為 "a" 可以傳遞給 gettext 程序去翻譯,因?yàn)樵搮?shù)不是字符串文字。
解決該問(wèn)題的另一種方法是下面這個(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
命令行開(kāi)關(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)語(yǔ)言環(huán)境目錄是不同的;比如在 RedHat Linux 上是
/usr/share/locale
,在 Solaris 上是/usr/lib/locale
。gettext
模塊不會(huì)支持這些基于不同系統(tǒng)的默認(rèn)值;而它的默認(rèn)值為sys.base_prefix/share/locale
(請(qǐng)參閱sys.base_prefix
)。基于上述原因,最好每次都在應(yīng)用程序的開(kāi)頭使用明確的絕對(duì)路徑來(lái)調(diào)用bindtextdomain()
。- 2
參閱上方
bindtextdomain()
的腳注。