Поддержка мультиязычности в плагинах

Discussion in 'Разработка плагинов' started by fromgate, 4/1/16.

  1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
Dismiss Notice
We welcome you on our site. This site is devoted to the Nukkit project and all that is connected with him. Here you can communicate, download plugins, also many other things get acquainted! Register the account right now :3
  1. fromgate

    fromgate Administrator

    Messages:
    668
    Likes Received:
    187
    Поддержка мультиязычности в плагинах

    Хочу поговорить о создании плагинов с поддержкой нескольких языков.

    Зачем нужна поддержка нескольких языков
    • Даже если Вы будете делать плагин на "универсальном" языке - английском, всё равно пользователи плагина будут просить плагин на их родном языке. Или откажутся от Вашего плагина, если в нём нет поддержки их языка или хотя бы возможности перевести плагин.
    • Если не заложить поддержку языков заранее, то потом, когда количество сообщений вырастет многократно, а плагин обретет мировую популярность ;), то переделывать его будет не так-то просто.
    • Сейчас пока плагинов не много, то у нужного кому-то плагину не может и не существовать альтернативы на родном языке. Поэтому, даже если Вы пишите плагин в расчете на русскоязычную аудиторию, он вполне может приглянуться и тем, кто русского совсем не понимает. Почему-бы сразу это не предусмотреть?
    Какие способы организации поддержки нескольких языков бывают

    Основной подход к переводам такой. Создается один файл содержащий все типы сообщений в плагине (или если хотите программе), потом создаются аналогичных файлов, в которых сообщения переведены на другие языки.

    Использование стандартных средств Java
    Можно использовать стандартные средства Java для интернационализации приложений, почитать об этом можно на оффсайте.
    Сложного ничего нет, однако почему-то в мире bukkit-программирования использовался другой подход.

    Использование конфигураторов или HashMap
    Из опыта разработки плагинов для bukkit, я сталкивался с другими методами интернационализации. Довольно часто фактически используются встроенные методы конфигурации (в Bukkit - это класс YamlConfiguration, а в Nukkit — Config).

    Самый простой пример использования такого подхода будет выглядеть примерно так:
    Code:
    Config lng = new Config("russian.yml");
    String message = lng.getNested ("hello-message");
    
    Соответственно, теперь нужно выводить не само сообщение, а содержимое переменной message.
    Иногда так даже и использовали что-то вроде:
    Code:
    player.sendMessage (cfg.getNested("hello-message");
    
    На мой вкус это несколько громоздкий вариант. Поэтому я долгое время поступал немного иначе.
    Я создавал класс, содержащий один объект HashMap, в который вгружались все сообщения (в зависимости от языка).
    В общем-то не сильно отличается от использования встроенного конфигуратора (тем более, что в конфигураторы тоже построены на основе Map-коллекций).

    У всех этих подходов есть недостатки:
    • Для получение конечного текста происходит по текстовому идентификатору. Одна ошибка допущенная в текстовом идентификаторе приводит к тому, что результат будет совсем не тем, что ожидался. Причем, поскольку в процессе тетстирования плагинов Вы можете не заметить опечатку, то результат может выпезти через несколько месяцев, когда Вам сообщат об этом пользователи плагина.
    • Нужно синхронно добавлять текстовые идентификаторы и сообщения. Я часто сталкивался с ситуацией, когда я добавлял по ходу программирования какое-то количество идентификаторов, а потом начинал добавлять к ним текст. В итоге у меня часть идентификатров оставалась пропущенной и это тоже вылезало поздно.
    В конце-концов я в итоге сделал, универсальный для себя механизм, который и предлагаю Вашему вниманию.

    Использование ENUM в качестве хранилища сообщений
    Написав несколько плагинов с поддержкой файлов перевода, я понял, что именно мне нужно от системы переводов:
    1. Это должен быть один универсальный класс для всех сообщений;
    2. Класс должен позволять создать единый стиль сообщений для плагина (кто-то любит зелёный текст, кто-то всё пишет голубым и т.д.);
    3. По возможности процедура вывода текста должна быть как можно менее громоздкой. Я просто возненавидел конструкции вида player.sendMessage (TextFormat.colorize(language.get("message-id")));
    4. Было бы отлично, если бы опечатки и несуществующие идентификаторы выявлялись прямо в процессе редактирования кода;
    5. Необходимо, чтобы языковые файлы:
      • Могли находиться как в виде ресурса в jar-файле, так и в виде файла в папке плагина;
      • При появлении новых сообщений в плагине, для старых файлов перевода использовался англоязычный вариант.
    Самым лучшим вариантом, мне показалось использование в качестве основы для интернационализации - ENUM.
    Готовый код шаблона (именно шаблона — в процессе использования, его придётся редактировать, я разместил на github'е).
    Особенности у этого класса следующие:
    1. Поддержка неограниченного количества языков, возможность редактирования текста пользователем плагина.
    2. Поскольку каждое сообщение идентифицируется ключем ENUM'а, то "пропавших" или "забытых" сообщений быть не может. Они все будут отражены в файле перевода.
    3. Вывод сообщения игроку, в лог, бродкаст сообщения производится методами самого элемента ENUM. Грубо говоря сообщение само себя выводит. Это значительно сокращает код.
    4. Если выбран несуществующий (в ресурсах или в виде файла) язык, то будет создан файл с именем языка и сообщениями по умолчанию - у пользователя появится возможность самому перевести текст.
    5. Отсутствует возможность динамического добавления сообщений (это можно считать главным недостатком, который впрочем вытекает из главного же преимущества).

    Как пользоваться классом Message
    Его нужно добавить в проект. Смело добавляйте в собственный пакет. Просто создайте у себя класс, называющийся Message, и скопируйте содержимое с гитхаба.
    Свои сообщения надо будет добавлять прямо в этот класс, по аналогии со стандартными (которые уже там есть):
    Code:
    EXAMPLE_MESSAGE ("Example message"),
    
    Если Вам нужно сообщение, в котроом будут отражены какие-либо переменные, то места где их необходимо будет подставить, надо будет обозначить как: %1%, %2% и т.д.
    Code:
    HELLO_MESSAGE ("%1%, hello!"),
    HELLO_MESSAGE_RED ("%1%, hello!",'c','4'), // здесь hello - красное, имя - тёмно-красное
    HELLO_MESSAGE_GREEN ("%1%, hello!",'2'), // здесь и сообщение и имя - тёмно-зелёное
    
    В плагине, в коде метода onEnable() необходимо инициализировать Message:

    Code:
    @Override
    public void onEnable(){
    // Здесь выполняете всё, что касается вашего конфига
    Message.init (this);
    }
    
    Метод Message.init() выполняет следующее:
    • Считывает из файла config.yml значение параметров "general.language" (язык) и "general.debug" (true/false - включение выключение режима отладки)
    • Файл перевода, размещенный в виде ресурса в файле плагина (<плагин>.jar/lang/<язык>.lng) в папку плагина.
    • Считывает из соответствующего файла перевода сообщения и дополняет файл перевода не переведенным сообщениями (допустим они появились в плагине после обновления версии).
    Сразу после инициализации можно пользоваться сообщениями.
    Каждое сообщение (элемент enum), может:
    • Вывести игроку (или в консоль): Message.MESSAGE.pring(player);
    • Вывести у игрока в центре экрана: Message.MESSAGE.tip(player);
    • Распечатать в логе сервера: Message.MESSAGE.log();
    • Вывести отладочное сообщение в лог сервера: Message.MESSAGE.debug();
    • Вывести сообщение всем игрокам на сервере, у которых есть определенный пермишен: Message.MESSAGE.broadcast ("perm.ission");
    • Быть представленно в виде текста: String msg = Message.MESSAGE.getText();
    При этом, есть дополнительные возможности по управлению сообщениями. Всем этим методам параметрами могут передаваться различные объекты.
    Это дает возможность, вывода значения переменных и т.д.
    Допустим, Вам необходимо вывести в сообщении имя игрока и у Вас есть код подобный этому:
    Code:
    player.sendMessage (TextFormat.RED+player.getName()+", hello!");
    
    то его нужно преобразовать в что-то вроде
    Code:
    Message.HELLO.print(player,player.getName());
    
    В качестве параметров, могут выступать любые объекты. При этом типы double и float будут отображены в "усеченном" виде - будут показаны только два числа после запятой. А объект класса Location будет представлено в формате [level] x,y,z.
    При выводе сообщение будет иметь стандартные цвета, которые задаются здесь: https://github.com/fromgate/Message-nukkit/blob/master/src/Message.java#L39-L40
    Используются два цвета (они обозначены двумя переменными c1 и с2). Первый символ c1 используется для обозначения цвета основного текста, а c2 - для обозначения переменных. Эти цвета можно изменять при выводе текста, если указывать цветовые символы в качестве параметров
    Т.е. если Вы хотите вывести сообщение из примера выше в красных цветах, надо выполнить следующий код:
    Code:
    Message.HELLO.print(player,'c','4',player.getName());
    
    В этом случае сообщение будет вывено красным, а имя игрока - тёмно красным.

    Кроме поддержки кодов, есть ещё несколько управляющих "слов":
    • "SKIPCOLOR" - используется чтобы отключить использование стандартных цветов (надо будет самому раскрашивать цвет и параметры)
    • "NOCOLOR" (или "NOCOLORS") - цвет будет вообще без цвета. Даже если он там был.
    • "FULLFLOAT" - Отображать числа с плавающей запятой (float, double) полностью (0.1483294829 вместо 0.15)

    Плагины которые используют Message:

    Примечание
    В деле организации мультиязычности технический вопрос - это не самый главный. Главное ещё привлечь переводчиков (если плагин интересный и популярный с этим проблем не будет) и предоставить им удобную возможность для перевода.
    Как я этот вопрос решил для себя, я описал тут: http://nukkit.ru/threads/kak-organizovat-perevod-plaginov-na-drugie-jazyki.336/

    P.S. Возможно предложенный вариант не самый лучший, способ интернационализации. Буду рад если будут предложены и другие варианты.
     
    Last edited: 25/8/16
    Leonidius, SkyDreamer and Pub4Game like this.
  2. fromgate

    fromgate Administrator

    Messages:
    668
    Likes Received:
    187
    Добавил возможность дополинтельной инициализации цвета для сообщений. Т.е. если нужно, чтобы сообщение сразу было красным можно описывать так:

    Code:
    HELLO_MESSAGE ("%1%, hello!",'c','4'), // здесь hello - красное, имя - тёмно-красное
    HELLO_MESSAGE3 ("%1%, hello!",'2'), // здесь и сообщение и имя - тёмно-зелёное
    
     
  3. Bruno

    Bruno Пользователь

    Messages:
    13
    Likes Received:
    1
    Minecraft:
    Bruno
    Замечательно!
     
  4. Dereku

    Dereku Пользователь

    Messages:
    5
    Likes Received:
    3
    Minecraft:
    _Dereku
    .properties, и пускай переводят как душе угодно. А самое главное - независимость от платформы, ибо properties - есть жабовский конфиг.
     
  5. fromgate

    fromgate Administrator

    Messages:
    668
    Likes Received:
    187
    Да хранить-то можно в чём угодно. Меня именно заботит проблема удобства в процессе программирования - ENUM не даёт ошибиться в ключе или "забыть его указать" и т.п.
    Т.е. такой способ организации.
     
  6. Dereku

    Dereku Пользователь

    Messages:
    5
    Likes Received:
    3
    Minecraft:
    _Dereku
    enum имеет место быть, но придётся ручками править сам класс. Мне же проще использовать строки, ибо при каждом отсутствующем ключе локализации я пилю выхлоп в консоль.
     
  7. fromgate

    fromgate Administrator

    Messages:
    668
    Likes Received:
    187
    Ага, вот именно это меня и бесит ;) После компиляции идти, открывать файл с соббщениями и т.п.
    Дело вкуса одним словом.
     
  8. Leonidius

    Leonidius Developer (Level 2)

    Messages:
    56
    Likes Received:
    18
    Minecraft:
    Leonidius20
    У меня возникла проблема с использованием этого класса, а именно: перед каждым сообщением в чат добавляется "ull". На счёт сообщений в логах не знаю, не проверял. Как это исправить?


    P.S. Ничего себе у меня опечаток в английском тексте вышло...
    [​IMG]
     
  9. fromgate

    fromgate Administrator

    Messages:
    668
    Likes Received:
    187
    В onEnable плагина надо добавить Message.init (this);
    Префикс берётся из имени плагина, насколько я помню ;)
     
  10. Leonidius

    Leonidius Developer (Level 2)

    Messages:
    56
    Likes Received:
    18
    Minecraft:
    Leonidius20
    У меня есть Message.init(this);
    А плагин не называется ull, у него другое название. Как-то можно убрать этот префикс?
     
  11. Leonidius

    Leonidius Developer (Level 2)

    Messages:
    56
    Likes Received:
    18
    Minecraft:
    Leonidius20
    У меня выходит избавиться от этих лишних букв, только если использовать NOCOLOR. Видимо, что-то с цветом не то
     
  12. fromgate

    fromgate Administrator

    Messages:
    668
    Likes Received:
    187
    Leonidius likes this.
  13. Leonidius

    Leonidius Developer (Level 2)

    Messages:
    56
    Likes Received:
    18
    Minecraft:
    Leonidius20

Share This Page