Лучший веб-сайт с несколькими языками

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

Позвольте мне сначала набросать ситуацию, которую я ищу

Я собираюсь обновить/переработать систему управления контентом, которую я использую довольно долгое время. Тем не менее, я чувствую, что многоязычность - это большое улучшение этой системы. Раньше я не использовал никаких фреймворков, но я собираюсь использовать 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 Blade или любой другой парсер шаблонов. Как я уже сказал, это должно быть независимым от письменного решения.
  • База данных Driven: возможно, мне не нужно упоминать об этом еще раз. Но решение должно быть основано на базе данных. CMS нацелен быть объектно-ориентированным и MVC, поэтому мне нужно будет думать о логической структуре данных для строк. Поскольку мои шаблоны будут структурированы: шаблоны /Controller/View.php, возможно, эта структура будет иметь наибольший смысл: Controller.View.parameter. В таблице базы данных эти поля будут длинными с полем value. Внутри шаблонов мы можем использовать некоторый метод сортировки типа echo __('Controller.View.welcome', array('name', 'Joshua')), а параметр содержит Welcome, :name. Таким образом, результат Welcome, Joshua. Это хороший способ сделать это, потому что параметры, такие как: имя, легко понять в редакторе.
  • Низкая загрузка базы данных. Конечно, указанная выше система будет загружать нагрузку на базу данных, если эти строки будут загружены на ходу. Поэтому мне понадобится система кэширования, которая повторно отображает языковые файлы, как только они будут отредактированы/сохранены в среде администрирования. Поскольку файлы сгенерированы, необходим хороший формат файловой системы. Думаю, мы можем пойти с languages/en_EN/Controller/View.php или .ini, что вам больше всего подходит. Возможно, даже .ini даже разобрался быстрее. Этот файл должен содержать данные в format parameter=value; , Я думаю, это лучший способ сделать это, так как каждый View, который отображается, может включать в себя его собственный языковой файл, если он существует. Параметры языка затем должны быть загружены в определенное представление, а не в глобальную область, чтобы предотвратить переписывание параметров друг от друга.
  • Перевод таблицы базы данных: на самом деле это то, о чем я больше всего беспокоюсь. Я ищу способ создания переводов Новости/Страницы/и т.д. как можно быстрее. Наличие двух таблиц для каждого модуля (например, News и News_translations) является вариантом, но, похоже, много работы для получения хорошей системы. Одна из вещей, которые я придумал, основана на системе data versioning, которую я написал: есть одно имя таблицы базы данных Translations, эта таблица имеет уникальную комбинацию language, tablename и primarykey. Например: en_En/News/1 (ссылка на английскую версию статьи новостей с ID = 1). Но есть два огромных недостатка в этом методе: в первую очередь эта таблица имеет тенденцию довольно долгое время с большим количеством данных в базе данных, а во-вторых, было бы неплохо использовать эту настройку для поиска в таблице. Например. поиск SEO-пуля элемента будет полным текстовым поиском, который довольно тупой. Но, с другой стороны, это быстрый способ создания переводимого контента в каждой таблице очень быстро, но я не верю, что этот pro перегружает консоль.
  • Работа с интерфейсом. Кроме того, интерфейсу потребуется некоторое мышление. Разумеется, мы сохранили бы доступные языки в базе данных и (де) активны те, которые нам нужны. Таким образом, script может генерировать выпадающий список, чтобы выбрать язык, а внутренний сервер может автоматически решить, какие переводы можно сделать с помощью 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-ident-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:

Я забыл упомянуть: функция __() является псевдонимом для перевода данной строки. Внутри этого метода, очевидно, должен быть какой-то метод возврата, когда текст по умолчанию загружается, когда еще нет доступных переводов. Если перевод отсутствует, он должен быть вставлен или файл перевода должен быть регенерирован.

Ответ 1

Посылка темы

На многоязычном сайте есть три различных аспекта:

  • интерфейс перевода
  • Содержание
  • маршрутизация URL

В то время как все они взаимосвязаны по-разному, с точки зрения CMS они управляются с использованием разных элементов пользовательского интерфейса и хранятся по-разному. Вы, кажется, уверены в своей реализации и понимании первых двух. Вопрос касался последнего аспекта - "Перевод URL-адресов? Должны ли мы это делать или нет?" И

?

Из чего можно сделать URL?

