среда, 26 марта 2014 г.

Symfony 2: Локализация

Стандартная локализация, которая предоставляется в Symfony 2.4, подразумевает изменение локали через URI. Например, переводы страниц будут доступны по таким ссылкам:

localhost/symtest/en/homepage
localhost/symtest/uk/homepage
localhost/symtest/ru/homepage

Предлагаемый по умолчанию механизм локализации, реализуется в Symfony 2.4 довольно просто.

1. Настройка

В настройках, в файле app/config/config.yml, нужно указать локаль отката:

framework:
    #...
    translator: { fallback: en }
    #...

Локаль отката используется в случае, если для какой то локали не определён перевод. В таком случае будет загружаться локаль отката.

2. Маршруты

Каждый маршрут должен иметь один параметр по умолчанию и выглядеть приблизительно так:

homepage:
    pattern:      /{_locale}
    defaults:     { _controller: SiteMemberBundle:Default:index, _locale: en }
    requirements:
        _locale:  en|ru|uk

или так:

store_editproduct:
    pattern:      /{_locale}/store/editproduct/{id}
    defaults:     { _controller: SiteStoreBundle:Default:editproduct, _locale: en }
    requirements:
        id:       \d+
        _locale:  en|ru|uk

Этот параметр по умолчанию указывает на локаль которая должна загружаться, если она явно не указана в ссылке. Например, ссылки вида:

/
/store/editproduct/3

будут загружать английскую локаль.

Как видно из примеров, в каждом маршруте на параметр _locale накладывается ограничение вида: en|uk|ru. Благодаря этому, попытка загрузить локаль, которая не входит в список ограничения, вызовет код ошибки 404.

Таким образом, если нужно изменить локаль по умолчанию, или добавить новую к списку ограничения, необходимо пройтись по всех маршрутах и изменить соответствующие параметры. Чтобы избежать лишней работы, нужно считывать значения этих параметров из файла app/config/parameters.yml. При создании проекта параметр locale создается по умолчанию, а параметр available_langs (название произвольное) необходимо создать самому. В результате, каждый маршрут следует оформлять в таком виде:

store_editproduct:
    pattern:      /{_locale}/store/editproduct/{id}
    defaults:     { _controller: SiteStoreBundle:Default:editproduct, _locale: %locale% }
    requirements:
        id:       \d+
        _locale:  %available_langs%

Существует более оптимальный подход централизации параметра _locale. В файле app/routing.yml, для каждого подключаемого бандла прописывать следующее:

site_store:
    resource:     "@SiteStoreBundle/Resources/config/routing.yml"
    prefix:       /{_locale}
    requirements:
        _locale:  %available_langs%

после чего, маршрут нужно строить таким образом:

store_editproduct:
    pattern:      /store/editproduct/{id}
    defaults:     { _controller: SiteStoreBundle:Default:editproduct, _locale: %locale% }
    requirements:
        id:       \d+

3. Файлы переводов

Файлы переводов, следует размещать в каталоге Resorces/translations соответствующего бандла. Для того, чтобы структурировать файлы переводов - например, можно сделать так, что каждый шаблон будет иметь свой файл перевода - нужно использовать домены в названиях файлов. Допустим есть такие шаблоны:

Resources/
    views/
        Default/
            template_1.html.twig
            template_2.html.twig
        layout.html.twig

и нужно локализовать их на два языка: ru и en. В таком случае необходимо создать такие файлы переводов:

Resources/
    translations/
        layout.en.yml
        layout.ru.yml
        template_1.en.yml
        template_1.ru.yml
        template_2.en.yml
        template_2.ru.yml

Доменом называется первая часть названия файла, в данном случае это: layout, template_1 и template_2.

Если нужно локализовать глобальные шаблоны, например, основной шаблон сайта, app/Resources/views/base.html.twig, то следует создать каталог app/Resources/translations и поместить туда файлы переводов для этих шаблонов.

Важно! После того как были добавлены новые файлы переводов, необходимо почистить кешь. Сделать это можно с помощью команд:

php app/console cache:clear --env=dev
php app/console cache:clear --env=prod

4. Функции перевода

Каждое сообщение, которое нужно перевести, следует передать в функцию перевода, которая переведет это сообщение в соответствующую локаль.

  • В контролере следует использовать такую конструкцию:
    $this->get('translator')->trans('message', array(), 'domain');
    
  • В шаблоне перевод выполняется таким образом:
    {% trans from "domain" %}message{% endtrans %}
    
  • В форме сервис транслятора используется автоматически, поэтому не нужно явно использовать функции перевода в классе формы. Но, чтобы указать домен файла перевода, нужно переопределить метод setDefaultOptions таким образом:
    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'translation_domain' => 'manageprod',
        ));
    }
    

    Заметка! Следует иметь ввиду, что все валидационные сообщения формы, которые прописаны в сущностях (entity), или в классе формы - не переводятся автоматически. Поэтому, при локализации этих сообщений, нужно явно использовать функцию трансляции в шаблоне. Например, перевести ошибку валидации можно таким образом:

    {{ form_errors(prodForm.description) | trans({}, "manageprod") }}
    

    здесь первый параметр метода trans - это массив параметров передаваемых в сообщение, а второй - домен файлов перевода.

5. Смена локали

Чтобы дать пользователю возможность менять локаль, можно пойти двумя путями.

  • Первый способ состоит в том, чтобы создать простые ссылки вида:
    <ul class="lang-menu">
      <li>
          <a href="{{ path(app.request.get('_route'), 
                         app.request.get('_route_params')|merge({'_locale': 'ru'})) }}"
                         >{% trans %}language.ukrainian{% endtrans %}</a>
      </li>
      <li>
          <a href="{{ path(app.request.get('_route'), 
                         app.request.get('_route_params')|merge({'_locale': 'en'})) }}"
                         >{% trans %}language.english{% endtrans %}</a>
      </li>
    </ul>
    

    Таким образом, пользователь будет переадресовываться на текущую страницу и локаль поменяется.

    Такой вариант полезный тем, что работает даже в том случае, если отключён JavaScript. Если же проектом предусмотрено, что у пользователя должен быть включён JavaScript, то можно пойти другим путём.

  • Второй способ - создать выпадающий список и при выборе из него конкретного значения менять локаль. Для этого нужно использовать такой код:
    <select id="lang" onchange="location=this.options[this.selectedIndex].value;">
        <option value="{{ path(app.request.get('_route'), 
                             app.request.get('_route_params')|merge({'_locale': 'ru'})) }}"
                             >Русский</option>
        <option value="{{ path(app.request.get('_route'), 
                             app.request.get('_route_params')|merge({'_locale': 'en'})) }}"
                             >Английcкий</option>
    </select>
    

    Когда пользователь выберет из списка какое то значение, то произойдет перезагрузка страницы на текущую и , чтобы установить в списке выбранную им локаль, нужно использовать код:

    <script type="text/javascript">
        $(document).ready(function(){
            $("#lang [value='{{ path(app.request.get('_route'), 
                   app.request.get('_route_params')|
                   merge({'_locale': app.request.get('_locale')})) }}']")
                   .attr("selected", "selected");
        });
    </script>
    
Удачи!

1 комментарий: