Дата обновления перевода 2021-06-12

Переводы

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

// текст будет *всегда* отображаться на английском
echo 'Hello World';

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

Note

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

Процесс перевода имеет несколько шагов:

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

Установка

Для начала, выполните эту команду, чтобы установить переводчик, перед его использованием:

1
$ composer require symfony/translation

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

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

  • YAML
    1
    2
    3
    4
    5
    # config/packages/translation.yaml
    framework:
        default_locale: 'en'
        translator:
            default_path: '%kernel.project_dir%/translations'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- config/packages/translation.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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config default-locale="en">
            <framework:translator>
                <framework:default-path>'%kernel.project_dir%/translations'</framework:default-path>
                <!-- ... -->
            </framework:translator>
        </framework:config>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // config/packages/translation.php
    use Symfony\Config\FrameworkConfig;
    
    return static function (FrameworkConfig $framework) {
        // ...
        $framework
            ->defaultLocale('en')
            ->translator()
                ->defaultPath('%kernel.project_dir%/translations')
        ;
    };
    

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

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

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

// ...
use Symfony\Contracts\Translation\TranslatorInterface;

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

    return new Response($translated);
}

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

  • YAML
    1
    2
    # translations/messages.fr.yaml
    Symfony is great: J'aime Symfony
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- translations/messages.fr.xlf -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <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>
    
  • PHP
    1
    2
    3
    4
    // translations/messages.fr.php
    return [
        'Symfony is great' => "J'aime Symfony",
    ];
    

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

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

Исползование реальных сообщений или сообщений ключевых слов

Этот пример иллюстрирует две разные философии при создании сообщений для перевода:

$translator->trans('Symfony is great');

$translator->trans('symfony.great');

В первом методе, сообщения написаны на языке локали по умолчанию (в данном случае - английском). Это сообщение затем используется как “id” при создании переводов.

Во втором методе, сообщения на самом деле являются “ключевыми словами”, передающими идею сообщения. Сообщение ключевых слов затем используется как “id” для любых переводов. В данном случае, переводы должны быть сделаны для локали по умолчанию (т.е. для перевода symfony.great в Symfony is great).

Второй метод удобен, как как сообщение ключевых слов не нужно будет изменять в каждом файле перевода, если вы решите, что сообщение на самом деле должно читатья как “Symfony is really great” в локали по умолчанию.

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

Дополнительно, форматы файлов php и yaml поддерживают вложенные id, чтобы избежать повторений, если вы используете ключевые слова вместо реального текста для ваших id:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    symfony:
        is:
            # id - symfony.is.great
            great: Symfony is great
            # id = symfony.is.amazing
            amazing: Symfony is amazing
        has:
            # id - symfony.has.bundles
            bundles: Symfony has bundles
    user:
        # id - user.login
        login: Login
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [
        'symfony' => [
            'is' => [
                // id - symfony.is.great
                'great'   => 'Symfony is great',
                // id - symfony.is.amazing
                'amazing' => 'Symfony is amazing',
            ],
            'has' => [
                // id - symfony.has.bundles
                'bundles' => 'Symfony has bundles',
            ],
        ],
        'user' => [
            // id - user.login
            'login' => 'Login',
        ],
    ];
    

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

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

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

Tip

При переводе строк, которые находятся не в домене по умолчанию (messages), вы должны указать доман в качестве третьего аргумента trans():

$translator->trans('Symfony is great', [], 'admin');

Формат сообщений

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

// ...
$translated = $translator->trans('Hello '.$name);

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

Другая сложность возникает, когда у вас есть переводы, которые могут быть или не быть множественными, в зависимости от какой-то переменной:

1
2
There is one apple.
There are 5 apples.

Чтобы справляться с такими ситуациями,Symfony следует синтаксису ICU MessageFormat, используя PHP-класс MessageFormatter. Прочтите больше об этом в How to Translate Messages using the ICU MessageFormat.

Переводимые объекты

New in version 5.2: Переводимые объекты были представлены в Symfony 5.2.

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

Вместо перевода строки во время ее создания, вы можете использовать “переводимый объект”, который является экземпляром класса TranslatableMessage. Этот объект хранит всю необходимую информацию для полного перевода его содержания при необходимости:

use Symfony\Component\Translation\TranslatableMessage;

// первый аргумент обязательный и является оригиналом сообщения
$message = new TranslatableMessage('Symfony is great!');
// необязательный второй аргумент определяет параметры перевода, а
// необязательный третий аргумент явлеется доменом перевода
$status = new TranslatableMessage('order.status', ['%status%' => $order->getStatus()], 'store');

Теперь шаблоны намного проще, так как вы можете передать переводимые объекты фильтру trans:

1
2
<h1>{{ message|trans }}</h1>
<p>{{ status|trans }}</p>

Tip

Также существует функция под названием t(), доступная как в Twig, так и в PHP, как сокращение для создания переводимых объектов.

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

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

Использование тегов Twig

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

1
{% trans %}Hello %name%{% endtrans %}

Caution

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

Tip

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

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

