翻譯

總覽

為了使 Django 專案可翻譯,您必須在 Python 程式碼和樣板中加入最少的鉤子。這些鉤子稱為翻譯字串。它們告訴 Django:「如果該語言有此文字的翻譯,則此文字應翻譯成最終使用者的語言。」標記可翻譯的字串是您的責任;系統只能翻譯它知道的字串。

然後,Django 提供實用工具將翻譯字串提取到訊息檔案中。此檔案對翻譯人員來說是一種方便的方式,可在目標語言中提供翻譯字串的等效內容。翻譯人員填寫完訊息檔案後,必須進行編譯。此過程依賴 GNU gettext 工具集。

完成此操作後,Django 會根據使用者的語言偏好,在每個可用語言中動態翻譯 Web 應用程式。

Django 的國際化鉤子預設為開啟,這表示框架的某些位置會有一些與 i18n 相關的額外負擔。如果您不使用國際化,您應該花兩秒鐘在設定檔中設定 USE_I18N = False。然後,Django 會進行一些最佳化,以避免載入國際化機制。

注意

請確保您已為您的專案啟用了翻譯 (最快的方法是檢查 MIDDLEWARE 是否包含 django.middleware.locale.LocaleMiddleware)。如果您還沒設定,請參閱 Django 如何偵測語言偏好

國際化:在 Python 程式碼中

標準翻譯

使用函式 gettext() 指定翻譯字串。慣例是將其匯入為較短的別名 _,以節省輸入時間。

注意

Python 的標準函式庫 gettext 模組會將 _() 安裝到全域命名空間中,作為 gettext() 的別名。在 Django 中,我們選擇不遵循此做法,原因有兩個

  1. 有時,您應該使用 gettext_lazy() 作為特定檔案的預設翻譯方法。在全域命名空間中沒有 _() 的情況下,開發人員必須思考哪一個翻譯函式最合適。

  2. 底線字元 (_) 用於表示 Python 互動式 Shell 和 doctest 測試中的「前一個結果」。安裝全域 _() 函式會導致干擾。將 gettext() 明確匯入為 _() 可避免此問題。

哪些函式可以別名為 _

由於 xgettext (由 makemessages 使用) 的運作方式,只有接受單一字串引數的函式才能匯入為 _

在此範例中,文字 "歡迎來到我的網站。" 會標記為翻譯字串

from django.http import HttpResponse
from django.utils.translation import gettext as _


def my_view(request):
    output = _("Welcome to my site.")
    return HttpResponse(output)

您可以不使用別名來編寫此程式碼。此範例與前一個範例相同

from django.http import HttpResponse
from django.utils.translation import gettext


