О языковом переводе интерфейса в shareware
Posted: июля 7, 2009 | Author: Андрей Шкуропий | Filed under: easyQuizzy, Разработка софта | Tags: easyQuizzy, GNU GetText, MUI, Unicode, интернационализация, интерфейс, локализация, множественные формы, перевод | Комментариев нетБольшинство границ между странами в Интернете на сегодняшний день фактически стерты. Но одна граница, которая осталась в наличии, еще долго будет головной болью разработчиков программного обеспечения — языковая. Создание интернациональных программ всегда было большой проблемой, и каждая компания решала ее по-своему. Сегодня хочу написать, как эту проблему решили мы. Наше решение не претендует на оптимальность для всех, но, если вы никогда раньше не занимались интернационализацией, то это поможет вам построить свою систему, более эффективную для вас.
Для начала немного теории.
Есть два термина, которые разработчики часто путают: интернационализация и локализация программы. Интернационализация — это адаптация продукта для потенциального использования в любой стране, а локализация — это добавление специальных функций для использования программы в некотором определённом регионе. Интернационализация производится на начальных этапах разработки. Локализация делается для каждого целевого языка отдельно (подробнее смотрите здесь). Разработчики shareware занимаются обычно как раз интернационализацией, потому что у них просто недостаточно ресурсов для создания отдельных версий приложений для разных языков или стран, как делает, например, Microsoft.
При интернационализации важно еще на стадии проектирования не привязываться к конкретным языкам и странам во избежание конфликтов на национальной почве. Если вы хотите, чтобы продажи программы пошли в ОАЭ, надо, как минимум, вставлять в нее текстовые редакторы с поддержкой двунаправленного письма, использовать Unicode-строки во всех местах и, соответственно, Unicode-шрифты. Также следует озаботиться запросом у операционной системы изменяющихся от страны к стране региональных стандартов представления чисел, даты, денежной единицы и всем остальным, что на русском языке по-идиотски называют “культура приложения”, очевидно криво переведя английский термин “software culture”.
Такими нехитрыми способами можно порешать практически все проблемы интернационализации, кроме одной: вы не сможете сами перевести строки пользовательского интерфейса на все языки.
Многие это последнее проблемой вообще не считают и просто переводят свой интерфейс на английский язык, так как интернациональное shareware продается лучше всего почему-то в англоязычных странах. Если вам этого мало, то придется ответить на главный вопрос: как вы будете взаимодействовать с переводчиками?
Способов решения два:
- Платный. Вынести все используемые в программе строки в файл ресурса, раздать его переводчикам, заплатив примерно от $5 до $10 за каждые 1000 символов, и включить переведенные ресурсы в программу.
- Бесплатный. Сделать файлы с переводными строками доступными для изменения “на ходу” всем желающим, и поощрять самостоятельное выполнение переводов заинтересованными пользователями с постепенным включением их в дистрибутивы новых версий.
Платный способ хорош своей простотой для программистов, но вам придется повторять его снова и снова при выходе новых версий приложения. Причем, пока вы не закончите с переводами, нельзя будет сделать релиз.
Бесплатный способ имеет то преимущество, что позволяет при необходимости заказывать платные переводы, не жертвуя возможностью получить дополнительные бесплатные переводы, однако он и более сложный в плане реализации, так как приходится создавать прозрачную и надежную схему взаимодействия с переводчиками.
Мы нашли способ объединить эти два решения.
Был создан специальный сайт для переводчиков, на котором они могут с помощью Wiki-движка редактировать переводные файлы в формате XML. Переводные файлы при достижении определенной степени завершенности у нас включаются в дистрибутив программы в виде ресурсов, и пользователь после установки программы их редактировать не может, но, если загрузить переводной файл с сайта и положить его в папку программы, то он загрузится вместо файла, который был прикреплен к программе при компиляции. Это сделано для возможности быстрой проверки качества пользовательского перевода без необходимости выпускать новую сборку программы.
Решение редактировать XML-файлы вручную может показаться странным из-за необходимости валидации, но преимущества для нас перекрыли недостатки. XML очень просто структурируется и позволяет легко задать правила форматирования даже для тех, кто этот формат первый раз в жизни видит. Идентификаторами переводных строк у нас являются оригинальные строки на английском языке, что позволяет программисту не выдумывать никаких таблиц идентификации и не синхронизировать потом номера строк-оригиналов между десятком переводных файлов.
Для применения перевода следует делать так, как привыкли делать переводчики PHP-страниц, интернационализированных с помощью библиотеки GNU GetText — пробрасывать оригинальные английские строки сквозь функцию трансляции, которая в зависимости от текущего выбранного языка выдает нужную переводную строку. Пример на Delphi:
CloseButton.Caption := SS('Close')
При такой реализации пришлось решить несколько технических вопросов, самыми главными из которых были следующие.
- Как идентифицировать используемые языки?
- Как переводить множественные формы в предложениях с числительными?
Рассмотрим эти вопросы подробнее.
Языков на свете очень много. Теоретически, добровольцы могут захотеть перевести ваш интерфейс на любой язык, поэтому лучше сразу быть готовым к этому в программе. Кроме обычного названия языка, написанного на нем самом, следует держать его английское название для использования в меню выбора языка, а также буквенный или числовой код. Этот код нужен будет потом для кодирования переводных строк и, опционально, для автоматического определения языка при первом запуске программы, чтобы пользователю не нужно было переключаться с английского языка на свой вручную.
С этими буквенными кодами языков не все просто.
Имеется несколько версий стандарта буквенного кодирования языков под названием ISO 639, и каждая последующая версия расширяет их доступный набор. Стандарт ISO 639-1 регламентирует двухбуквенные коды для основных мировых языков (en, ru, fr и т.д.), ISO 639-2 расширяет код до трех символов, значительно увеличивая набор доступных языков (eng, rus, fra и т.д.) и вводя тонкости их модификаций (например, fre/fra — современный французский, а frm — средневековый французский), ISO 639-3 тоже трехбуквенный, но покрывает также десятки диалектов и наречий, включая все разновидности наречий американских индейцев. Гигантскую сводную таблицу этих стандартов можно посмотреть здесь.
Кстати, многие делают ошибку, путая код языка с кодом страны. Хотя они иногда и совпадают (например, для России и русского языка, ru-RU), но в общем случае они должны отличаться. Коды стран должны соответствовать стандарту ISO 3166, и их записывают обычно большими буквами, если они стоят в паре с кодом языка (en_US, en_GB, uk_UA). Код страны важен для определения региональных настроек, но, поскольку предполагается, что интернационализация программы была сделана вдумчиво, и все региональные настройки типа даты и валюты выбираются из операционной системы, мы отбросили код страны за ненадобностью. Единственное место, где он может пригодиться в таком случае — это поддержка “местных” особенностей языков и локальных диалектов. Характерный пример: британский и американский английский могут отличаться используемыми наборами слов. Однако, если ваше приложение не содержит больших объемов связного текста, этот аспект можно игнорировать, все равно текстовые метки над полями ввода из трех слов правильно поймут и американцы, и англичане.
Мы приняли за основу языкового кодирования стандарт ISO 639-2, как наиболее распространенный трехбуквенный код, который описывает большинство языков. А если его недостаточно для идентификации языка (или диалекта), то дополнительно к этому коду используется код ISO 639-3. Посмотреть, как строится наш переводной XML-файл с секцией описания доступных языков, можно по этой ссылке.
Автоматический выбор языка в таком случае слегка затруднен тем, что привычный программистам двухбуквенный код текущего языка операционной системы (en, ru и т.д.), возвращаемый системной функцией GetLocaleInfo() с параметром LOCALE_SISO639LANGNAME, соответствует двухбуквенному стандарту ISO 639-1, поэтому пришлось построить таблицу соответствия между ISO 639-1 и ISO 639-2. Так что для большинства языков автоматический выбор все равно сработает. А если не сработает, то пользователю придется его выбрать вручную в меню один раз, ничего не поделаешь.
Переходим к множественным формам.
Перевод предложений, в которых присутствуют численные переменные, очень часто бывает затруднен выбором нужной множественной формы в конкретном языке, чтобы предложение читалось правильно при любых значениях этих переменных.
Пример: дана переводная строка «Было задано x вопросов», где x — числовая переменная. При обычной подстановке x = 1 получается строка, как будто написанная роботом, который не знает, как правильно согласовывать предложения с числами: «Было задано 1 вопросов». Некоторые разработчики используют убогую схему подстановки вроде такой «Было задано x вопрос(ов)», или вообще «Было задано x вопрос(-а, -ов)», т.е. право выбора правильной формы великодушно предоставляется пользователю.
А если все делать, как следует, то переводная строка должна была бы меняться так:
-
(x = 1) «Был задан x вопрос»
-
(x = 2) «Было задано x вопроса»
-
(x = 5) «Было задано x вопросов»
-
(x = 21) «Был задан x вопрос»
-
(x = 35) «Было задано x вопросов»
Если посмотреть на изменения строки при последовательном увеличении переменной x, то можно увидеть, что в русском языке достаточно только трех множественных форм предложения, чтобы охватить все варианты.
Особой формы обычно требует также нулевое значение переменной:
-
(x = 0) «Не было задано ни одного вопроса»
В английском языке и многих других языках достаточно только двух форм: единичной и множественной. В словенском языке четыре множественных формы, в русском, украинском и белорусском — три.
Правила выбора нужной формы в зависимости от значения переменной в общем случае отличаются для разных языков. Описания правил выбора нужной множественной формы для большинства языков можно посмотреть на этом сайте.
Когда я решил включить поддержку множественных форм в программу, начал искать в Интернете готовые эффективные реализации этого механизма. Требовалось обеспечить как простоту программирования, так и легкость понимания правил для переводчиков. Оказалось, что все самые “продвинутые” библиотеки интернационализации используют метод работы с множественными формами, который был введен в уже упоминавшейся GNU GetText. Все формы предложения с переменными нумеруются, начиная с нуля, и переводчику предлагается в переводном файле задать для своего языка алгоритм выбора формы, а для каждой строки с числовыми переменными — все пронумерованные формы предложений. Пример:
msgid "%d day ago" msgid_plural "%d days ago" msgstr[0] "%d день назад" msgstr[1] "%d дня назад" msgstr[2] "%d дней назад"
Здесь msgid — это оригинальная английская строка для одиночной формы, msgid_plural — оригинальная английская строка во множественном числе, %d — целочисленная переменная, по которой будет производиться выбор нужной формы в целевом языке, а массив msgstr[n] представляет все одиночные и множественные формы переведенной строки.
У этого метода есть серьезный недостаток, из-за которого я его отбросил: выбор нужной формы возможен только по одной числовой переменной, которая встречается в предложении. Если в предложение будут включены две (и более) переменные, то придется разбивать переводные строки на куски и потом склеивать в программе, усложняя и без того мутный для переводчиков синтаксис.
Отличное решение было подсмотрено мной на Хабрахабре у разработчика Afan (к сожалению, не смог найти его настоящее имя) вот в этой статье.
Суть его в том, чтобы включать все множественные формы в одну строку, используя специальную синтаксическую конструкцию такого вида:
{%номер_переменной|форма_0|форма_1|форма_2|…|форма_N}
где
-
номер_переменной — порядковый номер целочисленной переменной, по значению которой будет выбрана нужная форма предложения;
-
форма_0 — нулевая форма, которая будет выбрана при нулевом значении переменной;
-
форма_1…форма_N — все множественные формы предложения в данном языке.
Пример использования в наших переводных файлах:
<id text="{%1|No right answers|1 right answer|%1 right answers},
{%2|no wrong answers|1 wrong answer|%2 wrong answers}">
<rus>{%1|Нет правильных ответов|%1 правильный ответ|%1 правильных ответа|%1 правильных ответов},
{%2|нет неправильных ответов|%2 неправильный ответ|%2 неправильных ответа|%2 неправильных ответов}</rus>
</id>
В данном примере оригинальная английская строка имеет две переменных: %1 — количество правильных ответов, %2 — количество неправильных ответов. С помощью данного синтаксиса множественных форм они при любых значениях переменных сформируют согласованную строку.
Например, при подстановке %1 = 0, а %2 = 5 получим «No right answers, 5 wrong answers».
Обратите внимание, что внутри каждой формы можно использовать переменные, константы или вообще обойтись только текстом.
И для программиста, и для переводчика цельная строка такой конструкции будет гораздо нагляднее, чем разбитая на две части по методу GNU GetText. Разбирать такие строки в программе можно с помощью простейшего однопроходного интерпретатора, разработка которого доступна студенту-второкурснику с факультета программирования. Я применил рекурсивный спуск по простой левосторонней грамматике. В указанной выше ссылке на статью есть способ разбора с помощью регулярных выражений на C#.
Вот в принципе и вся наша схема интернационализации приложений, которую я буду применять для всех будущих shareware-проектов. Остальные мелкие технические вопросы вроде использования запрещенных символов и подстановки переменных я здесь рассматривать не буду, так как они уже описаны на нашем wiki-сайте для переводчиков.
Если у вас есть идеи по улучшению данной схемы — пожалуйста, пишите в комментариях или на почту. Я подумываю о том, чтобы сделать свой модуль языковой поддержки бесплатным программным компонентом, и, если будет достаточно желающих его получить, то так и сделаю.
Leave a Reply