Очень важно то, что не знаешь IDN. Вместо этого используйте transliteration (также: транскрипция и латинизация). Хотя на первый взгляд IDN представляется жизнеспособным вариантом для международных URL-адресов, он фактически не работает так, как рекламируется по двум причинам:

  • некоторые браузеры превратят символы не-ASCII, такие как 'ч' или 'ž' в '%D1%87' и '%C5%BE'
  • Если пользователь имеет собственные темы, шрифт темы, скорее всего, не будет содержать символов для этих букв

Я действительно пытался подойти к IDN-подходу несколько лет назад в проекте на основе Yii (ужасная структура, IMHO). Перед тем, как очистить это решение, я столкнулся с обеими вышеупомянутыми проблемами. Кроме того, я подозреваю, что это может быть вектор атаки.

Доступные параметры... как я их вижу.

В принципе у вас есть два варианта, которые можно абстрагировать как:

  • http://site.tld/[:query]: где [:query] определяет выбор языка и содержимого

  • http://site.tld/[:language]/[:query]: где [:language] часть URL определяет выбор языка, а [:query] используется только для идентификации содержимого

Запрос есть A и Ω..

Предположим, вы выбрали http://site.tld/[:query].

В этом случае у вас есть один основной источник языка: содержимое сегмента [:query]; и два дополнительных источника:

  • значение $_COOKIE['lang'] для этого конкретного браузера
  • список языков в HTTP Accept-Language (1), (2) заголовок

Во-первых, вам нужно сопоставить запрос с одним из определенных шаблонов маршрутизации (если ваш выбор - Laravel, затем читайте здесь). При успешном совпадении шаблона вам нужно найти язык.

Вам придется пройти через все сегменты шаблона. Найдите потенциальные переводы для всех этих сегментов и определите, какой язык был использован. Два дополнительных источника (cookie и header) будут использоваться для разрешения конфликтов маршрутизации, когда (а не "если" ) они возникают.

Возьмем, например: 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-адреса: "title". Как в названии продукта в интернет-магазине или заголовке статьи на новостном сайте.

Пример: http://site.tld/en/news/article/121415/EU-as-global-reserve-currency

В этом случае '/news/article/121415' будет запросом, а 'EU-as-global-reserve-currency' - заголовком. Чисто для целей SEO.

Можно ли это сделать в Laravel?

Своего рода, но не по умолчанию.

Я не слишком хорошо знаком с этим, но из того, что я видел, 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 );

.. или его вариации в зависимости от конкретной реализации.

Ответ 2

Внедрение i18n без использования производительности Использование предварительного процессора, как это предложил Томас Блей

На работе мы недавно реализовали реализацию i18n по нескольким нашим свойствам, и одна из вещей, с которыми мы продолжали бороться, - это успех, связанный с переводом на лету, тогда я обнаружил это замечательное сообщение в блоге Thomas Bley, которое вдохновило нас на то, как мы используем 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, просто получить keypairs с помощью 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, чтобы переопределить язык, но я не предлагаю subdomain-per-language или page-per-language, потому что это затруднит просмотр страниц, которые будут популярны, и уменьшит значение входящих ссылок, так как вы будете иметь их более слабое распространение.

Зачем использовать этот метод?

