Продолжая дискуссию о преимуществах Fluent перед привычным gettext, публикую официальную позицию создателей Fluent в переводе.
Gettext — это система локализации, глубоко укоренившаяся в проект GNU и сопутствующие ему архитектурные решения. Fluent Project рассматривает gettext как хороший пример полноценной низкоуровневой платформонезависимой экосистемы библиотек и инструментов для управления полным циклом выпуска продукта с файлами локализации в удобочитаемом формате. В то же время парадигма Fluent приводит нас к другим архитектурным решениям в важных локализационных аспектах, которые, в свою очередь, приводят к совершенно разным API и жизненным циклам.
Другими словами, gettext — отличный проект, но мы не разделяем его взгляды на подход к локализации.
Вот основные различия между gettext и Fluent:
gettext | Fluent | |
---|---|---|
Идентификатор сообщения | исходная строка | предоставляется разработчиком |
Привязка аргументов | позиционная* | на основе ключей |
Аннулирование перевода | нечёткие совпадения | изменение идентификатора |
Хранение данных | удобочитаемый формат (.po) или компилированный формат (.mo) | удобочитаемый формат (.ftl) |
Внешние аргументы | нет | богатая поддержка |
Поддержка множественного числа | special-cased | часть общего синтаксиса селектора вариантов |
Широта поддержки множественного числа | На усмотрение разработчика, влияет на все переводы | На усмотрение локализатора, влияет только на конкретную локаль |
Создан для | Языков семейства С* | веба, современных клиентских языков |
Ссылки на сообщения | определяется разработчиком | определяется локализатором |
Шаблоны сообщений | необходимы (.pot) | нет |
Комментарии локализаторов | нет* | полная поддержка |
Восстановление после ошибок | хрупкая | сильная логика восстановления |
Составные сообщения | нет | значение + атрибут на сообщение |
Двунаправленные тексты | нет | двунаправленная изоляция |
Международное форматирование | нет | явное и неявное |
Договорённость
Самое важное различие gettext и Fluent — идентификатор сообщения. В gettext решили использовать исходную строку (обычно на английском) в качестве идентификатора. Этот выбор кажется простым, но в дальнейшем он навязывает множество ограничений.
Прежде всего, при таком подходе любое изменение в оригинальной строке делает недействительными все сопутствующие ей переводы. Это серьёзно увеличивает нагрузку на разработчиков, вынуждая их никогда не изменять исходные сообщения, поскольку это потребует обновления всех переводов.
Во-вторых, это усложняет введение нескольких сообщений с одинаковым текстом на исходном языке, которые должны переводиться по-разному. Например, текст для кнопки «Open» и для отметки «Open» могут переводиться по-разному, поскольку первый текст — команда, а второй — описание. В gettext есть опциональная контекстная строка msgctxt для различия строк с одинаковым исходным сегментом. Такой подход возлагает ответственность за распознавание подобных ситуаций на разработчиков, что противоречит принципу разделения интересов.
Fluent не рекомендует использовать тексты повторно именно по этой причине. Отделение исходного текста от других переводов также важно для нашей способности вводить составные сообщения (которые содержат несколько строк для одной единицы перевода, привязанной к одному виджету пользовательского интерфейса) и для ссылок на сообщения на основе идентификатора.
Fluent устанавливает «договорённость» между разработчиками и локализаторами. Разработчик вводит уникальный идентификатор и набор переменных (кол-во непрочитанных соообщений, имя пользователя и т.п.), а локализатор, используя синтаксиc Fluent, решает, как именно сконструировать текст сообщения для этого идентификатора.
Разработчик не должен беспокоиться о детальной реализации переводов подобных сообщений. Всё, что нужно разработчику, это чтобы на запрос строки по конкретному идентификатору он получил одну строчку текста, подходящего для конкретного места в UI.
Варианты сообщений
В gettext поддерживается небольшой набор функций для интернационализации, в частности — для множественных чисел. Но такой синтаксис для множественных чисел является особым случаем, дополнением к стандартному синтаксису gettext, и его сложно масштабировать для других случаев, требующих вариативности.
Fluent поддерживает базовую концепцию вариативности строк, которую можно использовать с селекторами. Обычно правило множественного числа и будет таким селектором, но в зависимости от грамматических особенностей языка, могут быть и другие, такие как пол, склонение или даже окружение — например, время суток или операционная система. Синтаксис Fluent позволяет локализаторам учитывать все эти особенности и создавать текст, точно подходящий под ситуацию.
Внешние аргументы
Gettext не поддерживает внешние аргументы. Другими словами, нельзя задать форматирование параметров — чисел, дат. Для форматирования параметров в gettext рекомендуется возвращать строку, которую в дальнейшем передадут в printf или запустят String.prototype.replace на результирующей строке.
Во Fluent поддержка внешних аргументов находится в самой основе синтаксиса. Внешние аргументы не только интерполируются, но и используются в качестве параметров для селектора, а так же могут передаваться во встроенные функции. Это позволяет локализаторам создавать гораздо более точные тексты для конкретных случаев. Вдобавок ко всему, Fluent размещает маркеры FSI / PDI вокруг объектов, чтобы защитить изоляцию направленности в двунаправленном тексте, и запрещает любые манипуляции с конечными строками, снижая нагрузку на разработчиков.
Изолирование ответственности
Кроме того, способ, которым gettext обрабатывает правила для множественных чисел, требует от разработчика системы выбора, будет ли сообщение многовариантным сообщением или единственной строкой. С точки зрения Fluent, разработчик не должен заниматься подобными вопросами. Во многих случаях, когда в английском достаточно одного варианта, в других языках нужно добавлять варианты со множественными числами.
Fluent предполагает, что разработчик не должен обладать подобными лингвистическими знаниями при разработке ПО со множеством локалей, и у каждого языка должна быть определённая свобода действий при локализации.
В результате Fluent хранит каждый перевод отдельно, без «утечки» требований одного языка на другие, и сохраняет все переводы «непрозрачными» для разработчика, которому не нужно беспокоиться о том, какие функции локализаторам могут понадобиться для данной строки.
Аннулирование перевода
В цикле разработки можно выделить три ситуации, когда перевод «аннулируется» (станет недействительным) по отношению к оригиналу:
- Незначительное изменение: не влияет на перевод (исправление пунктуации, опечатки).
- Среднее изменение: влияет на конструкцию сообщения, но не отменяет корректности связанного перевода (например, Show All Bookmarks -> Show Bookmarks Manager).
- Значительное изменение: новый смысл предложения (Click to save -> Click to open).
По архитектурным причинам, gettext объединяет все три уровня в одно состояние под названием нечёткое совпадение (fuzzy). Любое изменение исходной строки (хоть полное, хоть незначительное) приводит к аннулированию переводов.
Во Fluent использование уникальных идентификаторов позволяет держать два из этих уровней отдельно от третьего: при внесении небольших изменений в исходный текст строки и при сохранении идентификатора переводы остаются валидными. С другой стороны, если разработчик поменяет идентификатор, то все переводы аннулируются и потребуют обновления.
Мы считаем, что такое архитектурное решение более выгодно для большинства циклов выпуска, хотя и признаём, что для изменений среднего уровня разработчику придётся выбирать между сохранением или изменением идентификатора (т.е. между незначительным и значительным изменением).
Мы так же рассматриваем идею о версионности сообщений, чтобы разработчик мог помечать сообщение как обновлённое без полного аннулирования его содержимого. Такое состояние позволит сохранить перевод валидным исходя из той точки зрения, что старая версия перевода всё же лучше, чем непереведённая строка, и в то же время позволит инструментам уведомить локализатора о необходимости обновить перевод.
Формат данных
В gettext использует три формата файлов — *.po, *.pot и *.mo. Это влияет на внедрение gettext в производственный цикл, добавляя этапы вроде извлечения и компиляции сообщений.
Fluent использует единый формат файла *.ftl, что упрощает внедрение и не требует дополнительных шагов, которые могут привести к расхождению в данных.
Поддержка Юникода
Gettext можно кодировать в UTF-8. В целом, на этом поддержка Unicode заканчивается. В нём используется свой набор данных для множественных чисел, не умеет работать с форматированием дат и чисел, не помогает в работе с двунаправленными текстами.
Fluent активно использует стандартизованные библиотеки и алгоритмы CLDR, ICU и ECMA402, аккуратно объединяя локализацию и интернационализацию.
Заключение
Мы верим, что API и синтаксис Fluent представляют собой существенное улучшение по сравнению с gettext, и мы рекомендуем использовать их для международного ПО.