
Fluent: локализация UI, практические примеры
Разберём ошибки в локализации AliExpress, LinkedIn и Instagram, и посмотрим, как можно было бы использовать Fluent, чтобы сделать текст в UI красивым и естественным. Эти техники пригодятся не только в многоязычном окружении, но и в приложении с одной локалью. Сначала посмотрим, какие способы используются для форматирования строк с переменными, а потом разберём конкретные кейсы.
Способы форматирования строк с переменными
Конкатенация строк
Самый старый и самый плохой способ - конкатенация нескольких строк в одно предложение, например: userName = Василий userOstavil = оставил commentCount = 15 commentVkonce = комментариев Складываем эти строки, и получаем: Василий оставил 15 комментариев. У этого способа множество проблем, но с точки зрения UX-райтера или локализатора - невозможность поменять последовательность значений без вмешательства разработчика, а так же кривой результат, когда значение переменной выходит за рамки предусмотренного, например: Елена оставил 1 комментариев.
Переменные с фиксированной последовательностью
Чуть более продвинутый способ записи - использование переменных внутри текстовой строки. В самом простом случае все переменные выглядят одинаково, и какую куда вставлять - решает их порядок появления в тексте: comments = %s оставил %s комментариев. Такой подход даёт локализаторам чуть больше свободы действий, но может привести к неприятным ошибкам. Например, нельзя выкинуть переменную, или поменять их местами (очень важно при переводе на другой язык, когда грамматика диктует другую последовательность). Кроме того, без контекста порой невозможно понять, что означает %s - и может привести к некорректным переводам.
Уникальные переменные
Наиболее распространённый способ генерации предложений: comments = {$userName} оставил {$commentsNumber} комментариев. Здесь уже можно менять переменные местами, и (при нормальном наименовании) они дают представление о финальном результате. Однако во флективных языках вроде русского, где, к примеру, от рода предыдущего слова зависит окончание следующего, сгенерировать правильный текст бывает проблематично - приходится просить разработчика писать разные варианты строк под разные ситуации. Это тратит время разработчика на "костыль" для каждой ситуации, и создаёт множество дублирующих ключей, ведь не во всех языках будет разница между разными родами. Когда разработчик не может помочь, прибегают к самым некрасивым методам, вроде: comments = {$userName} оставил(а) {$commentsNumber} комментарий(ев). Здесь на помощь может прийти проект Fluent от Mozilla, который унифицирует запись подобных строк, и позволяет локализатору (переводчику) добавлять альтернативные варианты отображения текста в зависимости от значения переменной. Я уже публиковал перевод подробной статьи про Fluent на Хабре, и переводил документацию для переводчиков. В этой же статье я хочу показать примеры того, как Fluent мог бы улучшить локализацию таких популярных продуктов, как AliExpress, Instagram и LinkedIn, с конкретными примерами.
AliExpress
Селекторы для числительных

reviews = {$reviewsCount} голоса(ов)) orders = {$ordersCount} заказа(ов) Проблемы очевидны: текст не учитывает написание слов "заказ" и "голос" в зависимости от числительного. Если бы AliExpress использовал синтаксис Fluent, то переводчик мог бы записать текст вот так: reviews = ⠀⠀⠀⠀{$reviewsCount -> ⠀⠀⠀⠀⠀⠀⠀⠀[0] Ни одного голоса ⠀⠀⠀⠀⠀⠀⠀⠀[1] Один голос ⠀⠀⠀⠀⠀⠀⠀⠀[one] {$reviewsCount} голос ⠀⠀⠀⠀⠀⠀⠀⠀[few] {$reviewsCount} голоса ⠀⠀⠀⠀⠀⠀⠀⠀*[many] {$reviewsCount} голосов ⠀⠀⠀⠀} Как это работает?
orders = ⠀⠀⠀⠀{$ordersCount -> ⠀⠀⠀⠀⠀⠀⠀⠀[0] Ни одного заказа ⠀⠀⠀⠀⠀⠀⠀⠀[1] Один заказ ⠀⠀⠀⠀⠀⠀⠀⠀[one] {$ordersCount} заказ ⠀⠀⠀⠀⠀⠀⠀⠀[few] {$ordersCount} заказа ⠀⠀⠀⠀⠀⠀⠀⠀[666] Тысяча чертей и 666 заказов ⠀⠀⠀⠀⠀⠀⠀⠀*[many] {$ordersCount} заказов ⠀⠀⠀⠀} Можете попробовать скопировать этот текст в песочницу Fluent и сами поиграть с текстом и переменными. Значения переменных для песочницы: { "ordersCount": 672, "reviewsCount": 194 }Объединение селекторов
orders-reviews = Отзывы ({$reviewsCount}) | По заказам ({$ordersCount}) 
orders-reviews = ⠀⠀⠀⠀⠀⠀⠀⠀{$reviewsCount -> ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[0] Ни одного голоса ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[1] Один голос ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[one] {$reviewsCount} голос ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[few] {$reviewsCount} голоса ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀*[many] {$reviewsCount} голосов ⠀⠀⠀⠀⠀⠀⠀⠀} по {$ordersCount -> ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[1] одному заказу ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[one] {$ordersCount} заказу ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[few] {$ordersCount} заказам ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀*[many] {$ordersCount} заказам }Форматирование времени
order-time = hh:mm MM. dd yyyy 
order-time = ⠀⠀⠀⠀Время заказа: { DATETIME($date, month: "long", year: "numeric", day: "numeric", weekday: "long") } В качестве переменной времени укажем время в формате Unix Time (конвертер): { "date": 556593884000 } На выходе мы получим: order-time = ⠀⠀⠀⠀Время заказа: вторник, 30 апреля 2019 г. Прелесть в том, что для другого языка - например, английского, - форматирование будет соответствовать принятому там стандарту: order-time = ⠀⠀⠀⠀Время заказа: Tuesday, April 30, 2019LinkedIn, Instagram
Род
Деловая соцсеть LinkedIn прекрасно знает пол каждого пользователя — здесь не принято его скрывать. Тем не менее, в русской локализации она всегда сомневается, используя тяжёлые конструкции: recommendation-date = ⠀⠀⠀⠀{ DATETIME($reviewDate, month: "long", year: "numeric", day: "numeric") }, ⠀⠀⠀⠀{$reviewerFirstName} был(а) {$usersRelationship} участника {$userFirstName} 
{ ⠀⠀⠀⠀"reviewDate": 1555902684000, ⠀⠀⠀⠀"reviewerFirstName": "Kyle", ⠀⠀⠀⠀"reviewerRelationship": "непосредственным менеджером", ⠀⠀⠀⠀"userFirstName": "Vladimir" ⠀⠀⠀⠀} Разработчикам будет не сложно добавить переменную для пола пользователя, которая будет передаваться в строку для форматирования: {"reviewerGender": "male"} И тогда строка в синтаксисе Fluent будет выглядеть вот так: recommendation-date = ⠀⠀⠀⠀{ DATETIME($reviewDate, month: "long", year: "numeric", ⠀⠀⠀⠀day: "numeric") }, {$reviewerFirstName} {$reviewerGender -> ⠀⠀⠀⠀⠀⠀⠀⠀*[male] был ⠀⠀⠀⠀⠀⠀⠀⠀[female] была ⠀⠀⠀⠀} {$usersRelationship} участника {$userFirstName} То же самое происходит в Instagram: 
mention = {$mentionUser} упомянул(-а) вас в комментарии: {$commentText} Конечно, в Instagram, в отличие от LinkedIn, указывать пол не обязательно, поэтому можно добавить третью опцию [other], которая в непонятных случаях будет выдавать соответствующую строку: mention = {$mentionUser} {$mentionUserGender -> ⠀⠀⠀⠀⠀⠀⠀⠀*[male] упомянул ⠀⠀⠀⠀⠀⠀⠀⠀[female] упомянула ⠀⠀⠀⠀⠀⠀⠀⠀[other] упомянул(а) ⠀⠀⠀⠀⠀⠀⠀⠀} вас в комментарии: {$commentText} Например: Василий упомянул вас в комментарии: крутяк! Елена упомянула вас в комментарии: сам дурак :-( Kagino упомянул(а) вас в комментарии: Всем чмоки, болезные Накидывайте больше примеров, задавайте вопросы - будем разбираться вместе.









