CakePHP: i18n y Translate Behavior

Últimamente he tenido un montón de problemas con el Translate Behavior, que es el encargado de agregar internacionalización a una aplicación web desarrollada en CakePHP.
El problema no sé si era que yo no entendía el funcionamiento interno o que no se adaptaba a mis necesidades. Pero antes de entrar en los detalles del mismo veamos la forma de actuar de dicho behavior.
Supuesto
Se supone que tenemos una tabla -fictícea- llamada posts por ejemplo, cuya estructura es muy simple: posts(id, title, content, created, modified). Los campos susceptibles de traducción son title y content. Entonces la tabla que realmente tenemos que crear sería la siguiente: posts(id, created, modified), excluyendo los campos traducibles.
Una vez hemos hecho el análisis crearemos la tabla donde se van a guardar las traducciones, esta tabla se llama i18n y su esquema sql viene en app/config/sql/i18n.sql, es el siguiente:
CREATE TABLE i18n (
id int(10) NOT NULL auto_increment,
locale varchar(6) NOT NULL,
model varchar(255) NOT NULL,
foreign_key int(10) NOT NULL,
field varchar(255) NOT NULL,
content mediumtext,
PRIMARY KEY (id),
# UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field),
# INDEX I18N_LOCALE_ROW(locale, model, foreign_key),
# INDEX I18N_LOCALE_MODEL(locale, model),
# INDEX I18N_FIELD(model, foreign_key, field),
# INDEX I18N_ROW(model, foreign_key),
INDEX locale (locale),
INDEX model (model),
INDEX row_id (foreign_key),
INDEX field (field)
);
Hasta aquí la configuración de la base de datos, ya tenemos las tablas preparadas para aceptar información.
Modelo
Ahora vamos a configurar el modelo Posts para indicarle a CakePHP cuales son los campos susceptibles de traducción y ate cabos sueltos. Editamos post.php agregando lo siguiente en $actsAs:
class Post extends AppModel
{
var $name = 'Post';
var $actsAs = array('Translate' => array('title', 'content'));
}
Se supone que a partir de ahora cuando guardemos datos a través de un formulario se guardará más o menos algo así:
INSERT INTO `posts` (`modified`,`created`) VALUES ('2008-02-15 13:40:21','2008-02-15 13:40:21')
INSERT INTO `i18n` (`locale`,`model`,`foreign_key`,`field`,`content`) VALUES ('eng','Post',1,'MyTitle','MyContent')
INSERT INTO `i18n` (`locale`,`model`,`foreign_key`,`field`,`content`) VALUES ('spa','Post',1,'MiTitulo','MiContenido')
Suponiendo que MyTitle, MyContent y MiTitulo, MiContenido sean los valores rellenados en los inputs del formulario :D. Bien, pues aqui es donde radica el principal problema. Según he probado -y por pruebas no ha sido- falla la foreign_key puesto que la cambia cada vez que intentamos guardar un idioma distinto, con lo que no se refiere al mismo Post y nada de lo anterior funciona.
Hack
La solución la he encontrado aquí, un pequeño hack al behavior y automágicamente podremos insertar varios idiomas de golpe. Una vez editado el archivo cake/libs/model/behaviors/translate.php y agregado el anterior hack todo será más sencillo, fijaos en las diferencias:
if (is_array($value)) {
foreach ($value as $loc=>$val) {
$tmploc = array('locale'=>$loc);
$RuntimeModel->create(array_unique(array_merge($conditions,$tmploc, array($RuntimeModel->displayField => $field, 'content' => $val))));
$RuntimeModel->save();
}
}
else {
$RuntimeModel->create(array_merge($conditions, array($RuntimeModel->displayField => $field, 'content' => $value)));
$RuntimeModel->save();
}
Ahora solo falta preparar los datos -osea, el formulario-.
Vista
Podemos hacerlo de varias formas, primero la guarra que no servirá de mucho si queremos agregar un idioma nuevo, puesto que tendremos que tocar todos los formularios de agregado/edición:
echo $form->create('Post');
# Spa
echo $form->input('Post.title.spa');
echo $form->input('Post.content.spa');
# Eng
echo $form->input('Post.title.eng');
echo $form->input('Post.content.eng');
# Por
echo $form->input('Post.title.por');
echo $form->input('Post.content.por');
echo $form->end('Submit');
Y la forma más lógica sería tener un array donde almacenamos todos los lenguajes que va a soportar nuestra aplicación, por ejemplo en config/config.php algo así:
$config['Settings'] = Configure::read('Settings');
$config['Settings'] = Set::merge(ife(empty($config['Settings']), array(), $config['Settings']), array
(
'default_language' => 'spa',
'languages' => array('eng','spa', 'por', 'gal'),
));
Ojo: Para cargar esta configuración debemos agregar una linea en config/bootstrap.php:
Configure::load('config');
Con lo que a la hora de crear el formulario de agregado/edición de datos todo sería más sencillo:
$languages=Configure::read('Settings.languages');
echo $form->create('Post');
foreach($languages as $lang)
{
echo $form->input('Post.title.'.$lang);
echo $form->input('Post.content.'.$lang);
}
echo $form->end('Submit');
Controlador
El punto final sería en el controlador, la funcion admin_add() o admin_edit() que se encargan de insertar-modificar los datos introducidos. No tiene mucho truco:
function admin_add()
{
if (!empty($this->data))
{
$this->Post->create();
$this->Post->save($this->data);
}
}
Conclusión
Así de simple. Creo que no se me olvida nada, aunque el descubrimiento ha sido reciente y he decidido escribirlo ahora que está fresco. Imagino que la integración con l10n será mucho más sencilla siempre que mantengamos la misma convención a la hora de llamar a los idiomas (spa, eng...).