Cómo crear una aplicación Multilingüe: Una Demostración con PHP y Gettext

Ya sea que esté creando un sitio web o una aplicación web completa, hacerlo accesible a un público más amplio a menudo requiere que esté disponible en diferentes idiomas y configuraciones regionales.

Las diferencias fundamentales entre la mayoría de los lenguajes humanos lo hacen todo menos fácil. Las diferencias en las reglas gramaticales, los matices del idioma, los formatos de fecha y más se combinan para hacer de la localización un desafío único y formidable.

Considere este ejemplo sencillo.

Las reglas de pluralización en inglés son bastante sencillas: puedes tener una forma singular de una palabra o una forma plural de una palabra.

En otros idiomas, sin embargo, como las lenguas eslavas, hay dos formas plurales además de la singular. Incluso puede encontrar idiomas con un total de cuatro, cinco o seis formas plurales, como en esloveno, irlandés o árabe.

La forma en que se organiza el código y cómo se diseñan los componentes y la interfaz, juega un papel importante a la hora de determinar la facilidad con la que puede localizar su aplicación.

La internacionalización (i18n) de su base de código ayuda a garantizar que se pueda adaptar a diferentes idiomas o regiones con relativa facilidad. La internacionalización generalmente se realiza una vez, preferiblemente al principio del proyecto para evitar la necesidad de grandes cambios en el código fuente en el futuro.

Cómo crear una aplicación Multilingüe: Una demostración con PHP y Gettext

Una vez que su base de código se ha internacionalizado, la localización (l10n) se convierte en una cuestión de traducir el contenido de su aplicación a un idioma/configuración regional específico.

La localización debe realizarse cada vez que se necesite admitir un nuevo idioma o región. Además, cada vez que se actualiza una parte de la interfaz (que contiene texto), se pone a disposición nuevo contenido, que luego debe ser localizado (es decir, traducido) a todas las configuraciones regionales compatibles.

En este artículo, aprenderemos a internacionalizar y localizar software escrito en PHP. Analizaremos las diversas opciones de implementación y las diferentes herramientas que están disponibles a nuestra disposición para facilitar el proceso.

Herramientas para la internacionalización

La forma más fácil de internacionalizar el software PHP es mediante el uso de archivos de matriz. Las matrices se rellenarán con cadenas traducidas, que luego se podrán buscar desde las plantillas:

<h1><?=$TRANS?></h1>

Sin embargo, esta no es una forma recomendada para proyectos serios, ya que definitivamente planteará problemas de mantenimiento en el futuro. Algunas cuestiones pueden incluso aparecer al principio, como la falta de soporte para la interpolación variable o la pluralización de sustantivos, etc.

Una de las herramientas más clásicas (a menudo tomadas como referencia para i18n y l10n) es una herramienta de Unix llamada Gettext.

Aunque se remonta a 1995, sigue siendo una herramienta integral para traducir software que también es fácil de usar. Si bien es bastante fácil de comenzar, todavía tiene poderosas herramientas de soporte.

Gettext es lo que usaremos en este post. Presentaremos una gran aplicación GUI que se puede usar para actualizar fácilmente sus archivos fuente l10n, evitando así la necesidad de lidiar con la línea de comandos.

Bibliotecas Para Facilitar Las Cosas

Principales marcos y bibliotecas web PHP que admiten Gettext

Existen importantes frameworks y bibliotecas web PHP que admiten Gettext y otras implementaciones de i18n. Algunos son más fáciles de instalar que otros, o cuentan con características adicionales o admiten diferentes formatos de archivo i18n. Aunque en este documento nos centramos en las herramientas provistas con el núcleo PHP, aquí hay una lista de algunas otras que vale la pena mencionar:

  • oscarotero / Gettext: Compatibilidad con Gettext con una interfaz orientada a objetos; incluye funciones auxiliares mejoradas, potentes extractores para varios formatos de archivo (algunos de ellos no son compatibles de forma nativa con el comando gettext). También puede exportar a formatos más allá de los archivos .mo/.po, lo que puede ser útil si necesita integrar sus archivos de traducción en otras partes del sistema, como una interfaz JavaScript.

  • symfony / translation: Admite muchos formatos diferentes, pero recomienda usar XLIFFS detallados. No incluye funciones auxiliares ni un extractor incorporado, pero admite marcadores de posición que usan strtr() internamente.

  • zend / i18n: Admite archivos de matriz e INI, o formatos Gettext. Implementa una capa de almacenamiento en caché para evitar la necesidad de leer el sistema de archivos cada vez. También incluye ayudantes de visualización y validadores y filtros de entrada compatibles con la configuración regional. Sin embargo, no tiene extractor de mensajes.

Otros frameworks también incluyen módulos i18n, pero no están disponibles fuera de sus bases de código:

  • Laravel: Soporta archivos de matriz básicos; no tiene extractor automático, pero incluye un ayudante @lang para archivos de plantilla.

  • Yii: Admite la traducción basada en matrices, Gettext y bases de datos, e incluye un extractor de mensajes. Respaldado por la extensión Intl, disponible desde PHP 5.3, y basado en el proyecto ICU. Esto permite a Yii ejecutar reemplazos poderosos, como deletrear números, formatear fechas, horas, intervalos, moneda y ordinales.

Si decide optar por una de las bibliotecas que no proporcionan extractores, es posible que desee utilizar los formatos Gettext, para que pueda utilizar la cadena de herramientas Gettext original (incluido Poedit) como se describe en el resto del capítulo.

Instalación de Gettext

Es posible que necesite instalar Gettext y la biblioteca PHP relacionada mediante el administrador de paquetes, como apt-get o yum. Una vez instalado, habilítelo agregando extension=gettext.so (Linux/Unix) o extension=php_gettext.dll (Windows) a su archivo php.ini.

Aquí también usaremos Poedit para crear archivos de traducción. Probablemente lo encontrará en el administrador de paquetes de su sistema; está disponible para Unix, Mac y Windows y también se puede descargar de forma gratuita en su sitio web.

Tipos de archivos Gettext

Hay tres tipos de archivos con los que normalmente trabaja mientras trabaja con Gettext.

Los principales son archivos PO (Objeto portátil) y MO (Objeto Máquina), el primero es una lista de «objetos traducidos» legibles y el segundo es el binario correspondiente (para ser interpretado por Gettext al realizar la localización). También hay un archivo POT (Plantilla PO), que simplemente contiene todas las claves existentes de sus archivos de origen, y se puede usar como guía para generar y actualizar todos los archivos PO.

Los archivos de plantilla no son obligatorios; dependiendo de la herramienta que esté utilizando para hacer l10n, estará bien con solo archivos PO/MO. Tendrás un par de archivos PO/MO por idioma y región, pero solo un POTE por dominio.

Separar dominios

Hay algunos casos, en proyectos grandes, en los que es posible que necesite separar traducciones cuando las mismas palabras transmiten un significado diferente en contextos diferentes.

En esos casos, deberá dividirlos en diferentes «dominios», que básicamente se denominan grupos de archivos POT/PO/MO, donde el nombre del archivo es dicho dominio de traducción.

Los proyectos pequeños y medianos generalmente, por simplicidad, usan solo un dominio; su nombre es arbitrario, pero usaremos «principal» para nuestras muestras de código.

En los proyectos de Symfony, por ejemplo, se utilizan dominios para separar la traducción de los mensajes de validación.

Código de configuración regional

Una configuración regional es simplemente un código que identifica una versión de un idioma. Se define siguiendo las especificaciones ISO 639-1 e ISO 3166-1 alfa-2: dos letras minúsculas para el idioma, opcionalmente seguidas de un subrayado y dos letras mayúsculas que identifican el código de país o región.

Para lenguas raras, se utilizan tres letras.

Para algunos oradores, la parte del país puede parecer redundante. De hecho, algunos idiomas tienen dialectos en diferentes países, como el alemán austríaco (de_AT) o el portugués brasileño (pt_BR). La segunda parte se usa para distinguir entre esos dialectos; cuando no está presente, se toma como una versión» genérica «o» híbrida » del idioma.

Estructura de directorios

Para usar Gettext, necesitaremos adherirnos a una estructura específica de carpetas.

Primero, deberá seleccionar una raíz arbitraria para sus archivos l10n en su repositorio de fuentes. Dentro de él, tendrá una carpeta para cada configuración regional necesaria y una carpeta fija «LC_MESSAGES» que contendrá todos sus pares PO/MO.

Carpeta LC_MESSAGES

Formas plurales

