gettext --- 多語種國際化服務?

源代碼: Lib/gettext.py


gettext 模塊為 Python 模塊和應用程序提供國際化 (Internationalization, I18N) 和本地化 (Localization, L10N) 服務。它同時支持 GNU gettext 消息編目 API 和更高級的、基于類的 API,后者可能更適合于 Python 文件。下方描述的接口允許用戶使用一種自然語言編寫模塊和應用程序消息,并提供翻譯后的消息編目,以便在不同的自然語言下運行。

同時還給出一些本地化 Python 模塊及應用程序的小技巧。

GNU gettext API?

模塊 gettext 定義了下列 API,這與 gettext API 類似。如果你使用該 API,將會對整個應用程序產(chǎn)生全局的影響。如果你的應用程序支持多語種,而語言選擇取決于用戶的語言環(huán)境設置,這通常正是你所想要的。而如果你正在本地化某個 Python 模塊,或者你的應用程序需要在運行時切換語言,相反你或許想用基于類的API。

gettext.bindtextdomain(domain, localedir=None)?

domain 綁定到本地目錄 localedir。 更具體地來說,模塊 gettext 將使用路徑 (在 Unix 系統(tǒng)中): localedir/language/LC_MESSAGES/domain.mo 查找二進制 .mo 文件,此處對應地查找 language 的位置是環(huán)境變量 LANGUAGE, LC_ALL, LC_MESSAGESLANG 中。

如果遺漏了 localedir 或者設置為 None,那么將返回當前 domain 所綁定的值 1

gettext.textdomain(domain=None)?

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

gettext.gettext(message)?

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

gettext.dgettext(domain, message)?

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

gettext.ngettext(singular, plural, n)?

gettext() 類似,但考慮了復數(shù)形式。如果找到了翻譯,則將 n 代入復數(shù)公式,然后返回得出的消息(某些語言具有兩種以上的復數(shù)形式)。如果未找到翻譯,則 n 為 1 時返回 singular,為其他數(shù)時返回 plural

復數(shù)公式取自編目頭文件。它是 C 或 Python 表達式,有一個自變量 n,該表達式計算的是所需復數(shù)形式在編目中的索引號。關于在 .po 文件中使用的確切語法和各種語言的公式,請參閱 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)?

與前綴中沒有 p 的相應函數(shù)類似(即 gettext(), dgettext(), ngettext(), dngettext() ),但是僅翻譯給定的 message context。

3.8 新版功能.

注意,GNU gettext 還定義了 dcgettext() 方法,但它被認為不實用,因此目前沒有實現(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 提供了更多的靈活性和更強的便利性。這是本地化 Python 應用程序和模塊的推薦方法。gettext 定義了一個 GNUTranslations 類,該類實現(xiàn)了 GNU .mo 格式文件的解析,并且具有用于返回字符串的方法。本類的實例也可以將自身作為函數(shù) _() 安裝到內(nèi)建命名空間中。

gettext.find(domain, localedir=None, languages=None, all=False)?

本函數(shù)實現(xiàn)了標準的 .mo 文件搜索算法。它接受一個 domain,它與 textdomain() 接受的域相同??蛇x參數(shù) localedirbindtextdomain() 中的相同??蛇x參數(shù) languages 是多條字符串的列表,其中每條字符串都是一種語言代碼。

如果沒有傳入 localedir,則使用默認的系統(tǒng)語言環(huán)境目錄。 2 如果沒有傳入 languages,則搜索以下環(huán)境變量:LANGUAGE、LC_ALLLC_MESSAGESLANG。從這些變量返回的第一個非空值將用作 languages 變量。環(huán)境變量應包含一個語言列表,由冒號分隔,該列表會被按冒號拆分,以產(chǎn)生所需的語言代碼字符串列表。

find() 將擴展并規(guī)范化 language,然后遍歷它們,搜索由這些組件構建的現(xiàn)有文件:

localedir/language/LC_MESSAGES/domain.mo

find() 返回找到類似的第一個文件名。如果找不到這樣的文件,則返回 None。如果傳入了 all,它將返回一個列表,包含所有文件名,并按它們在語言列表或環(huán)境變量中出現(xiàn)的順序排列。

gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False)?

根據(jù) domain、localedirlanguages,返回 *Translations 實例,首先應將前述參數(shù)傳入 find() 以獲取關聯(lián)的 .mo 文件路徑的列表。名字與 .mo 文件名相同的實例將被緩存。如果傳入 class_,它將是實際被實例化的類,否則實例化 GNUTranslations。類的構造函數(shù)必須只接受一個 文件對象 參數(shù)。如果傳入 codeset,那么在 lgettext()lngettext() 方法中,對翻譯后的字符串進行編碼的字符集將被改變。

