Лучшая практика многоязычного сайта
Я боролся с этим вопросом уже несколько месяцев, но у меня не было ситуации, в которой мне нужно было бы изучить все возможные варианты раньше. Прямо сейчас я чувствую, что пришло время узнать о возможностях и создать свое личное предпочтение для использования в моих будущих проектах.
Позвольте мне сначала набросать ситуацию, которую я ищу
Я собираюсь обновить / перестроить систему управления контентом, которой я пользуюсь уже довольно давно. Тем не менее, я чувствую, что мультиязычность - большое улучшение этой системы. Раньше я не использовал никаких фреймворков, но я собираюсь использовать Laraval4 для предстоящего проекта. Laravel кажется лучшим выбором более чистого способа кодирования PHP. Sidenote: Laraval4 should be no factor in your answer
, Я ищу общие способы перевода, которые не зависят от платформы / фреймворка.
Что должно быть переведено
Поскольку система, которую я ищу, должна быть максимально удобной для пользователя, метод управления переводом должен быть внутри CMS. Не нужно запускать FTP-соединение для изменения файлов перевода или любых разбираемых html/php шаблонов.
Кроме того, я ищу самый простой способ перевода нескольких таблиц базы данных, возможно, без необходимости создания дополнительных таблиц.
Что я придумала сама
Как я уже искал, читал и пробовал сам. У меня есть несколько вариантов. Но я все еще не чувствую, что достиг наилучшего метода для того, что действительно ищу. Прямо сейчас, это то, что я придумал, но у этого метода также есть побочные эффекты.
- PHP Parsed Templates: система шаблонов должна быть проанализирована PHP. Таким образом, я могу вставить переведенные параметры в HTML без необходимости открывать шаблоны и изменять их. Кроме того, синтаксический анализ PHP дает мне возможность иметь 1 шаблон для всего сайта, а не иметь подпапку для каждого языка (что у меня было раньше). Методом достижения этой цели может быть Smarty, TemplatePower, Laravel's Blade или любой другой анализатор шаблонов. Как я уже сказал, это должно быть независимым от письменного решения.
- Управление базой данных: возможно, мне не нужно упоминать об этом снова. Но решение должно основываться на базе данных. CMS предназначена для объектно-ориентированного и MVC, поэтому мне нужно подумать о логической структуре данных для строк. Поскольку мои шаблоны будут структурированы: templates/Controller/View.php, возможно, эта структура будет иметь больше смысла:
Controller.View.parameter
, Таблица базы данных будет иметь эти поля длинной сvalue
поле. Внутри шаблонов мы могли бы использовать какой-то метод сортировки, такой какecho __('Controller.View.welcome', array('name', 'Joshua'))
и параметр содержитWelcome, :name
, Таким образом, результатWelcome, Joshua
, Это кажется хорошим способом сделать это, потому что такие параметры, как: name, легко понять редактору. - Низкая загрузка базы данных: Конечно, вышеуказанная система будет вызывать загрузку базы данных, если эти строки загружаются на ходу. Поэтому мне нужна система кеширования, которая повторно отображает языковые файлы, как только они редактируются / сохраняются в среде администрирования. Поскольку файлы генерируются, необходима также хорошая структура файловой системы. Я думаю, мы можем пойти с
languages/en_EN/Controller/View.php
или.ini, что вам больше подходит. Возможно,.ini даже в конце разбирается быстрее. Это должно содержать данные вformat parameter=value;
, Я думаю, что это лучший способ сделать это, так как каждый представленный вид может включать свой собственный языковой файл, если он существует. Затем параметры языка должны быть загружены в конкретное представление, а не в глобальную область видимости, чтобы параметры не перезаписывали друг друга. - Перевод таблицы базы данных: это то, что меня больше всего беспокоит. Я ищу способ создания переводов новостей / страниц / и т. Д. как можно быстрее. Наличие двух таблиц для каждого модуля (например,
News
а такжеNews_translations
) - вариант, но очень хочется много работать, чтобы получить хорошую систему. Одна из вещей, которые я придумал, основана наdata versioning
Система, которую я написал: есть одно имя таблицы базы данныхTranslations
эта таблица имеет уникальную комбинациюlanguage
,tablename
а такжеprimarykey
, Например: en_En / News / 1 (ссылается на английскую версию новости с идентификатором =1). Но у этого метода есть два огромных недостатка: во-первых, эта таблица имеет тенденцию получать довольно много времени с большим количеством данных в базе данных, и, во-вторых, использовать эту настройку для поиска в таблице было бы чертовски сложно. Например, поиск поискового SEO-элемента - это полнотекстовый поиск, который довольно глуп. Но с другой стороны: это быстрый способ очень быстро создавать переводимый контент в каждой таблице, но я не верю, что этот профессионал перевешивает доводы "против". - Работа с внешним интерфейсом. Кроме того, клиенту нужно подумать. Конечно, мы будем хранить доступные языки в базе данных и (де) активировать те, которые нам нужны. Таким образом, сценарий может создать раскрывающийся список для выбора языка, а серверная часть может автоматически решить, какие переводы можно выполнить с помощью CMS. Выбранный язык (например, en_EN) будет затем использоваться при получении языкового файла для представления или для получения правильного перевода для элемента контента на веб-сайте.
Итак, они есть. Мои идеи пока. Они даже не включают в себя параметры локализации для дат и т. Д., Но, поскольку мой сервер поддерживает PHP5.3.2+, лучшим вариантом является использование расширения intl, как описано здесь: http://devzone.zend.com/1500/internationalization-in-php-53/ - но это будет полезно на любом последующем стадионе разработки. На данный момент основной вопрос заключается в том, как использовать лучшие практики перевода контента на веб-сайте.
Помимо всего, что я здесь объяснил, у меня все еще есть еще одна вещь, которую я еще не решил, это выглядит как простой вопрос, но на самом деле это вызывает у меня головную боль:
Перевод URL? Должны ли мы сделать это или нет? и каким образом?
Так что.. если у меня есть этот URL: http://www.domain.com/about-us
и английский мой язык по умолчанию. Должен ли этот URL быть переведен в http://www.domain.com/over-ons
когда я выбираю нидерландский язык? Или мы должны пойти легким путем и просто изменить содержимое страницы, видимой на /about
, Последнее не кажется верным вариантом, потому что это приведет к созданию нескольких версий одного и того же URL-адреса, при этом индексация содержимого не удастся правильно.
Другой вариант использует http://www.domain.com/nl/about-us
вместо. Это создает как минимум уникальный URL для каждого контента. Также было бы проще перейти на другой язык, например http://www.domain.com/en/about-us
предоставленный URL легче понять как посетителям Google, так и пользователям. Используя эту опцию, что мы делаем с языками по умолчанию? Должен ли язык по умолчанию удалить язык, выбранный по умолчанию? Так что перенаправление http://www.domain.com/en/about-us
в http://www.domain.com/about-us
... На мой взгляд, это лучшее решение, потому что, когда CMS настроен только для одного языка, нет необходимости указывать этот язык в URL.
И третий вариант - это комбинация из обоих вариантов: использование "language-идентификации-less" -URL (http://www.domain.com/about-us
) для основного языка. И используйте URL с переведенным слагом SEO для подъязыков: http://www.domain.com/nl/over-ons
& http://www.domain.com/de/uber-uns
Надеюсь, мой вопрос ломит твои головы, они точно ломают мои! Это уже помогло мне решить вопрос здесь. Дали мне возможность пересмотреть методы, которые я использовал ранее, и идею, которая у меня есть для моей будущей CMS.
Я хотел бы поблагодарить вас за то, что вы нашли время, чтобы прочитать эту кучу текста!
// Edit #1
:
Я забыл упомянуть: функция __() является псевдонимом для перевода заданной строки. В этом методе, очевидно, должен быть какой-то резервный метод, в котором текст по умолчанию загружается, когда еще нет доступных переводов. Если перевод отсутствует, он должен быть либо вставлен, либо файл перевода должен быть восстановлен.
15 ответов
Предпосылка темы
В многоязычном сайте есть три различных аспекта:
- перевод интерфейса
- содержание
- URL-маршрутизация
Хотя все они связаны между собой по-разному, с точки зрения CMS они управляются с использованием различных элементов пользовательского интерфейса и хранятся по-разному. Вы, кажется, уверены в своей реализации и понимании первых двух. Вопрос был о последнем аспекте - "Перевод URL? Должны ли мы делать это или нет? И каким образом?"
Из чего можно сделать URL?
Очень важная вещь - не увлекайтесь IDN. Вместо этого отдавайте предпочтение транслитерации (также: транскрипция и латинизация). Хотя на первый взгляд IDN кажется приемлемым вариантом для международных URL-адресов, на самом деле он не работает так, как рекламируется по двум причинам:
- некоторые браузеры превращают символы без ASCII, например
'ч'
или же'ž'
в'%D1%87'
а также'%C5%BE'
- если у пользователя есть собственные темы, шрифт темы, скорее всего, не будет иметь символов для этих букв
Я на самом деле пытался использовать IDN несколько лет назад в проекте на основе Yii (ужасная структура, IMHO). Я столкнулся с обеими вышеупомянутыми проблемами прежде, чем очистить это решение. Также я подозреваю, что это может быть вектор атаки.
Доступные варианты... как я их вижу.
По сути, у вас есть два варианта, которые можно абстрагировать как:
http://site.tld/[:query]
: где[:query]
определяет выбор языка и контентаhttp://site.tld/[:language]/[:query]
: где[:language]
часть URL определяет выбор языка и[:query]
используется только для идентификации контента
Запрос Α и Ω ..
Допустим, вы выбираете http://site.tld/[:query]
,
В этом случае у вас есть один основной источник языка: содержание [:query]
сегмент; и два дополнительных источника:
- значение
$_COOKIE['lang']
для этого конкретного браузера - список языков в HTTP-заголовке Accept-Language (1), (2)
Во-первых, вам нужно сопоставить запрос с одним из определенных шаблонов маршрутизации (если вы выбрали Laravel, тогда читайте здесь). После успешного сопоставления с шаблоном вам необходимо найти язык.
Вам придется пройти через все сегменты шаблона. Найдите потенциальные переводы для всех этих сегментов и определите, какой язык использовался. Два дополнительных источника (cookie и заголовок) будут использоваться для разрешения конфликтов маршрутизации, когда (не "если") они возникают.
Взять, к примеру: http://site.tld/blog/novinka
,
Это транслитерация "блог, новинка"
, что на английском означает примерно "blog", "latest"
,
Как вы уже можете заметить, на русском языке "блог" будет транслитерирован как "блог". Что означает, что для первой части [:query]
вы (в лучшем случае) в конечном итоге ['en', 'ru']
список возможных языков. Затем вы берете следующий сегмент - "новинка". Это может иметь только один язык в списке возможностей: ['ru']
,
Когда в списке есть один элемент, вы успешно нашли язык.
Но если у вас 2 (например, русский и украинский) или больше возможностей... или 0, в зависимости от обстоятельств. Вы должны будете использовать cookie и / или заголовок, чтобы найти правильный вариант.
А если ничего не помогает, вы выбираете язык сайта по умолчанию.
Язык как параметр
Альтернативой является использование URL, который может быть определен как http://site.tld/[:language]/[:query]
, В этом случае при переводе запроса вам не нужно угадывать язык, потому что в этот момент вы уже знаете, какой использовать.
Существует также вторичный источник языка: значение cookie. Но здесь нет смысла возиться с заголовком Accept-Language, потому что вы не имеете дело с неизвестным количеством возможных языков в случае "холодного старта" (когда пользователь впервые открывает сайт с помощью пользовательского запроса).
Вместо этого у вас есть 3 простых, приоритетных варианта:
- если
[:language]
сегмент установлен, используйте его - если
$_COOKIE['lang']
установлен, используйте его - использовать язык по умолчанию
Если у вас есть язык, вы просто пытаетесь перевести запрос, а если перевод не удается, используйте "значение по умолчанию" для этого конкретного сегмента (на основе результатов маршрутизации).
Нет здесь третьего варианта?
Да, технически вы можете комбинировать оба подхода, но это усложнит процесс и позволит разместить только людей, которые хотят вручную изменить URL-адрес http://site.tld/en/news
в http://site.tld/de/news
и ожидаем, что страница новостей изменится на немецкий.
Но даже этот случай можно было бы смягчить, используя значение cookie (которое будет содержать информацию о предыдущем выборе языка), чтобы реализовать его с меньшей магией и надеждой.
Какой подход использовать?
Как вы уже догадались, я бы порекомендовал http://site.tld/[:language]/[:query]
как более разумный вариант.
Также в реальной ситуации слова у вас будет 3-я основная часть в URL: "заголовок". Как в названии товара в интернет-магазине, так и в заголовке статьи на новостном сайте.
Пример: http://site.tld/en/news/article/121415/EU-as-global-reserve-currency
В этом случае '/news/article/121415'
будет запрос, а 'EU-as-global-reserve-currency'
это название. Чисто для целей SEO.
Это можно сделать в Ларавеле?
Вроде, но не по умолчанию.
Я не слишком знаком с этим, но из того, что я видел, Laravel использует простой механизм маршрутизации на основе паттернов. Для реализации многоязычных URL-адресов вам, вероятно, придется расширить базовый класс (ы), потому что многоязычная маршрутизация требует доступа к различным формам хранения (базе данных, кешу и / или файлам конфигурации).
Это маршрутизируется. Что теперь?
В результате вы получите две ценные информации: текущий язык и переведенные сегменты запроса. Эти значения затем могут быть использованы для отправки в класс (ы), который будет производить результат.
В основном, следующий URL: http://site.tld/ru/blog/novinka
(или версия без '/ru'
) превращается в нечто вроде
$parameters = [
'language' => 'ru',
'classname' => 'blog',
'method' => 'latest',
];
Который вы просто используете для отправки:
$instance = new {$parameter['classname']};
$instance->{'get'.$parameters['method']}( $parameters );
.. или какой-то его вариант, в зависимости от конкретной реализации.
Реализация i18n без снижения производительности с использованием препроцессора, как было предложено Томасом Бли
На работе мы недавно реализовали i18n на нескольких наших объектах, и одной из тех вещей, с которыми мы продолжали бороться, было снижение производительности при работе с переводом на лету, а затем я обнаружил этот замечательный пост в блоге Томаса Блея. что вдохновило то, как мы используем i18n для обработки больших объемов трафика с минимальными проблемами с производительностью.
Вместо вызова функций для каждой операции перевода, которая, как мы знаем в PHP, дорогая, мы определяем наши базовые файлы с заполнителями, а затем используем препроцессор для кэширования этих файлов (мы сохраняем время модификации файла, чтобы убедиться, что мы обслуживаем новейший контент во все времена).
Теги перевода
Томас использует {tr}
а также {/tr}
теги, чтобы определить, где перевод начинается и заканчивается. Из-за того, что мы используем TWIG, мы не хотим использовать {
чтобы избежать путаницы, поэтому мы используем [%tr%]
а также [%/tr%]
вместо. В основном это выглядит так:
`return [%tr%]formatted_value[%/tr%];`
Обратите внимание, что Томас предлагает использовать базовый английский в файле. Мы не делаем этого, потому что мы не хотим изменять все файлы перевода, если мы изменим значение на английском языке.
Файлы INI
Затем мы создаем файл INI для каждого языка в формате placeholder = translated
:
// lang/fr.ini
formatted_value = number_format($value * Model_Exchange::getEurRate(), 2, ',', ' ') . '€'
// lang/en_gb.ini
formatted_value = '£' . number_format($value * Model_Exchange::getStgRate())
// lang/en_us.ini
formatted_value = '$' . number_format($value)
Было бы тривиально разрешить пользователю изменять их внутри CMS, просто получить пары ключей preg_split
на \n
или же =
и сделать CMS способной записывать в файлы INI.
Компонент предварительной обработки
В сущности, Томас предлагает использовать функцию "компиляции" (хотя, по правде говоря, это препроцессор), подобную этой, для получения ваших файлов перевода и создания статических файлов PHP на диске. Таким образом, мы по существу кэшируем наши переведенные файлы вместо вызова функции перевода для каждой строки в файле:
// This function was written by Thomas Bley, not by me
function translate($file) {
$cache_file = 'cache/'.LANG.'_'.basename($file).'_'.filemtime($file).'.php';
// (re)build translation?
if (!file_exists($cache_file)) {
$lang_file = 'lang/'.LANG.'.ini';
$lang_file_php = 'cache/'.LANG.'_'.filemtime($lang_file).'.php';
// convert .ini file into .php file
if (!file_exists($lang_file_php)) {
file_put_contents($lang_file_php, '<?php $strings='.
var_export(parse_ini_file($lang_file), true).';', LOCK_EX);
}
// translate .php into localized .php file
$tr = function($match) use (&$lang_file_php) {
static $strings = null;
if ($strings===null) require($lang_file_php);
return isset($strings[ $match[1] ]) ? $strings[ $match[1] ] : $match[1];
};
// replace all {t}abc{/t} by tr()
file_put_contents($cache_file, preg_replace_callback(
'/\[%tr%\](.*?)\[%\/tr%\]/', $tr, file_get_contents($file)), LOCK_EX);
}
return $cache_file;
}
Примечание: я не проверял, что регулярное выражение работает, я не копировал его с сервера нашей компании, но вы можете увидеть, как работает операция.
Как это назвать
Опять же, этот пример от Томаса Блея, а не от меня:
// instead of
require("core/example.php");
echo (new example())->now();
// we write
define('LANG', 'en_us');
require(translate('core/example.php'));
echo (new example())->now();
Мы сохраняем язык в файле cookie (или в переменной сеанса, если мы не можем получить файл cookie), а затем извлекаем его при каждом запросе. Вы можете объединить это с дополнительным $_GET
параметр для переопределения языка, но я не предлагаю субдомен на язык или страницу на язык, потому что будет сложнее увидеть, какие страницы популярны, и уменьшит ценность входящих ссылок по мере их появления более редко распространяется.
Зачем использовать этот метод?
Нам нравится этот метод предварительной обработки по трем причинам:
- Огромный выигрыш в производительности, если не вызывать целую кучу функций для контента, который редко меняется (с этой системой 100 тыс. Посетителей на французском языке все равно в конечном итоге выполнят замену перевода только один раз).
- Он не добавляет никакой нагрузки на нашу базу данных, поскольку использует простые плоские файлы и является решением на чистом PHP.
- Возможность использовать выражения PHP в наших переводах.
Получение переведенного содержимого базы данных
Мы просто добавляем столбец для контента в нашей базе данных под названием language
Затем мы используем метод доступа для LANG
константа, которую мы определили ранее, поэтому наши вызовы SQL (к сожалению, с использованием ZF1) выглядят так:
$query = select()->from($this->_name)
->where('language = ?', User::getLang())
->where('id = ?', $articleId)
->limit(1);
Наши статьи имеют сложный первичный ключ над id
а также language
так статья 54
может существовать на всех языках. наш LANG
по умолчанию en_US
если не указано
URL Slug Translation
Я бы объединил две вещи здесь, одна из них - функция в вашем загрузчике, которая принимает $_GET
параметр для языка и переопределяет переменную cookie, а другой - маршрутизацию, которая принимает несколько слагов. Тогда вы можете сделать что-то вроде этого в своей маршрутизации:
"/wilkommen" => "/welcome/lang/de"
... etc ...
Они могут быть сохранены в виде плоского файла, который может быть легко записан с вашей панели администратора. JSON или XML могут обеспечить хорошую структуру для их поддержки.
Примечания относительно нескольких других вариантов
Перевод на лету на основе PHP
Я не вижу, чтобы они предлагали какое-либо преимущество перед предварительно обработанными переводами.
Front-end основанные переводы
Я давно нашел это интересным, но есть несколько предостережений. Например, вы должны предоставить пользователю полный список фраз на вашем веб-сайте, которые вы планируете переводить, это может быть проблематично, если есть области сайта, которые вы скрываете или не разрешили им доступ к ним.
Вы также должны были бы предположить, что все ваши пользователи желают и могут использовать Javascript на вашем сайте, но по моей статистике, около 2,5% наших пользователей работают без него (или используют Noscript, чтобы заблокировать использование наших сайтов),
Переводы на основе базы данных
Скорости соединения с базой данных в PHP- это не то, что нужно, и это увеличивает и без того высокие затраты на вызов функции для каждой фразы для перевода. Проблемы производительности и масштабируемости кажутся непреодолимыми при таком подходе.
Я предлагаю вам не изобретать колесо и использовать список сокращений языков gettext и ISO. Вы видели, как i18n/l10n реализован в популярных CMS или фреймворках?
Используя gettext, вы получите мощный инструмент, в котором многие случаи уже реализованы в виде множественных чисел. В английском у вас есть только 2 варианта: единственное и множественное число. Но на русском языке, например, есть 3 формы, и это не так просто, как на английском.
Также многие переводчики уже имеют опыт работы с gettext.
Посмотрите на CakePHP или Drupal. Оба многоязычных включены. CakePHP как пример локализации интерфейса и Drupal как пример перевода контента.
Для l10n использование базы данных совсем не так. По запросам будет много тонн. Стандартный подход заключается в получении всех данных l10n в памяти на ранней стадии (или во время первого вызова функции i10n, если вы предпочитаете ленивую загрузку). Это может быть чтение из.po файла или из БД всех данных одновременно. А потом просто читать запрашиваемые строки из массива.
Если вам нужно внедрить онлайн-инструмент для перевода интерфейса, вы можете хранить все эти данные в БД, но все же сохранить все данные в файл для работы с ним. Чтобы уменьшить объем данных в памяти, вы можете разбить все ваши переведенные сообщения / строки на группы и затем загрузить только те группы, которые вам нужны, если это будет возможно.
Так что вы совершенно правы в своем #3. За одним исключением: обычно это один большой файл, а не файл для каждого контроллера. Потому что для производительности лучше всего открыть один файл. Вы, вероятно, знаете, что некоторые высоконагруженные веб-приложения компилируют весь код PHP в один файл, чтобы избежать файловых операций при вызове include/require.
О URL. Google косвенно предлагает использовать перевод:
чтобы четко указать французский контент: http://example.ca/fr/v%C3%A9lo-de-montagne.html
Также я думаю, что вам нужно перенаправить пользователя на префикс языка по умолчанию, например, http://examlpe.com/about-us перенаправит на http://examlpe.com/en/about-us Но если ваш сайт использует только один язык, то вы вообще не нужны префиксы.
Проверьте: http://www.audiomicro.com/trailer-hit-impact-psychodrama-sound-effects-836925 http://nl.audiomicro.com/aanhangwagen-hit-effect-psychodrama-geluidseffecten-836925 http://de.audiomicro.com/anhanger-hit-auswirkungen-psychodrama-sound-effekte-836925
Перевод контента является более сложной задачей. Я думаю, что будут различия между разными типами контента, например, статьями, пунктами меню и т. Д. Но в #4 вы на правильном пути. Загляните в Drupal, чтобы иметь больше идей. У него достаточно понятная схема БД и достаточно хороший интерфейс для перевода. Как вы создаете статью и выбираете язык для нее. А потом вы можете перевести его на другие языки.
Я думаю, что это не проблема с URL-слизнями. Вы можете просто создать отдельную таблицу для слизней, и это будет правильным решением. Также, используя правильные индексы, нет проблем с запросом таблицы даже с огромным количеством данных. И это был не полнотекстовый поиск, а совпадение строк, если для slug будет использоваться тип данных varchar, и у вас также может быть индекс для этого поля.
PS Извините, но мой английский далеко не идеален.
Это зависит от того, сколько контента на вашем сайте. Сначала я использовал базу данных, как и все остальные люди здесь, но это может занять много времени для сценария всех операций базы данных. Я не говорю, что это идеальный метод, особенно если у вас много текста, но если вы хотите сделать это быстро без использования базы данных, этот метод может сработать, однако вы не можете позволить пользователям вводить данные который будет использоваться в качестве файлов перевода. Но если вы добавите переводы самостоятельно, это сработает:
Допустим, у вас есть этот текст:
Welcome!
Вы можете ввести это в базу данных с переводами, но вы также можете сделать это:
$welcome = array(
"English"=>"Welcome!",
"German"=>"Willkommen!",
"French"=>"Bienvenue!",
"Turkish"=>"Hoşgeldiniz!",
"Russian"=>"Добро пожаловать!",
"Dutch"=>"Welkom!",
"Swedish"=>"Välkommen!",
"Basque"=>"Ongietorri!",
"Spanish"=>"Bienvenito!"
"Welsh"=>"Croeso!");
Теперь, если ваш сайт использует куки, у вас есть это, например:
$_COOKIE['language'];
Чтобы упростить его, давайте превратим его в код, который можно легко использовать:
$language=$_COOKIE['language'];
Если ваш язык cookie - валлийский, и у вас есть этот код:
echo $welcome[$language];
Результатом этого будет:
Croeso!
Если вам нужно добавить много переводов для вашего сайта, а база данных слишком трудоемкая, использование массива может стать идеальным решением.
Я предлагаю вам не зависеть от базы данных для перевода, это может быть очень сложным делом и может быть серьезной проблемой в случае кодирования данных.
Я столкнулся с подобной проблемой некоторое время назад и написал следующий класс, чтобы решить мою проблему
Объект: Locale\Locale
<?php
namespace Locale;
class Locale{
// Following array stolen from Zend Framework
public $country_to_locale = array(
'AD' => 'ca_AD',
'AE' => 'ar_AE',
'AF' => 'fa_AF',
'AG' => 'en_AG',
'AI' => 'en_AI',
'AL' => 'sq_AL',
'AM' => 'hy_AM',
'AN' => 'pap_AN',
'AO' => 'pt_AO',
'AQ' => 'und_AQ',
'AR' => 'es_AR',
'AS' => 'sm_AS',
'AT' => 'de_AT',
'AU' => 'en_AU',
'AW' => 'nl_AW',
'AX' => 'sv_AX',
'AZ' => 'az_Latn_AZ',
'BA' => 'bs_BA',
'BB' => 'en_BB',
'BD' => 'bn_BD',
'BE' => 'nl_BE',
'BF' => 'mos_BF',
'BG' => 'bg_BG',
'BH' => 'ar_BH',
'BI' => 'rn_BI',
'BJ' => 'fr_BJ',
'BL' => 'fr_BL',
'BM' => 'en_BM',
'BN' => 'ms_BN',
'BO' => 'es_BO',
'BR' => 'pt_BR',
'BS' => 'en_BS',
'BT' => 'dz_BT',
'BV' => 'und_BV',
'BW' => 'en_BW',
'BY' => 'be_BY',
'BZ' => 'en_BZ',
'CA' => 'en_CA',
'CC' => 'ms_CC',
'CD' => 'sw_CD',
'CF' => 'fr_CF',
'CG' => 'fr_CG',
'CH' => 'de_CH',
'CI' => 'fr_CI',
'CK' => 'en_CK',
'CL' => 'es_CL',
'CM' => 'fr_CM',
'CN' => 'zh_Hans_CN',
'CO' => 'es_CO',
'CR' => 'es_CR',
'CU' => 'es_CU',
'CV' => 'kea_CV',
'CX' => 'en_CX',
'CY' => 'el_CY',
'CZ' => 'cs_CZ',
'DE' => 'de_DE',
'DJ' => 'aa_DJ',
'DK' => 'da_DK',
'DM' => 'en_DM',
'DO' => 'es_DO',
'DZ' => 'ar_DZ',
'EC' => 'es_EC',
'EE' => 'et_EE',
'EG' => 'ar_EG',
'EH' => 'ar_EH',
'ER' => 'ti_ER',
'ES' => 'es_ES',
'ET' => 'en_ET',
'FI' => 'fi_FI',
'FJ' => 'hi_FJ',
'FK' => 'en_FK',
'FM' => 'chk_FM',
'FO' => 'fo_FO',
'FR' => 'fr_FR',
'GA' => 'fr_GA',
'GB' => 'en_GB',
'GD' => 'en_GD',
'GE' => 'ka_GE',
'GF' => 'fr_GF',
'GG' => 'en_GG',
'GH' => 'ak_GH',
'GI' => 'en_GI',
'GL' => 'iu_GL',
'GM' => 'en_GM',
'GN' => 'fr_GN',
'GP' => 'fr_GP',
'GQ' => 'fan_GQ',
'GR' => 'el_GR',
'GS' => 'und_GS',
'GT' => 'es_GT',
'GU' => 'en_GU',
'GW' => 'pt_GW',
'GY' => 'en_GY',
'HK' => 'zh_Hant_HK',
'HM' => 'und_HM',
'HN' => 'es_HN',
'HR' => 'hr_HR',
'HT' => 'ht_HT',
'HU' => 'hu_HU',
'ID' => 'id_ID',
'IE' => 'en_IE',
'IL' => 'he_IL',
'IM' => 'en_IM',
'IN' => 'hi_IN',
'IO' => 'und_IO',
'IQ' => 'ar_IQ',
'IR' => 'fa_IR',
'IS' => 'is_IS',
'IT' => 'it_IT',
'JE' => 'en_JE',
'JM' => 'en_JM',
'JO' => 'ar_JO',
'JP' => 'ja_JP',
'KE' => 'en_KE',
'KG' => 'ky_Cyrl_KG',
'KH' => 'km_KH',
'KI' => 'en_KI',
'KM' => 'ar_KM',
'KN' => 'en_KN',
'KP' => 'ko_KP',
'KR' => 'ko_KR',
'KW' => 'ar_KW',
'KY' => 'en_KY',
'KZ' => 'ru_KZ',
'LA' => 'lo_LA',
'LB' => 'ar_LB',
'LC' => 'en_LC',
'LI' => 'de_LI',
'LK' => 'si_LK',
'LR' => 'en_LR',
'LS' => 'st_LS',
'LT' => 'lt_LT',
'LU' => 'fr_LU',
'LV' => 'lv_LV',
'LY' => 'ar_LY',
'MA' => 'ar_MA',
'MC' => 'fr_MC',
'MD' => 'ro_MD',
'ME' => 'sr_Latn_ME',
'MF' => 'fr_MF',
'MG' => 'mg_MG',
'MH' => 'mh_MH',
'MK' => 'mk_MK',
'ML' => 'bm_ML',
'MM' => 'my_MM',
'MN' => 'mn_Cyrl_MN',
'MO' => 'zh_Hant_MO',
'MP' => 'en_MP',
'MQ' => 'fr_MQ',
'MR' => 'ar_MR',
'MS' => 'en_MS',
'MT' => 'mt_MT',
'MU' => 'mfe_MU',
'MV' => 'dv_MV',
'MW' => 'ny_MW',
'MX' => 'es_MX',
'MY' => 'ms_MY',
'MZ' => 'pt_MZ',
'NA' => 'kj_NA',
'NC' => 'fr_NC',
'NE' => 'ha_Latn_NE',
'NF' => 'en_NF',
'NG' => 'en_NG',
'NI' => 'es_NI',
'NL' => 'nl_NL',
'NO' => 'nb_NO',
'NP' => 'ne_NP',
'NR' => 'en_NR',
'NU' => 'niu_NU',
'NZ' => 'en_NZ',
'OM' => 'ar_OM',
'PA' => 'es_PA',
'PE' => 'es_PE',
'PF' => 'fr_PF',
'PG' => 'tpi_PG',
'PH' => 'fil_PH',
'PK' => 'ur_PK',
'PL' => 'pl_PL',
'PM' => 'fr_PM',
'PN' => 'en_PN',
'PR' => 'es_PR',
'PS' => 'ar_PS',
'PT' => 'pt_PT',
'PW' => 'pau_PW',
'PY' => 'gn_PY',
'QA' => 'ar_QA',
'RE' => 'fr_RE',
'RO' => 'ro_RO',
'RS' => 'sr_Cyrl_RS',
'RU' => 'ru_RU',
'RW' => 'rw_RW',
'SA' => 'ar_SA',
'SB' => 'en_SB',
'SC' => 'crs_SC',
'SD' => 'ar_SD',
'SE' => 'sv_SE',
'SG' => 'en_SG',
'SH' => 'en_SH',
'SI' => 'sl_SI',
'SJ' => 'nb_SJ',
'SK' => 'sk_SK',
'SL' => 'kri_SL',
'SM' => 'it_SM',
'SN' => 'fr_SN',
'SO' => 'sw_SO',
'SR' => 'srn_SR',
'ST' => 'pt_ST',
'SV' => 'es_SV',
'SY' => 'ar_SY',
'SZ' => 'en_SZ',
'TC' => 'en_TC',
'TD' => 'fr_TD',
'TF' => 'und_TF',
'TG' => 'fr_TG',
'TH' => 'th_TH',
'TJ' => 'tg_Cyrl_TJ',
'TK' => 'tkl_TK',
'TL' => 'pt_TL',
'TM' => 'tk_TM',
'TN' => 'ar_TN',
'TO' => 'to_TO',
'TR' => 'tr_TR',
'TT' => 'en_TT',
'TV' => 'tvl_TV',
'TW' => 'zh_Hant_TW',
'TZ' => 'sw_TZ',
'UA' => 'uk_UA',
'UG' => 'sw_UG',
'UM' => 'en_UM',
'US' => 'en_US',
'UY' => 'es_UY',
'UZ' => 'uz_Cyrl_UZ',
'VA' => 'it_VA',
'VC' => 'en_VC',
'VE' => 'es_VE',
'VG' => 'en_VG',
'VI' => 'en_VI',
'VN' => 'vn_VN',
'VU' => 'bi_VU',
'WF' => 'wls_WF',
'WS' => 'sm_WS',
'YE' => 'ar_YE',
'YT' => 'swb_YT',
'ZA' => 'en_ZA',
'ZM' => 'en_ZM',
'ZW' => 'sn_ZW'
);
/**
* Store the transaltion for specific languages
*
* @var array
*/
protected $translation = array();
/**
* Current locale
*
* @var string
*/
protected $locale;
/**
* Default locale
*
* @var string
*/
protected $default_locale;
/**
*
* @var string
*/
protected $locale_dir;
/**
* Construct.
*
*
* @param string $locale_dir
*/
public function __construct($locale_dir)
{
$this->locale_dir = $locale_dir;
}
/**
* Set the user define localte
*
* @param string $locale
*/
public function setLocale($locale = null)
{
$this->locale = $locale;
return $this;
}
/**
* Get the user define locale
*
* @return string
*/
public function getLocale()
{
return $this->locale;
}
/**
* Get the Default locale
*
* @return string
*/
public function getDefaultLocale()
{
return $this->default_locale;
}
/**
* Set the default locale
*
* @param string $locale
*/
public function setDefaultLocale($locale)
{
$this->default_locale = $locale;
return $this;
}
/**
* Determine if transltion exist or translation key exist
*
* @param string $locale
* @param string $key
* @return boolean
*/
public function hasTranslation($locale, $key = null)
{
if (null == $key && isset($this->translation[$locale])) {
return true;
} elseif (isset($this->translation[$locale][$key])) {
return true;
}
return false;
}
/**
* Get the transltion for required locale or transtion for key
*
* @param string $locale
* @param string $key
* @return array
*/
public function getTranslation($locale, $key = null)
{
if (null == $key && $this->hasTranslation($locale)) {
return $this->translation[$locale];
} elseif ($this->hasTranslation($locale, $key)) {
return $this->translation[$locale][$key];
}
return array();
}
/**
* Set the transtion for required locale
*
* @param string $locale
* Language code
* @param string $trans
* translations array
*/
public function setTranslation($locale, $trans = array())
{
$this->translation[$locale] = $trans;
}
/**
* Remove transltions for required locale
*
* @param string $locale
*/
public function removeTranslation($locale = null)
{
if (null === $locale) {
unset($this->translation);
} else {
unset($this->translation[$locale]);
}
}
/**
* Initialize locale
*
* @param string $locale
*/
public function init($locale = null, $default_locale = null)
{
// check if previously set locale exist or not
$this->init_locale();
if ($this->locale != null) {
return;
}
if ($locale == null || (! preg_match('#^[a-z]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[a-z]+_[a-zA-Z]+_[a-zA-Z_]+$#', $locale))) {
$this->detectLocale();
} else {
$this->locale = $locale;
}
$this->init_locale();
}
/**
* Attempt to autodetect locale
*
* @return void
*/
private function detectLocale()
{
$locale = false;
// GeoIP
if (function_exists('geoip_country_code_by_name') && isset($_SERVER['REMOTE_ADDR'])) {
$country = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);
if ($country) {
$locale = isset($this->country_to_locale[$country]) ? $this->country_to_locale[$country] : false;
}
}
// Try detecting locale from browser headers
if (! $locale) {
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
foreach ($languages as $lang) {
$lang = str_replace('-', '_', trim($lang));
if (strpos($lang, '_') === false) {
if (isset($this->country_to_locale[strtoupper($lang)])) {
$locale = $this->country_to_locale[strtoupper($lang)];
}
} else {
$lang = explode('_', $lang);
if (count($lang) == 3) {
// language_Encoding_COUNTRY
$this->locale = strtolower($lang[0]) . ucfirst($lang[1]) . strtoupper($lang[2]);
} else {
// language_COUNTRY
$this->locale = strtolower($lang[0]) . strtoupper($lang[1]);
}
return;
}
}
}
}
// Resort to default locale specified in config file
if (! $locale) {
$this->locale = $this->default_locale;
}
}
/**
* Check if config for selected locale exists
*
* @return void
*/
private function init_locale()
{
if (! file_exists(sprintf('%s/%s.php', $this->locale_dir, $this->locale))) {
$this->locale = $this->default_locale;
}
}
/**
* Load a Transtion into array
*
* @return void
*/
private function loadTranslation($locale = null, $force = false)
{
if ($locale == null)
$locale = $this->locale;
if (! $this->hasTranslation($locale)) {
$this->setTranslation($locale, include (sprintf('%s/%s.php', $this->locale_dir, $locale)));
}
}
/**
* Translate a key
*
* @param
* string Key to be translated
* @param
* string optional arguments
* @return string
*/
public function translate($key)
{
$this->init();
$this->loadTranslation($this->locale);
if (! $this->hasTranslation($this->locale, $key)) {
if ($this->locale !== $this->default_locale) {
$this->loadTranslation($this->default_locale);
if ($this->hasTranslation($this->default_locale, $key)) {
$translation = $this->getTranslation($this->default_locale, $key);
} else {
// return key as it is or log error here
return $key;
}
} else {
return $key;
}
} else {
$translation = $this->getTranslation($this->locale, $key);
}
// Replace arguments
if (false !== strpos($translation, '{a:')) {
$replace = array();
$args = func_get_args();
for ($i = 1, $max = count($args); $i < $max; $i ++) {
$replace['{a:' . $i . '}'] = $args[$i];
}
// interpolate replacement values into the messsage then return
return strtr($translation, $replace);
}
return $translation;
}
}
использование
<?php
## /locale/en.php
return array(
'name' => 'Hello {a:1}'
'name_full' => 'Hello {a:1} {a:2}'
);
$locale = new Locale(__DIR__ . '/locale');
$locale->setLocale('en');// load en.php from locale dir
//want to work with auto detection comment $locale->setLocale('en');
echo $locale->translate('name', 'Foo');
echo $locale->translate('name', 'Foo', 'Bar');
Как это устроено
{a:1}
заменяется 1-м аргументом, переданным методу Locale::translate('key_name','arg1')
{a:2}
заменяется вторым аргументом, переданным методу Locale::translate('key_name','arg1','arg2')
Как работает обнаружение
- По умолчанию, если
geoip
будет установлен код страныgeoip_country_code_by_name
и если geoip не установлен, запасной вариант дляHTTP_ACCEPT_LANGUAGE
заголовок
Просто дополнительный ответ: Абсолютно используйте переведенные URL с идентификатором языка перед ними: http://www.domain.com/nl/over-ons
Гибридные решения, как правило, усложняются, поэтому я просто придерживаюсь этого. Зачем? Потому что URL имеет важное значение для SEO.
О переводе БД: Количество языков более или менее фиксировано? Или скорее непредсказуемый и динамичный? Если это будет исправлено, я просто добавлю новые столбцы, в противном случае использовать несколько таблиц.
Но, в общем, почему бы не использовать Drupal? Я знаю, что каждый хочет создать свою собственную CMS, потому что она быстрее, экономичнее и т. Д. И т. Д. Но это просто плохая идея!
Я не собираюсь пытаться уточнить ответы, которые уже даны. Вместо этого я расскажу вам о том, как мой собственный PHP-фреймворк OOP обрабатывает переводы.
Внутренне, мой фреймворк использует такие коды, как en, fr, es, cn и так далее. Массив содержит языки, поддерживаемые веб-сайтом: array('en','fr','es','cn') Код языка передается через $_GET (lang=fr), и если он не передан или недействителен, он устанавливается на первый язык в массиве. Таким образом, в любой момент во время выполнения программы и с самого начала, текущий язык известен.
Полезно понять, какой контент нужно переводить в типичном приложении:
1) сообщения об ошибках от классов (или процедурный код) 2) сообщения об ошибках от классов (или процедурный код) 3) содержимое страницы (обычно хранится в базе данных) 4) строки всего сайта (например, имя веб-сайта) 5) script- конкретные строки
Первый тип прост для понимания. По сути, речь идет о сообщениях типа "не удалось подключиться к базе данных...". Эти сообщения необходимо загружать только при возникновении ошибки. Мой класс менеджера получает вызов от других классов и, используя информацию, передаваемую в качестве параметров, просто переходит в соответствующую папку класса и получает файл ошибки.
Второй тип сообщений об ошибках больше похож на сообщения, которые вы получаете, когда проверка формы прошла неправильно. ("Вы не можете оставить... пустым" или "пожалуйста, выберите пароль длиной более 5 символов"). Строки должны быть загружены до запуска класса. Я знаю, что
Для фактического содержимого страницы я использую одну таблицу на язык, каждая таблица имеет префикс кода для языка. Таким образом, en_content - это таблица с содержанием на английском языке, es_content - для Испании, cn_content - для Китая, а fr_content - для французского.
Четвертый вид строки актуален на всем сайте. Это загружается через файл конфигурации, названный с использованием кода для языка, то есть en_lang.php, es_lang.php и так далее. В глобальный языковой файл вам нужно будет загрузить переведенные языки, такие как массив ("английский", "китайский", "испанский", "французский"), в глобальный файл и массив "Английский" ("Anglais", "Chinois", " Espagnol ','Francais ') во французском файле. Поэтому, когда вы заполняете раскрывающийся список для выбора языка, он отображается на правильном языке;)
Наконец, у вас есть специфичные для скрипта строки. Поэтому, если вы пишете приложение для приготовления пищи, это может быть "Ваша духовка была недостаточно горячей".
В моем цикле приложений сначала загружается глобальный языковой файл. Там вы найдете не только глобальные строки (например, "Jack's Website"), но и настройки для некоторых классов. В основном все, что зависит от языка или культуры. Некоторые из этих строк включают маски для дат (MMDDYYYY или DDMMYYYY) или языковые коды ISO. В основной языковой файл я включаю строки для отдельных классов, потому что их так мало.
Второй и последний языковой файл, который читается с диска, является языковым файлом сценария. lang_en_home_welcome.php - языковой файл для сценария home / welcome. Сценарий определяется режимом (дом) и действием (приветствие). У каждого скрипта есть своя папка с файлами настроек и языков.
Сценарий извлекает содержимое из базы данных, называя таблицу содержимого, как описано выше.
Если что-то идет не так, менеджер знает, где взять файл ошибок, зависящий от языка. Этот файл загружается только в случае ошибки.
Таким образом, вывод очевиден. Подумайте о проблемах перевода, прежде чем приступить к разработке приложения или среды. Вам также нужен рабочий процесс разработки, который включает переводы. С моей структурой я разрабатываю весь сайт на английском языке, а затем переводю все соответствующие файлы.
Просто краткое последнее слово о том, как реализованы строки перевода. В моем фреймворке есть один глобальный объект $ manager, который запускает сервисы, доступные для любого другого сервиса. Так, например, служба форм получает службу html и использует ее для написания html. Одной из служб в моей системе является услуга переводчика. $translationator->set($service,$code,$string) устанавливает строку для текущего языка. Языковой файл представляет собой список таких утверждений. $translationator->get($service,$code) извлекает строку перевода. Код $ может быть числовым, например 1, или строкой, например, no_connection. Между сервисами не может быть конфликтов, потому что у каждого есть свое пространство имен в области данных переводчика.
Я публикую это здесь в надежде, что это спасет кого-то от необходимости заново изобретать колесо, как я это делал несколько лет назад.
У меня была такая же проблема некоторое время назад, до того как я начал использовать фреймворк Symfony.
Просто используйте функцию __(), которая имеет arameters pageId (или objectId, objectTable, описанный в #2), целевой язык и необязательный параметр резервного языка (по умолчанию). Язык по умолчанию может быть установлен в некоторых глобальных конфигурациях, чтобы иметь более простой способ изменить его позже.
Для хранения контента в базе данных я использовал следующую структуру: (pageId, language, content, variable).
pageId будет FK для вашей страницы, которую вы хотите перевести. если у вас есть другие объекты, такие как новости, галереи или что-то еще, просто разбейте его на 2 поля: objectId, objectTable.
язык - очевидно, он будет хранить строку языка ISO EN_en, LT_lt, EN_us и т. д.
content - текст, который вы хотите перевести вместе с подстановочными знаками для замены переменных. Пример "Здравствуйте, г-н. %%name%%. Баланс вашего счета равен %%balance%%."
переменные - json-кодированные переменные. PHP предоставляет функции для быстрого их анализа. Пример "имя: Лауринас, баланс: 15.23".
Вы упомянули также слизняк. Вы можете свободно добавить его в эту таблицу просто для быстрого поиска.
Вызовы вашей базы данных должны быть сведены к минимуму с кэшированием переводов. Он должен храниться в массиве PHP, потому что это самая быстрая структура в языке PHP. Как вы сделаете это кеширование, зависит от вас. Из моего опыта у вас должна быть папка для каждого поддерживаемого языка и массив для каждого идентификатора страницы. Кэш должен быть перестроен после обновления перевода. ТОЛЬКО измененный массив должен быть восстановлен.
я думаю, что ответил на это в # 2
ваша идея совершенно логична. этот довольно простой, и я думаю, не доставит вам никаких проблем.
URL-адреса должны быть переведены с использованием сохраненных слагов в таблице перевода.
Заключительные слова
всегда полезно исследовать лучшие практики, но не изобретать велосипед. просто возьмите и используйте компоненты из хорошо известных фреймворков и используйте их.
взгляните на компонент перевода Symfony. Это может быть хорошей базой кода для вас.
Я снова и снова задавал связанные с этим вопросы, потом заблудился на официальных языках... но просто чтобы помочь вам немного, я хотел бы поделиться некоторыми выводами:
Рекомендую взглянуть на продвинутую CMS
Typo3
за PHP
(Я знаю, что есть много вещей, но это тот, который я считаю наиболее зрелым)
Plone
в Python
Если вы обнаружите, что сеть в 2013 году должна работать иначе, начните с нуля. Это означало бы собрать команду высококвалифицированных / опытных людей для создания новой CMS. Может быть, вы хотели бы взглянуть на полимер для этой цели.
Если речь идет о кодировании и поддержке многоязычных сайтов / родного языка, я думаю, что каждый программист должен иметь представление о юникоде. Если вы не знаете Unicode, вы наверняка испортите ваши данные. Не используйте тысячи кодов ISO. Они только сохранят тебе память. Но вы можете делать буквально все с UTF-8, даже хранить китайские символы. Но для этого вам нужно хранить 2 или 4-байтовые символы, что делает его в основном utf-16 или utf-32.
Если речь идет о кодировке URL, опять же, вы не должны смешивать кодировки и знать, что, по крайней мере, для доменного имени существуют правила, определенные различными лобби, которые предоставляют такие приложения, как браузер. например, домен может быть очень похож на:
ь ankofamerica.com или bankofamerica.com samesamebutdifferent;)
Конечно, вам нужна файловая система для работы со всеми кодировками. Еще один плюс для юникода, использующего файловую систему utf-8.
Если речь идет о переводах, подумайте о структуре документов. например, книга или статья. У тебя есть docbook
спецификации, чтобы понять об этих структурах. Но в HTML это просто блоки контента. Таким образом, вы хотели бы иметь перевод на этом уровне, а также на уровне веб-страницы или домена. Таким образом, если блок не существует, его просто нет, если веб-страница не существует, вы будете перенаправлены на верхний уровень навигации. Если домен должен быть совершенно другим по структуре навигации, тогда.. это совершенно другая структура для управления. Это уже может быть сделано с Typo3.
Если речь идет о фреймворках, наиболее зрелых из известных мне, для выполнения общих вещей, таких как MVC(модное слово, я действительно ненавижу это! Как "производительность". Если вы хотите что-то продать, используйте слово "производительность" и "Featurerich", и вы продаете... что? ад) Zend
, Было бы неплохо привести стандарты в кодировщики php-хаоса. Но у typo3 также есть Framework помимо CMS. Недавно он был переработан и теперь называется flow3. Фреймворки, конечно, охватывают абстракцию базы данных, шаблоны и концепции для кэширования, но имеют свои сильные стороны.
Если речь идет о кэшировании... это может быть ужасно сложным / многослойным. В PHP вы будете думать о акселераторе, коде операции, а также о html, httpd, mysql, xml, css, js ... любых видах кешей. Конечно, некоторые части должны кэшироваться, а динамические части, такие как ответы в блоге, не должны. Некоторые должны быть запрошены через AJAX с сгенерированными URL. JSON, hashbangs и т. Д.
Затем вы хотели бы, чтобы какой-либо небольшой компонент на вашем сайте был доступен или управлялся только определенным пользователям, так что концептуально это играет большую роль.
Кроме того, вы хотели бы делать статистику, возможно, иметь распределенную систему / facebook из facebooks и т. Д. Любое программное обеспечение, которое будет построено поверх ваших CMS... так что вам нужен другой тип базы данных: память, bigdata, xml, что угодно,
Ну, я думаю, этого пока достаточно. Если вы не слышали ни о typo3 / plone, ни о упомянутых фреймворках, у вас достаточно для изучения. На этом пути вы найдете множество решений для вопросов, которые вы еще не задавали.
Если тогда вы думаете, давайте создадим новую CMS, потому что ее 2013 и php все равно скоро умрут, тогда вы можете присоединиться к любой другой группе разработчиков, надеюсь, не потерявшись.
Удачи!
И кстати. как насчет людей, не имеющих больше сайтов в будущем? а мы все будем на гугл +? Я надеюсь, что разработчики станут немного более креативными и сделают что-то полезное (чтобы не быть усвоенным борглей)
//// Редактировать /// Просто немного подумать о вашем существующем приложении:
Если у вас php mysql CMS и вы хотите встроить поддержку мультиязычности. Вы можете использовать свою таблицу с дополнительным столбцом для любого языка или вставить перевод с идентификатором объекта и идентификатором языка в ту же таблицу или создать идентичную таблицу для любого языка и вставить туда объекты, а затем создать объединение выбора, если хотите чтобы они все отображались. Для базы данных используйте utf8 general ci и, конечно, в front/backend используйте utf8 text/encoding. Я использовал сегменты пути URL для URL так, как вы уже объяснили, как
domain.org/en/ о вы можете сопоставить lang ID с вашей таблицей контента. в любом случае вам нужно иметь карту параметров для ваших URL, чтобы вы могли определить параметр, который будет отображаться из сегмента пути в вашем URL, который будет, например,
domain.org/en/about/employees/IT/administrators/
настройка поиска
PageId| URL
1 | /about/employees/../..
1 | /../about/employees../../
сопоставить параметры с сегментом пути URL ""
$parameterlist[lang] = array(0=>"nl",1=>"en"); // default nl if 0
$parameterlist[branch] = array(1=>"IT",2=>"DESIGN"); // default nl if 0
$parameterlist[employertype] = array(1=>"admin",1=>"engineer"); //could be a sql result
$websiteconfig[]=$userwhatever;
$websiteconfig[]=$parameterlist;
$someparameterlist[] = array("branch"=>$someid);
$someparameterlist[] = array("employertype"=>$someid);
function getURL($someparameterlist){
// todo foreach someparameter lookup pathsegment
return path;
}
Так сказать, это уже освещалось в верхнем посте.
И чтобы не забыть, вам нужно "переписать" URL вашего генерирующего php-файла, который в большинстве случаев будет index.php
Настоящая проблема при создании многоязычного веб-сайта - это контент. Как вы собираетесь хранить разные версии одной и той же статьи? Вы используете реляционную базу данных или нереляционную?
Используя реляционную БД, такую как MySQL, вы можете воспользоваться преимуществами
JSON
тип данных для хранения всех различных версий одного и того же поля вместе.
При использовании нереляционной БД вы можете просто хранить разные версии в одном и том же объекте, идентифицируемом по их ключам.
Если вы используете Laravel, вы можете найти пакет Laravel Translatable полезным при работе с традиционными реляционными базами данных.
Если вы размещаете статический контент, Google Firebase Hosting поддерживает правила хостинга i18n, которые возвращают контент для страны, языка или языка + страны, включаяindex.html
,404.html
илиmanifest.json
файлы (для прогрессивных веб-приложений).
Их хостинг выберет страну на основе IP-адреса пользователя и выберет язык на основе заголовка их браузера, а затем применит приоритетные правила для возврата содержимого каждого запрошенного файла.
Код языка + код страны (например, контент из fr_ca/)
Только код страны (например, контент из ALL_ca/)
Только код языка (например, контент из fr/ или es_ALL/)
Содержимое «по умолчанию», которое находится за пределами каталога «i18n content», например, в корне общего каталога.
Правила 1 и 3 применяются в порядке значений качества для каждого языка в запросе.Accept-Language
заголовок.
Пример
public/
index.html // Default homepage
manifest.json // Default manifest.json
404.html // Default custom 404 page
localized-files/
ALL_ca/
index.html
es_ALL/
index.html
404.html
manifest.json << Spanish
fr/
index.html
404.html
manifest.json << French
fr_ca/
index.html
manifest.json
// firebase.json
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"i18n": {
"root": "/localized-files" // <<< "i18n content" folder
}
...
}
Полная информация ...
Работа с базой данных:
Создать таблицу языков "Языки":
Поля:
language_id (основной и автоинкрементный)
Название языка
создан в
создано
updated_at
updated_by
Создайте таблицу в базе данных 'content':
Поля:
content_id (основной и автоинкрементный)
основное содержание
header_content
footer_content
leftsidebar_content
rightsidebar_content
language_id (внешний ключ: ссылка на таблицу языков)
создан в
создано
updated_at
updated_by
Front End Work:
Когда пользователь выбирает какой-либо язык из выпадающего списка или любой области, затем сохраняет идентификатор выбранного языка в сеансе, как,
$ _SESSION [ 'язык']=1;
Теперь извлекайте данные из базы данных 'content' на основе идентификатора языка, сохраненного в сессии
Подробности можно найти здесь http://skillrow.com/multilingual-website-in-php-2/
Как человек, который живет в Квебеке, где почти весь сайт является французским и английским... я пробовал много, если не самый многоязыковой плагин для WP... единственное полезное решение, которое работает со всеми моими сайтами, это mQtranslate... я живу и умираю с этим!
Как насчет WORDPRESS + MULTI-LANGUAGE SITE BASIS
(плагин)? сайт будет иметь структуру:
- example.com/ eng / category1 /....
- example.com/ eng / my-page....
- example.com/ rus / category1 /....
- example.com/ rus / my-page....
Плагин обеспечивает интерфейс для перевода всех фраз, с простой логикой:
(ENG) my_title - "Hello user"
(SPA) my_title - "Holla usuario"
тогда это можно считать echo translate('my_title', LNG); // LNG is auto-detected
ps однако, проверьте, если плагин все еще активен.
Очень простой вариант, который работает с любым веб-сайтом, на котором вы можете загрузить Javascript, - http://www.multilingualizer.com/.
Он позволяет вам разместить весь текст для всех языков на одной странице, а затем скрывает языки, которые пользователь не должен видеть. Работает хорошо.