Можно сконвертировать нативные Java-классы в XML/JSON/YAML?
Если бы я назвал этот вопрос "Идеальное решение задачи интернационализации Java-приложения", то его бы заминусовали и дали бы ссылку на какую-нибудь библиотеку. Но именно эта задача породила данный вопрос.
Я не знаю все готовых решений, но у многих есть есть один существенный недостаток - они статически невалидируемы. Это значит, что ассоциативные массивы с переводами на нужный язык пишутся в таких форматах, как XML или JSON, при этом до компиляции программы нельзя проверить, забыл ли программист что-то указать или не перепутал ли он ключ - это всплывёт только при компиляции, или, что ещё хуже, при запуске программы.
Итак, субъективно, но на концептуальном уровне идеальное решение интернационализации Java-приложения должно обладать такими свойствами:
- Статическая валидируемость. Это значит, что если программист забыл добавить какой-то перевод, или добавил лишний, или какой-то перепутал, то об этом будет известно ещё до того, как программу будут компилировать (грубо говоря, IDE должна распознать и подсветить ошибку).
- Динамическая подгружаемость файлов. Это особенность нужна, когда и статических строк, и языков очень много (то есть в общем случае). Включать в сборку все языки - незачем, ибо большинству пользователей нужен лишь один язык. Достаточно в сборку включить один язык по умолчанию, а остальные - подгрузить из интернета по необходимости (то есть в тот момент, как пользователь пожелает переключить язык интерфейса).
Пример решения на другом языке
Пока не знаю, позволяет ли это сделать Java, но на моём основном языке - TypeScript, такое возможно (в совокупности с Webpack).
- Создаём тип, в которым должны быть указаны все ключи:
type Translations {
applicationTitle: string;
dayOfWeek: {
sunday: string;
monday: string;
// ...
},
greet: (userName: string)=> string;
}
- Создаём TypeScript-файл под каждый язык. Явное указание типа не даст чего-то перепутать или забыть:
const russian: Translations = {
applicationTitle: "Приложение",
dayOfWeek: {
sunday: "воскресенье";
monday: "понедельник";
// ...
},
greet: (userName: string)=> `${userName}, доброго времени суток!`
}
Требование статической валидируемости соблюдено.
- Если речь идёт о веб-приложениях, то там разумно язык по умолчанию подшить в общую сборку, а остальные - динамически подгружать. Webpack позволяет это легко (если знать как) сделать. Да, подгружаться будут откомпилированные JavaScript-файлы, а не TypeScript, но в JavaScript-файлах ошибки быть не может, потому что иначе компилятор TypeScript-а их бы не скомпилировал (при определённых настройках). С десктопными Node.js приложениями, думаю, возможно нечто подобное - загрузить нужный JavaScript-файл, отсутствие ошибок в котором гарантированно предварительной компиляцией из TypeScript-а, и прочитать его.
Что насчет Java?
Я знаю в Java два инструмента обязывания: final (обязывает инициализировать поля в конструкторе) и abstract (обязывает реализовать методы. interface по концепции близок, по тому его сюда же). Объявить тип можно примерно так:
abstract class Translation {
final String applicationTitle;
final DaysOfWeek DaysOfWeek;
abstract greet(String userName);
}
Все переводы должны быть унаследованы от этого класса. Осталось их только сконвертировать в нужный формат. О том, как это сделать, и есть мой вопрос.
Проблематичный являются функции шаблонизации, например greet(String userName). Пока для меня аннотации - это магия, но возможно, они помогут решить эту проблему.