Статьи

Локализация PHP-приложений «Правильный путь», часть 2

Добро пожаловать в эту серию статей, в которых вы узнаете, как локализовать ваши PHP-приложения, используя gettext и его расширение PHP. В первой части вы сделали первые шаги к этому, установив gettext и Poedit, создав файл перевода и написав скрипт Hello World. В этой части вы познакомитесь с каждой из функций, используемых в скрипте, и углубитесь в библиотеку gettext и ее использование.

Сценарий «Привет, мир!»

Для обзора в части 1 был показан следующий скрипт под именем TestI18N/test-locale.php :

 <?php // I18N support information here $language = "en_US"; putenv("LANG=" . $language); setlocale(LC_ALL, $language); // Set the text domain as "messages" to // use Locale/en_US/LC_MESSAGES/messages.mo $domain = "messages"; bindtextdomain($domain, "Locale"); bind_textdomain_codeset($domain, "UTF-8"); // Use the messages domain textdomain($domain); echo _("HELLO_WORLD"); 

Вызов putenv() и установка переменной среды LANG указывает gettext, какой язык он будет использовать для этого сеанса. en_US – это идентификатор английского языка, используемый в США. Первая часть локали представляет собой двухбуквенную строчную аббревиатуру для языка в соответствии со спецификацией ISO 639-1, а вторая часть представляет собой двухбуквенный прописной код страны в соответствии со спецификацией ISO 3166-1 alpha-2. setlocale() определяет локаль, используемую в приложении, и влияет на то, как PHP сортирует строки, понимает форматирование даты и времени и форматирует числовые значения.

gettext вызывает файл каталога, используемый для хранения сообщений перевода (файл MO), доменом . Функция bindtextdomain() сообщает gettext, где найти домен для использования; первый параметр – это имя каталога без расширения .mo , а второй параметр – это путь к родительскому каталогу, в котором en_US/LC_MESSAGES подпуть en_US/LC_MESSAGES (в котором, в свою очередь, находится файл перевода). Если вам интересно, откуда en_US/LC_MESSAGES подпуть en_US/LC_MESSAGES , он en_US/LC_MESSAGES методом gettext с использованием значений переменной LANG вы указали с помощью putenv() и категории локали LC_MESSAGES . Вы можете вызывать bindtextdomain() несколько раз, чтобы связать столько доменов, сколько хотите, в случае, если вы разделили переводы на несколько файлов.

Вызов bind_textdomain_codeset() очень важен, поскольку bind_textdomain_codeset() этого может привести к неожиданным символам в выводе при использовании букв не-ASCII. Так как сообщения каталога закодированы в UTF-8, это то, что пример кода устанавливает как набор кодов. Я всегда рекомендую использовать UTF-8, так как это наиболее широко поддерживаемая кодировка Unicode. Не используйте другие менее известные кодировки, если вы точно не знаете, что делаете; Вы столкнетесь с серьезными проблемами, особенно в Интернете.

Вызов textdomain() сообщает gettext, какой домен использовать для любых последующих вызовов gettext() , или его сокращенного псевдонима _() , или метода поиска формы во множественном числе ngettext() . Я расскажу о работе с множественными формами в следующей части, но сейчас вы должны знать, что все три из этих методов ищут сообщения в текущем домене, указанном с помощью textdomain() .

Наконец, скрипт вызывает _() , который ищет HELLO_WORLD в файле messages.mo и возвращает связанный с ним msgstr, текст Hello World!

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

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

 <?php $language = "en_US"; putenv("LANG=" . $language); setlocale(LC_ALL, $language); $domain = "foo"; bindtextdomain($domain, "Locale"); bind_textdomain_codeset($domain, "UTF-8"); // ... 

gettext попытается найти каталог Locale/en_US/LC_MESSAGES/foo.mo , который не должен существовать.

При просмотре вывода скрипта вы увидите HELLO_WORLD вместо Hello World! gettext не может выполнить перевод, потому что нет допустимого каталога, хотя другой сценарий может заключаться в том, что указанный msgid может не существовать ни в каких каталогах, зарегистрированных в gettext, и он достаточно умен, чтобы использовать оригинальную строку, которую вы указали.

Таргетинг на несколько локалей

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

В том же TestI18N/Locale создайте новый каталог с именем fr_FR содержащий другой каталог LC_MESSAGES , и используйте процедуры, описанные в части 1, для создания нового каталога для французского языка. Когда вы закончите, у вас должна быть следующая иерархия:

en_US and fr_FR directories

Когда вы указываете настройки каталога в Poedit, не забудьте установить французский язык как язык, а Франция – страну.

Окно настроек Poedit для французского

Мой французский messages.po будет выглядеть так при открытии в текстовом редакторе:

 msgstr ""
 msgstr ""
 "Project-Id-Version: TestProjectn"
 "POT-Creation-Date: n"
 "PO-Revision-Date: n"
 «Последний переводчик: имя_переименования <email@example.com> n»
 "Language-Team: MyTeam <team@example.com> n"
 "MIME-версия: 1.0n"
 "Тип содержимого: текст / обычный; кодировка = utf-8n"
 "Content-Transfer-Encoding: 8bitn"
 "X-Poedit-Language: Frenchn"
 "X-Poedit-Country: ФРАНЦИЯ"
 "X-Poedit-SourceCharset: utf-8n"

 # Тестовый токен 1
 msgstr "HELLO_WORLD"
 msgstr "Добрый день!"

 # Тестовый токен 2
 msgstr "TEST_TRANSLATION"
 msgstr "Тест de traduction ..." 

Большинство строк заголовка файла говорят сами за себя, поэтому я пропущу прямо к фактическим строкам перевода, которые начинаются с первого msgid после заголовков. Обратите внимание, что для каждой фразы, которую нужно перевести, есть две строки, msgid, который является строкой идентификатора в вашем коде, gettext будет искать, и msgstr, который является переведенным сообщением, которое gettext заменит для идентификатора. Первое определение указывает gettext использовать Bonjour tout le monde! всякий раз, когда он видит HELLO_WORLD . Второй инструктирует gettext использовать Test de traduction… для TEST_TRANSLATION .

Снова откройте файл каталога в Poedit и щелкните запись «Сохранить каталог» на панели значков, чтобы сохранить и скомпилировать ее. Затем измените скрипт PHP, чтобы использовать fr_FR вместо en_US . Когда вы запустите его, вы увидите, что вывод в вашем браузере теперь французский!

Резюме

В этой части вы узнали, что делает каждый вызов функции в скрипте Hello World, представленном в части 1. С точки зрения своего API, gettext на самом деле не большая библиотека. Существует всего несколько функций, большинство из которых вы будете использовать только один раз во всем приложении. Наиболее часто используемым будет gettext() , или его псевдоним _() , и его множественное число эквивалентно ngettext() . Вы также узнали, как настроить таргетинг на несколько fr_FR в нашем примере это en_US и fr_FR ) и как gettext возвращается к сообщению, если отсутствует перевод.

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

Изображение через sgame / Shutterstock