第十九章: 國際化

第十九章: 國際化

Django誕生於美國中部堪薩斯的勞倫斯,距美國的地理中心不到40英里。 像大多數開源項目同樣,Djano社區逐漸開始包括來自全球各地的許多參與者。 鑑於Django社區逐漸變的多樣性,國際化本地化逐漸變得很重要。 因爲不少開發者對這些措辭比較困惑,因此咱們將簡明的定義一下它們。javascript

  • 國際化* 是指爲了該軟件在任何地區的潛在使用而進行程序設計的過程。 它包括了爲未來翻譯而標記的文本(好比用戶界面要素和錯誤信息等)、日期和時間的抽象顯示以便保證不一樣地區的標準獲得遵循、爲不一樣時區提供支持,而且通常 確保代碼中不會存在關於使用者所在地區的假設。 您會常常看到國際化被縮寫爲「I18N」(18表示Internationlization這個單詞首字母I和結尾字母N之間的字母有18個)。html

  • 本地化* 是指使一個國際化的程序爲了在某個特定地區使用而進行實際翻譯的過程。 有時,本地化縮寫爲 L10N 。java

Django自己是徹底國際化了的,全部的字符串均因翻譯所需而被標記,而且設定了與地域無關的顯示控制值,如時間和日期。 Django是帶着50個不一樣的本地化文件發行的。 即便您的母語不是英語,Django也頗有可能已經被翻譯爲您的母語了。python

這些本地化文件所使用的國際化框架一樣也能夠被用在您本身的代碼和模板中。正則表達式

您只須要添加少許的掛接代碼到您的Python代碼和模板中。 這些掛接代碼被稱爲* 翻譯字符串* 。它們告訴Django:若是這段文本的譯文可用的話,它應被翻譯爲終端用戶指定的語言。算法

Django會根據用戶的語言偏好,在線地運用這些掛接指令去翻譯Web應用程序。數據庫

本質上來講,Django作兩件事情:django

  • 它讓開發者和模板的做者指定他們的應用程序的哪些部分應該被翻譯。windows

  • 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代碼和模板中嵌入待翻譯的字符串。

  2. 第二步:把那些字符串翻譯成你要支持的語言。

  3. 第三步:在你的Django settings文件中激活本地中間件。

咱們將詳細地對以上步驟逐一進行描述。

一、如何指定待翻譯字符串

翻譯字符串指定這段須要被翻譯的文本。 這些字符串能夠出如今您的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_naneverbose_name_plural選項,而不是依賴於Django默認的verbose_nameverbose_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 %}模板標記翻譯一個常量字符串 (括以單或雙引號) 或 可變內容:

<title>{% trans "This is the title." %}</title>
<title>{% trans myvar %}</title>

若是有noop 選項,變量查詢仍是有效但翻譯會跳過。 當空缺內容要求未來再翻譯時,這頗有用。

<title>{% trans "myvar" noop %}</title>

在一個帶 {% 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])

System Message: ERROR/3 (<string>, line 519)

Error in 「cnid」 directive: no content permitted.

.. cnid:: 109


  在這種狀況下,當

System Message: WARNING/2 (<string>, line 523)

Explicit markup ends without a blank line; unexpected unindent.

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字符串。

使用這個修飾符意味着你能寫你的函數而且假設輸入是合適的字符串,而後在末尾添加對惰性翻譯對象的支持。

二、如何建立語言文件

當你標記了翻譯字符串,你就須要寫出(或獲取已有的)對應的語言翻譯信息。 這裏就是它如何工做的。

地域限制

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/<language>/LC_MESSAGES/django.(po|mo)

  • $PROJECTPATH/locale/<language>/LC_MESSAGES/django.(po|mo)

  • 全部在settings文件中 LOCALE_PATHS 中列出的路徑以其列出的順序搜索 <language>/LC_MESSAGES/django.(po|mo)

  • $PYTHONPATH/django/conf/locale/<language>/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/$', '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公用程序。

  • 在同一文件夾下展開這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 (<string>, line 1346); backlink

Inline literal start-string without end-string.

相關文章
相關標籤/搜索