gettext — 多語言國際化服務

原始碼: Lib/gettext.py


gettext 模組為您的 Python 模組和應用程式提供國際化 (I18N) 和本地化 (L10N) 服務。它同時支援 GNU gettext 訊息目錄 API 和一個更高級別的、基於類的 API,後者可能更適合 Python 檔案。以下描述的介面允許您用一種自然語言編寫模組和應用程式訊息,並提供翻譯後的訊息目錄,以便在不同的自然語言下執行。

還提供了一些關於本地化您的 Python 模組和應用程式的提示。

GNU gettext API

gettext 模組定義了以下 API,它與 GNU gettext API 非常相似。如果您使用此 API,您將全域性影響整個應用程式的翻譯。如果您的應用程式是單語的,並且語言的選擇取決於使用者的語言環境,那麼這通常是您想要的。如果您正在本地化 Python 模組,或者您的應用程式需要在執行時切換語言,您可能需要使用基於類的 API。

gettext.bindtextdomain(domain, localedir=None)

domain 繫結到語言環境目錄 localedir。更具體地說,gettext 將使用路徑(在 Unix 上)查詢給定域的二進位制 .mo 檔案: localedir/language/LC_MESSAGES/domain.mo,其中 language 將在環境變數 LANGUAGELC_ALLLC_MESSAGESLANG 中依次查詢。

如果省略 localedir 或為 None,則返回 domain 的當前繫結。[1]

gettext.textdomain(domain=None)

更改或查詢當前的全域性域。如果 domainNone,則返回當前的全域性域,否則將全域性域設定為 domain,並返回它。

gettext.gettext(message)

根據當前的全域性域、語言和語言環境目錄,返回 message 的本地化翻譯。此函式通常在本地名稱空間中別名為 _()(請參閱下面的示例)。

gettext.dgettext(domain, message)

gettext() 類似,但在指定的 domain 中查詢訊息。

gettext.ngettext(singular, plural, n)

gettext() 類似,但考慮複數形式。如果找到翻譯,則將複數公式應用於 n,並返回結果訊息(某些語言有不止兩種複數形式)。如果找不到翻譯,則當 n 為 1 時返回 singular;否則返回 plural

複數公式取自目錄頭。它是一個 C 或 Python 表示式,其中有一個自由變數 n;該表示式求值為目錄中複數的索引。有關在 .po 檔案中使用的精確語法以及各種語言的公式,請參閱GNU gettext 文件

gettext.dngettext(domain, singular, plural, n)

ngettext() 類似,但在指定的 domain 中查詢訊息。

gettext.pgettext(context, message)
gettext.dpgettext(domain, context, message)
gettext.npgettext(context, singular, plural, n)
gettext.dnpgettext(domain, context, singular, plural, n)

與字首中沒有 p 的相應函式(即 gettext()dgettext()ngettext()dngettext())類似,但翻譯僅限於給定的訊息上下文

3.8 版本新增。

請注意,GNU gettext 也定義了 dcgettext() 方法,但認為它沒有用處,因此目前未實現。

以下是此 API 的典型用法示例

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print(_('This is a translatable string.'))

基於類的 API

gettext 模組的基於類的 API 比 GNU gettext API 提供了更大的靈活性和便利性。這是本地化 Python 應用程式和模組的推薦方法。gettext 定義了一個 GNUTranslations 類,該類實現了 GNU .mo 格式檔案的解析,並具有返回字串的方法。此類的例項也可以將自己安裝到內建名稱空間中作為函式 _()

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

此函式實現了標準的 .mo 檔案搜尋演算法。它接受一個 domain,與 textdomain() 接受的相同。可選的 localedirbindtextdomain() 中的相同。可選的 languages 是一個字串列表,其中每個字串都是一個語言程式碼。

如果未給出 localedir,則使用預設的系統區域設定目錄。[2] 如果未給出 languages,則搜尋以下環境變數:LANGUAGELC_ALLLC_MESSAGESLANG。第一個返回非空值的變數將用於 languages 變數。環境變數應包含一個以冒號分隔的語言列表,該列表將以冒號分隔以生成預期的語言程式碼字串列表。

find() 然後擴充套件和規範化語言,然後遍歷它們,搜尋由這些元件構建的現有檔案

localedir/language/LC_MESSAGES/domain.mo

find() 返回找到的第一個此類檔名。如果找不到此類檔案,則返回 None。如果給出了 all,它將返回所有檔名的列表,按照它們在語言列表或環境變數中出現的順序排列。

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

基於 domainlocaledirlanguages 返回 *Translations 例項,它們首先傳遞給 find() 以獲取關聯的 .mo 檔案路徑列表。具有相同 .mo 檔名的例項會被快取。如果提供了 class_,則例項化的實際類是 class_,否則為 GNUTranslations。該類的建構函式必須接受單個 檔案物件 引數。

如果找到多個檔案,則後面的檔案將用作較早檔案的回退。為了允許設定回退,使用 copy.copy() 從快取中克隆每個翻譯物件;實際的例項資料仍然與快取共享。

如果找不到 .mo 檔案,則當 fallback 為 false(這是預設值)時,此函式會引發 OSError,如果 fallback 為 true,則返回 NullTranslations 例項。

在 3.3 版本中更改: IOError 過去會被引發,它現在是 OSError 的別名。

在 3.11 版本中更改: 刪除了 codeset 引數。

gettext.install(domain, localedir=None, *, names=None)

這會基於 domainlocaledir 將函式 _() 安裝到 Python 的 builtins 名稱空間中,它們會傳遞給函式 translation()

對於 names 引數,請參閱翻譯物件的 install() 方法的描述。

如下所示,你通常會透過將應用程式中需要翻譯的字串包裝在 _() 函式的呼叫中來標記它們,如下所示

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

為了方便起見,你希望將 _() 函式安裝到 Python 的 builtins 名稱空間中,以便在應用程式的所有模組中都可以輕鬆訪問它。

在 3.11 版本中更改: names 現在是僅限關鍵字的引數。

NullTranslations

翻譯類是實際實現將原始原始檔訊息字串翻譯為已翻譯訊息字串的類。所有翻譯類使用的基類是 NullTranslations;這提供了可用於編寫你自己的專用翻譯類的基本介面。以下是 NullTranslations 的方法

class gettext.NullTranslations(fp=None)

接受一個可選的檔案物件 fp,基類會忽略它。初始化“受保護的”例項變數 _info_charset,它們由派生類設定,以及 _fallback,它透過 add_fallback() 設定。如果 fp 不是 None,則呼叫 self._parse(fp)

_parse(fp)

基類中的空操作,此方法接受檔案物件 fp,並從檔案中讀取資料,初始化其訊息目錄。如果您有不支援的訊息目錄檔案格式,您應該覆蓋此方法來解析您的格式。

add_fallback(fallback)

fallback 新增為當前翻譯物件的回退物件。如果翻譯物件無法提供給定訊息的翻譯,則應諮詢回退。

gettext(message)

如果已設定回退,則將 gettext() 轉發到回退。否則,返回 message。在派生類中被覆蓋。

ngettext(singular, plural, n)

如果已設定回退,則將 ngettext() 轉發到回退。否則,如果 n 為 1,則返回 singular;否則返回 plural。在派生類中被覆蓋。

pgettext(context, message)

如果已設定回退,則將 pgettext() 轉發到回退。否則,返回翻譯後的訊息。在派生類中被覆蓋。

3.8 版本新增。

npgettext(context, singular, plural, n)

如果已設定回退,則將 npgettext() 轉發到回退。否則,返回翻譯後的訊息。在派生類中被覆蓋。

3.8 版本新增。

info()

返回一個字典,其中包含在訊息目錄檔案中找到的元資料。

charset()

返回訊息目錄檔案的編碼。

install(names=None)

此方法將 gettext() 安裝到內建名稱空間中,將其繫結到 _

