Переводы

Термин "интернационализация" (часто сокращаемый как i18n) относится к процессу абстрагирования строк и прочих специфичных для конкретной локали частей из вашего приложения, и перемещения их на такой уровень, где они могут быть переведены и конвертированы на основании локали пользователя (т.е. в зависимости от языка и страны). Для текста это означает, что его нужно окружить специальной функцией, способной переводить текст (или "сообщение") на язык пользователя:

1
2
3
4
5
6
7
8
// текст будет *всегда* отображаться на английском
dump('Hello World');
die();

// текст может быть переведен на язык конечного пользователя или
// по умолчанию на английский
dump($translator->trans('Hello World'));
die();

Note

Термин локаль в общих чертах относится к языку и стране пользователя. Это может быть любая строка, испоьльзуемая вашим приложением для управления переводами и прочими различиями форматов (например, формат валюты). Рекомендуется использовать стандарт ISO 639-1 для языковых кодов, подчеркивание (_) и затем ISO 3166-1 alpha-2 для кодов стран (например, для French/France (французский, Франция) получится fr_FR).

В этой главе вы узнаете, как использовать компонент перевод (Translation) в фреймворке Symfony. Вы можете прочитать документацию Компонент перевод, чтобы узнать еще больше. В общем, процесс имеет несколько шагов:

  1. Подключить и настроить сервис Symfony перевод;
  1. Абстрагировать строки (т.н. "сообщения"), обернув их в вызовы Translator ("Базовый перевод translation-basic");
  2. Создать ресурсы/файлы перевода для каждой поддерживаемой локали, которые будут переводить каждое сообщение в приложении;
  3. Определить, установить и управлять локалью пользователя для запроса, и по желанию, для всей сессии пользователя.

Конфигурирование

Переводы обрабатываются сервисом translator, который использует локаль пользователя для поиска и отображения переведенных сообщений. Перед тем как его использовать, активируйте translator в файле конфигурации:

  • YAML
    1
    2
    3
    # app/config/config.yml
    framework:
        translator: { fallbacks: [en] }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config>
            <framework:translator>
                <framework:fallback>en</framework:fallback>
            </framework:translator>
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        'translator' => array('fallbacks' => array('en')),
    ));
    

Смотрите Резервные локали перевода translation-fallback для того, чтобы узнать детали о ключе fallbacks, и что делает Symfony, когда не может найти перевод.

Локаль, используемая в переводах, та же, которая сохраняется по запросу. Она обычно устанавливается с помощью атрибута _locale на ваших маршрутах (смотрите Локаль и URL translation-locale-url).

Базовый перевод

Перевод текста осуществляется сервисом translator (Translator). Для перевода текстового блока (называемого сообщением) используйте метод trans(). Предположим, например, что вы переводите простое сообщение внутри контроллера:

1
2
3
4
5
6
7
8
9
// ...
use Symfony\Component\HttpFoundation\Response;

public function indexAction()
{
    $translated = $this->get('translator')->trans('Symfony is great');

    return new Response($translated);
}

При выполнении этого кода, Symfony попытается перевести сообщение "Symfony is great" («Symfony замечательный»), основываясь на locale пользователя. Для этого вам необходимо указать Symfony как перевести это сообщение при помощи "ресурса для перевода", который обычно представляет собой набор переведенных сообщений для данной локали. Этот "словарь" переводов может быть создан в нескольких различных форматах, рекомендуемым является формат XLIFF:

  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- messages.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="symfony_is_great">
                    <source>Symfony is great</source>
                    <target>J'aime Symfony</target>
                </trans-unit>
            </body>
        </file>
    </xliff>
    
  • YAML
    1
    2
    # messages.fr.yml
    Symfony is great: J'aime Symfony
    
  • PHP
    1
    2
    3
    4
    // messages.fr.php
    return array(
        'Symfony is great' => 'J\'aime Symfony',
    );
    

Чтобы узнать, где эти файлы должны быть расположены, смотрите Местоположение ресурсов перевода translation-resource-locations.

Теперь, если языковой локалью пользователя будет Французская (например, fr_FR или fr_BE), это сообщение будет переведено как J'aime Symfony. Вы также можете переводит сообщение внутри ваших шаблонов.

Процесс перевода

