Pull to refresh

Comments 34

По поводу текста в ресурсных файлах, это зависит от того что именно мы там храним, если это переводы для полей форм, различных ошибок (то есть впринципе они не меняются) то это файлы однозначно. Если же храним перевод контента, то тут без баз никак.
Использую схему больше всего похожую на таблицу локализации, но для каждого языка (вернее локали) своя таблица. То есть не EntityTranslate, а EntityRuRu, EntityEnUs и т. д.

Статический текст обычно в исполняемых файлах, но обращение идёт через функцию/хелпер типа _() и способ хранения может быть легко заменен на любой другой.
Используем gettext, на каждую локаль — свой файл с терминами. При загрузке приложения вся локализация кешируется.
Самый «ровный» вариант — это, конечно, второй. Но при наличии в приложении большого числа вложенных связей между таблицами, получаем кучу зубодробительных SQL запросов, которые мало того, что тормозят, так еще и сильно усложняют настройку кэша. Задача локализации контента пользователей до сих пор остается одной из самых неприятных (по крайней мере в случае использования реляционной БД.)
Методом проб и ошибок лично я пришел к решению в виде нескольких БД. К примеру: одна для русского, другая — для английского + простенький скрипт для синхронизаций изменений главных настроек сайта. Материалы тоже синхронизируются при создании, т.е. мы создаем материал в одной версии, она дублируется в другую, но в статусе «не опубликовано». Переключать БД при запросах в очень просто, у меня это базовая функция в фреймворке.
Плюсы: отдельные данные для разных версий сайта в виде нескольких БД. Возможность вести не совпадающие по контенту версии сайта (в русском статья есть, в англ. не нужна, к примеру), использовать разные настройки для сайтов. На все версии сайта у нас один код фреймворка и вообще никаких хаков, вся реализация не более 100 строк. Никаких сложных связей и запросов к БД
Как по мне — очень спорный вариант. Для поддержаия целостности данных затрачивается много ресурсов. Однако, как мне кажется такой вариант может быть реализован в очень крупном проекте, но там это будет уже скорее шагом к масштабированию, нежели к локализации.
Хабр такой хабр… Заминусовали комментарий по делу, предлагающий вполне жизненную альтернативу (да, спорную, но действенную) — зато стабильно плюсуют комментарии типа «Nokia RIP» в статье о покупке нокии мелкософтом… Как будто у нас тут камеди клаб какой-нибудь.
Используем 2й и 4й.
2й вариант для таблиц, активно учавствующих в бизнес логике, где нельзя держать несколько одинаковых записей рядом.
4й вариант больше для контента (новости, статические страницы, статьи).
Видел еще реализации с несколькими таблицами (news_ru, news_en)

Начинали с gettext, но потом ушли все же в базу (больше тысячи ключей).
Я предпочитаю об этом не думать и использовать стандартные средства, предоставляемые фреймворком. Если вариантов больше одного, то выбираю наиболее популярный (читай: активно поддерживаемый, стабильный для данной версии, с минимальным количеством негативных отзывов и т.д.) А как там оно решается внутри — уже не моё дело. В большинстве случаев считаю такой подход самым верным. Ну а если это меганагрузка, много данных и всё такое, то там уже можно и подумать, потестировать, но, думается мне, в большинстве случаев это будет не переделывание, а оптимизация уже существующего решения.
Вы, очевидно, политик
Проблема действительно достаточно нетривиальная.
У одного проекта (достаточно сложная CRM) долго думали и пробовали делать разные варианты.
В том числе и с хранением сериализованных данных и массивов (Postgre позволяет).

Остановились на 2-м варианте. В результате после работы и саппорта в течении несольких лет вывод был неутишителен — такой подход, хоть и гибок, очень сильно осложняет работу по бизнес-логике. Т.е. даже простые запросы превращаются в жутких монстров, разобраться потом с которыми дикая мука + есессно тормозит.

Для себя решили, что гораздо проще и приятнее использовать вариант1, даже не смотря на его ограничения.
Ибо при многоязычности практически всегда все сущности должны быть переведены и присутствовать в базе + есть отличная возможность сделать запрос типа «select a_defLng, a_ru, b_defLng, b_ru» и при незаполненных a_ru, брать a_defLng — достаточно удобно, ричем это даже можно делать на стороне SQL запроса.

Кроме того, недостаток про «изменения БД при добавлении нового языка» не такой уж страшный ИМХО — ибо БД все равно меняется в процессе эволюции ПО, да и отнести этот недостаток нужно больше к проблеме архитектинга.

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

Некоторое время писал на C++ для Windows, для себя создал приложение которое работает с .ini файлами, в которых хранятся статические переводы (в формате key=value). При этом количество языков — неограниченно.
А что по поводу стандартных *.po локализаций? В том же втором zend frameworke работают, так сказать «на ура». В файл сериализуются значения по столбцам «английскаяверсия-китайскаяверсия», фишка в том, что если язык приложения изначально английский, то код транслятора даже не запускается, если же нужно показать вид на китайском, то уже вызываем транслятор
Это ресурсные файлы. Для интерфейса нормально, для, например, записей в блогах — очень накладно.
Для перевода использую yandex.translate.api, кэш языковых данных хранится в /lang/md5(исходный_текст+язык_на_который_переводим).lang, если такой файл есть то никуда не лезем за переводом, и всегда можно вызвать редактор перевода на сайте и откорректировать автоматический перевод, оптимальнее варианта мультиязычности я еще не видел
Тоесть, наример для портала у которого есть скажем 100 публикаций — оригиалы хранятся в БД, 20 ручных переводов хранятся в файлах, для остальных 80 в случае обращения переводы гененируются автоматически и также сохраняются в файлы?
Нет ли у вас проблем с контролем целостности данных при таком подходе?
есть 100 статей, при первом просмотре сайта на языке отличного от исходного делаем переводы через yandex.translate.api всей информации и кэшируем, в дальнейшем берем данные из кэша, если что то в переводе не устраивает то есть интерфейс правки любой текстовой информации на странице, то есть правим не исходный текст в базе а содержимое кэш-файла, md5(исходный_текст+язык_на_который_переводим).lang
Тоже достаточно плотно занимался этой проблемой, особенно для заранее неопределенного количества поддерживаемых языков.

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

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

P.S. Почему везде В и С? Зачем использовать по два локализованных поля? Не понял смысл…
B и С только для примера. В таблице может быть много полей требующих перевода на разные языки. Напрмиер — Title, Description, Content и так далее.
Я так понимаю в вашем случае речь идет только о переводе интерфейса? Данные не переводятся? Или просто нет необходимости одну и ту же сущность отображать на разных языках?
Сложно, кстати, разобраться в схемах из-за это. Однобуквенные переменные зло :)
Сначала как раз были вариант Field1, Field2 и Title, Description но потом подумал, что так будет лаконичнее. В реальной практике же поля в БД так никогда не заываю )
Используем схему схожую со 2 вариантом, только таблица с переводами 1, в ней хранятся записи для всех сущностей, в формате сущность-id-свойство-язык-значение и при приходе запроса строятся временные таблицы одинаковой с основной структуры (из raw_topics на русском -> topics на нужном языке), но с переведенными значениями (кол-во таблиц и записей в них не огромно). Так было удобно локализовать уже готовую развитую систему, плюс нет необходимости в жестком наличии перевода, все будет работать, хотя запись будет не переведена. При этом переводы для шаблонов хранятся в файлах и используется gettext.
Выборка перевода идет не по ключу, а по like текстового поля, я првильно понял?
по ключу, таблица+поле+id_записи+язык (типа topics+name+11+en)
Реализовывал подобную схему, только без временных таблиц — просто выборка данных из основной таблицы и потом замена текстовых полей данными из таблицы переводов.
Дополнительный плюс — не нужно модифицировать структуру БД в случае появления нового языка/сущности/колонки — достаточно просто обновить конфиг.
Из минусов — для больших таблиц решение «в лоб» вряд ли подойдет, нужно отдельно думать над вопросом производительности.
Относительно недавно был пост Локализация ASP.NET MVC приложений на эту тему. Мы используем свой фрэймворк для поддержки мультиязычности, ничего не храним в ресурсах, а все переводы добавляются прямо у нас на сайте с помощью редактора доступного для переводчиков.
Для статических переводов пока ничего лучше, чем gettext не придумали (не забываем про plural forms например).

С переводами контента пока лично не сталкивался, трудно что-то предложить. Но думаю какая бы схема БД не была задействована, есть смысл спрятать реализацию за абстракцией в ORM.
Локализация интерфейса — Get text
Локализация контента — метка языка в табличке в БД
Перепробовав много разных вариантов, пришел к тому, что самый простой в поддержке и реализации вариант — использование PgSQL с типом данных hstore для переводимых полей. В hstore можно хранить полноценный хеш переводов (en => name, ru => имя, ...), а не просто сериализованный массив, хранящийся как строка. При этом средствами SQL можно делать практически любые запросы, в том числе на наличие/отсутствие нужного перевода.
Добавлю ещё: можно на значения ключей повесить индексы, сделать полнотекстовый поиск опять же.
А так — лучше получается вариант 3а: в таблице Entity отдельные hstore поля для каждого языка (например, ruLang, enLang), а в этих полях: ключ — имя поля таблицы (например, name, title, description), значение — перевод. И это очень удобно использовать в объектной модели и в коде (например есть возможность переопределить стандартные поля у объектов переводами из полей).
Вариант 1.

Преимущество относительно Варианта 2 — скорость выборки данных по нескольким языкам, так как избегаем перекрестных запросов.

Гибкость реализуется с помощью грамотного интерфейса управления языками на сайте. При добавлении/удалении нового языка на сайте система знает какие поля необходимо добавить/удалить в таблицах.
Про локализацию БД пропущен еще один способ: когда таблица локализации Translation одна и та же для всех сущностей базы Entity1, Entity2 и т.д., и либо Entity на нее ссылается с ограничением 1:1 и BEFORE DELETE удаляющим триггером, либо же она ссылается на первичный ключ Entiry (но тогда средствами СУБД внешний ключ не создать, нужно делать на триггерах проверку, плюс надо обеспечить глобально-уникальные ID во всех табличах Entiry1, Entity2 и т.д.

А про локализацию статики пропущено деление на 2 подвида: когда ключом строки выступает сама строка на базовом языке (идеология gettext), и когда ключ языковой строки — синтетический (типа index_greeting, enter_your_email и т.д.). Второй способ по моему опыту оказывается удобнее gettext, потому что исчезает базовый язык, исправление опечатки в котором приводит к проблемам в остальных локализациях (плюс можно делать легко веб-интерфейс для realtime-перевода или правки текстов на сайте).
Sign up to leave a comment.

Articles