Нам нравится этот метод предварительной обработки по трем причинам:

  • Огромный прирост производительности за счет того, что вы не вызываете целую кучу функций для контента, который редко изменяется (с этой системой, 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-трансляция On-The-Fly

Я не вижу, что они предлагают преимущество перед предварительно обработанными переводами.

Переводы на основе интерфейса

Я давно нашел эти интересные, но есть несколько предостережений. Например, вы должны предоставить пользователю полный список фраз на своем веб-сайте, который вы планируете переводить, это может быть проблематично, если есть области сайта, которые вы скрываете или не разрешаете им получать доступ.

Вы также должны предположить, что все ваши пользователи готовы и могут использовать Javascript на вашем сайте, но из моей статистики около 2,5% наших пользователей работают без него (или используя Noscript для блокировки наших сайтов от используя его).

Переводы, управляемые базой данных

Скорости подключения к базе данных PHP не имеют ничего общего, и это добавляет к уже высоким накладным расходам вызова функции для каждой фразы для перевода. Проблемы с производительностью и масштабируемостью кажутся непреодолимыми с помощью этого подхода.

Ответ 3

Я предлагаю вам не изобретать колесо и использовать список атрибутов gettext и ISO. Вы видели, как i18n/l10n реализованы в популярных CMS или фреймворках?

Используя gettext, у вас будет мощный инструмент, в котором многие случаи уже реализованы как множественные формы чисел. На английском языке у вас есть только 2 варианта: единственное и множественное число. Но на русском языке, например, есть 3 формы, и это не так просто, как на английском языке.

Также у многих переводчиков уже есть опыт работы с gettext.

Взгляните на CakePHP или Drupal. Оба многоязычных включены. CakePHP в качестве примера локализации интерфейса и Drupal в качестве примера преобразования содержимого.

Для l10n использование базы данных совсем не так. Это будет тонны по запросам. Стандартный подход заключается в том, чтобы получить все данные l10n в памяти на ранней стадии (или во время первого вызова функции i10n, если вы предпочитаете ленивую загрузку). Это может быть чтение из файла .po или из базы данных DB сразу. И чем просто читать запрошенные строки из массива.

Если вам нужно внедрить онлайн-инструмент для перевода интерфейса, вы можете иметь все эти данные в БД, но не сохранять все данные в файл для работы с ним. Чтобы уменьшить объем данных в памяти, вы можете разделить все переведенные сообщения/строки на группы и загружать только те группы, которые вам нужны, если это будет возможно.

Итак, ты совершенно прав в своем # 3. За одним исключением: обычно это один большой файл, а не файл для каждого контроллера. Потому что для производительности лучше всего открыть один файл. Вероятно, вы знаете, что некоторые высокопроизводительные веб-приложения компилируют весь PHP-код в один файл, чтобы избежать операций с файлами при вызове include/require.

О URL-адресах. Google косвенно предлагает использовать перевод:

чтобы четко указать французский контент: http://example.ca/fr/vélo-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, чтобы иметь больше идей. Он имеет достаточно четкую схему БД и достаточно хороший интерфейс для перевода. Как вы создаете статью и выбираете язык для нее. И чем позже вы сможете перевести его на другие языки.

Drupal translation interface

Я думаю, что это не проблема с URL-пулями. Вы можете просто создать отдельную таблицу для пули, и это будет правильным решением. Также, используя правильные индексы, не стоит запрашивать таблицу даже с огромным количеством данных. И это был не полный текстовый поиск, а совпадение строк, если будет использовать тип данных varchar для slug, и вы также можете иметь индекс в этом поле.

PS Извините, мой английский далеко не идеален, хотя.

Ответ 4

Это зависит от количества контента вашего сайта. Сначала я использовал базу данных, как и все другие люди здесь, но для script может потребоваться все работы базы данных. Я не говорю, что это идеальный метод, особенно если у вас много текста, но если вы хотите сделать это быстро, не используя базу данных, этот метод может работать, но вы не можете разрешать пользователям вводить данные которые будут использоваться в качестве файлов перевода. Но если вы добавите перевод самостоятельно, он будет работать:

Скажем, у вас есть этот текст:

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, у вас есть это, например:

$_COOKIE['language'];

Чтобы упростить преобразование в код, который можно легко использовать:

$language=$_COOKIE['language'];

Если ваш язык cookie валлийский, и у вас есть этот фрагмент кода:

echo $welcome[$language];

Результатом этого будет:

Croeso!

Если вам нужно добавить много переводов для своего сайта, а база данных слишком много, использование массива может быть идеальным решением.

Ответ 5

Я предлагаю вам не зависеть от базы данных для перевода, это может быть действительно беспорядочная задача и может быть крайне сложной задачей в случае кодирования данных.

У меня была аналогичная проблема в прошлом, и я написал следующий класс для решения моей проблемы.

Объект: 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} заменяется первым аргументом, переданным методу Locale::translate('key_name','arg1') {a:2} заменяется на 2-й аргумент, переданный методу Locale::translate('key_name','arg1','arg2')

Как работает обнаружение

  • По умолчанию, если geoip установлен, он возвращает код страны geoip_country_code_by_name, и если geoip не установлен, резервное копирование в заголовок HTTP_ACCEPT_LANGUAGE

Ответ 6

Просто дополнительный ответ: Абсолютно используйте переведенные URL с идентификатором языка перед ними: http://www.domain.com/nl/over-ons
Гибридные решения имеют тенденцию усложняться, поэтому я бы просто придерживался этого. Зачем? Потому что URL имеет важное значение для SEO.

О переводе БД: Количество языков более или менее фиксировано? Или скорее непредсказуемый и динамичный? Если это будет исправлено, я просто добавлю новые столбцы, в противном случае использовать несколько таблиц.

Но, в общем, почему бы не использовать Drupal? Я знаю, что каждый хочет создать свою собственную CMS, потому что она быстрее, экономнее и т.д. И т.д. Но это просто плохая идея!