Como dijimos en la introducción, diferentes idiomas pueden tener diferentes reglas de pluralización. Sin embargo, Gettext nos ahorra este problema.

Al crear un nuevo .archivo po, tendrá que declarar las reglas de pluralización para ese idioma, y las piezas traducidas que sean sensibles al plural tendrán una forma diferente para cada una de esas reglas.

Al llamar a Gettext en código, tendrá que especificar un número relacionado con la oración (por ejemplo, para la frase «Tiene n mensajes.», tendrá que especificar el valor de n), y resolverá la forma correcta para usar, incluso usando la sustitución de cadenas si es necesario.

Las reglas plurales se componen del número de reglas necesarias con una prueba booleana para cada regla (se puede omitir la prueba para, como máximo, una regla). Por ejemplo:

  • Japonés: nplurals=1; plural=0; – una regla: no hay formas plurales

  • Español: nplurals=2; plural=(n != 1); – dos reglas: use la forma plural solo cuando n no es 1, de lo contrario use la forma singular.

  • Portugués brasileño: nplurals=2; plural=(n > 1); – dos reglas, use la forma plural solo cuando n es mayor que 1, de lo contrario use la forma singular.

Para una explicación más profunda, hay un tutorial informativo de LingoHub disponible en línea.

Gettext determinará qué regla usar en función del número proporcionado y utilizará la versión localizada correcta de la cadena. Para cadenas donde se necesita manejar la pluralización, deberá incluir en el .archivo po una oración diferente para cada regla plural definida.

Implementación de ejemplo

Después de toda esa teoría, pongámonos un poco prácticos. Aquí hay un extracto de a .archivo po (no se preocupe demasiado por la sintaxis, pero en su lugar, solo tenga una idea del contenido general):

