Лучшая практика многоязычного сайта

179

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

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

Я собираюсь обновить / перестроить систему управления контентом, которую я использую уже довольно давно. Тем не менее, я чувствую, что мультиязычность - большое улучшение этой системы. Раньше я не использовал никаких фреймворков, но я собираюсь использовать Laraval4 для предстоящего проекта. Laravel кажется лучшим выбором более чистого способа кодирования PHP. Sidenote: Laraval4 should be no factor in your answer, Я ищу общие способы перевода, которые не зависят от платформы / фреймворка.

Что должно быть переведено

Поскольку система, которую я ищу, должна быть максимально удобной для пользователя, метод управления переводом должен быть внутри CMS. Не нужно запускать FTP-соединение для изменения файлов перевода или любых проанализированных html / php шаблонов.

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

Что я придумала сама

Как я уже искал, читал и пробовал сам. У меня есть несколько вариантов. Но я все еще не чувствую, что достиг наилучшего метода для того, что действительно ищу. Прямо сейчас, это то, что я придумал, но у этого метода также есть побочные эффекты.

  1. PHP Parsed Templates : система шаблонов должна быть проанализирована PHP. Таким образом, я могу вставить переведенные параметры в HTML без необходимости открывать шаблоны и изменять их. Помимо этого, синтаксический анализ PHP дает мне возможность иметь 1 шаблон для всего сайта, вместо того, чтобы иметь подпапку для каждого языка (что у меня было раньше). Методом достижения этой цели может быть Smarty, TemplatePower, Laravel's Blade или любой другой анализатор шаблонов. Как я уже сказал, это должно быть независимым от письменного решения.
  2. Управление базой данных : возможно, мне не нужно упоминать это снова. Но решение должно основываться на базе данных. CMS предназначена для объектно-ориентированного и MVC, поэтому мне нужно подумать о логической структуре данных для строк. Поскольку мои шаблоны будут структурированы: шаблоны / Controller / view.php возможно эта структура имеет смысл использовать : Controller.View.parameter. Таблица базы данных будет иметь эти поля long с valueполем. Внутри шаблонов мы можем использовать какой-то метод сортировки, например, echo __('Controller.View.welcome', array('name', 'Joshua'))и параметр содержит Welcome, :name. Таким образом, результат Welcome, Joshua. Это кажется хорошим способом сделать это, потому что такие параметры, как: name, легко понять редактору.
  3. Низкая загрузка базы данных : Конечно, вышеуказанная система будет вызывать загрузку базы данных, если эти строки загружаются на ходу. Поэтому мне нужна система кеширования, которая повторно отображает языковые файлы, как только они редактируются / сохраняются в среде администрирования. Поскольку файлы генерируются, необходима также хорошая структура файловой системы. Я думаю, мы можем пойти с languages/en_EN/Controller/View.phpили .ini, что вам больше подходит. Возможно, .ini даже быстрее разбирается в конце. Это должно содержать данные в format parameter=value; . Я предполагаю, что это лучший способ сделать это, так как каждый представленный вид может включать свой собственный языковой файл, если он существует. Затем языковые параметры должны быть загружены в конкретное представление, а не в глобальную область видимости, чтобы параметры не перезаписывали друг друга.
  4. Перевод таблицы базы данных : это то, что меня больше всего беспокоит. Я ищу способ создания переводов новостей / страниц / и т. Д. как можно быстрее. Наличие двух таблиц для каждого модуля (например, Newsи News_translations) - вариант, но кажется, что нужно много работать, чтобы получить хорошую систему. Одна из вещей , которые я придумал основан на data versioningсистеме , которую я написал: одно имя таблицы базы данных Translations, эта таблица имеет уникальное сочетание language, tablenameиprimarykey, Например: en_En / News / 1 (ссылается на английскую версию новости с идентификатором = 1). Но у этого метода есть два огромных недостатка: во-первых, эта таблица имеет тенденцию получать довольно много времени с большим количеством данных в базе данных, и, во-вторых, использование этой установки для поиска в таблице было бы адской работой. Например, поиск поискового SEO-элемента - это полнотекстовый поиск, который довольно глуп. Но с другой стороны: это быстрый способ очень быстро создавать переводимый контент в каждой таблице, но я не верю, что этот профессионал перевешивает доводы "против".
  5. Работа с внешним интерфейсом. Кроме того, клиенту нужно подумать. Конечно, мы будем хранить доступные языки в базе данных и (де) активировать те, которые нам нужны. Таким образом, сценарий может создать раскрывающийся список для выбора языка, а серверная часть может автоматически решить, какие переводы можно выполнить с помощью 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:

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

Джошуа - Пендо
источник
3
как насчет gettext? php.net/manual/en/book.gettext.php
Франсуа Буржуа

Ответы:

115

Предпосылка темы

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

  • перевод интерфейса
  • содержание
  • 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 простых, приоритетных варианта:

  1. если установлен [:language]сегмент, используйте его
  2. если $_COOKIE['lang']установлено, используйте его
  3. использовать язык по умолчанию

Если у вас есть язык, вы просто пытаетесь перевести запрос, а если перевод не удается, используйте «значение по умолчанию» для этого конкретного сегмента (на основе результатов маршрутизации).

Здесь нет третьего варианта?

Да, технически вы можете комбинировать оба подхода, но это усложнило бы процесс и только разместить людей , которые хотят вручную 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 );

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

tereško
источник
1
Спасибо за еще одно понимание! Очень продуманный! Я думал о наличии параметра языка в URL, а также. Это просто кажется лучшим способом определения определенного языка, не только для пользователя, но и для целей SEO, а также. В случае, если пользователь меняет / en / news на / de / news, моя идея заключалась в том, чтобы сделать 301 (постоянный) перенаправление, например, на / de / nachrichten. Просто чтобы убедиться, что у каждого языка есть только один уникальный URL на страницу (опять же для целей SEO)
Джошуа - Pendo
Выбрать лучший ответ становится все сложнее и сложнее, в настоящее время существует около 3/4 ответов, каждый из которых заслуживает по меньшей мере части награды. В совокупности они становятся надежным ответом на все, что я хотел прояснить вместе :)
Джошуа - Пендо
Я принял ваш ответ, чтобы дать вам хотя бы несколько дополнительных ответов за подробный ответ, который вы дали при переводе URL. Высоко ценится! Тем не менее, награда присуждается человеку под вами, так как он ответил на каждый аспект моего вопроса независимым от платформы способом.
Джошуа - Пендо
52

Реализация 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параметром для переопределения языка, но я не предлагаю субдомен на язык или страницу на язык, потому что это усложнит просмотр популярных страниц и снизит ценность входящих ссылки, как вы будете иметь их более редко распространяться.

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

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

  1. Огромный выигрыш в производительности, если не вызывать целую кучу функций для контента, который редко изменяется (с этой системой 100 000 посетителей на французском языке все равно в конечном итоге выполнят замену перевода только один раз).
  2. Он не добавляет никакой нагрузки на нашу базу данных, поскольку использует простые плоские файлы и является решением на чистом PHP.
  3. Возможность использовать выражения 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 не являются чем-то особенным, и это увеличивает и без того высокие затраты на вызов функции для каждой фразы для перевода. Проблемы производительности и масштабируемости кажутся непреодолимыми при таком подходе.

Глюк Желание
источник
Я вижу, что я перепутал вас с "Front-end Translations", я имел в виду способ разбора переведенных строк на экране. Я определенно не ищу способ перевести это на стороне клиента! То, что я имел в виду, было самым простым способом переключения языков во внешнем интерфейсе, но это, очевидно, использование куки или пользовательских настроек :)
Джошуа - Pendo
Да, и с помощью Database-Driven я больше нацелился на метод управления всеми переводами, поэтому моим идеальным решением будет серверная часть, которая записывает переводы в базу данных, за которой следует функция, которая генерирует компонент предварительной обработки, который генерирует PHP файл. Why?Я просто не хочу беспокоиться о небольших изменениях в тексте, пользователи должны иметь возможность делать это самостоятельно, без использования редактора кода и / или программы ftp :)
Джошуа - Pendo
@PENDO Я знаю, что вы не имели в виду интерфейсные переводы, это был слегка завуалированный комментарий по отношению к пользователю, предложившему интерфейсные среды перевода с использованием JS. ;)
глюк Desire
@PENDO Я согласен, я бы использовал бэкэнд, как вы предложили, но вместо базы данных я бы использовал простой файл из соображений производительности. Конечно, ядро предложение здесь предварительно рендеринг шаблонов при изменении , чтобы вы могли заменить .INIфайлы с таблицей базы данных 3-колонок с placeholder, replacement, language. Составной ключ на placeholderа language. Затем добавьте еще 2 столбца с tempfile(путь к шаблону) и modified(DATETIME).
глюк Desire
1
@PENDO Спасибо. Я поставил 250 резервных копий и планирую передать его Тереско через 24 часа, когда сайт позволит мне, так как вы выбрали оба ответа как правильные, и я думаю, что разделение лучше всего отражает ваши намерения.
Glitch Desire
15

