diff --git a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/AdventureComponentWrapper.java b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/AdventureComponentWrapper.java index 56d29a8..ce5ced2 100644 --- a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/AdventureComponentWrapper.java +++ b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/AdventureComponentWrapper.java @@ -3,6 +3,8 @@ package xyz.xenondevs.inventoryaccess.component; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.jetbrains.annotations.NotNull; +import xyz.xenondevs.inventoryaccess.component.i18n.AdventureComponentLocalizer; +import xyz.xenondevs.inventoryaccess.component.i18n.Languages; public class AdventureComponentWrapper implements ComponentWrapper { @@ -18,9 +20,17 @@ public class AdventureComponentWrapper implements ComponentWrapper { } @Override - public @NotNull ComponentWrapper clone() { + public @NotNull AdventureComponentWrapper localized(@NotNull String lang) { + if (!Languages.getInstance().doesServerSideTranslations()) + return this; + + return new AdventureComponentWrapper(AdventureComponentLocalizer.getInstance().localize(lang, component)); + } + + @Override + public @NotNull AdventureComponentWrapper clone() { try { - return (ComponentWrapper) super.clone(); + return (AdventureComponentWrapper) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } diff --git a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/BaseComponentWrapper.java b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/BaseComponentWrapper.java index d78556c..4e5e1ae 100644 --- a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/BaseComponentWrapper.java +++ b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/BaseComponentWrapper.java @@ -3,6 +3,8 @@ package xyz.xenondevs.inventoryaccess.component; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.chat.ComponentSerializer; import org.jetbrains.annotations.NotNull; +import xyz.xenondevs.inventoryaccess.component.i18n.BaseComponentLocalizer; +import xyz.xenondevs.inventoryaccess.component.i18n.Languages; public class BaseComponentWrapper implements ComponentWrapper { @@ -12,6 +14,14 @@ public class BaseComponentWrapper implements ComponentWrapper { this.components = components; } + @Override + public @NotNull ComponentWrapper localized(@NotNull String lang) { + if (!Languages.getInstance().doesServerSideTranslations()) + return this; + + return new BaseComponentWrapper(BaseComponentLocalizer.getInstance().localize(lang, components)); + } + @Override public @NotNull String serializeToJson() { return ComponentSerializer.toString(components); diff --git a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/ComponentWrapper.java b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/ComponentWrapper.java index b64e3c6..5d48bf2 100644 --- a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/ComponentWrapper.java +++ b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/ComponentWrapper.java @@ -1,11 +1,34 @@ package xyz.xenondevs.inventoryaccess.component; import org.jetbrains.annotations.NotNull; +import xyz.xenondevs.inventoryaccess.component.i18n.Languages; public interface ComponentWrapper extends Cloneable { + /** + * Serializes the component to a json string. + * + * @return The json representation of the component. + */ @NotNull String serializeToJson(); + /** + * Creates a localized version of the component by replacing all translatable components with text components + * of the specified language. + *

+ * This method will return the same {@link ComponentWrapper} when {@link Languages} is disabled. + * + * @param lang The language to use. + * @return A new {@link ComponentWrapper} of the localized component or the same {@link ComponentWrapper} + * if {@link Languages} is disabled. + */ + @NotNull ComponentWrapper localized(@NotNull String lang); + + /** + * Clones this {@link ComponentWrapper}. + * + * @return The cloned {@link ComponentWrapper}. + */ @NotNull ComponentWrapper clone(); } diff --git a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/AdventureComponentLocalizer.java b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/AdventureComponentLocalizer.java new file mode 100644 index 0000000..1de7a0b --- /dev/null +++ b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/AdventureComponentLocalizer.java @@ -0,0 +1,57 @@ +package xyz.xenondevs.inventoryaccess.component.i18n; + +import net.kyori.adventure.text.*; + +public class AdventureComponentLocalizer extends ComponentLocalizer { + + public static final AdventureComponentLocalizer INSTANCE = new AdventureComponentLocalizer(); + + private AdventureComponentLocalizer() { + } + + public static AdventureComponentLocalizer getInstance() { + return INSTANCE; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public Component localize(String lang, Component component) { + if (!(component instanceof BuildableComponent)) + throw new IllegalStateException("Component is not a BuildableComponent"); + + return localize(lang, (BuildableComponent) component); + } + + @SuppressWarnings("NonExtendableApiUsage") + private , B extends ComponentBuilder> BuildableComponent localize(String lang, BuildableComponent component) { + ComponentBuilder builder; + if (component instanceof TranslatableComponent) { + builder = localizeTranslatable(lang, (TranslatableComponent) component).toBuilder(); + } else { + builder = component.toBuilder(); + } + + builder.mapChildrenDeep(child -> { + if (child instanceof TranslatableComponent) + return localizeTranslatable(lang, (TranslatableComponent) child); + return child; + }); + + return builder.build(); + } + + private BuildableComponent localizeTranslatable(String lang, TranslatableComponent component) { + var formatString = Languages.getInstance().getFormatString(lang, component.key()); + if (formatString == null) + return component; + + var children = decomposeFormatString(lang, formatString, component, component.args()); + return Component.textOfChildren(children.toArray(ComponentLike[]::new)).style(component.style()); + } + + @Override + protected Component createTextComponent(String text) { + return Component.text(text); + } + +} diff --git a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/BaseComponentLocalizer.java b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/BaseComponentLocalizer.java new file mode 100644 index 0000000..0582048 --- /dev/null +++ b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/BaseComponentLocalizer.java @@ -0,0 +1,71 @@ +package xyz.xenondevs.inventoryaccess.component.i18n; + +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.TranslatableComponent; + +import java.util.stream.Collectors; + +public class BaseComponentLocalizer extends ComponentLocalizer { + + private static final BaseComponentLocalizer INSTANCE = new BaseComponentLocalizer(); + + private BaseComponentLocalizer() { + } + + public static BaseComponentLocalizer getInstance() { + return INSTANCE; + } + + public BaseComponent[] localize(String lang, BaseComponent[] components) { + var localizedComponents = new BaseComponent[components.length]; + for (int i = 0; i < components.length; i++) { + localizedComponents[i] = localize(lang, components[i]); + } + return localizedComponents; + } + + @Override + public BaseComponent localize(String lang, BaseComponent component) { + BaseComponent duplicate; + if (component instanceof TranslatableComponent) { + duplicate = localizeTranslatable(lang, (TranslatableComponent) component); + } else { + duplicate = component.duplicate(); + } + + var extra = duplicate.getExtra(); + if (extra != null) { + duplicate.setExtra( + extra.stream() + .map(child -> localize(lang, child)) + .collect(Collectors.toList()) + ); + } + + return duplicate; + } + + private BaseComponent localizeTranslatable(String lang, TranslatableComponent component) { + var formatString = Languages.getInstance().getFormatString(lang, component.getTranslate()); + if (formatString == null) + return component; + + var children = decomposeFormatString(lang, formatString, component, component.getWith()); + var result = new TextComponent(children.toArray(BaseComponent[]::new)); + + result.copyFormatting(component); + + var extra = component.getExtra(); + if (extra != null) + result.setExtra(extra); + + return result; + } + + @Override + protected BaseComponent createTextComponent(String text) { + return new TextComponent(text); + } + +} diff --git a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/ComponentLocalizer.java b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/ComponentLocalizer.java new file mode 100644 index 0000000..ee6226e --- /dev/null +++ b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/ComponentLocalizer.java @@ -0,0 +1,69 @@ +package xyz.xenondevs.inventoryaccess.component.i18n; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +abstract class ComponentLocalizer { + + private static final Pattern FORMAT_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?([A-Za-z%]|$)"); + + public abstract C localize(String lang, C component); + + protected abstract C createTextComponent(String text); + + protected List decomposeFormatString(String lang, String formatString, C component, List args) { + var matcher = FORMAT_PATTERN.matcher(formatString); + + var components = new ArrayList(); + var sb = new StringBuilder(); + var nextArgIdx = 0; + + var i = 0; + while (matcher.find(i)) { + var start = matcher.start(); + var end = matcher.end(); + + // check for escaped % + var matchedStr = formatString.substring(i, start); + if ("%%".equals(matchedStr)) { + sb.append('%'); + } else { + // check for invalid format, only %s is supported + var argType = matcher.group(2); + if (!"s".equals(argType)) { + throw new IllegalStateException("Unsupported format: '" + matchedStr + "'"); + } + + // retrieve argument index + var argIdxStr = matcher.group(1); + var argIdx = argIdxStr == null ? nextArgIdx++ : Integer.parseInt(argIdxStr) - 1; + + // validate argument index + if (argIdx < 0) + throw new IllegalStateException("Invalid argument index: " + argIdx); + + // append the text before the argument + sb.append(formatString, i, start); + // add text component + components.add(createTextComponent(sb.toString())); + // add argument component + components.add(args.size() <= argIdx ? createTextComponent("") : localize(lang, args.get(argIdx))); + // clear string builder + sb.setLength(0); + } + + // start next search after matcher end index + i = end; + } + + // append the text after the last argument + if (i < formatString.length()) { + sb.append(formatString, i, formatString.length()); + components.add(createTextComponent(sb.toString())); + } + + return components; + } + +} diff --git a/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/Languages.java b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/Languages.java new file mode 100644 index 0000000..2005a09 --- /dev/null +++ b/inventoryaccess/inventory-access/src/main/java/xyz/xenondevs/inventoryaccess/component/i18n/Languages.java @@ -0,0 +1,96 @@ +package xyz.xenondevs.inventoryaccess.component.i18n; + +import com.google.gson.stream.JsonReader; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.xenondevs.inventoryaccess.component.ComponentWrapper; + +import java.io.IOException; +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; + +public class Languages { + + private static final Languages INSTANCE = new Languages(); + private final Map> translations = new HashMap<>(); + private boolean serverSideTranslations = true; + + private Languages() { + } + + public static @NotNull Languages getInstance() { + return INSTANCE; + } + + /** + * Adds a language under the given lang code. + *

+ * This method will replace any existing language with the same lang code. + * + * @param lang The lang code of the language. + * @param translations The translations of the language. + */ + public void addLanguage(@NotNull String lang, @NotNull Map<@NotNull String, @NotNull String> translations) { + this.translations.put(lang, translations); + } + + /** + * Adds a language under the given lang code after reading it from the given reader. + *

+ * Note: The language is read as a json object with the translation keys as keys and the format strings as + * their string values. Any other json structure will result in an {@link IllegalStateException}. + * An example for such a structure are Minecraft's lang files. + * + * @param lang The lang code of the language. + * @param reader The reader for a language json file. + * @throws IOException If an error occurs while reading. + * @throws IllegalStateException If the json is not valid. + */ + public void loadLanguage(@NotNull String lang, @NotNull Reader reader) throws IOException { + var translations = new HashMap(); + try (var jsonReader = new JsonReader(reader)) { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + var key = jsonReader.nextName(); + var value = jsonReader.nextString(); + translations.put(key, value); + } + + addLanguage(lang, translations); + } + } + + /** + * Retrieves the format string for the given key under the given language. + * + * @param lang The language to use. + * @param key The key of the format string. + * @return The format string or null if there is no such language or key. + */ + public @Nullable String getFormatString(@NotNull String lang, @NotNull String key) { + var map = translations.get(lang); + if (map == null) + return null; + return map.get(key); + } + + /** + * Enables or disables server-side translations for {@link ComponentWrapper ComponentWrappers}. + * + * @param enable Whether server-side translations should be enabled. + */ + public void enableServerSideTranslations(boolean enable) { + serverSideTranslations = enable; + } + + /** + * Checks whether server-side translations are enabled. + * + * @return Whether server-side translations are enabled. + */ + public boolean doesServerSideTranslations() { + return serverSideTranslations; + } + +} diff --git a/invui/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java b/invui/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java index 4a0ffc5..4710525 100644 --- a/invui/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java +++ b/invui/src/main/java/xyz/xenondevs/invui/gui/AbstractGui.java @@ -124,13 +124,13 @@ public abstract class AbstractGui implements Gui, GuiParent { } private boolean didClickBackgroundItem(Player player, SlotElement.VISlotElement element, VirtualInventory inventory, int slot, ItemStack clicked) { - UUID uuid = player.getUniqueId(); + String lang = player.getLocale(); return inventory.getUnsafeItemStack(slot) == null - && (isBuilderSimilar(background, uuid, clicked) || isBuilderSimilar(element.getBackground(), uuid, clicked)); + && (isBuilderSimilar(background, lang, clicked) || isBuilderSimilar(element.getBackground(), lang, clicked)); } - private boolean isBuilderSimilar(ItemProvider builder, UUID uuid, ItemStack expected) { - return builder != null && builder.getFor(uuid).isSimilar(expected); + private boolean isBuilderSimilar(ItemProvider builder, String lang, ItemStack expected) { + return builder != null && builder.get(lang).isSimilar(expected); } @SuppressWarnings("deprecation") diff --git a/invui/src/main/java/xyz/xenondevs/invui/gui/SlotElement.java b/invui/src/main/java/xyz/xenondevs/invui/gui/SlotElement.java index d9ddcab..5c8724f 100644 --- a/invui/src/main/java/xyz/xenondevs/invui/gui/SlotElement.java +++ b/invui/src/main/java/xyz/xenondevs/invui/gui/SlotElement.java @@ -7,11 +7,10 @@ import xyz.xenondevs.invui.virtualinventory.VirtualInventory; import java.util.ArrayList; import java.util.List; -import java.util.UUID; public interface SlotElement { - ItemStack getItemStack(UUID viewerUUID); + ItemStack getItemStack(String lang); SlotElement getHoldingElement(); @@ -31,8 +30,8 @@ public interface SlotElement { } @Override - public ItemStack getItemStack(UUID viewerUUID) { - return item.getItemProvider().getFor(viewerUUID); + public ItemStack getItemStack(String lang) { + return item.getItemProvider().get(lang); } @Override @@ -76,9 +75,9 @@ public interface SlotElement { } @Override - public ItemStack getItemStack(UUID viewerUUID) { + public ItemStack getItemStack(String lang) { ItemStack itemStack = virtualInventory.getUnsafeItemStack(slot); - if (itemStack == null && background != null) itemStack = background.getFor(viewerUUID); + if (itemStack == null && background != null) itemStack = background.get(lang); return itemStack; } @@ -139,9 +138,9 @@ public interface SlotElement { } @Override - public ItemStack getItemStack(UUID viewerUUID) { + public ItemStack getItemStack(String lang) { SlotElement holdingElement = getHoldingElement(); - return holdingElement != null ? holdingElement.getItemStack(viewerUUID) : null; + return holdingElement != null ? holdingElement.getItemStack(lang) : null; } } diff --git a/invui/src/main/java/xyz/xenondevs/invui/item/ItemProvider.java b/invui/src/main/java/xyz/xenondevs/invui/item/ItemProvider.java index 5a3d762..b5718bd 100644 --- a/invui/src/main/java/xyz/xenondevs/invui/item/ItemProvider.java +++ b/invui/src/main/java/xyz/xenondevs/invui/item/ItemProvider.java @@ -1,35 +1,33 @@ package xyz.xenondevs.invui.item; import org.bukkit.Material; -import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import xyz.xenondevs.invui.window.AbstractWindow; +import org.jetbrains.annotations.Nullable; -import java.util.UUID; import java.util.function.Supplier; public interface ItemProvider extends Supplier, Cloneable { + /** + * An {@link ItemProvider} for an {@link ItemStack}. + */ ItemProvider EMPTY = new ItemWrapper(new ItemStack(Material.AIR)); /** - * Builds the {@link ItemStack} + * Gets the {@link ItemStack} translated in the specified language. * + * @param lang The language to translate the item in. * @return The {@link ItemStack} */ - ItemStack get(); + ItemStack get(@Nullable String lang); /** - * Gets the {@link ItemStack} for a specific player. - * This is the method called by {@link AbstractWindow} which gives you - * the option to (for example) create a subclass of {@link ItemProvider} that automatically - * translates the item's name into the player's language. + * Gets the {@link ItemStack} without requesting a specific language. * - * @param playerUUID The {@link UUID} of the {@link Player} - * for whom this {@link ItemStack} should be made. * @return The {@link ItemStack} */ - ItemStack getFor(@NotNull UUID playerUUID); + default ItemStack get() { + return get(null); + } } diff --git a/invui/src/main/java/xyz/xenondevs/invui/item/ItemWrapper.java b/invui/src/main/java/xyz/xenondevs/invui/item/ItemWrapper.java index 6f07154..64602db 100644 --- a/invui/src/main/java/xyz/xenondevs/invui/item/ItemWrapper.java +++ b/invui/src/main/java/xyz/xenondevs/invui/item/ItemWrapper.java @@ -1,7 +1,6 @@ package xyz.xenondevs.invui.item; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; import java.util.UUID; @@ -18,12 +17,7 @@ public class ItemWrapper implements ItemProvider { } @Override - public ItemStack get() { - return itemStack; - } - - @Override - public ItemStack getFor(@NotNull UUID playerUUID) { + public ItemStack get(String lang) { return itemStack; } diff --git a/invui/src/main/java/xyz/xenondevs/invui/item/builder/AbstractItemBuilder.java b/invui/src/main/java/xyz/xenondevs/invui/item/builder/AbstractItemBuilder.java index 8b37a94..3858f7d 100644 --- a/invui/src/main/java/xyz/xenondevs/invui/item/builder/AbstractItemBuilder.java +++ b/invui/src/main/java/xyz/xenondevs/invui/item/builder/AbstractItemBuilder.java @@ -3,21 +3,23 @@ package xyz.xenondevs.invui.item.builder; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import xyz.xenondevs.inventoryaccess.InventoryAccess; import xyz.xenondevs.inventoryaccess.component.BaseComponentWrapper; import xyz.xenondevs.inventoryaccess.component.ComponentWrapper; import xyz.xenondevs.invui.item.ItemProvider; import xyz.xenondevs.invui.util.ComponentUtils; import xyz.xenondevs.invui.util.Pair; -import xyz.xenondevs.invui.window.AbstractWindow; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @@ -71,7 +73,7 @@ public abstract class AbstractItemBuilder implements ItemProvider { * @return The {@link ItemStack} */ @Override - public ItemStack get() { + public ItemStack get(@Nullable String lang) { ItemStack itemStack; if (base != null) { itemStack = base; @@ -83,12 +85,25 @@ public abstract class AbstractItemBuilder implements ItemProvider { ItemMeta itemMeta = itemStack.getItemMeta(); if (itemMeta != null) { // display name - if (displayName != null) - InventoryAccess.getItemUtils().setDisplayName(itemMeta, displayName); + if (displayName != null) { + InventoryAccess.getItemUtils().setDisplayName( + itemMeta, + (lang != null) ? displayName.localized(lang) : displayName + ); + } // lore - if (lore != null) - InventoryAccess.getItemUtils().setLore(itemMeta, lore); + if (lore != null) { + if (lang != null) { + var translatedLore = lore.stream() + .map(wrapper -> wrapper.localized(lang)) + .collect(Collectors.toList()); + + InventoryAccess.getItemUtils().setLore(itemMeta, translatedLore); + } else { + InventoryAccess.getItemUtils().setLore(itemMeta, lore); + } + } // damage if (itemMeta instanceof Damageable) @@ -127,21 +142,6 @@ public abstract class AbstractItemBuilder implements ItemProvider { return itemStack; } - /** - * Builds the {@link ItemStack} for a specific player. - * This is the method called by {@link AbstractWindow} which gives you - * the option to (for example) create a subclass of {@link AbstractItemBuilder} that automatically - * translates the item's name into the player's language. - * - * @param playerUUID The {@link UUID} of the {@link Player} - * for whom this {@link ItemStack} should be built. - * @return The {@link ItemStack} - */ - @Override - public ItemStack getFor(@NotNull UUID playerUUID) { - return get(); - } - public T removeLoreLine(int index) { if (lore != null) lore.remove(index); return getThis(); diff --git a/invui/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java b/invui/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java index 4b2c3bc..105b463 100644 --- a/invui/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java +++ b/invui/src/main/java/xyz/xenondevs/invui/window/AbstractWindow.java @@ -66,9 +66,9 @@ public abstract class AbstractWindow implements Window, GuiParent { protected void redrawItem(int index, SlotElement element, boolean setItem) { // put ItemStack in inventory ItemStack itemStack; - if (element == null || (element instanceof SlotElement.VISlotElement && element.getItemStack(viewerUUID) == null)) { + if (element == null || (element instanceof SlotElement.VISlotElement && element.getItemStack(getLang()) == null)) { ItemProvider background = getGuiAt(index).getFirst().getBackground(); - itemStack = background == null ? null : background.getFor(viewerUUID); + itemStack = background == null ? null : background.get(getLang()); } else if (element instanceof SlotElement.LinkedSlotElement && element.getHoldingElement() == null) { ItemProvider background = null; @@ -80,9 +80,9 @@ public abstract class AbstractWindow implements Window, GuiParent { if (background != null) break; } - itemStack = background == null ? null : background.getFor(viewerUUID); + itemStack = background == null ? null : background.get(getLang()); } else { - itemStack = element.getItemStack(viewerUUID); + itemStack = element.getItemStack(getLang()); // This makes every item unique to prevent Shift-DoubleClick "clicking" multiple items at the same time. if (itemStack.hasItemMeta()) { @@ -255,7 +255,11 @@ public abstract class AbstractWindow implements Window, GuiParent { Player viewer = getViewer(); if (viewer == null) throw new IllegalStateException("The player is not online."); - InventoryAccess.getInventoryUtils().openCustomInventory(viewer, getInventories()[0], title); + InventoryAccess.getInventoryUtils().openCustomInventory( + viewer, + getInventories()[0], + title.localized(viewer.getLocale()) + ); } @Override @@ -263,7 +267,10 @@ public abstract class AbstractWindow implements Window, GuiParent { this.title = title; Player currentViewer = getCurrentViewer(); if (currentViewer != null) { - InventoryAccess.getInventoryUtils().updateOpenInventoryTitle(currentViewer, title); + InventoryAccess.getInventoryUtils().updateOpenInventoryTitle( + currentViewer, + title.localized(currentViewer.getLocale()) + ); } } @@ -307,6 +314,15 @@ public abstract class AbstractWindow implements Window, GuiParent { return Bukkit.getPlayer(viewerUUID); } + // TODO: could also return null / "en_us" + public @NotNull String getLang() { + var player = getViewer(); + if (player == null) + throw new IllegalStateException("Tried to receive the language from a viewer that is not online."); + + return player.getLocale(); + } + @Override public @NotNull UUID getViewerUUID() { return viewerUUID;