Skip to content

Commit

Permalink
Translation fallback (#2543)
Browse files Browse the repository at this point in the history
* Support for translation fallbacks

* It's actually 1.19

* Revert "It's actually 1.19"

This reverts commit fcec389.

* Add .

* Use a `MapTag` format

* Rename method + imports
  • Loading branch information
tal5 committed Oct 13, 2023
1 parent 9b58d00 commit dfd911d
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 56 deletions.
@@ -1,15 +1,16 @@
package com.denizenscript.denizen.tags.core;

import com.denizenscript.denizen.objects.properties.bukkit.BukkitElementExtensions;
import com.denizenscript.denizen.utilities.BukkitImplDeprecations;
import com.denizenscript.denizen.utilities.FormattedTextHelper;
import com.denizenscript.denizencore.objects.ObjectTag;
import com.denizenscript.denizencore.objects.core.ColorTag;
import com.denizenscript.denizencore.objects.core.ElementTag;
import com.denizenscript.denizencore.objects.core.ListTag;
import com.denizenscript.denizencore.objects.core.MapTag;
import com.denizenscript.denizencore.tags.TagManager;
import com.denizenscript.denizencore.tags.core.EscapeTagUtil;
import com.denizenscript.denizencore.utilities.CoreUtilities;
import com.denizenscript.denizen.utilities.BukkitImplDeprecations;
import org.bukkit.ChatColor;

public class TextTagBase {
Expand Down Expand Up @@ -180,41 +181,46 @@ public TextTagBase() {
});

// <--[tag]
// @attribute <&translate[<key>]>
// @attribute <&translate[key=<key>;(fallback=<fallback>);(with=<text>|...)]>
// @returns ElementTag
// @description
// Returns a special chat code that displays an autotranslated message.
// For example: - narrate "Reward: <&translate[item.minecraft.diamond_sword]>"
// Be warned that language keys change between Minecraft versions.
// Returns a special chat code that is read by the client to display an auto-translated message.
// "key" is the translation key.
// Optionally specify "fallback" as text to display when the client can't find a translation for the key.
// Optionally specify "with" as a list of input data for the translatable message (parts of the message that are dynamic).
// Be warned that language keys can change between Minecraft versions.
// Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.
// You can use <@link tag ElementTag.strip_color> to convert the translated output to plain text (pre-translated).
// -->
TagManager.registerTagHandler(ElementTag.class, "&translate", (attribute) -> { // Cannot be static due to hacked sub-tag
if (!attribute.hasParam()) {
return null;
}
String translateText = attribute.getParam();

// <--[tag]
// @attribute <&translate[<key>].with[<text>|...]>
// @returns ElementTag
// @description
// Returns a special chat code that displays an autotranslated message.
// Optionally, specify a list of escaped text values representing input data for the translatable message.
// Be aware that missing 'with' values will cause exceptions in your console.
// For example: - narrate "<&translate[commands.give.success.single].with[32|<&translate[item.minecraft.diamond_sword].escaped>|<player.name.escaped>]>"
// Be warned that language keys change between Minecraft versions.
// Note that this is a magic Denizen tool - refer to <@link language Denizen Text Formatting>.
// -->
StringBuilder with = new StringBuilder();
if (attribute.startsWith("with", 2)) {
ListTag withList = attribute.contextAsType(2, ListTag.class);
attribute.fulfill(1);
for (String str : withList) {
with.append(";").append(FormattedTextHelper.escape(EscapeTagUtil.unEscape(str)));
// @example
// Narrates a translatable of a diamond sword's name.
// - narrate "Reward: <&translate[key=item.minecraft.diamond_sword]>"
// @example
// Narrates a translatable with some input data.
// - narrate <&translate[key=commands.give.success.single;with=32|<&translate[key=item.minecraft.diamond_sword]>|<player.name>]>
// @example
// Narrates a custom translatable (from something like a resource pack), with a fallback in case it can't be translated.
// - narrate <&translate[key=my.custom.translation;fallback=Please use the resource pack!]>
// -->
TagManager.registerTagHandler(ElementTag.class, ObjectTag.class, "&translate", (attribute, param) -> { // Cannot be static due to hacked sub-tag
MapTag translateMap = param.asType(MapTag.class, CoreUtilities.noDebugContext);
if (translateMap == null) {
BukkitImplDeprecations.translateLegacySyntax.warn(attribute.context);
translateMap = new MapTag();
translateMap.putObject("key", param);

// <--[tag]
// @attribute <&translate[<key>].with[<text>|...]>
// @returns ElementTag
// @deprecated Use '<&translate[key=<key>;with=<text>|...]>'.
// @description
// Deprecated in favor of <@link tag &translate>.
// -->
if (attribute.startsWith("with", 2)) {
translateMap.putObject("with", new ListTag(attribute.contextAsType(2, ListTag.class), with -> new ElementTag(EscapeTagUtil.unEscape(with), true)));
attribute.fulfill(1);
}
}
return new ElementTag(ChatColor.COLOR_CHAR + "[translate=" + FormattedTextHelper.escape(translateText) + with + "]");
return new ElementTag(ChatColor.COLOR_CHAR + "[translate=" + FormattedTextHelper.escape(translateMap.savable()) + ']', true);
});

// <--[tag]
Expand Down
Expand Up @@ -290,6 +290,9 @@ public class BukkitImplDeprecations {
// Added 2023/03/27, deprecate officially by 2026
public static Warning oldAgeLockedControls = new FutureWarning("oldAgeLockedControls", "Several old ways of controlling whether an entity's age is locked are deprecated in favor of the 'EntityTag.age_locked' tag/mech pair.");

// Added 2023/10/04, deprecate officially by 2027
public static Warning translateLegacySyntax = new FutureWarning("translateLegacySyntax", "<&translate[...].with[...]> is deprecated in favor of the modern <&translate[key=...;with=...]> syntax.");

// ==================== PAST deprecations of things that are already gone but still have a warning left behind ====================

// Added on 2019/10/13
Expand Down
@@ -1,9 +1,12 @@
package com.denizenscript.denizen.utilities;

import com.denizenscript.denizen.nms.NMSHandler;
import com.denizenscript.denizen.nms.NMSVersion;
import com.denizenscript.denizen.objects.properties.bukkit.BukkitElementExtensions;
import com.denizenscript.denizencore.objects.core.ColorTag;
import com.denizenscript.denizencore.objects.core.ElementTag;
import com.denizenscript.denizencore.objects.core.ListTag;
import com.denizenscript.denizencore.objects.core.MapTag;
import com.denizenscript.denizencore.utilities.AsciiMatcher;
import com.denizenscript.denizencore.utilities.CoreConfiguration;
import com.denizenscript.denizencore.utilities.CoreUtilities;
Expand Down Expand Up @@ -171,15 +174,16 @@ public static String stringifySub(BaseComponent component, ChatColor parentColor
if (component instanceof TextComponent) {
builder.append(((TextComponent) component).getText());
}
else if (component instanceof TranslatableComponent) {
builder.append(ChatColor.COLOR_CHAR).append("[translate=").append(escape(((TranslatableComponent) component).getTranslate()));
List<BaseComponent> with = ((TranslatableComponent) component).getWith();
if (with != null) {
for (BaseComponent withComponent : with) {
builder.append(";").append(escape(stringify(withComponent)));
}
else if (component instanceof TranslatableComponent translatableComponent) {
MapTag map = new MapTag();
map.putObject("key", new ElementTag(translatableComponent.getTranslate(), true));
if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && translatableComponent.getFallback() != null) {
map.putObject("fallback", new ElementTag(translatableComponent.getFallback(), true));
}
if (translatableComponent.getWith() != null) {
map.putObject("with", new ListTag(translatableComponent.getWith(), baseComponent -> new ElementTag(stringify(baseComponent), true)));
}
builder.append("]");
builder.append(ChatColor.COLOR_CHAR).append("[translate=").append(escape(map.savable())).append(']');
}
else if (component instanceof SelectorComponent) {
builder.append(ChatColor.COLOR_CHAR).append("[selector=").append(escape(((SelectorComponent) component).getSelector())).append("]");
Expand Down Expand Up @@ -442,6 +446,42 @@ public static BaseComponent[] parse(String str, ChatColor baseColor, boolean cle
return new BaseComponent[]{new TextComponent(str)};
}

private static TranslatableComponent parseTranslatable(String str, ChatColor baseColor, boolean optimize) {
TranslatableComponent component = new TranslatableComponent();
if (!str.startsWith("map@")) {
List<String> innardParts = CoreUtilities.split(str, ';');
component.setTranslate(unescape(innardParts.get(0)));
for (int i = 1; i < innardParts.size(); i++) {
for (BaseComponent subComponent : parseInternal(unescape(innardParts.get(i)), baseColor, false, optimize)) {
component.addWith(subComponent);
}
}
return component;
}
MapTag map = MapTag.valueOf(unescape(str), CoreUtilities.noDebugContext);
if (map == null) {
return component;
}
ElementTag translationKey = map.getElement("key");
if (translationKey == null) {
return component;
}
component.setTranslate(translationKey.asString());
ElementTag fallback = map.getElement("fallback");
if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_20) && fallback != null) {
component.setFallback(fallback.asString());
}
ListTag withList = map.getObjectAs("with", ListTag.class, CoreUtilities.noDebugContext);
if (withList != null) {
for (String with : withList) {
for (BaseComponent withComponent : parseInternal(with, baseColor, false, optimize)) {
component.addWith(withComponent);
}
}
}
return component;
}

public static BaseComponent[] parseInternal(String str, ChatColor baseColor, boolean cleanBase, boolean optimize) {
str = CoreUtilities.clearNBSPs(str);
int firstChar = str.indexOf(ChatColor.COLOR_CHAR);
Expand All @@ -462,16 +502,7 @@ public static BaseComponent[] parseInternal(String str, ChatColor baseColor, boo
}
// Ensure compat with certain weird vanilla translate strings.
if (str.startsWith(ChatColor.COLOR_CHAR + "[translate=") && str.indexOf(']') == str.length() - 1) {
String translatable = str.substring("&[translate=".length(), str.length() - 1);
TranslatableComponent component = new TranslatableComponent();
List<String> innardParts = CoreUtilities.split(translatable, ';');
component.setTranslate(unescape(innardParts.get(0)));
for (int i = 1; i < innardParts.size(); i++) {
for (BaseComponent subComponent : parseInternal(unescape(innardParts.get(i)), baseColor, false, optimize)) {
component.addWith(subComponent);
}
}
return new BaseComponent[] { component };
return new BaseComponent[] {parseTranslatable(str.substring("&[translate=".length(), str.length() - 1), baseColor, optimize)};
}
}
if (!optimize) {
Expand Down Expand Up @@ -535,14 +566,7 @@ else if (innardType.equals("selector")) {
lastText.addExtra(component);
}
else if (innardType.equals("translate")) {
TranslatableComponent component = new TranslatableComponent();
component.setTranslate(unescape(innardBase.get(1)));
for (String extra : innardParts) {
for (BaseComponent subComponent : parseInternal(unescape(extra), baseColor, false, optimize)) {
component.addWith(subComponent);
}
}
lastText.addExtra(component);
lastText.addExtra(parseTranslatable(innards.substring("translate=".length()), baseColor, optimize));
}
else if (innardType.equals("click") && innardParts.size() == 1) {
int endIndex = findEndIndexFor(str, "click", endBracket);
Expand Down

0 comments on commit dfd911d

Please sign in to comment.