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