Одним из последних проектов над которым я работал около месяца — разработка социальной сети. Сайт не такой уж сложный. Но был (и есть один момент) который мне не дает покоя — это поддержка языков. В WordPress, данный вопрос давно и хорошо проработан. И если ты имеешь навыки работы за компьютером, писать и набирать текст. То перевести тему или плагин большого труда не составит. Но для самописной CMS, которую я использую практически во всех разработках — это проблема. Если изначально, поддержка языков не была проработано на должном уровне.По наивности, я решил использовать тот функционал локализации, который предоставляет фреймворк Kohana (собственно на котором и написан мой движок). По умолчанию, фразы и слова с переводом хранятся в обычном php файле в виде массива. Пример перевода пагинации ниже:
return array( 'First' => 'Первая', 'Last' => 'Последняя', 'Previous' => '<', 'Next' => '>', );
Преимущество данного подхода одно — производительность. Какое еще? Да кажется все. Но в высоко нагруженных проектах это важно!Если конечно дорабатывать, ранее созданный сайт с такой локализацией не планируется. То подобный способ вполне пригоден. Или если существует (или пишется под заказ) специально ПО, позволяющее сформировать вышеуказанный массив из функции «__(...)», то данный подход так же сгодится (об этой идее я кстати прочел на хабаре).Так вот. Я как представил себе, сколько это правок придется вносить в код и в текст во время доработок. Сколько нервов я потрачу с этими языками (а их на данный момент пять). Что «сразу» же решил отказаться от данного подхода.Потратив некоторое время, я пришел к следующему выводу.
- Можно использовать расширение PHP «gettext»
- Использовать наработки существующих CMS (WP, Drupal, Joomla)
Начал с первого. Честно сказать оказалось не так все просто. Всякие заморочки с установкой текущей локализации, непонятные пути к файлам и установкой кодировки. В общем, ничего у меня не вышло. К тому же! У клиента, на сервере данное расширение отсутствовало. В следствии чего, от данного подхода пришлось отказаться.Хотя с gettext разобраться все же нужно.Пункт второй. К счастью, на тот момент у меня был небольшой опыт работы с WP. И я решил в нем покопаться. Изучив как в нем реализован данный механизм. Было принято решение использовать именно его.Проанализировав последовательность вызовов функций «load_theme_textdomain()» и «__()» был разработан простенький класс работы с языками. Который был успешно внедрен (но окончательно не протестирован).
Как подключить POMO WordPress к своему движку
От самого WordPress нам понадобится пять файлов из каталога «/wp-includes/pomo/» (а именно — entry.php, mo.php, po.php, streams.php, translations.php). Возможно все файлы и не нужны, дело в том, что логику работы классов я не изучал, поэтому взял все и не стал «париться».Ниже, пример класса, выполняющего те же задачи что и функции «load_theme_textdomain()» и «__()» в WP.
<?php class Lng { /** * Массив языковых файлов * @var array */ private static $_lang_ar = array(); /** * Возвращает перевод строки * @param string $s переводимая строка. Пример "Hello from {site}" * @param strings $l языковый файл * @param array $a массив замены подстрок. Пример - array('{site}' => 'wp2fl.com') * @return strings */ public static function _($s, $l = 'default', $a = NULL) { $l = Lng::l().'-'.$l.'.mo'; if(isset(Lng::$_lang_ar[$l]) == FALSE) { Lng::$_lang_ar[$l] = new NOOP_Translations; } $s = Lng::$_lang_ar[$l] -> translate($s); return empty($a) ? $s : strtr($s, $a); } /** * Загружает языковый файл * @param string $l название файла * @param string $p путь к каталогу где храняться языковые файлы */ public static function load($l, $p) { $l = Lng::l().'-'.$l.'.mo'; if(class_exists('MO', FALSE) == FALSE) { throw new Exception('The "MO" library does not included'); } try { $MO = new MO(); if($MO -> import_from_file($p.$l) == FALSE) { return FALSE; } if(isset(Lng::$_lang_ar[$l])) { $MO -> merge_with(Lng::$_lang_ar[$l]); } Lng::$_lang_ar[$l] = &$MO; } catch (Exception $e) { throw new Exception('The language file "'.$l.'" does not exist'); } return TRUE; } /** * Возвращает текущий язык * @return string */ public static function l() { return 'ru'; } }
А еще ниже, пример использования:
<?php header('Content-Type: text/html; charset=utf-8'); try { /* Подключаем библиотеки */ require_once('vendor/pomo/mo.php'); require_once('library/lng.php'); /* Подключаем первый языковый файл */ Lng::load('first','languages/'); /* Подключаем второй языковый файл */ Lng::load('second','languages/'); } catch(Exception $e) { echo $e -> getMessage(); } $name_first = 'Андрей'; $name_second = 'Алексей'; ?> <p><?php echo Lng::_('Hello {name}, how are you?','first',array('{name}' => $name_first))?></p> <p><?php echo Lng::_('I am fine, thx {name}. And you?','second',array('{name}' => $name_second))?></p>
Не думаю что код нуждается в описании, т. к. везде есть комментарии. Обращу лишь ваше внимание на то, что языковые файлы должны храниться в каталоге «languages» и иметь названия «first» и «second».Результат работы кода следующий:
Привет Андрей, как ты? Нормально, спасибо Алексей. А как ты?
Рабочий пример можно скачать по этой ссылке. Удачных вам переводов 🙂
Не понял а что мешает создать простенькую функцию, например:
function lang($s){ global $lang; return isset($lang[$s]) ? $lang[$s] : $s; }
где $lang тот самый массив, о котором вы упоминали в начале. Его можно прописать в отдельном файле, например: langs/ru.php - ну и подключать нужные файлы по необходимости.
С переменными в строке тоже как бы проблем не должно возникнуть, если слегка дописать функцию, но суть вы, думаю, уловили. Или я не уловил сути сказанного вами?
Здравствуйте, Сергей!
Возможно я плохо изложил суть проблемы. Проблема не в возможности отображать преводимые фразы. Проблема в удобстве поиска фраз которые необходимо пеоевести. А редактор poedit ее очень хорошо решает. А использование poedit, "тянет" за собой расширение php gettext или pomo от WordPress.