О том, как процесс построен в LinkedIn — в этой статье (внимание — Java).
Интернационализация оказывает критическое влияние на деятельность и развитие сети 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). Это приводило к определенным проблемам:
- Добавление переводов означало пересборку и перезапуск всего сервиса.
- Если в переводе случалась ошибка, нельзя было просто вернуться к старой версии текста: нужно было откатывать код приложения к предыдущей версии.
- Переводы могут использоваться во многих сервисах — и все их в случае чего приходилось пересобирать и перезапускать.
По-новому
Новая система собирает и выкладывает properties-файлы отдельно от кода приложения. Мы ввели концепцию языкового пакета — это JAR-файл, содержащий весь переведенный контент для конкретного языка. Обновленные версии таких языковых пакетов могут выкладываться на веб-сервер в любой момент. Их также можно в любой момент откатить назад, если будут обнаружены ошибки.
Мы добавили новую библиотеку загрузки ресурсов, которая определяет доступность новых языковых пакетов и начинает использовать обновленные переводы — все это без перезапуска сервиса. Если библиотека не находит перевод, она использует исходные англоязычные строки.
Процесс
Внедрение новых переводов — лишь часть большой картины: нам также нужно было найти способ быстро находить новые и свежеизмененные строки, доставлять их переводчикам и включать результат перевода в языковой пакет. Вот так выглядит наш процесс полностью:
- Инженер отправляет новые или обновленные англоязычные строки в систему контроля версий.
- Сервер локализации сканирует систему контроля версий на предмет изменений раз в день и издает запрос на перевод всех новых и измененных строк.
- Раз в час сервер локализации собирает готовые переводы. Он проверяет новый контент и, затем, публикует полный языковой пакет со всеми переводами для конкретного языка в Хранилище.
- Система внедрения раз в час направляет обновленные языковые пакеты на тестирование и подгружает в работающий сервис дважды в день.
- На случай, если команде локализации понадобится изменить перевод в срочном порядке, есть возможность подгружать переводы вручную в любое время в один клик.
Обратная совместимость
Благодаря тому, что система динамической подгрузки языков отделяет локализуемые тексты от кода, появилась возможность выкладывать в работающий сервис новые строки контента раньше, чем код приложения, который их использует. Поэтому теперь все наши интернационализационные ресурсы должны быть обратно совместимы с предыдущими версиями, чтобы обеспечить уверенность в том, что ничего не сломается при внедрении новых строк.
В этом контексте обратная совместимость означает, что добавление новых строк всегда безопасно, но если вы изменяете существующую, то должны сохранить неизменным количество и тип переменных. Например, изначально у нас была такая строка:
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/
Добавить комментарий