diff --git a/api/src/main/java/net/kyori/adventure/translation/GlobalTranslatorImpl.java b/api/src/main/java/net/kyori/adventure/translation/GlobalTranslatorImpl.java index 842763be8..2d6a02437 100644 --- a/api/src/main/java/net/kyori/adventure/translation/GlobalTranslatorImpl.java +++ b/api/src/main/java/net/kyori/adventure/translation/GlobalTranslatorImpl.java @@ -24,7 +24,9 @@ package net.kyori.adventure.translation; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -95,9 +97,31 @@ public boolean removeSource(final @NotNull Translator source) { public @Nullable Component translate(final @NotNull TranslatableComponent component, final @NotNull Locale locale) { requireNonNull(component, "component"); requireNonNull(locale, "locale"); + return this.translate(component, locale, 0); + } + + private @Nullable Component translate(final @NotNull TranslatableComponent component, final @NotNull Locale locale, final int depth) { + if (depth >= 128) { + return null; + } for (final Translator source : this.sources) { - final Component translation = source.translate(component, locale); - if (translation != null) return translation; + Component translation = source.translate(component, locale); + if (translation != null) { + final List children = translation.children(); + if (translation instanceof TranslatableComponent) { + translation = this.translate((TranslatableComponent) translation, locale, depth + 1); + } + final List newChildren = new ArrayList<>(); + for (final Component child : children) { + if (child instanceof TranslatableComponent) { + final Component childTranslation = this.translate((TranslatableComponent) child, locale, depth + 1); + newChildren.add(childTranslation != null ? childTranslation : child); + } else { + newChildren.add(child); + } + } + return translation.children(newChildren); + } } return null; } diff --git a/api/src/test/java/net/kyori/adventure/translation/GlobalTranslatorTest.java b/api/src/test/java/net/kyori/adventure/translation/GlobalTranslatorTest.java index 324db2d43..3502264b9 100644 --- a/api/src/test/java/net/kyori/adventure/translation/GlobalTranslatorTest.java +++ b/api/src/test/java/net/kyori/adventure/translation/GlobalTranslatorTest.java @@ -46,6 +46,7 @@ class GlobalTranslatorTest { @BeforeEach void removeDummySourceBeforeEachTest() { GlobalTranslator.translator().removeSource(DummyTranslator.INSTANCE); + GlobalTranslator.translator().removeSource(DummyTranslatorRecursive.INSTANCE); } @ParameterizedTest @@ -103,6 +104,45 @@ void testTranslate() { ); } + @Test + void testRecursiveTranslateInitial() { + assertNull(GlobalTranslator.translator().translate("dummy", Locale.US)); + GlobalTranslator.translator().addSource(DummyTranslatorRecursive.INSTANCE); + assertEquals(new MessageFormat("Hello {0}!"), GlobalTranslator.translator().translate("dummy", Locale.US)); + assertEquals( + Component.text("{0}") + .append(Component.text("Hello ")) + .append(Component.text("!")), + GlobalTranslator.translator().translate(Component.translatable("dummy"), Locale.US) + ); + } + + @Test + void testRecursiveTranslateMiddle() { + assertNull(GlobalTranslator.translator().translate("dummy", Locale.US)); + GlobalTranslator.translator().addSource(DummyTranslatorRecursive.INSTANCE); + assertEquals(new MessageFormat("Hello {0}!"), GlobalTranslator.translator().translate("dummy", Locale.US)); + assertEquals( + Component.text("Hello ") + .append(Component.text("{0}")) + .append(Component.text("!")), + GlobalTranslator.translator().translate(Component.translatable("dummy2"), Locale.US) + ); + } + + @Test + void testRecursiveTranslateEnd() { + assertNull(GlobalTranslator.translator().translate("dummy", Locale.US)); + GlobalTranslator.translator().addSource(DummyTranslatorRecursive.INSTANCE); + assertEquals(new MessageFormat("Hello {0}!"), GlobalTranslator.translator().translate("dummy", Locale.US)); + assertEquals( + Component.text("Hello ") + .append(Component.text("!")) + .append(Component.text("{0}")), + GlobalTranslator.translator().translate(Component.translatable("dummy3"), Locale.US) + ); + } + static class DummyTranslator implements Translator { static final DummyTranslator INSTANCE = new DummyTranslator(); @@ -129,4 +169,42 @@ static class DummyTranslator implements Translator { : null; } } + + static class DummyTranslatorRecursive implements Translator { + static final Translator INSTANCE = new DummyTranslatorRecursive(); + + @Override + public @NotNull Key name() { + return Key.key("adventure", "test_dummy"); + } + + @Override + public @Nullable MessageFormat translate(final @NotNull String key, final @NotNull Locale locale) { + return (key.equals("dummy") && locale.equals(Locale.US)) + ? new MessageFormat("Hello {0}!") + : null; + } + + @Override + public @Nullable Component translate(final @NotNull TranslatableComponent component, final @NotNull Locale locale) { + if (!locale.equals(Locale.US)) + return null; + if (component.key().equals("dummy")) + return Component.translatable("otherDummy") + .append(Component.text("Hello ")) + .append(Component.text("!")); + if (component.key().equals("dummy2")) + return Component.text("Hello ") + .append(Component.translatable("otherDummy")) + .append(Component.text("!")); + if (component.key().equals("dummy3")) + return Component.text("Hello ") + .append(Component.text("!")) + .append(Component.translatable("otherDummy")); + if (component.key().equals("otherDummy")) + return Component.text("{0}"); + + return null; + } + } }