def my_view(request):
    output = gettext("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 的翻譯字串偵測實用工具 django-admin makemessages 將無法找到這些字串。稍後會有關於 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%d)。如果您使用位置內插,翻譯將無法重新排序佔位符文字。

由於字串提取是由 xgettext 命令完成的,因此只有 gettext 支援的語法才受 Django 支援。特別是,Python 的 f-strings 尚未受到 xgettext 支援,而且 JavaScript 樣板字串需要 gettext 0.21+。

翻譯人員的註解

如果您想要提供翻譯人員關於可翻譯字串的提示,您可以在字串前面的行中新增以 Translators 關鍵字為首的註解,例如

def my_view(request):
    # Translators: This message appears on the home page only
    output = gettext("Welcome to my site.")

然後,該註解會出現在與其下方可翻譯的建構相關的產生的 .po 檔案中,並且也應由大多數翻譯工具顯示。

注意

只是為了完整起見,這是產生的 .po 檔案的對應片段

#. Translators: This message appears on the home page only
# path/to/python/file.py:123
msgid "Welcome to my site."
msgstr ""

這也適用於樣板。如需更多詳細資訊,請參閱 樣板中翻譯人員的註解

將字串標記為不執行操作

使用函式 django.utils.translation.gettext_noop() 將字串標記為翻譯字串,而不進行翻譯。字串稍後會從變數翻譯。

如果您有應該以原始語言儲存的常數字串,因為它們會在系統或使用者之間交換 (例如資料庫中的字串),但應該在最後可能的時間點翻譯,例如當字串呈現給使用者時,則請使用此功能。

複數化

使用函式 django.utils.translation.ngettext() 指定複數訊息。

ngettext() 接受三個引數:單數翻譯字串、複數翻譯字串和物件數量。

當您需要將 Django 應用程式本地化為 複數形式的數量和複雜度大於英文中使用的兩種形式 (單數的 'object',以及所有 count 不等於 1 的情況下的 'objects',無論其值為何) 的語言時,此函式很有用。

例如

from django.http import HttpResponse
from django.utils.translation import ngettext


def hello_world(request, count):
    page = ngettext(
        "there is %(count)d object",
        "there are %(count)d objects",
        count,
    ) % {
        "count": count,
    }
    return HttpResponse(page)

在這個範例中,物件的數量會以 count 變數的形式傳遞給翻譯語言。

請注意,複數化是很複雜的,而且在每種語言中的運作方式都不同。將 count 與 1 比較並不總是正確的規則。這段程式碼看起來很精巧,但對於某些語言會產生不正確的結果。

from django.utils.translation import ngettext
from myapp.models import Report

count = Report.objects.count()
if count == 1:
    name = Report._meta.verbose_name
else:
    name = Report._meta.verbose_name_plural

text = ngettext(
    "There is %(count)d %(name)s available.",
    "There are %(count)d %(name)s available.",
    count,
) % {"count": count, "name": name}

不要嘗試實作你自己的單數或複數邏輯;它不會是正確的。在這種情況下,可以考慮使用以下方法。

text = ngettext(
    "There is %(count)d %(name)s object available.",
    "There are %(count)d %(name)s objects available.",
    count,
) % {
    "count": count,
    "name": Report._meta.verbose_name,
}

注意

當使用 ngettext() 時,請確保您為文字中包含的每個外插變數使用單一名稱。在上面的範例中,請注意我們如何在兩個翻譯字串中都使用了 name 這個 Python 變數。這個範例除了在某些語言中不正確外(如上所述),還會失敗。

text = ngettext(
    "There is %(count)d %(name)s available.",
    "There are %(count)d %(plural_name)s available.",
    count,
) % {
    "count": Report.objects.count(),
    "name": Report._meta.verbose_name,
    "plural_name": Report._meta.verbose_name_plural,
}

當執行 django-admin compilemessages 時,您會收到錯誤訊息。

a format specification for argument 'name', as in 'msgstr[0]', doesn't exist in 'msgid'

上下文標記

有時候,單字會有好幾個意思,例如英文中的 "May",它可以指月份名稱,也可以指動詞。為了讓翻譯人員可以在不同的上下文中正確翻譯這些單字,您可以使用 django.utils.translation.pgettext() 函式,或者如果字串需要複數化,則可以使用 django.utils.translation.npgettext() 函式。兩者都將上下文字串作為第一個變數。

在產生的 .po 檔案中,該字串會出現與相同字串的不同上下文標記一樣多次(上下文會出現在 msgctxt 行上),讓翻譯人員可以為每個字串提供不同的翻譯。

例如

from django.utils.translation import pgettext

month = pgettext("month name", "May")

from django.db import models
from django.utils.translation import pgettext_lazy


class MyThing(models.Model):
    name = models.CharField(
        help_text=pgettext_lazy("help text for MyThing model", "This is the help text")
    )

會以以下形式出現在 .po 檔案中

msgctxt "month name"
msgid "May"
msgstr ""

上下文標記也受 translateblocktranslate 樣板標籤支援。

延遲翻譯

django.utils.translation 中使用翻譯函式的延遲版本(在其名稱中很容易識別出 lazy 後綴)以延遲翻譯字串 – 當值被存取時而不是當它們被呼叫時。

這些函式會儲存字串的延遲參照 – 而不是實際的翻譯。翻譯本身會在字串用於字串內容中時進行,例如在樣板渲染中。

當對這些函式的呼叫位於模組載入時執行的程式碼路徑中時,這一點至關重要。

當定義模型、表單和模型表單時,很容易發生這種情況,因為 Django 實作這些時,它們的欄位實際上是類別層級的屬性。因此,請務必在以下情況下使用延遲翻譯。

模型欄位和關係的 verbose_namehelp_text 選項值

例如,要翻譯以下模型中name欄位的說明文字,請執行以下操作。

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    name = models.CharField(help_text=_("This is the help text"))

您可以透過使用 ForeignKeyManyToManyFieldOneToOneField 關係的名稱標記為可翻譯,方法是使用它們的 verbose_name 選項。

class MyThing(models.Model):
    kind = models.ForeignKey(
        ThingKind,
        on_delete=models.CASCADE,
        related_name="kinds",
        verbose_name=_("kind"),
    )

就像您在 verbose_name 中所做的一樣,您應該為關係提供小寫的 verbose 名稱文字,因為 Django 會在需要時自動將其轉換為首字母大寫。

模型 verbose 名稱值

建議始終提供明確的 verbose_nameverbose_name_plural 選項,而不是依賴 Django 透過查看模型的類別名稱來執行、以英文為中心且有點天真的 verbose 名稱後備決定。

from django.db import models
from django.utils.translation import gettext_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 = _("my things")

模型方法的 description 引數,用於 @display 裝飾器

對於模型方法,您可以透過 display() 裝飾器的 description 引數,向 Django 和管理網站提供翻譯。

from django.contrib import admin
from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    kind = models.ForeignKey(
        ThingKind,
        on_delete=models.CASCADE,
        related_name="kinds",
        verbose_name=_("kind"),
    )

    @admin.display(description=_("Is it a mouse?"))
    def is_mouse(self):
        return self.kind.type == MOUSE_TYPE

使用延遲翻譯物件

gettext_lazy() 呼叫的結果可以在您在其他 Django 程式碼中使用字串(str 物件)的任何地方使用,但它可能不適用於任意 Python 程式碼。例如,以下程式碼將無法運作,因為 requests 函式庫不處理 gettext_lazy 物件。

body = gettext_lazy("I \u2764 Django")  # (Unicode :heart:)
requests.post("https://example.com/send", data={"body": body})

您可以透過在將 gettext_lazy() 物件傳遞給非 Django 程式碼之前,將其轉換為文字字串來避免此類問題。

requests.post("https://example.com/send", data={"body": str(body)})

如果您不喜歡較長的 gettext_lazy 名稱,您可以將其別名為 _(底線),如下所示。

from django.db import models
from django.utils.translation import gettext_lazy as _


class MyThing(models.Model):
    name = models.CharField(help_text=_("This is the help text"))

使用 gettext_lazy()ngettext_lazy() 來標記模型和實用函式中的字串是一種常見的操作。當您在程式碼中的其他地方使用這些物件時,您應該確保不要意外地將它們轉換為字串,因為它們應該盡可能延後轉換(以便正確的語言環境生效)。這需要使用接下來描述的輔助函式。

延遲翻譯和複數

當對複數字串使用延遲翻譯 (n[p]gettext_lazy) 時,您通常在字串定義時不知道 number 引數。因此,您有權傳遞索引鍵名稱而不是整數作為 number 引數。然後,在字串插補期間,將在字典中該索引鍵下尋找 number。以下為範例。

from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ngettext_lazy


class MyForm(forms.Form):
    error_message = ngettext_lazy(
        "You only provided %(num)d argument",
        "You only provided %(num)d arguments",
        "num",
    )

    def clean(self):
        # ...
        if error:
            raise ValidationError(self.error_message % {"num": number})

如果字串只包含一個未命名的預留位置,您可以直接使用 number 引數進行插補。

class MyForm(forms.Form):
    error_message = ngettext_lazy(
        "You provided %d argument",
        "You provided %d arguments",
    )

    def clean(self):
        # ...
        if error:
            raise ValidationError(self.error_message % number)

格式化字串:format_lazy()

format_stringstr.format() 的任何引數包含延遲翻譯物件時,Python 的 str.format() 方法將無法運作。相反地,您可以使用 django.utils.text.format_lazy(),它會建立一個延遲物件,只有當結果包含在字串中時,才會執行 str.format() 方法。例如:

from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy

...
name = gettext_lazy("John Lennon")
instrument = gettext_lazy("guitar")
result = format_lazy("{name}: {instrument}", name=name, instrument=instrument)

在這種情況下,result 中的延遲翻譯只有在 result 本身用於字串中時才會轉換為字串(通常在樣板渲染時)。

延遲翻譯中延遲的其他用途

對於您想要延遲翻譯,但必須將可翻譯字串作為引數傳遞給另一個函式的任何其他情況,您可以自己將此函式包裝在延遲呼叫中。例如:

from django.utils.functional import lazy
from django.utils.translation import gettext_lazy as _


def to_lower(string):
    return string.lower()


to_lower_lazy = lazy(to_lower, str)

然後在稍後:

lazy_string = to_lower_lazy(_("My STRING!"))

語言的本地化名稱

get_language_info(lang_code)[原始碼]

get_language_info() 函式提供有關語言的詳細資訊。

>>> from django.utils.translation import activate, get_language_info
>>> activate("fr")
>>> li = get_language_info("de")
>>> print(li["name"], li["name_local"], li["name_translated"], li["bidi"])
German Deutsch Allemand False

字典的 namename_localname_translated 屬性分別包含以英文、語言本身和您目前使用中的語言顯示的語言名稱。bidi 屬性僅適用於雙向語言時為 True。

語言資訊的來源是 django.conf.locale 模組。範本程式碼也可以存取此資訊。請參閱下方。

國際化:在樣板程式碼中

Django 樣板中的翻譯使用兩個樣板標籤,且語法與 Python 程式碼中略有不同。為了讓您的樣板能夠存取這些標籤,請在樣板的頂部加上 {% load i18n %}。如同所有樣板標籤,此標籤需要在所有使用翻譯的樣板中載入,即使是繼承自其他已載入 i18n 標籤的樣板也需要載入。

警告

已翻譯的字串在樣板中渲染時不會被跳脫。這允許您在翻譯中包含 HTML,例如用於強調,但潛在的危險字元(例如 ")也會保持不變地渲染。

translate 樣板標籤

{% translate %} 樣板標籤翻譯常數字串(以單引號或雙引號括住)或變數內容。

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

如果存在 noop 選項,變數查找仍然會進行,但會跳過翻譯。這在「存根化」未來需要翻譯的內容時非常有用。

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

在內部,內嵌翻譯使用 gettext() 呼叫。

如果將樣板變數(上面的 myvar)傳遞給標籤,該標籤會先在執行時將該變數解析為字串,然後在訊息目錄中查找該字串。

無法在 {% translate %} 內的字串中混合樣板變數。如果您的翻譯需要帶有變數(佔位符)的字串,請改用 {% blocktranslate %}

如果您想在不顯示翻譯字串的情況下檢索它,您可以使用以下語法:

{% translate "This is the title" as the_title %}

<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">

實際上,您會使用它來取得可以在樣板中的多個位置使用的字串,或者將輸出作為其他樣板標籤或篩選器的參數使用。

{% translate "starting point" as start %}
{% translate "end point" as end %}
{% translate "La Grande Boucle" as race %}

<h1>
  <a href="/" title="{% blocktranslate %}Back to '{{ race }}' homepage{% endblocktranslate %}">{{ race }}</a>
</h1>
<p>
{% for stage in tour_stages %}
    {% cycle start end %}: {{ stage }}{% if forloop.counter|divisibleby:2 %}<br>{% else %}, {% endif %}
{% endfor %}
</p>

{% translate %} 也支援使用 context 關鍵字的 上下文標記

{% translate "May" context "month name" %}

blocktranslate 樣板標籤

translate 標籤相反,blocktranslate 標籤允許您使用佔位符來標記由文字和變數內容組成的複雜句子以進行翻譯。

{% blocktranslate %}This string will have {{ value }} inside.{% endblocktranslate %}

若要翻譯樣板表達式(例如,存取物件屬性或使用樣板篩選器),您需要將該表達式繫結到要在翻譯區塊中使用的區域變數。範例:

{% blocktranslate with amount=article.price %}
That will cost $ {{ amount }}.
{% endblocktranslate %}

{% blocktranslate with myvar=value|filter %}
This will have {{ myvar }} inside.
{% endblocktranslate %}

您可以在單個 blocktranslate 標籤內使用多個表達式。

{% blocktranslate with book_t=book|title author_t=author|title %}
This is {{ book_t }} by {{ author_t }}
{% endblocktranslate %}

注意

仍然支援先前更詳細的格式:{% blocktranslate with book|title as book_t and author|title as author_t %}

不允許在 blocktranslate 標籤內使用其他區塊標籤(例如 {% for %}{% if %})。

如果解析其中一個區塊參數失敗,blocktranslate 將使用 deactivate_all() 函式暫時停用當前活動的語言,並回復為預設語言。

此標籤也提供複數形式。若要使用它:

  • 使用名稱 count 指定並繫結計數器值。此值將用於選取正確的複數形式。

  • {% blocktranslate %}{% endblocktranslate %} 標籤內,使用 {% plural %} 標籤分隔單數和複數形式。

範例:

{% blocktranslate count counter=list|length %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktranslate %}

更複雜的範例:

{% blocktranslate with amount=article.price count years=i.length %}
That will cost $ {{ amount }} per year.
{% plural %}
That will cost $ {{ amount }} per {{ years }} years.
{% endblocktranslate %}

當您使用複數形式功能,並將值繫結到區域變數(除了計數器值之外)時,請記住 blocktranslate 結構在內部會轉換為 ngettext 呼叫。這表示相同的 關於 ngettext 變數的注意事項 適用。

無法在 blocktranslate 中執行反向 URL 查找,應事先檢索(和儲存)。

{% url 'path.to.view' arg arg2 as the_url %}
{% blocktranslate %}
This is a URL: {{ the_url }}
{% endblocktranslate %}

如果您想在不顯示翻譯字串的情況下檢索它,您可以使用以下語法:

{% blocktranslate asvar the_title %}The title is {{ title }}.{% endblocktranslate %}
<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">

實際上,您會使用它來取得可以在樣板中的多個位置使用的字串,或者將輸出作為其他樣板標籤或篩選器的參數使用。

{% blocktranslate %} 也支援使用 context 關鍵字的 上下文標記

{% blocktranslate with name=user.username context "greeting" %}Hi {{ name }}{% endblocktranslate %}

{% blocktranslate %} 支援的另一個功能是 trimmed 選項。此選項會從 {% blocktranslate %} 標籤內容的開頭和結尾移除換行字元,取代行開頭和結尾的任何空白,並使用空格字元分隔將所有行合併為一行。這對於縮排 {% blocktranslate %} 標籤的內容非常有用,而不會將縮排字元放入 .po 檔案中的對應條目,這使翻譯過程更容易。

例如,以下 {% blocktranslate %} 標籤:

{% blocktranslate trimmed %}
  First sentence.
  Second paragraph.
{% endblocktranslate %}

將在 .po 檔案中產生條目 "First sentence. Second paragraph.",與未指定 trimmed 選項時的 "\n  First sentence.\n  Second paragraph.\n" 相比。

傳遞給標籤和篩選器的字串文字

您可以透過使用熟悉的 _() 語法來翻譯作為參數傳遞給標籤和篩選器的字串文字。

{% some_tag _("Page not found") value|yesno:_("yes,no") %}

在這種情況下,標籤和篩選器都會看到翻譯後的字串,因此它們不需要知道翻譯。

注意

在此範例中,翻譯基礎結構將會收到字串 "yes,no",而不是個別字串 "yes""no"。翻譯後的字串需要包含逗號,以便篩選器解析程式碼知道如何分割參數。例如,德文翻譯人員可能會將字串 "yes,no" 翻譯為 "ja,nein"(保持逗號不變)。

樣板中給翻譯人員的註解

Python 程式碼 類似,可以使用註解來指定給翻譯人員的這些註解,可以使用 comment 標籤:

{% comment %}Translators: View verb{% endcomment %}
{% translate "View" %}

{% comment %}Translators: Short intro blurb{% endcomment %}
<p>{% blocktranslate %}A multiline translatable
literal.{% endblocktranslate %}</p>

或使用 {##} 單行註解結構

{# Translators: Label of a button that triggers search #}
<button type="submit">{% translate "Go" %}</button>

{# Translators: This is a text of the base template #}
{% blocktranslate %}Ambiguous translatable block of text{% endblocktranslate %}

注意

為了完整起見,以下是產生的 .po 檔案的對應片段:

#. Translators: View verb
# path/to/template/file.html:10
msgid "View"
msgstr ""

#. Translators: Short intro blurb
# path/to/template/file.html:13
msgid ""
"A multiline translatable"
"literal."
msgstr ""

# ...

#. Translators: Label of a button that triggers search
# path/to/template/file.html:100
msgid "Go"
msgstr ""

#. Translators: This is a text of the base template
# path/to/template/file.html:103
msgid "Ambiguous translatable block of text"
msgstr ""

在樣板中切換語言

如果您想在樣板中選取語言,可以使用 language 樣板標籤。

{% load i18n %}

{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<p>{% translate "Welcome to our page" %}</p>

{% language 'en' %}
    {% get_current_language as LANGUAGE_CODE %}
    <!-- Current language: {{ LANGUAGE_CODE }} -->
    <p>{% translate "Welcome to our page" %}</p>
{% endlanguage %}

雖然第一次出現的「Welcome to our page」使用目前的語言,但第二次出現的始終會是英文。

其他標籤

這些標籤也需要 {% load i18n %}

get_available_languages

{% get_available_languages as LANGUAGES %} 會回傳一個元組列表,其中第一個元素是語言代碼,第二個元素是語言名稱(翻譯成目前啟用的語系)。

get_current_language

{% get_current_language as LANGUAGE_CODE %} 會以字串形式回傳目前使用者偏好的語言。範例: en-us。請參閱Django 如何探索語言偏好

get_current_language_bidi

{% get_current_language_bidi as LANGUAGE_BIDI %} 會回傳目前語系的文字方向。如果為 True,表示為由右至左的語言,例如希伯來文、阿拉伯文。如果為 False,表示為由左至右的語言,例如英文、法文、德文等。

i18n 內容處理器

如果您啟用 django.template.context_processors.i18n 內容處理器,則每個 RequestContext 都可以存取上面定義的 LANGUAGESLANGUAGE_CODELANGUAGE_BIDI

get_language_info

您也可以使用提供的範本標籤和篩選器來擷取任何可用語言的相關資訊。若要取得單一語言的資訊,請使用 {% get_language_info %} 標籤。

{% get_language_info for LANGUAGE_CODE as lang %}
{% get_language_info for "pl" as lang %}

然後,您可以存取資訊

Language code: {{ lang.code }}<br>
Name of language: {{ lang.name_local }}<br>
Name in English: {{ lang.name }}<br>
Bi-directional: {{ lang.bidi }}
Name in the active language: {{ lang.name_translated }}

get_language_info_list

您也可以使用 {% get_language_info_list %} 範本標籤來擷取語言列表的資訊(例如,在 LANGUAGES 中指定的啟用語言)。請參閱關於 set_language 重新導向檢視的章節,以取得如何使用 {% get_language_info_list %} 顯示語言選擇器的範例。

除了 LANGUAGES 樣式的元組列表外,{% get_language_info_list %} 也支援語言代碼列表。如果您在檢視中執行此動作

context = {"available_languages": ["en", "es", "fr"]}
return render(request, "mytemplate.html", context)

您可以在範本中逐一查看這些語言

{% get_language_info_list for available_languages as langs %}
{% for lang in langs %} ... {% endfor %}

範本篩選器

為了方便起見,還有一些可用的篩選器

  • {{ LANGUAGE_CODE|language_name }} (「德文」)

  • {{ LANGUAGE_CODE|language_name_local }} (「Deutsch」)

  • {{ LANGUAGE_CODE|language_bidi }} (False)

  • {{ LANGUAGE_CODE|language_name_translated }} (「německy」,當啟用語言為捷克文時)

國際化:在 JavaScript 程式碼中

將翻譯新增至 JavaScript 會產生一些問題

  • JavaScript 程式碼無法存取 gettext 實作。

  • JavaScript 程式碼無法存取 .po.mo 檔案;它們需要由伺服器傳送。

  • JavaScript 的翻譯目錄應盡可能保持小巧。

Django 為這些問題提供整合式解決方案:它會將翻譯傳遞至 JavaScript,因此您可以從 JavaScript 內呼叫 gettext 等函式。

這些問題的主要解決方案是以下的 JavaScriptCatalog 檢視,其會產生一個 JavaScript 程式碼庫,其中包含模擬 gettext 介面的函式,以及翻譯字串陣列。

JavaScriptCatalog 檢視

class JavaScriptCatalog[來源]

一個產生 JavaScript 程式碼庫的檢視,其中包含模擬 gettext 介面的函式,以及翻譯字串陣列。

屬性

domain

翻譯網域,其中包含要新增至檢視輸出的字串。預設值為 'djangojs'

packages

已安裝應用程式中的應用程式名稱列表。這些應用程式應該包含 locale 目錄。所有這些目錄以及在 LOCALE_PATHS 中找到的所有目錄(永遠包含)都會合併到一個目錄中。預設值為 None,這表示 INSTALLED_APPS 中所有可用的翻譯都會在 JavaScript 輸出中提供。

具有預設值的範例:

from django.views.i18n import JavaScriptCatalog

urlpatterns = [
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
]

具有自訂套件的範例:

urlpatterns = [
    path(
        "jsi18n/myapp/",
        JavaScriptCatalog.as_view(packages=["your.app.label"]),
        name="javascript-catalog",
    ),
]

如果您的根 URLconf 使用 i18n_patterns(),則 JavaScriptCatalog 也必須由 i18n_patterns() 包裝,才能正確產生目錄。

使用 i18n_patterns() 的範例

from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
)

翻譯的優先順序是 packages 引數中較晚出現的套件,其優先順序高於開頭出現的套件。這在同一文字的翻譯發生衝突時很重要。

如果您在網站上使用多個 JavaScriptCatalog 檢視,且其中有些檢視定義相同的字串,則最後載入的目錄中的字串優先。

使用 JavaScript 翻譯目錄

若要使用目錄,請將動態產生的指令碼引入,如下所示

<script src="{% url 'javascript-catalog' %}"></script>

這會使用反向 URL 查閱來尋找 JavaScript 目錄檢視的 URL。載入目錄後,您的 JavaScript 程式碼可以使用以下方法

  • gettext

  • ngettext

  • interpolate

  • get_format

  • gettext_noop

  • pgettext

  • npgettext

  • pluralidx

gettext

gettext 函式的行為類似於 Python 程式碼中的標準 gettext 介面

document.write(gettext("this is to be translated"))

ngettext

ngettext 函式提供一個介面來使單字和詞組變成複數形式

const objectCount = 1 // or 0, or 2, or 3, ...
const string = ngettext(
    'literal for the singular case',
    'literal for the plural case',
    objectCount
);

interpolate

interpolate 函式支援動態填入格式字串。內插語法取自 Python,因此 interpolate 函式同時支援位置和具名內插

  • 位置插值:obj 包含一個 JavaScript 陣列物件,其元素值會按照它們在 fmt 預留位置中出現的順序,依序插入。

    const formats = ngettext(
      'There is %s object. Remaining: %s',
      'There are %s objects. Remaining: %s',
      11
    );
    const string = interpolate(formats, [11, 20]);
    // string is 'There are 11 objects. Remaining: 20'
    
  • 具名插值:此模式透過將可選的布林參數 named 傳遞為 true 來選取。 obj 包含一個 JavaScript 物件或關聯陣列。例如:

    const data = {
      count: 10,
      total: 50
    };
    
    const formats = ngettext(
        'Total: %(total)s, there is %(count)s object',
        'there are %(count)s of a total of %(total)s objects',
        data.count
    );
    const string = interpolate(formats, data, true);
    

不過,你不應該過度使用字串插值:這仍然是 JavaScript,因此程式碼必須重複進行正規表示式替換。這不像 Python 中的字串插值那樣快,所以請將其保留在真正需要它的情況下(例如,與 ngettext 結合以產生正確的複數形式)。

get_format

get_format 函式可以存取已設定的 i18n 格式設定,並可以檢索給定設定名稱的格式字串。

document.write(get_format('DATE_FORMAT'));
// 'N j, Y'

它可以存取以下設定

這對於維護與 Python 呈現的值的格式一致性非常有用。

gettext_noop

這模擬了 gettext 函式,但不執行任何操作,而是返回傳遞給它的任何內容。

document.write(gettext_noop("this will not be translated"))

這對於在未來需要翻譯時,替換程式碼的一部分很有用。

pgettext

pgettext 函式的行為類似於 Python 變體(pgettext()),提供一個具有上下文翻譯的詞。

document.write(pgettext("month name", "May"))

npgettext

npgettext 函式的行為也類似於 Python 變體(npgettext()),提供一個複數化的上下文翻譯詞。

document.write(npgettext('group', 'party', 1));
// party
document.write(npgettext('group', 'party', 2));
// parties

pluralidx

pluralidx 函式的工作方式與 pluralize 範本篩選器類似,它決定給定的 count 是否應該使用單字的複數形式。

document.write(pluralidx(0));
// true
document.write(pluralidx(1));
// false
document.write(pluralidx(2));
// true

在最簡單的情況下,如果不需要自訂複數化,則對於整數 1 會返回 false,對於所有其他數字則返回 true

但是,在所有語言中,複數化並非如此簡單。如果語言不支援複數化,則會提供一個空值。

此外,如果存在關於複數化的複雜規則,則目錄視圖將呈現條件運算式。這將評估為 true(應該複數化)或 false(應該複數化)值。

JSONCatalog 視圖

class JSONCatalog[來源]

為了使用另一個用戶端程式庫來處理翻譯,您可能需要利用 JSONCatalog 視圖。它類似於 JavaScriptCatalog,但返回 JSON 回應。

請參閱 JavaScriptCatalog 的文件,以了解 domainpackages 屬性的可能值和用法。

回應格式如下

{
    "catalog": {
        # Translations catalog
    },
    "formats": {
        # Language formats for date, time, etc.
    },
    "plural": "..."  # Expression for plural forms, or null.
}

關於效能的注意事項

各種 JavaScript/JSON i18n 視圖會在每次請求時從 .mo 檔案產生目錄。由於其輸出是恆定的,至少對於站台的特定版本而言是如此,因此它是快取的理想候選者。

伺服器端快取將減少 CPU 負載。它可以使用 cache_page() 裝飾器輕鬆實現。若要在翻譯變更時觸發快取失效,請提供一個與版本相關的金鑰前綴,如下面的範例所示,或將視圖對應到與版本相關的 URL

from django.views.decorators.cache import cache_page
from django.views.i18n import JavaScriptCatalog

# The value returned by get_version() must change when translations change.
urlpatterns = [
    path(
        "jsi18n/",
        cache_page(86400, key_prefix="jsi18n-%s" % get_version())(
            JavaScriptCatalog.as_view()
        ),
        name="javascript-catalog",
    ),
]

用戶端快取將節省頻寬並使您的網站載入速度更快。如果您使用 ETags(ConditionalGetMiddleware),您已經被涵蓋了。否則,您可以應用 條件裝飾器。在以下範例中,只要您重新啟動應用程式伺服器,快取就會失效

from django.utils import timezone
from django.views.decorators.http import last_modified
from django.views.i18n import JavaScriptCatalog

last_modified_date = timezone.now()

urlpatterns = [
    path(
        "jsi18n/",
        last_modified(lambda req, **kw: last_modified_date)(
            JavaScriptCatalog.as_view()
        ),
        name="javascript-catalog",
    ),
]

您甚至可以將 JavaScript 目錄預先產生為部署程序的一部分,並將其作為靜態檔案提供。這種激進的技術在 django-statici18n 中實現。

國際化:在 URL 模式中

Django 提供了兩種機制來國際化 URL 模式

警告

使用其中一個功能需要為每個請求設定一個作用中的語言;換句話說,您需要在 MIDDLEWARE 設定中加入 django.middleware.locale.LocaleMiddleware

URL 模式中的語言前綴

i18n_patterns(*urls, prefix_default_language=True)[來源]

此函式可以在根 URLconf 中使用,Django 將自動將當前活動的語言代碼添加到 i18n_patterns() 中定義的所有 URL 模式。

prefix_default_language 設定為 False 會從預設語言 (LANGUAGE_CODE) 中移除前綴。當將翻譯新增到現有網站時,這可能會很有用,這樣目前的 URL 就不會變更。

URL 模式範例

from django.conf.urls.i18n import i18n_patterns
from django.urls import include, path

from about import views as about_views
from news import views as news_views
from sitemap.views import sitemap

urlpatterns = [
    path("sitemap.xml", sitemap, name="sitemap-xml"),
]

news_patterns = (
    [
        path("", news_views.index, name="index"),
        path("category/<slug:slug>/", news_views.category, name="category"),
        path("<slug:slug>/", news_views.details, name="detail"),
    ],
    "news",
)

urlpatterns += i18n_patterns(
    path("about/", about_views.main, name="about"),
    path("news/", include(news_patterns, namespace="news")),
)

在定義這些 URL 模式之後,Django 將自動將語言前綴添加到由 i18n_patterns 函式新增的 URL 模式。範例

>>> from django.urls import reverse
>>> from django.utils.translation import activate

>>> activate("en")
>>> reverse("sitemap-xml")
'/sitemap.xml'
>>> reverse("news:index")
'/en/news/'

>>> activate("nl")
>>> reverse("news:detail", kwargs={"slug": "news-slug"})
'/nl/news/news-slug/'

使用 prefix_default_language=FalseLANGUAGE_CODE='en',URL 將為

>>> activate("en")
>>> reverse("news:index")
'/news/'

>>> activate("nl")
>>> reverse("news:index")
'/nl/news/'

警告

i18n_patterns() 僅允許在根 URLconf 中使用。在包含的 URLconf 中使用它會拋出 ImproperlyConfigured 例外。

警告

請確保您沒有可能與自動新增的語言前綴衝突的非前綴 URL 模式。

翻譯 URL 模式

也可以使用 gettext_lazy() 函式將 URL 模式標記為可翻譯。範例

from django.conf.urls.i18n import i18n_patterns
from django.urls import include, path
from django.utils.translation import gettext_lazy as _

from about import views as about_views
from news import views as news_views
from sitemaps.views import sitemap

urlpatterns = [
    path("sitemap.xml", sitemap, name="sitemap-xml"),
]

news_patterns = (
    [
        path("", news_views.index, name="index"),
        path(_("category/<slug:slug>/"), news_views.category, name="category"),
        path("<slug:slug>/", news_views.details, name="detail"),
    ],
    "news",
)

urlpatterns += i18n_patterns(
    path(_("about/"), about_views.main, name="about"),
    path(_("news/"), include(news_patterns, namespace="news")),
)

在您建立翻譯之後,reverse() 函式將以活動語言返回 URL。範例

>>> from django.urls import reverse
>>> from django.utils.translation import activate

>>> activate("en")
>>> reverse("news:category", kwargs={"slug": "recent"})
'/en/news/category/recent/'

>>> activate("nl")
>>> reverse("news:category", kwargs={"slug": "recent"})
'/nl/nieuws/categorie/recent/'

警告

在大多數情況下,最好僅在模式的語言代碼前綴區塊內使用翻譯後的 URL(使用 i18n_patterns()),以避免不小心翻譯的 URL 導致與未翻譯的 URL 模式衝突的可能性。

在範本中反轉

如果本地化的 URL 在範本中被反轉,它們總是使用目前的語言。若要連結到其他語言的 URL,請使用 language 範本標籤。它會在封閉的範本區段中啟用指定的語言。

{% load i18n %}

{% get_available_languages as languages %}

{% translate "View this category in:" %}
{% for lang_code, lang_name in languages %}
    {% language lang_code %}
    <a href="{% url 'category' slug=category.slug %}">{{ lang_name }}</a>
    {% endlanguage %}
{% endfor %}

language 標籤僅接受語言代碼作為參數。

本地化:如何建立語言檔案

一旦應用程式的字串常值被標記為稍後翻譯,就必須編寫(或取得)翻譯本身。以下說明其運作方式。

訊息檔案

第一步是為新語言建立一個訊息檔案。訊息檔案是一個純文字檔案,代表單一語言,其中包含所有可用的翻譯字串以及它們在指定語言中的表示方式。訊息檔案的副檔名為 .po

Django 提供了一個工具 django-admin makemessages,可自動建立和維護這些檔案。

Gettext 工具

makemessages 命令(以及稍後討論的 compilemessages)使用 GNU gettext 工具集中的命令:xgettextmsgfmtmsgmergemsguniq

支援的 gettext 工具的最低版本為 0.15。

若要建立或更新訊息檔案,請執行此命令

django-admin makemessages -l de

…其中 de 是您想要建立的訊息檔案的地區設定名稱。例如,巴西葡萄牙語為 pt_BR,奧地利德語為 de_AT,或印尼語為 id

該指令碼應從以下兩個位置之一執行

  • 您的 Django 專案的根目錄(包含 manage.py 的目錄)。

  • 您的其中一個 Django 應用程式的根目錄。

指令碼會在您的專案來源樹或應用程式來源樹中執行,並提取所有標記為翻譯的字串(請參閱Django 如何發現翻譯 並確保 LOCALE_PATHS 設定正確)。它會在 locale/LANG/LC_MESSAGES 目錄中建立(或更新)訊息檔案。在 de 範例中,檔案將為 locale/de/LC_MESSAGES/django.po

當您從專案的根目錄執行 makemessages 時,提取的字串會自動分發到正確的訊息檔案。也就是說,從包含 locale 目錄的應用程式檔案中提取的字串,將會放入該目錄下的訊息檔案中。從沒有任何 locale 目錄的應用程式檔案中提取的字串,將會放入 LOCALE_PATHS 中首先列出的目錄下的訊息檔案中,或者如果 LOCALE_PATHS 為空,則會產生錯誤。

預設情況下,django-admin makemessages 會檢查每個具有 .html.txt.py 副檔名的檔案。如果您想要覆寫該預設值,請使用 --extension-e 選項來指定要檢查的檔案副檔名

django-admin makemessages -l de -e txt

請用逗號分隔多個副檔名,和/或多次使用 -e--extension

django-admin makemessages -l de -e html,txt -e xml

警告

從 JavaScript 原始碼建立訊息檔案時,您需要使用特殊的 djangojs 網域,而不是 -e js

使用 Jinja2 範本?

makemessages 不了解 Jinja2 範本的語法。若要從包含 Jinja2 範本的專案中提取字串,請改用 訊息提取 (Message Extracting)來自 Babel

以下是一個 babel.cfg 設定檔範例

# Extraction from Python source files
[python: **.py]

# Extraction from Jinja2 templates
[jinja2: **.jinja]
extensions = jinja2.ext.with_

請務必列出您正在使用的所有副檔名!否則,Babel 將無法辨識這些副檔名定義的標籤,並且會完全忽略包含它們的 Jinja2 範本。

Babel 提供與 makemessages 類似的功能,可以在一般情況下取代它,並且不依賴 gettext。如需更多資訊,請閱讀其關於使用訊息目錄的文件。

沒有 gettext?

如果您沒有安裝 gettext 工具,makemessages 將會建立空的檔案。如果發生這種情況,請安裝 gettext 工具,或複製英文訊息檔案(locale/en/LC_MESSAGES/django.po),如果有的話,並將其用作起點,它是一個空的翻譯檔案。

在 Windows 上工作?

如果您使用的是 Windows,並且需要安裝 GNU gettext 工具,以便 makemessages 能夠運作,請參閱Windows 上的 gettext 以取得更多資訊。

每個 .po 檔案都包含一小段中繼資料,例如翻譯維護人員的聯絡資訊,但檔案的主要部分是訊息列表 – 翻譯字串和特定語言的實際翻譯文字之間的對應。

例如,如果您的 Django 應用程式包含文字 "歡迎來到我的網站。" 的翻譯字串,如下所示

_("Welcome to my site.")

…然後 django-admin makemessages 將建立一個包含以下程式碼片段的 .po 檔案 – 一個訊息

#: path/to/python/module.py:23
msgid "Welcome to my site."
msgstr ""

簡要說明

  • msgid 是翻譯字串,它會出現在來源中。請勿變更它。

  • msgstr 是您放置特定語言翻譯的地方。它一開始是空的,所以您有責任變更它。請務必在翻譯周圍保留引號。

  • 為了方便起見,每個訊息都以註解行的形式包含,以 # 作為前綴,並位於 msgid 行上方,其中包含翻譯字串的檔案名稱和行號。

長訊息是一種特殊情況。在這種情況下,msgstr(或 msgid)之後的第一個字串是空字串。然後,內容本身將在接下來的幾行中寫成每行一個字串。這些字串會直接串連在一起。請不要忘記字串內的結尾空格;否則,它們會被沒有空格的串連在一起!

注意您的字元集

由於 gettext 工具在內部運作的方式,以及因為我們希望允許在 Django 核心和您的應用程式中使用非 ASCII 來源字串,所以您必須使用 UTF-8 作為 .po 檔案的編碼(建立 .po 檔案時的預設值)。這表示每個人都將使用相同的編碼,這在 Django 處理 .po 檔案時非常重要。

模糊項目

makemessages 有時會產生標記為模糊的翻譯項目,例如,當翻譯是從先前翻譯的字串推斷出來時。預設情況下,模糊的項目不會compilemessages 處理。

若要重新檢查所有原始碼和範本中是否有新的翻譯字串,並更新所有語言的所有訊息檔案,請執行此操作

django-admin makemessages -a

編譯訊息檔案

在您建立訊息檔案之後 – 以及每次您對其進行變更時 – 您需要將其編譯為更有效率的形式,供 gettext 使用。請使用 django-admin compilemessages 工具來執行此操作。

此工具會在所有可用的 .po 檔案上執行,並建立 .mo 檔案,這些檔案是經過最佳化以便供 gettext 使用的二進位檔案。在您執行 django-admin makemessages 的相同目錄中,像這樣執行 django-admin compilemessages

django-admin compilemessages

就是這樣。您的翻譯已可供使用。

在 Windows 上工作?

如果您使用 Windows 並且需要安裝 GNU gettext 工具,以便 django-admin compilemessages 可以運作,請參閱 Windows 上的 gettext 以取得更多資訊。

.po 檔案:編碼和 BOM 使用。

Django 僅支援以 UTF-8 編碼且不帶任何 BOM (位元組順序標記) 的 .po 檔案。如果您的文字編輯器預設會在檔案開頭加入此類標記,則您需要重新設定它。

疑難排解:gettext() 在含有百分比符號的字串中錯誤地偵測到 python-format

在某些情況下,例如含有百分比符號後接一個空格和一個 字串轉換類型 (例如 _("10% interest")) 的字串,gettext() 會錯誤地將字串標記為 python-format

如果您嘗試編譯具有錯誤標記字串的訊息檔案,您會收到像 'msgid' 'msgstr' 中的格式規格數量不符'msgstr' 不是有效的 Python 格式字串,不像 'msgid' 之類的錯誤訊息。

為了解決這個問題,您可以透過新增第二個百分比符號來逸出百分比符號

from django.utils.translation import gettext as _

output = _("10%% interest")

或者您可以使用 no-python-format,以便將所有百分比符號視為文字

# xgettext:no-python-format
output = _("10% interest")

從 JavaScript 原始碼建立訊息檔案

您可以使用 django-admin makemessages 工具,以與其他 Django 訊息檔案相同的方式建立和更新訊息檔案。唯一的區別是您需要明確指定在 gettext 行話中稱為網域的內容,在此案例中為 djangojs 網域,方法是提供一個 -d djangojs 參數,如下所示

django-admin makemessages -d djangojs -l de

這會為德語建立或更新 JavaScript 的訊息檔案。更新訊息檔案後,請執行 django-admin compilemessages,就像您對正常的 Django 訊息檔案所做的一樣。

Windows 上的 gettext

這僅適用於想要提取訊息 ID 或編譯訊息檔案 (.po) 的人員。翻譯工作本身涉及編輯此類型的現有檔案,但如果您想建立自己的訊息檔案,或想要測試或編譯變更過的訊息檔案,請下載 預編譯的二進位安裝程式

您也可以使用從其他地方取得的 gettext 二進位檔案,只要 xgettext --version 命令正常運作即可。如果輸入 Windows 命令提示字元時,命令 xgettext --version 導致快顯視窗顯示「xgettext.exe 產生錯誤,將由 Windows 關閉」,請勿嘗試使用 Django 翻譯工具與 gettext 套件搭配使用。

自訂 makemessages 命令

如果您想將其他參數傳遞給 xgettext,您需要建立一個自訂的 makemessages 命令並覆寫其 xgettext_options 屬性

from django.core.management.commands import makemessages


class Command(makemessages.Command):
    xgettext_options = makemessages.Command.xgettext_options + ["--keyword=mytrans"]

如果您需要更高的彈性,您也可以在自訂的 makemessages 命令中新增一個新的引數

from django.core.management.commands import makemessages


class Command(makemessages.Command):
    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument(
            "--extra-keyword",
            dest="xgettext_keywords",
            action="append",
        )

    def handle(self, *args, **options):
        xgettext_keywords = options.pop("xgettext_keywords")
        if xgettext_keywords:
            self.xgettext_options = makemessages.Command.xgettext_options[:] + [
                "--keyword=%s" % kwd for kwd in xgettext_keywords
            ]
        super().handle(*args, **options)

雜項

set_language 重新導向檢視

set_language(request)[原始碼]

為了方便起見,Django 提供一個檢視,django.views.i18n.set_language(),該檢視會設定使用者的語言偏好,並重新導向到指定的 URL,或預設重新導向回上一頁。

透過在您的 URLconf 中新增以下行來啟用此檢視

path("i18n/", include("django.conf.urls.i18n")),

(請注意,此範例會使檢視在 /i18n/setlang/ 上可用。)

警告

請確保您未將上述 URL 包含在 i18n_patterns() 中 - 它本身需要是與語言無關的才能正常運作。

此檢視期望透過 POST 方法呼叫,並且在請求中設定一個 language 參數。如果啟用了工作階段支援,則此檢視會將語言選擇儲存在使用者的工作階段中。它還會將語言選擇儲存在預設名為 django_language 的 Cookie 中。(此名稱可透過 LANGUAGE_COOKIE_NAME 設定變更。)

設定語言選擇後,Django 會在 POSTGET 資料中尋找 next 參數。如果找到該參數,且 Django 認為它是安全的 URL (即它沒有指向不同的主機並使用安全的配置),則會執行重新導向到該 URL。否則,Django 可能會回復將使用者重新導向到來自 Referer 標頭的 URL,如果未設定,則回復到 /,這取決於請求的性質

  • 如果請求接受 HTML 內容 (基於其 Accept HTTP 標頭),則將始終執行回復。

  • 如果請求不接受 HTML,則僅在設定了 next 參數時才會執行回復。否則,將傳回 204 狀態碼 (無內容)。

以下是 HTML 範本程式碼範例

{% load i18n %}

<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
    <input name="next" type="hidden" value="{{ redirect_to }}">
    <select name="language">
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
    <input type="submit" value="Go">
</form>

在此範例中,Django 會在 redirect_to 環境變數中查閱使用者將被重新導向到的頁面 URL。

明確設定活動語言

您可能想要明確設定目前工作階段的活動語言。例如,使用者的語言偏好可能從另一個系統擷取。您已經了解了 django.utils.translation.activate()。這僅適用於目前執行緒。若要將語言持續儲存在 Cookie 中以供整個工作階段使用,請在回應中設定 LANGUAGE_COOKIE_NAME Cookie

from django.conf import settings
from django.http import HttpResponse
from django.utils import translation

user_language = "fr"
translation.activate(user_language)
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)

您通常會想要同時使用兩者:django.utils.translation.activate() 會變更此執行緒的語言,而設定 Cookie 會使此偏好在未來的請求中持續存在。

在檢視和範本之外使用翻譯

雖然 Django 提供了一組豐富的 i18n 工具,可在檢視和範本中使用,但它不限制在 Django 特定程式碼中使用。Django 翻譯機制可用於將任意文字翻譯成 Django 支援的任何語言 (當然,只要存在適當的翻譯目錄)。您可以載入翻譯目錄、啟用它並將文字翻譯成您選擇的語言,但請記住切換回原始語言,因為啟用翻譯目錄是在每個執行緒的基礎上完成的,而且此類變更將會影響在同一執行緒中執行的程式碼。

例如

from django.utils import translation


def welcome_translated(language):
    cur_language = translation.get_language()
    try:
        translation.activate(language)
        text = translation.gettext("welcome")
    finally:
        translation.activate(cur_language)
    return text

無論 LANGUAGE_CODE 和中介軟體設定的語言為何,使用值 'de' 呼叫此函式都會讓您獲得 "Willkommen"

特別感興趣的函式包括 django.utils.translation.get_language(),它會傳回目前執行緒中使用的語言、django.utils.translation.activate(),它會為目前執行緒啟用翻譯目錄,以及 django.utils.translation.check_for_language(),它會檢查 Django 是否支援指定的語言。

為了幫助撰寫更精簡的程式碼,還有一個上下文管理器 django.utils.translation.override(),它會在進入時儲存當前語言,並在退出時恢復它。有了它,上面的範例會變成:

from django.utils import translation


def welcome_translated(language):
    with translation.override(language):
        return translation.gettext("welcome")

實作注意事項

Django 翻譯的特殊性

Django 的翻譯機制使用 Python 內建的標準 gettext 模組。如果您了解 gettext,您可能會注意到 Django 翻譯方式中的這些特殊性:

  • 字串域是 djangodjangojs。此字串域用於區分將資料儲存在通用訊息檔案庫(通常是 /usr/share/locale/)中的不同程式。django 域用於 Python 和模板翻譯字串,並載入到全域翻譯目錄中。djangojs 域僅用於 JavaScript 翻譯目錄,以確保它們盡可能小。

  • Django 並非單獨使用 xgettext。它使用 xgettextmsgfmt 的 Python 包裝器。這主要是為了方便。

Django 如何發現語言偏好

一旦您準備好翻譯檔,或者如果您想使用 Django 自帶的翻譯檔,您就需要為您的應用程式啟用翻譯。

在幕後,Django 有一個非常彈性的模型來決定應該使用哪種語言 – 全站範圍、針對特定使用者,或兩者兼具。

若要設定全站範圍的語言偏好,請設定 LANGUAGE_CODE。如果透過 locale 中介軟體採用的任何方法都找不到更好的匹配翻譯(見下文),Django 會將此語言用作預設翻譯 – 最後的嘗試。

如果您只想使用您的母語執行 Django,您只需要設定 LANGUAGE_CODE 並確保對應的訊息檔案及其編譯版本(.mo)存在。

如果您想讓每個個別使用者指定他們偏好的語言,那麼您也需要使用 LocaleMiddlewareLocaleMiddleware 可以根據請求中的資料啟用語言選擇。它可以為每個使用者自訂內容。

若要使用 LocaleMiddleware,請將 'django.middleware.locale.LocaleMiddleware' 加入到您的 MIDDLEWARE 設定。由於中介軟體的順序很重要,請遵循這些準則:

  • 確保它是最先安裝的中介軟體之一。

  • 它應該在 SessionMiddleware 之後,因為 LocaleMiddleware 使用了 session 資料。而且它應該在 CommonMiddleware 之前,因為 CommonMiddleware 需要已啟用的語言才能解析請求的 URL。

  • 如果您使用 CacheMiddleware,請將 LocaleMiddleware 放在它之後。

例如,您的 MIDDLEWARE 看起來可能會像這樣:

MIDDLEWARE = [
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.common.CommonMiddleware",
]

(有關中介軟體的更多資訊,請參閱中介軟體文件。)

LocaleMiddleware 嘗試透過以下演算法來確定使用者的語言偏好:

  • 首先,它會在請求的 URL 中尋找語言前綴。只有在您在根 URLconf 中使用 i18n_patterns 函式時才會執行此操作。有關語言前綴以及如何國際化 URL 模式的更多資訊,請參閱 國際化:在 URL 模式中

  • 若失敗,它會尋找 Cookie。

    所使用的 Cookie 名稱由 LANGUAGE_COOKIE_NAME 設定設定。(預設名稱為 django_language。)

  • 若失敗,它會查看 Accept-Language HTTP 標頭。此標頭由您的瀏覽器發送,並按優先順序告知伺服器您偏好的語言。Django 會嘗試標頭中的每種語言,直到找到具有可用翻譯的語言為止。

  • 若失敗,它會使用全域的 LANGUAGE_CODE 設定。

注意事項:

  • 在這些地方,語言偏好應為標準的 語言格式,為字串。例如,巴西葡萄牙語是 pt-br

  • 如果基本語言可用,但指定的子語言不可用,Django 會使用基本語言。例如,如果使用者指定 de-at(奧地利德語),但 Django 只有 de 可用,Django 會使用 de

  • 只有在 LANGUAGES 設定中列出的語言才能被選取。如果您想將語言選擇限制為所提供語言的子集(因為您的應用程式未提供所有這些語言),請將 LANGUAGES 設定為語言清單。例如:

    LANGUAGES = [
        ("de", _("German")),
        ("en", _("English")),
    ]
    

    此範例將可自動選擇的語言限制為德語和英語(以及任何子語言,例如 de-chen-us)。

  • 如果您定義自訂的 LANGUAGES 設定,如上一個要點所述,您可以將語言名稱標記為翻譯字串 – 但請使用 gettext_lazy() 而不是 gettext(),以避免循環導入。

    以下是設定檔的範例:

    from django.utils.translation import gettext_lazy as _
    
    LANGUAGES = [
        ("de", _("German")),
        ("en", _("English")),
    ]
    

一旦 LocaleMiddleware 確定使用者的偏好,它就會將此偏好作為每個 HttpRequestrequest.LANGUAGE_CODE 提供。請隨意在您的檢視程式碼中讀取此值。以下是一個範例:

from django.http import HttpResponse


def hello_world(request, count):
    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 會建立一個文字-翻譯的記憶體統一目錄。為了實現這一點,它會按照以下演算法尋找翻譯,關於它檢查不同檔案路徑以載入編譯的訊息檔案.mo),以及同一個文字多個翻譯的優先順序。

  1. LOCALE_PATHS 中列出的目錄具有最高的優先順序,排在前面的目錄優先順序高於排在後面的目錄。

  2. 然後,它會在 INSTALLED_APPS 中列出的每個已安裝應用程式中尋找並使用(如果存在)locale 目錄。排在前面的目錄優先順序高於排在後面的目錄。

  3. 最後,使用 Django 提供的基礎翻譯 django/conf/locale 作為後備。

另請參閱:

包含在 JavaScript 資源中的文字翻譯是依照類似但不相同的演算法尋找的。有關詳細資訊,請參閱 JavaScriptCatalog

如果您也設定了 FORMAT_MODULE_PATH,您也可以將自訂格式檔案放在 LOCALE_PATHS 目錄中。

在所有情況下,翻譯的目錄名稱都預期使用地區名稱的表示法。例如:dept_BRes_AR等。對於地區語言變體的未翻譯字串,會使用通用語言的翻譯。例如,未翻譯的pt_BR字串會使用pt的翻譯。

這樣,您可以編寫包含自己翻譯的應用程式,並且可以在您的專案中覆蓋基礎翻譯。或者,您可以從多個應用程式建構一個大型專案,並將所有翻譯放入一個特定於您正在組合的專案的通用訊息檔案中。選擇權在您。

所有訊息檔案儲存庫的結構都相同。它們是:

  • 在您的設定檔中,LOCALE_PATHS 列出的所有路徑都會被搜尋 <language>/LC_MESSAGES/django.(po|mo)

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

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

要建立訊息檔案,您可以使用 django-admin makemessages 工具。並且您可以使用 django-admin compilemessages 來產生 gettext 使用的二進制 .mo 檔案。

您也可以執行 django-admin compilemessages --settings=path.to.settings,讓編譯器處理您 LOCALE_PATHS 設定中的所有目錄。

使用非英文作為基礎語言

Django 通常假設可翻譯專案中的原始字串是以英文撰寫的。您可以選擇其他語言,但您必須注意某些限制。

  • gettext 僅為原始訊息提供兩種複數形式,因此如果基礎語言的複數規則與英文不同,您還需要為基礎語言提供翻譯,以包含所有複數形式。

  • 當啟用英文變體且缺少英文字串時,後備語言將不是專案的 LANGUAGE_CODE,而是原始字串。例如,一個以西班牙文設定 LANGUAGE_CODE 且原始字串以俄文撰寫的網站的英文使用者,將會看到俄文文本而不是西班牙文。

返回頂部