Багатомовний веб-сайт з найкращих практик


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, Blade Laravel або будь-який інший аналізатор шаблонів. Як я вже сказав, це має бути незалежним від письмового рішення.
  2. База даних : можливо, мені це не потрібно більше згадувати. Але рішення повинно керуватися базою даних. CMS призначений для об'єктно-орієнтованого та MVC, тому мені потрібно буде продумати логічну структуру даних для рядків. Оскільки мої шаблони будуть структуровані: шаблони / Controller / view.php можливо ця структура має сенс використовувати : Controller.View.parameter. У таблиці бази даних ці поля будуть довгими з valueполем. Всередині шаблонів ми могли б використовувати якийсь такий метод, як echo __('Controller.View.welcome', array('name', 'Joshua'))параметр Welcome, :name. Таким чином, результат є Welcome, Joshua. Це здається хорошим способом зробити це, оскільки такі параметри, як: ім'я, редактор легко зрозуміти.
  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). Але у цього методу є 2 величезні недоліки: по-перше, ця таблиця має тенденцію отримувати досить довго з великою кількістю даних у базі даних, а по-друге, це завдання було б використовувати цю програму для пошуку таблиці. Наприклад, пошук 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-адресі.

І третій варіант - це комбінація обох варіантів: використання "мови-ідентифікації-менш" -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:

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


Відповіді:


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 (1) , (2)

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

Вам доведеться пройти всі сегменти візерунка. Знайдіть потенційні переклади для всіх цих сегментів та визначте, якою мовою користувалися. Два додаткових джерела (cookie та заголовок) будуть використані для вирішення конфліктів маршрутизації, коли (не "якщо") вони виникають.

Візьмемо , наприклад: http://site.tld/blog/novinka.

Це транслітерація "блог, новинка", що з англійської означає приблизно "blog", "latest".

Як ви вже помітили, російською мовою "блог" буде транслітерований як "блог". Що означає, що для першої частини [:query]ви (в кращому випадку ) опинитесь зі ['en', 'ru']списком можливих мов. Потім ви берете наступний сегмент - "novinka". Це може бути тільки одна мова зі списку можливостей: ['ru'].

Коли в списку є один елемент, ви успішно знайшли мову.

Але якщо у вас є 2 (наприклад: російська та українська) або більше можливостей .. або 0 можливостей, як це може бути. Вам потрібно буде скористатися файлом cookie та / або заголовком, щоб знайти правильний варіант.

І якщо все інше не вдається, ви вибираєте мову сайту за замовчуванням.

Мова як параметр

Альтернативою є використання URL-адреси, яку можна визначити як http://site.tld/[:language]/[:query]. У цьому випадку при перекладі запиту не потрібно вгадувати мову, тому що в цей момент ви вже знаєте, яку використовувати.

Існує також вторинне джерело мови: значення файлу cookie. Але тут немає сенсу возитися з заголовком Accept-Language, оскільки ви не маєте справу з невідомою кількістю можливих мов у разі "холодного запуску" (коли користувач вперше відкриває сайт за допомогою спеціального запиту).

Натомість у вас є три простих пріоритетні варіанти:

  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: "title". Як в назві товару в інтернет-магазині або заголовку статті на сайті новин.

Приклад: 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 );

.. або якась його варіація, залежно від конкретної реалізації.


1
Дякую за ще одне розуміння! Дуже продуманий! Я думав про те, щоб також встановити мовний параметр у URL-адресі. Це просто здається найкращим способом визначення конкретної мови не тільки для користувача, але і для SEO. У випадку, якщо користувач змінить / en / news на / de / news, моя ідея полягала в тому, щоб зробити перенаправлення 301 (постійне), наприклад, на / de / nachrichten. Просто щоб переконатися, що кожна мова має лише одну унікальну URL-адресу на сторінці (знову ж таки для цілей SEO)
Джошуа - Пендо

Вибирати найкращу відповідь стає все важче і складніше, на даний момент існує приблизно 3/4 відповідей, які заслуговують принаймні на частину винагороди в кожній. У поєднанні вони стають твердою відповіддю на все, що я хотів розяснити разом :)
Джошуа - Пендо

Я прийняв вашу відповідь, щоб дати вам хоча б додаткову відповідь на детальну відповідь, яку ви дали на переклад URL-адрес. Високо оцінений! Однак, нагорода присуджується людині нижче вас, оскільки він відповідав на кожен аспект мого запитання незалежно від платформи.
Джошуа - Пендо

52

Реалізація i18n без хіта продуктивності за допомогою попереднього процесора, як запропонував Томас Блі

На роботі ми нещодавно пройшли реалізацію i18n на декількох наших властивостях, і одна з речей, з якими ми продовжували боротися, - це хіт ефективності роботи з перекладом під час польоту, тоді я відкрив цей чудовий пост у блозі від Томаса Блі що надихнуло те, як ми використовуємо i18n для управління великими навантаженнями на трафік з мінімальними проблемами продуктивності.

Замість виклику функцій для кожної операції перекладу, яка, як ми знаємо, в PHP дорога, ми визначаємо наші базові файли із заповнювачами, а потім використовуємо попередній процесор для кешування цих файлів (ми зберігаємо час зміни файлу, щоб переконатися, що ми обслуговуємо останній вміст на всі часи).

Теги перекладу

Томас використовує {tr}та {/tr}теги, щоб визначити, де починаються і закінчуються переклади. У зв'язку з тим , що ми використовуємо гілочку, ми не хочемо використовувати , {щоб уникнути плутанини , тому ми використовуємо [%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 тис. Відвідувачів французькою мовою все одно закінчаться лише заміною перекладу один раз).
  2. Це не додає ніякого навантаження до нашої бази даних, оскільки він використовує прості плоскі файли і є розчином із чистою PHP.
  3. Можливість використовувати вирази PHP в наших перекладах.

Отримання змісту перекладеної бази даних

Ми просто додаємо стовпчик для вмісту в нашу базу даних, який називається language, потім використовуємо метод accessor для LANGконстанти, яку ми визначили раніше, тому наші SQL-дзвінки (на жаль, ZF1) виглядають так:

$query = select()->from($this->_name)
                 ->where('language = ?', User::getLang())
                 ->where('id       = ?', $articleId)
                 ->limit(1);

Наші вироби мають з'єднання первинного ключа над idі languageтак стаття 54може існувати на всіх мовах. Наші LANGза замовчуванням, en_USякщо вони не вказані.

Переклад слизових URL-адрес

Тут я поєднав би дві речі, одна - це функція у вашому завантажувальному інтерфейсі, яка приймає $_GETпараметр для мови та переосмислює змінну cookie, а інша - маршрутизація, яка приймає декілька слупів. Тоді ви можете зробити щось подібне у своїй маршрутизації:

"/wilkommen" => "/welcome/lang/de"
... etc ...

Вони можуть зберігатися у плоскому файлі, до якого можна легко записати з панелі адміністратора. JSON або XML можуть забезпечити гарну структуру для їх підтримки.

Примітки щодо кількох інших варіантів

Переклад на основі PHP на основі Fly

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

Переклади на основі фронту

Я давно знайшов ці цікаві, але є кілька застережень. Наприклад, ви повинні зробити доступним для користувача весь перелік фраз на своєму веб-сайті, який ви плануєте перекласти, це може бути проблематично, якщо є ділянки веб-сайту, які ви зберігаєте приховані, або ви не дозволяєте їм отримувати доступ.

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

Переклади, керовані базами даних

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


Я бачу, що я переплутав вас із "Перекладом на передній план", що я мав на увазі спосіб розбору перекладених рядків на екрані. Я точно не шукаю способу перекласти це на стороні клієнта! Що я мав на увазі, це найпростіший спосіб переключення мов на передній план, але це очевидно за допомогою файлу cookie або налаштування користувача :)
Джошуа - Пендо

О, і за допомогою Database-Driven я більше орієнтувався на метод управління всіма перекладами, тому моїм ідеальним рішенням був би бек-енд, який записує переклади в базу даних, а потім функція, яка генерує компонент попередньої обробки, що генерує PHP файл. Why?: просто .. Я не хочу, щоб мене турбували невеликі зміни в тексті, користувачі повинні мати можливість це робити, не використовуючи редактор коду та / або програму ftp :)
Джошуа - Пендо

@PENDO Я знаю, що ти не мав на увазі переклади на передній план, це був тонко завуальований коментар до користувача, який запропонував фронтальні рамки перекладу за допомогою JS. ;)
Glitch Desire

@PENDO Я погоджуюсь, я б використовував бекенд, як ви запропонували, але замість бази даних я використовував би плоский файл з причин продуктивності. Звичайно, ядро пропозицію тут попередньо рендеринг шаблонів при зміні , щоб ви могли замінити .INIфайли з таблицею бази даних 3-колонок з placeholder, replacement, language. Складений ключ на placeholderі language. Потім укажіть ще один 2-кратний знак tempfile(шлях до шаблону) та modified(DATETIME).
Glitch 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-код у одному файлі, щоб уникнути файлових операцій, коли включати / вимагати виклику.

Про 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-колами. Ви можете просто створити окрему таблицю для слизів, і це буде правильним рішенням. Також за допомогою правильних індексів не проблема запитувати таблицю навіть з величезною кількістю даних. І це був не повний пошук тексту, а відповідність рядків, якщо буде використаний тип даних varchar для slug, і ви можете мати індекс і на цьому полі.

PS На жаль, моя англійська далеко не ідеальна.


Дякую за витрачений час, щоб відповісти на моє запитання. Ваша англійська мені достатньо зрозуміла! Я вже поставлю вам +1 за ваші зусилля!
Джошуа - Пендо

Ярославе, ще раз дякую за вашу відповідь. Однак я пішов з двома іншими відповідями, що де трохи повніше і пояснити методи, що використовуються за кодом, а не вказувати, він уже є.
Джошуа - Пендо

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, у вас є це, наприклад:

$_COOKIE['language'];

Щоб зробити його легшим, давайте перетворимо його в код, який легко використовувати:

$language=$_COOKIE['language'];

Якщо мова ваших файлів cookie - валлійська, ви маєте цей код:

echo $welcome[$language];

Результатом цього стане:

Croeso!

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


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

7

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

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

Об'єкт: Місцезнаходження \ Місцезнаходження

<?php

  namespace Locale;

  class Locale{

// Following array stolen from Zend Framework
public $country_to_locale = array(
    'AD' => 'ca_AD',
    'AE' => 'ar_AE',
    'AF' => 'fa_AF',
    'AG' => 'en_AG',
    'AI' => 'en_AI',
    'AL' => 'sq_AL',
    'AM' => 'hy_AM',
    'AN' => 'pap_AN',
    'AO' => 'pt_AO',
    'AQ' => 'und_AQ',
    'AR' => 'es_AR',
    'AS' => 'sm_AS',
    'AT' => 'de_AT',
    'AU' => 'en_AU',
    'AW' => 'nl_AW',
    'AX' => 'sv_AX',
    'AZ' => 'az_Latn_AZ',
    'BA' => 'bs_BA',
    'BB' => 'en_BB',
    'BD' => 'bn_BD',
    'BE' => 'nl_BE',
    'BF' => 'mos_BF',
    'BG' => 'bg_BG',
    'BH' => 'ar_BH',
    'BI' => 'rn_BI',
    'BJ' => 'fr_BJ',
    'BL' => 'fr_BL',
    'BM' => 'en_BM',
    'BN' => 'ms_BN',
    'BO' => 'es_BO',
    'BR' => 'pt_BR',
    'BS' => 'en_BS',
    'BT' => 'dz_BT',
    'BV' => 'und_BV',
    'BW' => 'en_BW',
    'BY' => 'be_BY',
    'BZ' => 'en_BZ',
    'CA' => 'en_CA',
    'CC' => 'ms_CC',
    'CD' => 'sw_CD',
    'CF' => 'fr_CF',
    'CG' => 'fr_CG',
    'CH' => 'de_CH',
    'CI' => 'fr_CI',
    'CK' => 'en_CK',
    'CL' => 'es_CL',
    'CM' => 'fr_CM',
    'CN' => 'zh_Hans_CN',
    'CO' => 'es_CO',
    'CR' => 'es_CR',
    'CU' => 'es_CU',
    'CV' => 'kea_CV',
    'CX' => 'en_CX',
    'CY' => 'el_CY',
    'CZ' => 'cs_CZ',
    'DE' => 'de_DE',
    'DJ' => 'aa_DJ',
    'DK' => 'da_DK',
    'DM' => 'en_DM',
    'DO' => 'es_DO',
    'DZ' => 'ar_DZ',
    'EC' => 'es_EC',
    'EE' => 'et_EE',
    'EG' => 'ar_EG',
    'EH' => 'ar_EH',
    'ER' => 'ti_ER',
    'ES' => 'es_ES',
    'ET' => 'en_ET',
    'FI' => 'fi_FI',
    'FJ' => 'hi_FJ',
    'FK' => 'en_FK',
    'FM' => 'chk_FM',
    'FO' => 'fo_FO',
    'FR' => 'fr_FR',
    'GA' => 'fr_GA',
    'GB' => 'en_GB',
    'GD' => 'en_GD',
    'GE' => 'ka_GE',
    'GF' => 'fr_GF',
    'GG' => 'en_GG',
    'GH' => 'ak_GH',
    'GI' => 'en_GI',
    'GL' => 'iu_GL',
    'GM' => 'en_GM',
    'GN' => 'fr_GN',
    'GP' => 'fr_GP',
    'GQ' => 'fan_GQ',
    'GR' => 'el_GR',
    'GS' => 'und_GS',
    'GT' => 'es_GT',
    'GU' => 'en_GU',
    'GW' => 'pt_GW',
    'GY' => 'en_GY',
    'HK' => 'zh_Hant_HK',
    'HM' => 'und_HM',
    'HN' => 'es_HN',
    'HR' => 'hr_HR',
    'HT' => 'ht_HT',
    'HU' => 'hu_HU',
    'ID' => 'id_ID',
    'IE' => 'en_IE',
    'IL' => 'he_IL',
    'IM' => 'en_IM',
    'IN' => 'hi_IN',
    'IO' => 'und_IO',
    'IQ' => 'ar_IQ',
    'IR' => 'fa_IR',
    'IS' => 'is_IS',
    'IT' => 'it_IT',
    'JE' => 'en_JE',
    'JM' => 'en_JM',
    'JO' => 'ar_JO',
    'JP' => 'ja_JP',
    'KE' => 'en_KE',
    'KG' => 'ky_Cyrl_KG',
    'KH' => 'km_KH',
    'KI' => 'en_KI',
    'KM' => 'ar_KM',
    'KN' => 'en_KN',
    'KP' => 'ko_KP',
    'KR' => 'ko_KR',
    'KW' => 'ar_KW',
    'KY' => 'en_KY',
    'KZ' => 'ru_KZ',
    'LA' => 'lo_LA',
    'LB' => 'ar_LB',
    'LC' => 'en_LC',
    'LI' => 'de_LI',
    'LK' => 'si_LK',
    'LR' => 'en_LR',
    'LS' => 'st_LS',
    'LT' => 'lt_LT',
    'LU' => 'fr_LU',
    'LV' => 'lv_LV',
    'LY' => 'ar_LY',
    'MA' => 'ar_MA',
    'MC' => 'fr_MC',
    'MD' => 'ro_MD',
    'ME' => 'sr_Latn_ME',
    'MF' => 'fr_MF',
    'MG' => 'mg_MG',
    'MH' => 'mh_MH',
    'MK' => 'mk_MK',
    'ML' => 'bm_ML',
    'MM' => 'my_MM',
    'MN' => 'mn_Cyrl_MN',
    'MO' => 'zh_Hant_MO',
    'MP' => 'en_MP',
    'MQ' => 'fr_MQ',
    'MR' => 'ar_MR',
    'MS' => 'en_MS',
    'MT' => 'mt_MT',
    'MU' => 'mfe_MU',
    'MV' => 'dv_MV',
    'MW' => 'ny_MW',
    'MX' => 'es_MX',
    'MY' => 'ms_MY',
    'MZ' => 'pt_MZ',
    'NA' => 'kj_NA',
    'NC' => 'fr_NC',
    'NE' => 'ha_Latn_NE',
    'NF' => 'en_NF',
    'NG' => 'en_NG',
    'NI' => 'es_NI',
    'NL' => 'nl_NL',
    'NO' => 'nb_NO',
    'NP' => 'ne_NP',
    'NR' => 'en_NR',
    'NU' => 'niu_NU',
    'NZ' => 'en_NZ',
    'OM' => 'ar_OM',
    'PA' => 'es_PA',
    'PE' => 'es_PE',
    'PF' => 'fr_PF',
    'PG' => 'tpi_PG',
    'PH' => 'fil_PH',
    'PK' => 'ur_PK',
    'PL' => 'pl_PL',
    'PM' => 'fr_PM',
    'PN' => 'en_PN',
    'PR' => 'es_PR',
    'PS' => 'ar_PS',
    'PT' => 'pt_PT',
    'PW' => 'pau_PW',
    'PY' => 'gn_PY',
    'QA' => 'ar_QA',
    'RE' => 'fr_RE',
    'RO' => 'ro_RO',
    'RS' => 'sr_Cyrl_RS',
    'RU' => 'ru_RU',
    'RW' => 'rw_RW',
    'SA' => 'ar_SA',
    'SB' => 'en_SB',
    'SC' => 'crs_SC',
    'SD' => 'ar_SD',
    'SE' => 'sv_SE',
    'SG' => 'en_SG',
    'SH' => 'en_SH',
    'SI' => 'sl_SI',
    'SJ' => 'nb_SJ',
    'SK' => 'sk_SK',
    'SL' => 'kri_SL',
    'SM' => 'it_SM',
    'SN' => 'fr_SN',
    'SO' => 'sw_SO',
    'SR' => 'srn_SR',
    'ST' => 'pt_ST',
    'SV' => 'es_SV',
    'SY' => 'ar_SY',
    'SZ' => 'en_SZ',
    'TC' => 'en_TC',
    'TD' => 'fr_TD',
    'TF' => 'und_TF',
    'TG' => 'fr_TG',
    'TH' => 'th_TH',
    'TJ' => 'tg_Cyrl_TJ',
    'TK' => 'tkl_TK',
    'TL' => 'pt_TL',
    'TM' => 'tk_TM',
    'TN' => 'ar_TN',
    'TO' => 'to_TO',
    'TR' => 'tr_TR',
    'TT' => 'en_TT',
    'TV' => 'tvl_TV',
    'TW' => 'zh_Hant_TW',
    'TZ' => 'sw_TZ',
    'UA' => 'uk_UA',
    'UG' => 'sw_UG',
    'UM' => 'en_UM',
    'US' => 'en_US',
    'UY' => 'es_UY',
    'UZ' => 'uz_Cyrl_UZ',
    'VA' => 'it_VA',
    'VC' => 'en_VC',
    'VE' => 'es_VE',
    'VG' => 'en_VG',
    'VI' => 'en_VI',
    'VN' => 'vn_VN',
    'VU' => 'bi_VU',
    'WF' => 'wls_WF',
    'WS' => 'sm_WS',
    'YE' => 'ar_YE',
    'YT' => 'swb_YT',
    'ZA' => 'en_ZA',
    'ZM' => 'en_ZM',
    'ZW' => 'sn_ZW'
);

/**
 * Store the transaltion for specific languages
 *
 * @var array
 */
protected $translation = array();

/**
 * Current locale
 *
 * @var string
 */
protected $locale;

/**
 * Default locale
 *
 * @var string
 */
protected $default_locale;

/**
 *
 * @var string
 */
protected $locale_dir;

/**
 * Construct.
 *
 *
 * @param string $locale_dir            
 */
public function __construct($locale_dir)
{
    $this->locale_dir = $locale_dir;
}

/**
 * Set the user define localte
 *
 * @param string $locale            
 */
public function setLocale($locale = null)
{
    $this->locale = $locale;

    return $this;
}

/**
 * Get the user define locale
 *
 * @return string
 */
public function getLocale()
{
    return $this->locale;
}

/**
 * Get the Default locale
 *
 * @return string
 */
public function getDefaultLocale()
{
    return $this->default_locale;
}

/**
 * Set the default locale
 *
 * @param string $locale            
 */
public function setDefaultLocale($locale)
{
    $this->default_locale = $locale;

    return $this;
}

/**
 * Determine if transltion exist or translation key exist
 *
 * @param string $locale            
 * @param string $key            
 * @return boolean
 */
public function hasTranslation($locale, $key = null)
{
    if (null == $key && isset($this->translation[$locale])) {
        return true;
    } elseif (isset($this->translation[$locale][$key])) {
        return true;
    }

    return false;
}

/**
 * Get the transltion for required locale or transtion for key
 *
 * @param string $locale            
 * @param string $key            
 * @return array
 */
public function getTranslation($locale, $key = null)
{
    if (null == $key && $this->hasTranslation($locale)) {
        return $this->translation[$locale];
    } elseif ($this->hasTranslation($locale, $key)) {
        return $this->translation[$locale][$key];
    }

    return array();
}

/**
 * Set the transtion for required locale
 *
 * @param string $locale
 *            Language code
 * @param string $trans
 *            translations array
 */
public function setTranslation($locale, $trans = array())
{
    $this->translation[$locale] = $trans;
}

/**
 * Remove transltions for required locale
 *
 * @param string $locale            
 */
public function removeTranslation($locale = null)
{
    if (null === $locale) {
        unset($this->translation);
    } else {
        unset($this->translation[$locale]);
    }
}

/**
 * Initialize locale
 *
 * @param string $locale            
 */
public function init($locale = null, $default_locale = null)
{
    // check if previously set locale exist or not
    $this->init_locale();
    if ($this->locale != null) {
        return;
    }

    if ($locale == null || (! preg_match('#^[a-z]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[a-z]+_[a-zA-Z]+_[a-zA-Z_]+$#', $locale))) {
        $this->detectLocale();
    } else {
        $this->locale = $locale;
    }

    $this->init_locale();
}

/**
 * Attempt to autodetect locale
 *
 * @return void
 */
private function detectLocale()
{
    $locale = false;

    // GeoIP
    if (function_exists('geoip_country_code_by_name') && isset($_SERVER['REMOTE_ADDR'])) {

        $country = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);

        if ($country) {

            $locale = isset($this->country_to_locale[$country]) ? $this->country_to_locale[$country] : false;
        }
    }

    // Try detecting locale from browser headers
    if (! $locale) {

        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {

            $languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

            foreach ($languages as $lang) {

                $lang = str_replace('-', '_', trim($lang));

                if (strpos($lang, '_') === false) {

                    if (isset($this->country_to_locale[strtoupper($lang)])) {

                        $locale = $this->country_to_locale[strtoupper($lang)];
                    }
                } else {

                    $lang = explode('_', $lang);

                    if (count($lang) == 3) {
                        // language_Encoding_COUNTRY
                        $this->locale = strtolower($lang[0]) . ucfirst($lang[1]) . strtoupper($lang[2]);
                    } else {
                        // language_COUNTRY
                        $this->locale = strtolower($lang[0]) . strtoupper($lang[1]);
                    }

                    return;
                }
            }
        }
    }

    // Resort to default locale specified in config file
    if (! $locale) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Check if config for selected locale exists
 *
 * @return void
 */
private function init_locale()
{
    if (! file_exists(sprintf('%s/%s.php', $this->locale_dir, $this->locale))) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Load a Transtion into array
 *
 * @return void
 */
private function loadTranslation($locale = null, $force = false)
{
    if ($locale == null)
        $locale = $this->locale;

    if (! $this->hasTranslation($locale)) {
        $this->setTranslation($locale, include (sprintf('%s/%s.php', $this->locale_dir, $locale)));
    }
}

/**
 * Translate a key
 *
 * @param
 *            string Key to be translated
 * @param
 *            string optional arguments
 * @return string
 */
public function translate($key)
{
    $this->init();
    $this->loadTranslation($this->locale);

    if (! $this->hasTranslation($this->locale, $key)) {

        if ($this->locale !== $this->default_locale) {

            $this->loadTranslation($this->default_locale);

            if ($this->hasTranslation($this->default_locale, $key)) {

                $translation = $this->getTranslation($this->default_locale, $key);
            } else {
                // return key as it is or log error here
                return $key;
            }
        } else {
            return $key;
        }
    } else {
        $translation = $this->getTranslation($this->locale, $key);
    }
    // Replace arguments
    if (false !== strpos($translation, '{a:')) {
        $replace = array();
        $args = func_get_args();
        for ($i = 1, $max = count($args); $i < $max; $i ++) {
            $replace['{a:' . $i . '}'] = $args[$i];
        }
        // interpolate replacement values into the messsage then return
        return strtr($translation, $replace);
    }

    return $translation;
  }
}

Використання

 <?php
    ## /locale/en.php

    return array(
       'name' => 'Hello {a:1}'
       'name_full' => 'Hello {a:1} {a:2}'
   );

$locale = new Locale(__DIR__ . '/locale');
$locale->setLocale('en');// load en.php from locale dir
//want to work with auto detection comment $locale->setLocale('en');

echo $locale->translate('name', 'Foo');
echo $locale->translate('name', 'Foo', 'Bar');

Як це працює

{a:1}замінюється 1-го аргументу, переданого методу Locale::translate('key_name','arg1') {a:2}, замінюється 2-го аргументу, переданого методуLocale::translate('key_name','arg1','arg2')

Як працює виявлення

  • За замовчуванням, якщо geoipвін встановлений, він поверне код країни за допомогою, geoip_country_code_by_nameа якщо не встановлено геоіп для зворотного HTTP_ACCEPT_LANGUAGEзаголовка

Яким чином база даних буде безладна? Через можливі символи на різних мовах? Поки що в основному у мене є англійські, французькі, голландські, німецькі веб-сайти, так що наразі це не проблема. Дякую за відповідь, але оскільки це лише частина відповіді, він не виграє щедрості.
Джошуа - Пендо

ну я гадаю, що ваше питання корисне лише вам, але знайдуться деякі хлопці, які розглядають можливість використання таких мов, як хінді, тайська, китайська та арабська (для цієї мови знадобиться більше 1 байт для відображення символів) щодо потрібних мов. якщо ви використовуєте db, то utf8_general_ciпорівняння - це відповідний спосіб зробити це.
Шушант

Я згоден, я трохи простежив там. Дякуємо, що вказали, що також мультибітові символи є досить важливими, щоб їх згадати в цьому питанні :)
Джошуа - Пендо

5

Лише додатковий відповідь: Абсолютно використовуйте перекладені URL-адреси з ідентифікатором мови перед ними: http://www.domain.com/nl/over-ons
Рішення гібридів, як правило, ускладнюються, тому я б просто дотримувався цього. Чому? Тому що URL важливий для SEO.

Про переклад db: чи більше мов фіксовано кількість мов? А точніше, непередбачувано та динамічно? Якщо це виправлено, я б просто додавав нові стовпці, інакше йдучи з кількома таблицями.

Але взагалі, чому б не використовувати Drupal? Я знаю, що всі хочуть створити власну CMS, тому що це швидше, швидше тощо. Але це просто погана ідея!


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) сценарій- конкретні рядки

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

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

Для фактичного вмісту сторінки я використовую одну таблицю на кожну мову, кожна таблиця з префіксом коду для мови. Отже, en_content - це таблиця з вмістом англійської мови, es_content - для Іспанії, cn_content - для Китаю, а fr_content - це французький матеріал.

Четвертий вид рядка актуальний на всьому веб-сайті. Це завантажується через файл конфігурації, названий за допомогою коду для мови, тобто en_lang.php, es_lang.php тощо. У глобальний мовний файл вам потрібно буде завантажити такі перекладені мови, як array ("англійська", "китайська", "іспанська", "французька") в англійський глобальний файл та масив ("Англійський", "Чинойський", " Espagnol ',' Francais ') у французькому файлі. Тож, коли ви заповнюєте спадне меню для вибору мови, воно є правильною мовою;)

Нарешті, у вас є рядки для певного сценарію. Отже, якщо ви пишете заявку на приготування їжі, це може бути "Ваша духовка була недостатньо гарячою".

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

Другий і останній мовний файл, який читається з диска, - це файл мови скрипту. lang_en_home_welcome.php - це мовний файл для домашнього / вітального сценарію. Сценарій визначається режимом (домашній) та дією (привітання). Кожен скрипт має власну папку з файлами config та lang.

Сценарій витягує вміст із бази даних, називаючи таблицю вмісту, як пояснено вище.

Якщо щось піде не так, менеджер знає, де взяти залежний від мови файл помилок. Цей файл завантажується лише у випадку помилки.

Тож висновок очевидний. Подумайте над питаннями перекладу, перш ніж розпочати розробку програми чи основи. Вам також потрібен робочий процес розвитку, який включає переклади. За допомогою своєї програми я розробляю весь сайт англійською мовою, а потім перекладаю всі відповідні файли.

Просто коротке заключне слово про те, як реалізуються рядки перекладу. У моїй системі є єдиний глобальний менеджер $, який управляє послугами, доступними для будь-якої іншої служби. Так, наприклад, служба форми переймається службою html та використовує її для написання html. Однією з сервісів моєї системи є послуга перекладача. $ translator-> set ($ service, $ code, $ string) встановлює рядок для поточної мови. Мовний файл - це перелік таких висловлювань. $ translator-> get ($ служба, $ code) отримує рядок перекладу. Код $ може бути числовим як 1 або рядок типу "no_connection". Не може бути зіткнення між службами, оскільки кожен має свій простір імен в області даних перекладача.

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


4

У мене був такий самий проблемний час, перш ніж почати використовувати рамки Symfony .

  1. Просто скористайтеся функцією __ (), яка містить араметрі pageId (або objectId, objectTable, описану у №2), цільову мову та необов'язковий параметр резервної (за замовчуванням) мови. Мова за замовчуванням може бути встановлена ​​в деякій глобальній конфігурації, щоб мати простіший спосіб змінити її згодом.

  2. Для зберігання вмісту в базі даних я використовувала таку структуру: (pageId, мова, контент, змінні).

    • pageId буде ФК на вашу сторінку, яку ви хочете перекласти. якщо у вас є інші об'єкти, такі як новини, галереї чи будь-що інше, просто розділіть його на 2 поля objectId, objectTable.

    • мова - очевидно, вона зберігатиме мовний рядок ISO EN_en, LT_lt, EN_us тощо.

    • вміст - текст, який ви хочете перекласти разом із символами підстановки для заміни змінної. Приклад "Привіт, г-н. %% name %%. Сальдо вашого рахунку - %% баланс %%."

    • змінні - змінені json змінні. PHP надає функції для швидкого їх розбору. Приклад "назва: Laurynas, баланс: 15.23".

    • Ви також згадали про кулі полі. ви можете вільно додати його до цієї таблиці просто для швидкого пошуку.

  3. Виклики вашої бази даних повинні бути зведені до мінімуму при кешуванні перекладів. Він повинен зберігатися в масиві PHP, оскільки це найшвидша структура на мові PHP. Як ви зробите це кешування, залежить від вас. З мого досвіду у вас повинна бути папка для кожної мови, що підтримується, і масив для кожної сторінкиId. Кеш повинен бути відновлений після оновлення перекладу. ТІЛЬКО змінений масив повинен бути відновлений.

  4. я думаю, що я відповів на це у №2

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

URL-адреси повинні бути переведені за допомогою збережених мовців у таблиці перекладу.

Заключні слова

завжди добре вивчити кращі практики, але не винаходити колесо. просто беріть і використовуйте компоненти з добре відомих рамок і використовуйте їх.

погляньте на компонент перекладу Symfony . Це може бути гарною базою коду для вас.


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

1

Я задавав собі споріднені запитання знову і знову, потім загубився в офіційних мовах ... але просто для того, щоб трохи допомогти вам, я хотів би поділитися деякими висновками:

Рекомендую поглянути на розширені CMS

Typo3для PHP (я знаю, що є багато речей, але це той, який я вважаю найбільш зрілим)

Plone в Python

Якщо ви дізнаєтесь, що Інтернет в 2013 році повинен працювати інакше, почніть з нуля. Це означало б зібрати команду висококваліфікованих / досвідчених людей для створення нової CMS. Можливо, ви хочете подивитися на полімер для цієї мети.

Якщо мова йде про кодування та багатомовні веб-сайти / підтримку рідної мови, я думаю, кожен програміст повинен мати підказку про unicode. Якщо ви не знаєте unicode, ви, безумовно, зіпсуєте свої дані. Не йдіть з тисячами кодів ISO. Вони лише збережуть вам трохи пам’яті. Але з UTF-8 ви можете робити буквально все, навіть зберігати китайські символи. Але для цього вам потрібно буде зберегти 2 або 4 байтові символи, що в основному робить utf-16 або utf-32.

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

ьankofamerica.com або bankofamerica.com samesamebutdifferent;)

Звичайно, вам потрібна файлова система для роботи з усіма кодуваннями. Ще один плюс для unicode з використанням файлової системи utf-8.

Якщо це стосується перекладів, подумайте про структуру документів. наприклад, книга чи стаття. У вас є docbookспецифікації, щоб зрозуміти ці структури. Але в HTML - це лише блоки вмісту. Тому ви хочете, щоб переклад був на цьому рівні, також на рівні веб-сторінки або домену. Отже, якщо блоку не існує, його просто немає, якщо веб-сторінки не існує, ви перейдете на верхній рівень навігації. Якщо домен повинен бути абсолютно різним у структурі навігації, то .. його повна інша структура для управління. Це вже можна зробити за допомогою Typo3.

Якщо мова йде про рамки, найзріліші з яких я знаю, робити такі загальні речі, як MVC (модна мова, я її дуже ненавиджу! Як і "вистава". Якщо ви хочете щось продати, скористайтеся словом "performance" і "showrich", і ви продасте ... що чорт) є Zend. Доведено, що це добре привести стандарти в кодери PHP хаосу. Але typo3 також має рамки, крім CMS. Нещодавно він був перероблений і зараз називається flow3. Рамки, звичайно, охоплюють абстракцію, шаблони баз даних та концепції кешування, але мають індивідуальні переваги.

Якщо мова йде про кешування ... це може бути приголомшливо складним / багатошаровим. У PHP ви будете думати про акселератор, опкод, але також html, httpd, mysql, xml, css, js ... будь-які кеші. Звичайно, деякі частини слід кешувати, а динамічні частини, такі як відповіді в блозі, не повинні. Деякі з них потрібно запитувати через AJAX з генерованими URL-адресами. JSON, хешбанги тощо.

Тоді ви хочете мати будь-який маленький компонент на своєму веб-сайті, щоб отримати доступ до нього або керувати ним лише певні користувачі , так що концептуально це відіграє велику роль.

Крім того, ви хочете зробити статистику , можливо, маєте розподілену систему / фейсбук фейсбуків тощо. Будь-яке програмне забезпечення, яке потрібно будувати над вашими верхніми дисками ... так що вам потрібні різні типи пам’яті баз даних , bigdata, xml, що б там не було .

ну, я думаю, що цього досить. Якщо ви ще не чули ні про typo3 / plone, ні про згадані рамки, вам достатньо вивчити. На цьому шляху ви знайдете безліч рішень для запитань, яких ви ще не задавали.

Якщо тоді ви думаєте, давайте створити нову CMS, оскільки її 2013 та php все одно помре, то ви можете приєднатися до будь-якої іншої групи розробників, сподіваємось, не загубившись.

Удачі!

І btw. як щодо людей у ​​майбутньому більше не буде веб-сайтів? і всі ми будемо в google +? Я сподіваюся, що розробники стануть трішки креативнішими і роблять щось корисне (щоб не засвоював борлик)

//// Редагувати /// Поміркуйте лише про існуючу програму:

Якщо у вас є php mysql CMS і ви хотіли вбудувати підтримку на декількох мовах. ви можете або використовувати свою таблицю з додатковим стовпцем для будь-якої мови, або вставити переклад з ідентифікатором об’єкта та ідентифікатором мови в ту саму таблицю, або створити ідентичну таблицю для будь-якої мови та вставити туди об’єкти, а потім зробити об'єднання вибору, якщо ви хочете щоб усі вони були відображені. Для бази даних використовуйте utf8 загальний ci і, звичайно, спереду / бекенд використовуйте utf8 текст / кодування. Я використовував сегменти шляху для URL-адрес таким чином, як ви вже пояснили

domain.org/en/about ви можете зіставити ідентифікатор янг до таблиці вмісту у будь-якому випадку вам потрібно мати карту параметрів для своїх 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_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

Передня робота:

Коли користувач вибирає будь-яку мову зі спадного меню чи будь-якої області, тоді зберігає вибраний ідентифікатор мови в сеансі,

$_SESSION['language']=1;

Тепер витягуйте дані з таблиці "вміст" таблиці бази даних на основі ідентифікатора мови, що зберігається в сесії.

Деталі можна знайти тут http://skillrow.com/multilingual-website-in-php-2/


1
Це потрібний спосіб простої інтеграції мови, ви навіть намагалися прочитати повні повідомлення та відповіді?
Джошуа - Пендо

-2

Як людина, яка живе в Квебеку, де майже весь сайт є французькою та англійською мовами ... я спробував багато, якщо не більшість багатомовних плагінів для WP ... єдине корисне рішення, яке працює на всіх моїх сайтах - це mQtranslate ... я живу і вмираю разом з цим!

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


1
так добре, WP не був жодним фактором питання. Це міг стати коментарем, що стосується
Джошуа -

-3

Що з WORDPRESS + MULTI-LANGUAGE SITE BASIS(плагін)? сайт буде мати структуру:

  • example.com/ eng / category1 / ....
  • example.com/ eng / моя-сторінка….
  • example.com/ rus / category1 / ....
  • example.com/ rus / моя-сторінка….

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

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

то це може бути перекреслено:
echo translate('my_title', LNG); // LNG is auto-detected

ps, перевірте, чи плагін все ще активний.


3
і не є "Holla userio" іспанською мовою - "Hola Usuario"
bheatcoker

1
Lol Holla userio, це було смішно!
spekdrum

з тієї причини, я не знав іспанської мови (тільки використаний приклад), поспішайте поспішити downvote !! :)
T.Todua

-5

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

Це дозволяє помістити весь текст для всіх мов на одну сторінку, а потім приховати мови, якими користувач не повинен бачити. Добре працює.


Остерігайтеся, SEO було б дуже погано! Плюс ви завантажуєте весь вміст, тоді як вам просто потрібна його частина, що справді погана практика.
Хафенкраніч

дивні речі, що сайт лише англійською мовою ... чому вони не використовують їх рішення ??
eduardo.lopes
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.