Как LinkedIn делает локализации на 19 языков за 1 ночь

от автора

“Я хочу, чтобы после того, как программист добавил новую строчку в интерфейс, она сама перевелась на 19 языков и сама положила себя в SVN и была готова к релизу утром” — это мечта любого разработчика, вкусившего запретный плод локализации продукта на иностранные языки. В Alconost Translations мы помогаем если не исполнить, то хотя бы приблизиться к этой мечте. Да, решение, похожее на описанное в статье существует не только для разработчиков LinkedIn, но и для простых смертных.

О том, как процесс построен в LinkedIn — в этой статье (внимание — Java).

image


Интернационализация оказывает критическое влияние на деятельность и развитие сети LinkedIn, которая сегодня доступна на 19 разных языках. Чтобы ускорить работу с локализованными текстами, международная инженерная команда разработала систему динамического внедрения переведенных строк контента в работающие сервисы.

Новая система позволяет нам оперативно модифицировать контент: вносить быстрые правки и изменения без привлечения разработчиков, пересборки и перезапуска сервиса.

Введение

Весь контент изначально пишется на английском нашими разработчиками и продакт-менеджерами. Обычно текст, нуждающийся в переводе, содержится в properties-файле:

add_to_network__send_invitation=Send invitation add_to_network__cancel=Cancel add_to_network__add_message=Add a personal message add_to_network__user_message=I'd like to add you to my professional network.\n\n- {0} 

Затем наша штатная команда локализации переводит контент на разные языки. Вот, например, тот же properties-файл, переведенный на итальянский:

add_to_network__send_invitation=Invia un invito add_to_network__cancel=Annulla add_to_network__add_message=Aggiungi un messaggio personale add_to_network__user_message=Vorrei aggiungerti alla mia rete professionale.\n\n\n\n-{0} 

Чтобы текст отобразился на странице, мы используем в шаблонах функции интернационализации, которые обращаются по заданному ключу к properties-файлу для пользовательской локали.

<form>   <label for="message">${i18n('add_to_network__add_message')}</label>    <textarea id="message">${i18n('add_to_network__user_message', fullname)}</textarea>    <input type="submit" value="${i18n('add_to_network__send_invitation')}"/>   <input type="button" value="${i18n('add_to_network__cancel')}"/> </form> 

По-старому

До внедрения динамической подгрузки языков система собирала все properties-файлы в один артефакт вместе с кодом приложения (WAR). Это приводило к определенным проблемам:

  1. Добавление переводов означало пересборку и перезапуск всего сервиса.
  2. Если в переводе случалась ошибка, нельзя было просто вернуться к старой версии текста: нужно было откатывать код приложения к предыдущей версии.
  3. Переводы могут использоваться во многих сервисах — и все их в случае чего приходилось пересобирать и перезапускать.

По-новому

Новая система собирает и выкладывает properties-файлы отдельно от кода приложения. Мы ввели концепцию языкового пакета — это JAR-файл, содержащий весь переведенный контент для конкретного языка. Обновленные версии таких языковых пакетов могут выкладываться на веб-сервер в любой момент. Их также можно в любой момент откатить назад, если будут обнаружены ошибки.

Мы добавили новую библиотеку загрузки ресурсов, которая определяет доступность новых языковых пакетов и начинает использовать обновленные переводы — все это без перезапуска сервиса. Если библиотека не находит перевод, она использует исходные англоязычные строки.

Процесс

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

image

  1. Инженер отправляет новые или обновленные англоязычные строки в систему контроля версий.
  2. Сервер локализации сканирует систему контроля версий на предмет изменений раз в день и издает запрос на перевод всех новых и измененных строк.
  3. Раз в час сервер локализации собирает готовые переводы. Он проверяет новый контент и, затем, публикует полный языковой пакет со всеми переводами для конкретного языка в Хранилище.
  4. Система внедрения раз в час направляет обновленные языковые пакеты на тестирование и подгружает в работающий сервис дважды в день.
  5. На случай, если команде локализации понадобится изменить перевод в срочном порядке, есть возможность подгружать переводы вручную в любое время в один клик.

Обратная совместимость

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

В этом контексте обратная совместимость означает, что добавление новых строк всегда безопасно, но если вы изменяете существующую, то должны сохранить неизменным количество и тип переменных. Например, изначально у нас была такая строка:

accept_invite__hello_connect=Hello {0}, would you like to connect with {1}? 

Мы спокойно можем поменять некоторые слова:

# This change is backwards compatible accept_invite__hello_connect=Hi {0}, would you like to add {1} to your professional network? 

Но удаление, добавление или изменение типа переменных нарушит обратную совместимость, так как код приложения будет предоставлять значения только для прежних переменных:

# This change is NOT backwards compatible! accept_invite__hello_connect=Hello {0}, would you like to connect with {1}, a coworker at {2}? 

Мы добиваемся обратной совместимости с помощью исполняемого перед коммитом кода, который предупреждает удаление ресурсов и заодно проверяет, чтобы обновления существующих текстовых ресурсов были совместимы с набором переменных. Фрагмент кода ниже демонстрирует часть нашей логики валидации:

/**  * Verify that translation isn't missing indexes used with the   * original as well as does not specify additional indexes not  * present in the original.  */ private boolean verifyPlaceholderIndexMatch(String originalMessageTemplate,                                              PlaceholderInfo originalPlaceholderInfo,                                             PlaceholderInfo placeholderInfo) {   if(originalPlaceholderInfo.keySet().equals(placeholderInfo.keySet()) == false)   {     // remove all index numbers in the translation from the index      // set in the original to determine what's missing in the      // translation     Set<String> missingIndexSet = new HashSet<String>(originalPlaceholderInfo.keySet());     missingIndexSet.removeAll(placeholderInfo.keySet());      // remove  all index numbers in the original from the set of     // indexes in the translation to determine what extra index     // we have in the translation     Set<String> extraIndexSet = new HashSet<String>(placeholderInfo.keySet());     extraIndexSet.removeAll(originalPlaceholderInfo.keySet());     return false;   }   return true; } 

19 языков внедрены, впереди — все остальные!

Новая система ускорила процесс перевода в LinkedIn: попадание новых строк в работающий сервис стало значительно быстрее и легче, а мы получили возможность постепенно выкатывать переводы и откатывать их назад в случае необходимости. И самое важное: наша система интернационализации теперь может масштабироваться с учетом растущего количества приложений, языков и участников.

Переведено в Alconost Translations.

ссылка на оригинал статьи http://habrahabr.ru/company/alconost/blog/173467/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *