Archivos .po

48 views
Skip to first unread message

Almacenamiento Almacenamiento

unread,
Mar 20, 2012, 10:03:00 AM3/20/12
to php...@googlegroups.com, php---pro...@googlegroups.com
Estoy haciendo un proyecto, y me había creado un sistema de templates similar al de phpbb en el que colocas  {username} o {list_products} , y tener un .php con un nodo de palabras que representan cada variable encerrada por llaves, y mostrarlos en distintos templates de extension .tpl

Pero ahora me surge la situación de avanzar aun mas con esto, e integrarle un sistema de translate con archivos de extensión .po , que me parece que es lo mas correcto y organizado.

Estoy averiguando sobre el tema, pero aun no encuentro nada, si alguien podría ayudarme me haría un gran favor :)
Estoy abierto a ideas por si si saben otra forma mejor que un sistema de translate con archivos .po, 


Espero que me puedan ayudar,
Gracias,
Saludos

Camello Ar

unread,
Mar 20, 2012, 11:15:44 AM3/20/12
to php...@googlegroups.com, php---pro...@googlegroups.com
La extensión del archivo, en este caso no es lo relevante, más allá de la organización (saber que contiene cada archivo por su extensión)

Lo importante es la estructura del mismo. y como se acomodan los mismos en los templates

Historícamente se utilizó un sistema de variables o defines (de acuerdo al lenguaje de programación) con variables para cada cadena y su correspondiente texto traducido) puedes usar variables generales (para textos comunes como Aceptar, Cancelar, Guardar, Ver, etc) ue permiten reutilizar o particulares para secciones concretas. También puede utilizarse un array. Actualmente se utilizan archivos XML, que no utilizan espacio de variables, pero requieren algo más de proceso

Ej:
<?
$lenguaje = "Español";
$txtOk = "Aceptar";
$txtProductos = "Productos";
$txtLogin = "Ingrese su Usuario y Contraseña para continuar"; 

//como array
$txt['es'] = "Español";
$txt['es']['ok'] = "Aceptar";
$txt['es']['productos'] = "Productos";
$txt['es']['login'] = "Ingrese su Usuario y Contraseña para continuar"; 
?>

En el template
<?
$lng = 'es'; //debería tomarse de sesión o de alguna variable get o redirección como sitio/es/pagina
?>
<form>
<?=$txt[$lng]['login']>
CamposFormulario
 
e imprime
Ingrese su Usuario y Contraseña para continuar
Campos

--
Has recibido este mensaje porque estás suscrito al grupo "Grupo PHP Argentina" de Grupos de Google.
Para publicar una entrada en este grupo, envía un correo electrónico a php...@googlegroups.com.
Para anular tu suscripción a este grupo, envía un correo electrónico a php-arg+u...@googlegroups.com
Para tener acceso a más opciones, visita el grupo en http://groups.google.com/group/php-arg?hl=es.

Tordek

unread,
Mar 20, 2012, 3:38:05 PM3/20/12
to php...@googlegroups.com
On 20/03/12 11:03, Almacenamiento Almacenamiento wrote:

> Estoy averiguando sobre el tema, pero aun no encuentro nada, si

> alguien podr�a ayudarme me har�a un gran favor :)


> Estoy abierto a ideas por si si saben otra forma mejor que un
> sistema de translate con archivos .po,

Para esto se usa `gettext` y `ngettext`:
http://ar2.php.net/manual/en/function.gettext.php y
https://en.wikipedia.org/wiki/Gettext y
http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/gettext.html#gettext

Ten�s que hacer varias cosas:

0) generar un .po y compilarlo a .mo
1) Setear la locale al lenguaje que quer�s usar.
2) bindear los textdomain que vas a usar (esto te permite tener los
strings agrupados por app, en vez de un solo .po para todo el sitio).
3) setear el textdomain
4) usar gettext.

entonces, en tu c�digo hac�s

<?php printf(gettext("Ten�s %s mensajes."), $mensajes); ?> *

