Мультиязычный интерфейс

yrtimD

Guest
Мультиязычный интерфейс

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

1) текстовой ini-файл
name=value
+специальный класс для чтения конфигурации.

2) php-код, константа:
define('_NAME', 'value');

3) php-код, одномерный ассоциативный массив (по имени):
$lang['name'] = "value";

4) php-код, одномерный непрерывный массив (по ключу):
$lang[0] = "value";

5) база данных, таблица с полями id (int),
name (varchar) и value(varchar).
+специальный класс для чтения.

6) xml-файл <name xml:lang="en">value</name>
+парсер и специальный класс для чтения.

Какой из этих методов лучше для крупного проекта
и как это проверить?
 

Sirius

PHP+MySQL=LOVE
Я использую 4-й метод - однако для слишком больших проектов это наверное не самый лучший выбор!
 

trent

Developer
если база не подразумевается, то я делал ассоциативный массив $var["name"]["lang"] = ""; , если с базой, то заводил отдельную таблицу языков(локалей), и при работе с другими таблицами делал связку один ко многим. IMHO для большого проекта это будет самое то.
 

DiMA

php.spb.ru
Команда форума
я сделал свой вариант. Никогда не юзал gettext, но думаю, это нечто похоже на _().

В тексте у меня идет echo "<a href=...>".m("click here")."</a>";

В начале проге массив
$m['click here']='тыкай сюда'
и функция m(), которая языки переключает.

Еще есть отдельная прога, которая сама парсит файл и вырезает все вызовы функции m(), чтобы составить массив автоматически с учетом новых фраз и удалением не нужных. Это крайне удобно.
 

Silex

unitecsys
Седьмой метод, родившийся недавно, но еще глобально не опробованный: в папке dictionary создать кучу файликов типа clickme_rus.inc, clickme_eng.inc и т.п., а затем инклудить в нужном месте типа

PHP:
include('dictionary/clickme_'.$lang.'.inc').;
Преимущества: не юзает базу (кстати, вопрос, что быстрее: шерстить быструю базу или искать на диске файлик?). Не хранятся в памяти массивы. Может использоваться даже новичками - никаких классов, парсеров, xml-ля...

Недостатки: пригоден, только если проект небольшой, а то потом в той директории черт ногу сломит...

Просто как вариант - можно через switch в каждом месте вставки, но это, естественно, только для увеличения количества методов :)

PS Сам использую базу для хранения контента и служебных надписей + для отдельных страниц, которые не надо искать в базе, вышеописанный метод.
 

yrtimD

Guest
Gettext мне лично не понравился. Заставить это работать оказалось намного сложнее, чем воспользоватся str_replace(). По умолчанию gettext отключен, а это значит потребуется перекомпилировать php, либо подключить dll на Windows, в которой всё равно LS_MESSAGES нет (а надо ли ставить Cygwin лишь для этого). С первого раза не заработало. Хорошее решение, на мой взгляд, должно работать всегда и при любой конфигурации. :)

Вариант с вызовом функции m() не подходит, так как я не одобряю использование php-кода в HTML-шаблонах. Да и не только я наверно.

Куча файликов для крупного проекта действительно не подойдет. Представьте PHP-nuke, в котором будет 30000 файлов (20 языков, по 1500 фраз в каждом).

Я попробовал все и выбрал номер 4. Потому что:
  • Первый вариант рассматривался на примере CMS ezPublish, которая использует ini-файлы для загрузки любых других установок, а не только информации о языках. С установками, записанными в виде ini-файлов удобно работать вручную, но редактировать translation файлы руками, сами понимате, - вчерашний день.
  • Второй вариант проигрывает по времени загрузки где-то на 30%. 1000 констант грузится дольше чем массив из 1000.
  • Третий вариант тоже дольше грузится чем четвертый.
  • Ну, пятый вариант, в принципе подойдет. Только если прочитать фразы из базы, потом представить в виде массива, положить массив в кэш и далее читать из кэша.
  • Шестым вариантом, как мне кажется, имеет смысл воспользоватся когда на сайте есть ещё что-нибудь в xml. У меня нет. Если уж загружать XSLT-процессор, то нужно отказаться от html-шаблонов. Ведь когда работает XML, то что-либо похожее на FastTemplates становится ненужным.
Спасибо за предложенные варианты.
 

DiMA

php.spb.ru
Команда форума
Самый удобный вариант - это m():

- нет надобности что-то подключать (типа геттекста)
- нет проблемы с ключами, т.е. чтобы написать фразу не нужно отрываться от программы и переключатся на файл языка
- нет проблемы с чтением ключей (по числу узнать, что это за фраза)
- нет проблем со скоростью (можно константы, текстовый файл, массив любого вида - и изменить это в любое время)
- перед сдачей проекта можно заменить m(фраза) на m(число) для еще большего повышения скорости (автоматически тем же парсером)
- так же можно заменить с m() на $m[] и когда надо обратно

Поэтому фиксированные варианты отдыхают...
 

su1d

Старожил PHPClubа
делаем:
Код:
xgettext --keyword=m *.php
и получаем поддержку gettext'a в DlMA'ином варианте, не меняя исходников =)
 

DiMA

php.spb.ru
Команда форума
Че, неужели я родил идею самого геттекста? :)
 

su1d

Старожил PHPClubа
оно всегда так и бывает =)
попробуй напустить xgettext на свои исходники, и сделай
PHP:
 function m($msg) { return _($msg); }
 

makRo