Для того, чтобы перевести сообщение, Symfony использует простой процесс:

  • Определяется locale текущего пользователя, которая хранится в запросе;
  • Загружается каталог (т.е. большая коллекция) переводов сообщений из соответствующих ресурсов, определяемого locale (например, fr_FR). Сообщения, соответствующие резервные локали, также загружаются и добавляются в каталог (если они еще были созданы). В конечном итоге получается большой "словарь" с переводами.
  • Если сообщение есть в каталоге, то возвращается его перевод. Если же нет, переводчик возвращает оригинал сообщения.

При использовании метода trans(), Symfony ищет точно такую же строку в подходящем каталоге сообщений и возвращает его (при условии, что оно существует).

Заполнители в сообщениях

Иногда, сообщение, которое нужно перевести, содержит переменную:

1
2
3
4
5
6
7
8
use Symfony\Component\HttpFoundation\Response;

public function indexAction($name)
{
    $translated = $this->get('translator')->trans('Hello '.$name);

    return new Response($translated);
}

Тем не менее, создание перевода для этой строки невозможно, так как переводчик будет искать точно такое же сообщение, включая переменную (например, "Hello Ryan" или "Hello Fabien").

Для того, чтобы узнать, как справиться с такой ситуацией, смотрите
Заполнители в сообщениях component-translation-placeholders в документации компонентов. Для того, чтобы узнать, как сделать это в шаблонах, смотрите Twig Templates.

Множественность

Еще одной сложностью может стать перевод, который может быть как множественным, так и нет, в зависимости от какой-то переменной:

1
2
Есть одно яблоко.
Есть 5 яблок.
Для того, чтобы справиться с этим, используйте метод
transChoice() или тег/фильтр transchoice в вашем шаблоне.
Чтобы узнать намного больше информации, смотрите
Множественность в переводе component-translation-pluralization в документации компонентов.

Переводы в шаблонах

В большинстве случаев, перевод происходит в шаблонах. Symfony предоставляет родную поддержку как для шаблонов Twig, так и для PHP-шаблонов.

Шаблоны Twig

Symfony предоставляет специальные теги для Twig (trans и transchoice) для того, чтобы помочь с переводом сообщений статичного блочного текста:

1
2
3
4
5
{% trans %}Hello %name%{% endtrans %}

