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