Я предлагаю вам не изобретать колесо и использовать список сокращений языков 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é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

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

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

Ярослав
источник
Спасибо за потраченное время, чтобы ответить на мой вопрос. Ваш английский достаточно хорош для меня, чтобы понять! Я собираюсь +1 тебе уже за твои старания!
Джошуа - Пендо
Ярослав, еще раз спасибо за ваш ответ. Однако я пошел с 2 другими ответами , которые где немного более полной и объяснить методы , используемые за кодом вместо того , чтобы указывать , что уже есть.
Джошуа - Пендо
2
Нет проблем. Действительно, эти ответы более полные и интересные для меня тоже. Но я надеюсь, что вы получили что-то полезное из моего ответа тоже.
Ярослав
12

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

Допустим, у вас есть этот текст:

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!

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

user3749746
источник
1
Это не где-то рядом с ответом, который я просил. Кроме того, вместо того, чтобы все языки были доступны на каждой странице, вам лучше создавать файлы, lang.en.phpкоторые будут включены, и использовать, $lang['welcome']которые объявлены в каждом файле.
Джошуа - Пендо
7

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

Я столкнулся с подобной проблемой некоторое время назад и написал следующий класс, чтобы решить мою проблему

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

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

  • По умолчанию, если geoipон установлен, он вернет код страны, geoip_country_code_by_nameа если geoip не установлен, отката к HTTP_ACCEPT_LANGUAGEзаголовку
Shushant
источник
Каким образом база данных будет грязной? Из-за возможных символов на разных языках? Пока у меня в основном есть сайты на английском, французском, голландском, немецком, так что пока это не проблема. Спасибо за ответ, но так как это лишь часть ответа, он не выиграет награду.
Джошуа - Пендо
ну, я думаю, ваш вопрос полезен только для вас, но есть парни, которые рассмотрят использование языков, таких как хинди, тайский, китайский и арабский (эти языки потребуют больше 1 байта для представления символов) по сравнению с вашими необходимыми языками. Если вы используете БД, то utf8_general_ciсопоставление является подходящим способом сделать это.
Shushant
Я согласен, я немного отследил себя там. Спасибо за указание, также многоразрядные символы достаточно важны, чтобы быть упомянутыми в этом вопросе :)
Джошуа - Пендо
5

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

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

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

Remy
источник
1
Спасибо за Ваш ответ. Причина, по которой я не хочу использовать Drupal / Joomla, проста: я хочу убедиться, что я знаю все входы и выходы моей системы, возможные недостатки, как строится код (и важно: не собирать 300 программистов вместе) , У меня более чем достаточно причин, чтобы не выбирать открытый исходный код. Кроме того, я хочу, чтобы моя компания была важным фактором для моих клиентов, плохо, что они могут обратиться к любому другому разработчику и оставить меня ни с чем.
Джошуа - Пендо
7
Я думаю, что все эти причины оспариваются в тоннах статей. Надеемся, что ваши клиенты не выберут вас именно потому, что у вас есть собственная CMS, которую никто другой не может поддерживать. Но в любом случае, это совершенно другая дискуссия.
Реми
1
Я понимаю вашу точку зрения, но все же я предпочитаю систему, для которой я знаю все входы и выходы, и я не чувствую ничего, чтобы полагаться на кого-либо, кто работает, когда я использую плагин.
Джошуа - Пендо
1
Кроме того, я обычно достаточно хорошо документирую свою работу, так как я работаю на «армию из одного человека», и у меня не должно быть проблем с тем, чтобы познакомиться с системой.
Джошуа - Пендо
Плохая идея - выбрать Drupal, и даже Google говорит, что им все равно, переведен URL или нет. Он должен содержать идентификатор локали.
undefinedman
5

Я не собираюсь пытаться уточнить ответы, которые уже даны. Вместо этого я расскажу вам о том, как мой собственный 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 - для Испании, 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. Между сервисами не может быть конфликтов, потому что у каждого есть свое пространство имен в области данных переводчика.

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

JG Estiot
источник
4

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

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

  2. Для хранения контента в базе данных я использовал следующую структуру: (pageId, language, content, variable).

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

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

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

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

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

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

  4. я думаю, что ответил на это в # 2

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

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

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

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

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

Лауринас Малишаускас
источник
Спасибо за комментарий, +1 за ваше время. Laravel (в моем случае) использует некоторые части Symfony, если я не ошибаюсь, так что вы абсолютно правы в том, что не изобретаете колесо. Я начал этот вопрос (и щедрость), чтобы получить представление о том, как другие переводят, я начинаю верить, что есть много лучших практик :-)
Джошуа - Пендо
1

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

Рекомендую взглянуть на продвинутую 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

Доктор дама
источник
Спасибо за комментарий, наверняка есть вещи, о которых я должен подумать. Я использую кодировку utf8 уже пару лет, я однажды боролся с символами ;-) С другой стороны, тип CMS / Framework не должен был влиять на ваш ответ, так как я искал независимый от платформы метод, как если бы мы кодировали с нуля.
Джошуа - Пендо
если вы действительно хотите писать код с нуля, рекомендую взглянуть на дартланг и полимер. Так как dartlang работает в браузере и имеет 32- и 64-битную поддержку и может использоваться для большинства целей на стороне сервера и имеет компилятор dart2js, его действительно стоит изучить. Если люди говорят о независимости платформы, они думают о Java ... мы знаем, что это значит. Buildprocess ... Я думаю, я бы использовал JSON для обмена. сгенерированный клиентский сайт с хэш-бангом и серверной стороной ... делайте все, что хотите, чтобы обеспечить сотрудничество.
Доктор Дама
Логика размещения и генерации базы данных является основной задачей. Никто не собирается делать это здесь для вас ... но сама Идея имеет значение. Так как я не забочусь о лобби, но чтобы добиться цели, я надеюсь, что вы сможете создавать модели и делиться некоторыми вещами. Я сейчас работаю над подобными задачами. Но я все еще в планировании. Я рассматриваю Typo3 как бэкэнд и создаю новую клиентскую структуру. Многоязычный шаблон решается в бэкэнде и будет обмениваться информацией специальным образом для поисковых систем / веб-сервисов. Во всяком случае, все это зависит от контекста и непрерывной строительной задачи
д-р Дама
-1

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

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

Поля:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

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

Поля:

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;

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

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

user3445130
источник
1
Это был способ простой языковой интеграции, тогда вам нужно было, вы даже пытались прочитать полные посты и дать ответы?
Джошуа - Пендо
-2

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

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

menardmam
источник
1
да, хорошо, WP не был фактором вопроса. Это мог бы быть комментарий aswel
Джошуа - Пендо
-3

Что насчет 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 однако, проверьте, если плагин все еще активен.

T.Todua
источник
3
и это не "Holla Userio" на испанском языке это "Hola Usuario"
bheatcoker
1
Lol Holla userio, это было смешно!
спекдрум
по той причине, что я не знал испанский (только что использовал пример), спешите спешить вниз! :)
Т.Тодуа
-5

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

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

Пол Мартин
источник
Осторожно, SEO было бы очень плохо! Кроме того, вы загружаете весь контент, в то время как вам просто нужна его часть, что является очень плохой практикой.
Hafenkranich
странные вещи, что сайт только на английском языке ... почему они не используют свое решение ??
eduardo.lopes