如果提供了 names 引數,則它必須是一個序列,其中包含您想要安裝到內建名稱空間中的函式名稱,除了 _() 之外。支援的名稱是 'gettext''ngettext''pgettext''npgettext'

請注意,這只是使 _() 函式可用於您的應用程式的一種方式,儘管是最方便的方式。因為它會全域性影響整個應用程式,特別是內建名稱空間,所以本地化模組永遠不應該安裝 _()。相反,它們應該使用此程式碼使 _() 可用於其模組

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

這會將 _() 僅放在模組的全域性名稱空間中,因此僅影響此模組內的呼叫。

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

GNUTranslations

gettext 模組提供了一個從 NullTranslations 派生的附加類:GNUTranslations。此類覆蓋 _parse() 以便能夠讀取大端和小端格式的 GNU gettext 格式的 .mo 檔案。

GNUTranslations 從翻譯目錄中解析可選的元資料。按照 GNU gettext 的慣例,將元資料作為空字串的翻譯包含在內。此元資料採用 RFC 822 樣式的 key: value 對,並且應該包含 Project-Id-Version 鍵。如果找到鍵 Content-Type,則使用 charset 屬性來初始化“受保護的” _charset 例項變數,如果未找到,則預設為 None。如果指定了字元集編碼,則從目錄中讀取的所有訊息 ID 和訊息字串都將使用此編碼轉換為 Unicode,否則假定為 ASCII。

由於訊息 ID 也被讀取為 Unicode 字串,因此所有 *gettext() 方法都將假定訊息 ID 為 Unicode 字串,而不是位元組字串。

整個鍵/值對集被放入字典中,並設定為“受保護的” _info 例項變數。

如果 .mo 檔案的魔數無效,主版本號出乎意料,或者在讀取檔案時發生其他問題,則例項化 GNUTranslations 類可能會引發 OSError

class gettext.GNUTranslations

以下方法是從基類實現中覆蓋的

gettext(message)

在目錄中查詢 message id 並返回相應的訊息字串(作為 Unicode 字串)。如果目錄中沒有 message id 的條目,並且已設定回退,則查詢將轉發到回退的 gettext() 方法。否則,返回 message id。

ngettext(singular, plural, n)

執行訊息 ID 的複數形式查詢。singular 用作目錄中查詢的訊息 ID,而 n 用於確定要使用的複數形式。返回的訊息字串是 Unicode 字串。

如果目錄中未找到訊息 ID,並且指定了回退,則請求將轉發到回退的 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 的條目,並且已設定回退,則查詢將轉發到回退的 pgettext() 方法。否則,將返回 message id。

3.8 版本新增。

npgettext(context, singular, plural, n)

執行訊息 id 的複數形式查詢。singular 用作目錄中查詢的訊息 id,而 n 用於確定使用哪個複數形式。

如果在目錄中未找到 context 的訊息 id,並且指定了回退,則請求將轉發到回退的 npgettext() 方法。否則,當 n 為 1 時,返回 singular,在所有其他情況下返回 plural

3.8 版本新增。

Solaris 訊息目錄支援

Solaris 作業系統定義了自己的二進位制 .mo 檔案格式,但由於沒有找到有關此格式的文件,因此目前不支援。

Catalog 建構函式

GNOME 使用 James Henstridge 版本的 gettext 模組,但此版本具有略有不同的 API。其記錄的用法是

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

為了與此舊模組相容,函式 Catalog() 是上面描述的 translation() 函式的別名。

此模組與 Henstridge 的模組之間的一個區別是:他的目錄物件支援透過對映 API 進行訪問,但這似乎未使用,因此目前不支援。

使您的程式和模組國際化

國際化 (I18N) 是指使程式瞭解多種語言的操作。本地化 (L10N) 是指在您的程式國際化後,使其適應當地語言和文化習慣。為了為您的 Python 程式提供多語言訊息,您需要執行以下步驟

  1. 透過專門標記可翻譯的字串來準備您的程式或模組

  2. 在您標記的檔案上執行一套工具以生成原始訊息目錄

  3. 建立訊息目錄的特定於語言的翻譯

  4. 使用 gettext 模組,以便正確翻譯訊息字串

