Translations (RUS)



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

// text will *always* print out in English
echo 'Hello World';
// text can be translated into the end-user's language or default to English
echo $translator->trans('Hello World');

Понятие язык (locale) приблизительно соотносится со страной и языком пользователя. Это может быть строка, используемая Вашим приложением для выполнения переводов и иного дифференцирования формата (например, валютный формат). Мы предложили языковой код ISO639-1 с подчёркиванием (_) вместо кода страны ISO3166 (например, fr_FR для французского языка/Франция).

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

1. Включение и настройка компонента Translation;
2. Извлечение строк (например, “сообщений”), заключая их в оболочку запроса к “Переводчику”;
3. Создание ресурсов перевода для каждого поддерживаемого языка, а затем перевод каждого сообщения в приложении;
4. Определение, установка и правка языка пользователя во время сессии.

Настройка

Переводы выполняются с помощью сервиса “Translator — Переводчик”, которая использует язык клиента для поиска и возврата переведённого сообщения. Перед тем, как воспользоваться данной услугой, включите “Translator — Переводчик” в настройки:

YAML:

# app/config/config.yml
framework:
translator: { fallback: en }

XML:

<!-- app/config/config.xml -->
<framework:config>
<framework:translator fallback="en" />
</framework:config>

PHP:

// app/config/config.php
$container->loadFromExtension('framework', array('translator' => array('fallback' => 'en'),
));

Опция альтернативной замены (fallback) определяет язык, для которого не удалось найти перевод.
Когда не удаётся найти перевод, “Переводчик” сначала пытается найти перевод языка (например, fr для региона fr_FR). Если и это не удаётся, то перевод будет выполнен путём обращения к альтернативному языку.

Язык, используемый для перевода, сохраняется в сессии пользователя.

Основной перевод

\
Перевод текста осуществляется с помощью сервиса перевода (“Переводчика”). Чтобы перевести текстовый блок (называемый “сообщение”), используйте функцию trans(). Предположим, мы переводим простое сообщение в контроллере:

public function indexAction()
{
$t = $this->get('translator')->trans('Symfony2 is great');
return new Response($t);
}

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

XML:

<!-- messages.fr.xliff -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="1">
<source>Symfony2 is great</source>
<target>J'aime Symfony2</target>
</trans-unit>
</body>
</file>
</xliff>

PHP:

// messages.fr.php
return array(
'Symfony2 is great' => 'J\'aime Symfony2',
);

YAML:

# messages.fr.yml
Symfony2 is great: J'aime Symfony2

Теперь, если язык французский (например, fr_FR или fr_BE), сообщение будет переведено в J’aime Symfony2.

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

Непосредственно для перевода сообщения Symfony2 использует простую процедуру:

  • Определяется язык пользователя, сохраняющийся в сессии;
  • Каталог переведённых сообщений загружается из базы переводов, определённой для данного языка (например, fr_FR). Также загружаются сообщения для альтернативного языка и добавляются в каталог, если они не были внесены туда ранее. Конечный результат представляет собой вместительный “словарь” переводов;
  • Если сообщение находится в каталоге, выдаётся перевод. Если нет, “Переводчик” выдаёт исходное сообщение.

При использовании функции trans(), Symfony2 ищет и выдаёт соответствующую строку из соответствующего каталога сообщений (если такая строка существует).

“Заполнители” сообщений

Иногда в сообщении содержится переменная, которую нужно перевести:
public function indexAction($name)

{
$t = $this->get('translator')->trans('Hello '.$name);
return new Response($t);
}

Перевод такой строки невозможен, поскольку “Переводчик” попытается найти сообщение, полностью соответствующее оригиналу, включая части, содержащие переменные (например «Hello Ryan» или «Hello Fabien»). Вместо постоянного написания перевода для переменной $name, мы можем заменить последнюю “заполнителем”:

public function indexAction($name)
{
$t = $this->get('translator')->trans('Hello %name%', array('%name%' => $name));

new Response($t);
}

Теперь Symfony2 будет искать перевод самого сообщения (Hello %name%), а затем заменит “заполнители” соответствующими значениями. Перевод осуществляется так же, как и раньше:
XML:

<!-- messages.fr.xliff -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="1">
<source>Hello %name%</source>
<target>Bonjour %name%</target>
</trans-unit>
</body>
</file>
</xliff>

PHP:

// messages.fr.php
return array(
'Hello %name%' => 'Bonjour %name%',
);

YAML:

# messages.fr.yml
'Hello %name%': Hello %name%

“Заполнители” могут принимать любую форму, так как готовое сообщение восстанавливается с использованием функции PHP strtr. Однако, при переводе Twig шаблонов необходимо использовать комментарий %var%.

Как мы уже убедились, осуществление перевода заключает в себе два этапа:
1. Выделение сообщения, которое необходимо перевести, путём обработки в “Переводчике”.
2. Осуществление перевода сообщения для каждого выбранного Вами языка.

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

Каталоги сообщений

В процессе перевода Symfony2 составляет каталог сообщений для пользовательского языка и производит в нём поиск перевода сообщения. Каталог сообщений похож на словарь переводов для определённого языка. Например, каталог для языка fr_FR может содержать следующий перевод:

Symfony2 is Great => J'aime Symfony2

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

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

php app/console cache:clear

Локации перевода и правила присваивания имён

Symfony2 производит поиск файлов сообщений (т.е. переводов) в двух локациях:

  • Если сообщение находится в комплексе, соответствующие файлы должны быть расположены Resources/translations/ директории комплекса;
  • Чтобы подменить какие-либо переводы внутри комплекса, разместите файлы сообщения app/Resources/translations директории.
  • Название файлов перевода также немаловажно, поскольку Symfony2 использует определённые правила для получения сведений о переводах. Каждый файл сообщения следует называть по следующему образцу: domain.locale.loader:

  • domain: факультативный способ объединения сообщений в группы (например, admin, navigation или, по умолчанию, messages);
  • locale: язык перевода (например, en_GB, en и т.д.);
  • loader: способ загрузки и анализа файла (например, xliff, php или yml).
  • Loader может содержать название любого зарегистрированного средства загрузки. По умолчанию, Symfony предлагает следующие загрузчики:

  • xliff: XLIFF файл;
  • php: PHP файл;
  • yml: YAML файл.
  • Выбор загрузчика целиком зависит от Вашего вкуса.

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

    Создание переводов

    Каждый файл состоит из серии “перевод-id” пар для данной области и языка. ID – это идентификатор для отдельного перевода, который может быть сообщением на основном языке (например, «Symfony is great») Вашего приложения, либо уникальным идентификатором (например, «symfony2.great» – смотрите ниже):

    XML:

    <!-- src/Acme/DemoBundle/Resources/translations/messages.fr.xliff -->
    <?xml version="1.0"?>
    <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
    <file source-language="en" datatype="plaintext" original="file.ext">
    <body>
    <trans-unit id="1">
    <source>Symfony2 is great</source>
    <target>J'aime Symfony2</target>
    </trans-unit>
    <trans-unit id="2">
    <source>symfony2.great</source>
    <target>J'aime Symfony2</target>
    </trans-unit>
    </body>
    </file>
    </xliff>

    PHP:

    // src/Acme/DemoBundle/Resources/translations/messages.fr.php
    return array(
    'Symfony2 is great' => 'J\'aime Symfony2',
    'symfony2.great' => 'J\'aime Symfony2',
    );

    YAML:

    # src/Acme/DemoBundle/Resources/translations/messages.fr.yml
    Symfony2 is great: J'aime Symfony2
    symfony2.great: J'aime Symfony2

    Symfony2 найдёт и использует эти файлы, будь то «Symfony2 is great» или «symfony2.great» в регионе французского языка (например, fr_FR или fr_BE).

    Использование реальных или ключевых сообщений
    Данный пример иллюстрирует два различных подхода в создании сообщений для перевода:

    $t = $translator->trans('Symfony2 is great');
    $t = $translator->trans('symfony2.great');

    В первом случае сообщения составляются на стандартном языке (в данном случае – английском). Затем это сообщение используется в качестве “идентификатора” при создании переводов.

    Во втором случае сообщения являются, по сути, “ключевыми словами”, выражающими основную мысль. Затем ключевое сообщение используется в качестве “идентификатора” для любых переводов. В этом случае, переводы производятся на стандартный язык (т.е. перевод symfony2.great в Symfony2 is great).

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

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

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

    YAML:

    symfony2:
    is:
    great: Symfony2 is great
    amazing: Symfony2 is amazing
    has:
    bundles: Symfony2 has bundles
    user:
    login: Login

    PHP:

    return array(
    'symfony2' => array(
    'is' => array(
    'great' => 'Symfony2 is great',
    'amazing' => 'Symfony2 is amazing',
    ),
    'has' => array(
    'bundles' => 'Symfony2 has bundles',
    ),
    ),
    'user' => array(
    'login' => 'Login',
    ),
    );

    Некоторые уровни могут быть сведены в единые id/перевод пары путём добавления точки (.) в промежутках между уровнями. Таким образом, приведённые выше примеры эквивалентны следующему:

    YAML:

    symfony2.is.great: Symfony2 is great
    symfony2.is.amazing: Symfony2 is amazing
    symfony2.has.bundles: Symfony2 has bundles
    user.login: Login

    PHP:

    return array(
    'symfony2.is.great' => 'Symfony2 is great',
    'symfony2.is.amazing' => 'Symfony2 is amazing',
    'symfony2.has.bundles' => 'Symfony2 has bundles',
    'user.login' => 'Login',
    );

    Использование области сообщений

    Как мы убедились, файлы сообщений разбиты по языкам. Далее, указанные файлы могут разбиваться на “области”. Во время создания файла сообщения они составляет первую часть файлового имени. По умолчанию, эта область называется “messages”. Предположим, переводы были разбиты на три различных области: messages, admin и navigation. Перевод на французский язык будет иметь следующие файлы сообщения:

  • messages.fr.xliff
  • admin.fr.xliff
  • navigation.fr.xliff
  • При переводе строк, находящихся вне стандартной области (messages), Вам следует указать последнюю в качестве параметра trans():

    $this->get('translator')->trans('Symfony2 is great', array(), 'admin');

    Теперь Symfony2 будет производить поиск сообщения в области admin языка пользователя.

    Язык пользователя

    Язык, использующийся пользователем в данное время, хранится в сессии и может быть вызван с помощью session сервиса:

    $locale = $this->get('session')->getLocale();
    
    $this->get('session')->setLocale('en_US');

    Альтернативный и стандартный языки

    Если язык не задан в сессии, “Переводчик” будет использовать fallback_locale параметр настройки. По умолчанию, используется параметр en.

    Вы можете гарантированно установить язык в сессию пользователя, задав параметр default_locale для сессии:

    YAML:

    # app/config/config.yml
    framework:
    session: { default_locale: en }

    XML:

    <!-- app/config/config.xml -->
    <framework:config>
    <framework:session default-locale="en" />
    </framework:config>

    PHP:

    // app/config/config.php
    $container->loadFromExtension('framework', array(
    'session' => array('default_locale' => 'en'),
    ));

    Язык и URL

    Поскольку язык пользователя хранится в сессии, может возникнуть соблазн использовать единый URL для отображения ресурса на разных языках. Например, http://www.example.com/contact может отображать содержание на английском языке для одного и на французском языке для другого. К сожалению, в таком случае нарушается основное веб-правило: отдельный URL выдаёт один ресурс, независимо от пользователя. Кроме того, непонятно, какая версия содержания будет индексироваться поисковиками?

    Лучше всего включать язык в URL. Такая методика полностью поддерживается системой маршрутизации при использовании специального параметра _locale:

    YAML:

    contact:
    pattern: /{_locale}/contact
    defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en }
    requirements:
    _locale: en|fr|de

    XML:

    <route id="contact" pattern="/{_locale}/contact">
    <default key="_controller">AcmeDemoBundle:Contact:index</default>
    <default key="_locale">en</default>
    <requirement key="_locale">en|fr|de</requirement>
    </route>

    PHP:

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('contact', new Route('/{_locale}/contact', array('_controller' => 'AcmeDemoBundle:Contact:index','_locale' => 'en',), array('_locale' => 'en|fr|de')));
    return $collection;

    При использовании параметра _locale в маршруте, языковое соответствие автоматически будет установлено в сессию пользователя. Иными словами, если пользователь посещает URL /fr/contact, язык fr будет автоматически выбран языком пользовательской сессии.
    Теперь Вы можете использовать язык пользователя для создания маршрутов к другим переведённым страницам приложения.

    Образование множественного числа

    Образование множественного числа в сообщениях – трудная тема, поскольку правила могут быть довольно сложными. Рассмотрим, например, математическое представление о правилах образования множественного числа в русском языке:

    (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);

    Как видите, в русском языке у Вас может быть три различных формы множественного числа, каждой из которых присвоен индекс 0, 1 или 2. Поскольку каждая форма отображает определённую форму множественного числа, перевод также будет различным.

    Если при образовании множественного числа создаются новые формы перевода, Вы можете представить их в виде строк, разделённых вертикальной чертой (|):

    'There is one apple|There are %count% apples'

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

    $t = $this->get('translator')->transChoice(
    'There is one apple|There are %count% apples',
    10,
    array('%count%' => 10)
    );

    Второй параметр (в рассмотренном примере 10), является количеством описываемых объектов и используется для определения перевода, который будет использоваться. Также этот параметр заменяет заполнитель %count%.

    На основании заданного количества “Переводчик” выбирает верную форму множественного числа. В английском языке большинство слов имеют форму единственного числа, выражая один объект, и множественную форму для другого количества (0, 2, 3…). Таким образом, если параметр count будет равен 1, “Переводчик ” будет использовать первую строку (There is one apple) в качестве перевода. В другой ситуации будет использоваться There are %count% apples.

    Перевод на французский язык:

    'Il y a %count% pomme|Il y a %count% pommes'

    Даже если строки выглядят одинаково (две подстроки, разделённые вертикальной чертой), существуют различия в правилах французского языка: первая форма (единственное число) используется в тех случаях, когда показатель count равен 0 или 1. Таким образом, “Переводчик” автоматически будет использовать первую строку (Il y a %count% pomme) если count равняется 0 или 1.

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

    'one: There is one apple|some: There are %count% apples'
    
    'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'

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

    Образование множественного числа и точно заданные интервалы

    Самый простой способ образования множественного числа в сообщении — позволить Symfony2 использовать внутреннюю логику для выбора строки, основываясь на заданном количестве. Иногда Вам можете понадобиться другой перевод, предназначенный для особых случаев (для 0, или в случаях, когда когда показатель количества – отрицательное число). В таких случаях можно использовать точно заданные математические интервалы:

    '{0} There is no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There are many apples'

    Интервалы следуют за обозначением ISO 31-11. Вышеприведённая строка определяет четыре различных интервала: ровно 0, ровно 1, 2-19, а также 20 и больше.

    Вы также можете совмещать точно заданные и стандартные правила. В таком случае, если количество не соответствует определённому интервалу, стандартное правило вступает в силу после удаления точно заданного:

    '{0} There is no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %count% apples'

    Например, для 1 яблока будет использоваться стандартное правило There is one apple. Для 2-19 яблок будет выбрано второе стандартное правило There are %count% apples.

    Интервал может отображать конечное множество чисел:

    {1,2,3,4}

    Либо цифры между двумя другими числами:

    [1, +Inf[
    
    ]-1,2[

    Левый ограничитель может быть [ (включительно) или ] (исключая). Правый ограничитель может быть [ (исключая) или ] (включительно). Помимо чисел Вы можете использовать -Inf и +Inf для обозначения бесконечности.

    Переводы в темплейтах

    Большую часть времени перевод осуществляется в темплейтах. Symfony2 предоставляет встроенную поддержку для Twig и PHP темплейтов.

    Темплейты

    Symfony2 предоставляет специализированные Twig тэги (trans и transchoice) для помощи в переводе статических текстовых блоков:

    {% trans %}Hello %name%{% endtrans %}
    {% transchoice count %}
    {0} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples
    {% endtranschoice %}

    Тэг transchoice автоматически вызывает переменную %count% из текущего содержания и вставляет её в “Переводчик”. Данный механизм работает только тогда, когда Вы используете “заполнитель” вслед за шаблоном %var%.

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

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

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

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

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

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

    {# text translated between tags is never escaped #}
    {% trans %}
    <h3>foo</h3>
    {% endtrans %}
    {% set message = '<h3>foo</h3>' %}
    {# a variable translated via a filter is escaped by default #}
    {{ message | trans | raw }}
    {# but static strings are never escaped #}
    {{ '<h3>foo</h3>' | trans }}

    PHP темплейты

    Получить доступ к сервису перевода в PHP можно с помощью хелпера translator:

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

    Определение языка “Переводчика”

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

    $this->get('translator')->trans(
    'Symfony2 is great',
    array(),
    'messages',
    'fr_FR',
    );
    $this->get('translator')->trans(
    '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
    10,
    array('%count%' => 10),
    'messages',
    'fr_FR',
    );

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

    Перевод содержимого базы данных осуществляется Doctrine с переводящего дополнения.

    Резюме

    С помощью компонента Symfony2 Translation создание интернационализированных приложений не представляет сложности и сводится к нескольким основным этапам:

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


  • Добавить комментарий

    Ваш e-mail не будет опубликован. Обязательные поля помечены *