iT邦幫忙

DAY 27
0

為程式人寫的 Django Tutorial系列 第 27

Django Tutorial for Programmers: 27. Internationalisation

內容diff

今天來討論怎麼實作多國語系。這個主題可以分為兩部份:

1. 讓一個網站用多種語言顯示內容(國際化)。
2. 判斷使用者的地區與使用語言,以選擇顯示語言與格式。

Django 的國際化主要的做法是:

1. 在程式中標記字串,讓 Gettext 知道哪些字串需要翻譯。
2. 用 `manage.py makemessages` 把需要翻譯的字串抽出來,做成 `po` 檔。
3. 修改 `po` 檔,在其中加入合適的翻譯。
4. 用 `manage.py compilemessages` 把 `po` 檔編譯成 `mo` 檔。
5. 當某個 request 用到被翻譯的字串時,Django 會自動 lookup 對應的翻譯來使用。

由於 Django 中將翻譯檔編譯的功能是基於 GNU Gettext,所以如果你需要編譯翻譯檔,就必須有 GNU Gettext toolset。在 Linux 上這不是問題,但如果你用 [OS X](https://github.com/Homebrew/homebrew/issues/8461) 或 [Windows](https://docs.djangoproject.com/en/dev/topics/i18n/translation/#gettext-on-windows),就需要自行安裝 Gettext,並把它們加入 PATH。[註 2]

我們來把店家相關的名稱翻成中文。新增下面的檔案:

```python
# stores/apps.py

from django.apps.config import AppConfig
from django.utils.translation import ugettext_lazy as _

class StoresAppConfig(AppConfig):
    name = 'stores'
    verbose_name = _('Stores')
```

在 Python 程式中使用 `ugettext`,就可以把字串標記為需要翻譯;我們這裡使用 lazy 版本,因為我們要在 runtime 才能知道 `Stores` 要被翻譯成什麼語言。

我們要實際指定這個 config,Django 才知道要使用它:

```python
# stores/__init__.py

default_app_config = 'stores.apps.StoresAppConfig'
```

如法炮製,把 models 也翻譯一下:

```python
# stores/models.py

from django.utils.translation import ugettext_lazy as _


class Store(models.Model):
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL, null=True, related_name='owned_stores',
        verbose_name=_('owner'),
    )
    name = models.CharField(
        max_length=20, verbose_name=_('name'),
    )
    notes = models.TextField(
        blank=True, default='', verbose_name=_('notes'),
    )

    class Meta:
        verbose_name = _('Store')
        verbose_name_plural = _('Stores')

    # ...

class MenuItem(models.Model):
    store = models.ForeignKey(
        'Store', related_name='menu_items', verbose_name=_('store'),
    )
    name = models.CharField(
        max_length=20, verbose_name=_('name'),
    )
    price = models.IntegerField(
        verbose_name=_('price'),
    )

    class Meta:
        verbose_name = _('Menu item')
        verbose_name_plural = _('Menu items')
        
    # ...
```

欄位的名稱同樣是用 `verbose_name` 屬性。這個字串會在 model 被製作成 form 時被拿來當標籤使用。Model 本身的名稱則要用 `Meta` 裡面的屬性設定,其中 `verbose_name_plural` 代表複數的翻譯。如果不指定這個,Django 預設會在需要複數時直接在單數詞後面加 s;在英文裡這可能通常還 OK,但中文看起來就不對了,所以必須指定這個選項。

接著我們把 `stores` app 裡的翻譯整理成中文翻譯檔(`po` 檔)。首先建立 `stores/locale` 目錄:

```bash
mkdir stores/locale
```

然後要求 Django 產生 `po` 檔:

```bash
cd stores
python ../manage.py makemessages -l zh_TW
```

注意一定要進去 `stores` 目錄才能正常執行!這會讓 Django 產生一個 `zh_TW`(台灣中文)的翻譯檔,讓你可以打開來編輯。

翻譯的項目會類似下面這樣子:

```
#: models.py:21
msgid "Store"
msgstr ""
```

第一行的註解代表這個詞出現在哪裡,`msgid` 與 `msgstr` 分別代表被輸入 ugettext(也就是程式裡面使用的值)與實際要使用的翻譯。把你想要的翻譯填到雙引號中即可。

修改完之後,仍然在 `stores` 目錄中把 `po` 編譯成 `mo`:

```bash
python ../manage.py compilemessages
```

把 server 跑起來,進入 admin 看看。你的店家名稱應該都變成中文了![註 2]

目前我們都是用預設語言偵測。不過有時候我們還是會想手動切換。Django 主要是使用 session、cookie 與 HTTP header 中的 Accept-Languages 來判斷應該使用什麼語言。[註 3] 如果要實作多國語言,必須確認設定裡有以下內容:

1. `USE_I18N` 是 `True`。
2. `MIDDLEWARE_CLASSES` 中有 `'django.middleware.locale.LocaleMiddleware'`,且位於 `SessionMiddleware` 與 `CommonMiddleware` 之間。

有這些資料之後,我們就可以實作一個 form 來切換語言:

```python
# lunch/settings/base.py

# 設定我們要使用的語言。
LANGUAGES = (
    ('en-us', 'English (United States)',),
    ('zh-tw', '繁體中文(台灣)')
)
```

```html
{# base/templates/base.html #}

{# 在 navbar 裡 #}
<form class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post">
  {% csrf_token %}
  <select name="language" class="form-control language-switch">
    {% for code, name in LANGUAGES %}
      <option value="{{ code }}"{% if LANGUAGE_CODE == code %} selected{% endif %}>{{ name }}</option>
    {% endfor %}
  </select>
</form>
```

上面 form 的 action 指向 `set_language`。這是 Django 內建的切換語言用 form view,接受一個 `language` 參數,包含我們要用的 language code(另外它還可以接受 `next`,用途應該不用解釋——不過我們這裡不用,預設會重導向回目前頁面,這是我們要的行為沒錯)。這個 widget 的內容可以用 for 產生,而且我們把符合當下語言(可以用 `LANGUAGE_CODE` 得知)的選項選起來。

用點 JavaScript 讓表單直接在 widget 選擇改變時送出,而不用按 submit:

```javascript
// base/static/base/js/base.js

$('.language-switch').change(function () {
  $(this).parent('form').submit();
});
```

最後,記得把內建語言切換頁加入 URL conf:

```python
# lunch/urls.py

urlpatterns = patterns(
    # ...
    url(r'^i18n/', include('django.conf.urls.i18n')),
)
```

在頁面上按按看這個 widget,應該會感受到一個頁面重新整理。如果你再去看 admin,應該就可以看到語言改變了!

接著你也可以繼續修改其他部分,讓網站可以中英切換。在 Python 中,你可以使用 `ugettext` 與 `ugettext_lazy`,在 template 中則可以使用 `trans` tag(記得必須 `{% load i18n %}`,我超常忘記),例如像這樣:

```html
{% load i18n %}
<input type="submit" value="{% trans 'Submit' %}">
```

這些函式還有許多變體,例如可以根據一個額外參數切換單複數的 `ungettext` 系列,以及字串 format 的用法,例如:

```python
text = ungettext(
    'I have {count} apple', 'I have {count} apples', count,
).format(count=count)
```

就會根據 `count` 的值,顯示例如 *I have 1 apple* 或 *I have 5 apples* 等翻譯!不過這就讓你自己發掘吧,如果有什麼不懂得,就參考[官方文件](https://docs.djangoproject.com/en/1.7/topics/i18n/translation/)。

---

註 1:更好的方法是使用一個 staging server 加上 post-commit hooks 來自動編譯,不過設 staging server 有時候實在是 overkill。

註 2:如果你的 Accept-Languages header 沒有中文——如果你把系統語言設成其他語言,就會發生這種狀況,那麼就還是會看到英文,因為 Django 會根據它來判斷。不過先別急,馬上教你怎麼複寫。

註 3:Django 另外提供了用網址分辨(例如 `/zh/store/` 是中文,`/en/store/` 是英文),以及如果一切設定都無法決定,可以在設定檔寫死的做法,不過這裡不討論。

上一篇
Django Tutorial for Programmers: 26. REST API
下一篇
Django Tutorial for Programmers: 28. Logging 與 Data Migrations
系列文
為程式人寫的 Django Tutorial30

尚未有邦友留言

立即登入留言