{% transchoice count %}
    {0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples
{% endtranschoice %}

Тег transchoice автоматически получает переменную %count% из текущего контекста и передает ее переводчику. Этот механизм работает только если вы используете заполнитель после шаблона %var%.

Caution

Обозначение заполнителей %var% обязательно при переводе в шаблонах Twig, использующих тег.

Tip

Если вам нужно использовать символ процента (%) в коде, избегайте его с помощью дублирования: {% trans %}Percent: %percent%%%{% endtrans %}.

Вы также можете указать домен сообщения и указать некоторые дополнительные переменные:

1
2
3
4
5
6
7
{% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %}

{% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %}

{% transchoice count with {'%name%': 'Fabien'} from "app" %}
    {0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf[ %name%, there are %count% apples
{% endtranschoice %}

Фильтры trans и transchoice могут быть использованы для перевода изменяющихся текстов и сложных выражений:

1
2
3
4
5
6
7
{{ message|trans }}

{{ message|transchoice(5) }}

{{ message|trans({'%name%': 'Fabien'}, "app") }}

{{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }}

Tip

Использования тегов или фильтров перевода имеет такой же эффект, с одним маленьким различием: автоматическое экранирование вывода применяется только к переводам, использующим фильтр. Другими словами, если вам нужно быть уверенными, что вывод вашего переведенного сообщения не будет экранирован, вы должны применить фильтр raw после фильтра перевода:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{# текст, переведенный между тегов, никогда не избегается #}
{% trans %}
    <h3>foo</h3>
{% endtrans %}

{% set message = '<h3>foo</h3>' %}

{# строки и переменные, переведенные с помощью фильтра, избегаются по умолчанию #}
{{ message|trans|raw }}
{{ '<h3>bar</h3>'|trans|raw }}

Tip

Вы можете установить домен перевода для целого шаблона Twig с помощью единственного тега:

1
{% trans_default_domain "app" %}

Заметьте, что это влияет только на текущий шаблон, а не на «включенный» шаблон (чтобы избежать побочных эффектов).

РHP-шаблоны

В PHP-шаблонах сервис перевода доступен через помощник translator:

1
2
3
4
5
6
7
<?php echo $view['translator']->trans('Symfony is great') ?>

<?php echo $view['translator']->transChoice(
    '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
    10,
    array('%count%' => 10)
) ?>

Имена ресурса/файла и расположение перевода

Symfony ищет файлы сообщений (т.е. переводы) в следующих местах по умолчанию:

  • каталоги app/Resources/translations;
  • каталоги app/Resources/<bundle name>/translations;
  • каталоги Resources/translations/ внутри любого пакета.

Места расположения перечислены в порядке приоритета. То есть, вы можете заменить перевод сообщений пакета в любом из первых 2 дирекотрий.

Механизм замещения работает на уровне ключей: только замененные ключи нужно указывать в файле сообщения с наивысшим приоритетом. Если ключ не найден в файле сообщения, переводчик автоматически обратится к файлам сообщений с более низким приоритетом.

Наименование файлов переводов также важно: каждый файл должен быть назван в соответствии со следующим шаблоном: domain.locale.loader:

  • domain: Опциональный способ для объединения сообщений в группы (например, admin, navigation или же по умолчанию messages) - см. Использование доменов сообщений using-message-domains;
  • locale: Локаль, которой соответствует перевод (например, en_GB, en, и т.д.);
  • loader: Как Symfony должен загрузить и анализировать файл (например, xlf, php, yml и т.д.).

Загрузчик (loader) может быть наименованием любого зарегистрированного загрузчика. По умолчанию в Symfony представлены многие загрузчики, включая следующие:

  • xlf: XLIFF-файл;
  • php: PHP-файл;
  • yml: YAML-файл.

Выбор загрузчика зависит только от вас и вашего вкуса. Рекомендуемым вариантом является использование xlf для переводов. Чтобы узнать больше вариантов, смотрите Загрузка каталогов сообщений component-translator-message-catalogs.

Note

вы можете добавлять другие каталоги с опцией paths в конфигурацию:

  • YAML
    1
    2
    3
    4
    5
    # app/config/config.yml
    framework:
        translator:
            paths:
                - '%kernel.project_dir%/translations'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-Instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"
    >
    
        <framework:config>
            <framework:translator>
                <framework:path>%kernel.project_dir%/translations</framework:path>
            </framework:translator>
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        'translator' => array(
            'paths' => array(
                '%kernel.project_dir%/translations',
            ),
        ),
    ));
    

Note

Вы также можете хранить переводы в базе данных, или любом другом хранилище при помощи вашего собственного класса, реализующего интерфейс LoaderInterface. Смотрите тег See the translation.loader для более подробной информации.

Caution

Каждый раз, когда вы создаете новый ресурс перевода (или устанавливаете пакет, который включает в себя ресурс перевода), не забудьте очистить ваш кеш, чтобы Symfony мог обнаружить новые ресурсы перевода.

1
$ php bin/console cache:clear

Резервные локали перевода

Представьте себе, что локаль пользователя – fr_FR, и что вы переводите ключ Symfony is great. Для того, чтобы найти французский перевод, Symfony проверяет ресурсы перевода для нескольких локалей:

  1. Для начала, Symfony ищет перевод в ресурсе fr_FR (например, messages.fr_FR.xlf);
  2. Если перевод не был найден, Symfony ищет перевод в ресурсе fr (например, messages.fr.xlf);
  3. Если перевод все еще не был найден, Symfony использует параметр конфигурации fallbacks (резерв), который по умолчанию обращается к ресурсу en (см. Конфигурирование).

Note

Когда Symfony не находит перевод в данной локали, он добавляет недостающий перевод в файл логов. Для того, чтобы узнать детали, смотрите Ведение журнала записей reference-framework-translator-logging.

Работа с локалью пользователя

Перевод осуществвляется исходя из локали пользователя. См. How to Work with the User's Locale, чтобы узнать больше о том, как управлять ею.

Перевод контента базы данных

The translation of database content should be handled by Doctrine through the Translatable Extension or the Translatable Behavior (PHP 5.4+). For more information, see the documentation for these libraries.

Debugging Translations

Когда вы работаете со множеством сообщений перевода на разных языках, может быть тяжело уследить, каких переводов не хватает, а какие больше не используются. Прочтите How to Find Missing or Unused Translation Messages, чтобы узнать, как определять такие сообщения.

Заключение

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

  • Извлеките сообщения вашего приложения, завернув каждое в методы trans() или transChoice(), (узнайте об этом в разделе Использование переводчика);
  • Переведите каждое сообщение для различных локалей, создав файлы переводов сообщений. Symfony найдёт и обработает каждый файл, так как их имена следуют специфическим соглашениям;
  • Управляйте локалью пользователя, которая хранится в запросе, но также может быть установлена в пользовательской сессии.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.