Generate handbooks on every launch, fix html docs

This commit is contained in:
AnimeGitB 2022-08-03 17:11:04 +09:30 committed by Luke H-W
parent e963419956
commit c1ff7332fe
5 changed files with 176 additions and 185 deletions

View File

@ -89,13 +89,13 @@ public final class Grasscutter {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
Crypto.loadKeys(); // Load keys from buffers. Crypto.loadKeys(); // Load keys from buffers.
Tools.createGmHandbooks();
// Parse arguments. // Parse arguments.
boolean exitEarly = false; boolean exitEarly = false;
for (String arg : args) { for (String arg : args) {
switch (arg.toLowerCase()) { switch (arg.toLowerCase()) {
case "-handbook", "-handbooks" -> { case "-handbook", "-handbooks" -> {
Tools.createGmHandbooks();
exitEarly = true; exitEarly = true;
} }
case "-dumppacketids" -> { case "-dumppacketids" -> {

View File

@ -3,6 +3,8 @@ package emu.grasscutter.command;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent; import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent;
import emu.grasscutter.utils.Language;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import java.util.List; import java.util.List;
@ -68,10 +70,13 @@ public interface CommandHandler {
return this.getClass().getAnnotation(Command.class).label(); return this.getClass().getAnnotation(Command.class).label();
} }
default String getDescriptionString(Player player) { default String getDescriptionKey() {
Command annotation = this.getClass().getAnnotation(Command.class); Command annotation = this.getClass().getAnnotation(Command.class);
String key = "commands.%s.description".formatted(annotation.label()); return "commands.%s.description".formatted(annotation.label());
return translate(player, key); }
default String getDescriptionString(Player player) {
return translate(player, getDescriptionKey());
} }
/** /**

View File

@ -1,154 +1,117 @@
package emu.grasscutter.server.http.documentation; package emu.grasscutter.server.http.documentation;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData; import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Language;
import express.http.Request; import express.http.Request;
import express.http.Response; import express.http.Response;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import static emu.grasscutter.config.Configuration.DOCUMENT_LANGUAGE; import static emu.grasscutter.config.Configuration.DOCUMENT_LANGUAGE;
import static emu.grasscutter.config.Configuration.RESOURCE;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
final class GachaMappingRequestHandler implements DocumentationHandler { final class GachaMappingRequestHandler implements DocumentationHandler {
private List<String> gachaJsons;
private Map<Long, String> map;
GachaMappingRequestHandler() { GachaMappingRequestHandler() {
final String textMapFile = "TextMap/TextMap" + DOCUMENT_LANGUAGE + ".json"; this.gachaJsons = createGachaMappingJsons();
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(
Utils.toFilePath(RESOURCE(textMapFile))), StandardCharsets.UTF_8)) {
map = Grasscutter.getGsonFactory().fromJson(fileReader,
new TypeToken<Map<Long, String>>() {
}.getType());
} catch (IOException e) {
Grasscutter.getLogger().warn("Resource does not exist: " + textMapFile);
map = new HashMap<>();
}
} }
@Override @Override
public void handle(Request request, Response response) { public void handle(Request request, Response response) {
if (map.isEmpty()) { final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0); // TODO: This should really be based off the client language somehow
response.status(500);
} else {
response.set("Content-Type", "application/json") response.set("Content-Type", "application/json")
.ctx() .ctx()
.result(createGachaMappingJson()); .result(gachaJsons.get(langIdx));
}
} }
private String createGachaMappingJson() { private List<String> createGachaMappingJsons() {
List<Integer> list; final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES;
final Language.TextStrings CHARACTER = Language.getTextMapKey(4233146695L); // "Character" in EN
final StringBuilder sb = new StringBuilder(); final Language.TextStrings WEAPON = Language.getTextMapKey(4231343903L); // "Weapon" in EN
list = new ArrayList<>(GameData.getAvatarDataMap().keySet()); final Language.TextStrings STANDARD_WISH = Language.getTextMapKey(332935371L); // "Standard Wish" in EN
Collections.sort(list); final Language.TextStrings CHARACTER_EVENT_WISH = Language.getTextMapKey(2272170627L); // "Character Event Wish" in EN
final Language.TextStrings CHARACTER_EVENT_WISH_2 = Language.getTextMapKey(3352513147L); // "Character Event Wish-2" in EN
final String newLine = System.lineSeparator(); final Language.TextStrings WEAPON_EVENT_WISH = Language.getTextMapKey(2864268523L); // "Weapon Event Wish" in EN
final List<StringBuilder> sbs = new ArrayList<>(NUM_LANGUAGES);
// if the user made choices for language, I assume it's okay to assign his/her selected language to "en-us" for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
// since it's the fallback language and there will be no difference in the gacha record page. sbs.add(new StringBuilder("{\n")); // Web requests should never need Windows line endings
// The enduser can still modify the `gacha_mappings.js` directly to enable multilingual for the gacha record system.
sb.append("{").append(newLine);
// Avatars // Avatars
boolean first = true; IntList list = new IntArrayList(GameData.getAvatarDataMap().keySet().intStream().sorted().toArray());
for (Integer id : list) { for (int id : list) {
AvatarData data = GameData.getAvatarDataMap().get(id); AvatarData data = GameData.getAvatarDataMap().get(id);
int avatarID = data.getId(); int avatarID = data.getId();
if (avatarID >= 11000000) { // skip test avatar if (avatarID >= 11000000) { // skip test avatar
continue; continue;
} }
if (first) { // skip adding comma for the first element String color = switch (data.getQualityType()) {
first = false; case "QUALITY_PURPLE" -> "purple";
} else { case "QUALITY_ORANGE" -> "yellow";
sb.append(","); case "QUALITY_BLUE" -> "blue";
} default -> "";
String color; };
switch (data.getQualityType()) { Language.TextStrings avatarName = Language.getTextMapKey(data.getNameTextMapHash());
case "QUALITY_PURPLE": for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++) {
color = "purple"; sbs.get(langIdx)
break; .append("\"")
case "QUALITY_ORANGE":
color = "yellow";
break;
case "QUALITY_BLUE":
default:
color = "blue";
}
// Got the magic number 4233146695 from manually search in the json file
sb.append("\"")
.append(avatarID % 1000 + 1000) .append(avatarID % 1000 + 1000)
.append("\" : [\"") .append("\" : [\"")
.append(map.get(data.getNameTextMapHash())) .append(avatarName.get(langIdx))
.append("(") .append("(")
.append(map.get(4233146695L)) .append(CHARACTER.get(langIdx))
.append(")\", \"") .append(")\", \"")
.append(color) .append(color)
.append("\"]") .append("\"],\n");
.append(newLine); }
} }
list = new ArrayList<>(GameData.getItemDataMap().keySet()); list = new IntArrayList(GameData.getItemDataMap().keySet().intStream().sorted().toArray());
Collections.sort(list);
// Weapons // Weapons
for (Integer id : list) { for (int id : list) {
ItemData data = GameData.getItemDataMap().get(id); ItemData data = GameData.getItemDataMap().get(id);
if (data.getId() <= 11101 || data.getId() >= 20000) { if (data.getId() <= 11101 || data.getId() >= 20000) {
continue; //skip non weapon items continue; //skip non weapon items
} }
String color; String color = switch (data.getRankLevel()) {
case 3 -> "blue";
switch (data.getRankLevel()) { case 4 -> "purple";
case 3: case 5 -> "yellow";
color = "blue"; default -> null;
break; };
case 4: if (color == null) continue; // skip unnecessary entries
color = "purple"; Language.TextStrings weaponName = Language.getTextMapKey(data.getNameTextMapHash());
break; for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++) {
case 5: sbs.get(langIdx)
color = "yellow"; .append("\"")
break;
default:
continue; // skip unnecessary entries
}
// Got the magic number 4231343903 from manually search in the json file
sb.append(",\"")
.append(data.getId()) .append(data.getId())
.append("\" : [\"") .append("\" : [\"")
.append(map.get(data.getNameTextMapHash()).replaceAll("\"", "")) .append(weaponName.get(langIdx).replaceAll("\"", "\\\\\""))
.append("(") .append("(")
.append(map.get(4231343903L)) .append(WEAPON.get(langIdx))
.append(")\",\"") .append(")\",\"")
.append(color) .append(color)
.append("\"]") .append("\"],\n");
.append(newLine);
} }
sb.append(",\"200\": \"") }
.append(map.get(332935371L))
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++) {
sbs.get(langIdx)
.append("\"200\": \"")
.append(STANDARD_WISH.get(langIdx))
.append("\", \"301\": \"") .append("\", \"301\": \"")
.append(map.get(2272170627L)) .append(CHARACTER_EVENT_WISH.get(langIdx))
.append("\", \"400\": \"")
.append(CHARACTER_EVENT_WISH_2.get(langIdx))
.append("\", \"302\": \"") .append("\", \"302\": \"")
.append(map.get(2864268523L)) .append(WEAPON_EVENT_WISH.get(langIdx))
.append("\"") .append("\"\n}\n");
.append("}\n}") }
.append(newLine); return sbs.stream().map(StringBuilder::toString).toList();
return sb.toString();
} }
} }

View File

@ -1,9 +1,7 @@
package emu.grasscutter.server.http.documentation; package emu.grasscutter.server.http.documentation;
import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.CommandMap; import emu.grasscutter.command.CommandMap;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
@ -12,112 +10,129 @@ import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.data.excels.SceneData; import emu.grasscutter.data.excels.SceneData;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import express.http.Request; import express.http.Request;
import express.http.Response; import express.http.Response;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.ArrayList;
import java.util.Map; import java.util.List;
import java.util.stream.Collectors;
final class HandbookRequestHandler implements DocumentationHandler { final class HandbookRequestHandler implements DocumentationHandler {
private List<String> handbookHtmls;
private final String template; private final String template;
private Map<Long, String> map;
public HandbookRequestHandler() { public HandbookRequestHandler() {
final File templateFile = new File(Utils.toFilePath(DATA("documentation/handbook.html"))); final File templateFile = new File(Utils.toFilePath(DATA("documentation/handbook.html")));
if (templateFile.exists()) { if (templateFile.exists()) {
template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8); this.template = new String(FileUtils.read(templateFile), StandardCharsets.UTF_8);
this.handbookHtmls = generateHandbookHtmls();
} else { } else {
Grasscutter.getLogger().warn("File does not exist: " + templateFile); Grasscutter.getLogger().warn("File does not exist: " + templateFile);
template = null; this.template = null;
}
final String textMapFile = "TextMap/TextMap" + DOCUMENT_LANGUAGE + ".json";
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(
Utils.toFilePath(RESOURCE(textMapFile))), StandardCharsets.UTF_8)) {
map = Grasscutter.getGsonFactory()
.fromJson(fileReader, new TypeToken<Map<Long, String>>() {
}.getType());
} catch (IOException e) {
Grasscutter.getLogger().warn("Resource does not exist: " + textMapFile);
map = new HashMap<>();
} }
} }
@Override @Override
public void handle(Request request, Response response) { public void handle(Request request, Response response) {
final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0); // TODO: This should really be based off the client language somehow
if (template == null) { if (template == null) {
response.status(500); response.status(500);
return; } else {
response.send(handbookHtmls.get(langIdx));
}
} }
final CommandMap cmdMap = new CommandMap(true); private List<String> generateHandbookHtmls() {
final int NUM_LANGUAGES = Language.TextStrings.NUM_LANGUAGES;
final List<String> output = new ArrayList<>(NUM_LANGUAGES);
final List<Language> languages = Language.TextStrings.getLanguages();
final List<StringBuilder> sbs = new ArrayList<>(NUM_LANGUAGES);
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.add(new StringBuilder(""));
// Commands table
new CommandMap(true).getHandlersAsList().forEach(cmd -> {
String label = cmd.getLabel();
String descKey = cmd.getDescriptionKey();
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + label + "</code></td><td>" + languages.get(langIdx).get(descKey) + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> cmdsTable = sbs.stream().map(StringBuilder::toString).toList();
// Avatars table
final Int2ObjectMap<AvatarData> avatarMap = GameData.getAvatarDataMap(); final Int2ObjectMap<AvatarData> avatarMap = GameData.getAvatarDataMap();
sbs.forEach(sb -> sb.setLength(0));
avatarMap.keySet().intStream().sorted().mapToObj(avatarMap::get).forEach(data -> {
int id = data.getId();
Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> avatarsTable = sbs.stream().map(StringBuilder::toString).toList();
// Items table
final Int2ObjectMap<ItemData> itemMap = GameData.getItemDataMap(); final Int2ObjectMap<ItemData> itemMap = GameData.getItemDataMap();
sbs.forEach(sb -> sb.setLength(0));
itemMap.keySet().intStream().sorted().mapToObj(itemMap::get).forEach(data -> {
int id = data.getId();
Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> itemsTable = sbs.stream().map(StringBuilder::toString).toList();
// Scenes table
final Int2ObjectMap<SceneData> sceneMap = GameData.getSceneDataMap(); final Int2ObjectMap<SceneData> sceneMap = GameData.getSceneDataMap();
sceneMap.keySet().intStream().sorted().mapToObj(sceneMap::get).forEach(data -> {
int id = data.getId();
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + data.getScriptData() + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> scenesTable = sbs.stream().map(StringBuilder::toString).toList();
// Monsters table
final Int2ObjectMap<MonsterData> monsterMap = GameData.getMonsterDataMap(); final Int2ObjectMap<MonsterData> monsterMap = GameData.getMonsterDataMap();
monsterMap.keySet().intStream().sorted().mapToObj(monsterMap::get).forEach(data -> {
int id = data.getId();
Language.TextStrings name = Language.getTextMapKey(data.getNameTextMapHash());
for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++)
sbs.get(langIdx).append("<tr><td><code>" + id + "</code></td><td>" + name.get(langIdx) + "</td></tr>\n");
});
sbs.forEach(sb -> sb.setLength(sb.length()-1)); // Remove trailing \n
final List<String> monstersTable = sbs.stream().map(StringBuilder::toString).toList();
// Add translated title etc. to the page. // Add translated title etc. to the page.
String content = template.replace("{{TITLE}}", translate("documentation.handbook.title")) for (int langIdx = 0; langIdx < NUM_LANGUAGES; langIdx++) {
.replace("{{TITLE_COMMANDS}}", translate("documentation.handbook.title_commands")) Language lang = languages.get(langIdx);
.replace("{{TITLE_AVATARS}}", translate("documentation.handbook.title_avatars")) output.add(template
.replace("{{TITLE_ITEMS}}", translate("documentation.handbook.title_items")) .replace("{{TITLE}}", lang.get("documentation.handbook.title"))
.replace("{{TITLE_SCENES}}", translate("documentation.handbook.title_scenes")) .replace("{{TITLE_COMMANDS}}", lang.get("documentation.handbook.title_commands"))
.replace("{{TITLE_MONSTERS}}", translate("documentation.handbook.title_monsters")) .replace("{{TITLE_AVATARS}}", lang.get("documentation.handbook.title_avatars"))
.replace("{{HEADER_ID}}", translate("documentation.handbook.header_id")) .replace("{{TITLE_ITEMS}}", lang.get("documentation.handbook.title_items"))
.replace("{{HEADER_COMMAND}}", translate("documentation.handbook.header_command")) .replace("{{TITLE_SCENES}}", lang.get("documentation.handbook.title_scenes"))
.replace("{{HEADER_DESCRIPTION}}", .replace("{{TITLE_MONSTERS}}", lang.get("documentation.handbook.title_monsters"))
translate("documentation.handbook.header_description")) .replace("{{HEADER_ID}}", lang.get("documentation.handbook.header_id"))
.replace("{{HEADER_AVATAR}}", translate("documentation.handbook.header_avatar")) .replace("{{HEADER_COMMAND}}", lang.get("documentation.handbook.header_command"))
.replace("{{HEADER_ITEM}}", translate("documentation.handbook.header_item")) .replace("{{HEADER_DESCRIPTION}}", lang.get("documentation.handbook.header_description"))
.replace("{{HEADER_SCENE}}", translate("documentation.handbook.header_scene")) .replace("{{HEADER_AVATAR}}", lang.get("documentation.handbook.header_avatar"))
.replace("{{HEADER_MONSTER}}", translate("documentation.handbook.header_monster")) .replace("{{HEADER_ITEM}}", lang.get("documentation.handbook.header_item"))
.replace("{{HEADER_SCENE}}", lang.get("documentation.handbook.header_scene"))
.replace("{{HEADER_MONSTER}}", lang.get("documentation.handbook.header_monster"))
// Commands table // Commands table
.replace("{{COMMANDS_TABLE}}", cmdMap.getHandlersAsList() .replace("{{COMMANDS_TABLE}}", cmdsTable.get(langIdx))
.stream() .replace("{{AVATARS_TABLE}}", avatarsTable.get(langIdx))
.map(cmd -> "<tr><td><code>" + cmd.getLabel() + "</code></td><td>" + .replace("{{ITEMS_TABLE}}", itemsTable.get(langIdx))
cmd.getDescriptionString(null) + "</td></tr>") .replace("{{SCENES_TABLE}}", scenesTable.get(langIdx))
.collect(Collectors.joining("\n"))) .replace("{{MONSTERS_TABLE}}", monstersTable.get(langIdx))
// Avatars table );
.replace("{{AVATARS_TABLE}}", GameData.getAvatarDataMap().keySet() }
.intStream() return output;
.sorted()
.mapToObj(avatarMap::get)
.map(data -> "<tr><td><code>" + data.getId() + "</code></td><td>" +
map.get(data.getNameTextMapHash()) + "</td></tr>")
.collect(Collectors.joining("\n")))
// Items table
.replace("{{ITEMS_TABLE}}", GameData.getItemDataMap().keySet()
.intStream()
.sorted()
.mapToObj(itemMap::get)
.map(data -> "<tr><td><code>" + data.getId() + "</code></td><td>" +
map.get(data.getNameTextMapHash()) + "</td></tr>")
.collect(Collectors.joining("\n")))
// Scenes table
.replace("{{SCENES_TABLE}}", GameData.getSceneDataMap().keySet()
.intStream()
.sorted()
.mapToObj(sceneMap::get)
.map(data -> "<tr><td><code>" + data.getId() + "</code></td><td>" +
data.getScriptData() + "</td></tr>")
.collect(Collectors.joining("\n")))
.replace("{{MONSTERS_TABLE}}", GameData.getMonsterDataMap().keySet()
.intStream()
.sorted()
.mapToObj(monsterMap::get)
.map(data -> "<tr><td><code>" + data.getId() + "</code></td><td>" +
map.get(data.getNameTextMapHash()) + "</td></tr>")
.collect(Collectors.joining("\n")));
response.send(content);
} }
} }

View File

@ -240,7 +240,7 @@ public final class Language {
private static final int TEXTMAP_CACHE_VERSION = 0x9CCACE02; private static final int TEXTMAP_CACHE_VERSION = 0x9CCACE02;
@EqualsAndHashCode public static class TextStrings implements Serializable { @EqualsAndHashCode public static class TextStrings implements Serializable {
public static final String[] ARR_LANGUAGES = {"EN", "CHS", "CHT", "JP", "KR", "DE", "ES", "FR", "ID", "PT", "RU", "TH", "VI"}; public static final String[] ARR_LANGUAGES = {"EN", "CHS", "CHT", "JP", "KR", "DE", "ES", "FR", "ID", "PT", "RU", "TH", "VI"};
public static final String[] ARR_GC_LANGUAGES = {"en-US", "zh-CN", "zh-TW", "JP", "KR", "DE", "es-ES", "fr-FR", "ID", "PT", "ru-RU", "TH", "VI"}; public static final String[] ARR_GC_LANGUAGES = {"en-US", "zh-CN", "zh-TW", "ja-JP", "ko-KR", "DE", "es-ES", "fr-FR", "ID", "PT", "ru-RU", "TH", "VI"};
public static final int NUM_LANGUAGES = ARR_LANGUAGES.length; public static final int NUM_LANGUAGES = ARR_LANGUAGES.length;
public static final List<String> LIST_LANGUAGES = Arrays.asList(ARR_LANGUAGES); public static final List<String> LIST_LANGUAGES = Arrays.asList(ARR_LANGUAGES);
public static final Object2IntMap<String> MAP_LANGUAGES = // Map "EN": 0, "CHS": 1, ..., "VI": 12 public static final Object2IntMap<String> MAP_LANGUAGES = // Map "EN": 0, "CHS": 1, ..., "VI": 12
@ -276,6 +276,14 @@ public final class Language {
} }
} }
public static List<Language> getLanguages() {
return Arrays.stream(ARR_GC_LANGUAGES).map(Language::getLanguage).toList();
}
public String get(int languageIndex) {
return strings[languageIndex];
}
public String get(String languageCode) { public String get(String languageCode) {
return strings[MAP_LANGUAGES.getOrDefault(languageCode, 0)]; return strings[MAP_LANGUAGES.getOrDefault(languageCode, 0)];
} }