04 Python Flask 教學10 所有文章

【Flask教學】實作 Flask i18n 多國語系

Flask-Babel 多國語系教學

為什麼會寫這篇呢?

Flask-Babel – 官方文件 的範例 code 中,教大家用偵測使用者的 Accept-Language header 來自動判別要顯示的語言版本,方法如下:

request.accept_languages.best_match(['de', 'fr', 'en'])

但其實對 SEO 很不友善,因為搜尋引擎爬蟲不會攜帶 Accept-Language header,也就是說如果用「自動偵測使用者語言」做法的話,搜尋引擎永遠只會收錄一種語言的網頁。

如果搜尋引擎只收錄一個語系頁面

將導致像是「英語」系使用者在搜尋時,搜尋結果永遠只會看到「中文」的網站,因為 Google 只收錄到預設的中文頁面。

如果要做到「英語」系使用者搜尋時,在搜尋結果上會顯示「英語」的頁面:

  1. 首先要讓不同語系有獨立不重複的網址,才能讓搜尋引擎收錄
  2. 再來要加上 Hreflang 標籤的 5 種參數設定來讓搜尋引擎更容易讀懂
  3. 最後再設定 Canonical 則是避免 Google 判定為重複內容

網址部分我最推薦的用法是用國家和地區頂級域名(Country code top-level domain,英語:ccTLD),但會需要買多個網域;如果考慮現實成本或網域被買走的話,使用子資料夾來區隔不同的語系,是對 SEO 和成本來說 CP 值最高的方法。

關於「子資料夾」網址範例如下:

  • 像是 Apple 網站
    • https://www.apple.com/am/
    • https://www.apple.com/tw/shop

關於「 ccTLD」 網址範例如下:

  • 像是 IKEA 網站
    • https://www.ikea.jp/
    • https://www.ikea.cn/
  • 像是 Shopback 網站
    • https://www.shopback.sg/
    • https://www.shopback.my/
    • https://www.shopback.com.tw/

而關於其他 5 種多國語系 url 結構設計、 SEO Hreflang 標籤和 SEO Canonical 的設定,可以參考此篇:
如何設計多語言係 Url 結構和 Hreflang SEO 優化 | Max行銷誌

實作篇:子資料夾路徑 + Flask-babel 多國語系

第一步:安裝 Flask-Babel 套件:

$ pip3 install Flask-Babel

本次實作範例 Code 存放於:flask-template/template-flask-i18n/flask at master · hsuanchi/flask-template · GitHub

專案資料夾結構:

├── app
│   ├── __init__.py
│   ├── babel.cfg
│   ├── config
│   │   └── config.py
│   ├── messages.pot
│   ├── templates
│   │   └── index.html
│   ├── translations
│   │   └── zh
│   │       └── LC_MESSAGES
│   │           ├── messages.mo
│   │           └── messages.po
│   └── view
│       └── index.py
├── main.py
├── requirements.txt
└── venv

第二步:Blueprint 建立子資料夾路徑

/flask/app/__init__.py 中可以看到,第 26 行:

from .view.index import main
app.register_blueprint(main, url_prefix='/<lang_code>/main')

可以看到這次的網址結構,是 blueprint 的前綴詞 url_prefix 來設定子資料夾的多國語系。

第三步:設定 Flask-Babel 語系

▍1. 取得子資料夾路徑 lang_code

/flask/app/__init__.py 中可以看到,第 44 行:

# 1 Get parameter lang_code from route
@app.url_value_preprocessor
def get_lang_code(endpoint, values):
    if values is not None:
        g.lang_code = values.pop('lang_code', defalut_language_str)

我們利用 Flask 內建的 url_value_preprocessor 來取得子資料夾的多國語系 value。

補充:什麼是 url_value_preprocessor ?
url_value_preprocessor 可以取得路徑 endpoint 和 value,會在 before_request() 之前觸發。

These functions will be called before the before_request() functions.
The function is passed the endpoint name and values dict. The return value is ignored.

url_value_preprocessor — Flask Documentation (2.0.x)

▍2. 驗證 lang_code 符合的語系

/flask/app/__init__.py 中可以看到,第 49 行:

# 2 Check lang_code type is in config
@app.before_request
def ensure_lang_support():
    lang_code = g.get('lang_code', None)
    if lang_code and lang_code not in support_language_list:
        g.lang_code = request.accept_languages.best_match(
            support_language_list)

但如果有人隨意亂改路徑怎麼辦,所以將 value 解析出來後,在before_request 之前,先將 value 與我們有提供的語系做比對,如果都沒有符合的語系的話,則使用 request.accept_languages.best_match 來判斷使用者目前最適合的語系。

▍3. 設定 Flask-babel 語系

/flask/app/__init__.py 中可以看到,第 57 行:

