Internacionalización en PHP: crear aplicaciones multilingües robustas con Intl e ICU
Guía práctica para separar textos y código, seleccionar locales y gestionar catálogos, plurales, números, monedas y fechas con PHP Intl.
Convertir una aplicación PHP en multilingüe no consiste solo en sustituir algunas cadenas por un array. Un sistema fiable separa el contenido de la lógica, elige idioma y región de forma predecible, formatea cantidades, monedas y fechas correctamente y mantiene un idioma alternativo cuando falta una traducción.
Internacionalización y localización
La internacionalización prepara el software para varios idiomas y convenciones culturales; la localización aporta textos y formatos para un público concreto. El código debe usar claves estables como checkout.total, mientras los catálogos contienen las traducciones. Una corrección editorial no debería exigir cambios en el código.
Activar Intl y definir locales
La extensión intl expone las funciones de ICU en PHP. En producción comprueba extension_loaded('intl') y la versión de ICU. Usa identificadores explícitos como es-ES. No dependas del valor predeterminado del servidor. Una lista permitida impide valores no compatibles y rutas manipuladas.
<?php
$supported = ['it-IT', 'en-GB', 'de-DE', 'fr-FR', 'es-ES', 'pt-PT'];
$requested = $_GET['lang'] ?? 'it-IT';
$locale = in_array($requested, $supported, true) ? $requested : 'it-IT';
$catalogue = require __DIR__ . '/lang/' . $locale . '.php';
function trans(string $key, array $vars = []): string {
global $catalogue, $locale;
$pattern = $catalogue[$key] ?? $key;
$formatted = MessageFormatter::formatMessage($locale, $pattern, $vars);
return $formatted === false ? $pattern : $formatted;
}
Los catálogos pueden ser archivos PHP o JSON, o datos de un servicio. Deben estar fuera de directorios públicos, validarse durante el despliegue y compartir un conjunto mínimo de claves. La estrategia de respaldo se define en un componente central.
Mensajes dinámicos y plurales
Concatenar fragmentos impone a todas las lenguas el mismo orden. MessageFormatter utiliza patrones ICU para que cada traducción controle el orden, las reglas de plural y los formatos insertados. Envía valores estructurados y registra los errores de Intl.
Números, monedas y fechas
Almacena datos neutrales: importes en unidades menores o formato decimal seguro, fechas en UTC y monedas como códigos ISO. La presentación se aplica solo al mostrar. NumberFormatter adapta separadores y símbolos, pero no convierte tipos de cambio. IntlDateFormatter aplica idioma, calendario y una zona horaria explícita.
<?php
$price = new NumberFormatter($locale, NumberFormatter::CURRENCY);
echo $price->formatCurrency(1299.90, 'EUR');
$date = new IntlDateFormatter(
$locale,
IntlDateFormatter::LONG,
IntlDateFormatter::SHORT,
'Europe/Rome'
);
echo $date->format(new DateTimeImmutable('2026-11-06 10:30:00'));
Elegir el idioma
Un orden práctico es preferencia guardada, ajuste de sesión validado, cookie firmada, Accept-Language y respaldo de la aplicación. La cabecera del navegador es una pista, no un segmento seguro para una ruta. Guarda el locale canónico y genera URL localizadas coherentes.
HTML, seguridad y SEO
Escapa los valores al renderizar según el contexto HTML, atributo o JavaScript. Las traducciones no son HTML confiable por defecto. Define el atributo lang, publica enlaces hreflang y mantén un mapa estable para slugs traducidos.
Pruebas y mantenimiento
Automatiza controles de JSON válido, claves ausentes, marcadores incompatibles y textos sin traducir. Prueba cero, uno y muchos elementos, además de una lengua con palabras largas. Fija la zona horaria en las pruebas y registra claves ausentes sin mostrarlas a los visitantes.
Conclusión
Una arquitectura multilingüe sólida separa lógica y contenido y delega en ICU el formato cultural. Con locales validados, catálogos versionados, respaldos explícitos y pruebas automáticas, añadir un idioma se convierte en una tarea editorial controlada.
Compartir
¿Cuál es tu reacción?
Me gusta
0
No me gusta
0
Me encanta
0
Divertido
0
Enfadado
0
Triste
0
¡Vaya!
0