angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Het bouwen van een meertalige Flask website met Flask-Babel

Er bestaan niet veel voorbeelden van meertalige Flaskvoorbeelden. Hier volgen we de suggesties uit de Flask documentatie.

7 augustus 2019
In Babel
post main image
Original photo unsplash.com/@jantined.

Met één enkele taal is er niet echt een probleem, we vergeten gewoon de rest van de wereld en bouwen onze één-talige Flask applicatie. We beginnen hoofdpijn te krijgen als de website meerdere talen moet ondersteunen. Wat is precies een website die meerdere talen ondersteunt? Hoeveel talen worden er ondersteund en welke talen? Voor Engelsen zijn er bijvoorbeeld en-GB en en-US. Welke delen van de website moeten in alle talen beschikbaar zijn? Wat zijn de onderdelen van een website eigenlijk? Ik zal me hier beperken tot het beantwoorden van de meest triviale vragen. Voor een goede introductie kunt u bijvoorbeeld ook de Wordpress Polylang plugin guide raadplegen, zie hieronder.

Wanneer u een website bouwt zonder meerdere talen te ondersteunen, gebruikt u urls voor uw inhoud, wanneer u meer talen wilt toevoegen, moet u alle urls onderhouden, d.w.z. wat er al is aangemaakt. Vanuit SEO-standpunt kunt u beter beginnen met alle meertalige componenten, zelfs als u nu twijfelt of u ooit een andere taal zult toevoegen.

Taalkeuze

Wanneer de taal in een cookie (sessie) in een cookie (sessie) is, kan het bekijken van de taal een probleem zijn wanneer cookies zijn uitgeschakeld. Met de taal in het domein/url is dit probleem geëlimineerd en is de website ook SEO-vriendelijker.

Optie 1: taal in de domeinextensie

example.com
example.es
example.pl

Voorbeeld: toyota
https://www.toyota.de
https://www.toyota.es

Optie 2: taal in het subdomein

en.example.com
es.example.com
pl. en. es. pl.example.com

Voorbeeld: cnn
https://edition.cnn.com
https://cnnespanol.cnn.com

Optie 3: taal in het urlpad

example.com/en
example.com/es
example.com/pl

Vertaalde url's

Vertaalde url's zijn zeer vriendelijk, maar zorgen ook voor meer complexiteit:

https://edition.cnn.com/entertainment /seccion
https://cnnespanol.cnn.com/seccion/entretenimiento

Sommige websites hebben geen vertaalde urls:

https://www.tesla.com/nl_NL/model3/design#battery
https://www.tesla.com/de_CH/model3/design#battery

Als we vertaalde taalurl's willen hebben, en waarom zouden we dat niet doen, dan hebben we ook meerdere eindpunten nodig voor een view-functie:

@pages.route('/<lang_code>/about-us', methods=['GET', 'POST'])
@pages.route('/<lang_code>/uber-uns', methods=['GET', 'POST'])
def about():
    return render_template(...)

Natuurlijk willen we dit op de een of andere manier geautomatiseerd hebben.... moeilijk.

Keuzes maken

Voorlopig richt ik me op een website met de 'language id' in het urlpad, zie hierboven optie 3, en ik zal me niet bezighouden met vertaalde url's. Omdat we gebruik Flaskmaken van, gebruiken we Flask-Babel voor onze vertalingen. Sommige teksten, zoals blogs, categorieën, categorieën, tags, staan in de database. We zullen deze later behandelen.

U wilt misschien een website met de hoofdtaal zonder de 'taal-id' in het urlpad en de andere talen met de 'taal-id' in het urlpad. Ik weet niet goed hoe ik dit Flask op dit moment moet doen en ik geloof ook dat dit de zaken erg ingewikkeld maakt. Denk ook aan SEO. Stel dat je na een paar jaar een andere taal wilt toevoegen dan kun je je url's niet veranderen. Samenvattend: Ik denk dat het geen slecht idee is om te beginnen met de taal in het urlpad.

In een eerste implementatie gebruikte ik een cookie voor de taalkeuze, nu moet ik deze verwijderen en de taal in de url gebruiken. Gelukkig geeft de Flask documentatie goede informatie over internationalisering. Ik stel voor dat je dit leest (ik heb het vaak gedaan). Ik heb er veel blueprints, voor de home page view.py voeg ik het volgende toe aan de weergave:

...
home_blueprint = Blueprint('home', __name__)

# lang_code in urls
@home_blueprint.url_defaults
def add_language_code(endpoint, values):
    values.setdefault('lang_code', g.lang_code)

@home_blueprint.url_value_preprocessor
def pull_lang_code(endpoint, values):

    url_lang_code_items_values = get_url_lang_code_items_values()
    url_lang_code_default_item_value = get_url_lang_code_default_item_value()

    g.lang_code = url_lang_code_default_item_value
    if values:
        if 'lang_code' in values:
            if values['lang_code'] in url_lang_code_items_values:
                g.lang_code = values.pop('lang_code', None)
            else:
                pass
...

De functie get_url_lang_code_items_waardes() geeft een lijst met lang_codes: en, nl, es, en functie get_url_lang_code_default_item_value() retourneert en, dus Engels is de standaard taal. Dan registreer ik in __init__.py het huis blueprint:

    from .blueprints.home.views import home_blueprint
    app.register_blueprint(home_blueprint, url_prefix='/<lang_code>')