*: lo ideal es escribir los strings en ingl�s/ASCII; sin tildes...
los strings en espa�ol van al .po.

y sale el string en el lenguaje que se haya seteado (obviamente,
siempre y cuando hayas definido el .po para ese lenguaje).

Hasta ac�, es un poco m�s dificil que la soluci�n que mencion�
Camello, y adem�s tiene otros problemas: Solo podes usar las locales
que haya instaladas (en la lista que ves con locale -a), y Apache
cachea los strings de gettext, haciendo que necesites reiniciar
Apache cada vez que modific�s tu .po (no s� si se actualiz� esto).

La parte donde empieza a haber una diferencia es con los plurales:

"Ten�s 1 mensajes." es feo, entonces us�s

<?php printf(ngettext("Ten�s %s mensaje.", "Ten�s %s mensajes.",
$mensajes), $mensajes); ?>

y sale en singular o plural, como corresponda.

Si tu traducci�n es espa�ol <-> ingl�s, hay un mapeo 1:1 entre tus
singulares y plurales (para 0 o 2+, se usa el plural; para 1, el
singular), pero en otros lenguajes no pasa lo mismo. En polaco, por
ejemplo, hay una forma para 1 elemento, una para 2, 3, y 4, (y para
cualquier numero que termine en 2, 3, o 4, que no sea 12, 13, y 14),
y una para todos los dem�s valores. [0]

Esto, con la soluci�n con arrays "b�sica", es imposible de traducir
correctamente.

Podr�as definir una funci�n que tome un array con la forma

$array[lenguaje][mensaje][plural_nro] = "mensaje";

por ejemplo:

$txt['PO_po']['plik'][1] = "plik";
$txt['PO_po']['plik'][2] = "pliki";
$txt['PO_po']['plik'][3] = "pliko'w";

y tu funci�n tiene que saber mapear $cantidad a (1, 2, 3) como
corresponda para ese lenguaje.

(Aunque... quiz� puedas definir $txt['PO_po']['plural_fn'] =
funcion;... pero con el caveat de que, como es el traductor el que
define la funci�n, est�s ejecutando c�digo ajeno directamente.)

Todo lo anterior es f�cilmente aplicable al XML; s�lo que ten�s que
ver c�mo acept�s una funci�n arbitraria desde el XML.

Algo similar se hace en los .po: se define un encabezado as�:

Plural-Forms: nplurals=3; \
plural=n==1 ? 0 : \
n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;

Que, como est� limitado a operaciones aritm�ticas, no puede ejecutar
c�digo arbitrario.


Las ventajas m�s obvias de usar gettext:
1) Los plurales se usan correctamente.
2) Ya todo est� armado y es parte de PHP (tendr�as que encontrar una
librer�a que lo haga, o armar la tuya, para usar lo otro).
3) Velocidad. [1] muestra que la extensi�n es m�s r�pida que un
gettext escrito a mano en PHP, o que la soluci�n simple con arrays.

Las desventajas:
1) Podr�as necesitar permisos que en un shared hosting no conseguis
(instalar locales, reiniciar apache).

[0]
http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html
[1]
http://mel.melaxis.com/devblog/2006/04/10/benchmarking-php-localization-is-gettext-fast-enough/

>
>
> Espero que me puedan ayudar,
> Gracias,
> Saludos

--
Guillermo O. �Tordek� Freschi. Programador, Escritor, Genio Maligno.
http://tordek.com.ar :: http://twitter.com/tordek

Camello Ar

unread,
Mar 20, 2012, 5:40:28 PM3/20/12
to php...@googlegroups.com
Otro problema que vi con los PO (al menos en la forma que los trabaja WP) es que cada uso del string está indicado en la forma archivo:linea, con lo cual con reescrituras de código hay que editar todos los PO para adaptarlos a las nuevas lineas

Ejemplo
#: 404.php:15
#: theloop.php:147
msgid "Oh no! You're looking for something which just isn't here! Fear not however, errors are to be expected, and luckily there are tools on the sidebar for you to use in your search for what you need." 
msgstr ""

