Django proporciona mecanismos para la traducción de las aplicaciones, tanto de los textos de las vistas, los de los templates, los mostrados por código JavaScript y la meta información de los modelos pero esto no incluye el contenido en base de datos, para traducir dicho contenido utilizaremos el módulo django-modeltranslation. En este artículo mostraremos un ejemplo muy sencillo pero ilustrativo sobre como utilizar todos los recursos mencionados.
Antes de comenzar es recomendable leer los artículos anteriores sobre Django ya que son los pasos previos a este artículo:
- Django: Venv bajo FreeBSD
- Django: MVT, Apps y URLs
- Django: Modelos de base de datos
- Django: Interfaz de administración
- Django: DTL(Django Template Language)
- Django: Debug Toolbar
- Django: Registro y autenticación de usuarios
- Django: Webpack
- Django: Bootstrap mediante WebPack
- Django: Proyecto en producción
Como siempre activamos el venv del proyecto:
source bin/activate
cd rxWodProject/
Para soportar varios idiomas en Django debemos habilitar el middleware django.middleware.locale.LocaleMiddleware:
if PRODUCTION_ENABLED:
MIDDLEWARE = [
'django.middleware.common.BrokenLinkEmailsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
MANAGERS = [('kr0m', 'kr0m@alfaexploit.com')]
import re
IGNORABLE_404_URLS = [
re.compile(r'\.(php|cgi)$'),
re.compile(r'^/phpmyadmin/'),
]
else:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Cambiamos el lenguaje por defecto a inglés de este modo cuando no encuentre traducciones para un idioma por defecto mostrará los textos en inglés:
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
Django determina el idioma a utilizar mediante las siguientes comprobaciones:
- Prefijo en la URL, para que esto funcione debemos habilitar en la configuración de URLs de Django los i18n_patterns
- Existencia de la Cookie: django_language
- Cabecera HTTP Accept-Language
- Si todo lo anterior falla utiliza el lenguaje por defecto: LANGUAGE_CODE
Para realizar las pruebas de forma mas cómoda es recomendable instalar algún addon de cambio de idioma en el navegador.
Como ejemplo muy sencillo vamos a traducir un message y un texto desde la vista index, para ello necesitamos importar gettext e indicar lo que deseamos traducir con _
from django.utils.translation import gettext as _
from django.shortcuts import get_object_or_404, get_list_or_404, render, redirect
from django.http import HttpResponse
from .models import Routine
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.utils.translation import gettext as _
@login_required()
def index(request):
#print('username: %s' % request.user.username)
routines = Routine.objects.filter(user=request.user)
messages.success(request, _('Message sent by index view'))
text = _('Text from view')
context = {
'routines': routines,
'text': text,
}
return render(request, 'rxWod/index.html', context)
En el template index vamos a traducir otro texto de prueba, para ello cargamos la internacionalización:
{% load i18n %}
Y utilizamos el tag translate:
{% translate "Text translated in template" %}
Quedaría del siguiente modo:
{% extends 'rxWod/base.html' %}
{% load render_bundle from webpack_loader %}
{% load webpack_static from webpack_loader %}
{% load i18n %}
{% block base_head %}
{% render_bundle 'index' 'css'%}
{% endblock %}
{% block base_body %}
{% translate "Text translated in template" %}
{% if messages %}
{% for message in messages %}
<p>{{ message }}</p>
{% endfor%}
{% endif %}
{% if text %}
<p">{{ text }}</p>
{% endif %}
{% if routines %}
{% for routine in routines %}
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-3 p-2 mb-2">
<div class="card text-center text-white bg-dark border border-primary rounded">
<div class="card text-center text-white bg-dark border border-primary rounded">
<div class="card-header">
<img class="text-center" src="{% webpack_static 'django-logo-positive.png' %}" width="150" height="90" alt="rxWod">
</div>
<div class="card-body">
<h5 class="card-title">{{ routine.date }}</h5>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p>No routines available.</p>
{% endif %}
{% render_bundle 'index' 'js'%}
{% endblock %}
La metainformación de la base de datos también se puede traducir, tan solo debemos importar gettext e indicar la traducción con _
from django.utils.translation import gettext_lazy as _
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator, MinValueValidator, validate_comma_separated_integer_list, MinLengthValidator, MaxLengthValidator
from django.utils.translation import gettext_lazy as _
# Exercise
class Exercise(models.Model):
exercise_id = models.IntegerField(default=-1, unique=True, null=False, blank=False)
name = models.CharField(max_length=200, unique=False, null=False, blank=False)
description = models.CharField(max_length=200, unique=False, null=False, blank=False)
default_value = models.IntegerField(default=1, validators=[MinValueValidator(1)])
EXERCISE_CATEGORY = [
(0, _('Shoulders')),
(1, _('Back')),
(2, _('Biceps')),
(3, _('Triceps')),
(4, _('Chest')),
(5, _('Core')),
(6, _('Gluteus')),
(7, _('Quadriceps')),
(8, _('Hamstring')),
(9, _('Cardio')),
(10, _('Lumbar')),
(11, _('Grip')),
]
category_id = models.PositiveSmallIntegerField(default=1, choices=EXERCISE_CATEGORY, null=False, blank=False)
EXERCISE_LEVEL = [
(0, 'N1'),
(1, 'N2'),
(2, 'RX'),
(3, 'RX+'),
]
level = models.PositiveSmallIntegerField(default=0, choices=EXERCISE_LEVEL, null=False, blank=False)
url = models.URLField()
is_metabolic = models.BooleanField(default=False, null=False, blank=False)
def __str__(self):
return_value = str(self.name) + ' [' + str(self.get_category_id_display()) + '] [' + str(self.get_level_display() + ']')
return return_value
# Routine
class Routine(models.Model):
date = models.DateTimeField(auto_now_add=True)
# Each routine has 10 exercises separated by ,: 1,2,3,4,5,6,7,8,9,10
exercise_ids = models.CharField(validators=[validate_comma_separated_integer_list], max_length=512, blank=True, null=False)
exercise_repetitions = models.CharField(validators=[validate_comma_separated_integer_list], max_length=512, blank=True, null=False)
rounds = models.IntegerField(default=1, validators=[MinValueValidator(1), MaxValueValidator(4)])
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False)
# There are 10 exercise categories, percentages are separated by ,: 1,2,3,4,5,6,7,8,9,10
percentages = models.CharField(validators=[validate_comma_separated_integer_list], max_length=512, blank=False, null=False)
level = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])
def __str__(self):
return str(self.date)
Generamos el directorio donde se almacenarán los ficheros de traducción:
mkdir rxWod/locale
Instalamos gettext si no lo tenemos ya instalado:
Generamos los ficheros de traduccions:
django-admin makemessages -l es
processing locale es
Dejamos las traducciones preparadas en el fichero django.po
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-20 13:19+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:14
msgid "Shoulders"
msgstr "Hombros"
#: models.py:15
msgid "Back"
msgstr "Espalda"
#: models.py:16
msgid "Biceps"
msgstr "Biceps"
#: models.py:17
msgid "Triceps"
msgstr "Triceps"
#: models.py:18
msgid "Chest"
msgstr "Pecho"
#: models.py:19
msgid "Core"
msgstr "Core"
#: models.py:20
msgid "Gluteus"
msgstr "Glúteos"
#: models.py:21
msgid "Quadriceps"
msgstr "Cuadriceps"
#: models.py:22
msgid "Hamstring"
msgstr "Isquiotibial"
#: models.py:23
msgid "Cardio"
msgstr "Cardio"
#: models.py:24
msgid "Lumbar"
msgstr "Lumbar"
#: models.py:25
msgid "Grip"
msgstr "Agarre"
#: templates/rxWod/index.html:13
msgid "Text translated in template"
msgstr "Texto traducido en el template"
#: views.py:12
msgid "Message sent by index view"
msgstr "Mensaje enviado desde la vista index"
#: views.py:13
msgid "Text from view"
msgstr "Texto desde la vista"
Compilamos las traducciones:
processing file django.po in /usr/home/kr0m/rxWod/rxWodProject/rxWod/locale/es/LC_MESSAGES
La traducción de textos generados por código JavaScript resulta un poco mas complicada ya que este se ejecuta en el navegador del cliente y no tiene acceso a gettext por lo tanto las traducciones deben ser solicitadas al servidor.
Django resuelve el problema mediante una librería JS y una vista especial que imita el comportamiento de la interfaz gettext, de este modo el cliente podrá obtener los textos traducidos mediante dicha librería.
Esto puede suponer un problema de rendimiento por lo tanto debemos tener varios aspectos en cuenta:
- Los catálogos de traducción deben de ser lo mas pequeños posible.
- Si la carga es considerable debemos cachear el contenido de las traducciones .
- Si la carga es extrema se pueden incluso precomputar los catálogos y servirlos como si se tratase de ficheros estáticos.
Generamos el directorio locale en el directorio raíz de nuestro proyecto, es necesario crearlo en esta localización porque es donde residen nuestros assets JS, cuando se ejecute el comando makemessages o compilemessages se generarán los ficheros en este nivel del árbol de directorios y no dentro de cada app.
mkdir locale
Para generar la vista especial debemos definir una URL para tal fin:
# JS translations
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
import debug_toolbar
from django.contrib import admin
from django.conf import settings
from django.urls import include, path
from django.views.i18n import JavaScriptCatalog
urlpatterns = [
# JS translations
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
# django.contrib.auth URLs
path('accounts/', include('django.contrib.auth.urls')),
path('', include('rxWod.urls')),
path('', include('usersAuth.urls')),
path('admin/', admin.site.urls),
path('__debug__/', include(debug_toolbar.urls)),
]
Importamos la librería JS en nuestro template mediante un import clásico.
<script src="{% url 'javascript-catalog' %}"></script>
{% extends 'rxWod/base.html' %}
{% load render_bundle from webpack_loader %}
{% load webpack_static from webpack_loader %}
{% load i18n %}
{% block base_head %}
{% render_bundle 'index' 'css'%}
{% endblock %}
{% block base_body %}
<script src="{% url 'javascript-catalog' %}"></script>
{% translate "Text translated in template" %}
{% if messages %}
{% for message in messages %}
<p>{{ message }}</p>
{% endfor%}
{% endif %}
{% if text %}
<p">{{ text }}</p>
{% endif %}
{% if routines %}
{% for routine in routines %}
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-3 p-2 mb-2">
<div class="card text-center text-white bg-dark border border-primary rounded">
<div class="card text-center text-white bg-dark border border-primary rounded">
<div class="card-header">
<img class="text-center" src="{% webpack_static 'django-logo-positive.png' %}" width="150" height="90" alt="rxWod">
</div>
<div class="card-body">
<h5 class="card-title">{{ routine.date }}</h5>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p>No routines available.</p>
{% endif %}
{% render_bundle 'index' 'js'%}
{% endblock %}
Ahora ya podemos hacer uso de ella mediante la función gettext:
gettext('text to translate')
import img from '../images/django-logo-positive.png';
// ------ OnLoad checks ------
window.onload = function() {
// Basic checks
console.log('----- window.onload -----');
if (typeof $.fn.popover == 'function') {
console.log('BootStrap working correctly');
} else {
console.log('BootStrap NOT working correctly');
}
if (typeof jQuery != 'undefined') {
// jQuery is loaded => print the version
console.log('JQuery version: ' + jQuery.fn.jquery);
}
};
alert(gettext('text to translate'));
console.log('Inside index. Edit me in assets/js/index.js');
Para generar los catálogos JS debemos generar los ficheros con el comando make messages pero debemos indicarle que ignore los JS instalados mediante yarn:
processing locale es
Dejamos las traducciones JS preparadas en el fichero djangojs.po
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-20 09:34+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: assets/js/index.js:20
msgid "text to translate"
msgstr "texto a traducir"
Compilamos las traducciones:
processing file djangojs.po in /usr/home/kr0m/rxWod/rxWodProject/locale/es/LC_MESSAGES
processing file django.po in /usr/home/kr0m/rxWod/rxWodProject/rxWod/locale/es/LC_MESSAGES
Las traducciones de los ficheros JS se habrán generado en el directorio locale/es/LC_MESSAGES/ pero cada app las necesita en su propio directorio así que las copiamos a cada una de ellas(en este ejemplo solo rxWod):
#cp locale/es/LC_MESSAGES/djangojs.mo usersAuth/locale/es/LC_MESSAGES/djangojs.mo
Al ejecutar el comando makemessages puede que se detecte una traducción similar a una ya existente, si esto ocurre en el fichero .po generado veremos un comentario #, fuzzy, si lo dejamos como fuzzy el texto NO se traducirá, si el texto debe traducirse debemos eliminar el comentario.
#: models.py:19
#, fuzzy
msgid "Quadriceps"
msgstr "Cuadriceps"
Como resumen los comandos a ejecutar para generar todas las traducciones del proyecto son los siguientes:
cd rxWod
django-admin makemessages -l es
cd ..
#cd usersAuth
#django-admin makemessages -l es
#cd ..
django-admin compilemessages
cp locale/es/LC_MESSAGES/djangojs.mo rxWod/locale/es/LC_MESSAGES/djangojs.mo
#cp locale/es/LC_MESSAGES/djangojs.mo usersAuth/locale/es/LC_MESSAGES/djangojs.mo
En el
artículo anterior
programamos un pequeño script para desplegar cambios en producción de forma mas fácil y sencilla, añadimos la parte de compilación de traducciones:
#!/usr/local/bin/bash
cd /home/kr0m/rxWod/rxWodProject
git pull
cd ..
source bin/activate
pip install -r rxWodProject/requirements.txt
cd rxWodProject
python manage.py makemigrations
python manage.py migrate
django-admin makemessages -l es -d djangojs -i node_modules
cd rxWod
django-admin makemessages -l es
cd ..
#cd usersAuth
#django-admin makemessages -l es
#cd ..
django-admin compilemessages
cp locale/es/LC_MESSAGES/djangojs.mo rxWod/locale/es/LC_MESSAGES/djangojs.mo
#cp locale/es/LC_MESSAGES/djangojs.mo usersAuth/locale/es/LC_MESSAGES/djangojs.mo
yarn install
yarn prod-build
python manage.py collectstatic --noinput
sudo /usr/sbin/service daphne restart
En la documentación de Django podemos encontrar una sección de troubleshooting acerca de las traducciones , esta resulta muy útil en caso de problemas inesperados.
Compilamos los bundles de WebPack y arrancamos el servidor integrado:
Comprobamos que las traducciones funcionen accediendo a:
http://localhost:8000/
Primero vemos en el JS que el texto aparece en inglés, ya que el navegador está configurado en inglés.
El resto de la web también aparece en inglés:
Por otro lado si cambiamos el idioma veremos los textos traducidos:
El resto de la web también:
NOTA: Podemos ver que Django de forma automática ha cambiado el formato de fecha sin que nosotros hayamos indicado nada al respecto.
Django es capaz de traducir los plurales de forma automática, para ello tan solo debemos indicar las posibles opciones mediante ngettext:
count_text = ngettext(
'there is %(count)d object',
'there are %(count)d objects',
count,
) % {
'count': count,
}
Modificamos la vista index para que haga uso de ngetttext:
from django.shortcuts import get_object_or_404, get_list_or_404, render, redirect
from django.http import HttpResponse
from .models import Routine
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
@login_required()
def index(request):
#print('username: %s' % request.user.username)
routines = Routine.objects.filter(user=request.user)
messages.success(request, _('Message sent by index view'))
text = _('Text from view')
count = int(request.GET['count'])
count_text = ngettext(
'there is %(count)d object',
'there are %(count)d objects',
count,
) % {
'count': count,
}
context = {
'routines': routines,
'text': text,
'count_text': count_text,
}
return render(request, 'rxWod/index.html', context)
Modificamos el template asociado a la vista para que muestre el texto pasado vía contexto:
{% extends 'rxWod/base.html' %}
{% load render_bundle from webpack_loader %}
{% load webpack_static from webpack_loader %}
{% load i18n %}
{% block base_head %}
{% render_bundle 'index' 'css'%}
{% endblock %}
{% block base_body %}
<script src="{% url 'javascript-catalog' %}"></script>
{% if count_text %}
<p">{{ count_text }}</p>
{% endif %}
{% translate "Text translated in template" %}
{% if messages %}
{% for message in messages %}
<p>{{ message }}</p>
{% endfor%}
{% endif %}
{% if text %}
<p">{{ text }}</p>
{% endif %}
{% if routines %}
{% for routine in routines %}
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-3 p-2 mb-2">
<div class="card text-center text-white bg-dark border border-primary rounded">
<div class="card text-center text-white bg-dark border border-primary rounded">
<div class="card-header">
<img class="text-center" src="{% webpack_static 'django-logo-positive.png' %}" width="150" height="90" alt="rxWod">
</div>
<div class="card-body">
<h5 class="card-title">{{ routine.date }}</h5>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p>No routines available.</p>
{% endif %}
{% render_bundle 'index' 'js'%}
{% endblock %}
Generamos las traducciones:
django-admin makemessages -l es
Traducimos las nuevas traducciones:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-20 18:24+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: models.py:14
msgid "Shoulders"
msgstr "Hombros"
#: models.py:15
msgid "Back"
msgstr "Espalda"
#: models.py:16
msgid "Biceps"
msgstr "Biceps"
#: models.py:17
msgid "Triceps"
msgstr "Triceps"
#: models.py:18
msgid "Chest"
msgstr "Pecho"
#: models.py:19
msgid "Core"
msgstr "Core"
#: models.py:20
msgid "Gluteus"
msgstr "Glúteos"
#: models.py:21
msgid "Quadriceps"
msgstr "Cuadriceps"
#: models.py:22
msgid "Hamstring"
msgstr "Isquiotibial"
#: models.py:23
msgid "Cardio"
msgstr "Cardio"
#: models.py:24
msgid "Lumbar"
msgstr "Lumbar"
#: models.py:25
msgid "Grip"
msgstr "Agarre"
#: templates/rxWod/index.html:17
msgid "Text translated in template"
msgstr "Texto traducido en el template"
#: views.py:12
msgid "Message sent by index view"
msgstr "Mensaje enviado desde la vista index"
#: views.py:13
msgid "Text from view"
msgstr "Texto desde la vista"
#: views.py:17
#, python-format
msgid "there is %(count)d object"
msgid_plural "there are %(count)d objects"
msgstr[0] "hay %(count)d objeto"
msgstr[1] "hay %(count)d objetos"
Compilamos las traducciones:
django-admin compilemessages
Compilamos los bundles de WebPack y arrancamos el servidor de pruebas:
Ahora si accedemos al index pasándole el parámetro count vía GET, si es singular aparecerá en singular, si es plural en plural:
En cambio si count vale mas de uno:
Por supuesto en inglés se comporta igual:
En cuanto a la base de datos estamos traduciendo metainformación pero todavía queda el contenido de la propia base de datos, para traducirlo tendremos que instalar uno de los módulo disponibles .
Yo personalmente he optado por django-modeltranslation .
Instalamos el módulo y congelamos las dependencias:
pip install django-modeltranslation
pip freeze > requirements.txt
Añadimos la app modeltranslation:
INSTALLED_APPS = [
'modeltranslation',
...
]
E indicamos los lenguajes que vamos a soportar, el primero será el idioma por defecto:
gettext = lambda s: s
LANGUAGES = (
('en', gettext('English')),
('es', gettext('Spanish')),
)
Indicamos los campos del modelo a traducir, en mi caso name y description:
from modeltranslation.translator import translator, TranslationOptions
from .models import Exercise
class ExerciseTranslationOptions(TranslationOptions):
fields = ('name', 'description')
translator.register(Exercise, ExerciseTranslationOptions)
Sincronizamos la base de datos para que genere los campos adicionales:
Missing languages in "name" field from "rxWod.exercise" model: en, es
SQL to synchronize "rxWod.exercise" schema:
ALTER TABLE "rxWod_exercise" ADD COLUMN "name_en" varchar(200);
ALTER TABLE "rxWod_exercise" ADD COLUMN "name_es" varchar(200);
Are you sure that you want to execute the previous SQL: (y/n) [n]: y
Executing SQL...
Done
Missing languages in "description" field from "rxWod.exercise" model: en, es
SQL to synchronize "rxWod.exercise" schema:
ALTER TABLE "rxWod_exercise" ADD COLUMN "description_en" varchar(200);
ALTER TABLE "rxWod_exercise" ADD COLUMN "description_es" varchar(200);
Are you sure that you want to execute the previous SQL: (y/n) [n]: y
Executing SQL...
Done
NOTA: Debemos ejecutar este comando cada vez que se añada un lenguaje nuevo al parámetro settings.LANGUAGES y cada que se añada un campo nuevo a traducir en rxWod/translation.py
Comprobamos que los nuevos campos existan en la base de datos:
su - postgres
psql
\c rxwod
\d "rxWod_exercise"
Table "public.rxWod_exercise"
Column | Type | Collation | Nullable | Default
----------------+------------------------+-----------+----------+----------------------------------------------
id | integer | | not null | nextval('"rxWod_exercise_id_seq"'::regclass)
exercise_id | integer | | not null |
name | character varying(200) | | not null |
description | character varying(200) | | not null |
default_value | integer | | not null |
category_id | smallint | | not null |
level | smallint | | not null |
url | character varying(200) | | not null |
is_metabolic | boolean | | not null |
name_en | character varying(200) | | |
name_es | character varying(200) | | |
description_en | character varying(200) | | |
description_es | character varying(200) | | |
Indexes:
"rxWod_exercise_pkey" PRIMARY KEY, btree (id)
"rxWod_exercise_exercise_id_key" UNIQUE CONSTRAINT, btree (exercise_id)
Check constraints:
"rxWod_exercise_category_id_check" CHECK (category_id >= 0)
"rxWod_exercise_level_check" CHECK (level >= 0)
Podemos ver que los campos adicionales están vacíos:
SELECT * FROM "rxWod_exercise" LIMIT 1;
id | exercise_id | name | description | default_value | category_id | level | url | is_metabolic | name_en | name_es | description_en | description_es
----+-------------+-----------+-------------+---------------+-------------+-------+------------------------+--------------+---------+---------+----------------+----------------
1 | 0 | test ex |test ex desc | 1 | 4 | 0 | http://alfaexploit.com | f | | | |
(1 row)
Para poblar los campos adicionales podemos hacerlo manualmente o copiar los campos originales a las traducciones .
El siguiente comando nos copiará los datos al idioma por defecto, en este caso EN:
Using default language: en
Working on models: rxWod.Exercise
Updating data of model '<class 'rxWod.models.Exercise'>'
Comprobamos que haya copiado los datos:
SELECT * FROM "rxWod_exercise" LIMIT 1;
id | exercise_id | name | description | default_value | category_id | level | url | is_metabolic | name_en | name_es | description_en | description_es
----+-------------+-----------+-------------+---------------+-------------+-------+------------------------+--------------+-----------+---------+----------------+----------------
1 | 0 | test ex |test ex desc | 1 | 4 | 0 | http://alfaexploit.com | f | test ex | | test ex desc |
(1 row)
Si además queremos copiarlo a otro idioma podemos indicarlo:
Using default language: en
Working on models: rxWod.Exercise
Updating data of model '<class 'rxWod.models.Exercise'>'
Comprobamos que haya copiado los datos:
SELECT * FROM "rxWod_exercise" LIMIT 1;
id | exercise_id | name | description | default_value | category_id | level | url | is_metabolic | name_en | name_es | description_en | description_es
----+-------------+-----------+-------------+---------------+-------------+-------+------------------------+--------------+-----------+-----------+----------------+----------------
1 | 0 | test ex |test ex desc | 1 | 4 | 0 | http://alfaexploit.com | f | test ex | test ex | test ex desc | test ex desc
(1 row)
Cada vez que introduzcamos un ejercicio nuevo debemos copiar los datos al idioma por defecto:
Y rellenar la traducción a español del ejercicio correspondiente desde la interfaz de admin:
Los campos que provienen del modelo y se muestran en la interfaz de admin como puede ser la categoria también aparecen traducidos según las preferencias de idioma del navegador:
Modificamos la vista index para que consulte los ejercicios:
from django.shortcuts import get_object_or_404, get_list_or_404, render, redirect
from django.http import HttpResponse
from .models import Exercise
from django.contrib.auth.decorators import login_required
from django.contrib import messages
@login_required()
def index(request):
exercises = Exercise.objects.all()
context = {
'exercises': exercises,
}
return render(request, 'rxWod/index.html', context)
Modificamos el template para que muestre los ejercicios:
{% extends 'rxWod/base.html' %}
{% load render_bundle from webpack_loader %}
{% load webpack_static from webpack_loader %}
{% block base_head %}
{% render_bundle 'index' 'css'%}
{% endblock %}
{% block base_body %}
{% if exercises %}
{% for exercise in exercises %}
<div class="col-xs-12 col-sm-12 col-md-6 col-lg-3 p-2 mb-2">
<div class="card text-center text-white bg-dark border border-primary rounded">
<div class="card text-center text-white bg-dark border border-primary rounded">
<div class="card-header">
<img class="text-center" src="{% webpack_static 'django-logo-positive.png' %}" width="150" height="90" alt="rxWod">
</div>
<div class="card-body">
<h5 class="card-title">{{ exercise.name }}</h5>
<h3>{{ exercise.description }}</h3>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p>No routines available.</p>
{% endif %}
{% endblock %}
Ahora cuando accedemos a la web con el navegador configurado en inglés veremos el contenido proveniente de la base de datos traducido:
En cambio si accedemos en español lo veremos en español: