Gettext es una excelente librería. Ha sido mi primera opción desde siempre. Sin embargo, a lo largo del tiempo, me ha dado algunos dolores de cabeza.

Cuando comencé con el CMS, hace unos 10 años, recuerdo haber evaluado varias alternativas, entre ellas, algunas librerías populares de aquel entonces, las conclusiones eran muy claras, gettext era rápida y fácil para el desarrollo, debido en partes también, a herramientas como poedit.

Los años pasaron y gettext continuó ahí, hasta hace unos días, cuando pasé unas horas tratando de hacerla funcionar en Windows. Lo logré desde afuera de php, agregando una variable de entorno desde el panel de control del sistema operativo. No es la mejor solución, lo sé, por eso estoy aquí pensando una alternativa a gettext.

Una idea poco original

La idea es simple, y para nada original (si no lo recuerdo mal, algo así utiliza WordPress). Pienso en transformar un archivo .po en un array.

Para el caso de las traducciones singulares, con el par clave-valor, siendo la clave el texto original y el valor su traducción, es más que suficiente.

Algo similar a esto, no sólo alcanza, sino que es extremadamente rápido.

function my_gettext($message) {
	global $translates;
	
	return $translates[$message] ?? $message;
}

Nota: en el script anterior utilizo una variable global sólo a modo ilustrativo, espero que se entienda que $translates es un array que contiene todas las traducciones y que ese array puede ser una propiedad de una clase.

Para el caso de los plurales, la cuestión se complica un poco, pero no mucho más.

 

Utilizando regexp

Ahora bien ¿cómo transformar un archivo .po en array? la solución más rápida es utilizar algunas expresiones regulares, como las que mostraré a continuación.

Para obtener las traducciones singulares:

preg_match_all('%^'
. 'msgid "(.+?)"'.'\R'
. 'msgstr "(.+?)"'.'\R'
. '%m', $po_contents, $matches);

$singulars= array_combine($matches[1], $matches[2]);

Para obtener los plurales:

preg_match_all('%^'
. 'msgid "(.+?)"'.'\R'
. 'msgid_plural "(.+?)"'.'\R'
. '((?:msgstr\[\d\] "(?:.+?)"\R){1,})'
. '%m', $po_contents, $matches, PREG_SET_ORDER);

$plurals = [];
foreach ($matches as $match) {
	preg_match_all('%"(.+?)"%m', $match[3], $matches2);
	$plurals[$match[1]] = $matches2[1];
}

El formato del array que elegí para el caso de las traducciones de los plurales es el siguiente:

$plurals = [
      ['key in singular' => [ 'esto es una traducción en singular', 'esto es una traducción en plural', 'esto es otra traducción en plural', ... ],
];

Como vemos, el valor es un array, donde el primer elemento es la traducción en singular y los siguientes elementos son variantes de traducciones de la forma plural que dependen de el valor n que le pasemos a la función ngettext.

 

Algunas precauciones

La primera precaución que se debe tener es volver a unir las cadenas largas que poedit las pone en varias líneas. Esto se hace de este modo:

$po_contents = str_replace("\"\n\"", '', $po_contents);

También, se deben excluir las traducciones "fuzzy" o borrosas, que son aquellas que gettext realiza automáticamente y requieren que una persona las revise. Esto se puede lograr de la siguiente manera:

$po_contents = preg_replace('%'
. '^#, (.*?)fuzzy'
. '(?s:.*?)'
. 'msgstr(?:\[\d\])? "(?:.+?)%m', 
'', $po_contents);

 

Conclusión

A diferencia de las pruebas hechas hace 10 años, ahora puedo decir que esta alternativa es un poco más rápida que gettext, al menos con el archivo de unas 2000 traducciones con el que lo probé. Sin perjuicio de ello, hay que tener en cuenta que mi alternativa no ejecuta otras funciones, como por ejemplo, el contexto.

Tampoco exploré demasiado la carga de memoria que puede llegar a significar, entendiendo que en los casos de uso que le daré, no van a representar un problema.

En fin, mi decisión ha sido seguir fiel a la biblioteca gettext, y utilizar ésta alternativa en aquellos momentos en donde surjan inconvenientes.