Привет! Меня зовут Александр Поляков, я руководитель команды i18n‑разработки в Фантехе Яндекса. Мы помогаем сервисам компании выходить на международные рынки, а именно решаем задачи, связанные с интернационализацией и локализацией интерфейсов.
В этом посте расскажу:
какие именно решения для интернационализации фронтенда мы разрабатываем;
какие есть правила форматирования данных для разных локалей и как определять эти локали;
о проблемах переводов в современных реалиях.
Помогут мне в этом коллеги из фронтенда — Павел Петрухин и Алексей Ершков.
Термины
Начнём с базовых понятий, вокруг которых всё и строится. Итак, есть два главных термина, и это не синонимы — интернационализация и локализация.
Интернационализация — совокупность технологических приемов разработки, которые упрощают адаптацию продукта к языковым и культурным особенностям регионов, отличных от того, в котором разрабатывался продукт. Проще говоря — это проектирование софта с целью максимального упрощения локализации. По-английски internationalization сокращают как i18n, где 18 — это число символов между буквами i и n в этом слове.
Локализация — процесс адаптации ПО к культуре какой-либо страны, например, перевод пользовательского интерфейса. По аналогии с i18n, l10n — сокращение localization, и 10 — это число символов между первым l и последним n. То есть локализация — это не просто перевод «Hello» на «Привет», но и использование местной валюты, десятичных разделителей и работа над другими деталями и элементами. Основная задача l10n — сделать софт более привычным для пользователя, а не просто его перевести.
Computer-Assisted Translation (CAT) tools — программное обеспечение для помощи переводчикам. Эти инструменты предоставляют различные функции, которые упрощают и улучшают процесс перевода текстов с одного языка на другой.
Главная цель нашей команды — сделать сервисы Фантеха доступными и удобными для пользователей во всех странах присутствия, при этом не сильно уронив скорость нашей разработки в РФ.
Ниже мы подробнее расскажем про каждый из пунктов, чтобы сразу было ясно, чем именно и для чего мы занимаемся.
Интернационализация Фантеха
Интерфейс почти всех продуктов Фантеха изначально был реализован на русском языке. Поэтому при выходе любого из сервисов в новую страну нужно оперативно перевести весь интерфейс на соответствующий язык. Для этого в первую очередь нужно интернационализировать свой код и добиться того, чтобы он умел работать с переводами и был готов к l10n. Это достигается путём выбора и внедрения в проект правильных i18n-библиотек, которые умеют работать как с языковыми, так и с культурными особенностями регионов.
Наша команда учит фронтендеров и бэкендеров сразу писать код так, чтобы он становился интернационализированным — был готов к локализации.
Но мало сделать хороший перевод, его нужно ещё и доставить в нужный сервис, не вызывая дополнительных проблем по пути. Поэтому мы создали механизм, который позволяет автоматически забирать у специалистов переводы и переносить их сразу в код сервисов. Параллельно мы разрабатываем сервис переводов, который будет единым решением по синхронизации с CAT tool. С его помощью мы планируем автоматизировать перевод текстов, заведённых менеджерами и редакторами в админках.
Важно было унифицировать на старте сразу несколько вещей:
выработать единый подход к определению локали пользователя;
определиться с универсальным форматом переводов;
зафиксировать одинаковые правила форматирования данных для разных локалей;
создать общую библиотеку для интернационализации;
разработать методологию качественного внедрения переводов;
начать использовать Code First-подход;
выработать единый подход к работе с RTL;
написать инструмент синхронизации переводов с CAT tool.
Единый подход к определению локали пользователя
Локаль — основа интернационализации и локализации. Это набор символов, язык пользователя, страна, часовой пояс и другие установки, необходимые, чтобы интерфейс отображался ожидаемо для пользователя в зависимости от его региона.
Существуют лучшие современные практики кодирования языковых тегов — BCP47, лежащие в основе Intl API (браузерного API для I18n и l10n).
Вы наверняка встречали подобное объявление локалей. Взять хотя бы те же языковые теги: ru для русского языка, en для английского. Рекомендации позволяют ещё и уточнять регионы, например, en-US — это английский язык в США, а he-IL — иврит в Израиле. Ряд языков может быть написан как кириллическими символами, так и латинскими, что тоже можно отразить в теге: сербский язык, sr-Cyrl (сербский кириллический) и sr-Latin (сербский латинский). Кроме этого, в стандарте есть поддержка unicode-расширений, которые позволяют дополнять его.
Очень важно корректно определять локаль для пользователя и грамотно использовать ее при SEO-оптимизации.
Универсальный формат разметки переводов
Разметка — это метатекст, который несёт дополнительную информацию. Например, она может выглядеть вот так: «Купить подписку за {price} с триальным периодом {duration}». Вместо переменных в фигурных скобках могут подставляться нужные данные. В нашем примере — это стоимость подписки и её длительность.
Мы выбрали ICU Message Format — он довольно удобен для представления переводов, а ещё интуитивно понятен как разработчикам, так и переводчикам. Кроме этого, формат позволяет учитывать грамматическое многообразие и богатство естественных языков.
Вот список основных фич этого формата, которые мы активно используем.
Плюрализация
{
"mail.range": "Напишем на почту за {days, plural, one {# день} few {# дня} other {# дней}}",
}
Интерполяция
const title = 'Агата Кристи'
{
"OnBoardingSubscriptionButton": "Слушать альбом {title}"
}
Условный выбор (select)
{
"subsriptionPurchase": "{availability, select, true {Доступна до} other {Недоступна с}} {date}"
}
Причины выбора ICU весьма прозаичны:
Более гибкий формат, который умеет сохранять контекст целого предложения, что упрощает работу переводчиков.
Удобство построения форм множественного числа. Мы можем их настраивать точечно. Это важно, потому что, скажем, в арабском языке плюральных форм целых шесть.
Наличие валидации.
Поддержка со стороны CAT tools.
Наличие большого количества библиотек на разных язык программирования.
Одинаковые правила форматирования данных для разных локалей
В каждой стране существует свой привычный и устоявшийся подход к отображению различных данных. Написание чисел и валют — не исключение, отличаются как разделители, так и положение знака самой валюты. У некоторых стран и вовсе собственное начертание чисел. Все эти особенности нам надо учитывать при отображении данных для пользователя. Подобное знание есть у нативного браузерного Intl API, которое и используется при отображении главных данных на основе локали.
Полный список локальных особенностей должен где-то храниться и как-то описываться. Поэтому Unicode собрал огромную базу данных (CLDR, Common Locale Data Registry) и продолжает её поддерживать. Де-факто это набор XML-файлов, в которых и хранится вся нужная информация по отображению данных для каждой из существующих локалей.
К примеру, вот так выглядит файл с информацией обо всех мировых валютах.
Вот еще несколько примеров:
Европейские числа для русской локали (ru) |
Европейские числа для английской локали (en) |
Арабские числа |
Китайская десятичная система нумерации |
123 456,789 |
123,456.789 |
١٢٣٤٥٦٫٧٨٩ |
一二三,四五六.七八九 |
Валюты в английской локали (en) |
Валюты в русской локали (ru) |
€1,234.57 |
123 457,00 € |
RUB 1,234.57 |
123 457,00 ₽ |
Локали дат |
Описание |
Примеры |
en-US |
Английский в Америке. |
12/20/2012 |
ko-KR |
Корейский в Корее, используется порядок год-месяц-день. |
2012. 12. 20. |
ar-EG |
Арабский в Египте, используются арабские цифры. |
٢٠/١٢/٢٠١٢ |
ja-JP-u-ca-japanese |
Японский в Японии с использованием японского календаря, где 2012 год является 24 годом эры Хэйсэй. |
24/12/20 |
Как видите, особенностей очень много, и все их нужно учитывать при разработке.
Общая библиотека для интернационализации
Мы исследовали и сравнили по ряду признаков множество библиотек для работы с переводами. В итоге остановились на Format.js как на библиотеке, которая отвечает современным стандартам и требованиям к международной разработке. Кроме того, у этого инструмента большой набор функций, а также он стабильно поддерживается.
Для нас важно, что библиотека использует BCP47 и является обёрткой над Intl API для форматирования данных, а также поддерживает ICU Message Format.
Методология качественного внедрения переводов
Кроме внедрения i18n, интернационализацию нужно поддерживать, и важно не смещать фокус продуктовых команд с самого продукта на инфраструктурные задачи. А это значит, что вокруг i18n и l10n необходимо навесить множество автоматики и линтеров. Они будут подсказывать разработчикам, как корректно писать код, и позволят не забывать при этом про локализацию проекта.
Бо́льшая часть подобной автоматики идет из коробки с Format.js, которая позволяет валидировать наличие переводов, их контекстов и заполнение всех полей в ICU-сообщениях. Однако часть автоматики необходимо реализовать руками. Яркий пример — генератор id для сообщений (мы планируем опубликовать его в опенсорс), а также автоматизированное получение, создание и разделение переводов.
Также важно обращать внимание не только на код, но и на обмен знаниями по работе с новыми подходами. Разработчиков много, и довольно важно не забывать про рассказы, как будет происходить разработка в новой реальности с новыми инструментами интернационализации.
Code First Approach Translations
В современных реалиях проектов есть две большие проблемы:
устаревание переводов;
сложная валидация.
Изначально мы не использовали Code First Approach Translations. Это приводило к проблеме, когда переводы становились неактуальными, потому что менеджеры вручную меняли что-то в CAT tool в обход разработки, и часто не на всех языках. Поэтому в рамках одного ключа возникали различия.
Валидировать такие переводы сложно, так как источником правды являлся CAT tool, в котором нельзя понять, какие переводы устарели и не используются, а какие — актуальны.
Чтобы решить эти проблемы, мы ввели два изменения:
Источником правды становится код.
Один ключ — один перевод. При изменении текста будет генерироваться новый ключ перевода.
Когда код становится источником правды, довольно просто статически анализировать ключи и компоненты переводов с помощью линтеров. Кроме того, всегда можно узнать, какие ключи сейчас действительно используются. На основе этого можно построить автоматизированную синхронизацию переводов только нужных проекту ключей.
Подход с одним переводом на ключ избавляет нас от обновлений. По итогу мы оставляем только две операции по работе с ключами: insert и delete. Таким образом, при любом обновлении строки появляется новый ключ, что гарантирует консистентность для различных версий приложения, использующих одни и те же ключи. Также с этим подходом будет довольно сложно сломать переводы.
Внедряя такой подход, мы теряем возможность моментально обновлять какие-то тексты в рантайме приложения. Такие тексты стоит выносить на бэкенд и получать их оттуда. Но из-за того, что переводы делаются долго (SLA до 2 дней), у нас появляется возможность повысить качество переводов и уменьшить количество текстов, которые не будут переведены в продакшене.
Как видите, локализация и интернационализация сервисов — это не просто взять и сделать качественный перевод элементов интерфейса и поменять значок валюты. Если у вас много сервисов, многие из них связаны между собой, и у них есть общий элемент экосистемы, в этом случае нужно подходить к делу максимально ответственно, чтобы сохранить качество продукта. В том числе и поэтому мы решили по возможности автоматизировать этот процесс.
VADemon
Покажит, пожалуйста, пример использования сообщений (с этими длинющими ID) посреди другого кода. Или скриншот React-компонента это и есть?
Со стороны перевода мне интересно, как дела у инструментария с видоизменившимися и похожими текстами. Есть ли где контекстуальные подсказки типа "вот этих текстов не стало", пока предстают к переводу с нуля новые тексты? Есть ли более общее "данная строка похожа на вот эту, которая уже переведена". Потому что иначе соблюдать стиль перевода будет сложно (а выводить абсолютно всё в глоссарий - прошлый век).
Ну и смежная тема -- переиспользование и согласование переводов между проектами. Хотя у Яндекса, как компании, может быть условный монорепозиторий для конечных пользователей CAT инструмента.
avpuser Автор
По поводу первого вопроса о "показе примера использования сообщений (с этими длинными ID) посреди другого кода", вы правы: скриншот React-компонента в статье — это и есть пример.
Относительно вопроса о контекстуальных подсказках вроде "вот этих текстов не стало" или "данная строка похожа на вот эту, которая уже переведена", в самом проекте такого инструмента у нас нет. Но он и не требуется, поскольку подобный функционал внедрен во многих CAT tools (в том числе в тот, который мы используем) через так называемую память переводов.
Что касается последнего вопроса о переиспользовании и согласовании переводов между проектами, то тут ответ такой. Мы рекомендуем в таких проектах использовать CAT tool с общей памятью переводов. Это значительно упрощает жизнь локализатора при переводе повторяющихся ключей.
Что касается самого процесса разработки, то мы настоятельно рекомендуем избегать переиспользования ключей между разными компонентами и страницами (особенно между разными проектами). Даже если текст в этих ключах одинаковый, все равно рекомендуется использовать два разных ключа. Это позволит избежать неожиданных изменений не в тех местах.
VADemon
Спасибо за ответ! Получается, всё сводится к этому пункту (логично) и тогда всё становится на свои места. И уникальные идентификаторы не помеха, т. к. инструмент это сам всё найдет и сопоставит. Собственно, в этом и было мое непонимание. Будучи не более чем любителем (перевод открытого софта и т.п.) я ни разу не видел, чтобы эта память переводов присутствовала или хотя бы нормально работала.
На том же Crowdin дается какое-то одно предложение из (как я теперь прочитал) "глобального Translation Memory" и все. Со стороны проекта можно загрузить свою базу с TM, и тогда вроде нормальнее будет (судя по страницам из их примеров), но мне кажется это неудобно устроено. Но это уже не к вам вопрос. В моем понимании, CAT должен автоматически составлять такую базу на основе предыдущих принятых переводов, шарить её между проектами одной тематической категории и правильно тегировать, чтобы понятно было откуда, что и как (при выборе подсказки). Естественно, подсказок несколько. Может быть в больших профессиональных конторах оно так и работает.