Symfony2 - Jobeet - Jour 19 - Internationalisation et localisation

1 1 1 1 1 1 1 1 1 1 Rating 0.00 (0 Votes)
Submit to DeliciousSubmit to DiggSubmit to FacebookSubmit to Google PlusSubmit to StumbleuponSubmit to TechnoratiSubmit to TwitterSubmit to LinkedIn

Hier (Symfony2 - Jobeet - Jour 18 - AJAX), nous avons terminé la fonctionnalité du moteur de recherche en faisant encore plus fun avec l'ajout d'AJAX. Maintenant, nous allons parler de  l'internationalisation (i18n ou) et la localisation (ou l10n) de Jobeet. 

Wikipédia :
L'internationalisation est le processus de conception d'un logiciel de sorte qu'il peut être adapté à différentes langues et régions sans modifications techniques.
La localisation est le processus d'adaptation des logiciels pour une région ou une langue spécifique en ajoutant des composants spécifiques de la localisation et de la traduction du texte. 

Utilisateur

Aucune internationalisation n'est possible sans un utilisateur. Lorsque votre site est disponible en plusieurs langues ou pour différentes régions du monde, l'utilisateur est responsable de choisir celle qui lui convient le mieux.
Les caractéristiques i18n et l10n de symfony sont basées sur la culture de l'utilisateur. La culture est la combinaison de la langue du pays et 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.
Les traductions sont assurées par un service de traducteurs qui utilise la locale de l'utilisateur pour rechercher et retourner les messages traduits. Avant de l'utiliser, activez le Translator dans votre configuration :

  • app/config/config.yml
# ...

framework:
    #esi:             ~
    translator:      { fallback: en }
    # ...
    default_locale:  "en"

# ...

Culture dans l'URL

Le site 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 d'environnement local. Pour les routes simples, ajoutez /{_locale} à l'avant de l'url :

  •  src/Erlem/JobeetBundle/Resources/config/routing.yml
login:
    pattern: /login
    defaults: { _controller: ErlemJobeetBundle:Default:login }

login_check:
    pattern: /login_check

logout:
  pattern: /logout

ErlemJobeetBundle_category:
    pattern:  /{_locale}/category/{slug}/{page}
    defaults: { _controller: ErlemJobeetBundle:Category:show, page: 1 }   
    requirements:
        _locale: en|fr

ErlemJobeetBundle_job:
    resource: "@ErlemJobeetBundle/Resources/config/routing/job.yml"
    prefix:   /{_locale}/job
    requirements: 
        _locale: en|fr

erlem_jobeet_homepage:
    pattern:  /
    defaults: { _controller: ErlemJobeetBundle:Job:index } 

ErlemJobeetBundle_api:
    pattern: /api/{token}/jobs.{_format}
    defaults: {_controller: "ErlemJobeetBundle:Api:list"}
    requirements:
        _format: xml|json|yaml

ErlemJobeetBundle_erlem_affiliate:
    resource: "@ErlemJobeetBundle/Resources/config/routing/affiliate.yml"
    prefix:   /{_locale}/affiliate       
    requirements: 
        _locale: en|fr

Comme nous avons besoin que les pages d'accueil soient supportées par les langues (/en/, /fr/, ...), la page d'accueil par défaut (/) doit rediriger vers celle appropriée, en fonction de la culture de l'utilisateur. Mais si l'utilisateur n'a pas encore de culture, parce qu'il utilise Jobeet pour la première fois, la culture privilégiée sera choisie pour lui (comme on le déclare précédemment, il sera /en/). Donc, il faut modifier le fichier route :

  • src/Erlem/Resources/config/routing.yml
# ...
erlem_jobeet_homepage:
    pattern:  /{_locale}/
    defaults: { _controller: ErlemJobeetBundle:Job:index }
    requirements: 
        _locale: en|fr
# ...

ErlemJobeetBundle_nonlocalized:
    pattern:  /
    defaults: { _controller: "ErlemJobeetBundle:Job:index" }

Ajoutons et modifions le comportement de ces deux routes dans le contrôleur :

  • src/Erlem/JobeetBundle/Controller/JobController.php
// ...

    public function indexAction()
    {
        $request = $this->getRequest();

        if($request->get('_route') == 'ErlemJobeetBundle_nonlocalized') {
            return $this->redirect($this->generateUrl('erlem_jobeet_homepage'));
        }

        $em = $this->getDoctrine()->getManager();

        // ...
    }

// ...

Si l'utilisateur a accès à la plate-forme Jobeet sans préciser sa langue (http://jobeet.local/app_dev.php), il sera redirigé vers la page d'accueil ayant la langue sélectionnée par défaut (http://jobeet.local/app_dev.php/en/).

Test de la culture

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 tests fonctionnels et ajoutez /en devant tous les URLs.

  •  src/erlem/JobeetBundle/Tests/Controller/JobControllerTest.php
<?php

namespace Erlem\JobeetBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Input\ArrayInput;
use Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand;
use Symfony\Component\DomCrawler\Crawler;

class JobControllerTest extends WebTestCase
{  
    // ...

    public function testIndex()
    {
        // get the custom parameters from app config.yml
        $kernel = static::createKernel();
        $kernel->boot();
        $max_jobs_on_homepage = $kernel->getContainer()->getParameter('max_jobs_on_homepage');

        $client = static::createClient();

        $crawler = $client->request('GET', '/fr/');
        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::indexAction', $client->getRequest()->attributes->get('_controller'));

        // If the selected culture is italian, the page requested will not be found
        $crawler = $client->request('GET', '/it/');
        $this->assertTrue(404 === $client->getResponse()->getStatusCode());

        $crawler = $client->request('GET', '/en/');
        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::indexAction', $client->getRequest()->attributes->get('_controller'));

        // expired jobs are not listed
        $this->assertTrue($crawler->filter('.jobs td.position:contains("Expired")')->count() == 0);

        // only $max_jobs_on_homepage jobs are listed for a category
        $this->assertTrue($crawler->filter('.category_programming tr')->count()<= $max_jobs_on_homepage); 
        $this->assertTrue($crawler->filter('.category_design .more_jobs')->count() == 0);
        $this->assertTrue($crawler->filter('.category_programming .more_jobs')->count() == 1);

        // jobs are sorted by date
        $this->assertTrue($crawler->filter('.category_programming tr')->first()->filter(sprintf('a[href*="/%d/"]', $this->getMostRecentProgrammingJob()->getId()))->count() == 1);

        // each job on the homepage is clickable and give detailed information
        $job = $this->getMostRecentProgrammingJob();
        $link = $crawler->selectLink('Web Developer')->first()->link();
        $crawler = $client->click($link);
        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::showAction', $client->getRequest()->attributes->get('_controller'));
        $this->assertEquals($job->getCompanySlug(), $client->getRequest()->attributes->get('company'));
        $this->assertEquals($job->getLocationSlug(), $client->getRequest()->attributes->get('location'));
        $this->assertEquals($job->getPositionSlug(), $client->getRequest()->attributes->get('position'));
        $this->assertEquals($job->getId(), $client->getRequest()->attributes->get('id'));

        // a non-existent job forwards the user to a 404
        $crawler = $client->request('GET', '/en/job/foo-inc/milano-italy/0/painter');
        $this->assertTrue(404 === $client->getResponse()->getStatusCode());

        // an expired job page forwards the user to a 404
        $crawler = $client->request('GET', sprintf('/en/job/sensio-labs/paris-france/%d/web-developer', $this->getExpiredJob()->getId()));
        $this->assertTrue(404 === $client->getResponse()->getStatusCode());
    }

    public function testJobForm()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/en/job/new');

        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::newAction', $client->getRequest()->attributes->get('_controller'));

        $form = $crawler->selectButton('Preview your job')->form(array(
            'job[company]'      => 'Sensio Labs',
            'job[url]'          => 'http://www.sensio.com',
            'job[file]'         => __DIR__.'/../../../../../web/bundles/erlemjobeet/images/sensio-labs.gif',
            'job[how_to_apply]' => 'Send me an email',
            'job[description]'  => 'You will work with symfony to develop websites for our customers',
            'job[location]'     => 'Atlanta, USA',
            'job[email]'        => 'for.a.job[at]example.com',
            'job[position]'     => 'Developer',
            'job[is_public]'    => false,
        ));

        $client->submit($form);
        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::createAction', $client->getRequest()->attributes->get('_controller'));

        $client->followRedirect();
        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::previewAction', $client->getRequest()->attributes->get('_controller'));

        $kernel = static::createKernel();
        $kernel->boot();
        $em = $kernel->getContainer()->get('doctrine.orm.entity_manager');

        $query = $em->createQuery('SELECT count(j.id) from ErlemJobeetBundle:Job j WHERE j.location = :location AND j.is_activated IS NULL AND j.is_public = 0');
        $query->setParameter('location', 'Atlanta, USA');
        $this->assertTrue(0 < $query->getSingleScalarResult());

        $crawler = $client->request('GET', '/en/job/new');
        $form = $crawler->selectButton('Preview your job')->form(array(
            'job[company]'      => 'Sensio Labs',
            'job[position]'     => 'Developer',
            'job[location]'     => 'Atlanta, USA',
            'job[email]'        => 'not.an.email',
        ));
        $crawler = $client->submit($form);

        // check if we have 3 errors
        $this->assertTrue($crawler->filter('.error_list')->count() == 3);
        // check if we have error on job_description field
        $this->assertTrue($crawler->filter('#job_description')->siblings()->first()->filter('.error_list')->count() == 1);
        // check if we have error on job_how_to_apply field
        $this->assertTrue($crawler->filter('#job_how_to_apply')->siblings()->first()->filter('.error_list')->count() == 1);
        // check if we have error on job_email field
        $this->assertTrue($crawler->filter('#job_email')->siblings()->first()->filter('.error_list')->count() == 1);
    }

    public function createJob($values = array(), $publish = false)
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/en/job/new');
        $form = $crawler->selectButton('Preview your job')->form(array_merge(array(
            'job[company]'      => 'Sensio Labs',
            'job[url]'          => 'http://www.sensio.com/',
            'job[position]'     => 'Developer',
            'job[location]'     => 'Atlanta, USA',
            'job[description]'  => 'You will work with symfony to develop websites for our customers.',
            'job[how_to_apply]' => 'Send me an email',
            'job[email]'        => 'for.a.job[at]example.com',
            'job[is_public]'    => false,
        ), $values));

        $client->submit($form);
        $client->followRedirect();

        if($publish) {
            $crawler = $client->getCrawler();
            $form = $crawler->selectButton('Publish')->form();
            $client->submit($form);
            $client->followRedirect();
        }

        return $client;
    }

    // ...

    public function testEditJob()
    {
        $client = $this->createJob(array('job[position]' => 'FOO3'), true);
        $crawler = $client->getCrawler();
        $crawler = $client->request('GET', sprintf('/en/job/%s/edit', $this->getJobByPosition('FOO3')->getToken()));
        $this->assertTrue( 404 === $client->getResponse()->getStatusCode());
    }

    public function testExtendJob()
    {
        // A job validity cannot be extended before the job expires soon
        $client = $this->createJob(array('job[position]' => 'FOO4'), true);
        $crawler = $client->getCrawler();
        $this->assertTrue($crawler->filter('input[type=submit]:contains("Extend")')->count() == 0);

        // A job validity can be extended hen the job expires soon
        // Create a new FOO5 job
        $client = $this->createJob(array('job[position]' => 'FOO5'), true);
        // Get the job and change the expire date to today
        $kernel = static::createKernel();
        $kernel->boot();
        $em = $kernel->getContainer()->get('doctrine.orm.entity_manager');
        $job = $em->getRepository('ErlemJobeetBundle:Job')->findOneByPosition('FOO5');
        $job->setExpiresAt(new \DateTime());
        $em->flush();

        // Go to preview page and extend the job
        $crawler = $client->request('GET', sprintf('/en/job/%s/%s/%s/%s', $job->getCompanySlug(), $job->getLocationSlug(), $job->getToken(), $job->getPositionSlug()));
        $crawler = $client->getCrawler();

        $form = $crawler->selectButton('Extend')->form();
        $client->submit($form);
        $client->followRedirect();
        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::previewAction', $client->getRequest()->attributes->get('_controller'));

        // Reload the job from database
        $job = $this->getJobByPosition('FOO5');

        // Check the expiration date
        $this->assertTrue($job->getExpiresAt()->format('y/m/d') == date('y/m/d', time() + 86400 * 30));
    }

    public function testSearch()
    {
        $client = static::createClient();

        $crawler = $client->request('GET', '/en/job/search');
        $this->assertEquals('Erlem\JobeetBundle\Controller\JobController::searchAction', $client->getRequest()->attributes->get('_controller'));

        $crawler = $client->request('GET', '/en/job/search?query=sens*', array(), array(), array(
            'X-Requested-With' => 'XMLHttpRequest',
        ));
        $this->assertTrue($crawler->filter('tr')->count()== 2);
    }
}
  • src/Erlem/JobeetBundle/Tests/Controller/AffiliateControllerTest.php