如果找到多個文件,后找到的文件將用作先前文件的替補。為了設置替補,將使用 copy.copy() 從緩存中克隆每個 translation 對象。實際的實例數(shù)據(jù)仍在緩存中共享。

如果 .mo 文件未找到,且 fallback 為 false(默認值),則本函數(shù)引發(fā) OSError 異常,如果 fallback 為 true,則返回一個 NullTranslations 實例。

在 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ù)的信息請參閱 translation 對象的 install() 方法的描述。

如下所示,通常將字符串包括在 _() 函數(shù)的調(diào)用中,以標記應用程序中待翻譯的字符串,就像這樣:

print(_('This string will be translated.'))

為了方便,一般將 _() 函數(shù)安裝在 Python 內(nèi)建命名空間中,以便在應用程序的所有模塊中輕松訪問它。

在 3.11 版更改: names is now a keyword-only parameter.

NullTranslations?

translation 類實際實現(xiàn)的是,將原始源文件消息字符串轉(zhuǎn)換為已翻譯的消息字符串。所有 translation 類使用的基類為 NullTranslations,它提供了基本的接口,可用于編寫自己定制的 translation 類。以下是 NullTranslations 的方法:

class gettext.NullTranslations(fp=None)?

接受一個可選參數(shù) 文件對象 fp,該參數(shù)會被基類忽略。初始化由派生類設置的 "protected" (受保護的)實例變量 _info_charset,與 _fallback 類似,但它是通過 add_fallback() 來設置的。如果 fp 不為 None,就會調(diào)用 self._parse(fp)。

_parse(fp)?

在基類中沒有操作,本方法接受文件對象 fp,從該文件讀取數(shù)據(jù),用來初始化消息編目。如果你手頭的消息編目文件的格式不受支持,則應重寫本方法來解析你的格式。

add_fallback(fallback)?

添加 fallback 為當前 translation 對象的替補對象。如果 translation 對象無法為指定消息提供翻譯,則應向替補查詢。

gettext(message)?

如果設置了替補,則轉(zhuǎn)發(fā) gettext() 給替補。否則返回 message。在派生類中被重寫。

ngettext(singular, plural, n)?

如果設置了替補,則轉(zhuǎn)發(fā) ngettext() 給替補。否則,n 為 1 時返回 singular,為其他時返回 plural。在派生類中被重寫。

pgettext(context, message)?

如果設置了替補,則轉(zhuǎn)發(fā) pgettext() 給替補。否則返回已翻譯的消息。在派生類中被重寫。

3.8 新版功能.

npgettext(context, singular, plural, n)?

如果設置了替補,則轉(zhuǎn)發(fā) npgettext() 給替補。否則返回已翻譯的消息。在派生類中被重寫。

3.8 新版功能.

info()?

返回 "protected"(受保護的) _info 變量,它是一個字典,包含在消息編目文件中找到的元數(shù)據(jù)。

charset()?

返回消息編目文件的編碼。

install(names=None)?

本方法將 gettext() 安裝至內(nèi)建命名空間,并綁定為 _。

如果傳入 names 參數(shù),該參數(shù)必須是一個序列,包含除 _() 外其他要安裝在內(nèi)建命名空間中的函數(shù)的名稱。支持的名稱有 'gettext', 'ngettext', 'pgettext', 'npgettext', 'lgettext''lngettext'。

注意,這僅僅是使 _() 函數(shù)在應用程序中生效的一種方法,盡管也是最方便的方法。由于它會影響整個應用程序全局,特別是內(nèi)建命名空間,因此已經(jīng)本地化的模塊不應該安裝 _(),而是應該用下列代碼使 _() 在各自模塊中生效:

import gettext
t = gettext.translation('mymodule', ...)
_ = t.gettext

這只將 _() 放在其模塊的全局命名空間中,所以只影響其模塊內(nèi)的調(diào)用。

在 3.8 版更改: 添加了 'pgettext''npgettext'。

GNUTranslations?

gettext 模塊提供了一個派生自 NullTranslations 的其他類:GNUTranslations。該類重寫了 _parse(),同時能以大端序和小端序格式讀取 GNU gettext 格式的 .mo 文件。

GNUTranslations 從翻譯編目中解析出可選的元數(shù)據(jù)。GNU gettext 約定,將元數(shù)據(jù)包括在空字符串的翻譯中。該元數(shù)據(jù)采用 RFC 822 樣式的 key: value 鍵值對,且應該包含 Project-Id-Version 鍵。如果找到 Content-Type 鍵,那么將用 charset 屬性去初始化 "protected"(受保護的) _charset 實例變量,而該變量在未找到鍵的情況下默認為 None。如果指定了字符編碼,那么從編目中讀取的所有消息 ID 和消息字符串都將使用此編碼轉(zhuǎn)換為 Unicode,若未指定編碼,則假定編碼為 ASCII。

由于消息 ID 也是作為 Unicode 字符串讀取的,因此所有 *gettext() 方法都假定消息 ID 為 Unicode 字符串,而不是字節(jié)串。

整個鍵/值對集合將被放入一個字典,并設置為 "protected"(受保護的) _info 實例變量。

如果 .mo 文件的魔法值 (magic number) 無效,或遇到意外的主版本號,或在讀取文件時發(fā)生其他問題,則實例化 GNUTranslations 類會引發(fā) OSError。

class gettext.GNUTranslations?

下列方法是根據(jù)基類實現(xiàn)重寫的:

gettext(message)?

在編目中查找 message ID,并以 Unicode 字符串形式返回相應的消息字符串。如果在編目中沒有 message ID 條目,且配置了替補,則查找請求將被轉(zhuǎn)發(fā)到替補的 gettext() 方法。否則,返回 message ID。

ngettext(singular, plural, n)?

查找消息 ID 的復數(shù)形式。singular 用作消息 ID,用于在編目中查找,同時 n 用于確定使用哪種復數(shù)形式。返回的消息字符串是 Unicode 字符串。

如果在編目中沒有找到消息 ID,且配置了替補,則查找請求將被轉(zhuǎn)發(fā)到替補的 ngettext() 方法。否則,當 n 為 1 時返回 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 字符串形式返回相應的消息字符串。如果在編目中沒有 message ID 和 context 條目,且配置了替補,則查找請求將被轉(zhuǎn)發(fā)到替補的 pgettext() 方法。否則,返回 message ID。

3.8 新版功能.

npgettext(context, singular, plural, n)?

查找消息 ID 的復數(shù)形式。singular 用作消息 ID,用于在編目中查找,同時 n 用于確定使用哪種復數(shù)形式。

如果在編目中沒有找到 context 對應的消息 ID,且配置了替補,則查找請求將被轉(zhuǎn)發(fā)到替補的 npgettext() 方法。否則,當 n 為 1 時返回 singular,其他情況返回 plural。

3.8 新版功能.

Solaris 消息編目支持?

Solaris 操作系統(tǒng)定義了自己的二進制 .mo 文件格式,但由于找不到該格式的文檔,因此目前不支持該格式。

編目構造器?

GNOME 用的 gettext 模塊是 James Henstridge 寫的版本,但該版本的 API 略有不同。它文檔中的用法是:

import gettext
cat = gettext.Catalog(domain, localedir)
_ = cat.gettext
print(_('hello world'))

為了與本模塊的舊版本兼容,Catalog() 函數(shù)是上述 translation() 函數(shù)的別名。

本模塊與 Henstridge 的模塊有一個區(qū)別:他的編目對象支持通過映射 API 進行訪問,但是該特性似乎從未使用過,因此目前不支持該特性。

國際化 (I18N) 你的程序和模塊?

國際化 (I18N) 是指使程序可切換多種語言的操作。本地化 (L10N) 是指程序的適配能力,一旦程序被國際化,就能適配當?shù)氐恼Z言和文化習慣。為了向 Python 程序提供不同語言的消息,需要執(zhí)行以下步驟:

  1. 準備程序或模塊,將可翻譯的字符串特別標記起來

  2. 在已標記的文件上運行一套工具,用來生成原始消息編目

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

  4. 使用 gettext 模塊,以便正確翻譯消息字符串

為了準備代碼以達成 I18N,需要巡視文件中的所有字符串。所有要翻譯的字符串都應包括在 _('...') 內(nèi),以此打上標記——也就是調(diào)用 _() 函數(shù)。例如:

filename = 'mylog.txt'
message = _('writing a log message')
with open(filename, 'w') as fp:
    fp.write(message)

在這個例子中,字符串 'writing a log message' 被標記為待翻譯,而字符串 'mylog.txt''w' 沒有被標記。

有一些工具可以將待翻譯的字符串提取出來。原版的 GNU gettext 僅支持 C 或 C++ 源代碼,但其擴展版 xgettext 可以掃描多種語言的代碼(包括 Python),來找出標記為可翻譯的字符串。Babel 是一個 Python 國際化庫,其中包含一個 pybabel 腳本,用于提取并編譯消息編目。Fran?ois Pinard 寫的稱為 xpot 的程序也能完成類似的工作,可從他的 po-utils 軟件包 中獲取。

