Появилась необходимость реализовать многоязыковую поддержку интерфейса и контента на сайте. Меня интересует как в таком случае спроектировать базу. Есть таблица Articles (Id, AddedByUser, AddedDate, Title, Description, Body, ViewCount).
Для поддержки многих языков есть 2 варианта
1. Articles (Id, AddedByUser, AddedDate, TitleRU, TitleEN, DescriptionRU, DescriptionEN, BodyRU, BodyEN, ViewCount).
Здравствуйте, Darooma, Вы писали:
D>Первый вариант проще, второй — гибче. Какой чаще используется? Какой лучше?
Второй вариант и проще и лучше
В случае первого варианта, что вы будете делать, если у вас будет 10 языков? А 100? А если новый язык смогут выбирать пользователи?
Здравствуйте, Darooma, Вы писали:
D>Первый вариант проще, второй — гибче. Какой чаще используется? Какой лучше?
скажу банальность, но всё зависит от требований. второй вариант я пробовал — опыт не очень положительный. по-любому на каком-то этапе приходится приводить данные к "плоскому" виду (т.е. к первому варианту), поскольку имеющиеся средства (работа с данными, элементы пользовательского интерфейса) не умеют работать со вторым вариантом. получается, что уж если вводить гибкость, то надо это делать во всём приложении разом (на всех уровнях), а это дорого.
второй вариант я бы рассматривал только если языков одновременно много — например, какие-то словари с вариантами на сорока языках. тогда весь UI сам по себе (т.е. бизнес-процессы так будут построены) будет кастомизирован под работу с определёнными сущностями, которые имеют многоязыковую основу.
если же языков немного (например, три — пусть даже всегда разных) и по сути все варианты всегда ходят вместе, то проще, на мой взгляд, засунуть всё в одну таблицу.
т.е. первый вариант я бы рассматривал по умолчанию, а второй для каких-то отдельных случаев в духе CMS (где так и так работа в основном ведётся на уровне знаний — метауровне).
Есть ещё один вариант — ввести столбец Language и хранить сущности для разных языков в разных строках. Выборку фильтровать по полю Language. По быстродействию и памяти — самый лучший вариант.
Здравствуйте, Makc2, Вы писали:
M>Есть ещё один вариант — ввести столбец Language и хранить сущности для разных языков в разных строках. Выборку фильтровать по полю Language. По быстродействию и памяти — самый лучший вариант.
Делали так реальную промышленную БД — заколебались идентификаторы записей выправлять (постоянно не на тот язык кто-нибудь ссылался) и записи синхронизировать (нередко получался какой-нибудь идентификатор в одном языке один, а в другом другой). Это не так и сложно, если база мелкая. А если большая, и делает несколько человек, то замучаешься.
Сейчас я сделал так: каждая таблица всего в одном экземпляре, но есть отдельная таблица с "переводами" строк. То есть там хранится идентификатор записи , идентификатор колонки, язык, текст на этом языке. От решения кайфую — за год еще ни разу не пожалел
Здравствуйте, Darooma, Вы писали:
D>Появилась необходимость реализовать многоязыковую поддержку интерфейса и контента на сайте. Меня интересует как в таком случае спроектировать базу. Есть таблица Articles (Id, AddedByUser, AddedDate, Title, Description, Body, ViewCount).
D>Для поддержки многих языков есть 2 варианта D>1. Articles (Id, AddedByUser, AddedDate, TitleRU, TitleEN, DescriptionRU, DescriptionEN, BodyRU, BodyEN, ViewCount).
D>2. Articles (Id, AddedByUser, AddedDate, ViewCount). D>ArticlesLocalized(Id, ArticleId, Title, Description, Body, LanguageId) D>Languages(Id, Name)
D>Первый вариант проще, второй — гибче. Какой чаще используется? Какой лучше?
Второй.
Не надо в ArticlesLocalized делать ID, пусть первичный ключ будет парой по внешними ключами на (lang, articleid). Кроме того ключ language надо сделать ISO идентификатором локали вроде ru-RU или en-US.
Кроме того надо продумать систему fallback если конетнт в нужном языке не найден, лучше всего редирект + вывод сообщения, но это уже не касается базы данных.
G>Кроме того надо продумать систему fallback если конетнт в нужном языке не найден, лучше всего редирект + вывод сообщения, но это уже не касается базы данных.
Что за система такая?
Здравствуйте, Darooma, Вы писали:
D>Здравствуйте, gandjustas, Вы писали:
G>>Кроме того надо продумать систему fallback если конетнт в нужном языке не найден, лучше всего редирект + вывод сообщения, но это уже не касается базы данных. D>Что за система такая?
смотри, ты заходишь по ссылке /ru-ru/somecontent , но контент этот доступен только на английском яызке. С точки зрения поиска не стоит отдавать на странице /ru-ru/somecontent на анлгийском, но и перекидывать пользователя молча тоже не стоит.
Лучший вариант — сделать temporary redirect + использовать куки для передачи сообщения вроде "страница на выбраном языке не найдена, предлагаем вам посмотреть на английском"
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Darooma, Вы писали:
D>>Здравствуйте, gandjustas, Вы писали:
G>>>Кроме того надо продумать систему fallback если конетнт в нужном языке не найден, лучше всего редирект + вывод сообщения, но это уже не касается базы данных. D>>Что за система такая?
G>смотри, ты заходишь по ссылке /ru-ru/somecontent , но контент этот доступен только на английском яызке. С точки зрения поиска не стоит отдавать на странице /ru-ru/somecontent на анлгийском, но и перекидывать пользователя молча тоже не стоит.
Это понятно.
G>Лучший вариант — сделать temporary redirect + использовать куки для передачи сообщения вроде "страница на выбраном языке не найдена, предлагаем вам посмотреть на английском"
Что такое "temporary redirect"?
Как "использовать куки для передачи сообщения"?
Что если просто вывести ссылку, что "страница на выбраном языке не найдена, предлагаем вам посмотреть на английском"?
И как пользователь вообще может попасть на такую страницу, если для текущего языка отдаются только статьи, которые написаны именно на этом языке. Например, если 3 ссылки: /en/article/34, /en/article/35 и /ru/article/88.
Как пользователь попадёт на /ru/article/34, кроме как если он вручную изменит url в адресной строке с en на ru? Стоит ли выводить сообщение в таком случае, ведь это уже его проблемы, ведь он сам поменял url? Я думаю, просто написать, что статья не найдена.
Здравствуйте, Darooma, Вы писали:
D>Здравствуйте, gandjustas, Вы писали:
G>>Здравствуйте, Darooma, Вы писали:
D>>>Здравствуйте, gandjustas, Вы писали:
G>>>>Кроме того надо продумать систему fallback если конетнт в нужном языке не найден, лучше всего редирект + вывод сообщения, но это уже не касается базы данных. D>>>Что за система такая?
G>>смотри, ты заходишь по ссылке /ru-ru/somecontent , но контент этот доступен только на английском яызке. С точки зрения поиска не стоит отдавать на странице /ru-ru/somecontent на анлгийском, но и перекидывать пользователя молча тоже не стоит.
D>Это понятно.
G>>Лучший вариант — сделать temporary redirect + использовать куки для передачи сообщения вроде "страница на выбраном языке не найдена, предлагаем вам посмотреть на английском" D>Что такое "temporary redirect"?
Обычный Respone.Redirect
D>Как "использовать куки для передачи сообщения"?
В куку помещаешь сообщение что контент не найден и url страницы и шифруешь это все. Делаешь редирект. Целевая страница берет куку, выводит сообщение, удаляет куку.
D>Что если просто вывести ссылку, что "страница на выбраном языке не найдена, предлагаем вам посмотреть на английском"?
D>И как пользователь вообще может попасть на такую страницу, если для текущего языка отдаются только статьи, которые написаны именно на этом языке. Например, если 3 ссылки: /en/article/34, /en/article/35 и /ru/article/88. D>Как пользователь попадёт на /ru/article/34, кроме как если он вручную изменит url в адресной строке с en на ru? Стоит ли выводить сообщение в таком случае, ведь это уже его проблемы, ведь он сам поменял url? Я думаю, просто написать, что статья не найдена.
1) Банально поправит руками в браузере.
2) сохраит ссылку, а перевод впоследствии будет удалена
просто писать "не найдена" не самый лучший вариант
А у меня отдельная таблица с переводами — это таблица связей сущностей на разных языках. Идентификаторы сущностей на разных языках разные, то есть первичный ключ — это просто идентификатор сущности, а не идентификатор плюс язык. Если вдруг пользователь на странице сущности поменяет язык, то происходит клиентский редирект на ту же страницу, но с идентификатором той связанной через таблицу переводов сущности, которая соответствует выбранному языку. Проблем ни разу не возникало. Отдельная таблица ресурсов мне кажется не оптимальным решением, так как она большая и требует отдельного запроса к базе.
Здравствуйте, Makc2, Вы писали:
M>А у меня отдельная таблица с переводами — это таблица связей сущностей на разных языках. Идентификаторы сущностей на разных языках разные, то есть первичный ключ — это просто идентификатор сущности, а не идентификатор плюс язык. Если вдруг пользователь на странице сущности поменяет язык, то происходит клиентский редирект на ту же страницу, но с идентификатором той связанной через таблицу переводов сущности, которая соответствует выбранному языку. Проблем ни разу не возникало. Отдельная таблица ресурсов мне кажется не оптимальным решением, так как она большая и требует отдельного запроса к базе.
Я и не говорю, что решение оптимальное. Оно простое и достаточно эффективное для некоторых случаев. Надо исходить из реальных задач, а в моем случае реальность такова, что перевод делается редко, а значит и таблица небольшая. Собственно, даже если бы она и была большой, то ничего страшного — индекс по идентификатору строки, на которую сделан перевод, очень селективный. Отдельного запроса к базе для перевода, конечно же, не надо: достаточно написать функцию получения перевода и вызывать ее прямо в запросе. Да, это еще одна выборка, но она не требует перехода с клиента на сервер и выполняется очень быстро.
G>просто писать "не найдена" не самый лучший вариант
У меня на уровне доступа к данным (DAL) генерится исключение HttpNotFoundException 404. Теперь, получается, прежде чем генерить исключение, нужно проверить наличиес статей на остальных языках, и если есть хоть одна на другом языке, то генерить не HttpNotFoundException, а своё собственное, например, ArticleOnCurrentLanguageNotFoundException. Так получается?
Здравствуйте, Darooma, Вы писали:
D>Здравствуйте, gandjustas, Вы писали:
G>>просто писать "не найдена" не самый лучший вариант
D>У меня на уровне доступа к данным (DAL) генерится исключение HttpNotFoundException 404.
оО, круто.
D>Теперь, получается, прежде чем генерить исключение, нужно проверить наличиес статей на остальных языках, и если есть хоть одна на другом языке, то генерить не HttpNotFoundException, а своё собственное, например, ArticleOnCurrentLanguageNotFoundException. Так получается?
Да
Помимо замедления выборки наличие таблицы ресурсов затрудняет внедрение ОРМ в слой доступа к данным. Да, можно воспользоваться мэппингом на хранимые процедуры. То есть провести лишнюю, совсем необязательную с точки зрения бизнес-логики работу. Напротив, отсутсвие лишней таблицы ресурсов, делает мэппинг тривиальным.
Здравствуйте, Makc2, Вы писали:
M>Помимо замедления выборки наличие таблицы ресурсов затрудняет внедрение ОРМ в слой доступа к данным. Да, можно воспользоваться мэппингом на хранимые процедуры. То есть провести лишнюю, совсем необязательную с точки зрения бизнес-логики работу. Напротив, отсутсвие лишней таблицы ресурсов, делает мэппинг тривиальным.
Вот тут можно поподробнее? У меня небольшой пробел в образовании в плане ORMов.
Вот максимально упрощенный пример.
Пусть есть таблица справочных данных REFS с полями: ID — первичный ключ и V_NAME — наименование на языке по умолчанию (например, английский).
А также таблица переводов TRANSLATIONS с полями: ID — первичный ключ, I_ROW_ID — ключ записи в таблице REFS, для которой перевод, V_NAME — строка перевода, C_LANG — язык.
Тогда если выборка обычных данных в системе без поддержки нескольких языков выглядит так:
SELECT ID, V_NAME FROM REFS
то выборка многоязычных данных на каком-то одном языке (здесь-на русском) будет выглядеть так:
SELECT ID, TRANSLATE_FUNC(V_NAME,ID,'ru') V_NAME FROM REFS
При этом принимаем во внимание, что в абсолютном большинстве случаев отображаемые строки используются только для показа пользователю, и самой программой никак особенно не обрабатываются. Кроме того, ситуация, когда надо не просто показать элемент справочника, а выбрать разом все его названия на всех языках мне лично ни разу не встречалась (за исключением случаев поиска по всем языкам сразу, но они тривиально и очень эффективно реализуются соответствующими SQL запросами). То есть я исхожу из того, что строку просто выбрали и показали, никаких исхитрений с кучей языков не производится.
С учетом этого вопрос: как разница между этими двумя запросами помешает работать с ORMом? Или вопрос не в невозможности реализации, а в некоей "концептуальной некорректности"?
Re[7]: Проектирование бд для многоязыкового сайта
От:
Аноним
Дата:
22.11.11 16:20
Оценка:
Например, в EntityFramework процесс создания классов сущностей сводится к выбору из базы данных нужных таблиц и нажатию на кнопку Создать. После этого можно сразу получать и изменять данные в таблицах сущностей без применения SQL. Генерятся сущности так, что каждой таблице соответствует одна сущность. В Вашем случае сгенерится сущность REFS со свойствами ID и V_NAME. ОРМ даже не подозревает о том, что ему нужно будет генерить запросы на выборку поля V_NAME с использованием TRANSLATE_FUNC. Следовательно, Вам нужно будет создавать хранимую процедуру и вручную мэппить её выходные параметры на свойства класса. Ну и, разумеется, Вам понадобится хранимая процедура на вставку и изменения данных с учётом языка. В общем, геморроя больше, чем радости.
Здравствуйте, gandjustas, Вы писали:
D>>Теперь, получается, прежде чем генерить исключение, нужно проверить наличиес статей на остальных языках, и если есть хоть одна на другом языке, то генерить не HttpNotFoundException, а своё собственное, например, ArticleOnCurrentLanguageNotFoundException. Так получается? G>Да
Я создал класс, унаследованный от HttpException — HttpArticleTranslationNotFound. Этот exception обрабатывается на уровне UI.
Как теперь передать на уровень UI ID-шники тех статей, которые найдены на других языках? То есть, как из уровня DAL передать в уровень UI пользовательские данные (ID статей на других языках) через exception?
Здравствуйте, Darooma, Вы писали:
D>Здравствуйте, gandjustas, Вы писали:
D>>>Теперь, получается, прежде чем генерить исключение, нужно проверить наличиес статей на остальных языках, и если есть хоть одна на другом языке, то генерить не HttpNotFoundException, а своё собственное, например, ArticleOnCurrentLanguageNotFoundException. Так получается? G>>Да
D>Я создал класс, унаследованный от HttpException — HttpArticleTranslationNotFound. Этот exception обрабатывается на уровне UI.
D>Как теперь передать на уровень UI ID-шники тех статей, которые найдены на других языках? То есть, как из уровня DAL передать в уровень UI пользовательские данные (ID статей на других языках) через exception?
Не стоит использовать Exception_ы для передачи управления.
Re[10]: Проектирование бд для многоязыкового сайта
Здравствуйте, gandjustas, Вы писали:
G>Не стоит использовать Exception_ы для передачи управления.
Разве ты не это имел ввиду, когда сказал...
D>>>>Теперь, получается, прежде чем генерить исключение, нужно проверить наличиес статей на остальных языках, и если есть хоть одна на другом языке, то генерить не HttpNotFoundException, а своё собственное, например, ArticleOnCurrentLanguageNotFoundException. Так получается? G>>>Да