Интернационализация веб-приложений на PHP - PullRequest
5 голосов
/ 21 июля 2011

Я создаю веб-приложение php, которое требует интернационализации.Я решил использовать get-text для системных строк и, возможно, некоторые таблицы базы данных для пользовательского контента.

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

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

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

В данный момент она хранится в следующей таблице:

app_privileges

  • id
  • Привилегия
  • Некоторые другие столбцы
  • Описание

Я планирую использовать приложение, подобное PoEdit, для генерации файлов gettext.Он может искать по всем файлам php, чтобы захватить строки.Но в подобных случаях, когда строка хранится в базе данных, извлечение строки для перехода может быть довольно трудоемким.Какие уловки и решения для этого?

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

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

Какие модели баз данных я могу использовать для хранения переводов для форм и типов продуктов?

Приветствия:)

Ответы [ 4 ]

2 голосов
/ 25 июля 2011

Для строк, которые более "системно" ориентированы, например:

  • кнопки
  • названия функций
  • привилегии
  • и т. Д.

Gettext - это хорошо.

Например, привилегии более ориентированы на систему (пользователи не создают привилегии, а администраторы предоставляют их пользователям, но не создают новые типы).привилегий).Таким образом, ваша таблица привилегий может иметь столбец «имя_полиса», который никогда не отображается и содержит только ключи gettext, например: «Привилегия: пользователь может редактировать сообщения в указанном форуме».

Строки в вашем приложении также должныне текст, который увидит пользователь, а что-то более подробное, например «Меню: Изменить настройки».

Эти строки проходят через gettext (даже для английского языка или «родного языка» сайта) и переводятся вправильные видимые пользователем строки.

Вы также должны использовать пронумерованные аргументы в стиле sprintf, то есть не «Цена% s равна% s», а «Цена% (1) s равна% (2) s».

Это имеет несколько преимуществ:

  • gettext предоставляет очень мало контекста и объединяет идентичные строки.Переводчик, который видит «Button: Edit this post», будет гораздо больше полезен, чем переводчик, который видит «Edit».В некоторых языках простой текст «Редактировать» может переводиться в разные слова или грамматические формы одного и того же глагола, в зависимости от того, что является «Отредактировано».
  • Вы можете изменить текст на английском языке по своему желанию, не нарушая ключи gettextдля других языков
  • пронумерованные аргументы обрабатывают языки с разным упорядочением темы / глагола / etc

Если у вас есть текст в изображениях (например, некоторые кнопки), вам нужно позаботиться об этом тоже,Gettext также может переводить имена файлов (images / buttons / en / submit.png => images / buttons / fr / valider.png, хотя было бы неплохо и регулярное выражение), и не забывайте, что для слепых людей, использующих программы чтения с экрана.

Для многоязычного пользовательского контента (хранящегося в таблицах) лучше использовать обычный реляционный подход.

Таблица posts (post_id ...) Таблица posts_translated (внешний ключ post_id, внешний ключ language_id, title, текст и т. д.)

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

1 голос
/ 25 июля 2011

Хитрый вопрос.

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

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

Если вам это удастся, я бысогласуйте соглашение TableName_ColumnName для ваших ключей gettext и сохраните все видимые пользователем системные сообщения в файлах gettext.

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

0 голосов
/ 25 июля 2011

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

id    title
1     a:2:{s:7:"english";s:12:"Hello World!";s:7:"spanish";s:14:"¡Hola, mundo!";}

Затем, когда запись загружается по id, вы просто десериализуете содержимое, вызывая unserialize и выбираете требуемый перевод.Вы также можете использовать json_encode и json_decode для сериализации и десериализации.Это может быть сделано в вашей модели или классе базовой модели.

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

РЕДАКТИРОВАТЬ : Но , как любезно отметили peufeu , это решение нарушает полнотекстовый поиск и не позволяет проверять существующие переводы с помощью простого SQLзапрос.Поэтому, если вы используете полнотекстовый поиск или вам нужно выбрать контент с помощью «переведенного» флага, не используйте его .

0 голосов
/ 25 июля 2011

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

  1. Насколько гибким должен быть механизм перевода.Вам нужно часто добавлять новые переведенные таблицы или обновлять структуру существующих таблиц?Вам нужно часто добавлять новые языки?
  2. Вам нужно отслеживать изменения исходного контента и деактивировать все переводы, когда оригинал был изменен.
  3. Вам нужен сервер для премодерации переводов(если в переводе участвуют несколько человек)

Для решения с максимальной гибкостью и расширяемостью вы можете пойти дальше.Оригинальные таблицы остаются без изменений.Для переводов вы добавляете таблицу translations со столбцами:

  • id первичный ключ
  • object_table имя переведенной таблицы
  • object_id ссылка напереведенный объект
  • language или language_id, если вам нужно динамически управлять языками
  • field имя переведенного поля
  • original_md5 хэш исходного значения поля (если вам нужно отслеживать изменения)
  • translation
  • author_id, published, date и другие поля, необходимые для модерации, если вам это нужно.

Затем в другой таблице или файле конфигурации вы описываете, какие таблицы и поля нужно перевести.Вы также можете описать типы полей, например, text, textarea, html, file и т. Д.

Например:

$translatedFields = array(
  // here key stands for translated table name
  'posts' => array(
    // here key stands for translated field
    // and value for field's type
    'title' => 'text',
    'body' => 'html',
  ),
);

Затем в вашемСлой доступа к данным вы определяете текущий язык и подставляете все запросы SELECT в таблицы, которые должны быть переведены.Здесь вам может помочь немного волшебства регулярного выражения и строгий синтаксис ваших запросов.Запрос SELECT title, body FROM posts WHERE posts.id='1' превращается в

SELECT
  IF(posts_title_translation.translation IS NULL,
    posts.title,
    posts_title_translation.translation) AS title,
  IF(posts_body_translation.translation IS NULL,
    posts.body,
    posts_body_translation.translation) AS body
FROM posts

LEFT JOIN translations AS posts_title_translation
ON posts_title_translation.object_id = posts.id
  AND posts_title_translation.object_table = 'posts'
  AND posts_title_translation.language = '$language'
  AND posts_title_translation.field = 'title'
---- And if you need premoderation, then filter off unpublished translations
--AND posts_title_translation.published

LEFT JOIN translations AS posts_body_translation
ON posts_body_translation.object_id = posts.id
  AND posts_body_translation.object_table = 'posts'
  AND posts_body_translation.language = '$language'
  AND posts_body_translation.field = 'body'
  --AND posts_body_translation.published

WHERE posts.id = '1'

(выражение IF в разделе SELECT позволяет выбрать исходное поле, если его перевод или публикация не выполняются).

Вот каку вас может быть гибкая система i18n.Переводы выполняются для каждого отдельного поля и автоматически подставляются в слой доступа к данным при выборе.

Это немного сложно, и, скажем так, частично под влиянием Joom! Fish расширение для Joomla !,но вот как я это сделаю.

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

...