# Flask-Babel
Flask-Babel 是一個 [Flask](http://flask.pocoo.org/) 的擴展,在 [babel](http://babel.edgewall.org/), [pytz](http://pytz.sourceforge.net/) 和 [speaklater](http://pypi.python.org/pypi/speaklater) 的幫助下添加 i18n 和 l10n 支持到任何 Flask 應用。它內置了一個時間格式化的支持,同樣內置了一個非常簡單和友好的 [`gettext`](http://docs.python.org/library/gettext.html#module-gettext "(in Python v2.7)") 翻譯的接口。
## 安裝
下面命令可以安裝擴展:
```
$ easy_install Flask-Babel
```
或者如果你安裝了 pip:
```
$ pip install Flask-Babel
```
請注意 Flask-Babel 需要 Jinja 2.5。如果你安裝一個老的版本你將會需要升級或者禁止 Jinja 支持。
## 配置
在配置好應用后所有需要做的就是實例化一個 [`Babel`](#flask.ext.babel.Babel "flask.ext.babel.Babel") 對象:
```
from flask import Flask
from flask.ext.babel import Babel
app = Flask(__name__)
app.config.from_pyfile('mysettings.cfg')
babel = Babel(app)
```
babel 對象本身以后支持用于配置 babel。Babel 有兩個配置值,這兩個配置值能夠改變內部的默認值:
| | |
| --- | --- |
| `BABEL_DEFAULT_LOCALE` | 如果沒有指定地域且選擇器已經注冊, 默認是缺省地域。默認是 `'en'`。 |
| `BABEL_DEFAULT_TIMEZONE` | 用戶默認使用的時區。默認是 `'UTC'`。選用默 認值的時候,你的應用內部必須使用該時區。 |
對于更復雜的應用你可能希望對于不同的用戶有多個應用,這個時候是選擇器函數派上用場的時候。babel 擴展第一次需要當前用戶的地區的時候,它會調用 [`localeselector()`](#flask.ext.babel.Babel.localeselector "flask.ext.babel.Babel.localeselector") 函數,第一次需要時區的時候,它會調用 [`timezoneselector()`](#flask.ext.babel.Babel.timezoneselector "flask.ext.babel.Babel.timezoneselector") 函數。
如果這些方法的任何一個返回 `None`,擴展將會自動回落到配置中的值。而且為了效率考慮函數只會調用一次并且返回值會被緩存。如果你需要在一個請求中切換語言的話,你可以 [`refresh()`](#flask.ext.babel.refresh "flask.ext.babel.refresh") 緩存。
選擇器函數的例子:
```
from flask import g, request
@babel.localeselector
def get_locale():
# if a user is logged in, use the locale from the user settings
user = getattr(g, 'user', None)
if user is not None:
return user.locale
# otherwise try to guess the language from the user accept
# header the browser transmits. We support de/fr/en in this
# example. The best match wins.
return request.accept_languages.best_match(['de', 'fr', 'en'])
@babel.timezoneselector
def get_timezone():
user = getattr(g, 'user', None)
if user is not None:
return user.timezone
```
以上的例子假設當前的用戶是存儲在 [`flask.g`](http://flask.pocoo.org/docs/api/#flask.g "(in Flask v1.0-dev)") 對象中。
## 格式化日期
你可以使用 [`format_datetime()`](#flask.ext.babel.format_datetime "flask.ext.babel.format_datetime"),[`format_date()`](#flask.ext.babel.format_date "flask.ext.babel.format_date"),[`format_time()`](#flask.ext.babel.format_time "flask.ext.babel.format_time") 以及 [`format_timedelta()`](#flask.ext.babel.format_timedelta "flask.ext.babel.format_timedelta") 函數來格式化日期。它們都接受一個 [`datetime.datetime`](http://docs.python.org/library/datetime.html#datetime.datetime "(in Python v2.7)") (或者 [`datetime.date`](http://docs.python.org/library/datetime.html#datetime.date "(in Python v2.7)"),[`datetime.time`](http://docs.python.org/library/datetime.html#datetime.time "(in Python v2.7)") 以及 [`datetime.timedelta`](http://docs.python.org/library/datetime.html#datetime.timedelta "(in Python v2.7)"))對象作為第一個參數,其它參數是一個可選的格式化字符串。應用程序應該使用天然的 datetime 對象且內部使用 UTC 作為默認時區。格式化的時候會自動地轉換成用戶時區以防它不同于 UTC。
為了能夠在命令行中使用日期格式化,你可以使用 [`test_request_context()`](http://flask.pocoo.org/docs/api/#flask.Flask.test_request_context "(in Flask v1.0-dev)") 方法:
```
>>> app.test_request_context().push()
```
這里是一些例子:
```
>>> from flask.ext.babel import format_datetime
>>> from datetime import datetime
>>> format_datetime(datetime(1987, 3, 5, 17, 12))
u'Mar 5, 1987 5:12:00 PM'
>>> format_datetime(datetime(1987, 3, 5, 17, 12), 'full')
u'Thursday, March 5, 1987 5:12:00 PM World (GMT) Time'
>>> format_datetime(datetime(1987, 3, 5, 17, 12), 'short')
u'3/5/87 5:12 PM'
>>> format_datetime(datetime(1987, 3, 5, 17, 12), 'dd mm yyy')
u'05 12 1987'
>>> format_datetime(datetime(1987, 3, 5, 17, 12), 'dd mm yyyy')
u'05 12 1987'
```
接著用不同的語言再次格式化:
```
>>> app.config['BABEL_DEFAULT_LOCALE'] = 'de'
>>> from flask.ext.babel import refresh; refresh()
>>> format_datetime(datetime(1987, 3, 5, 17, 12), 'EEEE, d. MMMM yyyy H:mm')
u'Donnerstag, 5\. M\xe4rz 1987 17:12'
```
關于格式例子的更多信息請參閱 [babel](http://babel.edgewall.org/) 文檔。
## 使用翻譯
日期格式化之外的另一個部分就是翻譯。Flask 使用 [`gettext`](http://docs.python.org/library/gettext.html#module-gettext "(in Python v2.7)") 和 Babel 配合一起實現翻譯的功能。gettext 的作用就是你可以標記某些字符串作為翻譯的內容并且一個工具會從應用中挑選這些,接著把它們放入一個單獨的文件為你來翻譯。在運行的時候原始的字符串(應該是英語)將會被你選擇的語言替換掉。
有兩個函數可以用來完成翻譯:[`gettext()`](#flask.ext.babel.gettext "flask.ext.babel.gettext") 和 [`ngettext()`](#flask.ext.babel.ngettext "flask.ext.babel.ngettext")。第一個函數用于翻譯含有 0 個或者 1 個字符串參數的字符串,第二個參數用于翻譯含有多個字符串參數的字符串。這里有些示例:
```
from flask.ext.babel import gettext, ngettext
gettext(u'A simple string')
gettext(u'Value: %(value)s', value=42)
ngettext(u'%(num)s Apple', u'%(num)s Apples', number_of_apples)
```
另外如果你希望在你的應用中使用常量字符串并且在請求之外定義它們的話,你可以使用一個“懶惰”字符串。“懶惰”字符串直到它們實際被使用的時候才會計算。為了使用一個“懶惰”字符串,請使用 [`lazy_gettext()`](#flask.ext.babel.lazy_gettext "flask.ext.babel.lazy_gettext") 函數:
```
from flask.ext.babel import lazy_gettext
class MyForm(formlibrary.FormBase):
success_message = lazy_gettext(u'The form was successfully saved.')
```
Flask-Babel 如何找到翻譯?首先你必須要生成翻譯。這里是你如何做到這一點:
## 翻譯應用
首先你需要用 [`gettext()`](#flask.ext.babel.gettext "flask.ext.babel.gettext") 或者 [`ngettext()`](#flask.ext.babel.ngettext "flask.ext.babel.ngettext") 在你的應用中標記你要翻譯的所有字符串。在這之后,是時候創建一個 `.pot` 文件。一個 `.pot` 文件包含所有的字符串,并且它是一個 `.po` 文件的模板,`.po` 文件包含已經翻譯的字符串。Babel 可以為你做所有的這一切。
首先你必須進入到你的應用所在的文件夾中并且創建一個映射文件夾。對于典型的 Flask 應用,這是你要的:
```
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
```
在你的應用中把它保存成 `babel.cfg` 或者其它類似的東東。接著是時候運行來自 Babel 中的 `pybabel` 命令來提取你的字符串:
```
$ pybabel extract -F babel.cfg -o messages.pot .
```
如果你使用了 [`lazy_gettext()`](#flask.ext.babel.lazy_gettext "flask.ext.babel.lazy_gettext") 函數,你應該告訴 pybabel,這時候需要這樣運行 `pybabel`:
```
$ pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot .
```
這會使用 `babel.cfg` 文件中的映射并且在 `messages.pot` 里存儲生成的模板。現在可以創建第一個翻譯。例如使用這個命令可以翻譯成德語:
```
$ pybabel init -i messages.pot -d translations -l de
```
`-d translations` 告訴 pybabel 存儲翻譯在這個文件夾中。這是 Flask-Babel 尋找翻譯的地方。可以把它放在你的模板文件夾旁邊。
現在如有必要編輯 `translations/de/LC_MESSAGES/messages.po` 文件。如果你感到困惑的話請參閱一些 gettext 教程。
為了能用需要編譯翻譯,`pybabel` 再次大顯神通:
```
$ pybabel compile -d translations
```
如果字符串變化了怎么辦?像上面一樣創建一個新的 `messages.pot` 接著讓 `pybabel` 整合這些變化:
```
$ pybabel update -i messages.pot -d translations
```
之后有些字符串可能會被標記成含糊不清。如果有含糊不清的字符串的時候,務必在編譯之前手動地檢查他們并且移除含糊不清的標志。
## 問題
在 Snow Leopard 上 pybabel 最有可能會以一個異常而失敗。如果發生了,檢查命令的輸出是否是 UTF-8:
```
$ echo $LC_CTYPE
UTF-8
```
不幸地這是一個 OS X 問題。為了修復它,請把如下的行加入到你的 `~/.profile` 文件:
```
export LC_CTYPE=en_US.utf-8
```
接著重啟你的終端。
## API
文檔這一部分列出了 Flask-Babel 中每一個公開的類或者函數。
### 配置
`class flask.ext.babel.Babel(app=None, default_locale='en', default_timezone='UTC', date_formats=None, configure_jinja=True)`
Central controller class that can be used to configure how Flask-Babel behaves. Each application that wants to use Flask-Babel has to create, or run [`init_app()`](#flask.ext.babel.Babel.init_app "flask.ext.babel.Babel.init_app") on, an instance of this class after the configuration was initialized.
`default_locale`
The default locale from the configuration as instance of a `babel.Locale` object.
`default_timezone`
The default timezone from the configuration as instance of a `pytz.timezone` object.
`init_app(app)`
Set up this instance for use with _app_, if no app was passed to the constructor.
`list_translations()`
Returns a list of all the locales translations exist for. The list returned will be filled with actual locale objects and not just strings.
New in version 0.6.
`localeselector(f)`
Registers a callback function for locale selection. The default behaves as if a function was registered that returns `None` all the time. If `None` is returned, the locale falls back to the one from the configuration.
This has to return the locale as string (eg: `'de_AT'`, ‘’`en_US`‘’)
`timezoneselector(f)`
Registers a callback function for timezone selection. The default behaves as if a function was registered that returns `None` all the time. If `None` is returned, the timezone falls back to the one from the configuration.
This has to return the timezone as string (eg: `'Europe/Vienna'`)
### Context 函數
`flask.ext.babel.get_translations()`
Returns the correct gettext translations that should be used for this request. This will never fail and return a dummy translation object if used outside of the request or if a translation cannot be found.
`flask.ext.babel.get_locale()`
Returns the locale that should be used for this request as `babel.Locale` object. This returns `None` if used outside of a request.
`flask.ext.babel.get_timezone()`
Returns the timezone that should be used for this request as `pytz.timezone` object. This returns `None` if used outside of a request.
### Datetime 函數
`flask.ext.babel.to_user_timezone(datetime)`
Convert a datetime object to the user’s timezone. This automatically happens on all date formatting unless rebasing is disabled. If you need to convert a [`datetime.datetime`](http://docs.python.org/library/datetime.html#datetime.datetime "(in Python v2.7)") object at any time to the user’s timezone (as returned by [`get_timezone()`](#flask.ext.babel.get_timezone "flask.ext.babel.get_timezone") this function can be used).
`flask.ext.babel.to_utc(datetime)`
Convert a datetime object to UTC and drop tzinfo. This is the opposite operation to [`to_user_timezone()`](#flask.ext.babel.to_user_timezone "flask.ext.babel.to_user_timezone").
`flask.ext.babel.format_datetime(datetime=None, format=None, rebase=True)`
Return a date formatted according to the given pattern. If no [`datetime`](http://docs.python.org/library/datetime.html#datetime.datetime "(in Python v2.7)") object is passed, the current time is assumed. By default rebasing happens which causes the object to be converted to the users’s timezone (as returned by [`to_user_timezone()`](#flask.ext.babel.to_user_timezone "flask.ext.babel.to_user_timezone")). This function formats both date and time.
The format parameter can either be `'short'`, `'medium'`, `'long'` or `'full'` (in which cause the language’s default for that setting is used, or the default from the `Babel.date_formats` mapping is used) or a format string as documented by Babel.
This function is also available in the template context as filter named `datetimeformat`.
`flask.ext.babel.format_date(date=None, format=None, rebase=True)`
Return a date formatted according to the given pattern. If no [`datetime`](http://docs.python.org/library/datetime.html#datetime.datetime "(in Python v2.7)") or [`date`](http://docs.python.org/library/datetime.html#datetime.date "(in Python v2.7)") object is passed, the current time is assumed. By default rebasing happens which causes the object to be converted to the users’s timezone (as returned by [`to_user_timezone()`](#flask.ext.babel.to_user_timezone "flask.ext.babel.to_user_timezone")). This function only formats the date part of a [`datetime`](http://docs.python.org/library/datetime.html#datetime.datetime "(in Python v2.7)") object.
The format parameter can either be `'short'`, `'medium'`, `'long'` or `'full'` (in which cause the language’s default for that setting is used, or the default from the `Babel.date_formats` mapping is used) or a format string as documented by Babel.
This function is also available in the template context as filter named `dateformat`.
`flask.ext.babel.format_time(time=None, format=None, rebase=True)`
Return a time formatted according to the given pattern. If no [`datetime`](http://docs.python.org/library/datetime.html#datetime.datetime "(in Python v2.7)") object is passed, the current time is assumed. By default rebasing happens which causes the object to be converted to the users’s timezone (as returned by [`to_user_timezone()`](#flask.ext.babel.to_user_timezone "flask.ext.babel.to_user_timezone")). This function formats both date and time.
The format parameter can either be `'short'`, `'medium'`, `'long'` or `'full'` (in which cause the language’s default for that setting is used, or the default from the `Babel.date_formats` mapping is used) or a format string as documented by Babel.
This function is also available in the template context as filter named `timeformat`.
`flask.ext.babel.format_timedelta(datetime_or_timedelta, granularity='second')`
Format the elapsed time from the given date to now or the given timedelta. This currently requires an unreleased development version of Babel.
This function is also available in the template context as filter named `timedeltaformat`.
### Gettext 函數
`flask.ext.babel.gettext(string, **variables)`
Translates a string with the current locale and passes in the given keyword arguments as mapping to a string formatting string.
```
gettext(u'Hello World!')
gettext(u'Hello %(name)s!', name='World')
```
`flask.ext.babel.ngettext(singular, plural, num, **variables)`
Translates a string with the current locale and passes in the given keyword arguments as mapping to a string formatting string. The `num` parameter is used to dispatch between singular and various plural forms of the message. It is available in the format string as `%(num)d` or `%(num)s`. The source language should be English or a similar language which only has one plural form.
```
ngettext(u'%(num)d Apple', u'%(num)d Apples', num=len(apples))
```
`flask.ext.babel.pgettext(context, string, **variables)`
Like [`gettext()`](#flask.ext.babel.gettext "flask.ext.babel.gettext") but with a context.
New in version 0.7.
`flask.ext.babel.npgettext(context, singular, plural, num, **variables)`
Like [`ngettext()`](#flask.ext.babel.ngettext "flask.ext.babel.ngettext") but with a context.
New in version 0.7.
`flask.ext.babel.lazy_gettext(string, **variables)`
Like [`gettext()`](#flask.ext.babel.gettext "flask.ext.babel.gettext") but the string returned is lazy which means it will be translated when it is used as an actual string.
Example:
```
hello = lazy_gettext(u'Hello World')
@app.route('/')
def index():
return unicode(hello)
```
`flask.ext.babel.lazy_pgettext(context, string, **variables)`
Like [`pgettext()`](#flask.ext.babel.pgettext "flask.ext.babel.pgettext") but the string returned is lazy which means it will be translated when it is used as an actual string.
New in version 0.7.
### 低層的 API
`flask.ext.babel.refresh()`
Refreshes the cached timezones and locale information. This can be used to switch a translation between a request and if you want the changes to take place immediately, not just with the next request:
```
user.timezone = request.form['timezone']
user.locale = request.form['locale']
refresh()
flash(gettext('Language was changed'))
```
Without that refresh, the [`flash()`](http://flask.pocoo.org/docs/api/#flask.flash "(in Flask v1.0-dev)") function would probably return English text and a now German page.