Ответ 7

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

  • Просто используйте функцию __(), которая имеет arameters pageId (или objectId, objectTable, описанную в № 2), целевой язык и необязательный параметр языка возврата (по умолчанию). Язык по умолчанию может быть установлен в некоторой глобальной конфигурации, чтобы иметь более простой способ изменить его позже.

  • Для хранения содержимого в базе данных я использовал следующую структуру: (pageId, язык, содержимое, переменные).

    • pageId будет FK на вашу страницу, которую вы хотите перевести. если у вас есть другие объекты, такие как новости, галереи или что-то еще, просто разделите их на 2 поля objectId, objectTable.

      Язык
    • - очевидно, что он сохранит строку языка ISO EN_en, LT_lt, EN_us и т.д.

    • content - текст, который вы хотите перевести вместе с подстановочными знаками для замены переменных. Пример "Hello mr. %% name %%. Баланс вашего аккаунта - баланс %% %%."

    • переменные - json закодированные переменные. PHP предоставляет функции для быстрого анализа этих данных. Пример "name: Laurynas, balance: 15.23".

    • Вы упомянули также поле пули. вы можете свободно добавить его в эту таблицу, чтобы быстро найти его.

  • Ваши вызовы в базе данных должны быть сведены к минимуму при кешировании переводов. Он должен храниться в массиве PHP, потому что это самая быстрая структура в языке PHP. Как вы это сделаете, это зависит от вас. По моему опыту вы должны иметь папку для каждого поддерживаемого языка и массив для каждого pageId. После обновления перевода кеш должен быть перестроен. ТОЛЬКО измененный массив должен быть регенерирован.

  • Я думаю, я ответил, что в # 2

  • Ваша идея совершенно логична. это довольно просто, и я думаю, что это не вызовет у вас никаких проблем.

URL-адреса должны быть переведены с использованием сохраненных пулов в таблицу переводов.

Заключительные слова

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

взгляните на компонент преобразования Symfony. Это может быть хорошей базой кода для вас.

Ответ 8

Я не собираюсь пытаться уточнить уже предоставленные ответы. Вместо этого я расскажу вам о том, как моя собственная платформа OOP PHP обрабатывает переводы.

Внутри, мои рамки используют коды, такие как en, fr, es, cn и т.д. Массив содержит языки, поддерживаемые веб-сайтом: array ('en', 'fr', 'es', 'cn') Код языка передается через $_GET (lang = fr), а если он не принят или недействителен, он устанавливается на первый язык в массиве. Поэтому в любой момент во время выполнения программы и с самого начала известен текущий язык.

Полезно понять, какой контент необходимо преобразовать в типичное приложение:

1) сообщения об ошибках из классов (или процедурный код) 2) сообщения об ошибках из классов (или процедурный код) 3) содержимое страницы (обычно хранящееся в базе данных) 4) строки на сайте (например, название веб-сайта) 5) script -специфические строки

Первый тип прост для понимания. В основном, мы говорим о таких сообщениях, как "невозможно подключиться к базе данных...". Эти сообщения нужно загружать только при возникновении ошибки. Мой класс менеджера получает вызов от других классов и использование информации, передаваемой как параметры, просто переходит к соответствующей папке класса и извлекает файл ошибки.

Второй тип сообщения об ошибке больше похож на сообщения, которые вы получаете, когда проверка формы прошла неправильно. ( "Вы не можете оставить... пустым" или "выберите пароль с более чем 5 символами" ). Строки должны быть загружены до запуска класса. Я знаю, что такое

Для фактического содержимого страницы я использую одну таблицу на язык, каждую таблицу с префиксом кода для языка. Итак, en_content - это таблица с содержанием на английском языке, es_content для spain, cn_content для Китая и fr_content - это французский материал.

Четвертый тип строки имеет значение на вашем веб-сайте. Это загружается через файл конфигурации с именем, использующим код для языка, который является en_lang.php, es_lang.php и т.д. В глобальном языковом файле вам нужно будет загрузить переведенные языки, такие как массив ( "английский", "китайский", "испанский", "французский" ) в английском глобальном файле и массиве ( "Англия", "Chinois", Espagnol ',' Francais ') во французском файле. Поэтому, когда вы заполняете раскрывающийся список для выбора языка, он находится на правильном языке;)

Наконец, у вас есть script -специфические строки. Поэтому, если вы пишете приложение для приготовления пищи, это может быть "Ваша духовка не была достаточно горячей".

В моем цикле приложений сначала загружается файл глобального языка. Там вы найдете не только глобальные строки (например, "Веб-сайт Джека" ), но и настройки для некоторых классов. В основном все, что зависит от языка или культуры. Некоторые из строк включают маски для дат (MMDDYYYY или DDMMYYYY) или коды языка ISO. В основном файле языка я включаю строки для отдельных классов, потому что их так мало.

Второй и последний языковой файл, который считывается с диска, - это языковой файл script. lang_en_home_welcome.php - это языковой файл для дома/приветствия script. A script определяется режимом (home) и действием (приветствуется). Каждый script имеет свою собственную папку с файлами config и lang.

script извлекает содержимое из базы данных, именовав таблицу содержимого, как описано выше.

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

Итак, вывод очевиден. Подумайте о проблемах с переводом, прежде чем начинать разработку приложения или рамки. Вам также необходим рабочий процесс разработки, который включает переводы. С моей картой я разрабатываю весь сайт на английском языке, а затем переводит все соответствующие файлы.

Просто последнее заключительное слово о том, как реализованы строки перевода. В моей системе есть один глобальный менеджер $manager, который запускает службы, доступные для любой другой службы. Например, служба формы получает службу html и использует ее для написания html. Одной из услуг моей системы является услуга переводчика. $translator- > set ($ service, $code, $string) устанавливает строку для текущего языка. Языковой файл представляет собой список таких утверждений. $translator- > get ($ service, $code) извлекает строку перевода. Код $может иметь числовое значение, например 1 или строку типа "no_connection". Не может быть никакого столкновения между службами, потому что у каждого есть собственное пространство имен в области данных транслятора.

Я размещаю это здесь, надеясь, что это спасет кого-то задачу изобретать колесо, как будто я должен был сделать несколько лет назад.

Ответ 9

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

Я рекомендую посмотреть расширенную CMS

Typo3 для PHP (я знаю, что есть много вещей, но это тот, который я считаю самым зрелым)

Plone в Python

Если вы узнаете, что сеть в 2013 году должна работать по-другому, начните с нуля. Это означало бы собрать команду высококвалифицированных/опытных людей для создания новой CMS. Может быть, вы хотели бы взглянуть на полимер для этой цели.

Если речь идет о кодировании и многоязычных веб-сайтах/поддержке на родном языке, я думаю, каждый программист должен иметь представление об Unicode. Если вы не знаете юникод, вы наверняка испортите свои данные. Не ходите с тысячами кодов ISO. Они только сэкономит вам память. Но вы можете буквально все с UTF-8 хранить китайские символы. Но для этого вам нужно будет хранить 2 или 4 байтовых символа, что делает его в основном utf-16 или utf-32.

Если это касается кодировки URL-адресов, то там вам не следует смешивать кодировки и знать, что, по крайней мере, для имени домена существуют правила, определенные разными лобби, которые предоставляют приложения, такие как браузер. например Домен может быть очень похож на:

ьankofamerica.com или bankofamerica.com samesamebutdifferent;)

Конечно, вам нужна файловая система для работы со всеми кодировками. Еще один плюс для unicode с использованием файловой системы utf-8.

Если речь идет о переводах, подумайте о структуре документов. например книги или статьи. У вас есть спецификации docbook, чтобы понять эти структуры. Но в HTML его как раз о блоках контента. Таким образом, вы хотели бы иметь перевод на этом уровне, также на уровне веб-страницы или домена. Поэтому, если блок не существует, его просто нет, если веб-страница не существует, вы перенаправляетесь на верхний уровень навигации. Если домен должен быть совершенно иным в структуре навигации, тогда... его полная структура для управления. Это уже можно сделать с помощью Typo3.

Если его о фреймворках, наиболее зрелых, которых я знаю, делать общие вещи, такие как MVC (модное слово, которое я действительно ненавижу! Как "производительность". Если вы хотите что-то продать, используйте слово performance и featurerich, и вы продаете... какого черта) Zend. Это оказалось хорошей идеей привнести стандарты в php-хаос-кодеры. Но у typo3 также есть Framework помимо CMS. Недавно он был переработан и теперь называется flow3. Структуры, конечно, охватывают абстракцию базы данных, шаблоны и концепции для кеширования, но имеют индивидуальные сильные стороны.

Если его кэширование... может быть сложным/многослойным. В PHP вы будете думать об accellerator, opcode, но также html, httpd, mysql, xml, css, js... любых типах кешей. Конечно, некоторые части должны быть кэшированы, а динамические части, такие как ответы в блоге, не должны. Некоторые должны запрашиваться через AJAX с генерируемыми URL-адресами. JSON, hashbangs и т.д.

