Журнал LinuxFormat - перейти на главную

LXF80:PHP

Материал из Linuxformat
Перейти к: навигация, поиск

ПОДРОБНО О РАСШИРЕНИЯХ ПРОГРАММИРОВАНИЕ СЦЕНАРИЕВ PHP: перевод с помощью Gettext

Пол Хадсон показывает простой способ добавить поддержку множества языков в ваши сценарии. Powodyenia!


История всего программирования вплоть до нашего времени — это история постоянного изобретения колеса. На самом деле это довольно неприятно, так как если вы сейчас захотите посадить в каждое колесо по белке, то у вас быстро закончатся белки. Так почему же мы до сих пор продолжаем заново переписывать базовые концепции и решения?

Если вам претит писать нечто, уже кем-то написанное (кроме случаев, когда вы хотите узнать, как это работает) — значит настало время отступить и позволить другим людям сделать работу для вас. В этой первой из посвящённых расширениям PHP статей мы рассмотрим Gettext, позволяющий создавать приложения с поддержкой множества языков. Итак, вперёд!

Множество переводов

В прошлой статье мы с вами убедились, что поддержка Unicode в PHP далека от совершенства, но это не значит, что программы на PHP вообще нельзя переводить на другие языки. Фактически, в PHP есть прекрасная поддержка системы GNU Gettext, которая позволяет перевести все строки в вашем проекте так, чтобы пользователи работали с ним на своём родном языке. Вероятно, вы подумали: «Ну, это не сложно, потребуется примерно такой код», — и написали нечто вроде:

include "lang/spanish.php";
print $welcome;

Такая программа будет прекрасно работать, но её очень трудно поддерживать — вам потребуется великолепная память, чтобы запомнить, что же содержится в переменной $welcome, особенно когда число строк, нуждающихся в переводе, перевалит за тысячу.

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

Поддержка Gettext по умолчанию отсутствует в PHP, так что вам потребуется достать последнюю версию исходного кода и во время конфигурации указать опцию --with-gettext в числе других опций, настраивающих, какие расширения вы собираетесь использовать. После выполнения команд make и make install вы будете готовы к последней проверке. Запустите php -m и убедитесь, что модуль Gettext установлен.

Кроме того, чтобы использовать Gettext в своих приложениях, вам потребуются пакеты gettext и gettext-devel. Модуль gettext устанавливается по умолчанию во всех дистрибутивах, которые я знаю, но утилиты для его использования в своих программах входят в gettext-devel, так что вам придётся установить и его. В моём дистрибутиве SUSE 10.0 gettext-devel загадочным образом отсутствует, но я быстро нашёл нужный RPM в Google.

Теперь давайте создадим простой сценарий, который затем будем переводить. Для этого нам потребуются несколько новых функций: setlocale() для того, чтобы подменить текущие языковые настройки пользователя, bindtextdomain() для того, чтобы указать где на жёстком диске находится файл перевода и textdomain(), чтобы приказать Gettext использовать этот перевод. Запишите следующий код в файл gettext_test.php:

<?php
setlocale(LC_ALL, "pl_PL");
bindtextdomain("messages", "./locale");
textdomain("messages");
echo gettext("Hello, Linux Format!\n");
?>

Hables ingles

Следующим шагом мы добавим несколько переводов нашего текста. И тут уже вступает в игру gettext-devel. Если у вас установлен этот пакет, значит, вы можете воспользоваться утилитой xgettext, которая извлекает все строки из программы и создаёт файл messages.po. Это тот самый файл, в котором будет жить ваш перевод, и именно его нужно отправлять команде, отвечающей за локализацию проекта, чтобы они могли сделать свою работу.

Поскольку у вас, скорее всего, нет под рукой команды локализации, вероятно вам придётся сделать и этот шаг самостоятельно (я надеюсь, что у вас найдётся польско-венгерский словарь). Для начала запустите

 xgettext -n gettext_test.php

Эта программа создаст файл messages.po, в котором после некоторого количества неинтересной информации будет следующее:

msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2006e-03-23 10:07+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: text1.php:6
msgid "Hello, Linux Format!\n"
msgstr ""

Каждой строке msgid соответствует строка в нашем PHP-сценарии. Ниже расположены строчки, начинающиеся на msgstr, в которых нужно указывать переводы. Использование параметра -n привело к включению в файл комментариев, указывающих где какая строчка находится.

Мы собираемся создать три варианта переводов: pl_PL (польский язык, на котором говорят в Польше), hu_HU (венгерский язык, на котором говорят в Венгрии) и en_GB (английский, на котором говорят в цивилизованной части мира). Мы уже указали в качестве области с текстами каталог locale, а это значит, что в этом каталоге нам нужно создать по подкаталогу для каждого поддерживаемого языка. Каждый их этих каталогов должен содержать подкаталог LC_MESSAGES, в котором уже будет храниться наш PO-файл с переводом.

Итак, скопируйте файл messages.po в каждый из этих каталогов.

Откройте файл в каталоге locale/pl_PL/LC_MESSAGES и отредактируйте его так, чтобы последними строчками были:

msgid "Hello, Linux Format!\n";
msgstr "Czesc, Linux Format!\n";

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

msgfmt messages.po

