diff --git a/api/src/main/java/net/kyori/adventure/translation/GlobalTranslator.java b/api/src/main/java/net/kyori/adventure/translation/GlobalTranslator.java index 537c754278..df5b18464f 100644 --- a/api/src/main/java/net/kyori/adventure/translation/GlobalTranslator.java +++ b/api/src/main/java/net/kyori/adventure/translation/GlobalTranslator.java @@ -25,12 +25,17 @@ import java.util.Locale; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslatableComponent; import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; import net.kyori.examination.Examinable; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static java.util.Objects.requireNonNull; /** * A global source of translations. The global source is the default source used by adventure platforms @@ -75,6 +80,25 @@ public interface GlobalTranslator extends Translator, Examinable { return GlobalTranslatorImpl.INSTANCE.renderer; } + /** + * Renders a component using the {@link #renderer() global renderer}. + * + *

The locale will be determined using the + * {@link #localeOverride(Audience) locale override} or determined using the + * {@link Identity#LOCALE locale pointer}.

+ * + * @param component the component to render + * @param audience the audience to extract the locale from + * @return the rendered component + * @since 4.22.0 + */ + static @NotNull Component render(final @NotNull Component component, final @NotNull Audience audience) { + Locale locale = translator().localeOverride(requireNonNull(audience, "audience")); + if (locale == null) locale = audience.getOrDefault(Identity.LOCALE, null); + if (locale == null) return component; + return render(component, locale); + } + /** * Renders a component using the {@link #renderer() global renderer}. * @@ -115,4 +139,24 @@ public interface GlobalTranslator extends Translator, Examinable { * @since 4.0.0 */ boolean removeSource(final @NotNull Translator source); + + /** + * Sets an override for the locale of the audience when fetched using {@link #localeOverride(Audience)}. + * + * @param audience the audience, may be a {@link ForwardingAudience} to set the override for all members in the audience + * @param locale the locale to set, or {@code null} to remove the override + * @see #localeOverride(Audience) + * @since 4.22.0 + */ + void overrideLocale(final @NotNull Audience audience, final @Nullable Locale locale); + + /** + * Returns the override locale for an audience that will be used in {@link #render(Component, Audience)}. + * + * @param audience the audience member + * @return the locale, if any + * @see #overrideLocale(Audience, Locale) + * @since 4.22.0 + */ + @Nullable Locale localeOverride(final @NotNull Audience audience); } 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 ef9172cea1..749f09441b 100644 --- a/api/src/main/java/net/kyori/adventure/translation/GlobalTranslatorImpl.java +++ b/api/src/main/java/net/kyori/adventure/translation/GlobalTranslatorImpl.java @@ -26,9 +26,14 @@ import java.text.MessageFormat; import java.util.Collections; import java.util.Locale; +import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.identity.Identity; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslatableComponent; @@ -45,6 +50,7 @@ final class GlobalTranslatorImpl implements GlobalTranslator { static final GlobalTranslatorImpl INSTANCE = new GlobalTranslatorImpl(); final TranslatableComponentRenderer renderer = TranslatableComponentRenderer.usingTranslationSource(this); private final Set sources = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Map localeOverrides = new ConcurrentHashMap<>(); private GlobalTranslatorImpl() { } @@ -80,6 +86,14 @@ public boolean removeSource(final @NotNull Translator source) { return TriState.FALSE; } + @Override + public boolean canTranslate(final @NotNull String key, final @NotNull Locale locale) { + for (final Translator source : this.sources) { + if (source.canTranslate(key, locale)) return true; + } + return false; + } + @Override public @Nullable MessageFormat translate(final @NotNull String key, final @NotNull Locale locale) { requireNonNull(key, "key"); @@ -102,6 +116,30 @@ public boolean removeSource(final @NotNull Translator source) { return null; } + @Override + public void overrideLocale(final @NotNull Audience audience, final @Nullable Locale locale) { + if (requireNonNull(audience, "audience") instanceof ForwardingAudience) { + for (final Audience single : ((ForwardingAudience) audience).audiences()) { + this.overrideLocale(single, locale); + } + } else { + audience.get(Identity.UUID).ifPresent(uuid -> { + if (locale == null) { + this.localeOverrides.remove(uuid); + } else { + this.localeOverrides.put(uuid, locale); + } + }); + } + } + + @Override + public @Nullable Locale localeOverride(final @NotNull Audience audience) { + final UUID uuid = requireNonNull(audience, "audience").getOrDefault(Identity.UUID, null); + if (uuid == null) return null; + return this.localeOverrides.get(uuid); + } + @Override public @NotNull Stream examinableProperties() { return Stream.of(ExaminableProperty.of("sources", this.sources));