Added localization support with translatable components, centralized translations, and replaced hardcoded strings across commands and messages.

This commit is contained in:
2026-07-24 16:06:20 +02:00
parent cf9326c2a5
commit ef2352d381
14 changed files with 131 additions and 34 deletions
@@ -52,6 +52,7 @@ public final class Main extends JavaPlugin {
@Override
public void onEnable() {
Translations.register();
Main.taskFactory = BukkitTaskChainFactory.create(this);
getLogger().info("Start constructing blocks from Database...");
database.loadPixelBlocks();
@@ -94,6 +95,7 @@ public final class Main extends JavaPlugin {
Bukkit.getOnlinePlayers().forEach(QuitWhileInPixelBlockListener::kickPlayerOutOfWorld);
taskFactory.shutdown(5, TimeUnit.SECONDS);
database.close();
Translations.unregister();
}
public static Main plugin() {
@@ -17,6 +17,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
@@ -52,7 +53,7 @@ public class PixelBlockItem {
return new BlockInfo(blockId, ownerId);
}
public static @NotNull ItemStack getBlockAsItem(@NotNull PixelBlock block) {
public static @NotNull ItemStack getBlockAsItem(@NotNull PixelBlock block, @NotNull Locale locale) {
String ownerName = Optional.ofNullable(Bukkit.getOfflinePlayer(block.getOwnerUUID()).getName()).orElseGet(() -> block.getOwnerUUID().toString());
ItemStack itemStack = HeadUtil.getCustomTextureHead(itemTexture);
@@ -60,10 +61,10 @@ public class PixelBlockItem {
meta.setMaxStackSize(1);
meta.getPersistentDataContainer().set(idProperty, PersistentDataType.STRING, block.getBlockUUID().toString());
meta.getPersistentDataContainer().set(ownerProperty, PersistentDataType.STRING, block.getOwnerUUID().toString());
meta.displayName(Component.text("Pixelblock von " + ownerName));
meta.displayName(Translations.render(Component.translatable("pixelblocks.item.name.owned", Component.text(ownerName)), locale));
meta.lore(List.of(
Component.text(ownerName + " ist der Besitzer dieses Blocks."),
Component.text("Klicke auf den gesetzten Block, um diesen zu bearbeiten!"),
Translations.render(Component.translatable("pixelblocks.item.lore.owner", Component.text(ownerName)), locale),
Translations.render(Component.translatable("pixelblocks.item.lore.edit-hint"), locale),
Component.text(block.getBlockUUID().toString()).color(NamedTextColor.DARK_GRAY)
));
itemStack.setItemMeta(meta);
@@ -72,13 +73,17 @@ public class PixelBlockItem {
}
public static @NotNull ItemStack getEmptyPixelBlock() {
return getEmptyPixelBlock(Translations.defaultLocale);
}
public static @NotNull ItemStack getEmptyPixelBlock(@NotNull Locale locale) {
ItemStack item = HeadUtil.getCustomTextureHead(itemTexture);
ItemMeta meta = item.getItemMeta();
meta.setMaxStackSize(1);
meta.displayName(Component.text("Leerer Pixelblock"));
meta.displayName(Translations.render(Component.translatable("pixelblocks.item.name.empty"), locale));
meta.lore(List.of(
Component.text("Der erste Spieler, der den Block platziert wird zum Besitzer des Blocks."),
Component.text("Klicke auf den gesetzten Block, um diesen zu bearbeiten!")
Translations.render(Component.translatable("pixelblocks.item.lore.first-placer"), locale),
Translations.render(Component.translatable("pixelblocks.item.lore.edit-hint"), locale)
));
meta.getPersistentDataContainer().set(idProperty, PersistentDataType.STRING, emptyBlockUUID.toString());
item.setItemMeta(meta);
@@ -0,0 +1,39 @@
package eu.mhsl.minecraft.pixelblocks;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationStore;
import org.jetbrains.annotations.NotNull;
import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
public class Translations {
public static final Locale defaultLocale = Locale.ENGLISH;
private static final List<Locale> supportedLocales = List.of(Locale.ENGLISH, Locale.GERMAN);
private static TranslationStore.StringBased<MessageFormat> store;
public static void register() {
store = TranslationStore.messageFormat(Key.key("pixelblocks", "translations"));
store.defaultLocale(defaultLocale);
for(Locale locale : supportedLocales) {
ResourceBundle bundle = ResourceBundle.getBundle("i18n.messages", locale, Translations.class.getClassLoader());
store.registerAll(locale, bundle, true);
}
GlobalTranslator.translator().addSource(store);
}
public static void unregister() {
if(store != null) GlobalTranslator.translator().removeSource(store);
}
// Für Texte, die persistiert werden (z. B. Item-Namen) und daher nicht
// automatisch pro Empfänger übersetzt werden können
public static @NotNull Component render(@NotNull Component component, @NotNull Locale locale) {
return GlobalTranslator.render(component, locale);
}
}
@@ -12,13 +12,13 @@ import java.util.UUID;
public class CreateSubCommand extends SubCommand {
public CreateSubCommand() {
super("create", "pixelblocks.command.create", "Erstellt einen neuen Pixelblock an deiner Position.");
super("create", "pixelblocks.command.create", "pixelblocks.command.create.description");
}
@Override
public void execute(@NotNull Player player, @NotNull String[] args) {
if(PixelBlockWorld.isPixelWorld(player.getWorld())) {
player.sendMessage(Component.text("Pixelblöcke können nicht innerhalb anderer Pixelblöcke erstellt werden.", NamedTextColor.RED));
player.sendMessage(Component.translatable("pixelblocks.error.create-inside").color(NamedTextColor.RED));
return;
}
@@ -11,13 +11,13 @@ import java.util.List;
public class DestroyAllSubCommand extends SubCommand {
public DestroyAllSubCommand() {
super("destroyall", "pixelblocks.command.destroyall", "Zerstört alle Pixelblöcke auf dem Server.");
super("destroyall", "pixelblocks.command.destroyall", "pixelblocks.command.destroyall.description");
}
@Override
public void execute(@NotNull Player player, @NotNull String[] args) {
List<PixelBlock> blocksToDestroy = List.copyOf(Main.pixelBlocks);
blocksToDestroy.forEach(pixelBlock -> pixelBlock.destroy(player, true));
player.sendMessage(Component.text(String.format("%d Pixelblöcke werden zerstört.", blocksToDestroy.size()), NamedTextColor.GREEN));
player.sendMessage(Component.translatable("pixelblocks.command.destroyall.success", Component.text(blocksToDestroy.size())).color(NamedTextColor.GREEN));
}
}
@@ -9,19 +9,19 @@ import org.jetbrains.annotations.NotNull;
public class ExitSubCommand extends SubCommand {
public ExitSubCommand() {
super("exit", "pixelblocks.command.exit", "Verlässt den Pixelblock, in dem du dich befindest.");
super("exit", "pixelblocks.command.exit", "pixelblocks.command.exit.description");
}
@Override
public void execute(@NotNull Player player, @NotNull String[] args) {
if(!PixelBlockWorld.isPixelWorld(player.getWorld())) {
player.sendMessage(Component.text("Du befindest dich nicht in einem Pixelblock.", NamedTextColor.RED));
player.sendMessage(Component.translatable("pixelblocks.error.not-in-pixelblock").color(NamedTextColor.RED));
return;
}
PixelBlock pixelBlock = PixelBlock.getPixelBlockFromBlockWorld(player.getWorld());
if(pixelBlock == null) {
player.sendMessage(Component.text("Dieser Pixelblock konnte nicht gefunden werden.", NamedTextColor.RED));
player.sendMessage(Component.translatable("pixelblocks.error.block-not-found").color(NamedTextColor.RED));
return;
}
@@ -12,7 +12,7 @@ import java.util.UUID;
public class GiveSubCommand extends SubCommand {
public GiveSubCommand() {
super("give", "pixelblocks.command.give", "Gibt dir ein Pixelblock-Item, optional mit fester UUID.");
super("give", "pixelblocks.command.give", "pixelblocks.command.give.description");
}
@Override
@@ -24,16 +24,16 @@ public class GiveSubCommand extends SubCommand {
try {
blockId = UUID.fromString(args[0]);
} catch(IllegalArgumentException e) {
player.sendMessage(Component.text(String.format("'%s' ist keine gültige UUID.", args[0]), NamedTextColor.RED));
player.sendMessage(Component.translatable("pixelblocks.error.invalid-uuid", Component.text(args[0])).color(NamedTextColor.RED));
return;
}
}
ItemStack item = PixelBlockItem.getEmptyPixelBlock();
ItemStack item = PixelBlockItem.getEmptyPixelBlock(player.locale());
PixelBlockItem.setBlockId(item, blockId);
player.getInventory().addItem(item);
player.sendMessage(Component.text(String.format("Pixelblock '%s' erhalten.", blockId), NamedTextColor.GREEN));
player.sendMessage(Component.translatable("pixelblocks.command.give.success", Component.text(blockId.toString())).color(NamedTextColor.GREEN));
}
@Override
@@ -25,7 +25,7 @@ public class PixelBlocksCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!(sender instanceof Player player)) {
sender.sendMessage(Component.text("Dieser Befehl kann nur von Spielern ausgeführt werden.", NamedTextColor.RED));
sender.sendMessage(Component.translatable("pixelblocks.command.players-only").color(NamedTextColor.RED));
return true;
}
@@ -39,7 +39,7 @@ public class PixelBlocksCommand implements CommandExecutor, TabCompleter {
}
if(!player.hasPermission(subCommand.get().permission())) {
player.sendMessage(Component.text("Dazu hast du keine Berechtigung.", NamedTextColor.RED));
player.sendMessage(Component.translatable("pixelblocks.command.no-permission").color(NamedTextColor.RED));
return true;
}
@@ -68,12 +68,13 @@ public class PixelBlocksCommand implements CommandExecutor, TabCompleter {
}
private void sendUsage(@NotNull Player player) {
player.sendMessage(Component.text("PixelBlocks-Befehle:", NamedTextColor.GOLD));
player.sendMessage(Component.translatable("pixelblocks.command.usage.header").color(NamedTextColor.GOLD));
this.subCommands.stream()
.filter(sub -> player.hasPermission(sub.permission()))
.forEach(sub -> player.sendMessage(Component.text()
.append(Component.text("/pixelblocks " + sub.name(), NamedTextColor.YELLOW))
.append(Component.text(" - " + sub.description(), NamedTextColor.GRAY))
.append(Component.text(" - ", NamedTextColor.GRAY))
.append(Component.translatable(sub.descriptionKey()).color(NamedTextColor.GRAY))
.build()));
}
}
@@ -8,12 +8,12 @@ import java.util.List;
public abstract class SubCommand {
private final String name;
private final String permission;
private final String description;
private final String descriptionKey;
protected SubCommand(@NotNull String name, @NotNull String permission, @NotNull String description) {
protected SubCommand(@NotNull String name, @NotNull String permission, @NotNull String descriptionKey) {
this.name = name;
this.permission = permission;
this.description = description;
this.descriptionKey = descriptionKey;
}
public final @NotNull String name() {
@@ -24,8 +24,8 @@ public abstract class SubCommand {
return permission;
}
public final @NotNull String description() {
return description;
public final @NotNull String descriptionKey() {
return descriptionKey;
}
public abstract void execute(@NotNull Player player, @NotNull String[] args);
@@ -1,11 +1,14 @@
package eu.mhsl.minecraft.pixelblocks.listeners;
import eu.mhsl.minecraft.pixelblocks.PixelBlockItem;
import eu.mhsl.minecraft.pixelblocks.Translations;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.inventory.ItemStack;
import java.util.Locale;
import java.util.UUID;
public class CraftPixelBlockListener implements Listener {
@@ -17,6 +20,9 @@ public class CraftPixelBlockListener implements Listener {
PixelBlockItem.BlockInfo info = PixelBlockItem.getBlockInfo(result);
if(info == null || !info.id().equals(PixelBlockItem.emptyBlockUUID)) return;
PixelBlockItem.setBlockId(result, UUID.randomUUID());
Locale locale = event.getWhoClicked() instanceof Player player ? player.locale() : Translations.defaultLocale;
ItemStack localizedResult = PixelBlockItem.getEmptyPixelBlock(locale);
PixelBlockItem.setBlockId(localizedResult, UUID.randomUUID());
event.getInventory().setResult(localizedResult);
}
}
@@ -24,13 +24,13 @@ public class PlacePixelBlockListener implements Listener {
World playerWorld = event.getPlayer().getWorld();
if(PixelBlockWorld.isPixelWorld(playerWorld)) {
event.getPlayer().sendMessage(Component.text("In Pixelblöcken kann kein Pixelblock platziert werden.", NamedTextColor.RED));
event.getPlayer().sendMessage(Component.translatable("pixelblocks.error.place-inside").color(NamedTextColor.RED));
event.setCancelled(true);
return;
}
if(PixelBlock.exists(info.id())) {
event.getPlayer().sendMessage(Component.text("Dieser Pixelblock existiert bereits in der Welt!", NamedTextColor.RED));
event.getPlayer().sendMessage(Component.translatable("pixelblocks.error.already-exists").color(NamedTextColor.RED));
event.setCancelled(true);
return;
}
@@ -124,7 +124,7 @@ public class PixelBlock {
public void enterBlock(@NotNull Player player) {
if(Main.configuration().onlyEditableByOwner() && !player.getUniqueId().equals(ownerUUID)) {
player.sendMessage(Component.text("Dieser Pixelblock gehört nicht dir!", NamedTextColor.RED));
player.sendMessage(Component.translatable("pixelblocks.error.not-owner").color(NamedTextColor.RED));
return;
}
@@ -171,13 +171,13 @@ public class PixelBlock {
public void destroy(Player destroyedBy, boolean force) {
if(!this.isAccessible) return;
if(!force && Main.configuration().onlyBreakableByOwner() && !destroyedBy.getUniqueId().equals(ownerUUID)) {
destroyedBy.sendMessage(Component.text("Dieser Pixelblock gehört nicht dir!", NamedTextColor.RED));
destroyedBy.sendMessage(Component.translatable("pixelblocks.error.not-owner").color(NamedTextColor.RED));
return;
}
Location returnLocation = this.getReturnLocation();
this.pixelWorld.getPlayersInWorld().forEach(p -> {
p.sendMessage(Component.text("Der Pixelblock wurde von einem anderen Spieler abgebaut!", NamedTextColor.RED));
p.sendMessage(Component.translatable("pixelblocks.info.destroyed-by-other").color(NamedTextColor.RED));
p.teleport(returnLocation);
});
@@ -193,7 +193,7 @@ public class PixelBlock {
this.removeEntities();
World world = this.pixelBlockLocation.getWorld();
world.playSound(this.pixelBlockLocation, Sound.BLOCK_COPPER_BULB_BREAK, 1.0F, 2.0F);
world.dropItem(this.getPixelBlockLocation().add(new Vector(0.5, 0.5, 0.5)), PixelBlockItem.getBlockAsItem(this));
world.dropItem(this.getPixelBlockLocation().add(new Vector(0.5, 0.5, 0.5)), PixelBlockItem.getBlockAsItem(this, destroyedBy.locale()));
})
.execute();
@@ -0,0 +1,22 @@
pixelblocks.command.players-only=Dieser Befehl kann nur von Spielern ausgeführt werden.
pixelblocks.command.no-permission=Dazu hast du keine Berechtigung.
pixelblocks.command.usage.header=PixelBlocks-Befehle:
pixelblocks.command.create.description=Erstellt einen neuen Pixelblock an deiner Position.
pixelblocks.command.give.description=Gibt dir ein Pixelblock-Item, optional mit fester UUID.
pixelblocks.command.give.success=Pixelblock '{0}' erhalten.
pixelblocks.command.exit.description=Verlässt den Pixelblock, in dem du dich befindest.
pixelblocks.command.destroyall.description=Zerstört alle Pixelblöcke auf dem Server.
pixelblocks.command.destroyall.success={0} Pixelblöcke werden zerstört.
pixelblocks.error.not-owner=Dieser Pixelblock gehört nicht dir!
pixelblocks.error.create-inside=Pixelblöcke können nicht innerhalb anderer Pixelblöcke erstellt werden.
pixelblocks.error.place-inside=In Pixelblöcken kann kein Pixelblock platziert werden.
pixelblocks.error.already-exists=Dieser Pixelblock existiert bereits in der Welt!
pixelblocks.error.invalid-uuid='{0}' ist keine gültige UUID.
pixelblocks.error.not-in-pixelblock=Du befindest dich nicht in einem Pixelblock.
pixelblocks.error.block-not-found=Dieser Pixelblock konnte nicht gefunden werden.
pixelblocks.info.destroyed-by-other=Der Pixelblock wurde von einem anderen Spieler abgebaut!
pixelblocks.item.name.owned=Pixelblock von {0}
pixelblocks.item.name.empty=Leerer Pixelblock
pixelblocks.item.lore.owner={0} ist der Besitzer dieses Blocks.
pixelblocks.item.lore.edit-hint=Klicke auf den gesetzten Block, um diesen zu bearbeiten!
pixelblocks.item.lore.first-placer=Der erste Spieler, der den Block platziert, wird zum Besitzer des Blocks.
@@ -0,0 +1,22 @@
pixelblocks.command.players-only=This command can only be used by players.
pixelblocks.command.no-permission=You don't have permission to do that.
pixelblocks.command.usage.header=PixelBlocks commands:
pixelblocks.command.create.description=Creates a new pixel block at your position.
pixelblocks.command.give.description=Gives you a pixel block item, optionally with a fixed UUID.
pixelblocks.command.give.success=Received pixel block '{0}'.
pixelblocks.command.exit.description=Leaves the pixel block you are currently in.
pixelblocks.command.destroyall.description=Destroys all pixel blocks on the server.
pixelblocks.command.destroyall.success={0} pixel blocks are being destroyed.
pixelblocks.error.not-owner=This pixel block does not belong to you!
pixelblocks.error.create-inside=Pixel blocks cannot be created inside other pixel blocks.
pixelblocks.error.place-inside=Pixel blocks cannot be placed inside pixel blocks.
pixelblocks.error.already-exists=This pixel block already exists in the world!
pixelblocks.error.invalid-uuid='{0}' is not a valid UUID.
pixelblocks.error.not-in-pixelblock=You are not inside a pixel block.
pixelblocks.error.block-not-found=This pixel block could not be found.
pixelblocks.info.destroyed-by-other=The pixel block was destroyed by another player!
pixelblocks.item.name.owned=Pixel block of {0}
pixelblocks.item.name.empty=Empty pixel block
pixelblocks.item.lore.owner={0} is the owner of this block.
pixelblocks.item.lore.edit-hint=Click the placed block to edit it!
pixelblocks.item.lore.first-placer=The first player to place this block becomes its owner.