Guest
4(3) вариант более всего подходит, если в основном данные (на разных языках) вытаскиваются из БД
Например новости:
PHP:
..("SELECT id, title_".$langNom.", anons_".$langNom.",  text_".$langNom." , date_format(date,'%e.%m.%Y') as datef FROM News ORDER BY date DESC LIMIT ...
 

trent

Developer
Originally posted by makRo
4(3) вариант более всего подходит, если в основном данные (на разных языках) вытаскиваются из БД
Например новости:
PHP:
..("SELECT id, title_".$langNom.", anons_".$langNom.",  text_".$langNom." , date_format(date,'%e.%m.%Y') as datef FROM News ORDER BY date DESC LIMIT ...
а локали храни в отдельной таблице
LocaleId
LocaleName
--------------
SELECT id, title FROM News WHERE LocaleId=".$langNom." ORDER BY date DESC LIMIT ...

зачем в базе избыточность?
делай связку один к одному к локалям... по id

а при твоем варианте, чтобы добавить новый язык придется делать alter...
 

yrtimD

Guest
DiMA:
А четвертый вариант всё равно, так или иначе, связан с функцией,
которая бы искала фразу. Например, я собираюсь установить одну константу ЯЗЫК, загрузить содержимое файла ЯЗЫК.php:
PHP:
$lang[1] = 'фраза 1';
Далее:
PHP:
function m($t)
{
    /* здесь, наверно, ничего переключать не надо?
    все фразы уже в массиве $lang */
    global $lang;
    return isset($lang[$t]) ? $lang[$t] : "анноун";
}
И далее для класса шаблонов:
PHP:
$tpl->assign('VARNAME_1', m(1));
Регистрировать переменные можно автоматически,
назначив встречающимся в выбранном html-шаблоне {VARNAME_число} возвращаемое значение от m(число).
Это как-то похоже ту самую функцию m()?
 

makRo

Guest
Автор оригинала: trent

а локали храни в отдельной таблице
LocaleId
LocaleName
--------------
SELECT id, title FROM News WHERE LocaleId=".$langNom." ORDER BY date DESC LIMIT ...

зачем в базе избыточность?
делай связку один к одному к локалям... по id

а при твоем варианте, чтобы добавить новый язык придется делать alter...
Можешь поподробнее свой вариант описать ?
Например для N языков, поля {id, date, text}

P.S. При добавлении языка в любом случае ЧТО-ТО нужно будет делать, база не узнает же сама :)
 

kvn

programmer
Когда я делал мультиязыковый сайт,
то с БД было решено сделать так:
Все языкозависимые данные отделять в доп.таблицу,
т.е. если
table "client":
id,
date_birth,
passport_num,
еще какая-нить НЕ зависимая от языка фигня..

то table "client_lang":
name,
surname,
еще какая-нить ЗАВИСИМАЯ от языка фигня..
lang_id.

table "lang"
id, 1....N
lang, en...ru...(для ЧПУ)
long_name (English/Русский/Украинский) - для отображения на сайте.

И все прекрасно работает до сих пор.

Соотв. темплейты лежат в папках
/en/
/ru/
...

Удачи.
 

makRo

Guest
Автор оригинала: kvn
Когда я делал мультиязыковый сайт,
то с БД было решено сделать так:

...
Приведи примеры SQL запросов на вывод данных и на добавление
 

trent

Developer
Originally posted by makRo

Можешь поподробнее свой вариант описать ?
Например для N языков, поля {id, date, text}

P.S. При добавлении языка в любом случае ЧТО-ТО нужно будет делать, база не узнает же сама :)
Locale
--------
Locale_Id, Locale_Prefix, Locale_Name
--------
1, "ru_RU", "Русский"
2, "en_GB", "English"
3, "de_DE", "Germany"


News
-------
News_Id, News_Text, Locale_Id
-------
1, "лала", 1
2, "lala", 2
3, "Ru&#223;land, Ru&#223;land &#252;ber alles", 3
4, "лала", 1

вот и все

а сессии хранить id текущего языка
и потом делать select * from News where Locale_Id=$id
 

makRo

Guest
Автор оригинала: trent

Locale
--------
Locale_Id, Locale_Prefix, Locale_Name
--------
1, "ru_RU", "Русский"
2, "en_GB", "English"
3, "de_DE", "Germany"


News
-------
News_Id, News_Text, Locale_Id
-------
1, "лала", 1
2, "lala", 2
3, "Ru&#223;land, Ru&#223;land &#252;ber alles", 3
4, "лала", 1

вот и все

а сессии хранить id текущего языка
и потом делать select * from News where Locale_Id=$id
а если у новости есть ещё поля общие для всех языков, например date, show_text, style ... и т.п.
тогда эти данные будут дублироваться ? в таком случае, это и есть избыточность

Ситуация:
Например нужно отредактировать дату новости (или другое общее поле), и новость написана на 3 языках. Как узнать какие новости общие ? Очевидно у них должен быть общий ключ, для чего нужно вводить дополнительное поле или выбрать в качестве ключа дату. При варианте хранить всё в одной таблице у них уже есть общий id.
При варианте c несколькими полями работать с новостями сложнее, а значит дополнительные ошибки и затраты времени.

Вопрос: нужно ли так запариваться из-за того чтобы не делать лишний раз ALTER ?? притом что делать его придётся весьма не часто, а может и никогда ..
 

yrtimD

Guest
makRo:
Да всё нормально с базой. Сделай тогда три таблицы. Вышеуказанные Locale, News (без Locale_Id), и далее карту значений News_map (id, news_id, locale_id, date, topic_id, style_id, user_id, ...)

Это универсальный вариант для любого кол-ва языков, новостей, топиков, стилей, юзеров и т.п.
 
Сверху