Переводы

Дата обновления перевода 2023-01-09

Переводы

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

1
2
3
4
5
6
// текст будет *всегда* отображаться на английском
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 ("");
  3. Создать ресурсы/файлы переводов для каждой поддерживаемой локали, которые будут переводить каждое сообщение в приложении;
  4. Определить, установить и управлять локалью пользователя для запроса, и по желанию, для всей сессии пользователя.

Установка

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

1
$ composer require symfony/translation

Конфигурация

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

  • YAML
  • XML
  • PHP
1
2
3
4
5
# config/packages/translation.yaml
framework:
    default_locale: 'en'
    translator:
        default_path: '%kernel.project_dir%/translations'

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

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

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

1
2
3
4
5
6
7
8
9
// ...
use Symfony\Contracts\Translation\TranslatorInterface;

public function index(TranslatorInterface $translator)
{
    $translated = $translator->trans('Symfony is great');

    // ...
}

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

  • YAML
  • XML
  • PHP
1
2
# translations/messages.fr.yaml
Symfony is great: J'aime Symfony

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

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

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

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

1
2
3
$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
  • PHP
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

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

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

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

Tip

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

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

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

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

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

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

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

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

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

Tip

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

1
{{ message|trans({'%name%': '...', '%count%': 1}, 'app') }}

Переменная message должна включать в себя все версии этого сообщения, основанные на значении параметра count parameter. Например:

