Python module - Babel
Tags: python, babel, programming
Published: 2021-01-15 16:06:05

一般在處理 i18n, l10n 時，通常會遇到時間格式、貨幣、語系、地區、官方語言等問題，例如需要針對不同語系的使用者，顯示不同語系的的國家名稱，這時候不妨考慮使用 [Babel](http://babel.pocoo.org/en/latest/) 幫忙處理這些瑣事。

> Babel is an integrated collection of utilities that assist in internationalizing and localizing Python applications, with an emphasis on web-based applications.

<!-- more -->

以下本文介紹 [Babel](http://babel.pocoo.org/en/latest/) 的基本用法。

### 本文環境

- Python 3.7

### 安裝 Babel

```
$ pip install Babel
```

除了直接安裝，也可以考慮將 Babel 安裝在 virtualenv 中。

關於 virtualenv - [Python 必備良藥 - virtualenv](https://myapollo.com.tw/zh-tw/virtualenv/)

### 語系（Locale）

Babel 收錄了 [Common Locale Data Repository (CLDR)](http://cldr.unicode.org/) ，因此可以透過 Babel 存取 Locale Data ，取得特定語系的資料，例如官方語言、貨幣、時區等資料。

而 Babel 的 Locale Data 是透過 [Locale class](http://babel.pocoo.org/en/latest/locale.html?highlight=zh_TW#the-locale-class) 進行存取。例如，載入 US 的 Locale Data ：

```
>>> from babel import Locale
>>> locale = Locale('en', 'US')
>>> locale.display_name
'English (United States)'
```

Locale object 有許多屬性可以使用，例如 `datetime_formats` 日期時間格式、 `currency_symbols` 貨幣符號、 `character_order` 書寫方向等等。

詳見 [Core Functionality](http://babel.pocoo.org/en/latest/api/core.html#babel.core.Locale) 。

### 地區（Territory）

能夠載入 Loclae Data 就可以輕易取得所有的地區列表。

以下為取得地區列表的範例：

```
from babel import Locale
en_locale = Locale('en', 'US')
for code, name in en_locale.territories.items():
    if code.isdigit() or code == 'ZZ':
        continue
    print(code, '=>', name)
```

執行結果：

```
AC => Ascension Island
AD => Andorra
AE => United Arab Emirates
AF => Afghanistan
AG => Antigua & Barbuda
AI => Anguilla
AL => Albania
AM => Armenia
AO => Angola
AQ => Antarctica
```

說明：

我們先用 `Locale('en', 'US')` 載入英文的 Locale Data 並指派給變數 `en_locale` ，接著走訪 `en_locale` 的屬性 `territories` ，印出每個 territory 的代碼與名字。其中， `if code.isdigit() or code == 'ZZ':` 這一行忽略了 `001` 代碼，這些數字代碼代表的是一個大區域，例如 001 代表 World ，而 `ZZ` 則代表 Unknown ，所以略過它們。

如果是要繁體中文的 Locale Data 則只要把 `Locale('en', 'US')` 換成 `Locale('zh', territory='TW', script='Hant')` 即可。

如果只有知道 Language Code (例如： `zh_CN` `zh_TW` )的話，可以直接使用 `Locale.parse()` ，該函數可以直接帶入 Language Code 就回傳相對應的 Locale Data ，例如：

```
>>> from babel import Locale
>>> zh_tw = Locale.parse('zh_TW')
>>> zh_tw.display_name
'中文 (繁體, 台灣)'
```

### 語言（Languages）

每個 Locale object 都可以取得其名稱：

```
>>> from babel import Locale
>>> zh_tw = Locale.parse('zh_TW')
>>> zh_tw.language_name
'中文'
```

也可以用 `display_name` 顯示比較清楚：

```
>>> zh_tw.display_name
'中文 (繁體, 台灣)'
```

此外，有些國家可能會有多種官方語言，可以用 `get_official_languages()` 函式取得這些資料，例如新加坡有多種官方語言：

```
>>> from babel.languages import get_official_languages
>>> get_official_languages('SG')
('en', 'zh_Hans', 'ms_Latn', 'ta')
```

上述可以看到新加坡官方語言有 4 種，我們可以進一步列印出這些語言的名稱：

```
>>> for lang in get_official_languages('SG'):
...     print(Locale.parse(lang).display_name)
English
中文 (简体)
Bahasa Melayu
தமிழ்
```

### 日期與時間

事實上，每個地區的日期時間表達方式也不盡相同，因此一個跨國平台或服務也經常會針對各地區顯示不同的日期時間格式，例如德國跟美國習慣的日期格式不一樣。這些資料也同樣可以透過 Babel 取得。

以下範例取得德國的日期格式：

```
>>> from babel import Locale
>>> de_de = Locale.parse('de_DE')
>>> for k, v in de_de.datetime_formats.items():
...     print(k, '=>', v)
```

執行結果如下：

```
full => {1} 'um' {0}
long => {1} 'um' {0}
medium => {1}, {0}
short => {1}, {0}
```

上述結果中的 `full` , `long` , `medium` , `short` 是 4 種日期格式（由長到短）， `{1}` 是 date 的 placeholder ， `{0}` 是 time 的 placeholder 。

上述範例只告訴我們一個語系的 4 種格式長怎樣，但實際上要轉換日期時間到該語系的格式則是透過 `format_datetime()` 幫忙，例如以下範例會用 `format_datetime()` 將現在的時間轉換成德國的 `long` 格式

```
>>> from datetime import datetime
>>> from babel.dates import format_datetime
>>> now_dt = datetime.now()
>>> format_datetime(now_dt, locale='de_DE', format='long')
'19. März 2018 um 10:39:17 +0000'
```

`format_datetime()` 接受的參數有 datetime object 、 locale （語系）與 format（格式），而 format 恰好接受的格式就是先前範例看到的 4 種格式，所以可以代入 `full` , `long` , `medium` , `short` 4 種，預設為 `medium` 。

如果只需要針對 date 或 time 進行格式轉換， Babel 還有提供 `format_date()` 與 `format_time()` 可以使用。

詳閱 [Date and Time](http://babel.pocoo.org/en/latest/dates.html#date-and-time)

### 數字與貨幣（Numbers and Currencies）

最後談談數字與貨幣。

只要是跨國平台或服務，一定會遇到數字格式與貨幣格式的問題，例如價格都是顯示 $10 元的話，有時候不見得真的是指美元 10 元，因此較好的方式是連貨幣符號都一併顯示。而千分位的標示符號，在各地區也不見得都是使用逗號 `,` ，用 Babel 則可以幫助處理這些瑣碎的事情。

直接看用 `format_decimal()` 轉換 decimal 格式的範例：

```
>>> from babel.numbers import format_decimal
>>> format_decimal(12345.12, locale='en_US')
'12,345.12'
>>> format_decimal(12345, locale='de_DE')
'12.345'
```

上述範例可以明顯看到德國是用 `.` 作為千分位的標示，與我們習慣的逗號不同。

貨幣的格式則是用 `format_currency()` 處理：

```
>>> from babel.numbers import format_currency
>>> format_currency(1099.98, 'USD', locale='en_US')
'$1,099.98'
>>> format_currency(1099, 'TWD', locale='zh_TW')
'$1,099.00'
```

上述範例顯示美國與台灣的金額格式，但這樣很難區分到底是美元還是台幣，因此 `format_currency()` 還提供 `format` 參數使用，可以把貨幣名稱顯示出來。例如：

```
>>> from babel.numbers import format_currency
>>> format_currency(1099, 'TWD', locale='zh_TW', format='¤ #,##0.00')
'$ 1,099.00'
>>> format_currency(1099, 'TWD', locale='zh_TW', format='¤¤ #,##0.00')
'TWD 1,099.00'
>>> format_currency(1099, 'TWD', locale='zh_TW', format='¤¤¤ #,##0.00')
'新台幣 1,099.00'
```

上述顯示 3 種不同格式的台幣顯示方式， `format` 參數中的 `¤` 代表符號， `¤¤` 代表貨幣縮寫，而 `¤¤¤` 代表貨幣的全名，因此可以視情況使用不同的格式。

另外，台幣由於官方單位是算到小數點後第 2 位，但由於現在最小單位是 1 塊錢，在顯示上我們會顯示整數，這部分也可以透過調整 `format` 達成，只要將 `format` 改為 `¤¤¤ #,##0` 即可，並代入 `currency_digits=False` 即可， `currency_digits=False` 代表不要使用官方定義的小數點。

例如：

```
>>> format_currency(1099, 'TWD', locale='zh_TW', format='¤¤¤ #,##0', currency_digits=False)
'新台幣 1,099'
```

### 總結

以上就是簡化過後的 Babel 介紹，詳情可以至 Babel 參閱更多資料。

### Reference

http://babel.pocoo.org/en/latest/index.html