msgid ""msgstr """Language: pt_BR\n""Content-Type: text/plain; charset=UTF-8\n""Plural-Forms: nplurals=2; plural=(n > 1);\n"msgid "We're now translating some strings"msgstr "Nós estamos traduzindo algumas strings agora"msgid "Hello %1$s! Your last visit was on %2$s"msgstr "Olá %1$s! Sua última visita foi em %2$s"msgid "Only one unread message"msgid_plural "%d unread messages"msgstr "Só uma mensagem não lida"msgstr "%d mensagens não lidas"

La primera sección funciona como un encabezado, con msgid y msgstr vacíos.

Describe la codificación del archivo, las formas plurales y algunas otras cosas. La segunda sección traduce una cadena simple del inglés al portugués brasileño, y la tercera hace lo mismo, pero aprovecha el reemplazo de cadena de sprintf, lo que permite que la traducción contenga el nombre de usuario y la fecha de visita.

La última sección es una muestra de formas de pluralización, mostrando la versión singular y plural como msgid en inglés y sus traducciones correspondientes como msgstr 0 y 1 (siguiendo el número dado por la regla plural).

Allí, también se usa el reemplazo de cadenas, por lo que el número se puede ver directamente en la oración, utilizando %d. Las formas plurales siempre tienen dos msgid (singular y plural), por lo que se recomienda no usar un lenguaje complejo como fuente de traducción.

Teclas de localización

Como habrás notado, estamos utilizando la oración real en inglés como ID de origen. Que msgid es el mismo que se usa en todos sus archivos .archivos po, lo que significa que otros idiomas tendrán el mismo formato y los mismos campos msgid, pero traducidos msgstr líneas.

Hablando de claves de traducción, aquí hay dos enfoques «filosóficos» estándar:

1. msgid como oración real

Las principales ventajas de este enfoque son:

  • Si hay partes del software sin traducir en un idioma determinado, la clave mostrada conservará algún significado. Por ejemplo, si sabe cómo traducir del inglés al español pero necesita ayuda para traducir al francés, puede publicar la nueva página con oraciones faltantes en francés, y partes del sitio web se mostrarán en inglés en su lugar.

  • Es mucho más fácil para el traductor entender lo que está pasando y hacer una traducción adecuada basada en msgid.

  • Te da l10n «gratis» para un idioma, el de origen.

Por otro lado, la principal desventaja es que, si necesita cambiar el texto real, debe reemplazar el mismo msgid en varios archivos de idioma.

2. msgid como clave estructurada única

Esto describiría el rol de oración en la aplicación de una manera estructurada, incluida la plantilla o parte donde se encuentra la cadena en lugar de su contenido.

Esta es una excelente manera de organizar el código, separando el contenido de texto de la lógica de la plantilla. Sin embargo, eso podría presentar problemas al traductor que perdería el contexto.

Se necesitaría un archivo de idioma fuente como base para otras traducciones. Por ejemplo, lo ideal sería que el desarrollador tuviera un «en».po «archivo, que los traductores leerían para entender en qué escribir» fr.po».

Las traducciones faltantes mostrarían claves sin sentido en la pantalla («top_menu.bienvenido» en lugar de «Hola, Usuario!»en la mencionada página francesa sin traducir).

Eso es bueno, ya que obligaría a completar la traducción antes de publicarla, pero es malo, ya que los problemas de traducción serían realmente horribles en la interfaz. Sin embargo, algunas bibliotecas incluyen una opción para especificar un lenguaje dado como «alternativa», con un comportamiento similar al del otro enfoque.

El manual Gettext favorece el primer enfoque, ya que, en general, es más fácil para traductores y usuarios en caso de problemas. Ese es el enfoque que usaremos aquí también.

Sin embargo, debe tenerse en cuenta que la documentación de Symfony favorece la traducción basada en palabras clave, para permitir cambios independientes de todas las traducciones sin afectar también a las plantillas.

Uso diario

En una aplicación común, usaría algunas funciones Gettext mientras escribía texto estático en sus páginas.

Esas frases aparecerían entonces en .archivos po, se traducen, se compilan en archivos. mo y, a continuación, Gettext los utiliza al renderizar la interfaz real. Dado eso, unamos lo que hemos discutido hasta ahora en un ejemplo paso a paso:

1. Un archivo de plantilla de ejemplo, que incluye algunas llamadas gettext diferentes

<?php include 'i18n_setup.php' ?><div> <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1> <!-- code indented this way only for legibility → <?php if ($unread): ?> <h2> <?=sprintf( ngettext('Only one unread message', '%d unread messages', $unread), $unread )?> </h2> <?php endif ?></div><h1><?=gettext('Introduction')?></h1><p><?=gettext('We\'re now translating some strings')?></p>
  • gettext() simplemente traduce un msgid a su correspondiente msgstr para un idioma determinado. También está la función abreviada _() que funciona de la misma manera

  • ngettext() hace lo mismo pero con reglas plurales

  • También hay dgettext() y dngettext(), que le permiten anular el dominio para una sola llamada (más información sobre la configuración del dominio en el siguiente ejemplo)

2. Un archivo de configuración de ejemplo (i18n_setup.php como se usa anteriormente), seleccionar la configuración regional correcta y configurar Gettext

Usando Gettext implica un poco de código repetitivo, pero se trata principalmente de configurar el directorio de configuraciones regionales y elegir los parámetros apropiados (una configuración regional y un dominio).

<?php/** * Verifies if the given $locale is supported in the project * @param string $locale * @return bool */function valid($locale) { return in_array($locale, ) && valid($_GET)) { // the locale can be changed through the query-string $lang = $_GET; //you should sanitize this! setcookie('lang', $lang); //it's stored in a cookie so it can be reused} elseif (isset($_COOKIE) && valid($_COOKIE)) { // if the cookie is present instead, let's just keep it $lang = $_COOKIE; //you should sanitize this!} elseif (isset($_SERVER)) { // default: look for the languages the browser says the user accepts $langs = explode(',', $_SERVER); array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ); }); foreach ($langs as $browser_lang) { if (valid($browser_lang)) { $lang = $browser_lang; break; } }}// here we define the global system locale given the found languageputenv("LANG=$lang");// this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instancesetlocale(LC_ALL, $lang);// this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mobindtextdomain('main', '../locales');// indicates in what encoding the file should be readbind_textdomain_codeset('main', 'UTF-8');// if your application has additional domains, as cited before, you should bind them here as wellbindtextdomain('forum', '../locales');bind_textdomain_codeset('forum', 'UTF-8');// here we indicate the default domain the gettext() calls will respond totextdomain('main');// this would look for the string in forum.mo instead of main.mo// echo dgettext('forum', 'Welcome back!');?>

3. Preparar la traducción para la primera ejecución

Una de las grandes ventajas que tiene Gettext sobre los paquetes personalizados de framework i18n es su amplio y potente formato de archivo.

Quizás estés pensando » Oh hombre, eso es bastante difícil de entender y editar a mano, ¡una matriz simple sería más fácil!»No se equivoquen, aplicaciones como Poedit están aquí para ayudar, mucho. Puede obtener el programa desde su sitio web, es gratuito y está disponible para todas las plataformas. Es una herramienta bastante fácil de usar y muy potente al mismo tiempo, que utiliza todas las funciones que Gettext tiene disponibles. Trabajaremos aquí con la última versión, Poedit 1.8.

Vista interior de Poedit.

En la primera ejecución, debe seleccionar «Archivo > Nuevo New» en el menú. Se te preguntará el idioma; seleccione o filtre el idioma al que desea traducir, o utilice el formato que mencionamos anteriormente, como en_US o pt_BR.

Seleccionar el idioma.

Ahora, guarde el archivo, usando la estructura de directorios que mencionamos también. Luego debe hacer clic en «Extraer de fuentes», y aquí configurará varios ajustes para las tareas de extracción y traducción. Podrás encontrar todo eso más tarde a través de «Catalog > Propiedades»:

  • Rutas de origen: Incluye todas las carpetas del proyecto a las que se llama gettext() (y las carpetas hermanas) – esta suele ser la carpeta de plantillas/vistas. Este es el único ajuste obligatorio.

  • Propiedades de traducción:

    • Nombre y versión del proyecto, Equipo y dirección de correo electrónico del equipo: Información útil que figura en el .encabezado de archivo po.
    • Formas plurales: Estas son las reglas que mencionamos antes. Puede dejarlo con la opción predeterminada la mayor parte del tiempo, ya que Poedit ya incluye una práctica base de datos de reglas plurales para muchos idiomas.
    • Conjuntos de caracteres: UTF-8, preferiblemente.
    • Conjunto de caracteres de código fuente: El conjunto de caracteres utilizado por su base de código, probablemente UTF-8 también, ¿verdad?
  • Palabras clave de origen: El software subyacente sabe cómo se ven las llamadas a funciones gettext() y similares en varios lenguajes de programación, pero también puede crear sus propias funciones de traducción. Será aquí donde agregarás esos otros métodos. Esto se discutirá más adelante en la sección «Consejos».

Después de configurar esas propiedades, Poedit ejecutará un análisis a través de sus archivos de origen para encontrar todas las llamadas de localización. Después de cada análisis, Poedit mostrará un resumen de lo que se encontró y lo que se eliminó de los archivos de origen. Las entradas nuevas estarán vacías en la tabla de traducción, lo que le permitirá ingresar las versiones localizadas de esas cadenas. Guárdelo y un archivo. mo será (re)compilado en la misma carpeta y, ¡listo!, su proyecto se internacionaliza!

Proyecto internacionalizado.

Poedit también puede sugerir traducciones comunes de la web y de archivos anteriores. Es práctico, por lo que solo tienes que comprobar si tienen sentido y aceptarlos. Si no está seguro acerca de una traducción, puede marcarla como Borrosa y se mostrará en amarillo. Las entradas azules son aquellas que no tienen traducción.

4. Traducir cadenas

Como habrás notado, hay dos tipos principales de cadenas localizadas: simples y con formas plurales.

Los simples solo tienen dos cajas: fuente y cadena localizada. La cadena de origen no se puede modificar, ya que Gettext/Poedit no incluye la capacidad de alterar sus archivos de origen; en su lugar, necesitará cambiar la fuente en sí y volver a escanear los archivos. (Consejo: Si hace clic con el botón derecho en una línea de traducción, se mostrará una pista con los archivos de origen y las líneas donde se está utilizando esa cadena.)

Las cadenas de forma plural incluyen dos cajas para mostrar las dos cadenas de origen, y pestañas para que pueda configurar las diferentes formas finales.

Configuración de formularios finales.

Ejemplo de una cadena con forma plural en Poedit, mostrando una pestaña de traducción para cada una.

Siempre que cambie sus archivos de código fuente y necesite actualizar las traducciones, simplemente presione Actualizar y Poedit volverá a escanear el código, eliminará las entradas inexistentes, fusionará las que cambiaron y agregará otras nuevas.

Poedit también puede intentar adivinar algunas traducciones, basadas en otras que hizo. Esas conjeturas y las entradas cambiadas recibirán un marcador «Difuso», que indica que necesitan revisión, que se muestra en amarillo en la lista.

También es útil si tienes un equipo de traducción y alguien intenta escribir algo de lo que no está seguro: solo márcalo borroso y alguien más lo revisará más tarde.

Finalmente, se recomienda dejar marcado» Ver entradas sin traducir > primero», ya que le ayudará a evitar olvidar cualquier entrada. Desde ese menú, también puede abrir partes de la interfaz de usuario que le permiten dejar información contextual para los traductores si es necesario.

Consejos & Trucos

Los servidores web pueden terminar almacenando en caché sus archivos. mo.

Si está ejecutando PHP como módulo en Apache (mod_php), es posible que tenga problemas con el archivo .mo almacenado en caché. Sucede la primera vez que se lee, y luego, para actualizarlo, es posible que deba reiniciar el servidor.

En Nginx y PHP5, por lo general, solo se necesita un par de actualizaciones de página para actualizar la caché de traducción, y en PHP7 rara vez se necesita.

Las bibliotecas proporcionan funciones auxiliares para mantener el código de localización corto.

Como lo prefieren muchas personas, es más fácil usar _() en lugar de gettext(). Muchas bibliotecas i18n personalizadas de frameworks también usan algo similar a t(), para acortar el código traducido. Sin embargo, esa es la única función que tiene un atajo.

Es posible que desee agregar en su proyecto algunos otros, como __() o _n() para ngettext(), o tal vez un elegante _r() que uniría llamadas gettext() y sprintf(). Otras bibliotecas, como Gettext de oscarotero, también proporcionan funciones auxiliares como estas.

En esos casos, deberá indicar a la utilidad Gettext cómo extraer las cadenas de esas nuevas funciones. No tengas miedo, es muy fácil. Es sólo un campo en el .archivo po o una pantalla de configuración en Poedit (en el editor, esa opción está dentro de «Catalog > Properties > Sources keywords»).

Recordar: Gettext ya conoce las funciones predeterminadas para muchos idiomas, así que no se preocupe si esa lista parece vacía. Debe incluir en esa lista las especificaciones de las nuevas funciones, siguiendo este formato específico:

  • Si crea algo como t(), que simplemente devuelve la traducción de una cadena, puede especificarla como t. Gettext sabrá que el único argumento de función es la cadena a traducir;

  • Si la función tiene más de un argumento, puede especificar en cuál está la primera cadena y, si es necesario, también la forma plural. Por ejemplo, si nuestra firma de función es __('one user', '%d users', $number), la especificación sería __:1,2, lo que significa que la primera forma es el primer argumento, y la segunda forma es el segundo argumento. Si su número viene como el primer argumento, la especificación sería __:2,3, indicando que la primera forma es el segundo argumento, y así sucesivamente.

Después de incluir esas nuevas reglas en el .archivo po, un nuevo escaneo traerá sus nuevas cadenas con la misma facilidad que antes.

Haga su Aplicación PHP multilingüe Con Gettext

Gettext es una herramienta muy poderosa para internacionalizar su proyecto PHP. Más allá de su flexibilidad que permite soportar un gran número de lenguajes humanos, su soporte para más de 20 lenguajes de programación le permite transferir fácilmente su conocimiento de usarlo con PHP a otros lenguajes como Python, Java o C#.

Además, Poedit puede ayudar a suavizar el camino entre el código y las cadenas traducidas, haciendo que el proceso sea más sencillo y fácil de seguir. También puede optimizar los esfuerzos de traducción compartida con su integración de Crowdin.

Siempre que sea posible, considere otros idiomas que sus usuarios puedan hablar. Esto es principalmente importante para proyectos que no son en inglés: Puede aumentar el acceso de usuario si lo publica en inglés, así como en su idioma nativo.

Por supuesto, no todos los proyectos necesitan internacionalización, pero es mucho más fácil iniciar i18n durante la infancia de un proyecto, incluso si no es necesario inicialmente, que hacerlo más adelante en el camino si posteriormente se convierte en un requisito. Y, con herramientas como Gettext y Poedit es más fácil que nunca.

Relacionado: Introducción A PHP 7: Lo Nuevo Y Lo Que Se Ha Ido

Leave a Reply

Tu dirección de correo electrónico no será publicada.