(Python 還包括了這些程序的純 Python 版本,稱為 pygettext.pymsgfmt.py,某些 Python 發(fā)行版已經(jīng)安裝了它們。pygettext.py 類似于 xgettext,但只能理解 Python 源代碼,無法處理諸如 C 或 C++ 的其他編程語言。pygettext.py 支持的命令行界面類似于 xgettext,查看其詳細用法請運行 pygettext.py --help。msgfmt.py 與 GNU msgfmt 是二進制兼容的。有了這兩個程序,可以不需要 GNU gettext 包來國際化 Python 應用程序。)

xgettext、pygettext 或類似工具生成的 .po 文件就是消息編目。它們是結(jié)構化的人類可讀文件,包含源代碼中所有被標記的字符串,以及這些字符串的翻譯的占位符。

然后把這些 .po 文件的副本交給各個人工譯者,他們?yōu)樗С值拿糠N自然語言編寫翻譯。譯者以 <語言名稱>.po 文件的形式發(fā)送回翻譯完的某個語言的版本,將該文件用 msgfmt 程序編譯為機器可讀的 .mo 二進制編目文件。gettext 模塊使用 .mo 文件在運行時進行實際的翻譯處理。

如何在代碼中使用 gettext 模塊取決于國際化單個模塊還是整個應用程序。接下來的兩節(jié)將討論每種情況。

本地化你的模塊?

如果要本地化模塊,則切忌進行全局性的更改,如更改內(nèi)建命名空間。不應使用 GNU gettext API,而應使用基于類的 API。

假設你的模塊叫做 "spam",并且該模塊的各種自然語言翻譯 .mo 文件存放于 /usr/share/locale,為 GNU gettext 格式。以下內(nèi)容應放在模塊頂部:

import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.gettext

本地化你的應用程序?

如果正在本地化應用程序,可以將 _() 函數(shù)全局安裝到內(nèi)建命名空間中,通常在應用程序的主文件中安裝。這將使某個應用程序的所有文件都能使用 _('...'),而不必在每個文件中顯式安裝它。

最簡單的情況,就只需將以下代碼添加到應用程序的主程序文件中:

import gettext
gettext.install('myapplication')

如果需要設置語言環(huán)境目錄,可以將其傳遞給 install() 函數(shù):

import gettext
gettext.install('myapplication', '/usr/share/locale')

即時更改語言?

如果程序需要同時支持多種語言,則可能需要創(chuàng)建多個翻譯實例,然后在它們之間進行顯式切換,如下所示:

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ù)代碼中,字符串會在編寫位置進行翻譯。但偶爾需要將字符串標記為待翻譯,實際翻譯卻推遲到后面。一個典型的例子是:

animals = ['mollusk',
           'albatross',
           'rat',
           'penguin',
           'python', ]
# ...
for a in animals:
    print(a)

此處希望將 animals 列表中的字符串標記為可翻譯,但不希望在打印之前對它們進行翻譯。

這是處理該情況的一種方式:

def _(message): return message

animals = [_('mollusk'),
           _('albatross'),
           _('rat'),
           _('penguin'),
           _('python'), ]

del _

# ...
for a in animals:
    print(_(a))

該方法之所以可行,是因為 _() 的虛定義只是簡單地返回了原本的字符串。并且該虛定義將臨時覆蓋內(nèi)建命名空間中 _() 的定義(直到 del 命令)。即使先前在本地命名空間中已經(jīng)有了 _() 的定義也請注意。

注意,第二次使用 _() 不會認為 "a" 可以傳遞給 gettext 程序去翻譯,因為該參數(shù)不是字符串文字。

解決該問題的另一種方法是下面這個例子:

def N_(message): return message

animals = [N_('mollusk'),
           N_('albatross'),
           N_('rat'),
           N_('penguin'),
           N_('python'), ]

# ...
for a in animals:
    print(_(a))

這種情況下標記可翻譯的字符串使用的是函數(shù) N_(),該函數(shù)不會與 _() 的任何定義沖突。但是,需要教會消息提取程序去尋找用 N_() 標記的可翻譯字符串。xgettext, pygettext, pybabel extractxpot 都支持此功能,使用 -k 命令行開關即可。這里選擇 N_() 為名稱完全是任意的,它也能輕易改為 MarkThisStringForTranslation()。

致謝?

以下人員為創(chuàng)建此模塊貢獻了代碼、反饋、設計建議、早期實現(xiàn)和寶貴的經(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

不同系統(tǒng)的默認語言環(huán)境目錄是不同的;比如在 RedHat Linux 上是 /usr/share/locale,在 Solaris 上是 /usr/lib/locale。gettext 模塊不會支持這些基于不同系統(tǒng)的默認值;而它的默認值為 sys.base_prefix/share/locale (請參閱 sys.base_prefix)?;谏鲜鲈?,最好每次都在應用程序的開頭使用明確的絕對路徑來調(diào)用 bindtextdomain()

2

參閱上方 bindtextdomain() 的腳注。