// ...     

    public function testAffiliateForm()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/en/affiliate/new');

        $this->assertEquals('Erlem\JobeetBundle\Controller\AffiliateController::newAction', $client->getRequest()->attributes->get('_controller'));

        $form = $crawler->selectButton('Submit')->form(array(
            'affiliate[url]'   => 'http://sensio-labs.com/',
            'affiliate[email]' => 'fabien.potencier[at]example.com'
        ));

        $client->submit($form);
        $this->assertEquals('Erlem\JobeetBundle\Controller\AffiliateController::createAction', $client->getRequest()->attributes->get('_controller'));

        $kernel = static::createKernel();
        $kernel->boot();
        $em = $kernel->getContainer()->get('doctrine.orm.entity_manager');

        $crawler = $client->request('GET', '/en/affiliate/new');
        $form = $crawler->selectButton('Submit')->form(array(
            'affiliate[email]'        => 'not.an.email',
        ));
        $crawler = $client->submit($form);

        // check if we have 1 errors
        $this->assertTrue($crawler->filter('.error_list')->count() == 1);
        // check if we have error on affiliate_email field
        $this->assertTrue($crawler->filter('#affiliate_email')->siblings()->first()->filter('.error_list')->count() == 1);
    }

    public function testCreate()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/en/affiliate/new');
        $form = $crawler->selectButton('Submit')->form(array(
            'affiliate[url]'   => 'http://sensio-labs.com/',
            'affiliate[email]' => 'address[at]example.com'
        ));

        $client->submit($form);
        $client->followRedirect();

        $this->assertEquals('Erlem\JobeetBundle\Controller\AffiliateController::waitAction', $client->getRequest()->attributes->get('_controller'));

        return $client;
    }

    public function testWait()
    {
        $client = static::createClient();
        $crawler = $client->request('GET', '/en/affiliate/wait');

        $this->assertEquals('Erlem\JobeetBundle\Controller\AffiliateController::waitAction', $client->getRequest()->attributes->get('_controller'));
    }

    // ...
  • src/Erlem/JobeetBundle/Tests/Controller/CategoryControllerTest.php
