Author: forresst
Date: 2010-02-12 16:09:07 +0100 (Fri, 12 Feb 2010)
New Revision: 27943

Added:
   doc/branches/1.2/jobeet/fr/19.txt
Log:
[doc-fr][1.2] Add doc in french, jobeet/19 rev:en/22448

Added: doc/branches/1.2/jobeet/fr/19.txt
===================================================================
--- doc/branches/1.2/jobeet/fr/19.txt                           (rev 0)
+++ doc/branches/1.2/jobeet/fr/19.txt   2010-02-12 15:09:07 UTC (rev 27943)
@@ -0,0 +1,1207 @@
+Jour 19 : Internationalisation et régionalisation
+=================================================
+
+Hier, nous avons terminé la fonctionnalité du moteur de recherche en la rendant
+encore plus fun avec l'ajout de quelques AJAX de qualité.
+
+Aujourd'hui, nous allons parler de 
l'**~internationalisation|Internationalisation~**
+(ou ~i18n|I18N~) de Jobeet et la **~régionalisation|Régionalisation~** (ou 
~l10n|L10n~). 
+
+Extrait de 
[Wikipedia](http://fr.wikipedia.org/wiki/Internationalisation_de_logiciel) :
+
+>L'**internationalisation** est le processus de conception d'un logiciel afin 
qu'il
+>puisse être adapté aux différentes ~langues|Langues~ et régions sans 
modifications
+>techniques.
+>
+>La **régionalisation** est le processus d'adaptation des logiciels à une 
région spécifique
+>ou à une langue en y ajoutant des éléments spécifiques ~locaux|Local~ et la
+>~traduction du texte|Traducions~.
+
+Comme toujours, le framework symfony n'a pas réinventé la roue, son support 
sur i18n
+et sur l10n est basé sur le [~standard ICU~](http://www.icu-project.org/).
+
+User
+----
+
+Aucune internationalisation n'est possible sans utilisateur. Quand votre site 
Web est
+disponible dans plusieurs langues ou pour différentes régions du monde, 
l'utilisateur
+est responsable de choisir celle qui lui convient le mieux.
+
+>**NOTE**
+>Nous avons déjà parlé de la classe User de symfony pendant la journée 13.
+
+### La ~Culture de l'utilisateur~
+
+Les caractéristiques i18n et l10n de symfony sont basées sur la 
**~culture|Culture~ de l'utilisateur**.
+La culture est la combinaison de la langue et du pays de l'utilisateur. Par 
exemple, la
+culture pour un utilisateur qui parle français est `fr` et la culture pour un 
utilisateur
+de la France est `fr_FR`.
+
+Vous pouvez gérer la culture de l'utilisateur en appelant les méthodes
+`setCulture()` et `getCulture()` sur l'objet User :
+
+    [php]
+    // in an action
+    $this->getUser()->setCulture('fr_BE');
+    echo $this->getUser()->getCulture();
+
+>**TIP**
+>La ~langue|Langue de l'utilisateur~ est codée par deux caractères minuscules, 
selon la
+>[norme ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) et le pays est
+>codé par deux caractères en majuscules, selon la
+>[norme ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1).
+
+### La culture préférée
+
+Par défaut, la culture de l'utilisateur est celle configurée dans le fichier de
+configuration ~`settings.yml`~ :
+
+    [yml]
+    # apps/frontend/config/settings.yml
+    all:
+      .settings:
+        default_culture: it_IT
+
+>**TIP**
+>Comme la culture est géré par l'objet User, il est stocké dans la
+>~session utilisateur|Session~. Au cours du développement, si vous changez la
+>~culture par défaut|Culture par défaut~, vous devrez effacer le 
~cookie|Cookies~
+>de votre session pour que le nouveau paramètre ait une influence dans votre 
navigateur.
+
+Lorsqu'un utilisateur démarre une session sur le site Jobeet, nous pouvons 
également
+déterminer la meilleure culture, sur la base des informations fournies par le 
~`Accept-Language`~
+de l'~entête HTTP|Entête HTTP~.
+
+La méthode `getLanguages()` de l'objet de requête renvoie un tableau des 
langues
+acceptées par l'utilisateur actuel, triées par ordre de préférence :
+
+    [php]
+    // in an action
+    $languages = $request->getLanguages();
+
+Mais la plupart du temps, votre site ne sera pas disponible dans les 136 
langues
+majeures du monde. La méthode `getPreferredCulture()` retourne le meilleur 
langage en
+comparant les langues préférées de l'utilisateur et les langues prises en 
charge par
+votre site web :
+
+    [php]
+    // in an action
+    $language = $request->getPreferredCulture(array('en', 'fr'));
+
+Dans l'appel précédent, la langue retournée sera anglais ou français selon
+les langues préférées de l'utilisateur, ou en anglais (la première langue dans
+le tableau) si aucune ne correspond.
+
+La culture dans l'URL
+---------------------
+
+Le site Web Jobeet sera disponible en anglais et en français. Comme une URL ne 
peut
+que représenter une ressource unique, la culture doit être incorporée dans 
l'URL. Pour
+ce faire, ouvrez le fichier ~`routing.yml`~, et ajoutez la variable spéciale 
`:sf_culture`
+pour toutes les routes sauf pour `api_jobs` et `homepage`. Pour les routes 
simples, ajoutez
+`/:sf_culture` devant `url`. Pour les collections de routes, ajoutez une option
+~`prefix_path`|Préfixe~ qui commence avec `/:sf_culture`.
+
+    [yml]
+    # apps/frontend/config/routing.yml
+    affiliate:
+      class: sfPropelRouteCollection
+      options:
+        model:          JobeetAffiliate
+        actions:        [new, create]
+        object_actions: { wait: get }
+        prefix_path:    /:sf_culture/affiliate
+
+    category:
+      url:     /:sf_culture/category/:slug.:sf_format
+      class:   sfPropelRoute
+      param:   { module: category, action: show, sf_format: html }
+      options: { model: JobeetCategory, type: object }
+      requirements:
+        sf_format: (?:html|atom)
+
+    job_search:
+      url:   /:sf_culture/search
+      param: { module: job, action: search }
+
+    job:
+      class: sfPropelRouteCollection
+      options:
+        model:          JobeetJob
+        column:         token
+        object_actions: { publish: put, extend: put }
+        prefix_path:    /:sf_culture/job
+      requirements:
+        token: \w+
+
+    job_show_user:
+      url:     /:sf_culture/job/:company_slug/:location_slug/:id/:position_slug
+      class:   sfPropelRoute
+<propel>
+      options:
+        model: JobeetJob
+        type: object
+        method_for_criteria: doSelectActive
+</propel>
+<doctrine>
+      options:
+        model: JobeetJob
+        type: object
+        method_for_query: retrieveActiveJob
+</doctrine>
+      param:   { module: job, action: show }
+      requirements:
+        id:        \d+
+        sf_method: get
+
+Lorsque la variable ~`sf_culture`~ est utilisée dans une route, symfony 
utilisera
+automatiquement sa valeur pour changer la culture de l'utilisateur.
+
+Comme nous avons besoin d'autant de pages d'accueil que de langue supportées
+(`/en/`, `/fr/`, ...), la page d'accueil par défaut (`/`) doit rediriger vers 
celle
+appropriée, conformément à la culture de l'utilisateur. Mais si l'utilisateur 
n'a pas
+encore de culture, parce qu'il agit pour la première fois sur Jobeet, la 
culture privilégiée
+sera choisie pour lui.
+
+D'abord, ajoutez la méthode `isFirstRequest()` à `myUser`. Elle retourne 
`true` seulement
+pour la première requête d'une session utilisateur :
+
+    [php]
+    // apps/frontend/lib/myUser.class.php
+    public function isFirstRequest($boolean = null)
+    {
+      if (is_null($boolean))
+      {
+        return $this->getAttribute('first_request', true);
+      }
+      else
+      {
+        $this->setAttribute('first_request', $boolean);
+      }
+    }
+
+Ajoutez la route `localized_homepage` :
+
+    [yml]
+    # apps/frontend/config/routing.yml
+    localized_homepage:
+      url:   /:sf_culture/
+      param: { module: job, action: index }
+      requirements:
+        sf_culture: (?:fr|en)
+
+Modifiez l'action `index` du module `job` pour implémenter la logique afin de 
rediriger
+l'utilisateur vers la «meilleure» page d'accueil lors de la première requête 
d'une session:
+
+    [php]
+    // apps/frontend/modules/job/actions/actions.class.php
+    public function executeIndex(sfWebRequest $request)
+    {
+      if (!$request->getParameter('sf_culture'))
+      {
+        if ($this->getUser()->isFirstRequest())
+        {
+          $culture = $request->getPreferredCulture(array('en', 'fr'));
+          $this->getUser()->setCulture($culture);
+          $this->getUser()->isFirstRequest(false);
+        }
+        else
+        {
+          $culture = $this->getUser()->getCulture();
+        }
+
+        $this->redirect('@localized_homepage');
+      }
+
+<propel>
+      $this->categories = JobeetCategoryPeer::getWithJobs();
+</propel>
+<doctrine>
+      $this->categories = Doctrine::getTable('JobeetCategory')->getWithJobs();
+</doctrine>
+    }
+
+Si la variable `sf_culture` n'est pas présente dans la requête, cela signifie 
que
+l'utilisateur est venu sur l'URL `/`. Si tel est le cas et que la session est 
nouvelle,
+la culture préférée est utilisée comme culture de l'utilisateur. Sinon, la 
culture actuelle
+de l'utilisateur est utilisée.
+
+La dernière étape consiste à rediriger l'utilisateur vers l'URL 
`localized_homepage`. Notez
+que la variable `sf_culture` n'a pas été passée dans l'appel de redirection 
puisque symfony
+l'ajoute automatiquement pour vous.
+
+Maintenant, si vous essayez d'aller à l'URL `/it/`, symfony va retourner une 
erreur
+~404|Erreur 404~ car nous avons limité la variable `sf_culture` `en` ou `fr`. 
Ajouter
+cette exigence à toutes les routes qui intègrent la culture :
+
+    [yml]
+    requirements:
+      sf_culture: (?:fr|en)
+
+~Culture~ ~Testing~
+-------------------
+
+Il est temps de tester notre implémentation. Mais avant d'ajouter plus de 
tests, nous avons
+besoin de corriger des objets existants. Comme toutes les URL ont changé, 
modifiez tous les fichiers
+de test fonctionnel dans `test/functional/frontend/` et ajoutez `/en` devant 
toutes les URLs. N'oubliez
+pas de changer également les URL dans le fichier
+`lib/test/JobeetTestFunctional.class.php`. Lancez la suite de test pour 
vérifier
+que vous avez correctement corrigé les tests :
+
+    $ php symfony test:functional frontend
+
+Le testeur de User donne une méthode `isCulture()` qui teste la culture de 
l'utilisateur
+actuel. Ouvrez le fichier `jobActionsTest` et ajoutez les tests suivants :
+
+    [php]
+    // test/functional/frontend/jobActionsTest.php
+    $browser->setHttpHeader('ACCEPT_LANGUAGE', 'fr_FR,fr,en;q=0.7');
+    $browser->
+      info('6 - User culture')->
+
+      restart()->
+
+      info('  6.1 - For the first request, symfony guesses the best culture')->
+      get('/')->
+      isRedirected()->followRedirect()->
+      with('user')->isCulture('fr')->
+
+      info('  6.2 - Available cultures are en and fr')->
+      get('/it/')->
+      with('response')->isStatusCode(404)
+    ;
+
+    $browser->setHttpHeader('ACCEPT_LANGUAGE', 'en,fr;q=0.7');
+    $browser->
+      info('  6.3 - The culture guessing is only for the first request')->
+
+      get('/')->
+      isRedirected()->followRedirect()->
+      with('user')->isCulture('fr')
+    ;
+
+Changer de langue
+-----------------
+
+Pour l'utilisateur qui veut changer la culture, un ~formulaire|Formulaires~ 
linguistique
+doit être ajoutée dans le layout. Le framework de formulaire ne prévoit pas 
une tel formulaire,
+mais comme le besoin est assez fréquent pour des sites web internationalisé, 
l'équipe de
+symfony maintient le
+[~`sfFormExtraPlugin`~](http://www.symfony-project.org/plugins/sfFormExtraPlugin?tab=plugin_readme),
+qui contient les ~validateurs|Validateurs~, les ~widgets|Widgets~ et les 
formulaires qui ne peuvent pas
+être inclus dans le package symfony principal car ils sont trop spécifiques ou 
qu'ils ont des dépendances
+externes mais ils sont néanmoins très utiles.
+
+Installez le plugin avec la tâche `plugin:install` :
+
+    $ php symfony plugin:install sfFormExtraPlugin
+
+Et videz le cache car le plugin définit de nouvelles classes :
+
+    $ php symfony cc
+
+>**NOTE**
+>Le `sfFormExtraPlugin` contient des widgets qui nécessitent des dépendances 
externes,
+>comme les bibliothèques JavaScript. Vous trouverez un widget pour la 
sélection de date,
+>un pour un éditeur WYSIWYG et d'autres encore. Prenez le temps de lire la 
documentation
+>où vous trouverez une foule de trucs utiles.
+
+Le plugin `sfFormExtraPlugin` offre un formulaire `sfFormLanguage` pour gérer 
la
+sélection de la langue. L'ajout du formulaire linguistique peut être fait dans 
le
+layout comme ceci :
+
+>**NOTE**
+>Le code ci-dessous n'est pas destiné à être mis en œuvre. Il est là pour vous 
montrer comment
+>vous pourriez être tenté de mettre en œuvre quelque chose d'une mauvaise 
manière. Nous allons
+>continuer à vous montrer comment l'implémenter correctement en utilisant 
symfony.
+
+    [php]
+    // apps/frontend/templates/layout.php
+    <div id="footer">
+      <div class="content">
+        <!-- footer content -->
+
+        <?php $form = new sfFormLanguage(
+          $sf_user,
+          array('languages' => array('en', 'fr'))
+          )
+        ?>
+        <form action="<?php echo url_for('@change_language') ?>">
+          <?php echo $form ?><input type="submit" value="ok" />
+        </form>
+      </div>
+    </div>
+
+Repérez vous un problème ? À droite, la création d'un objet de formulaire 
n'appartient pas à la
+couche de la Vue. Il doit être créé à partir d'une action. Mais, comme le code 
est dans le
+layout, le formulaire doit être créé pour chaque ~action|Action~, ce qui est 
loin d'être pratique.
+Dans de tels cas, vous devez utiliser un **component**. Un 
~component|Component~ est comme un partial,
+mais avec du code qui s'y rattachent. Considérez cela comme une action légère.
+
+L'inclusion d'un component à partir d'un Template peut être fait en utilisant 
le
+~Helper `include_component()`~ :
+
+    [php]
+    // apps/frontend/templates/layout.php
+    <div id="footer">
+      <div class="content">
+        <!-- footer content -->
+
+        <?php include_component('language', 'language') ?>
+      </div>
+    </div>
+
+Le helper prend le module et l'action comme arguments. Le troisième argument 
peut
+être utilisé pour passer des paramètres au component.
+
+Créez un module `language` pour accueillir le component et l'action qui va
+effectivement changer la langue de l'utilisateur :
+
+    $ php symfony generate:module frontend language
+
+Les components sont à définir dans le fichier `actions/components.class.php`.
+
+Créer ce fichier maintenant :
+
+    [php]
+    // apps/frontend/modules/language/actions/components.class.php
+    class languageComponents extends sfComponents
+    {
+      public function executeLanguage(sfWebRequest $request)
+      {
+        $this->form = new sfFormLanguage(
+          $this->getUser(),
+          array('languages' => array('en', 'fr'))
+        );
+      }
+    }
+
+Comme vous pouvez le voir, une classe components est très similaire à la 
classe des actions.
+
+Le Template pour un component utilise les mêmes conventions de nommage qu'un
+partial : un trait de soulignement (`_`), suivi par le nom du component :
+
+    [php]
+    // apps/frontend/modules/language/templates/_language.php
+    <form action="<?php echo url_for('@change_language') ?>">
+      <?php echo $form ?><input type="submit" value="ok" />
+    </form>
+
+Puisque le plugin ne prévoit pas l'action qui change effectivement la culture
+des utilisateurs, modifiez le fichier `routing.yml` pour créer la route
+de `change_language` :
+
+    [yml]
+    # apps/frontend/config/routing.yml
+    change_language:
+      url:   /change_language
+      param: { module: language, action: changeLanguage }
+
+Et créez l'action correspondante :
+
+    [php]
+    // apps/frontend/modules/language/actions/actions.class.php
+    class languageActions extends sfActions
+    {
+      public function executeChangeLanguage(sfWebRequest $request)
+      {
+        $form = new sfFormLanguage(
+          $this->getUser(),
+          array('languages' => array('en', 'fr'))
+        );
+
+        $form->process($request);
+
+        return $this->redirect('@localized_homepage');
+      }
+    }
+
+La méthode `process()` de `sfFormLanguage` prend soin de changer la culture de
+l'utilisateur, basé sur la soumission du formulaire de l'utilisateur.
+
+![Pied de page 
internationalisé](http://www.symfony-project.org/images/jobeet/1_2/19/footer.png)
+
+Internationalisation
+--------------------
+
+### Langues, ~Jeu de caractère~ et ~Encodage~
+
+Plusieurs langues ont des jeux de caractères différents. La langue anglaise 
est la
+plus simple car elle n'utilise que les caractères ~ASCII~, la langue française 
est un
+peu plus complexe avec des caractères accentués comme "é" et les langues comme 
le
+russe, le chinois ou l'arabe sont beaucoup plus complexes car tous leurs 
caractères sont
+en dehors de la plage ASCII. Ces langues sont définies avec des jeux de 
caractères
+totalement différents.
+
+Lorsqu'il s'agit de données internationalisées, il est préférable d'utiliser 
la norme
+unicode. L'idée derrière ~unicode|Unicode~ est d'établir un ensemble universel 
de
+caractères qui contient tous les caractères de toutes les langues. Le problème 
avec
+unicode est qu'un seul caractère peut être représenté avec pas moins de 21
+octets. Par conséquent, pour le web, nous utilisons ~UTF-8~, qui fait 
correspondre les
+points de code unicode à des séquences de longueur variable d'octets. En 
UTF-8, les langues
+les plus utilisés ont leurs caractères codés avec moins de 3 octets.
+
+UTF-8 est le codage par défaut utilisé par symfony, et il est défini dans le
+fichier de configuration `settings.yml` :
+
+    [yml]
+    # apps/frontend/config/settings.yml
+    all:
+      .settings:
+        charset: utf-8
+
+Aussi, pour activer la couche d'internationalisation de symfony, vous devez 
définir
+le paramètre `i18n` à `on` dans `settings.yml` :
+
+    [yml]
+    # apps/frontend/config/settings.yml
+    all:
+      .settings:
+        i18n: on
+
+### Templates
+
+Un site web internationalisé signifie que l'interface utilisateur est traduite 
en
+plusieurs langues.
+
+Dans un Template, toutes les chaînes qui dépendent de la langue doivent être 
entourées
+du ~`helper __()`~ (remarquez qu'il y a deux caractères de soulignement).
+
+Le helper `__()` fait parti du groupe d'helper `I18N`, qui contient des helpers
+qui facilitent la gestion i18n dans les Templates. Comme ce groupe de helper 
n'est pas
+chargé par défaut, vous devez soit l'ajouter manuellement dans chaque Template 
avec
+`use_helper('I18N')` comme nous l'avons fait pour le groupe d'helper `Text`, ou
+le charger globallement en l'ajoutant au ~paramètre `standard_helpers`~ :
+
+    [yml]
+    # apps/frontend/config/settings.yml
+    all:
+      .settings:
+        standard_helpers: [Partial, Cache, I18N]
+
+Voici comment utiliser le helper `__()` pour le pied de page de Jobeet :
+
+    [php]
+    // apps/frontend/templates/layout.php
+    <div id="footer">
+      <div class="content">
+        <span class="symfony">
+          <img src="/images/jobeet-mini.png" />
+          powered by <a href="http://www.symfony-project.org/";>
+          <img src="/images/symfony.gif" alt="symfony framework" /></a>
+        </span>
+        <ul>
+          <li>
+            <a href=""><?php echo __('About Jobeet') ?></a>
+          </li>
+          <li class="feed">
+            <?php echo link_to(__('Full feed'), '@job?sf_format=atom') ?>
+          </li>
+          <li>
+            <a href=""><?php echo __('Jobeet API') ?></a>
+          </li>
+          <li class="last">
+            <?php echo link_to(__('Become an affiliate'), '@affiliate_new') ?>
+          </li>
+        </ul>
+        <?php include_component('language', 'language') ?>
+      </div>
+    </div>
+
+>**NOTE**
+>Le helper `__()` peut prendre la chaîne de la langue par défaut ou vous pouvez
+>également utiliser un identificateur unique pour chaque chaîne. C'est juste 
une question de goût.
+>Pour Jobeet, nous allons utiliser la première stratégie ainsi les Teplates 
seront plus lisibles.
+
+Lorsque symfony rend un Template, chaque fois le helper `__()` est appelé,
+symfony regarde pour une traduction la culture de l'utilisateur actuel. Si une
+traduction est trouvée, elle est utilisée, sinon le premier argument est 
retourné
+comme une valeur de repli.
+
+Toutes les traductions sont stockées dans un ~catalogue|Catalogue de 
traduction~.
+Le framework i18n fournit un grand nombre de stratégies différentes pour 
stocker les traductions.
+Nous allons utiliser le format ["~XLIFF~"](http://en.wikipedia.org/wiki/XLIFF) 
qui est une norme et
+qui est la plus souple. C'est également le stockage utilisé pour l'admin 
generator et
+la plupart des plugins de symfony.
+
+>**NOTE**
+>Il existe d'autres catalogues de stockage comme ~`gettext`~, `MySQL` et 
`SQLite`. Comme toujours,
+>jetez un oeil à l'[API i18n](http://www.symfony-project.org/api/1_2/i18n) 
pour plus
+>de détails.
+
+### `i18n:extract`
+
+Au lieu de créer le fichier du catalogue à la main, utilisez la tâche
+intégrée ~`i18n:extract`|Tâche d'extraction I18n~ :
+
+    $ php symfony i18n:extract frontend fr --auto-save
+
+La tâche `i18n:extract` trouve toutes les chaînes qui doivent être traduits en 
`fr`
+dans l'application frontend et crée ou met à jour le catalogue correspondant.
+L'option `--auto-save` enregistre les nouvelles chaînes dans le catalogue.
+Vous pouvez également utiliser l'option `--auto-delete` pour supprimer 
automatiquement
+les chaînes qui n'existent plus.
+
+Dans notre cas, il remplit le fichier que nous avons créé :
+
+    [xml]
+    <!-- apps/frontend/i18n/fr/messages.xml -->
+    <?xml version="1.0" encoding="UTF-8"?>
+    <!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN"
+      "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd";>
+    <xliff version="1.0">
+      <file source-language="EN" target-language="fr" datatype="plaintext"
+          original="messages" date="2008-12-14T12:11:22Z"
+          product-name="messages">
+        <header/>
+        <body>
+          <trans-unit id="1">
+            <source>About Jobeet</source>
+            <target/>
+          </trans-unit>
+          <trans-unit id="2">
+            <source>Feed</source>
+            <target/>
+          </trans-unit>
+          <trans-unit id="3">
+            <source>Jobeet API</source>
+            <target/>
+          </trans-unit>
+          <trans-unit id="4">
+            <source>Become an affiliate</source>
+            <target/>
+          </trans-unit>
+        </body>
+      </file>
+    </xliff>
+
+Chaque traduction est géré par une balise `trans-unit` qui a un attribut `id`
+unique. Vous pouvez maintenant éditer ce fichier et ajouter des traductions 
pour
+la langue française :
+
+    [xml]
+    <!-- apps/frontend/i18n/fr/messages.xml -->
+    <?xml version="1.0" encoding="UTF-8"?>
+    <!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN"
+      "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd";>
+    <xliff version="1.0">
+      <file source-language="EN" target-language="fr" datatype="plaintext"
+          original="messages" date="2008-12-14T12:11:22Z"
+          product-name="messages">
+        <header/>
+        <body>
+          <trans-unit id="1">
+            <source>About Jobeet</source>
+            <target>A propos de Jobeet</target>
+          </trans-unit>
+          <trans-unit id="2">
+            <source>Feed</source>
+            <target>Fil RSS</target>
+          </trans-unit>
+          <trans-unit id="3">
+            <source>Jobeet API</source>
+            <target>API Jobeet</target>
+          </trans-unit>
+          <trans-unit id="4">
+            <source>Become an affiliate</source>
+            <target>Devenir un affilié</target>
+          </trans-unit>
+        </body>
+      </file>
+    </xliff>
+
+>**TIP**
+>Comme XLIFF est un format standard, de nombreux outils existent pour 
faciliter le
+>processus de traduction. [Open Language 
Tools](https://open-language-tools.dev.java.net/) est
+>un projet Open-Source en Java avec un éditeur XLIFF intégrée.
+
+-
+
+>**TIP**
+>Comme XLIFF est un format basé sur un fichier, les mêmes règles de priorité 
et de fusion
+>qui existent pour les autres fichiers de configuration de symfony sont 
également applicables.
+>Les fichiers i18n peuvent exister dans un projet, une application ou un 
module, et les
+traductions des fichiers les plus spécifiques substituent ceux des principaux.
+
+### Traductions avec arguments
+
+Le principe essentiel de l'internationalisation est de traduire des phrases
+entières. Mais certaines phrases intègrent des valeurs dynamiques. Dans Jobeet,
+c'est le cas sur la page d'accueil pour le lien du "more..." :
+
+    [php]
+    <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
+    <div class="more_jobs">
+      and <?php echo link_to($count, 'category', $category) ?> more...
+    </div>
+
+Le nombre d'emplois est une variable qui doit être remplacée par un espace 
réservé
+pour la traduction :
+
+    [php]
+    <!-- apps/frontend/modules/job/templates/indexSuccess.php -->
+    <div class="more_jobs">
+      <?php echo __('and %count% more...', array('%count%' => link_to($count, 
'category', $category))) ?>
+    </div>
+
+La chaîne à traduire est maintenant "and %count% more...", et l'espace réservé
+`%count%` sera remplacé par le nombre réel lors de l'exécution, grâce à la 
valeur
+donnée comme deuxième argument du helper `__()`.
+
+Ajoutez la nouvelle chaîne manuellement en insérant une balise `trans-unit` 
dans
+le fichier `messages.xml`, ou utilisez la tâche `i18n:extract` pour mettre à 
jour le
+fichier automatiquement :
+
+    $ php symfony i18n:extract frontend fr --auto-save
+
+Après l'exécution de la tâche, ouvrez le fichier XLIFF pour ajouter la 
traduction française :
+
+    [xml]
+    <trans-unit id="5">
+      <source>and %count% more...</source>
+      <target>et %count% autres...</target>
+    </trans-unit>
+
+La seule exigence dans la chaîne traduite est d'utiliser l'espace réservé 
`%count%`
+quelque part.
+
+Certaines autres chaînes sont encore plus complexes car ils impliquent les
+~pluriels|Pluriels (I18n)~. Selon certains chiffres, la syntaxe change, mais 
pas nécessairement
+de la même façon pour toutes les langues. Certaines langues ont des règles de 
grammaire très
+complexe pour les pluriels, comme le polonais ou le russe.
+
+Sur la page de catégorie, le nombre d'emplois dans la catégorie actuelle est 
affichée :
+
+    [php]
+    <!-- apps/frontend/modules/category/templates/showSuccess.php -->
+    <strong><?php echo $pager->getNbResults() ?></strong> jobs in this category
+
+Lorsqu'une phrase a différentes traductions, en fonction du nombre, le
+helper `format_number_choice()` doit être utilisée :
+
+    [php]
+    <?php echo format_number_choice(
+        '[0]No job in this category|[1]One job in this 
category|(1,+Inf]%count% jobs in this category',
+        array('%count%' => '<strong>'.$pager->getNbResults().'</strong>'),
+        $pager->getNbResults()
+      )
+    ?>
+
+Le ~Helper `format_number_choice()`~ prend trois arguments :
+
+  * La chaîne à utiliser en fonction du nombre
+  * Un tableau d'espace réservé
+  * Le numéro à utiliser pour déterminer le texte à utiliser
+ 
+La chaîne qui décrit les différentes traductions en fonction du nombre est
+formaté comme suit :
+
+  * Chaque possibilité est séparée par un caractère pipe (`|`)
+  * Chaque chaîne est composée d'une portée suivie de la traduction
+
+La ~portée|Portée (I18n)~ peut décrire n'importe quel éventail de nombres :
+
+  * `[1,2]`:     Accepte les valeurs entre 1 et 2 inclus
+  * `(1,2)`:     Accepte des valeurs comprises entre 1 et 2 en excluant 1 et 2
+  * `{1,2,3,4}`: Seules les valeurs définies sont acceptées
+  * `[-Inf,0)`:  Accepte les valeurs supérieures ou égales à l'infini négatif 
et
+                 strictement inférieur à 0
+  * `{n: n % 10 > 1 && n % 10 < 5}`: correspond à des nombres comme 2, 3, 4, 
22, 23, 24
+
+La traduction de la chaîne est similaire à d'autres chaînes de message :
+
+    [xml]
+    <trans-unit id="6">
+      <source>[0]No job in this category|[1]One job in this 
category|(1,+Inf]%count% jobs in this category</source>
+      <target>[0]Aucune annonce dans cette catégorie|[1]Une annonce dans cette 
catégorie|(1,+Inf]%count% annonces dans cette catégorie</target>
+    </trans-unit>
+
+Maintenant que vous savez comment internationaliser toutes sortes de chaines, 
prenez le
+temps d'ajouter l'appel `__()` à tous les Templates de l'application frontend. 
Nous n'allons
+pas internationaliser l'application backend.
+
+### ~Formulaires|Formulaires (I18n)~
+
+Les classes de formulaires contiennent de nombreuses chaines qui doivent être 
traduites,
+comme les labels, les messages d'erreur et les messages d'aide. Toutes ces 
chaînes sont
+automatiquement internationalisées par symfony, donc vous avez seulement 
besoin de fournir
+des traductions dans les fichiers XLIFF.
+
+>**NOTE**
+>Malheureusement, la tâche `i18n:extract` ne sait pas encore analyser les 
classes
+>de formulaires pour les chaînes non traduites.
+
+### Les objets de ##ORM##
+
+Pour le site web Jobeet, nous n'allons pas ~internationaliser tous les 
tables|Internationalisation du modèle~
+car il n'y a pas de sens de demander aux annonceurs de ~traduire|I18N 
(Modèle)~ leurs annonces
+d'emploi dans toutes les langues disponibles. Mais la table des catégories 
doit absolument
+être traduit.
+
+Le plugin de ##ORM## supporte la sortie des tables i18n. Pour chaque table qui
+contient des données localisées, deux tables doivent être créés : une pour les 
colonnes
+qui sont indépendantes de l'i18n, et l'autre avec les colonnes qui doivent être
+internationalisé. Les deux tables sont reliées par une relation 1-n.
+
+Mettez à jour le ~`schema.yml`|`schema.yml` (I18n)~ en conséquence :
+
+<propel>
+    [yml]
+    # config/schema.yml
+    jobeet_category:
+      _attributes:  { isI18N: true, i18nTable: jobeet_category_i18n }
+      id:           ~
+
+    jobeet_category_i18n:
+      id:           { type: integer, required: true, primaryKey: true,
+       ➥ foreignTable: jobeet_category, foreignReference: id }
+      culture:      { isCulture: true, type: varchar, size: 7,
+       ➥ required: true, primaryKey: true }
+      name:         { type: varchar(255), required: true }
+      slug:         { type: varchar(255), required: true }
+
+L'entrée `_attributes` définit les options pour la table.
+
+Et actualisez les ~fixtures|Fixtures (I18n)~ pour les catégories :
+
+    [yml]
+    # data/fixtures/010_categories.yml
+    JobeetCategory:
+      design:        { }
+      programming:   { }
+      manager:       { }
+      administrator: { }
+
+    JobeetCategoryI18n:
+      design_en:        { id: design, culture: en, name: Design }
+      programming_en:   { id: programming, culture: en, name: Programming }
+      manager_en:       { id: manager, culture: en, name: Manager }
+      administrator_en: { id: administrator, culture: en,
+       ➥ name: Administrator }
+
+      design_fr:        { id: design, culture: fr, name: Design }
+      programming_fr:   { id: programming, culture: fr,
+       ➥ name: Programmation }
+      manager_fr:       { id: manager, culture: fr, name: Manager }
+      administrator_fr: { id: administrator, culture: fr,
+       ➥ name: Administrateur }
+
+Reconstruisez le modèle pour créer les classes supplémentaires `i18n` :
+
+    $ php symfony propel:build-all --no-confirmation
+    $ php symfony cc
+
+Comme les colonnes `name` et `slug` ont été déplacées dans la table i18n, 
déplacez
+la méthode `setName()` de `JobeetCategory` vers `JobeetCategoryI18n` :
+
+    [php]
+    // lib/model/JobeetCategoryI18n.php
+    public function setName($name)
+    {
+      parent::setName($name);
+
+      $this->setSlug(Jobeet::slugify($name));
+    }
+
+Nous avons également besoin de corriger la méthode `getForSlug()` dans 
`JobeetCategoryPeer` :
+
+    [php]
+    // lib/model/JobeetCategoryPeer.php
+    static public function getForSlug($slug)
+    {
+      $criteria = new Criteria();
+      $criteria->addJoin(JobeetCategoryI18nPeer::ID, self::ID);
+      $criteria->add(JobeetCategoryI18nPeer::CULTURE, 'en');
+      $criteria->add(JobeetCategoryI18nPeer::SLUG, $slug);
+
+      return self::doSelectOne($criteria);
+    }
+</propel>
+<doctrine>
+    [yml]
+    # config/doctrine/schema.yml
+    JobeetCategory:
+      actAs:
+        Timestampable: ~
+        I18n:
+          fields: [name]
+          actAs:
+            Sluggable: { fields: [name], uniqueBy: [lang, name] }
+      columns:
+        name: { type: string(255), notnull: true }
+
+En se tournant sur le comportement `I18n`, un modèle nommé 
`JobeetCategoryTranslation`
+sera automatiquement créé et les `fields` spécifiés sont déplacés vers ce
+modèle.
+
+Remarquez que nous mettons simplement le comportement `I18n` et déplacons le 
comportement
+`Sluggable` pour être attaché au modèle `JobeetCategoryTranslation` 
automatiquement créé.
+L'option `uniqueBy` décrit pour le comportement `Sluggable` quels sont les 
champs qui déterminent
+si un slug est unique ou non. Dans notre cas, chaque slug doit être unique 
pour chaque paire
+`lang` et `name`.
+
+Et actualisez les ~fixtures|Fixtures (I18n)~ pour les catégories:
+
+    [yml]
+    # data/fixtures/categories.yml
+    JobeetCategory:
+      design:
+        Translation:
+          en:
+            name: Design
+          fr:
+            name: design
+      programming:
+        Translation:
+          en:
+            name: Programming
+          fr:
+            name: Programmation
+      manager:
+        Translation:
+          en:
+            name: Manager
+          fr:
+            name: Manager
+      administrator:
+        Translation:
+          en:
+            name: Administrator
+          fr:
+            name: Administrateur
+
+Nous avons besoin aussi de surcharger la méthode `findOneBySlug()` dans 
`JobeetCategoryTable`.
+Car Doctrine fournit quelques chercheurs magiques pour toutes les colonnes 
dans un modèle. Nous
+avons besoin de créer simplement la méthode `findOneBySlug()` afin de 
surcharger la fonctionnalité
+magique par défaut qu'offre Doctrine.
+
+Nous devons faire quelques modifications afin que la catégorie soit récupérée 
sur la
+base du slug anglais dans la table `JobeetCategoryTranslation`.
+
+    [php]
+    // lib/model/doctrine/JobeetCategoryTable.cass.php
+    public function findOneBySlug($slug)
+    {
+      $q = $this->createQuery('a')
+        ->leftJoin('a.Translation t')
+        ->andWhere('t.lang = ?', 'en')
+        ->andWhere('t.slug = ?', $slug);
+      return $q->fetchOne();
+    }
+
+Reconstruisez le modèle :
+
+    $ php symfony doctrine:build-all-reload --no-confirmation
+    $ php symfony cc
+
+</doctrine>
+
+>**TIP**
+<propel>
+>Comme la tâche `propel:build-all` supprime toutes les tables et les données 
de la base de données,
+</propel>
+<doctrine>
+>Comme la tâche `doctrine:build-all-reload` supprime toutes les tables et les 
données de la base de données,
+</doctrine>
+>n'oubliez pas de re-créer un utilisateur pour accéder au backend de
+>Jobeet avec la tâche `guard:create-user`. Autrement, vous pouvez ajouter un 
fichier fixture
+>pour l'ajouter automatiquement pour vous.
+
+<propel>
+Lors de la construction du modèle, symfony crée des méthodes proxy dans l'objet
+principal `JobeetCategory` pour accéder commodément aux colonnes i18n définies 
dans
+`JobeetCategoryI18n` :
+
+    [php]
+    $category = new JobeetCategory();
+
+    $category->setName('foo');       // sets the name for the current culture
+    $category->setName('foo', 'fr'); // sets the name for French
+
+    echo $category->getName();     // gets the name for the current culture
+    echo $category->getName('fr'); // gets the name for French
+</propel>
+<doctrine>
+Lorsque vous utilisez le comportement `I18n`, les proxies sont créés entre 
l'objet
+`JobeetCategory` et l'objet `JobeetCategoryTranslation`, donc toutes les 
anciennes fonctions
+pour récupérer le nom de la catégorie fonctionneront encore et récupéront la 
valeur pour la
+culture actuelle.
+
+    [php]
+    $category = new JobeetCategory();
+    $category->setName('foo'); // sets the name for the current culture
+    $category->getName(); // gets the name for the current culture
+
+    $this->getUser()->setCulture('fr'); // from your actions class
+
+    $category->setName('foo'); // sets the name for French
+    echo $category->getName(); // gets the name for French
+</doctrine>
+
+<propel>
+>**TIP**
+>Pour réduire le nombre de ~requêtes à la base de données|Performances~, 
utilisez la méthode `doSelectWithI18n()`
+>à la place de `doSelect()`. Cela permettra de récupérer l'objet principal et 
celui
+>du i18n dans une seule requête.
+>
+>     [php]
+>     $categories = JobeetCategoryPeer::doSelectWithI18n($c, $culture);
+</propel>
+<doctrine>
+>**TIP**
+>Pour réduire le nombre de ~requêtes à la base de données|Performances~, 
joignez `JobeetCategoryTranslation`
+>dans vos requêtes. Cela permettra de récupérer l'objet principal et celui du 
i18n dans une seule
+>requête.
+>
+>     [php]
+>     $categories = Doctrine_Query::create()
+>       ->from('JobeetCategory c')
+>       ->leftJoin('c.Translation t WITH t.lang = ?', $culture)
+>       ->execute();
+>
+>Le mot clé `WITH` ci-dessus va ajouter une condition à la condition `ON` 
ajoutée
+>automatiquement sur de la requête. Ainsi, la condition `ON` de la jointure 
sera à la
+>fin.
+>
+>     [sql]
+>     LEFT JOIN c.Translation t ON c.id = t.id AND t.lang = ?
+</doctrine>
+
+Comme la route `category` est liée à la classe du modèle `JobeetCategory` et
+<propel>
+parce que le `slug` fait maintenant partie de `JobeetCategoryI18n`, la route 
n'est pas en mesure
+</propel>
+<doctrine>
+parce que le `slug` fait maintenant partie de `JobeetCategoryTranslation`, la 
route
+n'est pas en mesure
+</doctrine>
+de récupérer l'objet `Category` automatiquement. Pour aider le système de 
routage,
+nous allons créer une méthode qui se chargera de la récupération de l'objet :
+
+<propel>
+    [php]
+    // lib/model/JobeetCategoryPeer.php
+    class JobeetCategoryPeer extends BaseJobeetCategoryPeer
+    {
+      static public function doSelectForSlug($parameters)
+      {
+        $criteria = new Criteria();
+        $criteria->addJoin(JobeetCategoryI18nPeer::ID, JobeetCategoryPeer::ID);
+        $criteria->add(JobeetCategoryI18nPeer::CULTURE, 
$parameters['sf_culture']);
+        $criteria->add(JobeetCategoryI18nPeer::SLUG, $parameters['slug']);
+
+        return self::doSelectOne($criteria);
+      }
+    }
+</propel>
+<doctrine>
+Comme nous avions déjà surchargé `findOneBySlug()`, refactorisons un peu plus 
afin
+que ces méthodes puissent être partagées. Nous allons créer des nouvelles 
méthodes
+`findOneBySlugAndCulture()` et `doSelectForSlug()` et changer la méthode 
`findOneBySlug()`
+pour utiliser simplement la méthode `findOneBySlugAndCulture()`.
+
+    [php]
+    // lib/model/doctrine/JobeetCategoryTable.class.php
+    public function doSelectForSlug($parameters)
+    {
+      return $this->findOneBySlugAndCulture($parameters['slug'], 
$parameters['sf_culture']);
+    }
+
+    public function findOneBySlugAndCulture($slug, $culture = 'en')
+    {
+      $q = $this->createQuery('a')
+        ->leftJoin('a.Translation t')
+        ->andWhere('t.lang = ?', $culture)
+        ->andWhere('t.slug = ?', $slug);
+      return $q->fetchOne();
+    }
+
+    public function findOneBySlug($slug)
+    {
+      return $this->findOneBySlugAndCulture($slug, 'en');
+    }
+</doctrine>
+
+Ensuite, utilisez l'~option `method`|option `method` (Routage)~ pour dire à la 
route `category`
+d'utiliser la méthode `doSelectForSlug()` pour récupérer l'objet :
+
+    [yml]
+    # apps/frontend/config/routing.yml
+    category:
+      url:     /:sf_culture/category/:slug.:sf_format
+      class:   sfPropelRoute
+      param:   { module: category, action: show, sf_format: html }
+      options: { model: JobeetCategory, type: object, method: doSelectForSlug }
+      requirements:
+        sf_format: (?:html|atom)
+
+Nous avons besoin de recharger les jeux de test pour régénérer les slugs 
adéquates
+pour les catégories :
+
+    $ php symfony propel:data-load
+
+Maintenant, la route `category` est internationalisé et l'URL pour une 
catégorie
+intègre le slug de la catégorie traduite :
+
+    /frontend_dev.php/fr/category/programmation
+    /frontend_dev.php/en/category/programming
+
+### Admin Generator
+
+A cause d'un bogue dans symfony 1.2.1, vous devez commenter le `title` dans la
+section `edit` :
+
+    [yml]
+    # apps/backend/modules/category/config/generator.yml
+    edit:
+      #title: Editing Category "%%name%%" (#%%id%%)
+
+Pour le backend, nous voulons que les traductions françaises et anglaises 
soient éditées
+dans le même formulaire :
+
+![Les catégories du 
backend](http://www.symfony-project.org/images/jobeet/1_2/19/backend_categories.png)
+
+L'intégration d'un ~formulaire i18n|Formulaire (Traduction)~ peut être fait en
+utilisant la méthode `embedI18N()` :
+
+    [php]
+    // lib/form/JobeetCategoryForm.class.php
+    class JobeetCategoryForm extends BaseJobeetCategoryForm
+    {
+      public function configure()
+      {
+<propel>
+        unset($this['jobeet_category_affiliate_list']);
+</propel>
+<doctrine>
+        unset(
+          $this['jobeet_affiliates_list'],
+          $this['created_at'], $this['updated_at']
+        );
+</doctrine>
+
+        $this->embedI18n(array('en', 'fr'));
+        $this->widgetSchema->setLabel('en', 'English');
+        $this->widgetSchema->setLabel('fr', 'French');
+      }
+    }
+
+L'interface de l'admin generator supporte l'internationalisation. Il est livré
+avec des traductions pour plus de 20 langues, et il est très facile d'en 
ajouter une
+nouvelle, ou pour d'en personnaliser une existante. Copiez le fichier pour la 
langue que
+vous souhaitez personnaliser depuis symfony (les traductions de l'admin se 
trouvent dans
+<propel>
+`lib/vendor/symfony/lib/plugins/sfPropelPlugin/i18n/`) dans le répertoire
+</propel>
+<doctrine>
+`lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/i18n/`) dans le répertoire
+</doctrine>
+`i18n` de l'application. Comme le fichier dans votre application sera fusionné 
avec celle de
+symfony, ne conservez que les chaines modifiées dans le fichier de 
l'application.
+
+Vous remarquerez que les fichiers de traduction de l'admin generator sont 
nommés
+`sf_admin.fr.xml`, au lieu de `fr/messages.xml`. En réalité, `messages` est le 
nom
+du catalogue par défaut utilisé par symfony et il peut être modifié pour 
permettre une
+meilleure séparation entre les différentes parties de votre application. En 
utilisant
+un autre catalogue que celui par défaut, cela nécessite que vous le spécifier 
lorsque
+vous utilisez le helper `__()` :
+
+    [php]
+    <?php echo __('About Jobeet', array(), 'jobeet') ?>
+
+Dans l'appel précédent de `__()`, symfony va chercher la chaîne "About Jobeet" 
dans
+le catalogue `jobeet`.
+
+### Tests
+
+La correction des ~tests|I18n (Test)~ est une partie intégrante de la 
migration de
+l'internationalisation. Premièrement, mettez à jour les jeux de test pour les 
catégories
+en copiant les jeux de test que nous avons
+<propel>
+défini ci-dessus dans `test/fixtures/010_categories.yml`.
+</propel>
+<doctrine>
+défini ci-dessus dans `test/fixtures/categories.yml`.
+</doctrine>
+
+Reconstruisez le modèle de l'environnement de `test` :
+
+<propel>
+    $ php symfony propel:build-all-load --no-confirmation --env=test
+</propel>
+<doctrine>
+    $ php symfony doctrine:build-all-reload --no-confirmation --env=test
+</doctrine>
+
+Vous pouvez maintenant lancer tous les tests pour vérifier qu'ils s'exécutent 
bien :
+
+    $ php symfony test:all
+
+>**NOTE**
+>Quand nous avons développé l'interface backend pour Jobeet, nous n'avons pas 
écrit
+>les tests fonctionnels. Mais chaque fois que vous créez un module avec la 
ligne de commande
+>de symfony, symfony produit aussi des bouts de test. Ces bouts sont sûres 
d'être enlevés.
+
+Régionalisation
+---------------
+
+### ~Templates~
+
+Le support des différentes cultures, c'est aussi le soutien de différents 
formats pour les dates
+et les chiffres. Dans un Template, plusieurs helpers sont à votre disposition 
pour vous
+aider à prendre en compte toutes ces différences, basée sur la culture 
actuelle de l'utilisateur :
+
+Dans le groupe d'helper
+[`Date`](http://www.symfony-project.org/api/1_2/DateHelper) :
+
+ | Helper                         | Description                                
                |
+ | ------------------------------ | 
---------------------------------------------------------- |
+ | `format_date()`                | Formate la date                            
                |
+ | `format_datetime()`            | Formate la date avec l'heure (heures, 
minutes, secondes)   |
+ | `time_ago_in_words()`          | Affiche le temps écoulé entre une date et 
maintenant       |
+ | `distance_of_time_in_words()`  | Affiche le temps écoulé entre deux dates   
                |
+ | `format_daterange()`           | Formate un intervalle de dates             
                |
+
+Dans le groupe d'helper
+[`Number`](http://www.symfony-project.org/api/1_2/NumberHelper) :
+
+ | Helper              | Description          |
+ | ------------------- | -------------------- |
+ | `format_number()`   | Formate un nombre    |
+ | `format_currency()` | Formate une monnaie  |
+
+Dans le groupe d'helper
+[`I18N`](http://www.symfony-project.org/api/1_2/I18NHelper) :
+
+ | Helper              | Description                     |
+ | ------------------- | ------------------------------- |
+ | `format_country()`  | Affiche le nom d'un pays        |
+ | `format_language()` | Affiche le nom d'une langue     |
+
+### ~Formulaires (I18n)~ 
+
+Le framework de formulaire fournit plusieurs ~widgets|Widgets (I18n)~ et 
~validateurs|Validateurs (I18n)~ pour régionalisé les données :
+
+ * 
[`sfWidgetFormI18nDate`](http://www.symfony-project.org/api/1_2/sfWidgetFormI18nDate)
+ * 
[`sfWidgetFormI18nDateTime`](http://www.symfony-project.org/api/1_2/sfWidgetFormI18nDateTime)
+ * 
[`sfWidgetFormI18nTime`](http://www.symfony-project.org/api/1_2/sfWidgetFormI18nTime)
+
+ * 
[`sfWidgetFormI18nSelectCountry`](http://www.symfony-project.org/api/1_2/sfWidgetFormI18nSelectCountry)
+ * 
[`sfWidgetFormI18nSelectCurrency`](http://www.symfony-project.org/api/1_2/sfWidgetFormI18nSelectCurrency)
+ * 
[`sfWidgetFormI18nSelectLanguage`](http://www.symfony-project.org/api/1_2/sfWidgetFormI18nSelectLanguage)
+
+ * 
[`sfValidatorI18nChoiceCountry`](http://www.symfony-project.org/api/1_2/sfValidatorI18nChoiceCountry)
+ * 
[`sfValidatorI18nChoiceLanguage`](http://www.symfony-project.org/api/1_2/sfValidatorI18nChoiceLanguage)
+
+À demain
+--------
+
+L'internationalisation et la régionalisation sont des citoyens de première 
classe dans symfony.
+Fournir un site régionalisé à vos utilisateurs est très facile car symfony 
fournit tous les
+outils de base et vous donne même les tâches en ligne de commande pour le 
faire rapidement.
+
+Soyez prêts pour un tutoriel très spécial demain où nous déplacerons beaucoup 
de
+fichiers et explorons une approche différente de l'organisation d'un projet
+symfony.
+
+__ORM__

-- 
You received this message because you are subscribed to the Google Groups 
"symfony SVN" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/symfony-svn?hl=en.

Reply via email to