diff --git a/api/src/main/java/com/rexcantor64/triton/api/language/Language.java b/api/src/main/java/com/rexcantor64/triton/api/language/Language.java index 01b09c40..09954860 100644 --- a/api/src/main/java/com/rexcantor64/triton/api/language/Language.java +++ b/api/src/main/java/com/rexcantor64/triton/api/language/Language.java @@ -7,7 +7,7 @@ * * @since 1.0.0 */ -public interface Language { +public interface Language extends Localized { /** * @return The name of the language. diff --git a/api/src/main/java/com/rexcantor64/triton/api/language/Localized.java b/api/src/main/java/com/rexcantor64/triton/api/language/Localized.java new file mode 100644 index 00000000..e283c6c0 --- /dev/null +++ b/api/src/main/java/com/rexcantor64/triton/api/language/Localized.java @@ -0,0 +1,37 @@ +package com.rexcantor64.triton.api.language; + +/** + * Represents something that has a language. + * It can be a player or even a language itself. + * + * @since 3.8.0 + */ +public interface Localized { + + /** + * Get the string identifier of the language of this object. + * Depending on the underlying implementation, it can get it from a + * player's current language, a language object or even a string itself. + * + * @return The string identifier of the language of this object. + * @since 3.8.0 + */ + default String getLanguageId() { + final Language language = this.getLanguage(); + if (language == null) { + return null; + } + return this.getLanguage().getName(); + } + + /** + * Get the language of this object. + * Depending on the underlying implementation, it can get it from a + * player's current language, a language object or derive it from its string id. + * + * @return The language of this object. + * @since 3.8.0 + */ + Language getLanguage(); + +} diff --git a/api/src/main/java/com/rexcantor64/triton/api/players/LanguagePlayer.java b/api/src/main/java/com/rexcantor64/triton/api/players/LanguagePlayer.java index 5ece4fc5..46008720 100644 --- a/api/src/main/java/com/rexcantor64/triton/api/players/LanguagePlayer.java +++ b/api/src/main/java/com/rexcantor64/triton/api/players/LanguagePlayer.java @@ -1,6 +1,7 @@ package com.rexcantor64.triton.api.players; import com.rexcantor64.triton.api.language.Language; +import com.rexcantor64.triton.api.language.Localized; import java.util.UUID; @@ -10,7 +11,7 @@ * * @since 1.0.0 */ -public interface LanguagePlayer { +public interface LanguagePlayer extends Localized { /** * Get the {@link Language language} of the player. diff --git a/core/src/main/java/com/rexcantor64/triton/language/Language.java b/core/src/main/java/com/rexcantor64/triton/language/Language.java index 57033630..fdba225d 100644 --- a/core/src/main/java/com/rexcantor64/triton/language/Language.java +++ b/core/src/main/java/com/rexcantor64/triton/language/Language.java @@ -57,4 +57,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(name, minecraftCodes, rawDisplayName, displayName, banner, flagCode, cmds); } + + @Override + public Language getLanguage() { + return this; + } } diff --git a/core/src/main/java/com/rexcantor64/triton/language/LanguageManager.java b/core/src/main/java/com/rexcantor64/triton/language/LanguageManager.java index 5e05b226..37f6a35d 100644 --- a/core/src/main/java/com/rexcantor64/triton/language/LanguageManager.java +++ b/core/src/main/java/com/rexcantor64/triton/language/LanguageManager.java @@ -1,19 +1,24 @@ package com.rexcantor64.triton.language; -import com.rexcantor64.triton.SpigotMLP; import com.rexcantor64.triton.Triton; +import com.rexcantor64.triton.api.language.Localized; import com.rexcantor64.triton.api.language.SignLocation; import com.rexcantor64.triton.api.players.LanguagePlayer; import com.rexcantor64.triton.language.item.LanguageSign; import com.rexcantor64.triton.language.item.LanguageText; +import com.rexcantor64.triton.language.localized.StringLocale; import com.rexcantor64.triton.storage.LocalStorage; import lombok.Getter; import lombok.NonNull; import lombok.val; -import lombok.var; import net.md_5.bungee.api.ChatColor; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,6 +39,10 @@ public String matchPattern(String input, LanguagePlayer p) { return matchPattern(input, p.getLang().getName()); } + public String matchPattern(String input, Localized localized) { + return matchPattern(input, localized.getLanguageId()); + } + public String matchPattern(String input, String language) { for (Map.Entry entry : matches.entrySet()) { String replacement = entry.getValue().getMessageRegex(language); @@ -56,16 +65,14 @@ public String getText(@NonNull LanguagePlayer p, String code, Object... args) { } public String getText(@NonNull String languageName, @NonNull String code, @NonNull Object... args) { - val language = getLanguageByName(languageName, true); - - return getText(language, code, args); + return getText(new StringLocale(languageName), code, args); } - public String getText(@NonNull com.rexcantor64.triton.api.language.Language language, @NonNull String code, @NonNull Object... args) { - val text = getTextForLanguage(language.getName(), code, args); + public String getText(@NonNull Localized localized, @NonNull String code, @NonNull Object... args) { + val text = getTextForLanguage(localized.getLanguageId(), code, args); if (text.isPresent()) return text.get(); - for (String fallbackLanguage : language.getFallbackLanguages()) { + for (String fallbackLanguage : localized.getLanguage().getFallbackLanguages()) { val textFallback = getTextForLanguage(fallbackLanguage, code, args); if (textFallback.isPresent()) return textFallback.get(); } diff --git a/core/src/main/java/com/rexcantor64/triton/language/LanguageParser.java b/core/src/main/java/com/rexcantor64/triton/language/LanguageParser.java index 2c1108f5..ceb0856b 100644 --- a/core/src/main/java/com/rexcantor64/triton/language/LanguageParser.java +++ b/core/src/main/java/com/rexcantor64/triton/language/LanguageParser.java @@ -4,13 +4,13 @@ import com.rexcantor64.triton.SpigotMLP; import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.api.config.FeatureSyntax; +import com.rexcantor64.triton.api.language.Localized; +import com.rexcantor64.triton.language.localized.StringLocale; import com.rexcantor64.triton.language.parser.AdvancedComponent; -import com.rexcantor64.triton.player.LanguagePlayer; import com.rexcantor64.triton.player.SpigotLanguagePlayer; import com.rexcantor64.triton.utils.ComponentUtils; import com.rexcantor64.triton.wrappers.legacy.HoverComponentWrapper; import lombok.val; -import lombok.var; import me.clip.placeholderapi.PlaceholderAPI; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -83,9 +83,9 @@ public String parseString(String language, FeatureSyntax syntax, String input) { return replaceLanguages(input, language, syntax); } - public String replaceLanguages(String input, LanguagePlayer p, FeatureSyntax syntax) { + public String replaceLanguages(String input, Localized p, FeatureSyntax syntax) { if (input == null) return null; - val translated = replaceLanguages(input, p.getLang().getName(), syntax); + val translated = replaceLanguages(input, p.getLanguageId(), syntax); if (translated != null && p instanceof SpigotLanguagePlayer && Triton.isSpigot() && Triton.asSpigot().isPapiEnabled()) { SpigotLanguagePlayer slp = (SpigotLanguagePlayer) p; @@ -171,26 +171,21 @@ private List removeTritonLinks(BaseComponent... baseComponents) { return result; } - public BaseComponent[] parseComponent(LanguagePlayer p, FeatureSyntax syntax, BaseComponent... text) { - return parseComponent(p.getLang().getName(), syntax, p, text); - } - public BaseComponent[] parseComponent(String language, FeatureSyntax syntax, BaseComponent... text) { - return parseComponent(language, syntax, null, text); + return parseComponent(new StringLocale(language), syntax, text); } - public BaseComponent[] parseComponent(String language, FeatureSyntax syntax, LanguagePlayer contextPlayer, BaseComponent... text) { + public BaseComponent[] parseComponent(Localized language, FeatureSyntax syntax, BaseComponent... text) { text = ComponentSerializer.parse(ComponentSerializer.toString(text)); text = removeTritonLinks(text).toArray(new BaseComponent[0]); - val advancedComponent = parseAdvancedComponent(language, syntax, AdvancedComponent.fromBaseComponent(text), contextPlayer); + val advancedComponent = parseAdvancedComponent(language, syntax, AdvancedComponent.fromBaseComponent(text)); if (advancedComponent == null) return null; return advancedComponent.toBaseComponent(); } - private AdvancedComponent parseAdvancedComponent(String language, FeatureSyntax syntax, - AdvancedComponent advancedComponent, - LanguagePlayer contextPlayer) { + private AdvancedComponent parseAdvancedComponent(Localized language, FeatureSyntax syntax, + AdvancedComponent advancedComponent) { String input = advancedComponent.getTextClean(); input = Triton.get().getLanguageManager().matchPattern(input, language); Integer[] i; @@ -214,7 +209,7 @@ private AdvancedComponent parseAdvancedComponent(String language, FeatureSyntax if (!Triton.get().getConf().getDisabledLine().isEmpty() && code.equals(Triton.get().getConf().getDisabledLine())) return null; - val result = parseTritonTranslation(Triton.get().getLanguageManager().getText(language, code), contextPlayer); + val result = parseTritonTranslation(Triton.get().getLanguageManager().getText(language, code), language); advancedComponent.inheritSpecialComponents(result); builder.append(result.getTextClean()); builder.append(input.substring(i[1])); @@ -231,7 +226,7 @@ private AdvancedComponent parseAdvancedComponent(String language, FeatureSyntax for (int k = 0; k < argIndexList.size(); k++) { Integer[] argIndex = argIndexList.get(k); AdvancedComponent argAdvancedComponent = AdvancedComponent.fromString(args.substring(argIndex[2], argIndex[3])); - argAdvancedComponent = parseAdvancedComponent(language, syntax, argAdvancedComponent, contextPlayer); + argAdvancedComponent = parseAdvancedComponent(language, syntax, argAdvancedComponent); if (argAdvancedComponent == null) { argList[k] = ""; continue; @@ -239,7 +234,7 @@ private AdvancedComponent parseAdvancedComponent(String language, FeatureSyntax argList[k] = argAdvancedComponent.getText(); advancedComponent.inheritSpecialComponents(argAdvancedComponent); } - val result = parseTritonTranslation(SpigotMLP.get().getLanguageManager().getText(language, code, argList), contextPlayer); + val result = parseTritonTranslation(SpigotMLP.get().getLanguageManager().getText(language, code, argList), language); advancedComponent.inheritSpecialComponents(result); builder.append(result.getTextClean()); builder.append(input.substring(i[1])); @@ -260,8 +255,9 @@ private AdvancedComponent parseAdvancedComponent(String language, FeatureSyntax val replaced = replaceLanguages(Triton.get().getLanguageManager() .matchPattern(string, language), language, syntax); if (replaced == null) { - if (entry.getValue().getAction() != HoverEvent.Action.SHOW_ITEM) + if (entry.getValue().getAction() != HoverEvent.Action.SHOW_ITEM) { entry.setValue(null); + } continue; } entry.setValue(HoverComponentWrapper @@ -269,13 +265,14 @@ private AdvancedComponent parseAdvancedComponent(String language, FeatureSyntax } } - for (val entry : advancedComponent.getAllTranslatableArguments().entrySet()) + for (val entry : advancedComponent.getAllTranslatableArguments().entrySet()) { advancedComponent.getAllTranslatableArguments().put(entry.getKey(), entry.getValue().stream() - .map(comp -> parseAdvancedComponent(language, syntax, comp, contextPlayer)).collect(Collectors.toList())); + .map(comp -> parseAdvancedComponent(language, syntax, comp)).collect(Collectors.toList())); + } return advancedComponent; } - private AdvancedComponent parseTritonTranslation(String translatedResult, LanguagePlayer contextPlayer) { + private AdvancedComponent parseTritonTranslation(String translatedResult, Localized contextPlayer) { if (contextPlayer instanceof SpigotLanguagePlayer && Triton.isSpigot() && Triton.asSpigot().isPapiEnabled()) { SpigotLanguagePlayer slp = (SpigotLanguagePlayer) contextPlayer; val bukkitPlayer = slp.toBukkit(); diff --git a/core/src/main/java/com/rexcantor64/triton/language/localized/StringLocale.java b/core/src/main/java/com/rexcantor64/triton/language/localized/StringLocale.java new file mode 100644 index 00000000..05b9152d --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/language/localized/StringLocale.java @@ -0,0 +1,23 @@ +package com.rexcantor64.triton.language.localized; + +import com.rexcantor64.triton.Triton; +import com.rexcantor64.triton.api.language.Language; +import com.rexcantor64.triton.api.language.Localized; +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class StringLocale implements Localized { + + private final String languageId; + private Language cachedLanguage; + + @Override + public Language getLanguage() { + if (cachedLanguage == null) { + cachedLanguage = Triton.get().getLanguageManager().getLanguageByName(languageId, true); + } + return cachedLanguage; + } +} diff --git a/core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java b/core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java index 3cfb7427..eece4cb0 100644 --- a/core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java +++ b/core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java @@ -1,5 +1,7 @@ package com.rexcantor64.triton.player; +import com.rexcantor64.triton.api.language.Language; + import java.util.UUID; public interface LanguagePlayer extends com.rexcantor64.triton.api.players.LanguagePlayer { @@ -16,4 +18,8 @@ public interface LanguagePlayer extends com.rexcantor64.triton.api.players.Langu void waitForClientLocale(); + @Override + default Language getLanguage() { + return this.getLang(); + } } diff --git a/core/src/main/java/com/rexcantor64/triton/utils/ItemStackTranslationUtils.java b/core/src/main/java/com/rexcantor64/triton/utils/ItemStackTranslationUtils.java index a274421f..4108039c 100644 --- a/core/src/main/java/com/rexcantor64/triton/utils/ItemStackTranslationUtils.java +++ b/core/src/main/java/com/rexcantor64/triton/utils/ItemStackTranslationUtils.java @@ -1,12 +1,16 @@ package com.rexcantor64.triton.utils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtList; import com.rexcantor64.triton.Triton; +import com.rexcantor64.triton.api.language.Localized; import com.rexcantor64.triton.config.MainConfig; -import com.rexcantor64.triton.player.LanguagePlayer; import lombok.val; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; @@ -23,6 +27,15 @@ public class ItemStackTranslationUtils { + private static final MethodAccessor NBT_DESERIALIZER_METHOD; + + static { + val mojangsonParserClass = MinecraftReflection.getMinecraftClass("nbt.MojangsonParser", "MojangsonParser"); + FuzzyReflection fuzzy = FuzzyReflection.fromClass(mojangsonParserClass); + val method = fuzzy.getMethodByParameters("deserializeNbtCompound", MinecraftReflection.getNBTCompoundClass(), new Class[]{String.class}); + NBT_DESERIALIZER_METHOD = Accessors.getMethodAccessor(method); + } + /** * Translates an item stack in one of two ways: * - if the item has a CraftBukkit handler, the item is translated through its NBT tag; @@ -36,7 +49,7 @@ public class ItemStackTranslationUtils { * @param translateBooks Whether it should translate written books * @return The translated item stack, which may or may not be the same as the given parameter */ - public static ItemStack translateItemStack(ItemStack item, LanguagePlayer languagePlayer, boolean translateBooks) { + public static ItemStack translateItemStack(ItemStack item, Localized languagePlayer, boolean translateBooks) { if (item == null || item.getType() == Material.AIR) { return item; } @@ -133,7 +146,7 @@ public static ItemStack translateItemStack(ItemStack item, LanguagePlayer langua * @param languagePlayer The language player to translate for * @param translateLore Whether to attempt to translate the lore of the item */ - public static void translateNbtItem(NbtCompound compound, LanguagePlayer languagePlayer, boolean translateLore) { + public static void translateNbtItem(NbtCompound compound, Localized languagePlayer, boolean translateLore) { if (!compound.containsKey("display")) { return; } @@ -197,13 +210,28 @@ public static void translateNbtItem(NbtCompound compound, LanguagePlayer languag } } - private static String translate(String string, LanguagePlayer languagePlayer, MainConfig.FeatureSyntax featureSyntax) { + public static String translateNbtString(String nbt, Localized localized) { + val compound = deserializeItemTagNbt(nbt); + translateNbtItem(compound, localized, true); + return serializeItemTagNbt(compound); + } + + private static NbtCompound deserializeItemTagNbt(String nbt) { + val nmsCompound = NBT_DESERIALIZER_METHOD.invoke(null, nbt); + return NbtFactory.fromNMSCompound(nmsCompound); + } + + private static String serializeItemTagNbt(NbtCompound nbt) { + return nbt.getHandle().toString(); + } + + private static String translate(String string, Localized localized, MainConfig.FeatureSyntax featureSyntax) { if (string == null) { return null; } return main().getLanguageParser().replaceLanguages( - main().getLanguageManager().matchPattern(string, languagePlayer), - languagePlayer, + main().getLanguageManager().matchPattern(string, localized), + localized, featureSyntax ); } diff --git a/core/src/main/java/com/rexcantor64/triton/wrappers/HoverComponentWrapper.java b/core/src/main/java/com/rexcantor64/triton/wrappers/HoverComponentWrapper.java index 6ad0ccda..2992b295 100644 --- a/core/src/main/java/com/rexcantor64/triton/wrappers/HoverComponentWrapper.java +++ b/core/src/main/java/com/rexcantor64/triton/wrappers/HoverComponentWrapper.java @@ -2,9 +2,12 @@ import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.api.config.FeatureSyntax; +import com.rexcantor64.triton.api.language.Localized; +import com.rexcantor64.triton.utils.ItemStackTranslationUtils; import lombok.val; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.ItemTag; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.hover.content.Content; import net.md_5.bungee.api.chat.hover.content.Entity; @@ -16,7 +19,7 @@ public class HoverComponentWrapper { - public static HoverEvent handleHoverEvent(HoverEvent hoverEvent, String language, FeatureSyntax syntax) { + public static HoverEvent handleHoverEvent(HoverEvent hoverEvent, Localized language, FeatureSyntax syntax) { val changed = new AtomicBoolean(false); val contents = hoverEvent.getContents(); @@ -43,7 +46,8 @@ public static HoverEvent handleHoverEvent(HoverEvent hoverEvent, String language val item = (Item) content; if (item.getTag() != null) { val tag = item.getTag(); - // TODO + val newTag = ItemTag.ofNbt(ItemStackTranslationUtils.translateNbtString(tag.getNbt(), language)); + item.setTag(newTag); } newContents.add(item); } else if (content instanceof Entity) {