// ...

    public function testShow()
    {
        $kernel = static::createKernel();
        $kernel->boot();

        // get the custom parameters from app/config.yml
        $max_jobs_on_category = $kernel->getContainer()->getParameter('max_jobs_on_category');
        $max_jobs_on_homepage = $kernel->getContainer()->getParameter('max_jobs_on_homepage');

        $client = static::createClient();

        $categories = $this->em->getRepository('ErlemJobeetBundle:Category')->getWithJobs();

        // categories on homepage are clickable
        foreach($categories as $category) {
            $crawler = $client->request('GET', '/en/');

            $link = $crawler->selectLink($category->getName())->link();
            $crawler = $client->click($link);

            $this->assertEquals('Erlem\JobeetBundle\Controller\CategoryController::showAction', $client->getRequest()->attributes->get('_controller'));
            $this->assertEquals($category->getSlug(), $client->getRequest()->attributes->get('slug'));

            $jobs_no = $this->em->getRepository('ErlemJobeetBundle:Job')->countActiveJobs($category->getId()); 

            // categories with more than $max_jobs_on_homepage jobs also have a "more" link                 
            if($jobs_no > $max_jobs_on_homepage) {
                $crawler = $client->request('GET', '/en/');
                $link = $crawler->filter(".category_" . $category->getSlug() . " .more_jobs a")->link();
                $crawler = $client->click($link);

                $this->assertEquals('Erlem\JobeetBundle\Controller\CategoryController::showAction', $client->getRequest()->attributes->get('_controller'));
                $this->assertEquals($category->getSlug(), $client->getRequest()->attributes->get('slug'));
            }

            $pages = ceil($jobs_no/$max_jobs_on_category);

            // only $max_jobs_on_category jobs are listed 
            $this->assertTrue($crawler->filter('.jobs tr')->count() <= $max_jobs_on_category);
            $this->assertRegExp("/" . $jobs_no . " jobs/", $crawler->filter('.pagination_desc')->text());

            if($pages > 1) {
                $this->assertRegExp("/page 1\/" . $pages . "/", $crawler->filter('.pagination_desc')->text());

                for ($i = 2; $i <= $pages; $i++) {
                    $link = $crawler->selectLink($i)->link();
                    $crawler = $client->click($link);

                    $this->assertEquals('Erlem\JobeetBundle\Controller\CategoryController::showAction', $client->getRequest()->attributes->get('_controller'));
                    $this->assertEquals($i, $client->getRequest()->attributes->get('page'));
                    $this->assertTrue($crawler->filter('.jobs tr')->count() <= $max_jobs_on_category);
                    if($jobs_no > 1) {
                        $this->assertRegExp("/" . $jobs_no . " jobs/", $crawler->filter('.pagination_desc')->text());
                    }
                    $this->assertRegExp("/page " . $i . "\/" . $pages . "/", $crawler->filter('.pagination_desc')->text());
                }
            }     
        }
    }

    // ...

Changement de langue

Pour que l'utilisateur puisse changer de langue, un formulaire de langue doit être ajouté dans la mise en page. Créons le :

  •  src/Erlem/JobeetBundle/Resources/views/layout.html.twig
<!-- ... -->
<div id="footer">
    <div class="content">
        <!-- ... -->
        <form action={{ path('ErlemJobeetBundle_changeLanguage') }} method="get">
            <label>Language</label>
                <select name="language">
                    <option value="en" {% if app.request.get('_locale') == 'en' %}selected="selected"{% endif %}>English</option>
                    <option value="fr" {% if app.request.get('_locale') == 'fr' %}selected="selected"{% endif %}>French</option>
                </select>
            <input type="submit" value="Ok"> 
        </form>
    </div>
