Если вы когда-либо работали над созданием сайта, который должен был быть доступен на нескольких языках, вы знаете, насколько это может быть сложно. С помощью компонента перевода Symfony2 вы можете легко создавать интернационализированные сайты. Я покажу вам, как с некоторым примером кода и некоторым обсуждением его API.
Основной пример
Давайте начнем с создания файла translation.php
с содержимым ниже.
<?php require 'vendor/autoload.php'; use SymfonyComponentTranslationTranslator, SymfonyComponentTranslationLoaderArrayLoader; $translator = new Translator('fr_FR'); $translator->addLoader('array', new ArrayLoader()); $translator->addResource('array', array( 'Hello World!' => 'Bonjour tout le monde!', ), 'fr_FR' ); echo $translator->trans('Hello World!') . "n"; echo $translator->trans('How are you?') . "n";
Объект Translator
создается путем передачи локали, которую вы хотите использовать (в данном случае French / France) и, необязательно, экземпляра объекта MessageSelector
в качестве второго аргумента. MessageSelector
используется для множественного числа. Если вы не передадите его, конструктор Translator
создаст свой собственный.
Затем устанавливается загрузчик, который используется для загрузки языковых строк. Метод addLoader()
принимает имя (вы можете дать любое имя) и объект типа LoaderInterface
. В этом случае я использую ArrayLoader
который реализует интерфейс.
Метод addResource()
добавляет перевод сообщений. Первым аргументом является имя загрузчика, данное addLoader()
а вторым аргументом в этом случае является массив (так как я использую ArrayLoader
). В зависимости от загрузчика второй аргумент может измениться. Третий аргумент — строка языка.
Поиск перевода осуществляется с помощью метода trans()
. Если соответствующий перевод не найден, возвращается исходная строка.
Переводы обычно не выполняются программистами. Вместо этого они сделаны профессиональными переводчиками. Переводчики могут не иметь знаний в области программирования, и мы можем не захотеть давать им код. К счастью, Symfony может использовать разные загрузчики, из которых загружается список сообщений перевода. Полный список поддерживаемых загрузчиков: ArrayLoader
, CsvFileLoader
, MoFileLoader
, XliffFileLoader
, IcuDatFileLoader
, PhpFileLoader
, YamlFileLoader
, IcuResFileLoader
, PoFileLoader
, IniFileLoader
и QtTranslationsLoader
.
Давайте посмотрим, как загружать строки перевода, используя PoFileLoader
вместо массива, чтобы людям, которые переводят, было проще.
Измените второй аргумент addLoader()
на экземпляр объекта PoFileLoader
. Также в addResource()
путь к файлу PO.
<?php require 'vendor/autoload.php'; use SymfonyComponentTranslationTranslator, SymfonyComponentTranslationLoaderPoFileLoader; $translator = new Translator('fr_FR'); $translator->addLoader('pofile', new PoFileLoader()); $translator->addResource('pofile', 'languages/po/fr_FR.po', 'fr_FR');
Обработка резервных локалей
Вы можете использовать PHP-класс Locale
для получения запрошенной локали из заголовка Accept-Language или использовать компонент Symfony Locale (который расширяет класс PHP некоторыми дополнительными функциями) для динамической установки локали.
<?php $locale = Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']); $translator = new Translator($locale);
Но что, если локаль недоступна? Вы можете установить запасной языковой стандарт, используя метод setFallbackLocale()
. Если языковой стандарт не найден или языковой стандарт существует, но отсутствует перевод, тогда Translator
обратится к запасному варианту.
<?php $translator->setFallbackLocale('fr');
Один и тот же язык может отличаться в разных странах; Например, рассмотрим английский, и вы заметите en_GB для британского английского, en_US для американского английского и т. д. Каждый язык может иметь различия в зависимости от региона. Так что насчет наличия нескольких запасных вариантов?
Скажем, в этом случае запрошенная локаль fr_CA и у вас нет перевода fr_CA. В качестве запасного варианта вы можете попытаться получить переводы из fr_FR, а если нет, то из en_US, а затем из общего английского.
<?php $translator->setFallbackLocale(array('fr_FR', 'en_US', 'en'));
Могут быть случаи, когда вы хотите разделить сообщения одной локали на множество маленьких блоков. По умолчанию все строки добавляются и проверяются в домене сообщений (поэтому нам не нужно было передавать домен в trans()
).
Соглашение об именах для файлов перевода: домен . формат локали , например messages.fr.po
, navigation.fr.po
и т. д. Если вы хотите получать переводы из определенного домена, вы должны указать домен в качестве третьего аргумента для trans()
. Откаты происходят только в одном домене, а не в других доменах.
Вместе это выглядит так:
<?php require 'vendor/autoload.php'; use SymfonyComponentTranslationTranslator, SymfonyComponentTranslationLoaderPoFileLoader; $locale = Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']); $translator = new Translator($locale); $translator->setFallbackLocale(array('fr_FR', 'en_US', 'en')); $translator->addLoader('pofile', new PoFileLoader()); $iterator = new FilesystemIterator("languages/po"); $filter = new RegexIterator($iterator, '/.(po)$/'); foreach($filter as $entry) { $name = $entry->getBasename('.po'); list($domain, $locale) = explode('.', $name); $translator->addResource('pofile', $entry->getPathname(), $locale, $domain ); } echo $translator->trans('Hello World!') . "n"; echo $translator->trans('How are you?') . "n"; echo $translator->trans('How are you?', array(), 'navigation') . "n";
Если вы называете свои файлы перевода именами регионов, например, fr_FR.po, fr_CA.po и т. Д., То может быть хорошей идеей назвать некоторые из ваших запасных вариантов исключительно по языку (fr.po). Рассмотрим кого-нибудь из Бельгии, который посещает ваш сайт и запрашивает заголовок Accept-Language fr_BE. Возможно, у вас нет fr_BE, и fr_FR не будет соответствовать. Лучше отступить на fr.po, чем полностью переключиться на другой язык, например английский.
Только для языка с именем fallback можно извлечь из локали и поместить в начало массива следующим образом:
<?php $fallbacks = array('fr_FR', 'en_US', 'en'); $locale = Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']); array_unshift($fallbacks, substr($locale, 0, 2)); $translator->setFallbackLocale($fallbacks);
плюрализация
Работа с формами множественного числа является одной из самых сложных частей интернационализации. Чтобы выбрать разные переводы на основе номера, вы используете метод transChoice()
. Вот преувеличенный пример, чтобы продемонстрировать его использование:
<?php $value = 1; echo $translator->transChoice( '[-Inf, 0]There is nothing to delete|{1}Are you sure you want to delete this file|]1,10[ %count% files will be deleted|[10,Inf] Are you sure you want to delete all files', $value, array('%count%' => $value) ) . "n";
Альтернативные строки разделяются символом трубы. Измените значение $value
и посмотрите, как Translator
изменяет вывод. Сначала вы должны получить сообщение «Вы уверены, что хотите удалить этот файл». Если вы измените $value
на 2 на 9, вы получите сообщение «Файлы $ value будут удалены». Если вы используете 10 или больше, вы получите сообщение «Вы уверены, что хотите удалить все файлы». Если $value
меньше 1, вы получите «Удалить нечего».
Symfony Translation использует нотацию ISO 31-11, поэтому для выбора правильного предложения в приведенном выше описании используются интервальные классы. Мы можем написать диапазоны как это:
[a, b] означает a <= x <= b [a, b [означает a <= x <b ] a, b] означает a <x <= b ] a, b [означает a <x <b
Полезное мнемоническое устройство, облегчающее понимание диапазона, состоит в том, чтобы посмотреть, в каком направлении открывается квадратная скобка. Если квадратная скобка открывается в сторону числа, то это включительно.
[3 означает, что значение <= 3 (включительно) ] 3 означает, что значение <3 (исключая)
Вы также можете определить набор конкретных значений с помощью фигурных скобок, например {1,2,3}, чтобы они соответствовали только значениям 1, 2 и 3.
Преобразование между форматами перевода
Я начал эту статью, используя ArrayLoader
потому что, пожалуй, большинству разработчиков легче всего начать. Но потом я переключился на PoFileLoader
потому что PO-файлы проще для переводчиков. Что если у вас есть набор переводов в виде массивов или даже другой формат, например YAML , и вы хотите преобразовать их? Вы можете конвертировать из одного формата в другой для любого из загрузчиков Symfony, используя самосвалы.
<?php require 'vendor/autoload.php'; SymfonyComponentTranslationLoaderYamlFileLoader, SymfonyComponentTranslationMessageCatalogue, SymfonyComponentTranslationDumperPoFileDumper; $loader = new YamlFileLoader(); $iterator = new FilesystemIterator("languages/yaml"); $filter = new RegexIterator($iterator, '/.(yml)$/'); foreach($filter as $file) { $name = $file->getBasename('.yml'); list($domain, $locale) = explode('.', $name); $array = $loader->load($file->getPathname(), $locale, $domain); $catalogue = new MessageCatalogue($locale); $catalogue->addCatalogue($array); $dumper = new PoFileDumper(); $dumper->dump($catalogue, array('path'=> __DIR__ . '/languages/pofile')); }
Заказ<?php require 'vendor/autoload.php'; SymfonyComponentTranslationLoaderYamlFileLoader, SymfonyComponentTranslationMessageCatalogue, SymfonyComponentTranslationDumperPoFileDumper; $loader = new YamlFileLoader(); $iterator = new FilesystemIterator("languages/yaml"); $filter = new RegexIterator($iterator, '/.(yml)$/'); foreach($filter as $file) { $name = $file->getBasename('.yml'); list($domain, $locale) = explode('.', $name); $array = $loader->load($file->getPathname(), $locale, $domain); $catalogue = new MessageCatalogue($locale); $catalogue->addCatalogue($array); $dumper = new PoFileDumper(); $dumper->dump($catalogue, array('path'=> __DIR__ . '/languages/pofile')); }
Просто замените загрузчик на тот, который вы используете для ввода, и дампер для любого вывода, который вам нужен.
Резюме
Мы рассмотрели, как переводить строки, как работать с резервными локалями, как обрабатывается множественное число и как использовать Dumper. Я надеюсь, что это руководство поможет вам начать с интернационализации, которая упрощается с помощью компонента Symfony Translation.
Изображение через Fotolia