Переклади

Дата оновлення перекладу 2024-06-10

Переклади

Термін "інтернаціоналізація" (часто скорочуваний як 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');

Процес перекладу має декілька кроків:

  1. Підключити та сконфігурувати сервіс перекладів Symfony.
  2. Абстрагувати рядки (так звані "повідомлення"), огорнувши їх у виклики Translator ("_");
  3. Створити ресурси/файли перекладів для кожної підтримуваної локалі, які будуть перекладати кожне повідомлення у додатку;
  4. Визначити, установити та управляти локаллю користувачат для запиту і, за бажанням, для всієї сесії користувача.

Установка

Спочатку виконайте цю команду, щоб встановити перекладач, перед його використанням:

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'

Tip

Ви також можете визначити опцію enabled_locales , щоб обмежити локалі, у яких доступний ваш додаток.

Базовий переклад

Переклад тексту здійснюється сервісом 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. Прочитайте більше про це у .

Об'єкти, що перекладаються

Іноді переклад змісту в шаблонах може бути громіздким, так як вам потрібні початкове повідомлення, параметри перекладу та домен перекладу для кожного повідомлення. Створення перекладу у вашому контролері або сервісах спрощує ваші шаблони, але вимагає впровадження сервісу перекладача в інші частини вашого додатку, та його імітації у ваших тестах.

Замість перекладу рядку під час його створення, ви можете використати "об'єкт, що перекладається", який є екземпляром класу 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

# переглянути допомогу команди, щоб побачити її опції (префікс, формат виведення, домен, сортування і т.д.)
$ php bin/console translation:extract --help

Команда translation:update шукає відстуні переклади в:

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

Імена ресурсу/файлу та розташування перекладу

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 для простих проектів, та 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. Див. розділ Ідентифікація за допомогою агента користувача для пояснень і прикладів.

Також переконайтеся, що _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 для локалей і доменів,
# сконфігурованих у файлі config/packages/translation.yaml.
# це оновить існуючі переклади вже у постачальнику.
$ 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 постачальників перекладів, ви можете створювати
власних постачальників. Для цього вам потрібно створити два класи:

  1. Перший клас повинен реалізовувати ProviderInterface;
  2. Другий клас повинен бути фабрикою, яка буде створювати екземпляри першого класу. Вона повинна реалізовувати

ProviderFactoryInterface (ви можете розширити AbstractProviderFactory для спрощення його створення).

Після створення цих двох класів вам потрібно зареєструвати вашу фабрику як сервіс і позначити її тегом translation.provider_factory .

Робота з локаллю користувача

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

Переклад відбувається на основі локалі користувача. Локаль поточного користувача зберігається у запиті і доступна через об'єкт Request:

1
2
3
4
5
6
use Symfony\Component\HttpFoundation\Request;

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 перевіряє джерела перекладу для декількох локалей:

  1. Спочатку Symfony шукає переклад у джерелі перекладів es_AR (аргентинська іспанська) (наприклад, messages.es_AR.yaml);
  2. Якщо переклад не був знайдений, Symfony шукає переклад у батьківській локалі, яка автоматично визначається лише для деяких локалей. У цьому прикладі, батьківська локаль - es_419 (латиноамериканська іспанська);
  3. Якщо переклад не був знайдений, Symfony шукає переклад у джерелі перекладу es (іспанська) (наприклад, messages.es.yaml);
  4. Якщо переклад все ще не був знайдений, 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) {

    // тут, аргумент буде встановлено як '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 }}

Уявіть, що локаллю вашого додатку за замовчуванням є 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>

and for the en locale:

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 і виконаєте команду, ви отримаєте

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. і 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) {
    // ... there are missing and/or unused translations
}

Як знайти помилки у файлах перекладу

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/

Перекладач псевдолокалізації

Note

Перекладач псевдолокалізації призначено лише для розробки.

На наступному зображенні показано типове меню на веб-сторінці:

Меню, що показує кілька елементів, гарно вирівняних один біля одного.

На цьому іншому зображенні показано те саме меню, коли користувач перемикає мову на іспанську. Неочікувано, частина тексту обрізана, а інший зміст настільки довгий, що що він переповнює екран і його не видно:

Іспанською мовою деякі елементи меню містять більше літер, що призводить до їхнього обрізання.

Такі помилки дуже поширені, оскільки різні мови можуть бути довшими або коротшими, ніж оригінальна мова додатку. Інша поширена помилка полягає в тому, щоб
перевіряти роботу додатку лише з основними наголошеними літерами, замість того, щоб перевірити наявність більш складних знаків, таких як є в польській, чеській або інших мовах.

Ці проблеми можна вирішити за допомогою псевдолокалізації - методу тестування програмного забезпечення, який використовується для тестування інтернаціоналізації. У цьому методі замість перекладу тексту програмного забезпечення на іноземну мову, текстові елементи додатку замінюються зміненою версією мови оригіналу.

Наприклад, 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.

А це та сама сторінка з увімкненою псевдолокалізацією:

Сторінка входу демо-версії Symfony з псевдолокалізацією.

Висновок

За допомогою компонента Symfony Translation, створення інтернаціонального додатку бульше не має бути болісним процесом, та полягає у наступних базових кроках:

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