為了準備您的程式碼以進行 I18N,您需要檢視檔案中的所有字串。任何需要翻譯的字串都應該透過將其包裝在 _('...') 中進行標記 — 即,呼叫函式 _。例如

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 發行版會為您安裝它們。pygettext.py 類似於 xgettext,但只理解 Python 原始碼,並且不能處理其他程式語言,例如 C 或 C++。pygettext.py 支援與 xgettext 類似的命令列介面;有關其使用的詳細資訊,請執行 pygettext.py --helpmsgfmt.py 與 GNU msgfmt 二進位制相容。使用這兩個程式,您可能不需要 GNU gettext 包即可使您的 Python 應用程式國際化。)

xgettextpygettext 和類似的工具會生成 .po 檔案,這些檔案是訊息目錄。它們是結構化的、人類可讀的檔案,其中包含原始碼中每個標記的字串,以及這些字串的翻譯版本的佔位符。

然後,將這些 .po 檔案的副本交給編寫每種受支援的自然語言的翻譯的個人翻譯人員。他們將完成的特定於語言的版本作為 <語言名稱>.po 檔案發回,該檔案使用 msgfmt 程式編譯成機器可讀的 .mo 二進位制目錄檔案。.mo 檔案由 gettext 模組在執行時用於實際的翻譯處理。

如何在程式碼中使用 gettext 模組取決於您是使單個模組還是整個應用程式國際化。接下來的兩節將討論每種情況。

本地化您的模組

如果要本地化您的模組,則必須注意不要進行全域性更改,例如,對內建名稱空間進行更改。您不應使用 GNU gettext API,而應使用基於類的 API。

假設您的模組名為“spam”,並且該模組的各種自然語言翻譯 .mo 檔案以 GNU gettext 格式位於 /usr/share/locale 中。以下是您要放在模組頂部的程式碼

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

本地化您的應用程式

如果要本地化您的應用程式,則可以將 _() 函式全域性安裝到內建名稱空間中,通常在應用程式的主驅動程式檔案中。這將使您的所有應用程式特定檔案僅使用 _('...'),而無需在每個檔案中顯式安裝它。

因此,在簡單情況下,您只需將以下程式碼新增到應用程式的主驅動程式檔案中

import gettext
gettext.install('myapplication')

如果需要設定區域設定目錄,則可以將其傳遞給 install() 函式

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

動態更改語言

如果您的程式需要同時支援多種語言,您可能需要建立多個翻譯例項,然後在它們之間顯式切換,如下所示

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

延遲翻譯

在大多數編碼情況下,字串在編碼位置進行翻譯。但是,有時您需要標記字串以進行翻譯,但將實際翻譯延遲到以後。一個經典的例子是

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

之所以有效,是因為 _() 的虛擬定義只是返回未更改的字串。並且此虛擬定義將臨時覆蓋內建名稱空間中 _() 的任何定義(直到 del 命令)。但是請注意,如果您在區域性名稱空間中具有 _() 的先前定義。

請注意,第二次使用 _() 不會將“a”標識為可翻譯為 gettext 程式的,因為該引數不是字串文字。

處理此問題的另一種方法是使用以下示例

def N_(message): return message

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

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

在這種情況下,您正在使用函式 N_() 標記可翻譯的字串,該函式不會與 _() 的任何定義衝突。但是,您將需要教您的訊息提取程式查詢使用 N_() 標記的可翻譯字串。xgettextpygettextpybabel extractxpot 都透過使用 -k 命令列開關來支援此操作。此處選擇 N_() 完全是任意的;它可以很容易地成為 MarkThisStringForTranslation()

致謝

以下人員為本模組的建立貢獻了程式碼、反饋、設計建議、先前的實現以及寶貴的經驗

  • Peter Funk

  • James Henstridge

  • Juan David Ibáñez Palomar

  • Marc-André Lemburg

  • Martin von Löwis

  • François Pinard

  • Barry Warsaw

  • Gustavo Niemeyer

腳註