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

Construyendo un sitio web multilingüe Flask con FlaskBabel

No existen muchos ejemplos para el multilenguaje Flask. Aquí seguimos las sugerencias de la Flask documentación.

7 agosto 2019
En Babel
post main image
Original photo unsplash.com/@jantined.

Con un solo idioma no hay realmente un problema, simplemente nos olvidamos del resto del mundo y construimos nuestra aplicación en un solo idioma Flask . Comenzamos a tener dolores de cabeza cuando el sitio web debe soportar varios idiomas. ¿Qué es exactamente un sitio web que soporta múltiples idiomas? ¿Cuántos idiomas se admitirán y en qué idiomas? Para el inglés existen, por ejemplo, en-GB y en-US. ¿Qué partes del sitio web deben estar disponibles en todos los idiomas? ¿Cuáles son las partes de un sitio web? Me limitaré a responder las preguntas más triviales. Para una buena introducción también puede consultar, por ejemplo, la guía de plugins de Wordpress Polylang, ver más abajo.

Cuando se construye un sitio web sin soportar múltiples idiomas se utilizan urls para el contenido, cuando se desea añadir más idiomas se deben mantener todas las urls, es decir, lo que ya se ha creado. Desde el punto de vista de SEO es mejor empezar con todos los componentes multilingües en su lugar, incluso si usted duda ahora si alguna vez agregará otro idioma.

Selección de idioma

Cuando el idioma está en una cookie (sesión) la correcta visualización del idioma puede ser un problema cuando las cookies están deshabilitadas. Con el idioma en el dominio / url este problema se elimina y también el sitio web es más amigable con el SEO.

Opción 1: idioma en la extensión del dominio

example.com
example.es
example.pl

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

Opción 2: idioma en el subdominio

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

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

Opción 3: idioma en la ruta de la url

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

URLs traducidas

Las urls traducidas son muy amigables, pero también introducen más complejidad:

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

Algunos sitios web no tienen urls traducidos:

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

Si queremos tener urls traducidas, y por qué no, también necesitamos múltiples puntos finales para una función de visualización:

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

Por supuesto que queremos que esto sea de alguna manera automatizado.... difícil.

Tomar decisiones

Por el momento me concentro en un sitio web con el `identificador de idioma' en la ruta de la url, ver arriba opción 3, y no trataré con urls traducidas. Porque estamos usando Flask, usamos, usamos Flask,Babel para nuestras traducciones. Algunos textos, por ejemplo, blogs, categorías, etiquetas, están en la base de datos. Nos ocuparemos de esto más tarde.

Es posible que desee un sitio web con el idioma principal sin el `identificador de idioma' en la ruta de la url y los demás idiomas con el `identificador de idioma' en la ruta de la url. Realmente no sé cómo hacer esto en Flask este momento y también creo que esto complica mucho las cosas. Resumen: No creo que sea una mala idea empezar con el idioma en la ruta de la url.

En una primera implementación utilicé una cookie para la selección del idioma, ahora debo eliminarla y utilizar el idioma en la url. Afortunadamente, la Flask documentación proporciona buena información sobre la internacionalización. Te sugiero que leas esto (lo hice muchas veces). Tengo muchos blueprints, para la página de inicio view.py añado lo siguiente a la vista:

...
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
...

La función get_url_lang_code_items_values() devuelve una lista de códigos lang: en, nl, es, y la función get_url_lang_code_default_item_value() devuelve en, por lo que el inglés es el idioma por defecto. Luego en __init__.py registro la vivienda blueprint:

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

¿Qué pasa si escribimos una url sin ruta, o una url totalmente aleatoria? Aparecerá un mensaje de error:

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

Los Flask doctores no dan una solución, pero después de algunos dolores de cabeza (otra vez) creo que encontré una solución para resolver esto usando el handler before_request. En este handler miro la url de la petición. La ruta de esta url está dividida en partes. La primera parte debe ser nuestro idioma. Si la primera parte está en nuestra lista de idiomas, simplemente continúe. Si la página no puede ser encontrada Flask generará un 404, página no encontrada, lo que está bien. Si la primera parte no es nuestra lista de idiomas, entonces el manejador before_request devuelve una url de redirección a la página principal del idioma por defecto.

Después de implementar esto, el sitio web se mostró sin estilos y recibí mensajes de error extraños en la consola de herramientas para desarrolladores. La solución era excluir el directorio estático. Así que esto es lo que está pasando en el handler before_request:

    @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)

Esto funciona, pero ¿cómo cambiamos el idioma?

En la plantilla base.html tengo un selector de idioma desplegable:

	<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>

A continuación, activamos el Babel idioma localeselector cuando se cambia el idioma:

    @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, está funcionando ahora pero debe ser optimizado. También hay dos problemas en este momento:

  • Cuando se cambia el idioma, esto no se muestra en la url después.
    Tienes que ir a una nueva página. Después de cambiar el idioma con el menú desplegable, la url no muestra el idioma en la url inmediatamente, sólo en el siguiente clic. Tal vez debería mover el Babel código al handler before_request?
  • Muchas llamadas a url_defaults en cada llamada
    me gustaría tener el código:
    @home_blueprint.url_defaults @home_
    blueprint.url_value_preprocessor
    en el __init__.py en lugar de en el blueprint views.py pero esto no funciona.
    ¿Por qué quiero esto? Porque no me gusta duplicar el mismo código y veo muchas llamadas a @auth_blueprint.url, etc. Creo que deberían hacerse una vez en __init__.py pero tal vez me equivoque.

Enlaces / créditos

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

Deje un comentario

Comente de forma anónima o inicie sesión para comentar.

Comentarios (1)

Deje una respuesta.

Responda de forma anónima o inicie sesión para responder.

avatar

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