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_MESSAGESLANG 中。

如果遺漏了 localedir 或者設(shè)置為 None,那么將返回當(dāng)前 domain 所綁定的值 1

gettext.textdomain(domain=None)?

修改或查詢當(dāng)前的全局域。如果 domainNone,則返回當(dāng)前的全局域,不為 None 則將全局域設(shè)置為 domain,并返回它。

gettext.gettext(message)?

返回 message 的本地化翻譯,依據(jù)包括當(dāng)前的全局域、語(yǔ)言和語(yǔ)言環(huán)境目錄。本函數(shù)在本地命名空間中通常有別名 _() (參考下面的示例)。

gettext.dgettext(domain, message)?

gettext() 類似,但在指定的 domain 中查找 message。

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ù) localedirbindtextdomain() 中的相同??蛇x參數(shù) languages 是多條字符串的列表,其中每條字符串都是一種語(yǔ)言代碼。

如果沒(méi)有傳入 localedir,則使用默認(rèn)的系統(tǒng)語(yǔ)言環(huán)境目錄。 2 如果沒(méi)有傳入 languages,則搜索以下環(huán)境變量:LANGUAGE、LC_ALLLC_MESSAGESLANG。從這些變量返回的第一個(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、localedirlanguages,返回 *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.3 版更改: 曾經(jīng)是 IOError 被引發(fā)而不是 OSError 。

在 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 function translation().

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)?

在編目中查找 contextmessage 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í)行以下步驟:

  1. 準(zhǔn)備程序或模塊,將可翻譯的字符串特別標(biāo)記起來(lái)

  2. 在已標(biāo)記的文件上運(yùn)行一套工具,用來(lái)生成原始消息編目

  3. 創(chuàng)建消息編目的不同語(yǔ)言的翻譯

  4. 使用 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.pymsgfmt.py,某些 Python 發(fā)行版已經(jīng)安裝了它們。pygettext.py 類似于 xgettext,但只能理解 Python 源代碼,無(wú)法處理諸如 C 或 C++ 的其他編程語(yǔ)言。pygettext.py 支持的命令行界面類似于 xgettext,查看其詳細(xì)用法請(qǐng)運(yùn)行 pygettext.py --helpmsgfmt.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 extractxpot 都支持此功能,使用 -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/localegettext 模塊不會(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() 的腳注。