Table
為什麼會寫這篇呢?
Flask-Babel – 官方文件 的範例 code 中,教大家用偵測使用者的 Accept-Language header
來自動判別要顯示的語言版本,方法如下:
1 |
request.accept_languages.best_match(['de', 'fr', 'en']) |
但其實對 SEO 很不友善,因為搜尋引擎爬蟲不會攜帶 Accept-Language header
,也就是說如果用「自動偵測使用者語言」做法的話,搜尋引擎永遠只會收錄一種語言的網頁。
如果搜尋引擎只收錄一個語系頁面
將導致像是「英語」系使用者在搜尋時,搜尋結果永遠只會看到「中文」的網站,因為 Google 只收錄到預設的中文頁面。
如果要做到「英語」系使用者搜尋時,在搜尋結果上會顯示「英語」的頁面:
- 首先要讓不同語系有獨立不重複的網址,才能讓搜尋引擎收錄
- 再來要加上 Hreflang 標籤的 5 種參數設定來讓搜尋引擎更容易讀懂
- 最後再設定 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 套件:
1 |
$ pip3 install Flask-Babel |
本次實作範例 Code 存放於:flask-template/template-flask-i18n/flask at master · hsuanchi/flask-template · GitHub
專案資料夾結構:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
├── 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 行:
1 2 |
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 2 3 4 5 |
# 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.
url_value_preprocessor — Flask Documentation (2.0.x)
The function is passed the endpoint name and values dict. The return value is ignored.
▍2. 驗證 lang_code 符合的語系
在 /flask/app/__init__.py
中可以看到,第 49 行:
1 2 3 4 5 6 7 |
# 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 行:
1 2 3 4 |
# 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 加入進去
1 2 3 4 5 6 7 |
# 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
中可以看到:
1 2 3 4 5 6 7 8 9 10 11 12 |
<!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 裡面找,也可以在這邊修改前綴詞 ({{_()}}
) 設定。
1 2 3 |
[python: **.py] [jinja2: **/templates/**.html] extensions=jinja2.ext.autoescape,jinja2.ext.with_ |
Step 2 ~ 3
1 2 3 4 5 |
# Step2 探索專案 templates 下的所有 html $ pybabel extract -F babel.cfg -o messages.pot . # Step3 自動建立 translations 資料夾 $ pybabel init -i messages.pot -d translations -l zh |
這邊輸入完第三步指令後,會看到系統自動建立出 translations 的資料夾。
1 2 3 4 5 |
│ ├── translations │ │ └── zh │ │ └── LC_MESSAGES │ │ ├── messages.mo │ │ └── messages.po |
進入 /zh/LC_MESSAGES /messages.po
後,接下來就是翻譯的時間,把你想翻譯的結果寫在 msgstr 裡面,就可以了!
1 2 3 4 5 6 7 |
#: templates/index.html:11 msgid "Hello World!" msgstr "" #: templates/index.html:12 msgid "My name is Max" msgstr "" |
完成結果如下:
1 2 3 4 5 6 7 |
#: templates/index.html:11 msgid "Hello World!" msgstr "你好" #: templates/index.html:12 msgid "My name is Max" msgstr "馬克思" |
最後 Step 4 將剛剛翻譯的結果從 po 檔輸出成 mo 檔
1 2 |
# Step4 將 po 檔轉換成 mo $ pybabel compile -d translations |
補充:
.mo 檔 (Machine Object) 是程式在執行翻譯時會閱讀的檔案
.po 檔 (Portable Object) 則是預留給我們作為編輯用的檔案
因此 Step4 就是將編輯完的 .po 產生新的.mo 給程式閱讀
接下來就是運行 Flask 試試看結果啦!
1 2 |
$ 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
1 2 3 4 5 6 7 8 |
# 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 的使用說明,我覺得這三篇寫得很清楚,推薦補充閱讀:
- GitHub – twtrubiks/Flask-Babel-example: How Use Flask-Babel on Windows – Python Flask
- Flask實作ext_17_Flask_babel多語系 – HackMD
- Python – Flask i18n 多國語系功能實作 | My.APOLLO
關於 Flask 教學的延伸閱讀:
▍關於 Flask 教學系列目錄:
▍其他 Flask 相關教學:
- 【Flask教學系列】Flask 為甚麼需要 WSGI 與 Nginx
- 【Flask教學系列】Flask-JWT-Extended 實作
- 【Flask教學系列】實作 Flask CORS
- 【Flask教學系列】實作 Flask CSRF Protection
- 【Flask教學系列】實作 Dockerfile + Flask 教學 (附GitHub完整程式)
- 【Flask 教學系列】 Python Flask 綠界金流 API 信用卡串接
那麼有關於【Flask教學】實作 Flask i18n 多國語系 的介紹就到這邊告一個段落囉!有任何問題可以在以下留言~
有關 Flask 的最新文章,都會發佈在 Max 的 Facebook 粉絲專頁,如果想看最新更新,還請您按讚或是追蹤唷!