Django誕生于美國中部堪薩斯的勞倫斯,距美國的地理中心不到40英里。 像大多數開源項目一樣,Djano社區逐漸開始包括來自全球各地的許多參與者。 鑒于Django社區逐漸變的多樣性,_國際化_和_本地化_逐漸變得很重要。 由于很多開發者對這些措辭比較困惑,所以我們將簡明的定義一下它們。
* 國際化* 是指為了該軟件在任何地區的潛在使用而進行程序設計的過程。 它包括了為將來翻譯而標記的文本(比如用戶界面要素和錯誤信息等)、日期和時間的抽象顯示以便保證不同地區的標準得到遵循、為不同時區提供支持,并且一般確保代碼中不會存在關于使用者所在地區的假設。 您會經常看到國際化被縮寫為“I18N”(18表示Internationlization這個單詞首字母I和結尾字母N之間的字母有18個)。
* 本地化* 是指使一個國際化的程序為了在某個特定地區使用而進行實際翻譯的過程。 有時,本地化縮寫為_L10N_?。
Django本身是完全國際化了的,所有的字符串均因翻譯所需而被標記,并且設定了與地域無關的顯示控制值,如時間和日期。 Django是帶著50個不同的本地化文件發行的。 即使您的母語不是英語,Django也很有可能已經被翻譯為您的母語了。
這些本地化文件所使用的國際化框架同樣也可以被用在您自己的代碼和模板中。
您只需要添加少量的掛接代碼到您的Python代碼和模板中。 這些掛接代碼被稱為* 翻譯字符串* 。它們告訴Django:如果這段文本的譯文可用的話,它應被翻譯為終端用戶指定的語言。
Django會根據用戶的語言偏好,在線地運用這些掛接指令去翻譯Web應用程序。
本質上來說,Django做兩件事情:
* 它讓開發者和模板的作者指定他們的應用程序的哪些部分應該被翻譯。
* Django根據用戶的語言偏好來翻譯Web應用程序。
備注:
Django的翻譯機制是使用 GNU?gettext?(http://www.gnu.org/software/gettext/),具體為Python自帶的標準模塊?gettext?。
如果您不需要國際化:
Django的國際化掛接是默認開啟的,這可能會給Django的運行增加一點點開銷。 如果您不需要國際化支持,那么您可以在您的設置文件中設置?USE_I18N?=?False?。 如果?USE_I18N?被設為 False ,那么Django會進行一些優化,而不加載國際化支持機制。
您也可以從您的?TEMPLATE_CONTEXT_PROCESSORS?設置中移除?'django.core.context_processors.i18n'?。
對你的Django應用進行國際化的三個步驟:
1. 第一步:在你的Python代碼和模板中嵌入待翻譯的字符串。
1. 第二步:把那些字符串翻譯成你要支持的語言。
1. 第三步:在你的Django settings文件中激活本地中間件。
我們將詳細地對以上步驟逐一進行描述。
## 1、如何指定待翻譯字符串
翻譯字符串指定這段需要被翻譯的文本。 這些字符串可以出現在您的Python代碼和模板中。 而標記出這些翻譯字符串則是您的責任;系統僅能翻譯出它所知道的東西。
### 在Python 代碼中
#### 標準翻譯
使用函數?ugettext()?來指定一個翻譯字符串。 作為慣例,使用短別名?_?來引入這個函數以節省鍵入時間.
在下面這個例子中,文本?"Welcome?to?my?site"?被標記為待翻譯字符串:
~~~
from django.utils.translation import ugettext as _
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)
~~~
顯然,你也可以不使用別名來編碼。 下面這個例子和前面兩個例子相同:
~~~
from django.utils.translation import ugettext
def my_view(request):
output = ugettext("Welcome to my site.")
return HttpResponse(output)
~~~
翻譯字符串對于計算出來的值同樣有效。 下面這個例子等同前面一種:
~~~
def my_view(request):
words = ['Welcome', 'to', 'my', 'site.']
output = _(' '.join(words))
return HttpResponse(output)
~~~
翻譯對變量也同樣有效。 這里是一個同樣的例子:
~~~
def my_view(request):
sentence = 'Welcome to my site.'
output = _(sentence)
return HttpResponse(output)
~~~
(以上兩個例子中,對于使用變量或計算值,需要注意的一點是Django的待翻譯字符串檢測工具,make-messages.py?,將不能找到這些字符串。 稍后,在?makemessages?中會有更多討論。)
你傳遞給?`_()`?或?`gettext()`?的字符串可以接受占位符,由Python標準命名字符串插入句法指定的。 例如:
~~~
def my_view(request, m, d):
output = _('Today is %(month)s %(day)s.') % {'month': m, 'day': d}
return HttpResponse(output)
~~~
這項技術使得特定語言的譯文可以對這段文本進行重新排序。 比如,一段英語譯文可能是"Today?is?November?26."?,而一段西班牙語譯文會是?"Hoy?es?26?de?Noviembre."?使用占位符(月份和日期)交換它們的位置。
由于這個原因,無論何時當你有多于一個單一參數時,你應當使用命名字符串插入(例如:?%(day)s?)來替代位置插入(例如:?%s?or?%d?)。 如果你使用位置插入的話,翻譯動作將不能重新排序占位符文本。
#### 標記字符串為不操作
使用?django.utils.translation.gettext_noop()?函數來標記一個不需要立即翻譯的字符串。 這個串會稍后從變量翻譯。
使用這種方法的環境是,有字符串必須以原始語言的形式存儲(如儲存在數據庫中的字符串)而在最后需要被翻譯出來(如顯示給用戶時)。
#### 惰性翻譯
使用?django.utils.translation.gettext_lazy()?函數,使得其中的值只有在訪問時才會被翻譯,而不是在gettext_lazy()?被調用時翻譯。
例如:要翻譯一個模型的?help_text,按以下進行:
~~~
from django.utils.translation import ugettext_lazy
class MyThing(models.Model):
name = models.CharField(help_text=ugettext_lazy('This is the help text'))
~~~
在這個例子中,?ugettext_lazy()?將字符串作為惰性參照存儲,而不是實際翻譯。 翻譯工作將在字符串在字符串上下文中被用到時進行,比如在Django管理頁面提交模板時。
在Python中,無論何處你要使用一個unicode 字符串(一個unicode?類型的對象),您都可以使用一個ugettext_lazy()?調用的結果。 一個ugettext_lazy()對象并不知道如何把它自己轉換成一個字節串。如果你嘗試在一個需要字節串的地方使用它,事情將不會如你期待的那樣。 同樣,你也不能在一個字節串中使用一個 unicode 字符串。所以,這同常規的Python行為是一致的。 例如:
~~~
# This is fine: putting a unicode proxy into a unicode string.
u"Hello %s" % ugettext_lazy("people")
# This will not work, since you cannot insert a unicode object
# into a bytestring (nor can you insert our unicode proxy there)
"Hello %s" % ugettext_lazy("people")
~~~
如果你曾經見到到像"hello"這樣的輸出,你就可能在一個字節串中插入了ugettext_lazy()的結果。 在您的代碼中,那是一個漏洞。
如果覺得?gettext_lazy?太過冗長,可以用?_?(下劃線)作為別名,就像這樣:
~~~
from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
name = models.CharField(help_text=_('This is the help text'))
~~~
在Django模型中總是無一例外的使用惰性翻譯。 為了翻譯,字段名和表名應該被標記。(否則的話,在管理界面中它們將不會被翻譯) 這意味著在Meta類中顯式地編寫verbose_nane和verbose_name_plural選項,而不是依賴于Django默認的verbose_name和verbose_name_plural(通過檢查model的類名得到)。
~~~
from django.utils.translation import ugettext_lazy as _
class MyThing(models.Model):
name = models.CharField(_('name'), help_text=_('This is the help text'))
class Meta:
verbose_name = _('my thing')
verbose_name_plural = _('mythings')
~~~
#### 復數的處理
使用django.utils.translation.ungettext()來指定以復數形式表示的消息。 例如:
~~~
from django.utils.translation import ungettext
def hello_world(request, count):
page = ungettext('there is %(count)d object',
'there are %(count)d objects', count) % {
'count': count,
}
return HttpResponse(page)
~~~
ngettext?函數包括三個參數: 單數形式的翻譯字符串,復數形式的翻譯字符串,和對象的個數(將以?count?變量傳遞給需要翻譯的語言)。
### 模板代碼
Django模板使用兩種模板標簽,且語法格式與Python代碼有些許不同。 為了使得模板訪問到標簽,需要將{%?load?i18n?%}?放在模板最前面。
這個{%?trans?%}模板標記翻譯一個常量字符串 (括以單或雙引號) 或 可變內容:
~~~
{% trans "This is the title." %}
{% trans myvar %}
~~~
如果有noop?選項,變量查詢還是有效但翻譯會跳過。 當空缺內容要求將來再翻譯時,這很有用。
~~~
{% trans "myvar" noop %}
~~~
在一個帶?{%?trans?%}?的字符串中,混進一個模板變量是不可能的。如果你的譯文要求字符串帶有變量(占位符placeholders),請使用?{%?blocktrans?%}?:
~~~
{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %}
~~~
使用模板過濾器來翻譯一個模板表達式,需要在翻譯的這段文本中將表達式綁定到一個本地變量中:
~~~
{% blocktrans with value|filter as myvar %}
This will have {{ myvar }} inside.
{% endblocktrans %}
~~~
如果需要在?blocktrans?標簽內綁定多個表達式,可以用?and?來分隔:
~~~
{% blocktrans with book|title as book_t and author|title as author_t %}
This is {{ book_t }} by {{ author_t }}
{% endblocktrans %}
~~~
為了表示單復數相關的內容,需要在?{%?blocktrans?%}?和?{%?endblocktrans?%}?之間使用?{%?plural?%}?標簽來指定單復數形式,例如:
~~~
{% blocktrans count list|length as counter %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktrans %}
~~~
其內在機制是,所有的塊和內嵌翻譯調用相應的?gettext?或?ngettext?。
每一個RequestContext可以訪問三個指定翻譯變量:
* {{?LANGUAGES?}}?是一系列元組組成的列表,每個元組的第一個元素是語言代碼,第二個元素是用該語言表示的語言名稱。
* 作為一二字符串,LANGUAGE_CODE是當前用戶的優先語言。 例如:?en-us。(請參見下面的Django如何發現語言偏好)
* LANGUAGE_BIDI就是當前地域的說明。 如果為真(True),它就是從右向左書寫的語言,例如: 希伯來語,阿拉伯語。 如果為假(False),它就是從左到右書寫的語言,如: 英語,法語,德語等。
如果你不用這個RequestContext擴展,你可以用3個標記到那些值:
~~~
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
~~~
這些標記亦要求一個?{%?load?i18n?%}?。
翻譯的hook在任何接受常量字符串的模板塊標簽內也是可以使用的。 此時,使用?_()?表達式來指定翻譯字符串,例如:
~~~
{% some_special_tag _("Page not found") value|yesno:_("yes,no") %}
~~~
在這種情況下,標記和過濾器兩個都會看到已經翻譯的字符串,所有它們并不需要提防翻譯操作。
備注:
在這個例子中,翻譯結構將放過字符串"yes,no",而不是單獨的字符串"yes"和"no"。翻譯的字符串將需要包括逗號以便過濾器解析代碼明白如何分割參數。 例如, 一個德語翻譯器可能會翻譯字符串?"yes,no"?為"ja,nein"?(保持逗號原封不動)。
### 與惰性翻譯對象一道工作
在模型和公用函數中,使用ugettext_lazy()和ungettext_lazy()來標記字符串是很普遍的操作。 當你在你的代碼中其它地方使用這些對象時,你應當確定你不會意外地轉換它們成一個字符串,因為它們應被盡量晚地轉換(以便正確的地域生效) 這需要使用及個幫助函數。
#### 拼接字符串: string_concat()
標準Python字符串拼接(''.join([...])?) 將不會工作在包括惰性翻譯對象的列表上。 作為替代,你可以使用django.utils.translation.string_concat(), 這個函數創建了一個惰性對象,其連接起它的內容?_并且_?僅當結果被包括在一個字符串中時轉換它們為字符串 。 例如:
~~~
from django.utils.translation import string_concat
# ...
name = ugettext_lazy(u'John Lennon')
instrument = ugettext_lazy(u'guitar')
result = string_concat([name, ': ', instrument])
~~~
在這種情況下,當result?自己被用與一個字符串時,?result?中的惰性翻譯將僅被轉換為字符串(通常在模板渲染時間)。
#### allow_lazy() 修飾符
Django提供很多功能函數(如:取一個字符串作為他們的第一個參數并且對那個字符串做些什么)。(尤其在django.utils?中) 這些函數被模板過濾器像在其他代碼中一樣直接使用。
如果你寫你自己的類似函數并且與翻譯打交道,當第一個參數是惰性翻譯對象時,你會面臨“做什么”的難題。 因為你可能在視圖之外使用這個函數(并且因此當前線程的本地設置將會不正確),所以你不想立即轉換其為一個字符串。
象這種情況,請使用?django.utils.functional.allow_lazy()?修飾符。 它修改這個函數以便?_假如_第一個參數是一個惰性翻譯, 這個函數的賦值會被延后直到它需要被轉化為一個字符串為止。
例如:
~~~
from django.utils.functional import allow_lazy
def fancy_utility_function(s, ...):
# Do some conversion on string 's'
# ...
fancy_utility_function = allow_lazy(fancy_utility_function, unicode)
~~~
allow_lazy()?裝飾符 采用了另外的函數來裝飾,以及一定量的,原始函數可以返回的特定類型的額外參數 (*args?) 。 通常,在這里包括?unicode?就足夠了并且確定你的函數將僅返回Unicode字符串。
使用這個修飾符意味著你能寫你的函數并且假設輸入是合適的字符串,然后在末尾添加對惰性翻譯對象的支持。
## 2、如何創建語言文件
當你標記了翻譯字符串,你就需要寫出(或獲取已有的)對應的語言翻譯信息。 這里就是它如何工作的。
地域限制
Django不支持把你的應用本地化到一個連它自己都還沒被翻譯的地域。 在這種情況下,它將忽略你的翻譯文件。 如果你想嘗試這個并且Django支持它,你會不可避免地見到這樣一個混合體––參雜著你的譯文和來自Django自己的英文。 如果你的應用需要你支持一個Django中沒有的地域,你將至少需要做一個Django core的最小翻譯。
### 消息文件
第一步,就是為一種語言創建一個信息文件。 信息文件是包含了某一語言翻譯字符串和對這些字符串的翻譯的一個文本文件。 信息文件以?.po?為后綴名。
Django中帶有一個工具,?bin/make-messages.py?,它完成了這些文件的創建和維護工作。 運行以下命令來創建或更新一個信息文件:
~~~
django-admin.py makemessages -l de
~~~
其中?de?是所創建的信息文件的語言代碼。 在這里,語言代碼是以本地格式給出的。 例如,巴西地區的葡萄牙語為?pt_BR?,澳大利亞地區的德語為?de_AT?。
這段腳本應該在三處之一運行:
* Django項目根目錄。
* 您Django應用的根目錄。
* django?根目錄(不是Subversion檢出目錄,而是通過?$PYTHONPATH?鏈接或位于該路徑的某處)。 這僅和你為Django自己創建一個翻譯時有關。
這段腳本遍歷你的項目源樹或你的應用程序源樹并且提取出所有為翻譯而被標記的字符串。 它在locale/LANG/LC_MESSAGES?目錄下創建(或更新)了一個信息文件。針對上面的de,應該是locale/de/LC_MESSAGES/django.po。
作為默認,?django-admin.py?makemessages?檢測每一個有?.html?擴展名的文件。? 以備你要重載缺省值,使用--extension?或?-e?選項指定文件擴展名來檢測。
~~~
django-admin.py makemessages -l de -e txt
~~~
用逗號和(或)使用-e或--extension來分隔多項擴展名:
~~~
django-admin.py makemessages -l de -e html,txt -e xml
~~~
當創建JavaScript翻譯目錄時,你需要使用特殊的Django域:**not**?-e?js?。
沒有gettext?
如果沒有安裝?gettext?組件,?make-messages.py?將會創建空白文件。 這種情況下,安裝?gettext?組件或只是復制英語信息文件(?conf/locale/en/LC_MESSAGES/django.po?)來作為一個起點;只是一個空白的翻譯信息文件而已。
工作在Windows上么?
如果你正在使用Windows,且需要安裝GNU gettext共用程序以便?django-admin?makemessages?可以工作,請參看下面Windows小節中gettext部分以獲得更多信息。
.po?文件格式很直觀。 每個?.po?文件包含一小部分的元數據,比如翻譯維護人員的聯系信息,而文件的大部分內容是簡單的翻譯字符串和對應語言翻譯結果的映射關系的列表。
舉個例子,如果Django應用程序包括一個?"Welcome?to?my?site."?的待翻譯字符串 ,像這樣:
~~~
_("Welcome to my site.")
~~~
則django-admin.py?makemessages將創建一個?.po?文件來包含以下片段的消息:
~~~
#: path/to/python/module.py:23
msgid "Welcome to my site."
msgstr ""
~~~
快速解釋:
* msgid?是在源文件中出現的翻譯字符串。 不要做改動。
* msgstr?是相應語言的翻譯結果。 剛創建時它只是空字符串,此時就需要你來完成它。 注意不要丟掉語句前后的引號。
* 作為方便之處,每一個消息都包括:以?#?為前綴的一個注釋行并且定位上邊的msgid?行,文件名和行號。
對于比較長的信息也有其處理方法。?msgstr?(或?msgid?)后緊跟著的字符串為一個空字符串。 然后真正的內容在其下面的幾行。 這些字符串會被直接連在一起。 同時,不要忘了字符串末尾的空格,因為它們會不加空格地連到一起。
若要對新創建的翻譯字符串校驗所有的源代碼和模板,并且更新所有語言的信息文件,可以運行以下命令:
~~~
django-admin.py makemessages -a
~~~
### 編譯信息文件
創建信息文件之后,每次對其做了修改,都需要將它重新編譯成一種更有效率的形式,供?gettext?使用。可以使用django-admin.py?compilemessages完成。
這個工具作用于所有有效的?.po?文件,創建優化過的二進制?.mo?文件供?gettext?使用。在你可以運行django-admin.py?makemessages的目錄下,運行django-admin.py?compilemessages:
~~~
django-admin.py compilemessages
~~~
就是這樣了。 你的翻譯成果已經可以使用了。
## Django如何處理語言偏好
一旦你準備好了翻譯,如果希望在Django中使用,那么只需要激活這些翻譯即可。
在這些功能背后,Django擁有一個靈活的模型來確定在安裝和使用應用程序的過程中選擇使用的語言。
要設定一個安裝階段的語種偏好,請設定LANGUAGE_CODE。如果其他翻譯器沒有找到一個譯文,Django將使用這個語種作為缺省的翻譯最終嘗試。
如果你只是想要用本地語言來運行Django,并且該語言的語言文件存在,只需要簡單地設置?LANGUAGE_CODE?即可。
如果要讓每一個使用者各自指定語言偏好,就需要使用?LocaleMiddleware?。?LocaleMiddleware?使得Django基于請求的數據進行語言選擇,從而為每一位用戶定制內容。 它為每一個用戶定制內容。
使用?LocaleMiddleware?需要在?MIDDLEWARE_CLASSES?設置中增加'django.middleware.locale.LocaleMiddleware'?。 中間件的順序是有影響的,最好按照依照以下要求:
* 保證它是第一批安裝的中間件類。
* 因為?LocalMiddleware?要用到session數據,所以需要放在?SessionMiddleware?之后。
* 如果你使用CacheMiddleware,把LocaleMiddleware放在它后面。
例如,?MIDDLE_CLASSES?可能會是如此:
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
)
(更多關于中間件的內容,請參閱第17章)
LocaleMiddleware?按照如下算法確定用戶的語言:
* 首先,在當前用戶的 session 的中查找django_language鍵;
* 如未找到,它會找尋一個cookie
* 還找不到的話,它會在 HTTP 請求頭部里查找Accept-Language, 該頭部是你的瀏覽器發送的,并且按優先順序告訴服務器你的語言偏好。 Django會嘗試頭部中的每一個語種直到它發現一個可用的翻譯。
* 以上都失敗了的話, 就使用全局的?LANGUAGE_CODE?設定值。
備注:
> 在上述每一處,語種偏好應作為字符串,以標準的語種格式出現。 例如,巴西葡萄牙語是pt-br
>
> 如果一個基本語種存在而亞語種沒有指定,Django將使用基本語種。 比如,如果用戶指定了?de-at?(澳式德語)但Django只有針對?de?的翻譯,那么?de?會被選用。
>
> 只有在?LANGUAGES?設置中列出的語言才能被選用。 若希望將語言限制為所提供語言中的某些(因為應用程序并不提供所有語言的表示),則將?LANGUAGES?設置為所希望提供語言的列表,例如: 例如:
~~~
LANGUAGES = (
('de', _('German')),
('en', _('English')),
)
~~~
> 上面這個例子限制了語言偏好只能是德語和英語(包括它們的子語言,如?de-ch?和?en-us?)。
>
> 如果自定義了?LANGUAGES?,將語言標記為翻譯字符串是可以的,但是,請不要使用django.utils.translation?中的?gettext()?(決不要在settings文件中導入?django.utils.translation?,因為這個模塊本身是依賴于settings,這樣做會導致無限循環),而是使用一個“虛構的”?gettext()?。
>
> 解決方案就是使用一個“虛假的”?gettext()?。以 下是一個settings文件的例子:
~~~
ugettext = lambda s: s
LANGUAGES = (
('de', ugettext('German')),
('en', ugettext('English')),
)
~~~
> 這樣做的話,?make-messages.py?仍會尋找并標記出將要被翻譯的這些字符串,但翻譯不會在運行時進行,故而需要在任何使用?_LANGUAGES_?的代碼中用“真實的”?ugettext()。
>
> LocaleMiddleware?只能選擇那些Django已經提供了基礎翻譯的語言。 如果想要在應用程序中對Django中還沒有基礎翻譯的語言提供翻譯,那么必須至少先提供該語言的基本的翻譯。 例如,Django使用特定的信息ID來翻譯日期和時間格式,故要讓系統正常工作,至少要提供這些基本的翻譯。
>
> 以英語的?.po?文件為基礎,翻譯其中的技術相關的信息,可能還包括一些使之生效的信息。
>
> 技術相關的信息ID很容易被認出來:它們都是大寫的。 這些信息ID的翻譯與其他信息不同:你需要提供其對應的本地化內容。 例如,對于 DATETIME_FORMAT (或 DATE_FORMAT 、 TIME_FORMAT ),應該提供希望在該語言中使用的格式化字符串。 格式被模板標簽now用來識別格式字符串。
一旦LocaleMiddleware決定用戶的偏好,它會讓這個偏好作為request.LANGUAGE_CODE對每一個HttpRequest有效。請隨意在你的視圖代碼中讀一讀這個值。 以下是一個簡單的例子:
~~~
def hello_world(request):
if request.LANGUAGE_CODE == 'de-at':
return HttpResponse("You prefer to read Austrian German.")
else:
return HttpResponse("You prefer to read another language.")
~~~
注意,對于靜態翻譯(無中間件)而言,此語言在settings.LANGUAGE_CODE中,而對于動態翻譯(中間件),它在request.LANGUAGE_CODE中。
## 在你自己的項目中使用翻譯
Django使用以下算法尋找翻譯:
* 首先,Django在該視圖所在的應用程序文件夾中尋找?locale?目錄。 若找到所選語言的翻譯,則加載該翻譯。
* 第二步,Django在項目目錄中尋找?locale?目錄。 若找到翻譯,則加載該翻譯。
* 最后,Django使用?django/conf/locale?目錄中的基本翻譯。
以這種方式,你可以創建包含獨立翻譯的應用程序,可以覆蓋項目中的基本翻譯。 或者,你可以創建一個包含幾個應用程序的大項目,并將所有需要的翻譯放在一個大的項目信息文件中。 決定權在你手中。
所有的信息文件庫都是以同樣方式組織的: 它們是:
* $APPPATH/locale//LC_MESSAGES/django.(po|mo)
* $PROJECTPATH/locale//LC_MESSAGES/django.(po|mo)
* 所有在settings文件中?LOCALE_PATHS?中列出的路徑以其列出的順序搜索/LC_MESSAGES/django.(po|mo)
* $PYTHONPATH/django/conf/locale//LC_MESSAGES/django.(po|mo)
要創建信息文件,也是使用?django-admin.py?makemessages.py?工具,和Django信息文件一樣。 需要做的就是進入正確的目錄——?conf/locale?(在源碼樹的情況下)或者?locale/?(在應用程序信息或項目信息的情況下)所在的目錄下。 同樣地,使用?compile-messages.py?生成?gettext?需要使用的二進制?django.mo?文件。
您亦可運行django-admin.py?compilemessages?--settings=path.to.settings?來使編譯器處理所有存在于您LOCALE_PATHS?設置中的目錄。
應用程序信息文件稍微難以發現——因為它們需要?LocaleMiddle?。如果不使用中間件,Django只會處理Django的信息文件和項目的信息文件。
最后,需要考慮一下翻譯文件的結構。 若應用程序要發放給其他用戶,應用到其它項目中,可能需要使用應用程序相關的翻譯。 但是,使用應用程序相關的翻譯和項目翻譯在使用?make-messages?時會產生古怪的問題。它會遍歷當前路徑下所有的文件夾,這樣可能會把應用消息文件里存在的消息ID重復放入項目消息文件中。
最容易的解決方法就是將不屬于項目的應用程序(因此附帶著本身的翻譯)存儲在項目樹之外。 這樣做的話,項目級的?make-messages?將只會翻譯與項目精確相關的,而不包括那些獨立發布的應用程序中的字符串。
## set_language?重定向視圖
方便起見,Django自帶了一個?django.views.i18n.set_language?視圖,作用是設置用戶語言偏好并重定向返回到前一頁面。
在URLconf中加入下面這行代碼來激活這個視圖:
~~~
(r'^i18n/', include('django.conf.urls.i18n')),
~~~
(注意這個例子使得這個視圖在?/i18n/setlang/?中有效。)
這個視圖是通過?GET?方法調用的,在請求中包含了?language?參數。 如果session已啟用,這個視圖會將語言選擇保存在用戶的session中。 否則,它會以缺省名django_language在cookie中保存這個語言選擇。(這個名字可以通過LANGUAGE_COOKIE_NAME設置來改變)
保存了語言選擇后,Django根據以下算法來重定向頁面:
* Django 在?POST?數據中尋找一個?下一個?參數。
* 如果?next?參數不存在或為空,Django嘗試重定向頁面為HTML頭部信息中?Referer?的值。
* 如果?Referer?也是空的,即該用戶的瀏覽器并不發送?Referer?頭信息,則頁面將重定向到?/?(頁面根目錄)。
這是一個HTML模板代碼的例子:
~~~
<form action="/i18n/setlang/" method="post">
<input name="next" type="hidden" value="/next/page/" />
<select name="language">
{% for lang in LANGUAGES %}
<option value="{{ lang.0 }}">{{ lang.1 }}</option>
{% endfor %}
</select>
<input type="submit" value="Go" />
</form>
~~~
## 翻譯與JavaScript
將翻譯添加到JavaScript會引起一些問題:
* JavaScript代碼無法訪問一個?gettext?的實現。
* JavaScript 代碼并不訪問 .po或 .mo 文件;它們需要由服務器分發。
* 針對JavaScript的翻譯目錄應盡量小。
Django已經提供了一個集成解決方案: 它會將翻譯傳遞給JavaScript,因此就可以在JavaScript中調用gettext?之類的代碼。
### javascript_catalog視圖
這些問題的主要解決方案就是?javascript_catalog?視圖。該視圖生成一個JavaScript代碼庫,包括模仿 gettext 接口的函數,和翻譯字符串的數組。 這些翻譯字符串來自于你在info_dict或URl中指定的應用,工程或Django內核。
像這樣使用:
~~~
js_info_dict = {
'packages': ('your.app.package',),
}
urlpatterns = patterns('',
(r'^jsi18n//pre>, 'django.views.i18n.javascript_catalog', js_info_dict),
)
~~~
packages?里的每個字符串應該是Python中的點分割的包的表達式形式(和在?INSTALLED_APPS?中的字符串相同的格式),而且應指向包含?locale?目錄的包。 如果指定了多個包,所有的目錄會合并成一個目錄。 如果有用到來自不同應用程序的字符串的JavaScript,這種機制會很有幫助。
你可以動態使用視圖,將包放在urlpatterns里:
~~~
urlpatterns = patterns('',
(r'^jsi18n/(?P<packages>\S+)/$', 'django.views.i18n.javascript_catalog'),
)
~~~
這樣的話,就可以在URL中指定由加號(?+?)分隔包名的包了。 如果頁面使用來自不同應用程序的代碼,且經常改變,還不想將其放在一個大的目錄文件中,對于這些情況,顯然這是很有用的。 出于安全考慮,這些值只能是?django.conf?或?INSTALLED_APPS?設置中的包。
### 使用JavaScript翻譯目錄
要使用這個目錄,只要這樣引入動態生成的腳本:
~~~
<script type="text/javascript" src="/path/to/jsi18n/"></script>
~~~
這就是管理頁面如何從服務器獲取翻譯目錄。 當目錄加載后,JavaScript代碼就能通過標準的?gettext?接口進行訪問:
~~~
document.write(gettext('this is to be translated'));
~~~
也有一個ngettext接口:
~~~
var object_cnt = 1 // or 0, or 2, or 3, ...
s = ngettext('literal for the singular case',
'literal for the plural case', object_cnt);
~~~
甚至有一個字符串插入函數:
~~~
function interpolate(fmt, obj, named);
~~~
插入句法是從Python借用的,所以interpolate?函數對位置和命名插入均提供支持:
> 位置插入?obj包括一個JavaScript數組對象,元素值在它們對應于fmt的占位符中以它們出現的相同次序順序插值 。 例如:
~~~
fmts = ngettext('There is %s object. Remaining: %s',
'There are %s objects. Remaining: %s', 11);
s = interpolate(fmts, [11, 20]);
// s is 'There are 11 objects. Remaining: 20'
~~~
> 命名插入 通過傳送為真(TRUE)的布爾參數name來選擇這個模式。?obj包括一個 JavaScript 對象或相關數組。 例如:
~~~
d = {
count: 10
total: 50
};
fmts = ngettext('Total: %(total)s, there is %(count)s object',
'there are %(count)s of a total of %(total)s objects', d.count);
s = interpolate(fmts, d, true);
~~~
但是,你不應重復編寫字符串插值: 這還是JavaScript,所以這段代碼不得不重復做正則表達式置換。 它不會和Python中的字符串插補一樣快,因此只有真正需要的時候再使用它(例如,利用?ngettext?生成合適的復數形式)。
### 創建JavaScript翻譯目錄
你可以創建和更改翻譯目錄,就像其他
Django翻譯目錄一樣,使用django-admin.py makemessages 工具。 唯一的差別是需要提供一個-d?djangojs?的參數,就像這樣:
~~~
django-admin.py makemessages -d djangojs -l de
~~~
這樣來創建或更新JavaScript的德語翻譯目錄。 和普通的Django翻譯目錄一樣,更新了翻譯目錄后,運行compile-messages.py?即可。
## 熟悉?gettext?用戶的注意事項
如果你了解?gettext?,你可能會發現Django進行翻譯時的一些特殊的東西:
* 字符串域為?django?或?djangojs?。字符串域是用來區別將數據存儲在同一信息文件庫(一般是/usr/share/locale/?)的不同程序。django 域是為Python和模板翻譯字符串服務的,被加載到全局翻譯目錄。?djangojs?域只是用來盡可能縮小JavaScript翻譯的體積。
* Django不單獨使用?xgettext?, 而是經過Python包裝后的xgettext和msgfmt。這主要是為了方便。
## Windows下的gettext
對于那些要提取消息或編譯消息文件的人們來說,需要的只有這么多。翻譯工作本身僅僅包含編輯這個類型的現存文件,但如果你要創建你自己的消息文件,或想要測試或編譯一個更改過的消息文件,你將需要這個gettext公用程序。
* > 從http://sourceforge.net/projects/gettext下載以下zip文件
* gettext-runtime-X.bin.woe32.zip
* gettext-tools-X.bin.woe32.zip
* libiconv-X.bin.woe32.zip
* 在同一文件夾下展開這3個文件。(也就是?C:\Program?Files\gettext-utils?)
* 更新系統路徑:
* 控制面板?>?系統>?高級?>?環境變量
* 在系統變量列表中,點擊Path,點擊Edit
* 把;C:\Program?Files\gettext-utils\bin加到變量值字段的末尾。
只要`xgettext?--version`命令正常工作,你亦可使用從別處獲得的gettext的二進制代碼。 有些版本的0.14.4二進制代碼被發現不支持這個命令。 不要試圖與Django公用程序一起使用一個gettext。在一個windows命令提示窗口輸入命令 `xgettext --version`?將導致出現一個錯誤彈出窗口–“xgettext.exe產生錯誤并且將被windows關閉”。
System Message: WARNING/2 (, line 1346);?_[backlink](http://docs.30c.org/djangobook2/chapter19/#id16)_
Inline literal start-string without end-string.