Реализация и универсализация i18n в CMS/CMF

    Предисловие


    Столкнулся с насущной проблемой, которая автоматически становится задачей:
    Как реализовать универсальный механизм перевода контента сайта, который удовлетворял бы потребности как маленьких сайтов, так и больших порталов?


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



    Итак, поставим задачу:
    Нужно реализовать мультиязычный сайт, который будет удобно редактировать и переводить. Сайт состоит из n-го числа статических страниц. Количество языков не определено, может быть 2, может быть 20.

    Ориентировочная структура таблицы следующая:
    id - идентификатор страницы (не переводится);
    title - заголовок страницы (переводится);
    description - краткое описание страницы (переводится);
    full_text - полный текст страницы (переводится).

    Аргументы:
    • Есть инструмент — самопальный CMF, который обучен автоматической генерации админки, генерации вывода контента (все генераторы построены на структуре базы) и многим другим приятными удобствами для облегчения рутинного труда.
    • Есть идеи по реализации выложенные ниже.

    Результат:
    • Удобный административный интерфейс для перевода контента сайта.
    • Тривиальный сайт со статическими страницами на большом количестве языков.


    Перейдем к идеям


    Я не думал над названиями идей, но буду субъективно характеризовать. Так и будем их идентифицировать.

    1. Плохая, понятная и общедоступная:
    Не париться с универсализацией и создавать в этой же таблице поля с постфиксами определяющий каждый язык, т.е. выйдет так:
    id
    title_ru
    description_ru
    full_text_ru
    title_en
    description_en
    full_text_en

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

    2. Понятная, правильная и нормальная:
    Более универсальна чем предыдущая. Добавляем в эту таблицу только одно поле:
    id
    title
    description
    full_text
    culture <-

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

    3. Понятная, правильная, нормальная и улучшеная:
    К предыдущей структуре добавляем еще одно поле:
    id
    title
    description
    full_text
    culture
    parent_page_id <-

    Где parent_page_id — идентификатор оригинала страницы.
    Тогда у нас минус предыдущей структуры убирается. Но добавляется зависимость от языка оригинала. Редактирование такое-же как у предыдущей модели, добавляется один селект с большим количеством страниц на оригинальном языке.

    4. Интересная, правильная, но более сложная:
    Структура таблицы page:
    id
    и другие поля, которые не нуждаются в переводе, например идентификатор раздела в который положена эта страница

    Добавляем таблицу page_i18n:
    id
    page_id - идентификатор страницы из таблицы page
    title
    description
    full_text
    culture - язык страницы

    Соответственно для вывода придется использовать выборку из двух таблиц.
    Редактировать такую структуру не сложно. Можно выбирать даже направление перевода. Можно перевести конкретную страницу. Найти на каких страницах нет перевода. Есть полная связка страниц на сайте.

    5. Интересная и сложная, вики-перевод:
    Идея механизма перевода взята из launchpad.net
    Хороша для больших проектов где есть много переводчиков.

    Так же как в прошлом варианте.
    Структура таблицы page:
    id
    и другие поля, которые не нуждаются в переводе, например идентификатор раздела в который положена эта страница

    Добавляем таблицу page_i18n_cache:
    id
    page_id - идентификатор страницы из таблицы page
    title
    description
    full_text
    culture - язык страницы
    и другие поля, которые не нуждаются в переводе, например идентификатор раздела в который положена эта страница

    Создаем таблицу i18n_translate:
    id
    page_id - идентификатор страницы из таблицы page
    field - название поля из таблицы page_i18n_cache, например title
    translate - перевод этого поля
    user_id - пользователь который сделал перевод
    publish - является ли эта версия опубликованной
    culture - язык страницы
    возможны и другие поля, для запоминания данных о переводе, например дата перевода, кто модерировал перевод и т. д.

    1. В данном случае имеем безупречную независимость от оригинала, т.е. можем выбрать языком оригинала любой язык.
    2. «Кэшированую» схему, т.е. для выборки конкретной страницы, нам нужно сделать одну выборку и не сливаясь с другими таблицами. В кэшированой таблице находятся только опубликованные версии перевода.
    3. Интерфейс перевода простой: пользователь видит строку оригинала, переведенные другими пользователями строки и поля для ввода своего варианта.


    Итог


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

    Спасибо за внимание!

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 125

      –6
      В принципе уже есть мощный инструмент локализации — gettext
      Ознакомтесь со статьёй где описываются все плюшки такого подхода
        +4
        gettext более предназначен для перевода переменных внутри кода, а не самого наполнения.
          –1
          Я понимаю о чем вы, я изучал этот инструмент, но интересует именно не локализация текста шаблона (имеется в виду всяческие текстовые переменные такие, как сообщения об ошибках) это уже реализовано.
          Интересует комплексный подход к интернационализации, когда у нас есть именно контент, который нужно перевести и который хранится в базе.
            +2
            снова же проблемы с пониманием интернационализации и локализации, i18n и l10n :)
              +8
              простите, неправильно вас понял
              –1
              Ты совершенно не понял о чем статья — gettext используется для другого!
              0
              Лично я пришел к четвертому варианту по вашей классификации в последнем проекте. Лучше бы не приходил :-) Плюсом является только действительно понятный и четкий интерфейс для перевода. В остальном сплошные неудобства. Использовал самопальный наворот над ActiveRecord фреймворка Yii.

              В следующий раз, наверное, хочу пробовать второй вариант. Только к каждой странице добавить alias (кусок адреса url) и по нему связать страницы. Т.е. страницы с одинаковыми url являются переводами друг друга. А в адресной строке такие адреса отличаются первым сегментом — языком.

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

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

                  +1
                  посмотрите на плагин WPML для wordpress — на мой взгляд идеал того как это должно быть реализовано…
                +1
                Варианты 1 и 4, наиболее удобны, каждый при своих условиях.

                Я использую модификацию варианта 4, но только с одной таблицей.
                У меня составной примари ключ из строк:

                code — некоторый код страницы (например «main» или «about»), который используется только внутри кода.
                language — двубуквенный код языка
                и далее поля типа заголовка, текста, даты модификации, логина переводчика, и т.п.

                Этот подход имеет недостаток — строковый составной примари ключ, конечно, тормознутее целочисленных id.

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

                И еще плюс для внутреннего устройства кода довольно удобно делать выборки с ЧПУ, например www.example.com/ru/about, где ru и about сразу дают нужный ключ.

                  0
                  >>Я использую модификацию варианта 4, но только с одной таблицей.
                  Это уже вариант номер 2, а не 4…
                    0
                    Да, на 2 тоже похож, но все же ближе к 4-му, имхо, т.к. во 2-м id уникальный (в следствие того что он примари) ключ.
                    В моем варианте вместо доп. таблицы составной ключ, где каждое поле поотдельности не обладает уникальностью.
                    0
                    Представим случай интернациональным интернет-магазином, где есть, ну очень много товаров. И у нас наивная цель сделать возможным перевод на любой язык с любого языка.

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

                    У вас есть какие-нибудь примеры реализации?
                      0
                      Если честно, я третий вариант вообще не понимаю, в каких условиях он дает выгоду. Уж лучше в этом случае, имхо, 4-й вариант, где идентификатор страницы вынесен отдельно, а не «замкнут» в дополнительное поле той же таблицы.
                        0
                        Мы просто связываем все переводы с помощью ключа parent_page_id, в вашем случае альтернативой этого ключа будет ключ code.
                          0
                          Да, наверно вы правы.
                      0
                      >>Этот подход имеет недостаток — строковый составной примари ключ, конечно, тормознутее целочисленных id.
                      Тут можно попробовать primary сделать int, а соответствие lang:code-id вынести в какой-нить быстрый кэш.
                        0
                        Тоже вариант, но это уже оптимизация под хорошую нагрузку. В моем случае вполне вертелось и такое решение, страниц было не так много — несколько сотен, что вполне приемлимо.
                      +3
                      Насчет первого варианта — не согласен, что он плохой. Ваша аргументация:

                      Когда их становиться больше, то эта структура будет ну очень не удобная. А если еще и переводимых полей больше…

                      Это зависит от инструментов/абстракций, которые под это дело написаны. Например, для django есть приложение django-modeltranslation, которое реализует этот подход, и все удобно. Да и вообще, в чем неудобство — не очень понятно.

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

                      А тут смешан вопрос о структуре БД и вопрос о том, как ваша конкретная cms/cmf с ней работает. Абсолютно ничего не мешает сделать нормальный интерфейс для администрирования. Да и в любом случае, если нужно несколько вариантов перевода, то нужно заполнить несколько полей с переводами. Связь того, кто куда попадает, со структурой БД — это тоже, видимо, какая-то особенность вашей cms/cmf.

                      У первого подхода есть ряд плюсов: он простой, вместо join'ов или нескольких запросов данные выбираются из 1 таблицы, очень просто реализуется откат на «язык по умолчанию», если нужного перевода нет. В случае, если переводы лежат в отдельных таблицах, нужно или все переводы сразу для этого выбирать, или делать 2 запроса в случае, если перевода нет. Его минус — чтобы добавить язык, нужно менять структуру БД (что, впрочем, решается очень просто с помощью какого-нибудь инструмента для миграций), ну и разбухание одной таблицы.

                      Вы верно заметили, первый способ лучше использовать, когда языков немного, и они заранее известны. Но подавляющее большинство сайтов, где требуется i18n контента в базе — именно такие и есть ведь: 2, максимум 3 языка. Равноправный контент > чем на 3 языках — большая редкость, только википедия и приходит на ум сейчас. Обычно ведь перевод контента не требуется (или на это нет ресурсов — 2 языка-то поддерживать уже накладная задача), а нужен только перевод интерфейса, что решается совсем другими методами (gettext).
                        0
                        Я охарактеризовал их субъективно.

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

                        И задача стоит конкретно переводить не интерфейс а контент.
                          0
                          Да и плохой он только в случае, когда требуется универсализация, во всех остальных он чаще всего верен.
                            +1
                            Удобство — вопрос инструментов и уровня абстракции. Например, как это в django-modeltranslation сделано: в ORM мы по-прежнему указываем 1 поле, без всяких префиксов и тд, как будто переводы на другие языки нас не интересуют. В таблице при этом создаются также поля под все необходимые языки, но с таблицей-то мы работаем через ORM, и у нас там по-прежнему 1 поле. При доступе к полю правильный язык выбирается автомагически по языку текущего пользователя. При необходимости можно выбрать данные для нужного языка вручную. Если перевод не заполнен, идет откат на язык по умолчанию.

                            Если нужно добавить новый язык, то указываем его в настройках (1 строка), запускаем south (инструмент для миграций), он сам определяет, какие колонки нужно добавить/убрать и генерирует миграцию (+1 команда в консоли), смотрим, что там все в порядке, запускаем ее на локальной машине (+1 команда в консоли) и на сервере (еще +1 команда в консоли) — структура таблиц поменялась. Не вижу тут абсолютно ничего корявого, наоборот, очень удобно и минимум действий.

                            Другое дело, что язык нельзя добавить из интерфейса администратора. Если языков 30, то это может быть не удобно (хотя вполне приемлемо, разве что 30 раз придется «пнуть» программиста, чтобы он дописал строку в настройки и выполнил 3 команды в консоли). И еще это может быть плохо с точки зрения БД (если контент большой — весь в одной таблице).

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

                              Я же руководствуюсь принципом 7 раз отмерь 1 отрежь. Хочу добиться правильного соотношения понятность/эффективность/универсальность.
                              И согласно моему выбору, в котором, надеюсь, мне помогут ваши комментарии я разработаю соответствующий генератор для своего фреймворка. И соответственно убрать рутину из разработки, хотя всю рутину не уберешь никогда, наверное :).

                              Простите за слово «корявость», это было выражением двух последних абзацев в вашем комментарии. :)

                                0
                                Самый простой вариант — переходите на Django, и забудете о подобных проблемах. Забудь о «глобальной» универсальности подхода — универсальность нужна только в контексте выбранной платформы для разработки.

                                «Хочу добиться правильного соотношения понятность/эффективность/универсальность» — тем более Django для вас. Я около года разрабатывал подобные «универсальные» вещи (я о реализациях велосипедов на PHP, да), вместо того чтобы заниматься реально интересными задачами, и сейчас, в принципе жалею о потраченном времени.)
                                  0
                                  Согласен, просто индивидуальность поставленных передо мной задач не позволяет мне сделать такой шаг. Тут есть четкая необходимость реализовать это в контексте своего фреймворка и реализовать «сразу», приняв правильное решение.
                                  Я имел опыт работы с Symfony и CodeIgniter. Там тоже решены эти задачи. Но все-же хочется решить их правильно индивидуально, предварительно обсудив с вами.

                                  Я работал с многими программистами, но ни один так четко и не сказал как правильно решить задачу с переводом контента. У всех свое мнение, которое они пытаются навязать. Это не плохо, наверное… Но в итоге, перестаешь понимать, что такое хорошо, а что такое плохо.
                                    +1
                                    мм… Реализация в Symphony мне когда нравилась больше, и она похожа на ваш вариант.) *если не ошибаюсь*. Хотя для работы я предпочитал CI.

                                    *PM mode on*
                                    Хорошо — это быстро, и в срок, а лучше чуть раньше.
                                    Хорошо — это производительно, легко поддерживаемое и кэшируемое.
                                    Хорошо — это второй вариант, скорее всего.
                                    *PM mode off*

                                    У вас условие — это наличие странного CRUD'а о котором вы говорите. Поэтому четвертый вариант правильней в вашей ситуации — проще интерфейс для переводов. А вообще — что вам мешает перегрузить свой CRUD для конкретного случая кастомным интерфейсом?
                                      0
                                      *не ошибаетесь*, но в данном случае я не использую симфони.
                                      Ничего не мешает. Задача состоит в написании такого CRUD. Просто хочется сделать его единым для проекта.

                                      *PM mode on*
                                      Бесспорно Вы правы.
                                      Но все же я хочу сначала разобраться что делать, а потом как делать.
                                      *PM mode off*
                                      0
                                      Вот imho самое достойное мнение в этом топике: habrahabr.ru/blogs/webdev/99480/#comment_3074020

                                      Разные задачи, наилучшие решения — тоже разные. У всех решений тут есть плюсы и минусы, идеального и подходящего для всех случаев решения нет (по крайней мере, мне оно неизвестно).

                            0
                            И все-таки, есть ли какие-то реальные мысли или скриншоты админок где реализована интернационализация?
                            • UFO just landed and posted this here
                                0
                                Да-да, что-то вроде этого. Спасибо.
                                0
                                При желании за красивым интерфейсом можно скрыть и «плохую» структуру таблицы №1.

                                В интерфейсе на мой взгляд главное:
                                1) все языковые версии страниц редактируются отдельно;
                                2) можно легко добавить новую языковую версию страницы с помощью копирования.

                                Но есть пример сложнее — к примеру у нас есть таблица с объектами недвижимости.
                                Каждый объект недвижимости содержит 10 свойств, из которых необходим перевод для различных языковых версий только для 3 свойств (к примеру название, адрес).
                                В таком случае, удобнее делать правку сразу всех языковых версий объекта на одной странице — с точки зрения интерфейса.
                                  0
                                  На своих проектах админку локализаций делал по принципу описанному в этой презентации www.slideshare.net/ingvar/symfony-presentation-i18n-2, там описано именно работа с отдельными сообщениями, на последних слайдах есть скриншот админки, делал локализацию объектов по тому же принципу, все поля подлежащие переводу клал в отдельную таблицу, минус что приходилось join`ить таблицы, но в плане удобства очень приятное решение. Список всех языков выделен в отдельные сущности, т.е. таблицы с переводами имеют внешние ключи. Так же в таблицах с переводами определен язык по умолчанию. Пока проблем в данном механизме не возникало. Если у кого проблемы были буду рад о них узнать.
                                  0
                                  Сам использую 1-ый вариант. Изначально была разработка по типу общая таблица для переводов где формировался уникальный ид из полей таблица + поле + id
                                    +1
                                    например countries_name_ru, имя извлекалось при помощи join, но когда эта таблица разрослась то давала неимоверные тормоза. На данный момент как говорил используется 1-ый вариант, структура задается в xml файле, т.е. просто указывается таблица и список полей, при добавлении языка или таблицы считывается настройки и формируются новые столбцы. Выборка производится IF(name_ru!='', name_ru, name) AS name. Проблем с добавлением той или иной версии языка нет, так как были разработаны все нужные инструменты. Также не приходилось делать сайты с огромным кол-вом языков (максимум 3). Также отмечу что все что касается статей, новостей или страниц контента имеет привязку к конкретному языку сайта и в переводе не нуждаются (тут уж все зависит от конкретной реализации). Как правило в переводимости нуждаются к примеру товары (заголовок, описание и т.д.). Это частный случай, и тут я использую нечто похожее на 4-ю схему.
                                      0
                                      там забыл еще указать локаль, правильно будет таблица + поле + id + код языка, получим countries_name_ru_ru или usergroup_title_1_ru
                                        0
                                        Понятно, спасибо. Вот по этому и интересует универсальное решение как для статических страниц, так и для товаров и более сложных структур.
                                    +4
                                    Наблюдение из практики — часто сам набор страниц для разных языков бывает разный
                                      0
                                      Согласен. Для такого случая полезными будут 2,4,5 варианты.
                                    • UFO just landed and posted this here
                                      0
                                      В Symfony встроен ваш 4 вариант. Весьма себе удобно работает:)
                                        0
                                        Да, я в курсе, он не мой :). А как администрируется? Есть скрины?
                                          0
                                          Генерируемая админка рассчитана на администрирование не i18n моделей. Как сделаете — так и будет.

                                          Я в свое время делал табы для переводимых полей моделей в форме редактирования. Делается за считанные минуты:)
                                        0
                                        на довольно нагруженном сайте (~20K хитов в день) используем четвертый вариант, слегка усложненный:
                                        оригинальная таблица article сделана для английского языка, содержит все поля:
                                        id
                                        title
                                        description
                                        short_description

                                        к ней джойнится таблица переводов article_langspec:
                                        language_id
                                        article_id
                                        title
                                        description
                                        short_description

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

                                        Сейчас весь интерфейс работы с таблицами обернули в единый класс, который сам в зависимости от локали подключает нужные таблицы и строит выборки. Результаты жестко кешируем.

                                        Итого: в плюсах имеем ускорение в получении оригинального контента.
                                        В минусах чуть более сложную структуру.
                                        Было желание перенести английскую версию тоже в ледспек (как в вашем 4м примере). Подозреваю это дало бы ускорение за счет того что результаты выборки стали бы помещаться в кеш мускуля.
                                        Еще хотелось поиграться с ленивой загрузкой переводов — грузить только по мере использования и жестко кешировать.
                                        Сейчас на сайте 8, кажется, языков. Система работает.
                                          0
                                          Вот как раз с кешированием, я думаю над пятым вариантом, т.е. дублируется структура основной таблицы и еще одно поле, идентификатор языка, как во втором варианте. Т.е. получаем комбинированную структуру и широкие возможности администрирования.
                                            0
                                            В итоге все работает без джоинов, а простой выборкой. Спасибо за живой пример.
                                              0
                                              к сожалению в основной таблице не только id уникален. У нас там хранятся такие вещи как code для найс урлов (одинаков для всех), картинки к статьям, иконки, файлы и т.д. и т.п.
                                              Кроме того, из-за пожелания заказчика «показывать переведенные выше чем все остальное» пришлось реализовать кеширование флага о наличии перевода в SET поле основной таблицы. Без этого сайт напрочь ложился.
                                              Если это все дублировать по разным таблицам получится очень тяжело. К тому же любой программист будет тыкать в вашу структуру БД и громко смеяться о ее нормализации.
                                                0
                                                Ну нормализация тут не доходит до четвертой нормальной формы.
                                                Если это критично, то этот вариант можно модифицировать с одним join. Что тоже принципиально равносильно, и тогда нормализируется вся структура.
                                                Все-ровно сначала нужно определиться на абстрактном уровне как делать.
                                            0
                                            Я бы сделал на каждый перевод свою базу данных…
                                            Поля не нуждающиеся в переводе просто дублируются.
                                            Места тратится больше, зато все остальное проще (вывод очень простой получается по-моему)
                                              0
                                              А готовое решение MODx + YAMS не смотрели?
                                                0
                                                Спасибо, посмотрел. Но больше похоже на инструмент перевода интерфейса, а не перевода контента…
                                                О модели в документации умалчивают. Больше рассказывается об автоматической генерации ссылок на локализированные страницы и вообще как это прикольно и круто.
                                                0
                                                Вот уже 2 года в нескольких проектах использовал 3-й вариант, в узких местах кешировал мемкешем, ни разу не пожалел, сбоев не было.

                                                Так как языки с разным направлением письма (ltr/rtl), переводы делать на одной и той же страницы было не удобно, сделал переключение всего интерфейса админки. Не переведенный контент для удобства в админке отображается на дефолтном языке, контент которого вносится первым.
                                                yfrog.com/n8i18np

                                                На самом сайте если для статьи (к примеру) нет перевода, то она легко отфильтровывается и не отображается.
                                                  0
                                                  Спасибо, а можно по-подробней об узких местах?
                                                    0
                                                    Единственным узким местом был джоин двух таблиц (возможно с условием) при выводе списка позиций (например статей).

                                                    Сорри! Только что обратил внимание, что не верно указал номер варианта — 3. Мой вариант 4.

                                                    У меня так
                                                    articles
                                                    — id (PK)
                                                    — alias
                                                    — created
                                                    — is_show

                                                    articles_i18n
                                                    — id (PK) — равен articles.id
                                                    — lang_id (PK)
                                                    — title
                                                    — description

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

                                                    Работа с такими таблицами реализована в одной модели (делал на Codeigniter). Такой механизм легко работает и с иерархическими структурами, такими как категории с Nested Sets.
                                                      0
                                                      абсолютно точно так же сделано в Doctrine I18n
                                                        0
                                                        с Doctrine близко не знаком… видимо велосипед изобретал :)
                                                  0
                                                  Попробуйте интернационализацию CodeIgniter — там в url добавляется язык, который через CI файл routes.php скрывается (ru|en). Мы так делали один сайт — пользователь нажимает на иконку языка — и вуаля — в урл добавляется язык, который после еще одного перехода скрывается. Можно оставлять.
                                                    0
                                                    Уровень абстракции на котором проводится обсуждение, предусматривает, что предварительно такой механизм уже реализован.
                                                      0
                                                      … и вместе с этим используем пункт 2!
                                                        0
                                                        Согласен, но скорее пункт 3, так как нужно чтоб при переходе на другой язык, у нас показывалась та же страница.
                                                    +2
                                                    Универсального решения я думаю нет. Все зависит от задачи. Когда клиент хочет интернационализацию на сайте, я всегда спрашиваю, что вам конкретно нужно товарищ? Сколько языков требуется?

                                                    — каждая страница сайта с оригинальным контентом имеет страницу-перевод. Фиксированное количество языков. При этом лучше чем вариант №1 ничего нет. Минимум запросов к БД, простое редактирование. Все модели данных имеющие контентные поля, имеют их дубликаты. Соответственно правка объектов такой модели состоит в написании оригинала и перевода/ов друг под другом.

                                                    — локализации сайта имеют разное количество страниц/асинхронное наполнение контентом языковых версий сайта. Лучше чем дубликат всего сайта с управлением из одной админки, да еще с многопользовательским доступом к разноязыковым моделям иногда сложно что-то придумать, каждая версия развивается как ей угодно. В данной статье это варианты №2,3 (но применительно ко всем контентным моделям сайта)

                                                    — если количество языков еще не определено, тогда, из моделей данных все контентные поля выносятся в отдельные модели имеющие метки языка. В данной статье это вариант №4,5. К примеру — новости: id,name,date,source — поля остаются в модели news, а контент набивается в объекты модели news_i18n связанную как hasMany в news, тоже самое с другими контентными моделями.
                                                      0
                                                      Объективно, спасибо.
                                                        +1
                                                        Еще просили скрины. За полчаса сделал решение к каждому из трех вариантов на базе моего фреймворка:
                                                        habreffect.ru/files/263/ea7b01c67/first.png
                                                        habreffect.ru/files/43b/2de358f7b/second.png
                                                        habreffect.ru/files/60b/bca785a20/third.png
                                                          0
                                                          Как-то Ваш фреймворк очень похож на MODx. Можете подробнее рассказать, что Вы такое с ним сделали, чтобы превратить в то, что получилось на скриншотах? И насколько это стало удобнее, чем исходный фреймворк?
                                                            0
                                                            Да, каюсь. Я позаимствовал стилевое оформление и иконки, а вся начинка моя. Т.к. не далеко не дизайнер, то просто портировал десяток тем от modx. Сам modx не используется вообще. Фреймворк больше всего похож на cakephp(model(своя ORM), практически такие же layout, view, добавлены несколько типов связей, реализация контроллеров совершенно другая) и codeigniter(но без хуков), сделан свой скаффолдинг(виден на скринах), прегенерация таблиц и связей в develop mode на основе описания моделей. В принципе все идеи лежат на поверхности, просто все сделано для себя с целью максимально увеличить скорость разработки.
                                                            0
                                                            Спасибо, так уже интересней.
                                                          0
                                                          У нас 3 таблицы:

                                                          id страницы
                                                          все поля которые не нуждаются в переводе (url, parent  и т.д.)


                                                          id языа
                                                          двубуквенный индекс языка


                                                          id страницы
                                                          id языка
                                                          поля, которые нуждаются в переводе


                                                          Таблица с языками используется для вывода «флажков» — переключателей языка.

                                                          В админке просто для каждой страницы сделаны как вкладки — на первой вкладке редактируем общие поля (url, родителя и т.д.) и для каждого языка по вкладке.

                                                          Достаточно удобно.

                                                            0
                                                            Спасибо, это мой четвертый вариант.
                                                            +1
                                                            Есть распаралелливание (несколько непохожих структурой ресурсов, каждый под свой язык), клонирование (механизм описанный выше, фиксированная структура сайта, можно переключаться с одного языка на другой на любой статье) и «языковые версии». Последний гибче и работают так:

                                                            — есть «сущность» или статья
                                                            — есть языковые версии этой сущности

                                                            Сущность — единая в системе и не несет в себе текста и прочих аттрибутов, кроме «привязки» к конкретному разделу (разделам) сайта.

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

                                                            В базе это хранится как:

                                                            — Табл. 1 — сущности
                                                            — Табл. 2 — языковые версии (одно из полей language — язык) связанные с Табл. 1

                                                            Выборка идет по таблице сущностей и по language (Табл. 2)

                                                            Например, сущность a [languages: en, fr, de], b [languages: en, fr].

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

                                                            Посетитель открывает страницу с материалом «a», CMS просматривает языковые версии материала и предлагает ему выбор языка [fr, de]. Человек может перейти на ту языковую версию, которая ему удобна (1).

                                                            Если он переходит на de и возвращается в раздел, CMS делает выборку материалов, как «статьи подвязанные к этому разделу с языковой локалью de» и покажет ему только материал «а», спрятав «b», не поддерживает этот язык (2).
                                                              0
                                                              Я так понимаю что таблица сущностей такая же как в 3-м варианте?
                                                                0
                                                                Нет, сущности вобще переводов и текстов не имеют.
                                                                  0
                                                                  Ну в сущности хранятся связки статьи с разделом, например, и служебные данные не требующие перевода.
                                                                  А в языковых версиях те поля, которые переводятся и таблица связей перевода со статьей.
                                                                  Так?
                                                                    +1
                                                                    Аааа… Это 4-й вариант. У нас так enterprise система была построена. Жутко удобно, только кругом надо айдишник локали пихать.
                                                              –1
                                                              А не лучше ли сделать все в одном поле?

                                                              [lang=ru]Привет, Мир![/lang]
                                                              [lang=en]Hello, World![/lang]

                                                              Помоему с этим вариантом все становится легко и просто, как считаете?
                                                                0
                                                                Нарушается принцип атомарности. В результате: как составить запрос на выборку, например, 10 последних статей, в которых есть английский язык?
                                                                  –1
                                                                  WHERE article.text like '%[lang=en]%'
                                                                    0
                                                                    Т.е. Вы имеете в виду почти такой же вариант как habrahabr.ru/blogs/webdev/99480/?reply_to=3073862#comment_3074312
                                                                      0
                                                                      Да, практически одно и тоже. Вопрос только в том, какой более удобный метод сериализации выбрать.
                                                                        0
                                                                        а что удобней сеарилизации пхп, когда мы можем туда всунуть массив, где ключами будет код языка.
                                                                        Все ровно вариант не хорош. Представьте себе что мы делаем листинг новостей. Представляете как затратно расшифровывать все данные?
                                                                      0
                                                                      Это ужасно.
                                                                    0
                                                                    Ваш пример касается перевода интерфейса, а не перевода контента.
                                                                      0
                                                                      почему-же, можно и контент так-же переводить
                                                                        0
                                                                        можно автоматом генерировать текстовые поля для редактирования имеющихся языков, а также для добавления текста на другом языке.
                                                                    +1
                                                                    Вот, что я на этот счет думал. Поля для примера, естественно. id — идентификатор primary key, publish — опубликован объект или нет, order — сортировка.

                                                                    table_object
                                                                    id, publish, order

                                                                    object_id — id объекта, system — тип поля (чтобы отличить заголовок от текста), data — собственно само наполнение поля, lang — язык.

                                                                    table_data
                                                                    object_id, system, data, lang

                                                                    Ну, и как пример:

                                                                    table_object:
                                                                    1, 1, 1

                                                                    table_data
                                                                    1, «title», «Приветствую вас на моей страничке!», «ru»;
                                                                    1, «title», «Welcome!», «en»;
                                                                    2, «introtext», «Как же я рад вас видеть!», «ru»;
                                                                    2, «introtext», «I hate you all!», «en»;

                                                                    И так далее…
                                                                      0
                                                                      Чтобы получить контроль версий перевода, можно добавить поле «time» в таблицу «table_data», содержащее timestamp добавления перевода.

                                                                      Собственно, мне пока не удалось найти явные недостатки такого подхода. Но версионность я сейчас на ходу придумал, она в 99% случаев не нужна, хотя всякое может быть.
                                                                        0
                                                                        А с контролем версий лучше использовать 5 вариант. Он масштабируемый.
                                                                          0
                                                                          Может я чего-то не понимаю, но версия — это состояние на конкретный момент времени. С полем timestamp мы это и получаем. Т.е. не вижу смысла усложнять.
                                                                            0
                                                                            да, но если делать контроль версий, то зачем лишними версиями захламлять таблицу, нужно делать, своего рода «ветку» — таблицу последних версий.
                                                                              0
                                                                              А как они могут быть «лишними»? Кроме того, при наличии индексов (а они есть), запрос будет обрабатываться за сотые доли секунды, даже если там миллиард записей. В таблице базы данных хлама быть не может :)
                                                                                0
                                                                                Бесспорно, но посмотрите на 5-й вариант.

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

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

                                                                                  Дата, в нашем случае, — обычное число, а не порнография вида «dd-mm-yyyy». Сервер баз данных прекрасно обработает этот запрос с разницей, разве что, в одну миллионную секунды (хотя, я уверен, разницы не будет вообще).

                                                                                  На счет публикации версионности:

                                                                                  Если нужна возможность публикации версий, то просто добавляем поле «publish» типа «double» и в запросе используем «WHERE `lang`= 'en' AND `publish` = '1' ORDER BY `timestamp` DESC LIMIT 1». Таким образом получаем последнюю опубликованную версию контента для заданного языка.
                                                                                    0
                                                                                    Согласен. Имеет право на жизнь.
                                                                                      0
                                                                                      В теории все, скорее всего, именно так. А вот на практике — зачастую денормализуют схему БД для улучшения производительности.

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

                                                                                      Более того, в таблице версий будет и последняя созданная запись. А в «текущей» таблице (назовем ее «быстрая таблица») будет та запись, которая на данный момент опубликована (даже если она — не последняя созданная). Именно данные из быстрой таблицы будут выбираться чаще всего, и постоянно кэшироваться, поэтому этот подход очень даже имеет право на жизнь.
                                                                          0
                                                                          Вы сейчас описали идею номер 4. Но спасибо, за более детализированное объяснение.
                                                                            +1
                                                                            Не совсем. Я не храню «id странички», я храню айдишники объекта. Таким образом можно например перевести только текст, или только заголовок.
                                                                              0
                                                                              ну да, логично. интересная мысль, спасибо!
                                                                          0
                                                                          Для мультиязычных страниц использую ваиант 4.
                                                                          Вполне вменяемо
                                                                            0
                                                                            Я хранил содержимое полей в БД в формате XML. То есть, запрашивая, скажем, название чего-либо из БД получал ответ в виде XML-документа, элементы которого содержали аттрибут с кодом языка по ISO и переводом в теле. XPath помогал быстро добраться до нужного языка, а кэшированный результат запроса позволял быстро получать перевод на другой язык.

                                                                            Из плюсов — легкость добавления новой локали, причем абсолютно любой без изменения структуры БД, моделей и прочего. Также легко наращивать функционал системы: хотит добавить ID переводчика, местные наречия, склонения — чуть изменить XML Schema и добавить новый аттрибут.

                                                                            Из минусов — этот способ годится лишь для небольших текстов, большой объем данных существенно снизит скорость обработки. Объем данных, скорость выборки в этом случае также отходят на второй план.
                                                                              0
                                                                              Вот и еще одинн вариант нарисовался. Он имеет право на жизнь. Но как вы сказали этот способ для небольших текстов.

                                                                              По сути, я так понял, что вы в одно поле таблицы записываете все переводы по данному тексту, и компонуете их с помощью xml. Если бы я так делал, то делал используя сериализацию массива.
                                                                              –2
                                                                              первый вариант работает быстрее всех и он проще всех => я за первый вариант
                                                                                0
                                                                                Вы представляете себе таблицу, в которой по 7-8 полей на каждый язык и штук 6 языков?
                                                                                  0
                                                                                  56 нужных столбцов… :)
                                                                                    –1
                                                                                    Максимальное число колонок в MySQL таблице, кажется, 2599. Так в чём проблема? Если 6 языков, то у вас будет возожность сделать для этой сущности 433 атрибута на всех языках…

                                                                                    Ну, и разумеется, делать SELECT * из таблицы не надо. Впрочем, * и так использовать не надо — зачем дёргать лишнее…

                                                                                    В принципе, ещё один вариант проще в некотором смысле, но помедленнее — хранить как обычно, упаковывать данные в xml. То есть в каждом поле таблицы, нуждающемся в переводе, храним xml, содержащий различные варианты перевода этого поля. Получаем поле как обычно, потом парсим. Если языков на сайте много, а хранимой в каждом поле информации много, то такой вариант, конечно, не очень эффективный… Однако если надо добавить поддержку многоязычности в существующую систему — это будет сделать довольно просто, ведь не придётся менять структуру базы.
                                                                                  0
                                                                                  Я так понимаю, основные концепции баз данных вы не изучали?
                                                                                  0
                                                                                  Имел дело только в первой реализацией.
                                                                                  На 4 языка мне этого варианта хватило, ну и с четом того, что все это работало на MODx CMS, других вариантов особо и не было. Все поля редактируются на одной странице.
                                                                                  Выглядит это примерно так:
                                                                                    0
                                                                                    У меня это выглядит примерно так. На каждой странице есть такие флажки, а так как у меня 4й вариант переключиться на соседний язык очень просто.


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

                                                                                    Так что теперь я склоняюсь все-таки к 3-ему варианту.
                                                                                    + полная свобода, что хочешь, то и переводи на любые языки
                                                                                    + скорость работы
                                                                                    + простота (KISS!)

                                                                                    Одинаковые поля при редактировании придется дублировать ручками. тут можно придумать что-нибудь с копированием.

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

                                                                                    В общем одним решением не накрыть все возможные варианты.
                                                                                      0
                                                                                      Если будем дублировать, столкнемся с ненормализированной базой… Но это не критично, мне кажется, при относительно небольшом объеме базы.

                                                                                      А 4-й вариант необязательно должен зависеть от языка оригинала. Просто за основу нужно брать ранее добавленный экземпляр страницы.
                                                                                      Спасибо за скрин!
                                                                                      0
                                                                                      хм, а мне нравится комбинированный подход в симфонии:
                                                                                      www.symfony-project.org/jobeet/1_4/Doctrine/ru/19
                                                                                      там есть специальный оператор $__ для шаблонов, который позволяет получать подстановку значений из xml, а так же мультиязычные поля в таблице реализованы связью id ->таблица значений для поля(значение, язык). Кеширование тоже прикручивается на ура.
                                                                                        0
                                                                                        Я конечно понимаю, что в этом посте рассматривается back-end реализация универсального механизма перевода контента сайта, но вот есть ленивое решение на js — translateth.is
                                                                                          0
                                                                                          Очень ленивое… :) Но работает.
                                                                                        • UFO just landed and posted this here
                                                                                            +1
                                                                                            В моем самопальном CMF (кстати, очень похожем на описанный в статье) я сделал сначала вариант без мультиязычности, затем переписал на мультиязыковой (2 таблицы по версии 4).

                                                                                            Потом понял, что универсальность — не всегда удобно, а мультиязычность в таком виде требуется очень редко. Поэтому временно отвлекся от CMF/CMS и занялся более решением более практичных задач.
                                                                                              0
                                                                                              По-хорошему, фреймфорк должен позволить быстро сделать любую модель мультиязычной без изменения ее API. А в «базовой» поставке модели должны быть не мультиязычны — именно потому, что чаще мультиязычность не нужна.
                                                                                                0
                                                                                                Ну у меня на данном этапе базовая поставка так и организована без мультиязычности. Сейчас я хочу, чтоб когда я описываю параметры поля модели что-то вроде «i18n=true» у меня автоматически генерировался CRUD и структура базы для данной таблицы.
                                                                                                  0
                                                                                                  myModel:
                                                                                                    ActAs: 
                                                                                                      I18N: 
                                                                                                        fields: [title, description]
                                                                                                  


                                                                                                  :) Попробуйте symfony. У меня есть подозрение, что вы хотите написать тоже самое:)
                                                                                                    0
                                                                                                    Почти так же, но только не YAML, и без указания отдельно полей, которые нуждаются в переводе :)
                                                                                                    0
                                                                                                    Структура таблиц у меня и генерируется автоматически.

                                                                                                    Проставил при создании класса объектов галочку «нужна мультиязычность», отобрал нужные поля (они описаны в отдельной таблице) для основной таблицы и для языковой — обе таблицы созданы.

                                                                                                    Что касается базовой поставки — то интерфейс лучше сразу делать на нескольких языках. Ну и из моделей какую-нибудь сделать мультиязычной, для примера использования.
                                                                                                0
                                                                                                У себя в движке я тоже столкнулся в проблемой переводов. Реализовал ее так.

                                                                                                Можно использовать любое количество языков сколько только захочется. В момент запроса Get(), GetList(), GetRows() все прозрачно само вынимается из базы, если же например страница на французском, но перевода нет — вставляется что будет первым.

                                                                                                У меня таких типов языка 2, строчный и Визивиг редактор.
                                                                                                  0
                                                                                                  Что-то картинка не вставилась, извините: fotky.com.ua/pfiles/23900.jpg
                                                                                                    0
                                                                                                    Ага, спасибо за шот. Натолкнули на интересную мысль.
                                                                                                  0
                                                                                                  Итак, сразу отметим, что 5-й вариант — это «навороченный» 4-й.

                                                                                                  Если хотите универсальности (2 -> 20 -> 50 -> N языков), то 4-й — ваша отправная точка.

                                                                                                  Кроме прочего, нужно обратить внимание на несколько общих моментов, связанных с многоязычностью (если еще не столкнулись — то столкнетесь):
                                                                                                  — Действительно ли нужен «оригинальный язык»? Это очень сильно зависит от того, что за сервис работает на движке. Если это вики или сервис коллективных переводов, то это одно, а если просто наборы страничек (тем более зачастую не связанных) — совсем другое. В последнем случае, это могут быть разные/частично пересекающиеся сайты в зависимости от языковой версии (американцы видят одно, украинцы — другое). Здесь также важны конкретные сценарии работы с проектом — к примеру, если это интернет-магазин, то показывать все товары всем или это не так важно? Личное мнение — видеть контент на чужом языке (кроме, может быть, английского) носителям другого языка в большинстве случаев не имеет смысла.

                                                                                                  — Существуют точки связи переведенного контента и ресурсов (встроенных текстов и картинок). Здесь в самой архитектуре фреймворка хорошо бы предусмотреть «форматтеры», позволяющие отдельные элементы контента и ресурсов представлять в разном виде на разных языках. Например, дата в разных странах отмечается по-разному, или в некоторой фразе могут быть переставлены местами слова, или просто иметь разную форму («1 Page, 2 Pages, ..., N Pages» и «1 Страница, 2 Страницы, ..., 5 Страниц»), и так далее. Немаловажно, что форматирование может требоваться элементам, которые не являются переводимыми (Дата).

                                                                                                  — Нужно учитывать отличия элементов пунктуации и орфографии. (Для всего этого можно хранить в некотором смысле «профайлы» языков).

                                                                                                  — Если проекту требуется большое количество языков, при такой универсальности это неизбежно (и очень сильно) сказывается на производительности, так что минимизируйте обращения к БД. Тема многоязычности (а особенно — много-много-многоязычности) неразрывно связана с memcached (всего и вся, на всех уровнях).

                                                                                                  — Существует ряд сценариев, когда не нужна одновременная поддержка многих языков в рамках одного приложения, а нужна только возможность выбрать язык на этапе развертывания. Это отдельная большая тема, как это можно сделать (например, реальный сценарий таков, что в разных странах в интернет-магазине продаются товары, некоторые из них доступны везде, а многие — уникальны для отдельной страны. Возможно технически хранить данные о товарах магазинов на разных языках вообще в разных базах данных, даже распределенных географически, но в рамках одной компании, и иметь определенный механизм ассоциаций, специальную БД сверки, либо вообще поддерживать независимо, либо как то еще — миллион вариантов).

                                                                                                  — В качестве резюме предыдущего пункта, хочу отметить крайне важное — стопроцентно универсального решения на все случаи жизни, скорее всего нет, у всего есть свои недостатки. Так что, исходите из задачи, не пытайтесь совсем все заранее продумать, пробуйте, экспериментируйте и оценивайте недостатки с практической точки зрения. There is no Silver Bullet!
                                                                                                    0
                                                                                                    Отдельная добавка про «форматтеры» даты.

                                                                                                    Не вздумайте использовать средства форматирования СУБД на основании языка, присущего записи.

                                                                                                    Должен выбираться максимально простой, по возможности однородный и однообразный Result-Set, и кэшироваться, после чего обрабатываться и кэшироваться в отформатированном виде/в виде готовой страницы/готовой части страницы.
                                                                                                    0
                                                                                                    Делали CRM, у заказчика — рекламного агенства — там хранились описания площадок.
                                                                                                    Базовая табличка — русская.
                                                                                                    table_name (id, title, descr, ...)

                                                                                                    Все остальные идут как table_name_en, table_name_de и т.п.
                                                                                                    Главная штука — следить, чтобы id совпадали у карточки площадки и ее же карточек на других языках.

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

                                                                                                    Only users with full accounts can post comments. Log in, please.