Wat gebeurt er als we een url zonder pad typen, of een totaal willekeurige url? U krijgt een foutmelding:

TypeError: homepage() got an unexpected keyword argument 'lang_code'

De Flask docs geven geen oplossing, maar na wat hoofdpijn (weer) denk ik dat ik een oplossing heb gevonden om dit op te lossen met de before_request handler. In deze handler kijk ik naar het verzoek url. De weg van deze url is in delen gesplitst. Het eerste deel moet onze taal zijn. Als het eerste deel in onze lijst van talen staat, ga dan gewoon door. Als de pagina niet gevonden Flask kan worden, genereert dit een 404, pagina niet gevonden, wat prima is. Als het eerste deel niet onze lijst met talen is, dan stuurt de before_request handler een redirect url terug naar de startpagina van de standaard taal.

Na het implementeren hiervan werd de website getoond zonder stijlen en kreeg ik vreemde foutmeldingen in de Developer tools console. De oplossing was om de statische directory uit te sluiten. Dus dit is wat er gebeurt in de before_request handler:

    @app.before_request
    def before_request():

        #  try to handle missing lang_code in url interceptor

        url_lang_code_items_values = get_url_lang_code_items_values()
        url_lang_code_default_item_value = get_url_lang_code_default_item_value()

        # check for a valid url = starts with /lang_code/
        request_path = request.path.strip('/')
        request_path_parts = urlparse(request_path).path.split('/')
        if request.method in ['GET'] and len(request_path_parts) > 0:
            request_path_part_0 = request_path_parts[0]

            # do nothing with static urls !!!
            if request_path_part_0 != 'static' and request_path_part_0 not in url_lang_code_items_values:
                # fucked up url
                redir_url_parts = []
                redir_url_parts.append( request.url_root.strip('/') )
                redir_url_parts.append( url_lang_code_default_item_value.strip('/') )
                redir_url = '/'.join(redir_url_parts)
                return redirect(redir_url)

Dit werkt, maar hoe veranderen we de taal?

In het base.html sjabloon heb ik een dropdown language selector:

	<li class="nav-item dropdown">
		<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
		{{ language_selected }}
		</a>
		<div class="dropdown-menu" aria-labelledby="navbarDropdown">
			<a class="dropdown-item" href="{{ request.path }}?lc=en_US">{{ _('EN') }}</a>
			<a class="dropdown-item" href="{{ request.path }}?lc=nl_NL">{{ _('NL') }}</a>
			<a class="dropdown-item" href="{{ request.path }}?lc=es_ES">{{ _('ES') }}</a>
		</div>
	</li>

Vervolgens activeren we de Babel taal localeselector wanneer de taal wordt veranderd:

    @babel.localeselector
    def get_locale():
        request_lc = request.args.get('lc')
        if not request_lc:
            if not 'lang_code' in g:
                # use default
                g.lang_code = 'en'
                request_lc = 'en_US'
            else:
                if g.lang_code == 'es':
                    request_lc = 'es_ES'
                elif g.lang_code == 'nl':
                    request_lc = 'nl_NL'
                else:
                    request_lc = 'en_US'

        else:
            # set g.lang_code to the requested language
            if request_lc == 'nl_NL':
               g.lang_code = 'nl'
            elif request_lc == 'es_ES':
               g.lang_code = 'es'
            else:
                request_lc = 'en_US'
                g.lang_code = 'en'
            #sys.exit()
        session['lc'] = request_lc
        return request_lc

Oké, het werkt nu maar moet geoptimaliseerd worden. Ook zijn er op dit moment nog twee problemen:

  • Wanneer de taal wordt gewijzigd, wordt dit achteraf niet in de url weergegeven.
    Je moet naar een nieuwe pagina gaan. Na het veranderen van de taal met de dropdown toont de url niet direct de taal in de url, alleen bij de volgende klik. Misschien moet ik de Babel code verplaatsen naar de before_request handler?
  • Veel oproepen naar url_defaults op elk gesprek
    dat ik graag de code zou willen hebben:
    @home_blueprint.url_defaults
    @home_blueprint.url_value_preprocessor
    in de __init__.py in plaats van in de blueprint views.py maar dit werkt niet.
    Waarom wil ik dit? Omdat ik niet graag dezelfde code dupliceer en ik zie veel gesprekken naar @auth_blueprint.url etc.. Ik denk dat ze één keer in __init__.py moeten worden gedaan, maar misschien heb ik het mis.

Links / credits

Flask Series: Internationalization
https://damyanon.net/post/flask-series-internationalization/

Flask-multilang-demo
https://github.com/DusanMadar/Flask-multilang-demo

How to Easily Create a Multilingual WordPress Site
https://www.wpbeginner.com/beginners-guide/how-to-easily-create-a-multilingual-wordpress-site/

Internationalized Application URLs
https://flask.palletsprojects.com/en/1.1.x/patterns/urlprocessors/

Multilingual flask application
https://stackoverflow.com/questions/3420897/multilingual-flask-application

Set languages and locales
https://docs.microsoft.com/en-us/windows-hardware/customize/mobile/mcsf/set-languages-and-locales

Laat een reactie achter

Reageer anoniem of log in om commentaar te geven.

Opmerkingen (1)

Laat een antwoord achter

Antwoord anoniem of log in om te antwoorden.

avatar

From where 'get_url_lang_code_items_values()' and 'get_url_lang_code_default_item_value()' come from?