Milestone: l10n
Jun. 9th, 2016 01:49 amЯ впервые сделал локализованное (двуязычное) приложение со всем обвесом на платформе CloudWall + jQuery.my. Типа, майлстоун. Покажу черех месяцок вместе с проектом, который сейчас на приложении делается.
К проблеме я присматривался давно, и всё никак не находилось полностью подходящего решения. Я несколько раз попробовал поприкручивать сторонние, плюнул слюной и нагородил своё.
Причин появления велосипедика несколько.
1. «Статическая» локализация это нэ
jQuery.my app, в общем случае, это набор вложенных друг в друга приложений, каждое из которых «сидит» на своём поддереве данных, модифицируя его в зависимости от действий юзера или других событий. Как правило, верхние уровни приложения мало что знают о подробностях работы глубоких уровней, и наоборот. Они могут даже вообще не знать о существовании друг друга.
Более того, при доставке пользователю система может менять код приложения – например, обрезать или подменять ветви, ответственные за недоступный данному конкретному юзеру функционал. Так как приложения – JSON-документы, такие штуки делаются особенно легко и это супер-удобная вообще-то фича платформы.
Естественно, словари локализации должны по умолчанию отрезаться/подменяться вместе с самими компонентами.
2. Полная локализация во время инициализации – это нэ
Иногда приложение вообще при старте не знает, какие внутри него будут исполняться компоненты – горячая замена кода же – но локализация вновь вставленных компонентов должна всё равно работать. Вообще, в идеале, пользователь должен иметь возможность поменять локаль во время работы приложения – и увидеть изменения без рестарта.
3. Тридцать лет и три года
Ну и русский язык же. То-есть, мы можем записать для инглиша "User is {1} year old", например. А в русском, по-хорошему, нам нужно склонять. 15 лет, 21 год, 33 года, вот это всё. Строковым шаблончиком не отделаться никак.
То-есть, у нас должны быть где-то строки, где-то шаблоны (такие, например), а где-то – вовсе функции. Которым надо что-то передавать и не давать повалить/изуродовать приложение, если они поломались.
4. Компоновка
Вообще говоря, локаль должна иметь возможность влиять на компоновку и пропорции интерфейса. То-есть, кнопки «Сохр.» и «Закр.» вместо Save и Close – это хороший пример, когда CSS и локаль живут отдельными независимыми жизнями и между ними бетонная стена. Так быть не должно.
----
В результате, по итогам нескольких итераций в течение двух недель, у меня образовалась идиома из двух строчек кода и очень компактный формат для исходных строк/шаблонов/функций. То-есть, раз получилась идиома и формат более-менее приколотился, надо это всё вносить в платформу.
Это вторая фича, которая войдёт в jQuery.my 1.3 как новая. А первая – поддержка ARIA-кодов, да.
К проблеме я присматривался давно, и всё никак не находилось полностью подходящего решения. Я несколько раз попробовал поприкручивать сторонние, плюнул слюной и нагородил своё.
Причин появления велосипедика несколько.
1. «Статическая» локализация это нэ
jQuery.my app, в общем случае, это набор вложенных друг в друга приложений, каждое из которых «сидит» на своём поддереве данных, модифицируя его в зависимости от действий юзера или других событий. Как правило, верхние уровни приложения мало что знают о подробностях работы глубоких уровней, и наоборот. Они могут даже вообще не знать о существовании друг друга.
Более того, при доставке пользователю система может менять код приложения – например, обрезать или подменять ветви, ответственные за недоступный данному конкретному юзеру функционал. Так как приложения – JSON-документы, такие штуки делаются особенно легко и это супер-удобная вообще-то фича платформы.
Естественно, словари локализации должны по умолчанию отрезаться/подменяться вместе с самими компонентами.
2. Полная локализация во время инициализации – это нэ
Иногда приложение вообще при старте не знает, какие внутри него будут исполняться компоненты – горячая замена кода же – но локализация вновь вставленных компонентов должна всё равно работать. Вообще, в идеале, пользователь должен иметь возможность поменять локаль во время работы приложения – и увидеть изменения без рестарта.
3. Тридцать лет и три года
Ну и русский язык же. То-есть, мы можем записать для инглиша "User is {1} year old", например. А в русском, по-хорошему, нам нужно склонять. 15 лет, 21 год, 33 года, вот это всё. Строковым шаблончиком не отделаться никак.
То-есть, у нас должны быть где-то строки, где-то шаблоны (такие, например), а где-то – вовсе функции. Которым надо что-то передавать и не давать повалить/изуродовать приложение, если они поломались.
4. Компоновка
Вообще говоря, локаль должна иметь возможность влиять на компоновку и пропорции интерфейса. То-есть, кнопки «Сохр.» и «Закр.» вместо Save и Close – это хороший пример, когда CSS и локаль живут отдельными независимыми жизнями и между ними бетонная стена. Так быть не должно.
----
В результате, по итогам нескольких итераций в течение двух недель, у меня образовалась идиома из двух строчек кода и очень компактный формат для исходных строк/шаблонов/функций. То-есть, раз получилась идиома и формат более-менее приколотился, надо это всё вносить в платформу.
Это вторая фича, которая войдёт в jQuery.my 1.3 как новая. А первая – поддержка ARIA-кодов, да.
no subject
Date: 2016-06-09 09:27 am (UTC)no subject
Date: 2016-06-09 10:13 am (UTC)Приложение, когда обращается к словарю, не знает, что там по ключу лежит – функция или строка. Пусть всегда думает, что строка.
В случае с примером, нам надо чтобы внутри приложения this.Lang.USER_AGE.assign(age) работал правильно вне зависимости от того, USER_AGE у нас строка шаблона или функция.
Это несложно сделать – надо просто обходить словарь при старте/переключении локали, и если какая-то ветка – функция, то:
а) переопределять у неё toString(), чтобы она при взятии в выражении без скобок вызывала себя со скобками
б) приделывать к ней метод assign , который будет вызывать её саму с переданными в assign параметрами.
Ну и ещё несколько похожих штришков.
Итого у тебя словарь выглядит так:
Lang:{
en:{ USER_AGE:"User is {1} year old" },
ru: { USER_AGE:function(age){ /* returns phrase */}}
}
no subject
Date: 2016-06-09 11:36 am (UTC)no subject
Date: 2016-06-10 01:21 am (UTC)Я есчо неверно первый раз пример в комменте написал.
Так что, скорее, «некоторые ветки словаря могут быть функциями, если их не реализовать просто шаблоном, но эти функции ведут себя в некотором смысле как строки».
no subject
Date: 2016-06-10 08:36 am (UTC)no subject
Date: 2016-06-10 09:04 am (UTC)Не очень понимаю, что значит «статическими», но прокомментирую, как понял.
$.my-приложение в транспортном формате – это json, в котором некоторые ветки – строковые представления функций (или регэкспов).
"function (x) { return \"a\"+x;}" – типа такого.
Так как у нас функции без сайд-эффектов, это прекрасно работает.
С учётом того, что функции вызываются в контексте самого приложения – то-есть почти всегда видят js-дерево через this – функции и с сайд-эффектами могут быть, но эти сайд-эффекты распространяются только в контексте экземпляра приложения.
То-есть у нас нет какого-то специального куска кода, внутри которого эти функции надо описывать.
Так весь $.my работает, и именно поэтому любое приложение можно вложить в любое другое с практически нулевым усилием на монтаж. Даже в само себя можно вложить.
В случае с локализацией я просто к этим функциям без сайд-эффектов (которые поэтому не обязаны быть «динамическими») прикручиваю во время переключения всякие хаки, вот и всё.
no subject
Date: 2016-06-10 10:39 am (UTC)no subject
Date: 2016-06-11 12:32 am (UTC)Ты же couchbase администрил, как же это «не запихнёшь»? Берёшь себе Spidermonkey – и запихиваешь. Или Lua вот есть.
no subject
Date: 2016-06-09 11:21 am (UTC)no subject
Date: 2017-05-30 06:43 am (UTC)