1
2
3
{% trans with {'%name%': 'Fabien'} from 'app' %}Hello %name%{% endtrans %}

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

Использование фильтров Twig

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

1
2
3
{{ message|trans }}

{{ message|trans({'%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-шаблоны

The translator service is accessible in PHP templates through the translator helper:

1
<?= $view['translator']->trans('Symfony is great') ?>

Forcing the Translator Locale

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

$translator->trans(
    'Symfony is great',
    [],
    'messages',
    'fr_FR'
);

Ивлечение содержания перевода и атоматическое обновление каталогов

Наиболее времязатратными задачами при переводе приложения являются извлечение всего содержания шаблона для перевода и содержание всех файлов перевода в синхроне. Symfony имеет команду под названием translation:update, которая помогает вам с этими задачами:

1
2
3
4
5
6
7
8
# отобразить все сообщения, которые должны быть переведены для французского языка
$ php bin/console translation:update --dump-messages fr

# обвносить файлы французского перевода с отсутствующими строками для этой локали
$ php bin/console translation:update --force fr

# просмотреть помощь команды, чтобы увидеть ее опции (prefix, output format, domain, sorting, и т.д.)
$ php bin/console translation:update --help

Команда translation:update ищет отстутствующие переводы в:

  • Шаблонах, хранящихся в каталоге templates/ (или любом другом каталоге, определенном в опциях конфигурации twig.default_path и twig.paths);
  • Любом PHP файле/классе, который внедряет или автомонтирует сервис translator и делает вызовы к методу trans().
  • Любом PHP файле/классе, хранящемся в каталоге src/, который создает Translatable Objects используя конструктор, или метод t() или вызывает метод trans().

New in version 5.3: Поддержка извлечения переводимых объектов была представлена в Symfony 5.3.

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

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

  • в каталоге ``translations/` (в корне проекта)

app/Resources/translations; * в каталоге Resources/translations/ внутри любого пакета.

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

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

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

  • domain: Домены помогают организовывать сообщения в группы. Если части приложения ясно не отделены друг от друга, рекомендуется использовать только домен по умолчанию messages (например, messages.en.yaml).
  • locale: Локаль, которой соответствует перевод (например, en_GB, en, и т.д.);
  • loader: Как Symfony должен загрузить и анализировать файл (например, xlf, php, yml и т.д.).

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

  • .yaml: YAML-файл
  • .xlf: XLIFF-файл;
  • .php: возвращает PHP-массив;
  • .csv: CSV-файл;
  • .json: JSON-файл;
  • .ini: INI-файл;
  • .dat, .res: пакет источников ICU;
  • .mo: формат объекта машины;
  • .po: формат переносимого объекта;
  • .qt: файл переводов QT XML;

Выбор загрузчика зависит только от вас и вашего вкуса. Рекомендуемым вариантом является использование YAML для простых проектов и XLIFF, если вы генерируете переводы со специализированными программами или командами.

Caution

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

1
$ php bin/console cache:clear

Note

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

  • YAML
    1
    2
    3
    4
    5
    # config/packages/translation.yaml
    framework:
        translator:
            paths:
                - '%kernel.project_dir%/custom/path/to/translations'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- config/packages/translation.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
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config>
            <framework:translator>
                <framework:path>%kernel.project_dir%/custom/path/to/translations</framework:path>
            </framework:translator>
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // config/packages/translation.php
    use Symfony\Config\FrameworkConfig;
    
    return static function (FrameworkConfig $framework) {
        $framework->translator()
            ->paths(['%kernel.project_dir%/custom/path/to/translations'])
        ;
    };
    

Note

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

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

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

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

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

  1. Для начала, Symfony ищет перевод в источнике переводов es_AR (аргентинский испанский) (например, messages.es_AR.yaml);

  2. Если перевод не был найден, Symfony ищет перевод в родительской локали, которая автоматически определяется только для некоторых локалей. В этом примере, родительская локаль - es_419 (латиноамериканский испанский);

  3. Если перевод не был найден, Symfony ищет перевод в источнике перевода es (испанский) (например, messages.es.yaml);

  4. Если перевод все еще не был найден, Symfony использует опцию fallbacks, которую можно сконфигурировать следующим образом.

    • YAML
      1
      2
      3
      4
      5
      # config/packages/translation.yaml
      framework:
          translator:
              fallbacks: ['en']
              # ...
      
    • XML
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      <!-- config/packages/translation.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
              https://symfony.com/schema/dic/services/services-1.0.xsd
              http://symfony.com/schema/dic/symfony
              https://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
      5
      6
      7
      8
      9
      // config/packages/translation.php
      use Symfony\Config\FrameworkConfig;
      
       return static function (FrameworkConfig $framework) {
           // ...
           $framework->translator()
               ->fallbacks(['en'])
           ;
       };
      

Note

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

Перевод содержания базы данных

Перевод содержания базы данных должен обрабатываться Doctrine через переводимое расширение или переводимое поведение (PHP 5.4+). Чтобы узнать больше, смотрите документацию для этих библиотек.

Отладка переводов

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

Заключение

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

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

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