</div>
<!-- ... -->

Ajouter une nouvelle route pour correspondre à l'action dans laquelle nous allons changer la langue :

  •  src/Erlem/JobeetBundle/Resources/config/routing.yml
# ...

ErlemJobeetBundle_changeLanguage:
    pattern: /change_language
    defaults: { _controller: "ErlemJobeetBundle:Default:changeLanguage" }
  • src/Erlem/JobetBundle/Controller/DefaultController.php
namespace Erlem\JobeetBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;

class DefaultController extends Controller
{
    // ...

    public function changeLanguageAction()
    {
        $language = $this->getRequest()->get('language');
        return $this->redirect($this->generateUrl('erlem_jobeet_homepage', array('_locale' => $language)));
    }
}

Ne pas oublier de vider le cache !

Les templates

Un site Web internationalisé signifie que l'interface utilisateur est traduite en plusieurs langues. Pour nous, ce sera l'anglais, par défaut, et le français. Afin de traduire les templates, nous allons utiliser la balise Twig {% de trans%}. Lorsque Symfony rend un Template, chaque fois que le {% trans%} balise est trouvée, Symfony regarde pour une traduction pour la culture de l'utilisateur actuel. Si la traduction est trouvée, elle est utilisée, sinon, la chaîne supposée être traduite est renvoyée en tant que valeur de repli.

Toutes les traductions sont stockées dans un catalogue, qui se trouve dans src/Erlem/JobeetBundle/Resources/translations/. Pour cela, nous allons utiliser le format XLIFF, qui est une norme et la plus souple.

Commençons la traduction par l'ajout de la {% trans%} étiquette à l'intérieur des templates :

  • src/Erlem/JobeetBundle/Resources/views/layout.html.twig
<!DOCTYPE html>
<html>
    <head>
        <title>
            {% block title %}
                {% trans %}Jobeet - Your best job board{% endtrans %}
            {% endblock %}
        </title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        {% block stylesheets %}
            <link rel="stylesheet" href={{ asset('bundles/erlemjobeet/css/main.css') }} type="text/css" media="all" />
            <link rel="alternate" type="application/atom+xml" title="Latest Jobs" href={{ url('erlem_job', {'_format': 'atom'}) }} />
        {% endblock %}
        {% block javascripts %}
            <script type="text/javascript" src={{ asset('bundles/erlemjobeet/js/jquery-2.0.3.min.js') }}></script>
            <script type="text/javascript" src={{ asset('bundles/erlemjobeet/js/search.js') }}></script>
        {% endblock %}
        <link rel="shortcut icon" href={{ asset('bundles/erlemjobeet/images/favicon.ico') }} />
    </head>
    <body>
        <div id="container">
            <div id="header">
                <div class="content">
                    <h1><a href={{ path('erlem_jobeet_homepage') }}>
                        <img alt="Jobeet Job Board" src={{ asset('bundles/erlemjobeet/images/logo.jpg') }} />
                    </a></h1>
 
                    <div id="sub_header">
                        <div class="post">
                            <h2>{% trans %}Ask for people{% endtrans %}</h2>
                            <div>
                                <a href={{ path('erlem_job_new') }}>{% trans %}Post a Job{% endtrans %}</a>
                            </div>
                        </div>
 
                        <div class="search">
                            <h2>{% trans %}Ask for a job{% endtrans %}</h2>
                            <form action={{ path('erlem_job_search') }} method="get">
                                <input type="text" name="query" value='{{ app.request.get('query') }}' id="search_keywords" />
                                <input type="submit" value="search" />
                                <img id="loader" src={{ asset('bundles/erlemjobeet/images/loader.gif') }} style="vertical-align: middle; display: none" />
                                <div class="help">
                                    {% trans %}Enter some keywords (city, country, position, ...){% endtrans %}
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
           <div id="job_history">
                {% trans %}Recent viewed jobs:{% endtrans %}
                <ul>
                    {% for job in app.session.get('job_history') %}
                        <li>
                            <a href={{ path('erlem_job_show', { 'id': job.id, 'company': job.companyslug, 'location': job.locationslug, 'position': job.positionslug }) }}>{{ job.position }} - {{ job.company }}</a>
                        </li>
                    {% endfor %}
                </ul>
            </div>
           <div id="content">
               {% for flashMessage in app.session.flashbag.get('notice') %}
                   <div class="flash_notice">
                       {{ flashMessage }}
                   </div>
               {% endfor %}
 
               {% for flashMessage in app.session.flashbag.get('error') %}
                   <div class="flash_error">
                       {{ flashMessage }}
                   </div>
               {% endfor %}
 
               <div class="content">
                   {% block content %}
                   {% endblock %}
               </div>
           </div>
 
           <div id="footer">
               <div class="content">
                   <span class="symfony">
                       <img src={{ asset('bundles/erlemjobeet/images/jobeet-mini.png') }} />
                           powered by <a href="http://www.symfony.com/">
                           <img src={{ asset('bundles/erlemjobeet/images/symfony.gif') }} alt="symfony framework" />
                       </a>
                   </span>
                   <ul>
                       <li><a href="/">{% trans %}About Jobeet{% endtrans %}</a></li>
                       <li class="feed"><a href={{ path('erlem_job', {'_format': 'atom'}) }}>{% trans %}Full feed{% endtrans %}</a></li>
                       <li><a href="/">{% trans %}Jobeet API{% endtrans %}</a></li>
                       <li class="last"><a href={{ path('erlem_affiliate_new') }}>{% trans %}Become an affiliate{% endtrans %}</a></li>
                   </ul>
                   <form action={{ path('ErlemJobeetBundle_changeLanguage') }} method="get">
                       <label>{% trans %}Language{% endtrans %}</label>
                       <select name="language">
                           <option value="en" {% if app.request.get('_locale') == 'en' %}selected="selected"{% endif %}>English</option>
                                <option value="fr" {% if app.request.get('_locale') == 'fr' %}selected="selected"{% endif %}>French</option>
                       </select>
                       <input type="submit" value="Ok"> 
                   </form>
               </div>
           </div>
       </div>
   </body>
