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.
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
Leer más
Babel Flask Multilanguage
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.
From where 'get_url_lang_code_items_values()' and 'get_url_lang_code_default_item_value()' come from?
Recientes
- Cómo ocultar las claves primarias de la base de datos UUID de su aplicación web
- Don't Repeat Yourself (DRY) con Jinja2
- SQLAlchemy, PostgreSQL, número máximo de filas por user
- Mostrar los valores en filtros dinámicos SQLAlchemy
- Transferencia de datos segura con cifrado de Public Key y pyNaCl
- rqlite: una alternativa de alta disponibilidad y dist distribuida SQLite
Más vistos
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- Usando PyInstaller y Cython para crear un ejecutable de Python
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados
- Flask RESTful API validación de parámetros de solicitud con esquemas Marshmallow