Переводы
Дата обновления перевода 2024-08-01
Переводы
Термин "интернационализация" (часто сокращаемый как 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
).
Переводы могут быть организованы в группы под названием домены. По умолчанию,
все сообщения используют домен по умолчанию messages
:
1
echo $translator->trans('Hello World', domain: 'messages');
Процесс перевода имеет несколько шагов:
- Подключить и сконфигурировать сервис переводов Symfony.
- Абстрагировать строки (т.н. "сообщения"), обернув их в вызовы
Translator
(""); - Создать ресурсы/файлы переводов для каждой поддерживаемой локали, которые будут переводить каждое сообщение в приложении;
- Определить, установить и управлять локалью пользователя для запроса, и по желанию, для всей сессии пользователя.
Установка
Для начала, выполните эту команду, чтобы установить переводчик, перед его использованием:
1
$ composer require symfony/translation
Конфигурация
Предыдущая команда создает первоначальный файл конфигурации, где вы можете определить локаль приложения по умолчанию и каталог, где находятся файлы перевода:
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): Response
{
$translated = $translator->trans('Symfony is great');
// ...
}
При выполнении этого кода, Symfony попытается перевести сообщение "Symfony
is great" («Symfony замечательный»), основываясь на locale
пользователя.
Для этого вам необходимо указать Symfony как перевести это сообщение при помощи
"ресурса для перевода", который обычно представляет собой набор переведенных
сообщений для данной локали. Этот "словарь" переводов может быть создан в
нескольких различных форматах:
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:
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
). Сообщения из резервных локалей , также загружаются и добавляются в каталог, если они еще были созданы. В конечном итоге получается большой "словарь" с переводами. Этот каталог кешируется в производстве для минимизирования влияния на производительность. - Если сообщение находится в каталоге, то возвращается его перевод. Если же нет, переводчик возвращает оригинал сообщения.
Формат сообщения
Иногда, сообщение, содержащее переменную, должно быть переведено:
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. Прочтите больше об этом в Как переводить сообщения, используя ICU MessageFormat.
Переводимые объекты
Иногда перевод содержания в шаблонах может быть громоздким, так как вам нужны изначальное сообщение, параметры перевода и домен перевода для каждого содержания. Создание перевода в вашем контроллере или сервисах упрощает ваши шаблоны, но требует внедрения сервиса переводчика в другие части вашего приложения, и его имитации в ваших тестах.
Вместо перевода строки во время ее создания, вы можете использовать "переводимый объект", который является экземпляром класса 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
Параметры перевода также могут быть TranslatableMessage.
Tip
Также существует функция под названием t(), доступная как в Twig, так и в PHP, как сокращение для создания переводимых объектов.
Переводы в шаблонах
В большинстве случаев, перевод происходит в шаблонах. Symfony предоставляет нативную поддержку как для шаблонов Twig, так и PHP.
Использование фильтров Twig
Фильтр trans
может быть использован для перевода переменных текстов
и сложных выражений:
1 2 3
{{ message|trans }}
{{ message|trans({'%name%': 'Fabien'}, 'app') }}
Tip
Вы можете установить домен перевода для всего шаблона Twig одним тегом:
1
{% trans_default_domain 'app' %}
Заметьте, что это влияет только на текущий шаблон, а не на "добавленный" шаблон (для избежания побочных действий).
По умолчанию, вывод переведенных сообщений экранируется; примените фильтр raw
после фильтра перевода, чтобы избежать автоматического экранирования:
1 2 3 4 5
{% set message = '<h3>foo</h3>' %}
{# строки и переменные, переведенные через фильтр, экранируются по умолчанию #}
{{ message|trans|raw }}
{{ '<h3>bar</h3>'|trans|raw }}
Использование тегов 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 %}
Caution
Использование тега перевода имеет такой же эффект, как и фильтрр, но с одним существенным различием: автоматическое экранирование вывода не применяется к переводам, использующим тег.
Форсирование локали переводчика
При переводе сообщения, переводчик использует указанную локаль или локаль
fallback
, если это необходимо. Вы также можете вручную указать, какую
локаль использовать для перевода:
1
$translator->trans('Symfony is great', locale: '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:extract
ищет отстутствующие переводы в:
- Шаблонах, хранящихся в каталоге
templates/
(или любом другом каталоге, определенном в опциях конфигурации twig.default_path и twig.paths ); - Любом PHP файле/классе, который внедряет или автомонтирует
сервис
translator
и делает вызовы к методуtrans()
. - Любом PHP файле/классе, хранящемся в каталоге
src/
, который создаёт , используя конструктор, методt()
или вызывает методtrans()
. - Любом PHP файле/классе, хранящемся в каталоге
src/
, который использует атрибуты ограничений с именованым(и) аргументом(ами)*message*
.
Имена источника/файла и местоположение перевода
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;
Выбор загрузчика зависит только от вас и вашего вкуса. Рекомендуемым вариантом является использование YAML для простых проектов и XLIFF, если вы генерируете переводы со специализированными программами или командами.
Caution
Каждый раз, когда вы создаете новый каталог сообщений (или устанавливаете пакет, содержащий каталог сообщений), не забудьте очистить ваш кеш, чтобы Symfony могла обнаружить ваши источники перевода:
1
$ php bin/console cache:clear
Note
Вы можете добавлять другие каталоги с опцией пути в конфигурацию:
1 2 3 4 5
# config/packages/translation.yaml
framework:
translator:
paths:
- '%kernel.project_dir%/custom/path/to/translations'
Переводы сущностей Doctrine
В отличие от содержания шаблонов, нецелесообразно переводить содержание, хранящееся в сущностях Doctrine, с помощью каталогов переводов. Вместо этого следует использовать переводимое расширение или переводимое поведение Doctrine. Для получения дополнительной информации читайте документации к этим библиотекам.
Пользовательские источники переводов
Если ваши переводы используют формат, не поддерживаемый Symfony, или вы храните их особым образом (например, не используя файлы или сущности Doctrine), вам необходимо предоставить пользовательский класс, реализующий интерфейс LoaderInterface. Дополнительную информацию смотрите в теге .
Поставщики переводов
При испольовании внешних переводчиков для перевода вашего приложения, вы должны отправлять им новое содержание на перевод регулярно и слиять результаты обратно с приложением.
Вместо того, чтобы делать это вручную, 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 |
Phrase | composer require symfony/phrase-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 |
---|---|
Crowdin | crowdin://PROJECT_ID:API_TOKEN@ORGANIZATION_DOMAIN.default |
Loco (localise.biz) | loco://API_KEY@default |
Lokalise | lokalise://PROJECT_ID:API_KEY@default |
Phrase | phrase://PROJECT_ID:API_TOKEN@default?userAgent=myProject |
Чтобы подключить поставщика переводов, добавьте корректный DSN в ваш файл .env
,
и сконфгурируйте опцию providers
:
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']
Important
Если вы используете Phrase в качестве провайдера, вы должны сконфигурировать агента пользователя в вашей dsn. См. Идентификация через User-Agent для обоснования и некоторых примеров.
Также сделайте так, чтобы _names_ локалей в Phrase были такими, как определено в RFC4646 (например, pt-BR, а не pt_BR). В противном случае Phrase будет создавать новую локаль для импортированных ключей.
Tip
Если вы используете Crowdin в качестве поставщика и некоторые из ваших локалей отличаются от Языковых кодов Crowdin, вы должны установить Пользовательские языковые коды в проекте Crowdin для каждой из ваших локалей, чтобы переопределить значение по умолчанию. Для этого вам нужно выбрать "locale" и указать пользовательский код в поле „Custom Code“.
Tip
Если вы используете Lokalise в качестве поставщика, и формат локали, следующий ISO 639-1 (например, "en" или "fr"), вам нужно установить Настройку пользовательского именования языка в Lokalise для каждой из ваших локалей, чтобы переопределить значение по умолчанию (которое следует ISO 639-1 с последующим под-кодом заглавными буквами, указывающими на вариацию национальности (например, "GB" или "US" в соответствии в ISO 3166-1 alpha-2)).
Tip
Поставщик Phrase использует функцию тегов Phrase для сопоставления переводов с доменами переводов Symfony. Если вам нужна помощь в организации тегов в Phrase, вы можете рассмотреть пакет тегов Phrase, который предоставляет несколько команд, помогающих вам в этом.
Пушинг и пуллинг переводов
После конфигурации сертификации для доступа к поставщику переводов, вы теперь можете использовать следующие команды для пушинга (загрузки) и пуллинга (скачивания) переводов:
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
Создание пользовательских поставщиков
Помимо использования встроенных в Symfony поставщиков переводов, вы можете создавать
собственных поставщиков. Чтобы сделать это, вам понадобится создать два класса:
- Первый класс должен реализовывать
ProviderInterface; #. Второй класс должен быть фабрикой, которая будет создавать экземпляры первого класса. Он должен реализовывать ProviderFactoryInterface (вы можете расширить AbstractProviderFactory,чтобы упростить его создание).
После создания этих двух классов вам нужно зарегистрировать фабрику как сервис и пометить ее тегом translation.provider_factory .
Работа с локалью пользователя
Перевод происходит основываясь на локали пользователя. Прочтите , чтобы узнать больше о работе с ней.
use SymfonyComponentHttpFoundationRequest;
public function index(Request $request): void { $locale = $request->getLocale(); }
Чтобы установить локаль пользователя, вы можете захотеть создать пользовательский слушатель событий, чтобы он был установлен до того, как понадобится любым другим частям системы (т.е. переводчику):
1 2 3 4 5 6 7
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
// some logic to determine the $locale
$request->setLocale($locale);
}
Note
Пользовательский слушатель должен быть вызван перед LocaleListener
, который
инициализирует локаль на основе текущего запроса. Для этого необходимо установить приоритет
вашего слушателя в более высокое значение, чем приоритет LocaleListener
(который можно
получить, выполнив команду debug:event kernel.request
).
Прочитайте для получения дополнительной информации о том, как сделать локаль пользователя "липкой" относительно его сессии.
Note
Установка локали с помощью $request->setLocale()
в контроллере происходит слишком
поздно, чтобы повлиять на переводчик. Либо установите локаль через слушатель
(как описано выше), URL (см. далее), либо вызывайте setLocale()
непосредственно в
сервисе translator
.
Об установке локали через маршрутизацию см. раздел ниже.
Локаль и URL
Поскольку вы можете хранить локаль пользователя в сессии, может возникнуть соблазн
использовать один и тот же URL для отображения источника на разных языках в зависимости
от локали пользователя. Например, http://www.example.com/contact
может отображать
содержание на английском языке для одного пользователя и на французском для другого. К
сожалению, это нарушает фундаментальное правило Сети: по определенному URL возвращается
один и тот же источник, независимо от пользователя. Еще больше запутывает проблему то, какая
версия содержания будет индексироваться поисковыми системами.
Более правильной политикой является включение локали в URL с помощью специального параметра _locale :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Controller/ContactController.php
namespace App\Controller;
// ...
class ContactController extends AbstractController
{
#[Route(
path: '/{_locale}/contact',
name: 'contact',
requirements: [
'_locale' => 'en|fr|de',
],
)]
public function contact(): Response
{
// ...
}
}
При использовании специального параметра _locale
в маршруте, совпадающая локаль
автоматически устанавливается в запросе и может быть извлечена с помощью
getLocale(). Другими словами,
если пользователь перейдет по URI /fr/contact
, то локаль fr
будет автоматически
установлена в качестве локали для текущего запроса.
Теперь вы можете использовать локаль для создания маршрутов к другим переведенным страницам в вашем приложении.
Tip
Определите требование локали как параметр контейнера , чтобы избежать жёсткого кодирования его значения во всех ваших маршрутах.
Установка локали по умолчанию
Что делать, если локаль пользователя не определена? Вы можете гарантировать, что
локаль устанавливается при каждом запросе пользователя, определив default_locale
для фреймворка:
1 2 3
# config/packages/translation.yaml
framework:
default_locale: en
Эта default_locale
также важна для переводчика, как показано в следующем разделе.
Выбор языка, предпочитаемого пользователем
Если ваше приложение поддерживает несколько языков, то при первом посещении пользователем вашего сайта, распространненой практикой является его перенаправление на наиболее подходящий язык в соответствии с его
предпочтениями. Это достигается с помощью метода getPreferredLanguage()
объекта Request :
1 2 3 4 5
// получить объект Request каким-либо образом (например, в качестве аргумента контроллера)
$request = ...
// передать массив локалей (их скриптовая и региональная части являются необязательными), поддерживаемых
// вашим приложением, и метод возвращает наилучшую локаль для текущего пользователя
$locale = $request->getPreferredLanguage(['pt', 'fr_Latn_CH', 'en_US'] );
Symfony находит наилучший возможный язык на основе локалей, переданных в качестве аргумента
и значения HTTP-заголовка Accept-Language
. Если не удается найти полное
совпадения между ними, Symfony попытается найти частичное совпадение на основе языка
(например, fr_CA
будет соответствовать fr_Latn_CH
, поскольку их язык одинаков).
Если полного или частичного совпадения нет, метод возвращает первую локаль, переданную
в качестве аргумента (поэтому порядок передаваемых локалей важен).
7.1
Возможность частичного совпадения локалей была представлена в Symfony 7.1.
Резервные локали перевода
Представьте себе, что локаль пользователя – es_AR
, и что вы переводите ключ
Symfony is great
. Для того, чтобы найти испанский перевод, Symfony проверяет
источники перевода для нескольких локалей:
- Для начала, Symfony ищет перевод в источнике переводов
es_AR
(аргентинский испанский) (например,messages.es_AR.yaml
); - Если перевод не был найден, Symfony ищет перевод в родительской локали,
которая автоматически определяется только для некоторых локалей. В этом
примере, родительская локаль -
es_419
(латиноамериканский испанский); - Если перевод не был найден, Symfony ищет перевод в источнике перевода
es
(испанский) (например,messages.es.yaml
); Если перевод все еще не был найден, Symfony использует опцию
fallbacks
, которую можно сконфигурировать следующим образом.1 2 3 4 5
# config/packages/translation.yaml framework: translator: fallbacks: ['en'] # ...
Note
Когда Symfony не находит перевод в заданной локали, она добавляет недостающий перевод в файл логов. Для того, чтобы узнать детали, смотрите .
Программное переключение локали
Иногда вам нужно изменять локаль приложения динамически, просто для выполнения кода. Представьте консольную команду, которая отображает шаблоны Twig электронных адресов почты на разных языках. Вам нужно изменить локаль только для того, чтобы отобразить эти шаблоны.
Класс LocaleSwitcher
позволяет вам одномоментно изменять локаль:
- Всех сервисов с тегом
kernel.locale_aware
; \Locale::setDefault()
;Если запрос доступен, атрибут запроса
_locale
.use SymfonyComponentTranslationLocaleSwitcher;
class SomeService { public function __construct( private LocaleSwitcher $localeSwitcher, ) { }
public function someMethod(): void { // вы можете получить текущую локаль приложения таким образом: $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' (испанская)
});
// вы можете опционально объявить аргумент в вашем обратном вызове, чтобы получить // внедренную локаль $this->localeSwitcher->runWithLocale('es', function(string $locale) {
// here, the $locale argument will be set to 'es'
});
// ...
}
}
При использовании автомонтирования , добавьте подсказку любого
контроллера или аргумента сервиса к классу LocaleSwitcher,
чтобы внедрить сервис переключения локалей. В других случаях, сконфигурируйте ваши сервисы
вручную, и внедрите сервис translation.locale_switcher
.
Как найти отсутствующие или неиспользованные сообщения перевода
Когда вы работаете с большим количеством сообщений перевода на разных языках, бывает
трудно отследить, какие переводы отсутствуют, а какие больше не используются. Команда
debug:translation
помогает найти эти отсутствующие или неиспользуемые шаблоны
сообщений перевода:
1 2 3 4
{# сообщения могут быть найдены при использовании фильтра trans и тега #}
{% trans %}Symfony is great{% endtrans %}
{{ 'Symfony is great'|trans }}
Caution
Экстракторы не могут найти сообщения, переведенные вне шаблонов (ворде ярлыков формы
или контроллеров), если не использовать или не вызвать метод
trans()
в переводчике (начиная с Symfony 5.3). Динамические переводы, использующие
переменные или выражения в шаблонах, также не обнаруживаются:
1 2 3
{# этот перевод использует переменную Twig, поэтому он не будет обнаружен #}
{% set message = 'Symfony is great' %}
{{ message|trans }}
Предположим, что default_locale вашего приложения - fr
, и вы сконфигурировали
en
в качестве резервной локали (см. и
о том, как их настраивать). И предположим, что вы
уже настроили некоторые переводы для локали fr
:
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="1">
<source>Symfony is great</source>
<target>J'aime Symfony</target>
</trans-unit>
</body>
</file>
</xliff>
и для локали en
:
1 2 3 4 5 6 7 8 9 10 11 12
<!-- translations/messages.en.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="1">
<source>Symfony is great</source>
<target>Symfony is great</target>
</trans-unit>
</body>
</file>
</xliff>
Чтобы исследовать все сообщения приложения в локали fr
, выполните:
1 2 3 4 5 6 7
$ php bin/console debug:translation fr
----------- ------------------ ---------------------------- ----------------------------------------
Состояние Id Предпросмотр сообщения (fr) Предпрросмотр резервного сообщения (en)
----------- ------------------ ---------------------------- ----------------------------------------
unused Symfony is great J'aime Symfony Symfony is great
----------- ------------------ ---------------------------- ----------------------------------------
Это отображает таблицу с результатом перевода сообщения в локали fr
и результатом
использования резервной локали en
. Кроме того, будет показано, когда перевод совпадает
с резервной локалью (это может свидетельствовать о том, что сообщение переведено некорректно).
Более того, это показывает, что сообщение Symfony is great
не используется, поскольку
оно переведено, но вы еще нигде его не использовали.
Теперь, если вы переведете это сообщение в одном из своих шаблонов, то получите следующий вывод:
1 2 3 4 5 6 7
$ php bin/console debug:translation fr
----------- ------------------ ---------------------------- ----------------------------------------
Состояние Id Предпросмотр сообщения (fr) Предпрросмотр резервного сообщения (en)
----------- ------------------ ---------------------------- ----------------------------------------
Symfony is great J'aime Symfony Symfony is great
----------- ------------------ ---------------------------- ----------------------------------------
Состояние пустое, что означает, что сообщение переведено в локали fr
и
использовано в одном шаблонов или нескольких шаблонах.
Если удалить сообщение Symfony is great
из файла переводов для локали fr
для локали fr
и выполнить команду, то вы получите:
1 2 3 4 5 6 7
$ php bin/console debug:translation fr
----------- ------------------ ---------------------------- ----------------------------------------
Состояние Id Предпросмотр сообщения (fr) Предпрросмотр резервного сообщения (en)
----------- ------------------ ---------------------------- ----------------------------------------
missing Symfony is great Symfony is great Symfony is great
----------- ------------------ ---------------------------- ----------------------------------------
Состояние указывает на то, что сообщение отсутствует, поскольку оно не переведено в
локали fr
, но оно все равно используется в шаблоне. Более того, сообщение
в локали fr
равно сообщению в локали en
. Это особый случай, поскольку id
непереведеного сообщения равен его переводу в локали en
.
Если скопировать содержание файла перевода в локали en
в файл перевода в локали
fr
и выполнить команду, то мы получим:
1 2 3 4 5 6 7
$ php bin/console debug:translation fr
----------- ------------------ ---------------------------- ----------------------------------------
Состояние Id Предпросмотр сообщения (fr) Предпрросмотр резервного сообщения (en)
----------- ------------------ ---------------------------- ----------------------------------------
fallback Symfony is great Symfony is great Symfony is great
----------- ------------------ ---------------------------- ----------------------------------------
Вы можете увидеть, что переводы сообщения идентичны в локалях fr
и en
,
что означает, что сообщение, вероятно, было скопировано с английского языка
на французский и, возможно, вы забыли его перевести.
По умолчанию проверяются все домены, но можно указать только один домен:
1
$ php bin/console debug:translation en --domain=messages
Когда в приложении имеется большое количество сообщений, полезно отобразить только
неиспользованные или только отсутствующие сообщения, используя опции --only-unused
или --only-missing
:
1 2
$ php bin/console debug:translation en --only-unused
$ php bin/console debug:translation en --only-missing
Коды выхода команд отладки
Код выхода команды debug:translation
изменяется в зависимости от статуса переводов.
Используйте следующие публичные константы, чтобы проверить его:
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Bundle\FrameworkBundle\Command\TranslationDebugCommand;
// общая ошибка (например, нет переводов)
TranslationDebugCommand::EXIT_CODE_GENERAL_ERROR;
// есть отсутствующие переводы
TranslationDebugCommand::EXIT_CODE_MISSING;
// есть неиспользованные переводы
TranslationDebugCommand::EXIT_CODE_UNUSED;
// некоторые переводы используют резервный перевод
TranslationDebugCommand::EXIT_CODE_FALLBACK;
Эти константы определены как "битовые маски", поэтому вы можете скомбиниовать их таким образом:
1 2 3
if (TranslationDebugCommand::EXIT_CODE_MISSING | TranslationDebugCommand::EXIT_CODE_UNUSED) {
// ... если отсутствующие и/или неиспользованные переводы
}
Как найти ошибки в файлах перевода
Symfony обрабатывает все файлы перевода приложения как часть процесса, который компилирует код приложения перед его выполнением. При возникновении ошибки в каком-либо файле перевода, вы увидите сообщение об ошибке, объясняющее суть проблемы.
При желании можно также валидировать содержание любого файла перевода YAML или XLIFF,
используя команды lint:yaml
и lint:xliff
:
1 2 3 4 5 6 7 8 9 10 11
# проверить один файл
$ php bin/console lint:yaml translations/messages.en.yaml
$ php bin/console lint:xliff translations/messages.en.xlf
# проверить целый каталог
$ php bin/console lint:yaml translations
$ php bin/console lint:xliff translations
# проверить несколько файлов или каталогов
$ php bin/console lint:yaml translations path/to/trans
$ php bin/console lint:xliff translations/messages.en.xlf translations/messages.es.xlf
Результаты мжоно экспортировать в JSON, используя опцию --format
:
1 2
$ php bin/console lint:yaml translations/ --format=json
$ php bin/console lint:xliff translations/ --format=json
При запуске линтеров внутри действий GitHub, вывод автоматически адаптируется к формату, требуемому GitHub, но вы можете форсировать этот формат:
1 2
$ php bin/console lint:yaml translations/ --format=github
$ php bin/console lint:xliff translations/ --format=github
Tip
Компонент Yaml предоставляет отдельную бинарность yaml-lint
, которая позволяет
вам проверять файлы YAML без необходимости создания консольного приложения:
1
$ php vendor/bin/yaml-lint translations/
Переводчик псевдолокализации
Переводчик псевдолокализации предназначен только для разработки.
На следующем рисунке показано типичное меню на веб-странице:
На другом изображении показано то же меню, когда пользователь переключает язык на испанский. Неожиданно некоторые тексты обрезаются, а содержание других настолько длинное, что его не видно:
Подобные ошибки встречаются очень часто, поскольку разные языки могут быть длиннее или короче, чем исходный язык приложения. Другая распространенная проблема заключается в том, чтобы проверять, работает ли приложение, только при использовании основных букв с акцентом, вместо проверки более сложных знаков, таких как можно найти в польском, чешском и т. д.
Эти проблемы можно решить с помощью псевдолокализации, метода тестирования программного обеспечения, используемого для тестирования интернационализации. В этом методе вместо перевода текста программного обеспечения на иностранный язык, текстовые элементы приложения заменяются на измененную версию оригинального языка.
Например, Account Settings
переводится как [!!! Àççôûñţ
Šéţţîñĝš !!!]
. Сначала оригинальный текст увеличивается в длину с помощью знаков
типа [!!! !!!]
, чтобы проверить работу приложения при использовании языков, более многословных
чем оригинальный. Это решает первую проблему.
Кроме того, оригинальные знаки заменяются похожими, но акцентированными.
Это делает текст хорошо читаемым и позволяет тестировать приложение
со всеми видами акцентированных и специальных знаков. Это решает
вторую проблему.
Добавлена полная поддержка псевдолокализации, чтобы помочь вам отладить проблемы интернационализации в ваших приложениях. Вы можете включить и настроить ее в конфигурации переводчика:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# config/packages/translation.yaml
framework:
translator:
pseudo_localization:
# заменить знаки на их акцентированный вариант
accents: true
# обернуть строки скобками
brackets: true
# контролирует, сколько дополнительных знаков будет добавлено, чтобы сделать текст длиннее
expansion_factor: 1.4
# сохранить оригинальные HTML-теги переведенного содержания
parse_html: true
# также перевести содержание этих HTML-атрибутов
localizable_html_attributes: ['title']
Вот и все. Теперь приложение начнет отображать это странное, но читаемое содержание, чтобы помочь вам интернационализировать его. Посмотрите, например, на разницу в приложении Symfony Demo. Это оригинальная страница:
А это та же страница с включенной псевдолокализацией:
Заключение
При помощи компонента Symfony Перевод, создание интернационального приложения больше не должно быть болезненным процессом и заключается в следующих базовых шагах:
- Извлеките сообщения вашего приложения, обернув каждое в метод trans();
- Переведите каждое сообщение для различных локалей, создав файлы переводов сообщений. Symfony найдёт и обработает каждый файл, так как их имена следуют специфическим соглашениям;
- Управляйте локалью пользователя, которая хранится в запросе, но также может быть установлена в пользовательской сессии.