# 3 Setting babel
@babel.localeselector
def get_locale():
    return g.get('lang_code')

我們將剛剛取得並驗證過後存在 g 裡面的 lang_code 變數取出,並使用 babel.localeselector 來設定 Flask-babel 的語系。

▍4. 檢視 lang_code 是否存在

因為剛剛 Step 1 的 lang_code 被 pop 取出,為了避免接下來有可能使用 url_for 但並沒有帶 lang_code,所以這邊使用 url_defaults 如果 endpoint 的 value 沒有 lang_code 會將剛剛取得的 g. lang_code 加入進去

# 4 Check lang_code exist after step1 pop parameter of lang_code
@app.url_defaults
def set_language_code(endpoint, values):
    if 'lang_code' in values or not g.lang_code:
        return
    if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
        values['lang_code'] = g.lang_code

第四步:設定 html 和編譯 messages.mo 檔

▍1. 設定 html

將會需要翻譯的部分用 {{ _('我需要被翻譯') }} 包起來,像是下面的範例,在 /flask/app/templates/index.html 中可以看到:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>{{ _('Hello World!') }}</div>
    <div>{{ _('My name is Max') }}</div>
</body>
</html>

▍2. 設定編譯 messages.mo 檔

流程圖:
1. Create – 首先會建立 babel.cfg
2. Extract – 然後掃專案內有需要編譯的 html
3. Build – 建立指定語言的資料夾
4. Compile – 翻譯然後從 po 檔輸出成 mo 檔

當初次建立多國語系時,要把 Step 1~4步都走一次,那就開始帶大家實作:

Step 1:
建立 Configuration File – babel.cfg,這個裡面會寫 babel 的設定,像是 html 會去 template 裡面找,也可以在這邊修改前綴詞 ({{_()}}) 設定。

[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

Step 2 ~ 3

# Step2 探索專案 templates 下的所有 html
$ pybabel extract -F babel.cfg -o messages.pot .

# Step3 自動建立 translations 資料夾
$ pybabel init -i messages.pot -d translations -l zh

這邊輸入完第三步指令後,會看到系統自動建立出 translations 的資料夾。

│   ├── translations
│   │   └── zh
│   │       └── LC_MESSAGES
│   │           ├── messages.mo
│   │           └── messages.po

進入 /zh/LC_MESSAGES /messages.po 後,接下來就是翻譯的時間,把你想翻譯的結果寫在 msgstr 裡面,就可以了!

#: templates/index.html:11
msgid "Hello World!"
msgstr ""

#: templates/index.html:12
msgid "My name is Max"
msgstr ""

完成結果如下:

#: templates/index.html:11
msgid "Hello World!"
msgstr "你好"

#: templates/index.html:12
msgid "My name is Max"
msgstr "馬克思"

最後 Step 4 將剛剛翻譯的結果從 po 檔輸出成 mo 檔

# Step4 將 po 檔轉換成 mo
$ pybabel compile -d translations

補充:
.mo 檔 (Machine Object) 是程式在執行翻譯時會閱讀的檔案
.po 檔 (Portable Object) 則是預留給我們作為編輯用的檔案
因此 Step4 就是將編輯完的 .po 產生新的.mo 給程式閱讀

接下來就是運行 Flask 試試看結果啦!

$ export FLASK_APP=main.py
$ flask run

http://127.0.0.1:5000/en/main/

http://127.0.0.1:5000/zh/main/

如果到這邊都沒問題,那恭喜你成功啦!

▍補充:當需要再次更新文檔時

Step 1 的 babel.cfg 已經建立過了,所以再次更新時就不用在建立了。

Step 2 ~ 4

而要留意的是 Step 3 的部分從 init 改成 update

# Step2 探索專案 templates 下的所有 html
$ pybabel extract -F babel.cfg -o messages.pot .

# Step3 更新之前建立過 translations 資料夾
$ pybabel update -i messages.pot -d translations

# Step4 將 po 檔轉換成 mo
$ pybabel compile -d translations

關於 Flask-Babel 的使用說明,我覺得這三篇寫得很清楚,推薦補充閱讀:

  1. GitHub – twtrubiks/Flask-Babel-example: How Use Flask-Babel on Windows – Python Flask
  2. Flask實作ext_17_Flask_babel多語系 – HackMD
  3. Python – Flask i18n 多國語系功能實作 | My.APOLLO

關於 Flask 教學的延伸閱讀:

▍關於 Flask 教學系列目錄:

▍其他 Flask 相關教學:

那麼有關於【Flask教學】實作 Flask i18n 多國語系 的介紹就到這邊告一個段落囉!有任何問題可以在以下留言~

有關 Flask 的最新文章,都會發佈在 Max 的 Facebook 粉絲專頁,如果想看最新更新,還請您按讚或是追蹤唷!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *