Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import net.buildtheearth.buildteamtools.modules.plotsystem.PlotSystemModule;
import net.buildtheearth.buildteamtools.modules.stats.StatsModule;
import net.buildtheearth.buildteamtools.modules.network.api.NominatimAPI;
import net.buildtheearth.buildteamtools.i18n.MessagePrefix;
import net.buildtheearth.buildteamtools.i18n.Translations;
import net.buildtheearth.buildteamtools.utils.io.ConfigurationLoader;
import net.buildtheearth.buildteamtools.utils.io.GeneratorConfig;
import net.buildtheearth.buildteamtools.utils.io.MapConfig;
Expand All @@ -28,6 +30,7 @@
*/
public class BuildTeamTools extends JavaPlugin {

/** Fallback legacy prefix; runtime chat prefix comes from {@code config.yml} {@code message-prefix}. */
public static final String PREFIX = "&9&lBTE &8> &7";
public static final String CONSOLE_PREFIX = "[BuildTeamTools] ";

Expand Down Expand Up @@ -94,6 +97,9 @@ public void reloadConfig() {
generatorConfigLoader.reload();
mapConfigLoader.reload();
plotSystemConfigLoader.reload();
MessagePrefix.apply(getMainConfig().messagePrefix());
Translations.reload(this);
setDebug(getMainConfig().debug());
}

public MainConfig getMainConfig() {
Expand Down
1,037 changes: 1,037 additions & 0 deletions src/main/java/net/buildtheearth/buildteamtools/i18n/MessageKeys.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package net.buildtheearth.buildteamtools.i18n;

import com.alpsbte.alpslib.utils.ChatHelper;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;

/**
* Plugin chat prefix used by translations via the MiniMessage {@code <prefix>} tag.
*
* <p>The appearance is configured in {@code config.yml} ({@code message-prefix}). Translation
* strings can insert it with {@code <prefix>} so branding stays consistent and easy to restyle.
*/
public final class MessagePrefix {

/** MiniMessage equivalent of the historical {@code &9&lBTE &8> &7} ChatHelper prefix. */
public static final String DEFAULT_MINI_MESSAGE =
"<dark_blue><bold>BTE</bold></dark_blue> <dark_gray>></dark_gray> ";

private static final MiniMessage PLAIN = MiniMessage.miniMessage();
private static final LegacyComponentSerializer LEGACY_AMPERSAND =
LegacyComponentSerializer.legacyAmpersand();

private static volatile Component component = PLAIN.deserialize(DEFAULT_MINI_MESSAGE);
private static volatile String miniMessage = DEFAULT_MINI_MESSAGE;

private MessagePrefix() {
}

public static void apply(@NotNull String configuredMiniMessage) {
String value = configuredMiniMessage.isBlank() ? DEFAULT_MINI_MESSAGE : configuredMiniMessage;
Component parsed = PLAIN.deserialize(value);
miniMessage = value;
component = parsed;
syncChatHelper(parsed);
}

public static Component component() {
return component;
}

public static String miniMessage() {
return miniMessage;
}

/** Legacy ampersand form for AlpsLib {@link ChatHelper} and older call sites. */
public static String legacyAmpersand() {
return LEGACY_AMPERSAND.serialize(component);
}

/**
* Resolves {@code <prefix>} / {@code <prefix/>} inside MiniMessage translation strings to the
* configured prefix component.
*/
public static TagResolver tagResolver() {
return TagResolver.resolver("prefix", (args, ctx) -> Tag.selfClosingInserting(component()));
}

private static void syncChatHelper(Component prefix) {
if (ChatHelper.plugin == null) {
return;
}
// ChatHelper stores a TextComponent; round-trip through legacy to match that shape.
ChatHelper.PREFIX_COMPONENT = LEGACY_AMPERSAND.deserialize(LEGACY_AMPERSAND.serialize(prefix));
}
}
156 changes: 156 additions & 0 deletions src/main/java/net/buildtheearth/buildteamtools/i18n/Messages.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package net.buildtheearth.buildteamtools.i18n;

import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Locale;

/**
* Helpers for building and rendering translated Adventure components.
*
* <p>Chat, action bars, and titles can usually send {@link Component#translatable} directly;
* Paper resolves them through {@link GlobalTranslator}. Inventory item names/lore do not, so
* use {@link #render} / {@link #legacy} / {@link #lore} for menus.
*
* <p>Prefer putting {@code <prefix>} inside translation strings when a chat prefix is wanted.
* {@link #withPrefix(Component)} remains for composing without editing the string.
*/
public final class Messages {

private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.legacySection();
private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText();

private Messages() {
}

public static Component component(String key, ComponentLike... args) {
return Component.translatable(key, args);
}

/** Convenience for {@code %s}/{@code <arg:N>} placeholders from plain values. */
public static Component component(String key, Object... args) {
ComponentLike[] components = new ComponentLike[args.length];
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
components[i] = arg instanceof ComponentLike componentLike
? componentLike
: Component.text(String.valueOf(arg));
}
return component(key, components);
}

public static Component withPrefix(Component message) {
return MessagePrefix.component().append(message);
}

public static Component playerOnly() {
return component(MessageKeys.Common.PLAYER_ONLY);
}

public static Component noPermission(String permission) {
return component(MessageKeys.Common.NO_PERMISSION, Component.text(permission));
}

public static Locale locale(@Nullable Audience audience) {
if (audience == null) {
return Translations.DEFAULT_LOCALE;
}
return audience.get(Identity.LOCALE).orElse(Translations.DEFAULT_LOCALE);
}

public static Locale locale(Player player) {
return player.locale();
}

public static Component render(Component component, Locale locale) {
return GlobalTranslator.render(component, locale);
}

public static Component render(@Nullable Audience audience, Component component) {
return render(component, locale(audience));
}

public static Component render(Player player, Component component) {
return render(component, locale(player));
}

public static Component render(Player player, String key, ComponentLike... args) {
return render(player, component(key, args));
}

/** Legacy section string for APIs that still require {@link String} item names/lore. */
public static String legacy(Player player, Component component) {
return LEGACY.serialize(render(player, component));
}

public static String legacy(Player player, String key, ComponentLike... args) {
return legacy(player, component(key, args));
}

public static String plain(Player player, String key, ComponentLike... args) {
return PLAIN.serialize(render(player, key, args));
}

/** Renders with the default locale when no viewer is available. */
public static String plain(String key, ComponentLike... args) {
return PLAIN.serialize(render((Audience) null, component(key, args)));
}

/** Legacy section string for the default locale when no viewer is available. */
public static String legacy(String key, ComponentLike... args) {
return LEGACY.serialize(render((Audience) null, component(key, args)));
}

/**
* Renders a translation as Adventure lore lines (newlines + wrap). Prefer this with
* {@link net.buildtheearth.buildteamtools.utils.ItemComponents}.
*/
public static List<Component> loreComponents(Player player, String key, ComponentLike... args) {
return loreComponents(player, key, TextWrap.DEFAULT_LORE_WIDTH, args);
}

public static List<Component> loreComponents(Player player, String key, int maxWidth, ComponentLike... args) {
return loreComponentLines(render(player, key, args), maxWidth);
}

/** Lore components for the default locale when no viewer is available. */
public static List<Component> loreComponents(String key, ComponentLike... args) {
return loreComponentLines(render((Audience) null, component(key, args)), TextWrap.DEFAULT_LORE_WIDTH);
}

/**
* @deprecated Prefer {@link #loreComponents}; kept for AnvilGUI / AlpsLib APIs that still take strings.
*/
@Deprecated
public static List<String> lore(Player player, String key, ComponentLike... args) {
return lore(player, key, TextWrap.DEFAULT_LORE_WIDTH, args);
}

/** @deprecated Prefer {@link #loreComponents}. */
@Deprecated
public static List<String> lore(Player player, String key, int maxWidth, ComponentLike... args) {
return loreLines(render(player, key, args), maxWidth);
}

/** @deprecated Prefer {@link #loreComponents}. */
@Deprecated
public static List<String> lore(String key, ComponentLike... args) {
return loreLines(render((Audience) null, component(key, args)), TextWrap.DEFAULT_LORE_WIDTH);
}

private static List<Component> loreComponentLines(Component rendered, int maxWidth) {
return TextWrap.wrap(rendered, maxWidth);
}

private static List<String> loreLines(Component rendered, int maxWidth) {
return loreComponentLines(rendered, maxWidth).stream().map(LEGACY::serialize).toList();
}
}
Loading
Loading