Internacionalización
Introducción
Las traducciones representan un elemento esencial de cualquier aplicación si desea adaptar sus páginas a diferentes idiomas sin tener que reescribirlas por completo.
El principio de la traducción
Hay tres elementos esenciales a la hora de implementar una traducción en Symfony:
-
los elementos que se han de traducir,
-
el idioma local al que deben realizarse las traducciones,
-
los diccionarios de traducción.
No hay un servicio de traducción propiamente dicho en Symfony.
Symfony simplemente transfiere una cadena de caracteres de un idioma a otro según la variable local y el diccionario apropiado.
Tenga en cuenta que la capitalización (mayúsculas o minúsculas) es importante en los catálogos de traducción.
La desventaja de este método es que usted debe escribir todos los catálogos de traducción. Esto puede resultar muy tedioso si tiene muchas páginas para traducir. Si Symfony no encuentra una coincidencia en el catálogo del idioma elegido con el texto que se ha de traducir, conservará el texto original.
Lo primero es asegurarse del idioma elegido para la traducción. Este idioma está definido por la variable local.
La variable local
La variable de entorno local predeterminada se define en el archivo de configuración config/packages/translation.yaml:
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- en
Aquí, de forma predeterminada, la local es «en». Se utilizará el inglés. Los diferentes códigos de idioma están definidos por la norma ISO 639. Se puede encontrar sus valores en el sitio de Wikipedia: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
Esta variable local puede cambiarse dinámicamente en las rutas.
Imaginemos que desea utilizar rutas diferentes para cada idioma; puede definir el idioma utilizado para la página colocándolo como parámetro en la ruta.
Sintaxis:
#[Route("/idioma/{_locale}/"...
El parámetro _locale determina la variable local utilizada en la ruta. El valor se asignará automáticamente a la variable local de su aplicación.
Tomemos un ejemplo. En el controlador TestController que creamos al principio de los capítulos sobre Symfony, creemos una nueva acción con una ruta que contenga la variable...
Los catálogos de traducción
Los catálogos de traducción se definen en la carpeta /translations. Si instala un paquete específico (en forma de bundle), podrá definir los catálogos de traducción en la carpeta Ressources/translations dentro del bundle que ha creado (todo esto en la carpeta vendor).
Cada catálogo de traducción tiene un nombre particular. Todos los nombres de catálogos siguen la siguiente sintaxis: domain.locale.loader.
El significado de esta sintaxis es el siguiente.
-
domain: una forma opcional de organizar los mensajes en grupos. A menos que las partes de la aplicación estén explícitamente separadas entre sí, se recomienda usar solo el nombre de dominio predeterminado de los mensajes: messages.
-
Locale: el idioma local utilizado por el catálogo.
-
Loader: el tipo de extensión utilizado para el catálogo. Aunque es posible definir catálogos en formato php o xml, se recomienda utilizar el formato YAML.
Por ejemplo, crearemos un catálogo en español para la local «es». Cree, en /translations, el archivo messages.es.yaml (usaremos el dominio estándar: messages).
Dentro de él, definiremos una traducción para el texto «Welcome to Symfony», por ejemplo.
El contenido del archivo translations/messages.es.yaml se verá así: etiqueta:valor.
En nuestro caso...
Los elementos para traducir
¿Cómo indicarle a Symfony lo que debe traducir?
Es posible, por ejemplo, realizar traducciones en un controlador: simplemente, inyecte el servicio $translator de la interfaz TranslatorInterface en el método deseado.
Sintaxis:
use Symfony\Contracts\Translation\TranslatorInterface;
public function methode(TranslatorInterface $translator)
{
...$translator->trans('texto para traducir');
}
Veamos un ejemplo. Dentro del método idioma() en el controlador TestController, copie estas líneas:
use Symfony\Contracts\Translation\TranslatorInterface;
...
#[Route("/idioma/{_locale}", name:"idioma", requirements:
["_locale"=> "en|fr|es"])]
public function idioma(Request $request,
TranslatorInterface $translator)
{
$textoTraducido = $translator->trans('Welcome to Symfony');
return new Response( $textoTraducido);
}
Si ejecuta la consulta localhost:8000/idioma/es, accederá al texto traducido:
![images/RI19_01.png](https://www.eni-training.com/download/3344c6bd-83e8-47fb-bff9-51328b4bd484/images/RI19_01.png?id=AAEAAAD%2f%2f%2f%2f%2fAQAAAAAAAAAMAgAAAE1FbmkuRWRpdGlvbnMuTWVkaWFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VuaS5FZGl0aW9ucy5NZWRpYXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAAwdGVzdCB2Ml84IC0gZmZhNjQzOWQtZDZjYi00NzYxLWJhYTMtZWZkZDNiZDcwMWM25rSq0F7p2IgL)
También es posible realizar traducciones en una plantilla (una vista).
Sintaxis:...
Las variables en las traducciones
Los catálogos de traducción también pueden tomar en cuenta variables en las traducciones.
Modifiquemos nuestro catálogo messages.es.yaml de la siguiente manera:
Welcome to Symfony: Bienvenido a Symfony
say_hello: Hola %name%:
%name% hace referencia a un parámetro cuyo valor será transmitido dinámicamente por un controlador o una vista.
Por ejemplo, en nuestra plantilla templates:test/index.html, usemos el texto say_hello pasándole un parámetro:
<h1>{% trans %}Welcome to Symfony{% endtrans %}</h1>
<h2>{{ 'say_hello'|trans({'%name%': 'amigo'}) }}</h2>
Esta vez, la solicitud da como resultado lo siguiente:
![images/RI19_03.png](https://www.eni-training.com/download/3344c6bd-83e8-47fb-bff9-51328b4bd484/images/RI19_03.png?id=AAEAAAD%2f%2f%2f%2f%2fAQAAAAAAAAAMAgAAAE1FbmkuRWRpdGlvbnMuTWVkaWFwbHVzLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJ0VuaS5FZGl0aW9ucy5NZWRpYXBsdXMuQ29tbW9uLldhdGVybWFyawIAAAAHcGlzVGV4dAlwaWR0ZURhdGUBAA0CAAAABgMAAAAwdGVzdCB2Ml84IC0gZmZhNjQzOWQtZDZjYi00NzYxLWJhYTMtZWZkZDNiZDcwMWM25rSq0F7p2IgL)
La ayuda para actualizar los catálogos
La tarea más larga al traducir una aplicación consiste en extraer todo el contenido del modelo para traducir y sincronizar todos los archivos de traducción. Symfony incluye un comando llamado translation:update que le ayuda en estas tareas laboriosas.
Por ejemplo, puede recuperar todos los mensajes de su sitio que aún no se han traducido en los catálogos con el comando:
php bin/console translation:extract --dump-messages es
Puede actualizar los archivos de traducción franceses con todas las traducciones pendientes en la aplicación. Las traducciones que faltan se reemplazarán por el texto de la cadena precedido de __ (ejemplo: Welcome to Symfony: __ Welcome to Symfony si esta cadena no está traducida).
php bin/console translation:extract --force es
La organización de los catálogos
Para poder orientarnos mejor en nuestros catálogos, que pueden ser muy extensos, tenemos la opción de estructurarlos.
Una de las prácticas es usar palabras clave en lugar de texto para las cadenas de origen que se deben traducir.
Por ejemplo, en lugar de traducir:
Welcome to Symfony: Bienvenido a Symfony
utilizaremos una abreviatura del tipo:
test.titulo: Bienvenido a Symfony
test es el nombre de la página en la que se debe realizar la traducción y titulo es el título de la traducción.
Por ejemplo, en la plantilla test/index.html.twig, usaremos este texto:
<h1>{% trans %}test.titulo{% endtrans %}</h1>
Esto puede simplificar en gran manera sus plantillas y sus catálogos, especialmente si el texto que se ha de traducir es extenso, como un párrafo entero, por ejemplo.
Una extensión de esta posibilidad es construir subniveles en el catálogo.
Será más claro y fácil gestionar catálogos organizados por páginas, por ejemplo.
Puede construir su catálogo messages.es.yaml de esta manera:
test:
titulo:
bienvenido: Bienvenido a Symfony
autor:
Hola: Hola {name} ...
La gestión del plural
Puede que necesitemos más flexibilidad en las traducciones, especialmente para la gestión de los plurales.
Imaginemos, por ejemplo, que queremos indicar el número de productos disponibles al realizar un pedido.
El mensaje variará según el número de productos disponibles:
-
Ningún producto disponible
-
Hay un producto disponible
-
Hay x productos disponibles
Por supuesto, en la plantilla es posible, con la instrucción {% if ...%}, verificar el número de productos disponibles y mostrar el mensaje correspondiente.
Pero si este mensaje debe mostrarse en varios lugares, esto nos obliga a duplicar código solo para la traducción.
Symfony propone una gestión automática del plural en el catálogo.
Pero para aprovechar este tipo de traducción, Symfony utiliza un formato especial: el formato ICU.
Este formato está disponible en PHP. Encontrará más información sobre este tipo de formato en la página http://userguide.icu-project.org/formatparse/messages
Para poder utilizar este formato, deberá renombrar el archivo de catálogo agregando +intl+icu.
He aquí ejemplos de conversiones de nombres de archivos de catálogo básicos a nombres de archivo en formato ICU:
Nombre del archivo estándar |
Nombre del archivo en formato ICU |
messages.en.yaml |
messages+intl-icu.en.yaml |
messages.es_ES.xlf... |
La traducción de los mensajes de las restricciones de validación
Traducir los mensajes en los controladores y en las vistas está bien, pero hay otros mensajes que pueden aparecer en su aplicación y que se deben traducir también. Este es el caso de los mensajes de error en las validaciones de formularios, por ejemplo (ver capítulo Formularios, sección Validación de los formularios).
Para traducir estos mensajes, se utiliza el dominio de validación en lugar del dominio de mensajes.
Cree un archivo translations/validators.es.yml e inserte en él las traducciones de sus mensajes de error de validación.
Tomaremos el ejemplo del mensaje de error de validación sobre el tamaño mínimo o máximo del número de caracteres en el nombre de los productos.
Modifiquemos el mensaje en las anotaciones de $numero en la entidad Producto:
#[ORM\Column(length: 200)]
#[Assert\Length(
min: 2,
max: 50,
minMessage: 'producto.numero.min',
maxMessage: 'producto.numero.max',
groups: ["all"]
)]
Según...
El uso del nombre de dominio
Acabamos de ver que, dependiendo del nombre de dominio, validadores o mensajes, no se realizan las mismas traducciones.
Puede ser útil especificar el nombre de dominio en el que está trabajando.
Supongamos que todas sus páginas tienen traducciones importantes. Su archivo messages.es.yaml corre el riesgo de crecer hasta el punto de que ya no pueda encontrar sus traducciones.
En ese caso, sería más conveniente dividir sus catálogos según el nombre de la página que se supone que debe usar estas traducciones.
Es posible definir el nombre de dominio deseado para nuestras traducciones.
Por ejemplo, creemos un archivo de traducción test+intl-icu.es.yaml.
say_thanks: Gracias {name}
En nuestra plantilla test/index.html.twig, añadimos esta línea:
<h2>{{ 'say_thanks'|trans({'name': 'amigo'}, 'test') }}</h2>
Puede comprobar que las traducciones siguen efectuándose correctamente.