Привет! Меня зовут Александр Поляков, я руководитель команды 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. Таким образом, при любом обновлении строки появляется новый ключ, что гарантирует консистентность для различных версий приложения, использующих одни и те же ключи. Также с этим подходом будет довольно сложно сломать переводы.

Пример использования библиотеки Format.JS в виде React-компонента
Пример использования библиотеки Format.JS в виде React-компонента
Пример использования библиотеки Format.JS в виде API
Пример использования библиотеки Format.JS в виде API

Внедряя такой подход, мы теряем возможность моментально обновлять какие-то тексты в рантайме приложения. Такие тексты стоит выносить на бэкенд и получать их оттуда. Но из-за того, что переводы делаются долго (SLA до 2 дней), у нас появляется возможность повысить качество переводов и уменьшить количество текстов, которые не будут переведены в продакшене.


Как видите, локализация и интернационализация сервисов — это не просто взять и сделать качественный перевод элементов интерфейса и поменять значок валюты. Если у вас много сервисов, многие из них связаны между собой, и у них есть общий элемент экосистемы, в этом случае нужно подходить к делу максимально ответственно, чтобы сохранить качество продукта. В том числе и поэтому мы решили по возможности автоматизировать этот процесс.

Комментарии (5)


  1. VADemon
    05.12.2023 15:47
    +1

    Покажит, пожалуйста, пример использования сообщений (с этими длинющими ID) посреди другого кода. Или скриншот React-компонента это и есть?

    Со стороны перевода мне интересно, как дела у инструментария с видоизменившимися и похожими текстами. Есть ли где контекстуальные подсказки типа "вот этих текстов не стало", пока предстают к переводу с нуля новые тексты? Есть ли более общее "данная строка похожа на вот эту, которая уже переведена". Потому что иначе соблюдать стиль перевода будет сложно (а выводить абсолютно всё в глоссарий - прошлый век).

    Ну и смежная тема -- переиспользование и согласование переводов между проектами. Хотя у Яндекса, как компании, может быть условный монорепозиторий для конечных пользователей CAT инструмента.


    1. avpuser Автор
      05.12.2023 15:47
      +1

      1. По поводу первого вопроса о "показе примера использования сообщений (с этими длинными ID) посреди другого кода", вы правы: скриншот React-компонента в статье — это и есть пример.

      2. Относительно вопроса о контекстуальных подсказках вроде "вот этих текстов не стало" или "данная строка похожа на вот эту, которая уже переведена", в самом проекте такого инструмента у нас нет. Но он и не требуется, поскольку подобный функционал внедрен во многих CAT tools (в том числе в тот, который мы используем) через так называемую память переводов.

      3. Что касается последнего вопроса о переиспользовании и согласовании переводов между проектами, то тут ответ такой. Мы рекомендуем в таких проектах использовать CAT tool с общей памятью переводов. Это значительно упрощает жизнь локализатора при переводе повторяющихся ключей.

      Что касается самого процесса разработки, то мы настоятельно рекомендуем избегать переиспользования ключей между разными компонентами и страницами (особенно между разными проектами). Даже если текст в этих ключах одинаковый, все равно рекомендуется использовать два разных ключа. Это позволит избежать неожиданных изменений не в тех местах.


      1. VADemon
        05.12.2023 15:47
        +1

        Мы рекомендуем в таких проектах использовать CAT tool с общей памятью переводов.

        Спасибо за ответ! Получается, всё сводится к этому пункту (логично) и тогда всё становится на свои места. И уникальные идентификаторы не помеха, т. к. инструмент это сам всё найдет и сопоставит. Собственно, в этом и было мое непонимание. Будучи не более чем любителем (перевод открытого софта и т.п.) я ни разу не видел, чтобы эта память переводов присутствовала или хотя бы нормально работала.

        На том же Crowdin дается какое-то одно предложение из (как я теперь прочитал) "глобального Translation Memory" и все. Со стороны проекта можно загрузить свою базу с TM, и тогда вроде нормальнее будет (судя по страницам из их примеров), но мне кажется это неудобно устроено. Но это уже не к вам вопрос. В моем понимании, CAT должен автоматически составлять такую базу на основе предыдущих принятых переводов, шарить её между проектами одной тематической категории и правильно тегировать, чтобы понятно было откуда, что и как (при выборе подсказки). Естественно, подсказок несколько. Может быть в больших профессиональных конторах оно так и работает.


  1. migratech
    05.12.2023 15:47

    А как обстоит дело с версткой, скажите, пожалуйста?


    1. avpuser Автор
      05.12.2023 15:47
      +1

      Про какую верстку идет речь? Уточните, пожалуйста, что именно вас интересует в верстке.