Вероятно, вы получите предупреждение о неправильной кодировке, но не беспокойтесь на этот счёт — msgfmt преобразует messages.po в messages.mo, который содержит бинарные версии наших строк и может быть использован gettext.

В каталоге locale/hu_HU/LC_MESSAGES вы, вероятно, захотите указать в качестве значения msgstr «Szia, Linux Format!\n», а в locale/en_GB/LC_MESSAGES — «What ho, Linux Format!\n». Предупреждая ваш вопрос, сразу отвечу — нет, в действительности я так не разговариваю. Запустите msgfmt в каждом из каталогов, так чтобы у вас в итоге получилась структура, показанная на рис. 1, где в качестве корня служит тот каталог, в котором вы работаете.

Прямо сейчас вы уже можете запустить программу и увидеть работу gettext в действии. Если вы помните, в программе мы указали в качестве языка pl_PL, так что если вы запустите php gettext_test.php, то получите на консоли текст 'Czesc, Linux Format!'.

Если вы измените файл gettext_test.php так, что вместо pl_PL там будет указано en_GB, то текст на консоли соответствующим образом изменится.

Обработка множественных форм.

Программисты ленивы. Я имею ввиду вот что — видели ли вы гденибудь текст наподобие «3 file(s) deleted»? Такие вещи — это обычное дело, поскольку требуется написать несколько лишних строк кода, чтобы программа говорила «files» когда речь идёт об удалении более чем одного файла и «file» в случае удаления единственного экземпляра. Но если даже потребовать от программистов аккуратности, они скорее всего напишут что-то вроде

if ($deleted_files == 1) {
echo "1 file deleted";
} else {
echo "$deleted_files deleted";
Img 80 79 1.jpg

Да, это немного получше, но всего лишь немного. Проблема в том, что все три языка, которые мы используем, имеют разные правила образования множественного числа: в английском языке используются формы file и files, венгры используют одну единственную форму (f jl), идёт ли речь об одном файле или о тысячах. Польский ещё сложнее — у вас есть один plik, два, три или четыре pliki, а для чисел от 5 до 21 используется форма plik w. Это очень сложно, но Gettext прекрасно справляется даже здесь.

Для начала давайте добавим в наш тестовый сценарий строку, которая зависит от числа. Добавьте в конец файла gettext_test.php следующий код:

$file_count = 0;
printf(ngettext("%d file", "%d files", $file_count), $file_
count);
echo "\n";

Функция ngettext() отвечает за образование множественного числа для выбранного языка. В качестве первого его параметра передаётся текст для единственного числа, в качестве второго — какую строку использовать для множественного числа, а в качестве третьего — число, от которого зависит выбор формы. %d передаётся в gettext и не изменяется им, так что например при использовании польского языка будет возвращено %d pliki, если $file_count равно 34. Затем эта строка передаётся в функцию printf(), которая уже подменяет %d на значение переменной $file_count.

Теперь нам нужно научить Gettext вычислять множественные формы для всех используемых языков. Итак, в en_GB messages.po добавьте следующую строчку прямо перед 'Project-Id-Version':

"Plural-Forms: nplurals=2; plural=n != 1\n"

Это значит: в этом языке есть две формы множественного числа, множественное надо использовать, если значение числа не равно единице. Теперь нам надо добавить переводы для строк %d file и %d files, но поскольку дело касается множественного числа, то и указывать переводы надо особым образом. Добавьте в messages.po в каталоге en_GB/LC_MESSAGES следующий текст:

msgid "%d file"
msgid_plural "%d files"
msgstr[0] "I say, there is %d file!"
msgstr[1] "I say, there are %d files!"

Для венгерского языка описание множественной формы будет следующим:

"Plural-Forms: nplurals=1; plural=0"

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

msgid "%d file"
msgid_plural "%d files"
msgstr[0] "%d f jl"

И наконец, польский язык. У него очень сложная строка описания множественной формы, поскольку надо обработать три типа множественного числа. К счастью, дружественные к GNU местные жители, которые написали справку к Gettext, подготовили для нас правильный вариант:

"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 &&
(n%100<10 || n%100>=20) ? 1 : 2;\n"

А вот перевод:

msgid "%d file"
msgid_plural "%d files"
msgstr[0] "%d plik"
msgstr[1] "%d pliki"
msgstr[2] "%d plik w"

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

Gettext — это всё, что вам нужно для того, чтобы ввести многоязычность в ваши программы. Да, придётся приложить некоторые усилия, но пользователи будут вам очень благодарны.


ПОДСКАЗКИ

  • Если вам надоело постоянно набирать gettext(), просто используйте более короткое имя функции _().
  • Если вы хотите установит ваш вариант перевода на уровне всей системы, то не используйте файлы под названием messages.mo.

Вместо этого используйте название своего приложения, например fuzz.mo, чтобы избежать конфликта имён. Вам потребуется изменить вызовы bindtextdomain() и textdomain(), чтобы они соответствовали новому имени файла.

  • Когда вы распространяете своё приложение для обычных пользователей, то вам не нужно включать в дистрибутивы PO файлы, вполне достаточно его скомпилированной MO версии. PO может потребоваться только тем, кто будет изменять перевод.
  • Чтобы узнать, как правильно обрабатывать множественные числа других языков, обратитесь к онлайн-документации

Gettext на http://www.gnu.org/software/manual.

Персональные инструменты
купить
подписаться
Яндекс.Метрика