user2990084
user2990084

Reputation: 2840

Format canonical URL structure correctly with URL Processors

EDIT: Could you read my question more carefully? This question is specific and not duplicated as has been said. Obviously I read this question before post.


In this demo code from the docs, the fragment after the lang parameter will be static. So, in English /en/about, and for example, in Portuguese /pt/about. Well, the correct, should be /pt/sobre.

Any idea about the correct way to make that with URL Processors?

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():

Upvotes: 5

Views: 812

Answers (1)

tbicr
tbicr

Reputation: 26080

Ok, you already have language prefix. So you need have several translations for next part.

The easiest way it use several routes or converters:

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

or

@app.route('/<lang_code>/<any(about,sobre):about>')
def about(about):
    pass

But it hard to support and add new languages.

Second way is change route method to translate special words or add special translate processor converter, last more interesting and implicit:

from werkzeug.routing import AnyConverter


languages = ['en', 'pt']


def translate_url(word, language):
    if language == 'pt' and word == 'about':
        return 'sobre'
    return word


class TranslateConverter(AnyConverter):

    def __init__(self, map, item):
        AnyConverter.__init__(self, map, *[translate_url(item, language) 
                                           for language in languages])


app.url_map.converters['tr'] = TranslateConverter


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

But this example have next issue:

/en/about
/en/sorbe
/pt/about
/pt/sorbe

is valid urls, but you can also try use own Rule class (Flask.url_rule_class) where in match method you can process this cases:

from werkzeug.routing import AnyConverter, Rule


class TranslateConverter(AnyConverter):

    def __init__(self, map, item):
        self.language_pairs = {language: translate_url(item, language)
                               for language in languages}
        AnyConverter.__init__(self, map, *tuple(self.language_pairs.values()))


class TranslateCorrelationRule(Rule):

    def match(self, path):
        result = Rule.match(self, path)
        if result is None:
            return result
        lang_code = result.get('lang_code')
        if lang_code is None:
            return result
        for name, value in self._converters.items():
            if not isinstance(value, TranslateConverter):
                continue
            if value.language_pairs[lang_code] != result[name]:
                return
        return result


app.url_map.converters['tr'] = TranslateConverter
app.url_rule_class = TranslateCorrelationRule

If you will simplify url_for usage for this examples you can use next sample:

@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    if not values:
        return
    g.lang_code = values.pop('lang_code', None)
    for key, value in values.items():
        if key.startswith('_'):
            values.pop(key)


class TranslateCorrelationRule(Rule):

    def _update_translate_values(self, values):
        lang_code = values.get('lang_code', getattr(g, 'lang_code', None))
        if lang_code is None:
            return values
        values = values.copy()
        for argument in self.arguments:
            if argument in values:
                continue
            converter = self._converters[argument]
            if not isinstance(converter, TranslateConverter):
                continue
            values[argument] = converter.language_pairs[lang_code]
        return values

    def suitable_for(self, values, method=None):
        return Rule.suitable_for(self, self._update_translate_values(values),
                                 method)

    def build(self, values, append_unknown=True):
        return Rule.build(self, self._update_translate_values(values),
                          append_unknown)

Upvotes: 5

Related Questions