</html>
  • src/Erlem/JobeetBundle/Resources/views/Job/show.html.twig
{% extends 'ErlemJobeetBundle::layout.html.twig' %}

{% block title %}
    {% trans with {'%company%': entity.company, '%position%': entity.position} %}%company% is looking for a %position%{% endtrans %}
{% endblock %}

{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" href={{ asset('bundles/erlemjobeet/css/job.css') }} type="text/css" media="all" />
{% endblock %}

{% block content %}
    {% if app.request.get('token') %}
        {% include 'ErlemJobeetBundle:Job:admin.html.twig' with {'job': entity} %}
    {% endif %}
    <div id="job">
        <h1>{{ entity.company }}</h1>
        <h2>{{ entity.location }}</h2>
        <h3>
            {{ entity.position }}
            <small> - {{ entity.type }}</small>
        </h3>

        {% if entity.logo %}
            <div class="logo">
                <a href={{ entity.url }}>
                    <img src="/uploads/jobs/{{ entity.logo }}
                        alt={{ entity.company }} logo" />
                </a>
            </div>
        {% endif %}

        <div class="description">
            {{ entity.description|nl2br }}
        </div>

        <h4>{% trans %}How to apply?{% endtrans %}</h4>

        <p class="how_to_apply">{{ entity.howtoapply }}</p>

        <div class="meta">
            <small>{% trans with {'%date%': entity.createdat|date('m/d/Y')} %}posted on %date%{% endtrans %}</small>
        </div>
    </div>
{% endblock %}
  • src/Erlem/JobeetBundle/Resources/views/Job/new.html.twig
<!-- ... -->
{% block content %}
    <h1>{% trans %}Job creation{% endtrans %}</h1>
    <!-- ... -->
        <br /> {% trans %}Whether the job can also be published on affiliate websites or not.{% endtrans %}
    <!-- ... -->
<!-- ... -->
  • src/Erlem/JobeetBundle/Resources/views/Job/index.html.twig
<!-- ... -->
    {% if category.morejobs %}
        <div class="more_jobs">
            {% trans with {'%count%': '<a href="' ~ path('ErlemJobeetBundle_category', { 'slug': category.slug }) ~ '">' ~  category.morejobs ~ '</a>'} %}and %count% more...{% endtrans %}
        </div>
    {% endif %}
<!-- ... -->
  • src/Erlem/JobeetBundle/Resources/views/Job/edit.html.twig
<!-- ... -->
{% block content %}
    <h1>{% trans %}Job edit{% endtrans %}</h1>
    <!-- ... -->
        <br /> {% trans %}Whether the job can also be published on affiliate websites or not.{% endtrans %}
    <!-- ... -->
<!-- ... -->
  • src/Erlem/JobeetBundle/Resources/views/Job/admin.html.twig
<div id="job_actions">
    <h3>Admin</h3>
    <ul>
        {% if not job.isActivated %}
            <ul>
                <li><a href={{ path('erlem_job_edit', { 'token': job.token }) }}>{% trans %}Edit{% endtrans %}</a></li>
                <li>
                    <form action={{ path('erlem_job_publish', { 'token': job.token }) }} method="post">
                        {{ form_widget(publish_form) }}
                            <button type="submit">{% trans %}Publish{% endtrans %}</button>
                    </form>
                </li>
            </ul>
        {% endif %}
        <li>
            <form action={{ path('erlem_job_delete', { 'token': job.token }) }} method="post">
                {{ form_widget(delete_form) }}
                    <button type="submit" onclick="if(!confirm('{% trans %}Are you sure?{% endtrans %}')) { return false; }">{% trans %}Delete{% endtrans %}</button>
            </form>
        </li>
        {% if job.isActivated %}
            <li {% if job.expiresSoon %} class="expires_soon" {% endif %}>
                {% if job.isExpired %}
                    {% trans %}Expired{% endtrans %}
                {% else %}
                    {% trans with {'%count%':'<strong>' ~ job.getDaysBeforeExpires ~ '</strong>' } %}Expires in %count% days{% endtrans %}
                {% endif %}

                {% if job.expiresSoon %}
                    <form action={{ path('erlem_job_extend', { 'token': job.token }) }} method="post">
                        {{ form_widget(extend_form) }}
                            <button type="submit" value="Extend">{% trans %}Extend{% endtrans %}</button> {% trans %}for another 30 days{% endtrans %}
                    </form>
                {% endif %}
            </li>
        {% else %}
            <li>
                [{% trans with {'%url%': '<a href="' ~ url('erlem_job_preview', { 'token': job.token, 'company': job.companyslug, 'location': job.locationslug, 'position': job.positionslug }) ~ '">URL</a>'} %}Bookmark this %url% to manage this job in the future{% endtrans %}.]
            </li>
        {% endif %}
    </ul>
</div>
  •  src/Erlem/JobeetBundle/Resources/views/Category/show.html.twig
{% extends 'ErlemJobeetBundle::layout.html.twig' %}

{% block title %}
    {% trans with {'%category%': category.name} %}Jobs in the %category% category{% endtrans %}
{% endblock %}

{% block stylesheets %}
    {{ parent() }}
    <link rel="stylesheet" href={{ asset('bundles/erlemjobeet/css/jobs.css') }} type="text/css" media="all" />
{% endblock %}

{% block content %}
    <div class="category">
        <div class="feed">
            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, '_format': 'atom' }) }}>Feed</a>
        </div>   
        <h1>{{ category.name }}</h1>
    </div>

    {% include 'ErlemJobeetBundle:Job:list.html.twig' with {'jobs': category.activejobs} %}

    {% if last_page > 1 %}
        <div class="pagination">
            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': 1 }) }}>
                <img src={{ asset('bundles/erlemjobeet/images/first.png') }} alt="First page" title="First page" />
            </a>

            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': previous_page }) }}>
                <img src={{ asset('bundles/erlemjobeet/images/previous.png') }} alt="Previous page" title="Previous page" />
            </a>

            {% for page in 1..last_page %}
                {% if page == current_page %}
                    {{ page }}
                {% else %}
                    <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': page }) }}>{{ page }}</a>
                {% endif %}
            {% endfor %}

            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': next_page }) }}>
                <img src={{ asset('bundles/erlemjobeet/images/next.png') }} alt="Next page" title="Next page" />
            </a>

            <a href={{ path('ErlemJobeetBundle_category', { 'slug': category.slug, 'page': last_page }) }}>
                <img src={{ asset('bundles/erlemjobeet/images/last.png') }} alt="Last page" title="Last page" />
            </a>
        </div>
    {% endif %}

    <div class="pagination_desc">
        {% transchoice total_jobs with {'%count%': '<strong>' ~ total_jobs ~ '</strong>'} %}
            {0} No job in this category|{1} One job in this category|]1,Inf] %count% jobs in this category
        {% endtranschoice %}
        {% if last_page > 1 %}
            - page <strong>{{ current_page }}/{{ last_page }}</strong>
        {% endif %}
    </div>        
{% endblock %}
  • src/Erlem/JobeetBundle/Resources/views/Affiliate/wait.html.twig
{% extends "ErlemJobeetBundle::layout.html.twig" %}

