Одним з останніх проєктів, над яким я працював близько місяця — розробка соціальної мережі. Сайт не такий вже й складний. Але був (і є один момент), який не дає мені спокою — це підтримка мов. У WordPress це питання давно й добре опрацьоване. І якщо ви вмієте працювати за комп’ютером, писати та набирати текст — перекласти тему чи плагін не складає великих труднощів. Але для самописної CMS, яку я використовую майже у всіх розробках — це проблема. Якщо з самого початку підтримка мов не була реалізована на належному рівні.
Через наївність, я вирішив використати той функціонал локалізації, який надає фреймворк Kohana (на якому, власне, й написано мій рушій). За замовчуванням фрази та слова з перекладом зберігаються в звичайному PHP-файлі у вигляді масиву. Приклад перекладу пагінації нижче:
return array(
'First' => 'Перша',
'Last' => 'Остання',
'Previous' => '<',
'Next' => '>',
);
Перевага цього підходу одна — продуктивність. Яка ще? Здається, що все. Але у високонавантажених проєктах це важливо! Якщо, звісно, не планується доопрацювання сайту з такою локалізацією — то метод підходить. Або якщо існує (чи пишеться під замовлення) спеціальне ПЗ, що дозволяє сформувати цей масив із функції «__(...)», — тоді також годиться (про цю ідею я читав на Хабрі).
Так от. Уявивши, скільки правок доведеться вносити у код і текст під час доробок, скільки нервів піде на підтримку мов (а їх наразі п’ять), я «відразу» ж вирішив відмовитися від цього методу.
Після деякого часу я дійшов висновку:
- Можна використати PHP-розширення «gettext»
- Скористатися підходом із CMS (WP, Drupal, Joomla)
Почав із першого. Чесно — не все так просто. Налаштування локалі, незрозумілі шляхи до файлів і кодування... Нічого не запрацювало. До того ж, на сервері клієнта не було встановлено це розширення. Тож довелося відмовитись. Хоча розібратись із gettext все ж варто.
Пункт другий. Добре, що у мене був певний досвід із WP. Я вирішив глибше розібратись. Проаналізувавши виклики функцій «load_theme_textdomain()» і «__()», я створив простий клас для роботи з перекладами. І хоча він ще не до кінця протестований — уже працює.
Як підключити POMO WordPress до власного рушія
Нам знадобляться 5 файлів із каталогу «/wp-includes/pomo/»:
entry.php, mo.php, po.php, streams.php, translations.php
Можливо, не всі потрібні — я просто взяв усі.
Нижче приклад класу, який повторює функціональність «load_theme_textdomain()» і «__()» у WP:
<?php
class Lng
{
private static $_lang_ar = array();
public static function _($s, $l = 'default', $a = NULL)
{
$l = Lng::l().'-'.$l.'.mo';
if (!isset(Lng::$_lang_ar[$l]))
{
Lng::$_lang_ar[$l] = new NOOP_Translations;
}
$s = Lng::$_lang_ar[$l]->translate($s);
return empty($a) ? $s : strtr($s, $a);
}
public static function load($l, $p)
{
$l = Lng::l().'-'.$l.'.mo';
if (!class_exists('MO', FALSE))
{
throw new Exception('The "MO" library does not included');
}
try {
$MO = new MO();
if (!$MO->import_from_file($p.$l)) {
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;
}
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».
Результат:
Привіт Андрій, як ти? Нормально, дякую Олексій. А ти?
Робочий приклад можна завантажити тут. Успішних вам п_
