![]() |
|
symfony Forms in ActionChapitre 8 - L'Internationalisation et la Localisation |
|
You are currently reading "symfony Forms in Action" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.

|
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. |
La plupart des applications web populaires sont consultables dans différentes langues et sont même parfois localisées en fonction du pays de l'internaute. Symfony apporte une solution efficace pour gérer ces problématiques à travers un sous-framework dédié (voir le chapitre "I18n And L10n" du livre symfony).
Le framework de formulaires permet également la gestion native de la traduction des interfaces et des objets internationalisés.
Dans un projet symfony, les formulaires sont internationalisables par défaut. On peut donc traduire les labels des champs de formulaire, les textes d'aide et les messages d'erreurs, en éditant les fichiers de traduction au format XLIFF, gettext ou tout autre format supporté par symfony.
Le Listing 8-1 reprend le code du formulaire de contact que nous avons développé dans les premiers chapitres.
Listing 8-1 - Formulaire de Contact
class ContactForm extends sfForm { public function configure() { $this->setWidgets(array( 'name' => new sfWidgetFormInput(), // le label sera "Name" par défaut 'email' => new sfWidgetFormInput(), // le label sera "Email" par défaut 'body' => new sfWidgetFormTextarea(), // le label sera "Body" par défaut )); // Changeons par exemple le label du champ email $this->widgetSchema->setLabel('email', 'Email address'); } }
Nous pouvons définir directement la traduction des noms de champs dans le fichier XLIFF correspondant. Le Listing 8-2 montre le fichier de traduction pour le français.
Listing 8-2 - Fichier de traduction XLIFF
// apps/frontend/i18n/messages.fr.xml <?xml version="1.0" ?> <xliff version="1.0"> <file original="global" source-language="en" datatype="plaintext"> <body> <trans-unit> <source>Name</source> <target>Nom</target> </trans-unit> <trans-unit> <source>Email address</source> <target>Adresse email</target> </trans-unit> <trans-unit> <source>Body</source> <target>Message</target> </trans-unit> </body> </file> </xliff>
Si vous utilisez les dictionnaires de stockage des chaînes de traduction de symfony (http://www.symfony-project.org/book/1_2/13-I18n-and-L10n#chapter_13_sub_managing_dictionaries), vous pouvez spécifier à quel dictionnaire les éléments textuels de l'interface du formulaire courant vont être associés. Dans le Listing 8-3, les éléments textuels de l'interface du formulaire ContactForm traduits en français seront stockés dans le fichier contact_form.fr.xml.
Listing 8-3 - Personnalisation du Dictionnaire de Traduction
class ContactForm extends sfForm { public function configure() { // ... $this->widgetSchema->getFormFormatter()->setTranslationCatalogue('contact_form'); } }
L'utilisation de dictionnaires dédiés aux formulaires est très pratique pour spécialiser et identifier rapidement les fichiers contenant les traductions.
Il peut s'avérer intéressant de générer des messages d'erreurs reprenant la valeur soumise par l'utilisateur dans la description de l'erreur (par exemple, "L'adresse email user@domain n'est pas valide."). Cela peut se faire depuis la classe de définition du formulaire en définissant des messages d'erreur personnalisés utilisant des références aux noms des paramètres de configuration du validateur associé, sous la forme %paramètre% comme nous l'avons déjà vu.
Le Listing 8-4 applique ce principe au champ name du formulaire de contact.
Listing 8-4 - Internationalisation des Messages d'Erreurs
class ContactForm extends sfForm { public function configure() { // ... $this->validatorSchema['name'] = new sfValidatorEmail( array('min_length' => 2, 'max_length' => 45), array('min_length' => 'Name "%value%" must be at least %min_length% characters.', 'max_length' => 'Name "%value%" must not exceed %max_length% characters.', ), ); } }
Nous pouvons maintenant référencer ces messages directement dans le fichier de traduction XLIFF francophone comme dans le Listing 8-5.
Listing 8-5 - Fichier XLIFF de traduction des Messages d'Erreurs
<trans-unit> <source>Name "%value%" must be at least %min_length% characters</source> <target>Le nom "%value%" doit comporter un minimum de %min_length% caractères</target> </trans-unit> <trans-unit> <source>Name "%value%" must not exceed %max_length% characters</source> <target>Le nom "%value%" ne peut comporter plus de %max_length% caractères</target> </trans-unit>
Il se peut cependant que vous désiriez utiliser un autre mécanisme de traduction de l'interface que celui proposé par symfony, par exemple dans le cas d'une utilisation avec un autre framework. Dans ce cas, vous devez définir un objet de traduction personnalisé.
Un objet de traduction se présente sous la forme d'un callable PHP. Plus spécifiquement, ces références peuvent être de trois types :
une chaîne de caractères comportant le nom d'une fonction comme par example ma_fonction
un tableau contenant une référence à une instance de classe et le nom de l'une de ses méthodes, par exemple array($unObjet, 'uneDeSesMethodes')
une instance d'objet de type sfCallable, classe permettant d'encapsuler facilement un callable dans un objet en PHP
En PHP, un callable est une référence à une fonction ou méthode de classe existante pouvant être appelés par le langage. On peut aussi définir un callable comme étant tout objet PHP renvoyant
truesi passé à la fonctionis_callable().
Par exemple, imaginons une classe d'internationalisation (typiquement le genre de chose dont on hérite en récupérant un projet existant), volontairement simple comme le montre le Listing 8-6.
Listing 8-6 - Classe I18N
class myI18n { static protected $default_culture = 'en'; static protected $messages = array('fr' => array( 'Name' => 'Nom', 'Email' => 'Courrier électronique', 'Subject' => 'Sujet', 'Body' => 'Message', )); static public function translateText($text) { $culture = isset($_SESSION['culture']) ? $_SESSION['culture'] : self::$default_culture; if (array_key_exists($culture, self::$messages) && array_key_exists($text, self::$messages[$culture])) { return self::$messages[$_SESSION['culture']][$text]; } return $text; } } // Utilisation type de la classe exemple $myI18n = new myI18n(); $_SESSION['culture'] = 'en'; echo $myI18n->translateText('Subject'); // => affiche "Subject" $_SESSION['culture'] = 'fr'; echo $myI18n->translateText('Subject'); // => affiche "Sujet"
Chaque formulaire peut définir son propre callable de gestion de l'internationalisation comme montré dans le Listing 8-7.
Listing 8-7 - Définition d'un Filtre d'Internationalisation de Formulaire
class ContactForm extends sfForm { public function configure() { // ... $this->widgetSchema->getFormFormatter()->setTranslationCallable(array(new myI18n(), 'translateText')); } }
La fonction de traduction de formulaires peut prendre jusqu'à trois arguments :
Le texte à traduire ;
un tableau associatif d'arguments à remplacer dans le texte original, typiquement les paramètres dynamiques évoqués un peu plus haut dans ce chapitre ;
un nom de dictionnaire sous la forme d'une chaîne de caractères, permettant de préciser un espace de nom associé au texte à traduire.
Pour y voir plus clair, regardons de plus près la valeur retournée par la méthode sfFormWidgetSchemaFormatter::translate() :
return call_user_func(self::$translationCallable, $subject, $parameters, $catalogue);
La propriété self::$translationCallable étant une référence au callable PHP, ce dernier sera donc appelé de la manière suivante :
$myI18n->translateText($subject, $parameters, $catalogue);
Si nous devions réécrire la classe MyI18n afin de tirer parti de ces paramètres supplémentaires reçus en arguments, elle pourrait devenir :
class myI18n { static protected $default_culture = 'en'; static protected $messages = array('fr' => array( 'messages' => array( 'Name' => 'Nom', 'Email' => 'Courrier électronique', 'Subject' => 'Sujet', 'Body' => 'Message', ), )); static public function translateText($text, $arguments = array(), $catalogue = 'messages') { $culture = isset($_SESSION['culture']) ? $_SESSION['culture'] : self::$default_culture; if (array_key_exists($culture, self::$messages) && array_key_exists($messages, self::$messages[$culture] && array_key_exists($text, self::$messages[$culture][$messages])) { $text = self::$messages[$_SESSION['culture']][$messages][$text]; $text = strtr($text, $arguments); } return $text; } }
Pourquoi les Méthodes de Définition du Traducteur et du Catalogue appartiennent-elles à la Classe
sfWidgetFormSchemaFormatter?Comme nous l'avons vu précédemment, le framework de formulaire est lui-même basé sur une architecture MVC. La classe
sfWidgetFormSchemaFormatterappartient à la couche Vue, elle est donc la plus à même d'intercepter tous les éléments textuels, puisque chargée du rendu de l'interface, et d'appliquer un éventuel traitement spécifique à ces derniers.
Si vous utilisez des objets Propel internationalisés, le framework de formulaires les prend en charge nativement. Prenons un exemple très simple de modèle de données internationalisé :
propel:
article:
id:
author: varchar(255)
created_at:
article_i18n:
title: varchar(255)
content: longvarchar
Générons les classes Propel et les classes de formulaires correspondantes :
$ php symfony build:model
$ php symfony build:forms
Vous noterez que les fichiers suivants ont été générés dans votre projet symfony :
lib/
form/
ArticleForm.class.php
ArticleI18nForm.class.php
BaseFormPropel.class.php
model/
Article.php
ArticlePeer.php
ArticleI18n.php
ArticleI18nPeer.php
Éditons la classe ArticleForm afin de configurer les langues que nous proposerons à la saisie (par défaut, le formulaire de base n'en inclut aucune). Ici, proposons le français et l'anglais au sein du même formulaire comme montré dans le Listing 8-8.
Listing 8-8 - Formulaires I18n pour un objet Propel internationalisé
class ArticleForm extends BaseArticleForm { public function configure() { $this->embedI18n(array('en', 'fr')); } }
Si vous désirez personnaliser les labels de langues dans le formulaire, vous pouvez le faire dans la méthode configure() de la classe de formulaire comme décrit dans le Listing 8-9 :
Listing 8-9 - Personnalisation des Labels de langue
$this->widgetSchema->setLabel('en', 'English'); $this->widgetSchema->setLabel('fr', 'French');
Figure 8-1 - Formulaire Propel Internationalisé

L'immense intérêt du framework de formulaire est ici qu'un appel à la méthode save() de l'objet de formulaire sauvera non seulement notre article mais la ou les traductions que nous lui aurons associées depuis le formulaire.
Comment passer la Culture de l'Utilisateur à un Formulaire ?
Si vous désirez proposer la saisie des données du formulaire dans la langue courante de l'utilisateur, il suffit de passer la culture en option du formulaire :
class articleActions extends sfActions { public function executeCreate($request) { $this->form = new ArticleForm(null, array('culture' => $this->getUser()->getCulture())); if ($request->isMethod('post') && $this->form->bindAndSave($request->getParameter('article'))) { $this->redirect('article/created'); } } }Dans la classe
ArticleForm, il suffit de récupérer la valeur de la culture passée en option :class ArticleForm extends BaseArticleForm { public function configure() { $this->embedI18n(array($this->getCurrentCulture())); } public function getCurrentCulture() { return isset($this->options['culture']) ? $this->options['culture'] : 'en'; } }
Pour proposer des champs de saisie localisés, c'est à dire formattés en fonction d'une certaine localisation géographique de l'utilisateur, au sein de formulaire, vous pouvez utiliser des widgets dédiés :
Pour proposer un champ de saisie de date et heure localisé dans le format lié aux usages de la langue courante de l'utilisateur, ces différents widgets sont disponibles :
Le widget sfWidgetFormI18nDate permet la saisie d'une date (jour, mois et année) :
$this->widgetSchema['published_on'] = new sfWidgetFormI18nDate(array('culture' => 'fr'));
Il est possible de préciser le format d'affichage du mois au moyen de l'option month_format qui peut prendre trois valeurs :
name permet d'afficher le nom du mois (c'est la valeur par défaut pour ce widget)short_name affichera le nom du mois sous une forme abrégéenumber affichera le numéro du mois dans l'année (de 1 à 12)Le widget sfWidgetFormI18nTime permet la saisie d'une heure (heures, minutes et secondes) :
$this->widgetSchema['published_on'] = new sfWidgetFormI18nTime(array('culture' => 'fr'));
Le widget sfWidgetFormI18nDateTime permet la saisie d'une date et d'un couple heures/minutes :
$this->widgetSchema['published_on'] = new sfWidgetFormI18nDateTime(array('culture' => 'fr'));
Le widget sfWidgetFormI18nSelectCountry permet de présenter à l'utilisateur une liste déroulante contenant les noms de pays traduits dans une langue donnée :
$this->widgetSchema['country'] = new sfWidgetFormI18nSelectCountry(array('culture' => 'fr'));
Vous pouvez au besoin restreindre les noms de pays listés au moyen de l'option countries :
$countries = array('fr', 'en', 'es', 'de', 'nl'); $this->widgetSchema['country'] = new sfWidgetFormI18nSelectCountry(array('culture' => 'fr', 'countries' => $countries));
Le widget sfWidgetFormI18nSelectLanguage permet de présenter à l'utilisateur une liste déroulante contenant les noms des langues traduites dans une langue donnée. Exemple en français :
$this->widgetSchema['language'] = new sfWidgetFormI18nSelectLanguage(array('culture' => 'fr'));
Vous pouvez au besoin restreindre les noms de langues listées au moyen de l'option languages :
$languages = array('fr', 'en', 'es', 'de', 'nl'); $this->widgetSchema['language'] = new sfWidgetFormI18nSelectLanguage(array('culture' => 'fr', 'languages' => $languages));
If you find a typo or an error, please register and open a ticket.
If you need support or have a technical question, please post to the official user mailing-list.