diff --git a/docs/core-features/text-and-localization.md b/docs/core-features/text-and-localization.md index 1ef1256..9c7e747 100644 --- a/docs/core-features/text-and-localization.md +++ b/docs/core-features/text-and-localization.md @@ -1,186 +1,175 @@ --- -description: Text formatting with HEX colors, MiniMessage, Adventure API, and i18n support. +description: Color formatting, message sending, MiniMessage, titles, and ResourceBundle-based i18n. --- -# Text & Localization +# Text and Localization -## Color Formatting +The `Text` utility class handles all message formatting and sending. It supports legacy `&` color codes, HEX colors (`<#RRGGBB>`), MiniMessage markup, and a built-in localization system backed by Java `ResourceBundle` files. -### Legacy Colors +## Color Formats -```java -Text.tell(player, "&aGreen &cRed &bBlue"); -Text.console("&7[Server] &aStarted successfully!"); -``` +| Format | Syntax | Example | Requires | +|---|---|---|---| +| Legacy codes | `&` + code | `&a` (green), `&l` (bold) | Any version | +| HEX colors | `<#RRGGBB>` | `<#FF5733>` | Spigot 1.16+ | +| MiniMessage | `mini:` prefix | `mini:text` | Adventure | +| Color scheme | `&p`, `&s`, `&t` | `&pPrimary color` | `ColorScheme` configured | + +### Legacy and HEX Colors -### HEX Colors (1.16+) +`Text.colorize()` translates both `&` color codes and `<#RRGGBB>` hex colors. HEX requires Spigot (or a fork) on 1.16+. ```java -Text.tell(player, "<#FF5733>Custom orange text!"); -Text.tell(player, "<#33FF57>Custom green <#3357FF>and blue!"); +String colored = Text.colorize("&aGreen &cRed &bBlue"); +String hex = Text.colorize("<#FF5733>Custom orange text!"); ``` ### Color Schemes +Define a three-color palette (`&p` primary, `&s` secondary, `&t` tertiary) through `BaseSettings`: + ```java -// Configure color scheme in your main class setBaseSettings(new BaseSettings() { @Override public ColorScheme colorScheme() { - return new ColorScheme("&6", "&7", "&f"); // Gold, Gray, White + return new ColorScheme("&6", "&7", "&f"); } }); -// Use placeholders -Text.tell(player, "&pPrimary"); // Gold -Text.tell(player, "&sSecondary"); // Gray -Text.tell(player, "&tTertiary"); // White +Text.tell(player, "&pPrimary &sSecondary &tTertiary"); ``` +`ColorScheme.fromConfig(ConfigurationSection)` can load colors from a YAML config section with `primary`, `secondary`, and `tertiary` keys. + ## Sending Messages -### To Players +### Player Messages + +| Method | Prefix | Color | Notes | +|---|---|---|---| +| `tell(CommandSender, String)` | Yes | Yes | Standard message | +| `coloredTell(CommandSender, String)` | No | Yes | Colors only, no prefix | +| `tellComponent(Player, Component)` | No | No | Sends an Adventure `Component` directly | +| `tellCentered(Player, String)` | No | Yes | Centered in chat; may not work with custom fonts or HEX colors | ```java -// With prefix Text.tell(player, "&aWelcome!"); - -// Without prefix Text.coloredTell(player, "&aHello!"); +Text.tellCentered(player, "&6&lWELCOME"); -// Send Component (for advanced formatting) Component component = Text.parseMini("Rainbow!"); Text.tellComponent(player, component); - -// Centered in chat -Text.tellCentered(player, "&6&lWELCOME"); ``` -### To Console +### Console Messages + +| Method | Prefix | Color | Notes | +|---|---|---|---| +| `console(String)` | Yes | Yes | Formatted console output | +| `coloredConsole(String)` | No | Yes | Colors only, no prefix | +| `log(String)` | No | No | Plain logger output at INFO level | +| `log(Level, String)` | No | No | Plain logger output at specified level | ```java -// Formatted console message (with prefix and colors) Text.console("&aPlugin loaded successfully!"); - -// Colored console message (colors but no prefix) Text.coloredConsole("&7Running background task..."); - -// Plain logger output (no colors, no prefix) Text.log("Starting initialization"); Text.log(Level.WARNING, "This is a warning"); ``` ### Broadcasting +| Method | Prefix | Color | +|---|---|---| +| `broadcast(String permission, String)` | Yes | Yes | +| `broadcastColored(String permission, String)` | No | Yes | + +Pass `null` as the permission to broadcast to all players. + ```java -// Broadcast to all players (with prefix) Text.broadcast(null, "&aServer event started!"); - -// Broadcast to players with permission Text.broadcast("myplugin.admin", "&cAdmin announcement!"); - -// Broadcast without prefix Text.broadcastColored(null, "&eGlobal message!"); ``` ## Titles ```java -// Simple title (default timing) Text.sendTitle(player, "&6Welcome!", "&eEnjoy your stay"); - -// With custom timing (fadeIn, stay, fadeOut in ticks) Text.sendTitle(player, "&6Welcome!", "&eEnjoy your stay", 10, 70, 20); - -// Only title (no subtitle) Text.sendTitle(player, "&6&lHELLO", null); - -// Only subtitle (no title) Text.sendTitle(player, null, "&7Welcome back"); - -// Clear current title/subtitle Text.clearTitle(player); ``` -## Localization - -### Setup Language Files - -PluginBase uses Java ResourceBundles for localization. Create language files in your plugin's resources: - -Create `src/main/resources/messages_en.properties`: +| Method | Parameters | +|---|---| +| `sendTitle(Player, String title, String subtitle)` | Default fade timing | +| `sendTitle(Player, String title, String subtitle, int fadeIn, int stay, int fadeOut)` | Timing in ticks | +| `clearTitle(Player)` | Clears current title and subtitle | -```properties -welcome=Welcome, {0}! -goodbye=Goodbye, {0}! -error.permission=You don't have permission! -``` - -Create `src/main/resources/messages_es.properties`: +Pass `null` for either `title` or `subtitle` to send only one. -```properties -welcome=¡Bienvenido, {0}! -goodbye=¡Adiós, {0}! -error.permission=¡No tienes permiso! -``` +## MiniMessage -Then register the resource bundle in your plugin: +Prefix any message string with `"mini:"` to parse it as [MiniMessage](https://docs.papermc.io/adventure/minimessage/) instead of legacy color codes. This works with `tell`, `coloredTell`, `tellLocalized`, and `coloredTellLocalized`. ```java -@Override -protected void enable() { - // Load the "messages" resource bundle for all locales - BaseManager.getTranslator().addResourceBundle("messages"); -} +Text.tell(player, "mini:Rainbow text!"); +Text.tell(player, "mini:Click for help!"); +Text.tell(player, "mini:Hover me!"); ``` -### Usage +To parse MiniMessage directly to a `Component`: ```java -// Best practice: Use tellLocalized to send directly -Text.tellLocalized(player, "welcome", player.getName()); -// English client: "Welcome, Steve!" -// Spanish client: "¡Bienvenido, Steve!" +Component comp = Text.parseMini("Text"); +Text.tellComponent(player, comp); +``` -// Without prefix -Text.coloredTellLocalized(player, "goodbye", player.getName()); +The `MINI_MESSAGE` field on `Text` exposes the underlying `MiniMessage` instance if you need direct access. -// Manual approach (if you need the string for other purposes) -String message = Text.localized("welcome", player, player.getName()); -Text.tell(player, message); +## Formatting Utilities -// Get localized string with default locale -String defaultMessage = Text.localizedDef("welcome", "Steve"); +| Method | Returns | Description | +|---|---|---| +| `colorize(String)` | `String` | Translates `&` codes and `<#HEX>` colors | +| `colorize(List)` | `List` | Batch colorize | +| `format(String)` | `String` | Prepends prefix, then colorizes | +| `error(String)` | `String` | Prepends prefix + `&c`, then colorizes | +| `strip(String)` | `String` | Removes all color codes | +| `strip(List)` | `List` | Batch strip | +| `parseMini(String)` | `Component` | Parses MiniMessage to an Adventure Component | +| `legacyParseMini(String)` | `String` | Parses MiniMessage, serializes to legacy string | +| `legacySerialize(Component)` | `String` | Serializes a Component to legacy format | -// Localized string with fallback -String msg = Text.localizedOrDefault(player, "missing.key", "&cDefault message"); +## Text Manipulation -// Shorthand for localized -String shorthand = Text.tl("welcome", player, player.getName()); -``` +| Method | Returns | Example | +|---|---|---| +| `capitalizeFirst(String)` | `String` | `"hello"` -> `"Hello"` | +| `titleCase(String, String)` | `String` | `"hello_world", "_"` -> `"Hello World"` | +| `toList(String)` | `List` | Splits on `\n` | +| `line(CommandSender)` | `String` | `CHAT_LINE` for players, `CONSOLE_LINE` for console | +| `getPrefix()` | `String` | Current plugin prefix | +| `getLocale(CommandSender)` | `Locale` | Player's client locale, or default locale for console/null | -## MiniMessage +Constants: `Text.CHAT_LINE` (player chat separator), `Text.CONSOLE_LINE` (console separator). -[MiniMessage](https://docs.papermc.io/adventure/minimessage/) support is enabled by prefixing your message with `"mini:"`. When detected, the message will be parsed using MiniMessage instead of legacy color codes. +## Ignoring Messages -```java -// Gradient text -Text.tell(player, "mini:Rainbow text!"); +Any message with the value `"ignore"` (case-insensitive) is silently discarded. This applies to `tell`, `coloredTell`, `console`, `coloredConsole`, `broadcast`, `broadcastColored`, `tellCentered`, `tellLocalized`, and `coloredTellLocalized`. -// Click actions -Text.tell(player, "mini:Click for help!"); +Useful for letting users disable messages in config files: -// Hover text -Text.tell(player, "mini:Hover me!"); +```yaml +# config.yml +join-message: "ignore" +``` -// Combined -String msg = "mini:" + - "Shop"; +```java +String msg = config.getString("join-message"); Text.tell(player, msg); - -// You can also parse MiniMessage directly to a Component -Component component = Text.parseMini("Rainbow text!"); -Text.tellComponent(player, component); ``` ## Prefix Configuration @@ -194,100 +183,134 @@ setBaseSettings(new BaseSettings() { @Override public boolean includePrefixOnEachLine() { - return true; // Add prefix to each line in multi-line messages + return true; } }); ``` -## Utility Methods +When `includePrefixOnEachLine()` returns `true` (the default), multi-line messages split on `\n` will have the prefix prepended to each line. -### Formatting +## Localization (i18n) -```java -// Colorize without sending -String colored = Text.colorize("&aGreen text"); -List coloredList = Text.colorize(Arrays.asList("&aLine 1", "&bLine 2")); +PluginBase uses Java `ResourceBundle` files (`.properties`) for localization. The player's Minecraft client locale is automatically detected and matched against registered bundles. -// Format with prefix -String formatted = Text.format("This will have the plugin prefix"); +### Setup -// Format as error (prefix + red) -String errorMsg = Text.error("Something went wrong!"); +Create `.properties` files in `src/main/resources/`: -// Strip all colors -String plain = Text.strip("&aGreen text"); // "Green text" -List plainList = Text.strip(coloredList); +`messages_en.properties`: +```properties +welcome=Welcome, {0}! +goodbye=Goodbye, {0}! +error.permission=You don''t have permission! ``` -### Text Manipulation +`messages_es.properties`: +```properties +welcome=Bienvenido, {0}! +goodbye=Adios, {0}! +error.permission=No tienes permiso! +``` -```java -// Capitalize first letter -String capitalized = Text.capitalizeFirst("hello"); // "Hello" +Register the bundle in your plugin's `enable()`: -// Title case with delimiter -String title = Text.titleCase("hello_world_test", "_"); // "Hello World Test" +```java +@Override +protected void enable() { + BaseManager.getTranslator().addResourceBundle("messages"); +} +``` -// Convert string with \n to list -List lines = Text.toList("Line 1\nLine 2\nLine 3"); +`addResourceBundle(String)` automatically scans for all locales defined in `Locales`. To register only specific locales, pass them explicitly: -// Get appropriate separator line -String line = Text.line(player); // CHAT_LINE for players, CONSOLE_LINE for console +```java +BaseManager.getTranslator().addResourceBundle("messages", Locales.ENGLISH, Locales.SPANISH); ``` -### MiniMessage Utilities +### Sending Localized Messages + +| Method | Prefix | Description | +|---|---|---| +| `tellLocalized(CommandSender, String key, Object... args)` | Yes | Sends a localized, formatted message | +| `coloredTellLocalized(CommandSender, String key, Object... args)` | No | Sends a localized, colorized message without prefix | ```java -// Parse MiniMessage to Component -Component comp = Text.parseMini("Text"); +Text.tellLocalized(player, "welcome", player.getName()); +Text.coloredTellLocalized(player, "goodbye", player.getName()); +``` -// Parse MiniMessage and convert to legacy string (for item names, etc.) -String legacy = Text.legacyParseMini("Text"); +### Retrieving Localized Strings -// Serialize Component to legacy format -String serialized = Text.legacySerialize(component); +| Method | Locale source | Description | +|---|---|---| +| `localized(String key, CommandSender, Object... args)` | Sender's client locale | Primary lookup method | +| `localized(String key, Locale, Object... args)` | Explicit `Locale` | For non-player contexts | +| `localizedDef(String key, Object... args)` | Default translator locale | Ignores sender locale | +| `localizedOrDefault(CommandSender, String key, String default, Object... args)` | Sender's client locale | Falls back to `default` if key is missing | +| `tl(String key, CommandSender, Object... args)` | Sender's client locale | Alias for `localized` | +| `tl(String key, Locale, Object... args)` | Explicit `Locale` | Alias for `localized` | + +```java +String msg = Text.localized("welcome", player, player.getName()); +String def = Text.localizedDef("welcome", "Steve"); +String safe = Text.localizedOrDefault(player, "missing.key", "&cDefault message"); ``` -### Helper Getters +Placeholders use `java.text.MessageFormat` syntax: `{0}`, `{1}`, etc. -```java -// Get current prefix -String prefix = Text.getPrefix(); +### Inline Localization Placeholders -// Get player's locale -Locale locale = Text.getLocale(player); +Embed localized keys within strings using `#{key}` syntax: -// Constants -String chatLine = Text.CHAT_LINE; // Player chat separator -String consoleLine = Text.CONSOLE_LINE; // Console separator +```java +String message = "#{greeting} player! #{welcome}"; +String result = Text.localizePlaceholders(message, player); ``` -## Special Features +| Method | Locale source | +|---|---| +| `localizePlaceholders(String, CommandSender, Object... args)` | Sender's locale | +| `localizePlaceholders(String, Locale, Object... args)` | Explicit locale | +| `localizePlaceholdersDef(String, Object... args)` | Default locale | -### Ignoring Messages +The same `args` are applied to every placeholder in the string. -Any message with the value `"ignore"` (case-insensitive) will not be sent. This is useful for configuration files where you want to disable certain messages: +### Loading Bundles from the Data Folder -```java -// This will not send anything -Text.tell(player, "ignore"); -Text.console("IGNORE"); // Case-insensitive +`addResourceBundleFromFolder(String)` loads `.properties` files from the `plugins//locales/` directory instead of from the JAR's classpath. This lets server administrators add or override translations at runtime. -// Useful in config.yml: -// join-message: "ignore" # Disables the message +```java +BaseManager.getTranslator().addResourceBundleFromFolder("messages"); ``` -### Localization Placeholders +### Translator API + +`BaseManager.getTranslator()` returns the `Translator` instance. Key methods beyond `addResourceBundle`: + +| Method | Description | +|---|---| +| `get(String key)` | Gets message for key using default locale | +| `get(String key, Locale)` | Gets message for key using specified locale | +| `containsKey(String key)` | Checks if key exists in default locale | +| `containsKey(String key, Locale)` | Checks if key exists in specified locale | +| `setLocale(Locale)` | Changes the default locale | +| `getLocale()` | Returns the current default locale | +| `add(LocaleReader)` | Registers a custom `LocaleReader` | +| `add(ResourceBundle)` | Registers a `ResourceBundle` directly | +| `clear()` | Removes all registered locale data | -You can embed localized keys within strings using `#{key}` syntax: +### ConfigLocaleReader (YAML-based Localization) + +For plugins that prefer a single YAML file over `.properties` bundles, `ConfigLocaleReader` wraps a Bukkit `FileConfiguration` as a `LocaleReader`: ```java -// Suppose you have: greeting=Hello, welcome=Welcome back -String message = "#{greeting} player! #{welcome}"; -String localized = Text.localizePlaceholders(message, player); -// Result: "Hello player! Welcome back" +FileConfiguration langConfig = YamlConfiguration.loadConfiguration(langFile); +LocaleReader reader = new ConfigLocaleReader(langConfig, Locales.ENGLISH); +BaseManager.getTranslator().add(reader); ``` +This is useful when your plugin already uses YAML for all configuration and you want a consistent `messages.yml` approach rather than `.properties` files. + ## Complete Example ```java @@ -295,7 +318,6 @@ public class MyPlugin extends BasePlugin { @Override protected void enable() { - // Configure settings setBaseSettings(new BaseSettings() { @Override public String prefix() { @@ -304,47 +326,23 @@ public class MyPlugin extends BasePlugin { @Override public ColorScheme colorScheme() { - return new ColorScheme("&6", "&7", "&e"); // Gold, Gray, Yellow + return new ColorScheme("&6", "&7", "&e"); } }); - // Load localization files BaseManager.getTranslator().addResourceBundle("messages"); - // Log to console Text.console("&aPlugin initialized!"); - // Join event with localized messages Events.subscribe(PlayerJoinEvent.class) .handler(e -> { Player player = e.getPlayer(); - - // Best practice: Use tellLocalized for direct sending Text.tellLocalized(player, "welcome.message", player.getName()); - - // Title with timing Text.sendTitle(player, "&pWelcome!", "&s" + player.getName(), 10, 70, 20); - - // MiniMessage with mini: prefix Text.tell(player, "mini:Enjoy your stay!"); - - // Broadcast to everyone Text.broadcast(null, "&a" + player.getName() + " joined the server!"); }) .bindWith(this); - - // Quit event - Events.subscribe(PlayerQuitEvent.class) - .handler(e -> { - Player player = e.getPlayer(); - - // Localized without prefix - Text.coloredTellLocalized(player, "goodbye.message", player.getName()); - - // Admin-only broadcast - Text.broadcast("myplugin.admin", "&7[Admin] &e" + player.getName() + " left"); - }) - .bindWith(this); } @Override @@ -354,10 +352,10 @@ public class MyPlugin extends BasePlugin { } ``` -Example `messages_en.properties`: +`messages_en.properties`: ```properties welcome.message=&pWelcome back, &s{0}&p! goodbye.message=&sSee you later, &t{0}&s! -error.no-permission=&cYou don't have permission to do that! +error.no-permission=&cYou don''t have permission to do that! ```