{% block content %}
    <div class="content">
        <h1>{% trans %}Your affiliate account has been created{% endtrans %}</h1>
        <div style="padding: 20px">
            {% trans %}Thank you!
            You will receive an email with your affiliate token
            as soon as your account will be activated.{% endtrans %}
        </div>
    </div>
{% endblock %}
  • src/Erlem/JobeetBundle/Resources/views/Affiliate/affiliate_new.html.twig
<!-- ... -->
    <h1>{% trans %}Become an affiliate{% endtrans %}</h1>
<!-- ... -->

Chaque traduction est gérée 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 :

  •  src/Erlem/JobeetBundle/Resources/translations/message.fr.xlf
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
    <file source-language="en" datatype="plaintext" original="file.ext">
        <body>
            <trans-unit id="1">
                <source>Jobeet - Your best job board</source>
                <target>Jobeet - Les meilleurs offres d'emplois</target>
            </trans-unit>
            <trans-unit id="2">
                <source>Enter some keywords (city, country, position, ...)</source>
                <target>Entre des mots cle (ville, pays, position, ...)</target>
            </trans-unit>
            <trans-unit id="3">
                <source>Recent viewed jobs:</source>
                <target>Dernier emplois vus:</target>
            </trans-unit>
            <trans-unit id="4">
                <source>About Jobeet</source>
                <target>A propos de Jobeet</target>
            </trans-unit>
            <trans-unit id="5">
                <source>Become an affiliate</source>
                <target>Devenir un affilie</target>
            </trans-unit>
            <trans-unit id="6">
                <source>and %count% more...</source>
                <target>et %count%
                        autres...</target>
            </trans-unit>
            <trans-unit id="7">
                <source>Language</source>
                <target>Langue</target>
            </trans-unit>
            <trans-unit id="8">
                <source>Publish</source>
                <target>Publier</target>
            </trans-unit>
            <trans-unit id="9">
                <source>Edit</source>
                <target>Editer</target>
            </trans-unit>
            <trans-unit id="10">
                <source>Are you sure?</source>
                <target>Etes-vous sur?</target>
            </trans-unit>
            <trans-unit id="11">
                <source>Delete</source>
                <target>Supprimer</target>
            </trans-unit>
            <trans-unit id="12">
                <source>Extend</source>
                <target>Prolonger</target>
            </trans-unit>
            <trans-unit id="13">
                <source>for another 30 days</source>
                <target>pour 30 jours supplementaires</target>
            </trans-unit>
            <trans-unit id="14">
                <source>Bookmark this %url% to manage this job in the future</source>
                <target>Marquer cette %url% pour gerer ce travail a l'avenir</target>
            </trans-unit>
            <trans-unit id="15">
                <source>Whether the job can also be published on affiliate websites or not.</source>
                <target>Si le travail peut egalement etre publie sur les sites affilies ou non.</target>
            </trans-unit>
            <trans-unit id="16">
                <source>%company% is looking for a %position%</source>
                <target>%company% est a la recherche d'un %position%</target>
            </trans-unit>
            <trans-unit id="17">
                <source>How to apply?</source>
                <target>comment appliquer?</target>
            </trans-unit>
            <trans-unit id="18">
                <source>posted on %date%</source>
                <target>poste en %date%</target>
            </trans-unit>
            <trans-unit id="19">
                <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 categorie|{1}Une annonce dans cette categorie|]1,+Inf] %count% annonces dans cette categorie</target>
            </trans-unit>
            <trans-unit id="20">
                <source>Jobs in the %category% category</source>
                <target>Travails dans le %category% categorie</target>
            </trans-unit>
            <trans-unit id="21">
                <source>Your affiliate account has been created</source>
                <target>Votre compte d'affiliation a ete cree</target>
            </trans-unit>
            <trans-unit id="22">
                <source>Thank you!
            You will receive an email with your affiliate token
            as soon as your account will be activated.</source>
                <target>On te remercie! Vous recevrez un email avec votre jeton d'affiliation des que votre compte sera active.</target>
            </trans-unit>
            <trans-unit id="23">
                <source>Expires in %count% days</source>
                <target>Expire en %count% jours</target>
            </trans-unit>
            <trans-unit id="24">
                <source>Ask for people</source>
                <target>Recherche des gens</target>
            </trans-unit>
            <trans-unit id="25">
                <source>Ask for a job</source>
                <target>Recherche d'un emploi</target>
            </trans-unit>
            <trans-unit id="26">
                <source>Jobeet API</source>
                <target>API Jobeet</target>
            </trans-unit>
            <trans-unit id="27">
                <source>Job creation</source>
                <target>Creation d'emploi</target>
            </trans-unit>
            <trans-unit id="28">
                <source>Job edit</source>
                <target>Edit l'emploi</target>
            </trans-unit>
            <trans-unit id="29">
                <source>Expired</source>
                <target>Expiré</target>
            </trans-unit>
            <trans-unit id="30">
                <source>Full feed</source>
                <target>Fil RSS</target>
            </trans-unit>
            <trans-unit id="31">
                <source>Post a Job</source>
                <target>Poste un emploi </target>
            </trans-unit>
        </body>
    </file>
</xliff>

Chaque fois que vous ajoutez une nouvelle traduction, vous devez effacer le cache après.


Symfony2 - Jour 18 - AJAX
[Sommaire]  


Pour ce tutoriel, je me suis inspiré du tutoriel Symfony2 Jobeet du site IntelligentBee

Submit to DeliciousSubmit to DiggSubmit to FacebookSubmit to Google PlusSubmit to StumbleuponSubmit to TechnoratiSubmit to TwitterSubmit to LinkedIn