Сообщить об ошибке.

Интернационализация приложения Flask при помощи URL-процессора

С версии 0.7, фреймворк Flask представляет концепцию обработчиков URL или как их еще называют: URL-процессоры. Идея состоит в том, чтобы общие части в URL-адреса не обрабатывались каждая в отдельной функции-представлении. Например, есть несколько URL-адресов, содержащих код языка, и необходимо сделать так, что бы исключить обработку языка в каждой отдельной функции.

URL-процессоры особенно полезны в сочетании со схемами blueprint. В этом материале рассмотрим использование URL-процессоров в конкретных приложениях, так и особенности применения со схемами blueprint.

Содержание:

Интернационализация приложений на Flask.

Рассмотрим такое приложение:

from flask import Flask, g

app = Flask(__name__)

@app.route('/<lang_code>/')
def index(lang_code):
    g.lang_code = lang_code
    ...

@app.route('/<lang_code>/about')
def about(lang_code):
    g.lang_code = lang_code
    ...

Из приведенного кода видно, что язык пользователя обрабатывается в каждой функции-представлении, и это очень много повторений, а следовательно и кода. Для упрощения всего этого можно использовать декоратор, но если генерировать URL-адреса от одной функции к другой, то все равно придется явно указывать языковой код пользователя, что может раздражать.

Для последнего действия, на помощь приходит декоратор экземпляра приложения @app.url_defaults(), который может автоматически вставлять значения в вызов url_for(). Приведенный ниже код проверяет, нет ли языкового кода еще в словаре значений URL и требуется ли конечной точке значение с именем lang_code:

@app.url_defaults
def add_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

Здесь, для выяснения, стоит ли передавать языковой код для данной конечной точки, используется метод app.url_map.is_endpoint_expecting().

Обратной этому методу, является декоратор @app.url_value_preprocessor(). Он выполняются сразу после сопоставления запроса и может выполнять код на основе изменяемых значений URL. Идея состоит в том, что он извлекает информацию из словаря значений и помещает ее в другое место:

@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code', None)

Таким образом, больше не нужно назначать lang_code для объекта flask.g в каждой функции. Можно еще больше улучшить поведение, написав свой собственный декоратор, который добавляет к URL-адресам код языка, но более красивым решением является использование схемы blueprint. После того, как lang_code будет извлечен из словаря значений и больше не будет перенаправлен в функцию-представление, код сократиться до следующего:

from flask import Flask, g

app = Flask(__name__)

@app.url_defaults
def add_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

@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code', None)

@app.route('/<lang_code>/')
def index():
    ...

@app.route('/<lang_code>/about')
def about():
    ...

Интернационализация приложений на схемах blueprint.

Так как схемы blueprint могут автоматически подставлять префикс для всех URL-адресов, это легко сделать автоматически для каждой функции-представления. Кроме того, в blueprint можно определить URL-процессор для каждой схемы, что уберет большую часть логики из декоратора blueprint.url_defaults(), т.к. ему больше не надо проверять, нужен ли lang_code URL-адресу:

from flask import Blueprint, g

bp = Blueprint('frontend', __name__, url_prefix='/<lang_code>')

@bp.url_defaults
def add_language_code(endpoint, values):
    values.setdefault('lang_code', g.lang_code)

@bp.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code')

@bp.route('/')
def index():
    ...

@bp.route('/about')
def about():
    ...