КАК ЭТИМ ПОЛЬЗОВАТЬСЯ
=====================

Для начала, можно посмотреть на класс Translations в тестах. Это довольно полный пример того,
как могут описываться переводы. Как видно из этого примера, переводы описываются в виде интерфейса.
Интерфейс является наследником TranslationBundle, который является всего лишь маркером, для того
чтобы можно было автоматизированно найти в нашем коде все строки которые нужно отправить в Танкер
для перевода. Далее, для краткости, всех наследников TranslationBundle будем называть bundle.

Поскольку Translations это интерфейс, пользоваться им в программе напрямую нельзя.
Чтобы воспользоваться переводами нужен класс, реализующий этот интерфейс, его можно получить так:

  Translations t = I18NBundle.implement(Translations.class);

Теперь переменную t можно использовать в программе для получения переводов.

Можно заметить, что все методы в bundle возвращают Translatable. Что это за зверь и почему не String?
Translatable - это объект перевода, то, что в итоге должно превратиться в строку на требуемом языке.
Примеры Translatable-типов можно посмотреть в пакете types. Translatable решает две задачи:

  - бывает так, что объект перевода определяется в тот момент, когда еще не известно,
    в какую локаль нужно выполнить перевод. Или локаль известна, но недоступна в конкретном
    участке кода, чтобы не увеличивать связность между компонентами программы. Если бы все методы
    в bundle возвращали String, то для реализации отложенного перевода пришлось бы либо использовать
    в каком-то виде частичные вызовы функций, либо устраивать протечки в абстракциях.

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

Чтобы превратить Translatable в строку нужно вызвать у него метод translate и передать в него
переводчик.

Переводчик - это абстракция над переводами из танкера, она не умеет ничего кроме
как вернуть перевод на конкретный язык для заданного ключа. Ключ перевода - это идентификатор,
которому в переводчике поставлена в соответствие строка перевода.

Где взять Translator? Например, вот здесь:

  I18NBundle.makeStubTranslatorFactory().getTranslator(locale)

Создание TranslatorFactory обычно должно происходить однократно при старте программы,
а вот вызов getTranslator нужно делать на каждый пользовательский запрос, передавая
в него пользовательскую локаль.

В примере кода выше видно, что переводчик привязывается к конкретной локали, т.е. конкретный
переводчик может переводить только на один определенный язык. TranslatorFactory позволяет
получить переводчик на нужный язык.

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

КАК УСТРОЕНА МАГИЯ ПО ПРЕВРАЩЕНИЮ TranslationBundle В КЛАСС
===========================================================

В основе лежат динамические прокси java. У нашего прокси внутри есть MethodInterpreter,
который умеет, как следует из названия, интерпретировать абстрактный аннотированный метод в bundle
и в вернуть объект MethodInterpretation, который как раз и реализует логику, описанную с помощью
аннотаций в нашем bundle.

MethodInterpreter большую часть работы по интерпретации делегирует входящим в его состав обработчикам
аннотаций (AnnotationHandler). У каждой аннотации, используемой в bundle, есть свой обработчик с
соответствием один к одному.

ЗАМЕТКИ ПО ВЫБРАННОМУ ДИЗАЙНУ
=============================

Основная альтернатива текущему дизайну - это способ который используется в перловом директе, а именно

   iget("Я переводимая строка")

Похожие способы используются в метрике и дисплее, см. https://st.yandex-team.ru/DIRECT-54793

Почему решено такой способ не использовать:

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

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

     Из положения можно было бы выйти, если рассматривать русские тексты в коде только в качестве
     ключей, а не в качестве реального перевода на русский. Но это может запутать разработчиков -
     можно в "запарке" внести изменения в сточку из iget, а потом долго и нудно отлаживать, почему
     же на бете реальный перевод не меняется.

     Полностью от этой проблемы избавиться нельзя, так как в коде все равно нужны русские тексты,
     для отладки, пока из танкера не пришли настоящие переводы. В нашем дизайне интернационализации
     эта проблема смягчается тем, что каждый такой отладочный русский текст помещается в аннотации
     в названии которой есть слово Stub (TranslationStub, PluralStub и т.д.), намекающее на природу
     текста. Тот же прием, в принципе, можно было бы использовать и с iget, заменив его на iget_stub
     но, нужно помнить, что этот же текст является ключем перевода, а stub как бы намекает на то,
     что текст можно свободно менять, а это неправда, при каждом изменении строка будет переводиться
     заново, что не есть хорошо, потому что лишняя работа для переводчиков и бардак в Танкере.
     А бардак, это не просто "а давайте регулярно вычищать в Танкере неиспользуемые переводы".
     Бардак потенциально может и к багам привести. К примеру, один разработчик взял да удалил строчку
     iget("привет"), а другой, независимо от первого, успел в другом месте кода такую же строчку
     добавить. Причем, успел это сделать до запуска чистки на танкере. Получили неожиданное
     переиспользование старого перевода в новом месте. Так мы плавно подошли к следующему пункту.

  2. Одна и та же строка на русском может переводиться на другие языки по разному в зависимости
     от контекста. Обозначить конекст в коде нет возможности. Можно было бы добавить в iget еще один
     параметр, context или comment, но тогда его нужно было бы делать частью ключа, со всеми
     вытекающими (см. п.1).

В нашем дизайне проблема "текстовых" ключей решена. В качестве ключа используется package_name +
interface_name + method_name. Это позволило смягчить первую проблему и полностью избавиться от
второй.

Вообще, способ интернационализации аля iget удобен для программиста - он лаконичен. Просто добавляешь
новую строку по месту использования и вуа-ля. Но вот нормальный ключ для такого способа сделать
проблематично. Единственно надежным способом было бы явное указание ключа рядом с текстом перевода
но тогда о лаконичности можно забыть. Различные способы неявного автоопределения ключа, типа "имя файла"
+ "имя функции" + "номер строчки в функции" - ненадежны, потому что переводы могут перепутаться при
внесении изменений в тело функции или файла.

Еще одна цель, которая преследовалась в процессе дизайна интернационализации - вынести из бизнес-кода
детали интернационализации. В общем случае речь идет о параметризованных переводах, типичный пример
- plural translations. Это когда перевод зависит от числительного: "1 яблоко", "2 яблока".

В перловом директе это обрабатывается так:

  iget( get_word_for_digit($camps_num, @{$push_messages_multi{'money_out_multi'}})

т.е. кусок интернационализации в виде get_word_for_digit вывалился в бизнес-код.
Хочелось сделать так, чтобы все ньюансы интернационализации оставались в модуле интернационализации.
В бизнесовом коде на джаве это будет выглядеть так:

  t.moneyOut(campsNum)
