[Idea]: Folia support for OpenInv #196
@@ -28,9 +28,10 @@ import com.lishid.openinv.internal.ISpecialEnderChest;
|
|||||||
import com.lishid.openinv.internal.ISpecialInventory;
|
import com.lishid.openinv.internal.ISpecialInventory;
|
||||||
import com.lishid.openinv.internal.ISpecialPlayerInventory;
|
import com.lishid.openinv.internal.ISpecialPlayerInventory;
|
||||||
import com.lishid.openinv.util.ConfigUpdater;
|
import com.lishid.openinv.util.ConfigUpdater;
|
||||||
import com.lishid.openinv.util.LanguageManager;
|
|
||||||
import com.lishid.openinv.util.Permissions;
|
import com.lishid.openinv.util.Permissions;
|
||||||
import com.lishid.openinv.util.StringMetric;
|
import com.lishid.openinv.util.StringMetric;
|
||||||
|
import com.lishid.openinv.util.lang.LanguageManager;
|
||||||
|
import com.lishid.openinv.util.lang.Replacement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -463,7 +464,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
|
|||||||
public @Nullable String getLocalizedMessage(
|
public @Nullable String getLocalizedMessage(
|
||||||
@NotNull CommandSender sender,
|
@NotNull CommandSender sender,
|
||||||
@NotNull String key,
|
@NotNull String key,
|
||||||
String @NotNull ... replacements) {
|
Replacement @NotNull ... replacements) {
|
||||||
return this.languageManager.getValue(key, getLocale(sender), replacements);
|
return this.languageManager.getValue(key, getLocale(sender), replacements);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,7 +484,7 @@ public class OpenInv extends JavaPlugin implements IOpenInv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, String @NotNull... replacements) {
|
public void sendMessage(@NotNull CommandSender sender, @NotNull String key, Replacement @NotNull... replacements) {
|
||||||
String message = getLocalizedMessage(sender, key, replacements);
|
String message = getLocalizedMessage(sender, key, replacements);
|
||||||
|
|
||||||
if (message != null && !message.isEmpty()) {
|
if (message != null && !message.isEmpty()) {
|
||||||
|
@@ -18,6 +18,7 @@ package com.lishid.openinv.commands;
|
|||||||
|
|
||||||
import com.lishid.openinv.OpenInv;
|
import com.lishid.openinv.OpenInv;
|
||||||
import com.lishid.openinv.util.TabCompleter;
|
import com.lishid.openinv.util.TabCompleter;
|
||||||
|
import com.lishid.openinv.util.lang.Replacement;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
@@ -69,7 +70,11 @@ public class ContainerSettingCommand implements TabExecutor {
|
|||||||
onOff = String.valueOf(getSetting.test(player));
|
onOff = String.valueOf(getSetting.test(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.sendMessage(sender, "messages.info.settingState","%setting%", any ? "AnyContainer" : "SilentContainer", "%state%", onOff);
|
plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
"messages.info.settingState",
|
||||||
|
new Replacement("%setting%", any ? "AnyContainer" : "SilentContainer"),
|
||||||
|
new Replacement("%state%", onOff));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import com.lishid.openinv.OpenInv;
|
|||||||
import com.lishid.openinv.internal.ISpecialInventory;
|
import com.lishid.openinv.internal.ISpecialInventory;
|
||||||
import com.lishid.openinv.util.Permissions;
|
import com.lishid.openinv.util.Permissions;
|
||||||
import com.lishid.openinv.util.TabCompleter;
|
import com.lishid.openinv.util.TabCompleter;
|
||||||
|
import com.lishid.openinv.util.lang.Replacement;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -175,16 +176,20 @@ public class OpenInvCommand implements TabExecutor {
|
|||||||
// Protected check
|
// Protected check
|
||||||
if (!Permissions.OVERRIDE.hasPermission(player)
|
if (!Permissions.OVERRIDE.hasPermission(player)
|
||||||
&& Permissions.EXEMPT.hasPermission(onlineTarget)) {
|
&& Permissions.EXEMPT.hasPermission(onlineTarget)) {
|
||||||
plugin.sendMessage(player, "messages.error.permissionExempt",
|
plugin.sendMessage(
|
||||||
"%target%", onlineTarget.getDisplayName());
|
player,
|
||||||
|
"messages.error.permissionExempt",
|
||||||
|
new Replacement("%target%", onlineTarget.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crossworld check
|
// Crossworld check
|
||||||
if (!Permissions.CROSSWORLD.hasPermission(player)
|
if (!Permissions.CROSSWORLD.hasPermission(player)
|
||||||
&& !onlineTarget.getWorld().equals(player.getWorld())) {
|
&& !onlineTarget.getWorld().equals(player.getWorld())) {
|
||||||
plugin.sendMessage(player, "messages.error.permissionCrossWorld",
|
plugin.sendMessage(
|
||||||
"%target%", onlineTarget.getDisplayName());
|
player,
|
||||||
|
"messages.error.permissionCrossWorld",
|
||||||
|
new Replacement("%target%", onlineTarget.getDisplayName()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ package com.lishid.openinv.commands;
|
|||||||
|
|
||||||
import com.lishid.openinv.OpenInv;
|
import com.lishid.openinv.OpenInv;
|
||||||
import com.lishid.openinv.util.TabCompleter;
|
import com.lishid.openinv.util.TabCompleter;
|
||||||
|
import com.lishid.openinv.util.lang.Replacement;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.Chunk;
|
||||||
@@ -57,7 +58,10 @@ public class SearchContainerCommand implements TabExecutor {
|
|||||||
Material material = Material.getMaterial(args[0].toUpperCase());
|
Material material = Material.getMaterial(args[0].toUpperCase());
|
||||||
|
|
||||||
if (material == null) {
|
if (material == null) {
|
||||||
plugin.sendMessage(sender, "messages.error.invalidMaterial", "%target%", args[0]);
|
plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
"messages.error.invalidMaterial",
|
||||||
|
new Replacement("%target%", args[0]));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,13 +104,18 @@ public class SearchContainerCommand implements TabExecutor {
|
|||||||
if (locations.length() > 0) {
|
if (locations.length() > 0) {
|
||||||
locations.delete(locations.length() - 2, locations.length());
|
locations.delete(locations.length() - 2, locations.length());
|
||||||
} else {
|
} else {
|
||||||
plugin.sendMessage(sender, "messages.info.container.noMatches",
|
plugin.sendMessage(
|
||||||
"%target%", material.name());
|
sender,
|
||||||
|
"messages.info.container.noMatches",
|
||||||
|
new Replacement("%target%", material.name()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.sendMessage(sender, "messages.info.container.matches",
|
plugin.sendMessage(
|
||||||
"%target%", material.name(), "%detail%", locations.toString());
|
sender,
|
||||||
|
"messages.info.container.matches",
|
||||||
|
new Replacement("%target%", material.name()),
|
||||||
|
new Replacement("%detail%", locations.toString()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ package com.lishid.openinv.commands;
|
|||||||
|
|
||||||
import com.lishid.openinv.OpenInv;
|
import com.lishid.openinv.OpenInv;
|
||||||
import com.lishid.openinv.util.TabCompleter;
|
import com.lishid.openinv.util.TabCompleter;
|
||||||
|
import com.lishid.openinv.util.lang.Replacement;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
@@ -114,14 +115,18 @@ public class SearchEnchantCommand implements TabExecutor {
|
|||||||
// Matches found, delete trailing comma and space
|
// Matches found, delete trailing comma and space
|
||||||
players.delete(players.length() - 2, players.length());
|
players.delete(players.length() - 2, players.length());
|
||||||
} else {
|
} else {
|
||||||
plugin.sendMessage(sender, "messages.info.player.noMatches",
|
plugin.sendMessage(
|
||||||
"%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level);
|
sender,
|
||||||
|
"messages.info.player.noMatches",
|
||||||
|
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.sendMessage(sender, "messages.info.player.matches",
|
plugin.sendMessage(
|
||||||
"%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level,
|
sender,
|
||||||
"%detail%", players.toString());
|
"messages.info.player.matches",
|
||||||
|
new Replacement("%target%", (enchant != null ? enchant.getKey().toString() : "") + " >= " + level),
|
||||||
|
new Replacement("%detail%", players.toString()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2011-2021 lishid. All rights reserved.
|
* Copyright (C) 2011-2022 lishid. All rights reserved.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,6 +18,7 @@ package com.lishid.openinv.commands;
|
|||||||
|
|
||||||
import com.lishid.openinv.OpenInv;
|
import com.lishid.openinv.OpenInv;
|
||||||
import com.lishid.openinv.util.TabCompleter;
|
import com.lishid.openinv.util.TabCompleter;
|
||||||
|
import com.lishid.openinv.util.lang.Replacement;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
@@ -46,7 +47,10 @@ public class SearchInvCommand implements TabExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (material == null) {
|
if (material == null) {
|
||||||
plugin.sendMessage(sender, "messages.error.invalidMaterial", "%target%", args.length > 0 ? args[0] : "null");
|
plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
"messages.error.invalidMaterial",
|
||||||
|
new Replacement("%target%", args.length > 0 ? args[0] : "null"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +60,10 @@ public class SearchInvCommand implements TabExecutor {
|
|||||||
try {
|
try {
|
||||||
count = Integer.parseInt(args[1]);
|
count = Integer.parseInt(args[1]);
|
||||||
} catch (NumberFormatException ex) {
|
} catch (NumberFormatException ex) {
|
||||||
plugin.sendMessage(sender, "messages.error.invalidNumber", "%target%", args[1]);
|
plugin.sendMessage(
|
||||||
|
sender,
|
||||||
|
"messages.error.invalidNumber",
|
||||||
|
new Replacement("%target%", args[1]));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,13 +81,18 @@ public class SearchInvCommand implements TabExecutor {
|
|||||||
if (players.length() > 0) {
|
if (players.length() > 0) {
|
||||||
players.delete(players.length() - 2, players.length());
|
players.delete(players.length() - 2, players.length());
|
||||||
} else {
|
} else {
|
||||||
plugin.sendMessage(sender, "messages.info.player.noMatches",
|
plugin.sendMessage(
|
||||||
"%target%", material.name());
|
sender,
|
||||||
|
"messages.info.player.noMatches",
|
||||||
|
new Replacement("%target%", material.name()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.sendMessage(sender, "messages.info.player.matches",
|
plugin.sendMessage(
|
||||||
"%target%", material.name(), "%detail%", players.toString());
|
sender,
|
||||||
|
"messages.info.player.matches",
|
||||||
|
new Replacement("%target%", material.name()),
|
||||||
|
new Replacement("%detail%", players.toString()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package com.lishid.openinv.internal;
|
package com.lishid.openinv.internal;
|
||||||
|
|
||||||
import com.lishid.openinv.OpenInv;
|
import com.lishid.openinv.OpenInv;
|
||||||
|
import com.lishid.openinv.util.lang.Replacement;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.bukkit.entity.HumanEntity;
|
import org.bukkit.entity.HumanEntity;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@@ -73,8 +74,7 @@ public class OpenInventoryView extends InventoryView {
|
|||||||
.getLocalizedMessage(
|
.getLocalizedMessage(
|
||||||
player,
|
player,
|
||||||
titleKey,
|
titleKey,
|
||||||
"%player%",
|
new Replacement("%player%", owner.getName()));
|
||||||
owner.getName());
|
|
||||||
title = Objects.requireNonNullElseGet(localTitle, () -> owner.getName() + titleDefaultSuffix);
|
title = Objects.requireNonNullElseGet(localTitle, () -> owner.getName() + titleDefaultSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2011-2022 lishid. All rights reserved.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.lishid.openinv.util;
|
|
||||||
|
|
||||||
import com.lishid.openinv.OpenInv;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import org.bukkit.ChatColor;
|
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple language manager supporting both custom and bundled languages.
|
|
||||||
*
|
|
||||||
* @author Jikoo
|
|
||||||
*/
|
|
||||||
public class LanguageManager {
|
|
||||||
|
|
||||||
private final OpenInv plugin;
|
|
||||||
private final String defaultLocale;
|
|
||||||
private final Map<String, YamlConfiguration> locales;
|
|
||||||
|
|
||||||
public LanguageManager(@NotNull OpenInv plugin, @NotNull String defaultLocale) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
this.defaultLocale = defaultLocale;
|
|
||||||
this.locales = new HashMap<>();
|
|
||||||
getOrLoadLocale(defaultLocale);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
|
|
||||||
YamlConfiguration loaded = locales.get(locale);
|
|
||||||
if (loaded != null) {
|
|
||||||
return loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream resourceStream = plugin.getResource("locale/" + locale + ".yml");
|
|
||||||
YamlConfiguration localeConfigDefaults;
|
|
||||||
if (resourceStream == null) {
|
|
||||||
localeConfigDefaults = new YamlConfiguration();
|
|
||||||
} else {
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream))) {
|
|
||||||
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to load resource " + locale + ".yml", e);
|
|
||||||
localeConfigDefaults = new YamlConfiguration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = new File(plugin.getDataFolder(), locale + ".yml");
|
|
||||||
YamlConfiguration localeConfig;
|
|
||||||
|
|
||||||
if (!file.exists()) {
|
|
||||||
localeConfig = localeConfigDefaults;
|
|
||||||
try {
|
|
||||||
localeConfigDefaults.save(file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to save resource " + locale + ".yml", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
localeConfig = YamlConfiguration.loadConfiguration(file);
|
|
||||||
|
|
||||||
// Add new language keys
|
|
||||||
List<String> newKeys = new ArrayList<>();
|
|
||||||
for (String key : localeConfigDefaults.getKeys(true)) {
|
|
||||||
if (localeConfigDefaults.isConfigurationSection(key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localeConfig.isSet(key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
localeConfig.set(key, localeConfigDefaults.get(key));
|
|
||||||
newKeys.add(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newKeys.isEmpty()) {
|
|
||||||
plugin.getLogger().info("[LanguageManager] Added new language keys: " + String.join(", ", newKeys));
|
|
||||||
try {
|
|
||||||
localeConfig.save(file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Unable to save resource " + locale + ".yml", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!locale.equals(defaultLocale)) {
|
|
||||||
localeConfigDefaults = locales.get(defaultLocale);
|
|
||||||
|
|
||||||
// Check for missing keys
|
|
||||||
List<String> newKeys = new ArrayList<>();
|
|
||||||
for (String key : localeConfigDefaults.getKeys(true)) {
|
|
||||||
if (localeConfigDefaults.isConfigurationSection(key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localeConfig.isSet(key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
newKeys.add(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newKeys.isEmpty()) {
|
|
||||||
plugin.getLogger().info("[LanguageManager] Missing translations from " + locale + ".yml: " + String.join(", ", newKeys));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall through to default locale
|
|
||||||
localeConfig.setDefaults(localeConfigDefaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
locales.put(locale, localeConfig);
|
|
||||||
return localeConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
|
|
||||||
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase()).getString(key);
|
|
||||||
if (value == null || value.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = ChatColor.translateAlternateColorCodes('&', value);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String getValue(@NotNull String key, @Nullable String locale, String @NotNull ... replacements) {
|
|
||||||
if (replacements.length % 2 != 0) {
|
|
||||||
plugin.getLogger().log(Level.WARNING, "[LanguageManager] Replacement data is uneven", new Exception());
|
|
||||||
}
|
|
||||||
|
|
||||||
String value = getValue(key, locale);
|
|
||||||
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < replacements.length; i += 2) {
|
|
||||||
value = value.replace(replacements[i], replacements[i + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011-2022 lishid. All rights reserved.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lishid.openinv.util.lang;
|
||||||
|
|
||||||
|
import com.lishid.openinv.OpenInv;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.configuration.Configuration;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple language manager supporting both custom and bundled languages.
|
||||||
|
*
|
||||||
|
* @author Jikoo
|
||||||
|
*/
|
||||||
|
public class LanguageManager {
|
||||||
|
|
||||||
|
private final OpenInv plugin;
|
||||||
|
private final String defaultLocale;
|
||||||
|
private final Map<String, YamlConfiguration> locales;
|
||||||
|
|
||||||
|
public LanguageManager(@NotNull OpenInv plugin, @NotNull String defaultLocale) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.defaultLocale = defaultLocale;
|
||||||
|
this.locales = new HashMap<>();
|
||||||
|
getOrLoadLocale(defaultLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull YamlConfiguration getOrLoadLocale(@NotNull String locale) {
|
||||||
|
YamlConfiguration loaded = locales.get(locale);
|
||||||
|
if (loaded != null) {
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = new File(plugin.getDataFolder(), locale + ".yml");
|
||||||
|
|
||||||
|
// Load locale config from disk and bundled locale defaults.
|
||||||
|
YamlConfiguration localeConfig = loadLocale(locale, file);
|
||||||
|
|
||||||
|
// If the locale is not the default locale, also handle any missing translations from the default locale.
|
||||||
|
if (!locale.equals(defaultLocale)) {
|
||||||
|
addTranslationFallthrough(locale, localeConfig, file);
|
||||||
|
|
||||||
|
if (plugin.getConfig().getBoolean("settings.secret.warn-about-guess-section", true)
|
||||||
|
&& localeConfig.isConfigurationSection("guess")) {
|
||||||
|
// Warn that guess section exists. This should run once per language per server restart
|
||||||
|
// when accessed by a user to hint to server owners that they can make UX improvements.
|
||||||
|
plugin.getLogger().info(() -> "[LanguageManager] Missing translations from " + locale + ".yml! Check the guess section!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locales.put(locale, localeConfig);
|
||||||
|
return localeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull YamlConfiguration loadLocale(
|
||||||
|
@NotNull String locale,
|
||||||
|
@NotNull File file) {
|
||||||
|
// Load defaults from the plugin's bundled resources.
|
||||||
|
InputStream resourceStream = plugin.getResource("locale/" + locale + ".yml");
|
||||||
|
YamlConfiguration localeConfigDefaults;
|
||||||
|
if (resourceStream == null) {
|
||||||
|
localeConfigDefaults = new YamlConfiguration();
|
||||||
|
} else {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream))) {
|
||||||
|
localeConfigDefaults = YamlConfiguration.loadConfiguration(reader);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to load resource " + locale + ".yml");
|
||||||
|
localeConfigDefaults = new YamlConfiguration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
// If the file does not exist on disk, save bundled defaults.
|
||||||
|
try {
|
||||||
|
localeConfigDefaults.save(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + locale + ".yml");
|
||||||
|
}
|
||||||
|
// Return loaded bundled locale.
|
||||||
|
return localeConfigDefaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the file does exist on disk, load it.
|
||||||
|
YamlConfiguration localeConfig = YamlConfiguration.loadConfiguration(file);
|
||||||
|
// Check for missing translations from the bundled file.
|
||||||
|
List<String> newKeys = getMissingKeys(localeConfigDefaults, localeConfig::isSet);
|
||||||
|
|
||||||
|
if (newKeys.isEmpty()) {
|
||||||
|
return localeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get guess section for missing keys.
|
||||||
|
ConfigurationSection guess = localeConfig.getConfigurationSection("guess");
|
||||||
|
|
||||||
|
for (String newKey : newKeys) {
|
||||||
|
// Set all missing keys to defaults.
|
||||||
|
localeConfig.set(newKey, localeConfigDefaults.get(newKey));
|
||||||
|
|
||||||
|
// Delete relevant guess keys in case this is a new translation.
|
||||||
|
if (guess != null) {
|
||||||
|
guess.set(newKey, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If guess section is empty, delete it.
|
||||||
|
if (guess != null && guess.getKeys(false).isEmpty()) {
|
||||||
|
localeConfig.set("guess", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info(() -> "[LanguageManager] Added new translation keys to " + locale + ".yml: " + String.join(", ", newKeys));
|
||||||
|
|
||||||
|
// Write new keys to disk.
|
||||||
|
try {
|
||||||
|
localeConfig.save(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + locale + ".yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
return localeConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTranslationFallthrough(
|
||||||
|
@NotNull String locale,
|
||||||
|
@NotNull YamlConfiguration localeConfig,
|
||||||
|
@NotNull File file) {
|
||||||
|
YamlConfiguration defaultLocaleConfig = locales.get(defaultLocale);
|
||||||
|
|
||||||
|
// Get missing keys. Keys that already have a guess value are not new and don't need to trigger another write.
|
||||||
|
List<String> missingKeys = getMissingKeys(
|
||||||
|
defaultLocaleConfig,
|
||||||
|
key -> localeConfig.isSet(key) || localeConfig.isSet("guess." + key));
|
||||||
|
|
||||||
|
if (!missingKeys.isEmpty()) {
|
||||||
|
// Set up guess section for missing keys.
|
||||||
|
for (String key : missingKeys) {
|
||||||
|
localeConfig.set("guess." + key, defaultLocaleConfig.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write modified guess section to disk.
|
||||||
|
try {
|
||||||
|
localeConfig.save(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().log(Level.WARNING, e, () -> "[LanguageManager] Unable to save resource " + locale + ".yml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall through to default locale.
|
||||||
|
localeConfig.setDefaults(defaultLocaleConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull List<String> getMissingKeys(
|
||||||
|
@NotNull Configuration configurationDefault,
|
||||||
|
@NotNull Predicate<String> nodeSetPredicate) {
|
||||||
|
List<String> missingKeys = new ArrayList<>();
|
||||||
|
for (String key : configurationDefault.getKeys(true)) {
|
||||||
|
if (!configurationDefault.isConfigurationSection(key) && !nodeSetPredicate.test(key)) {
|
||||||
|
// Missing keys are non-section keys that fail the predicate.
|
||||||
|
missingKeys.add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return missingKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getValue(@NotNull String key, @Nullable String locale) {
|
||||||
|
String value = getOrLoadLocale(locale == null ? defaultLocale : locale.toLowerCase()).getString(key);
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = ChatColor.translateAlternateColorCodes('&', value);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getValue(@NotNull String key, @Nullable String locale, Replacement @NotNull ... replacements) {
|
||||||
|
String value = getValue(key, locale);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Replacement replacement : replacements) {
|
||||||
|
value = value.replace(replacement.placeholder(), replacement.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011-2022 lishid. All rights reserved.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.lishid.openinv.util.lang;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data holder for string replacement in translations.
|
||||||
|
*
|
||||||
|
* @param placeholder the placeholder to be replaced
|
||||||
|
* @param value the value to insert
|
||||||
|
*/
|
||||||
|
public record Replacement(@NotNull String placeholder, @NotNull String value) {
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user