El 20 de marzo de 2012 16:38, Tordek <ked...@gmail.com> escribió:
On 20/03/12 11:03, Almacenamiento Almacenamiento wrote:

Estoy averiguando sobre el tema, pero aun no encuentro nada, si
alguien podría ayudarme me haría un gran favor :)

Estoy abierto a ideas por si si saben otra forma mejor que un
sistema de translate con archivos .po,
Tenés que hacer varias cosas:


0) generar un .po y compilarlo a .mo
1) Setear la locale al lenguaje que querés usar.

2) bindear los textdomain que vas a usar (esto te permite tener los strings agrupados por app, en vez de un solo .po para todo el sitio).
3) setear el textdomain
4) usar gettext.

entonces, en tu código hacés

<?php printf(gettext("Tenés %s mensajes."), $mensajes); ?> *

*: lo ideal es escribir los strings en inglés/ASCII; sin tildes... los strings en español van al .po.


y sale el string en el lenguaje que se haya seteado (obviamente, siempre y cuando hayas definido el .po para ese lenguaje).

Hasta acá, es un poco más dificil que la solución que mencionó Camello, y además tiene otros problemas: Solo podes usar las locales que haya instaladas (en la lista que ves con locale -a), y Apache cachea los strings de gettext, haciendo que necesites reiniciar Apache cada vez que modificás tu .po (no sé si se actualizó esto).


La parte donde empieza a haber una diferencia es con los plurales:

"Tenés 1 mensajes." es feo, entonces usás

<?php printf(ngettext("Tenés %s mensaje.", "Tenés %s mensajes.", $mensajes), $mensajes); ?>


y sale en singular o plural, como corresponda.

Si tu traducción es español <-> inglés, hay un mapeo 1:1 entre tus singulares y plurales (para 0 o 2+, se usa el plural; para 1, el singular), pero en otros lenguajes no pasa lo mismo. En polaco, por ejemplo, hay una forma para 1 elemento, una para 2, 3, y 4, (y para cualquier numero que termine en 2, 3, o 4, que no sea 12, 13, y 14), y una para todos los demás valores. [0]

Esto, con la solución con arrays "básica", es imposible de traducir correctamente.

Podrías definir una función que tome un array con la forma


$array[lenguaje][mensaje][plural_nro] = "mensaje";

por ejemplo:

$txt['PO_po']['plik'][1] = "plik";
$txt['PO_po']['plik'][2] = "pliki";
$txt['PO_po']['plik'][3] = "pliko'w";

y tu función tiene que saber mapear $cantidad a (1, 2, 3) como corresponda para ese lenguaje.

(Aunque... quizá puedas definir $txt['PO_po']['plural_fn'] = funcion;... pero con el caveat de que, como es el traductor el que define la función, estás ejecutando código ajeno directamente.)

Todo lo anterior es fácilmente aplicable al XML; sólo que tenés que ver cómo aceptás una función arbitraria desde el XML.

Algo similar se hace en los .po: se define un encabezado así:


Plural-Forms: nplurals=3; \
   plural=n==1 ? 0 : \
          n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;

Que, como está limitado a operaciones aritméticas, no puede ejecutar código arbitrario.


Las ventajas más obvias de usar gettext:

1) Los plurales se usan correctamente.
2) Ya todo está armado y es parte de PHP (tendrías que encontrar una librería que lo haga, o armar la tuya, para usar lo otro).
3) Velocidad. [1] muestra que la extensión es más rápida que un gettext escrito a mano en PHP, o que la solución simple con arrays.

Las desventajas:
1) Podrías necesitar permisos que en un shared hosting no conseguis (instalar locales, reiniciar apache).
--
Has recibido este mensaje porque estás suscrito al grupo "Grupo PHP Argentina" de Grupos de Google.
Para publicar una entrada en este grupo, envía un correo electrónico a php...@googlegroups.com.
Para anular tu suscripción a este grupo, envía un correo electrónico a php-arg+unsubscribe@googlegroups.com

Tordek

unread,
Mar 20, 2012, 6:20:54 PM3/20/12
to php...@googlegroups.com
On 20/03/12 18:40, Camello Ar wrote:
> Otro problema que vi con los PO (al menos en la forma que los
> trabaja WP) es que cada uso del string est� indicado en la forma
> archivo:linea, con lo cual con reescrituras de c�digo hay que editar

> todos los PO para adaptarlos a las nuevas lineas
>
> Ejemplo
> #: 404.php:15
> #: theloop.php:147
> msgid "Oh no! You're looking for something which just isn't here!
> Fear not however, errors are to be expected, and luckily there are
> tools on the sidebar for you to use in your search for what you need."
> msgstr ""

Esos son comentarios, para darte un poco de contexto de d�nde se
usan (y, en menor medida, para darte una referencia de d�nde se
usan, para ver que no repitas por error un string, o para hallar
typos). El .po original se extrae con una herramienta (que,
desafortunadamente, no pude encontrar c�mo se llama): se le pasa
como par�metro el directorio del proyecto, y extrae todos los
strings envueltos en _(), ngettext(), gettext(), y familia.

Camello Ar

unread,
Mar 20, 2012, 6:32:26 PM3/20/12
to php...@googlegroups.com
Una herramienta que se usa para editar y compilar a MO es Poedit. Aparentemente permite recorrer los PHP y extraer los strings traducibles


El 20 de marzo de 2012 19:20, Tordek <ked...@gmail.com> escribió:
On 20/03/12 18:40, Camello Ar wrote:
Otro problema que vi con los PO (al menos en la forma que los
trabaja WP) es que cada uso del string está indicado en la forma
archivo:linea, con lo cual con reescrituras de código hay que editar

todos los PO para adaptarlos a las nuevas lineas

Ejemplo
#: 404.php:15
#: theloop.php:147
msgid "Oh no! You're looking for something which just isn't here!
Fear not however, errors are to be expected, and luckily there are
tools on the sidebar for you to use in your search for what you need."
msgstr ""

Esos son comentarios, para darte un poco de contexto de dónde se usan (y, en menor medida, para darte una referencia de dónde se usan, para ver que no repitas por error un string, o para hallar typos). El .po original se extrae con una herramienta (que, desafortunadamente, no pude encontrar cómo se llama): se le pasa como parámetro el directorio del proyecto, y extrae todos los strings envueltos en _(), ngettext(), gettext(), y familia.



--
Guillermo O. «Tordek» Freschi. Programador, Escritor, Genio Maligno.
http://tordek.com.ar :: http://twitter.com/tordek

Mariano Iglesias

unread,
Mar 20, 2012, 6:37:12 PM3/20/12
to php...@googlegroups.com
No, el poedit no extrae, el poedit se usa para tomar los archivos POT y generar las vrsiones traducidas en .PO

Para extraer en linux tenes xgettext. Por ej aca tenes un script python que hice para extraer strings de funciones _t() y _tn()  (lo uso para lithium): http://pastium.org/view/ab4ca01f30df55b4be98c785bab43b9c

2012/3/20 Camello Ar <camel...@gmail.com>
Para anular tu suscripción a este grupo, envía un correo electrónico a php-arg+u...@googlegroups.com

Almacenamiento Almacenamiento

unread,
Mar 20, 2012, 9:47:26 PM3/20/12
to php...@googlegroups.com
Gracias gente me sirvio de mucho la ayuda :)
Ahora yo quiero pasarles un sitio donde explica bastante lo que Tordek explicaba tambien.


Espero que les sirva para los q hayan tenido el mismo problema,
Gracias
Saludos! :)

Camello Ar

unread,
Mar 20, 2012, 11:37:50 PM3/20/12
to php...@googlegroups.com

BARBAZUL

unread,
Mar 21, 2012, 12:25:47 AM3/21/12
to php...@googlegroups.com

Mgiglesias: juro que no es por llevarte la contra nomas (esta vez) pero con el poedit podes tirarle la carpeta del proyecto y te levanta los calls. Incluso podes customizar la función que usas para los calls. Además cuando volves a recorrer los archivos (por ejemplo en una nueva versión de la aplicacion) te resalta los strings que dejaste de usar y los nuevos que aparecieron.

Esta es otra ventaja de usar gettext: la administración de los locales se vuelve realmente MUY fácil con este tipo de herramientas.

Tordek

unread,
Mar 21, 2012, 2:03:36 AM3/21/12
to php...@googlegroups.com
2012/3/21 Camello Ar <camel...@gmail.com>:

Eso no es una solución:

1) Si un string falta, tenés un error. (Con gettext, en su lugar,
tenés el string en el idioma original.)
2) Para cada lenguaje tenés que modificar la función.
3) Ni la más mínima sospecha de que existen plurales.

Como mínimo, esperaba esto:

es_ES.php

$languages['es_ES']['plural_fn'] = function ($n) { return $n == 1 ? 0 : 1; };
$languages['es_ES']['home'] = "Inicio";
$languages['es_ES']['You have %s messages.'][0] = "Tenés %s mensaje.";
$languages['es_ES']['You have %s messages.'][1] = "Tenés %s mensajes.";

translate.php

define("DEFAULT", 'en_US');

function translate($language, $string) {
if (isset($languages[$language][$string])) {
return $languages[$language][$string];
} else {
return $string;
}
}

function translate_plural($language, $singular, $plural, $n) {
if (isset($languages[$language][$plural]) {
$plural_number = $languages[$language]['plural_fn'](n);
return $languages[$language][$plural][$plural_number];
} else {
if ($n == 1) {
return $singular;
} else {
return $plural;
}
}
}

(Aunque no tengo idea de si la función de plurales se puede usar de esa manera.)

Y sigue siendo una solución horrible: estamos redefiniendo lo que ya
hace gettext.

Almacenamiento Almacenamiento

unread,
Mar 21, 2012, 9:36:02 AM3/21/12
to php...@googlegroups.com
xD nose si es sera la manera mas correcta xD, yo había hecho una onda así antes: (en realidad ahora, porque todavía no implemente el sistema con archivos .po)

$lang['es']=array(
'forms'=>array(
'default'=>array(
'username'=>'usuario',
'password'=>'clave',
),
'register'=>array(
'submit'=>'Registrar',
),
'login'=>array(
'submit'=>'Ingresar',
)
)
);

Después con un sistema de templates que hice, que lee el archivo .tpl y con preg_match_all captura todas las palabras encerradas con llaves, y las reemplaza con estos array, lo hice con bucle for, foreach, y un if para condicionar las palabras. No me costó muchas lineas hacerlo y anda perfecto xD, pero nose si era la mejor idea ._. por eso fui por el lado de los archivos .po y de la function gettext() :B para q quede un poco mas elaborado y para aprender algo nuevo xD

Por cierto quiero compartir otro enlace que encontre ayer buscando y buscando mas info de esto, en este usan Poedit, y gettext() , es bastante corto y bien explicado con ejemplos xD

Si tienen algún otro enlace para compartir, agradecería :)
Saludos,

Mariano Iglesias

unread,
Mar 21, 2012, 10:04:40 AM3/21/12
to php...@googlegroups.com
No lo tenia, lo acabo de ver. Igual no me anduvo ni para adelante ni para atras :)

Igualmente el extraer las calls via un script es mucho mas recomendable que extraerlo con poedit, ya que podes automatizar el proceso en etapa de deployment. Cuando estas listo para largar una version de tu aplicacion, tiras el script y le tiras los POTs a los translators. Ellos luego te envian los .PO (y los .MO), y listo.

2012/3/21 BARBAZUL <tere...@gmail.com>

BARBAZUL

unread,
Mar 21, 2012, 3:43:32 PM3/21/12
to php...@googlegroups.com

Si, es bastaaaaante choto para setearlo la primera vez :-\

Reply all
Reply to author
Forward
0 new messages