Затем вы хотите, чтобы любой маленький компонент на вашем веб-сайте был доступен или управлялся только определенными пользователями, поэтому концептуально это играет большую роль.

Также вы хотели бы сделать статистику, возможно, иметь распределенную систему/фейсбук facebook и т.д. любое программное обеспечение, которое будет создано поверх ваших верхних cms... так что вам нужны разные типы баз данных inmemory, bigdata, xml, что бы то ни было.

Ну, я думаю, этого достаточно на данный момент. Если вы не слышали ни о typo3/plone, ни о упомянутых рамках, вам достаточно учиться. На этом пути вы найдете множество решений по вопросам, которые вы еще не задали.

Если вы думаете, давайте создадим новую CMS, потому что ее 2013 и php все равно умрут, тогда вы можете присоединиться к любой другой группе разработчиков, которые, надеюсь, не потеряются.

Удачи!

И кстати. как насчет того, что в будущем у людей больше не будет сайтов? и все мы будем на google +? Я надеюсь, что разработчики станут немного более креативными и сделают что-то полезное (чтобы не быть ассимилированным боргом)

////Изменить/// Немного подумал о вашем существующем приложении:

Если у вас есть php mysql CMS и вы хотите встроить поддержку многоязыков. вы можете либо использовать свою таблицу с дополнительным столбцом для любого языка, либо вставить перевод с идентификатором объекта и идентификатором языка в одной таблице или создать идентичную таблицу для любого языка и вставить туда объекты, а затем сделать выборный союз, если хотите чтобы они все отображались. Для базы данных используйте utf8 general ci и, конечно, в front/backend используйте текст/кодирование utf8. Я использовал сегменты URL-адресов для URL-адресов так, как вы уже объяснили, как

domain.org/en/about вы можете сопоставить lang ID с таблицей содержимого. в любом случае вам нужна карта параметров для ваших URL-адресов, поэтому вы хотите определить параметр, который должен быть сопоставлен с URL-адресом пути в вашем URL-адресе, который будет, например,

domain.org/en/about/employees/IT/administrators/

конфигурация поиска

PageId | URL

1 |/О/сотрудников/../..

1 |/../о/сотрудников../../

параметры карты для URL-адреса 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;
}

per say, которые были покрыты уже в верхнем сообщении.

И чтобы не забывать, вам нужно "переписать" URL-адрес вашему генерирующему php файлу, который в большинстве случаев был бы index.php

Ответ 10

Работа с базой данных:

Создать таблицу языков "Языки:

Поля:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

Создайте таблицу в базе данных:

Поля:

content_id(primary and auto incremented)

main_content

header_content

footer_content

leftsidebar_content

rightsidebar_content

language_id(foreign key: referenced to languages table)

created_at

created_by

updated_at

updated_by

Front End Work:

Когда пользователь выбирает любой язык из раскрывающегося списка или любой области, затем сохраните идентификатор выбранного языка в сеансе, как,

$_SESSION['language']=1;

Теперь извлекайте данные из содержимого таблицы базы данных на основе идентификатора языка, сохраненного в сеансе.

Подробности можно найти здесь http://skillrow.com/multilingual-website-in-php-2/

Ответ 11

Как человек, который живет в Квебеке, где почти все сайты французские и английские... я попробовал много, если не самый многоязычный плагин для WP... единственным полезным решением, которое работает с моим сайтом, является mQtranslate... я живу и умираю вместе с ним!

https://wordpress.org/plugins/mqtranslate/

Ответ 12

Как насчет WORDPRESS + MULTI-LANGUAGE SITE BASIS (плагин)? сайт будет иметь структуру:

  • example.com/ eng/category1/....
  • example.com/ анг/моя-страница....
  • example.com/ rus/category1/....
  • example.com/ рус/моя-страница....

Плагин предоставляет интерфейс для перевода всех фраз с простой логикой:

(ENG) my_title - "Hello user"
(SPA) my_title - "Holla usuario"

то он может быть выведен:
echo translate('my_title', LNG); // LNG is auto-detected

p.s. однако, проверьте, включен ли плагин.

Ответ 13

Действительно простой вариант, который работает с любым сайтом, на котором вы можете загрузить Javascript, www.multilingualizer.com

Он позволяет помещать весь текст для всех языков на одну страницу, а затем скрывать языки, которые пользователю не нужно видеть. Хорошо работает.