1
{0}%name% has no apples|{1}%name% has one apple|]1,Inf[ %name% has %count% apples

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

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

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

1
2
3
4
5
6
7
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' %}

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

Форсирование локали переводчика

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

1
2
3
4
5
6
$translator->trans(
    'Symfony is great',
    [],
    'messages',
    'fr_FR'
);

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

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

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

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

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

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

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

6.2

Поддержка PHP файлов/классов, которые используют атрибуты ограничений, была представлена в Symfony 6.2.

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

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

  • в каталоге translations/ (в корне проекта)
  • в каталоге translations/ внутри любого пакета (а также их каталоге Resources/translations/, который больше не рекомендуется для пакетов).

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

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

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

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

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

  • .yaml: файл YAML (вы также можете использовать расширение файла .yml);
  • .xlf: файл XLIFF (вы также можете использовать расширение файла .xliff);
  • .php: файл PHP, который возвращает массив с переводами;
  • .csv: файл CSV;
  • .json: файл JSON;
  • .ini: файл INI;
  • .dat, .res: ICU resource bundle;
  • .mo: Machine object format;
  • .po: Portable object format;
  • .qt: файл QT Translations TS XML;

6.1

Поддержка расширения файла .xliff была представлена в Symfony 6.1.

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

Caution

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

1
$ php bin/console cache:clear

Note

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

  • YAML
  • XML
  • PHP
1
2
3
4
5
# config/packages/translation.yaml
framework:
    translator:
        paths:
            - '%kernel.project_dir%/custom/path/to/translations'

Note

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

Поставщики переводов

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

Вместо того, чтобы делать это вручную, Symfony предоставляет интеграцию с несколькими сторонними сервисами перевода (например, Crowdin или Lokalise). Вы можете загружать и скачивать (называется "пушить" и "пуллить") переводы в/из этих сервисов и слиять результаты с приложением автоматически.

Установка и конфигурация стороннего поставщика

До того, как пушить/пуллить переводы в сторонний поставщик, вы должны установить пакет, предоставляющий интеграцию с этим поставщиком:

????????? ?????????? ? ???????
Crowdin composer require symfony/crowdin-translation-provider
Loco (localise.biz) composer require symfony/loco-translation-provider
Lokalise composer require symfony/lokalise-translation-provider

Каждая библиотека имеет рецепт Symfony Flex , который добавит пример конфигурации в ваш файл .env. Например, предположим, что вы хотите использовать Loco. Для начала, установите его:

1
$ composer require symfony/loco-translation-provider

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

1
2
# .env
LOCO_DSN=loco://API_KEY@default

LOCO_DSN не является настоящим адресом: это удобный формат, который снимает большинство работы конфигурации с Symfony. Схема loco активирует поставщика Loco, который вы только что установили, и который знает все о том, как пушить и пуллить переводы через Loco. Единственное, что вам нужно изменить - заполнитель API_KEY.

Эта таблица отображает полный список доступных DSN-форматов для каждого поставщика:

Чтобы подключить поставщика переводов, добавьте корректный DSN в ваш файл .env, и сконфгурируйте опцию providers:

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
# config/packages/translation.yaml
framework:
    translator:
        providers:
            loco:
                dsn: '%env(LOCO_DSN)%'
                domains: ['messages']
                locales: ['en', 'fr']

Tip

Если вы используете Lokalise в качестве поставщика, и формат локали, следующий ISO 639-1 (например, "en" или "fr"), вам нужно установить Настройку пользовательского именования языка в Lokalise для каждой из ваших локалей, чтобы переопределить значение по умолчанию (которое следует ISO 639-1 с последующим под-кодом заглавными буквами, указывающими на вариацию национальности (например, "GB" или "US" в соответствии в ISO 3166-1 alpha-2)).

Пушинг и пуллинг переводов

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# загрузить все локальные переводы в поставщик Loco для локалей и доменов
# configured in config/packages/translation.yaml file.
# это обновит существующие переводы уже в поставщике.
$ php bin/console translation:push loco --force

# загрузить новые локальные перевод в поставщик Loco для французской локали
# и домена валидаторов.
# это **не** будет обновлять уже существующие переводы в поставщике.
$ php bin/console translation:push loco --locales fr --domain validators

# загрузить новые локальные ереводы и удалить переводы поставщика, которые больше не
# существуют в локальных файлах для французской локали и домена валидаторов.
# это **не** будет обновлять уже существующие переводы в поставщике.
$ php bin/console translation:push loco --delete-missing --locales fr --domain validators

# проверить помощь команды, чтобы увидеть ее опции (формат, домены, локали, и т.д.)
$ php bin/console translation:push --help
1
2
3
4
5
6
7
8
9
10
11
12
# скачать все переводы поставщика в локальные файлы для локалей и доменов,
# сконфигурированных в файле config/packages/translation.yaml.
# полностью перепишет ваши локальные файлы.
$ php bin/console translation:pull loco --force

# скачать новые переводы из поставщика Loco в локальные файлы для французской
# локали и домена валидаторов.
# это **не** перепишет ваши локальные файлы, только скачает новые переводы.
$ php bin/console translation:pull loco --locales fr --domain validators

# проверить помощь команды, чтобы увидеть ее опции (формат, домены, локали, intl-icu и т.д.)
$ php bin/console translation:pull --help

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

Перевод происходит основываясь на локали пользователя. Прочтите , чтобы узнать больше о работе с ней.

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

Представьте себе, что локаль пользователя – 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
    • XML
    • PHP
    1
    2
    3
    4
    5
    # config/packages/translation.yaml
    framework:
        translator:
            fallbacks: ['en']
            # ...

Note

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

Программное переключение локали

6.1

LocaleSwitcher был представлен в Symfony 6.1.

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

Класс LocaleSwitcher позволяет вам одномоментно изменять локаль:

  • Всех сервисов с тегом kernel.locale_aware;
  • \Locale::setDefault();
  • Если запрос доступен, атрибут запроса _locale.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use Symfony\Component\Translation\LocaleSwitcher;

class SomeService
{
    private LocaleSwitcher $localeSwitcher;

    public function __construct(LocaleSwitcher $localeSwitcher)
    {
        $this->localeSwitcher = $localeSwitcher;
    }

    public function someMethod()
    {
        // вы можете получить текущую локаль приложения таким образом:
        $currentLocale = $this->localeSwitcher->getLocale();

        // вы можете установить локаль для всего приложения таким образом:
        // (с этого момента, приложение будет использовать 'fr' (французская) в качестве
        // локали; включая с локалью по умолчанию, использованной для перевода шаблонов Twig)
        $this->localeSwitcher->setLocale('fr');

        // сбросить текущую локаль вашего приложения до сконфигурированной локали по умолчанию
        // в config/packages/translation.yaml, опцией 'default_locale'
        $this->localeSwitcher->reset();

        // вы также можете выполнить какой-то код с определенной локалью, не изменяя
        // локаль для остального приложения
        $this->localeSwitcher->runWithLocale('es', function() {

            // например, отобразить здесь какие-то шаблоны Twig, используя локаль 'es' (испанская)

        });

        // ...
    }
}

При использовании автомонтирования , добавьте подсказку любого контроллера или аргумента сервиса к классу LocaleSwitcher, чтобы внедрить сервис переключения локалей. В других случаях, сконфигурируйте ваши сервисы вручную, и внедрите сервис translation.locale_switcher.

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

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

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

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

Заключение

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

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