A serverside user interface library for Minecraft: Java Edition
—
Internationalization support with locale-based rendering, translation registries, and global translation management. Adventure's translation system enables multi-language support for text components.
Components that display translated text based on the client's locale and registered translations.
/**
* Component that displays translated text
*/
interface TranslatableComponent extends BuildableComponent<TranslatableComponent, TranslatableComponent.Builder> {
/**
* Gets the translation key
* @return the translation key
*/
String key();
/**
* Gets the translation arguments
* @return list of translation arguments
*/
List<TranslationArgument> arguments();
/**
* Gets the fallback string used when translation is not available
* @return the fallback string or null
*/
@Nullable String fallback();
/**
* Sets the translation key
* @param key the translation key
* @return component with new key
*/
TranslatableComponent key(String key);
/**
* Sets the translation arguments
* @param arguments the arguments
* @return component with new arguments
*/
TranslatableComponent arguments(List<? extends TranslationArgumentLike> arguments);
TranslatableComponent arguments(TranslationArgumentLike... arguments);
/**
* Sets the fallback string
* @param fallback the fallback string
* @return component with new fallback
*/
TranslatableComponent fallback(@Nullable String fallback);
}Arguments that can be passed to translatable components for parameter substitution.
/**
* Argument for translatable components
*/
interface TranslationArgument {
/**
* Converts this argument to a component
* @return the component representation
*/
Component asComponent();
/**
* Converts this argument to a string
* @return the string representation
*/
String asString();
/**
* Creates a component argument
* @param component the component
* @return translation argument
*/
static TranslationArgument component(ComponentLike component);
/**
* Creates a string argument
* @param string the string
* @return translation argument
*/
static TranslationArgument string(String string);
/**
* Creates a numeric argument
* @param number the number
* @return translation argument
*/
static TranslationArgument numeric(Number number);
/**
* Creates a boolean argument
* @param bool the boolean value
* @return translation argument
*/
static TranslationArgument bool(boolean bool);
}
/**
* Objects that can be used as translation arguments
*/
interface TranslationArgumentLike {
/**
* Converts to a translation argument
* @return the translation argument
*/
TranslationArgument asTranslationArgument();
}Core interface for translating keys to formatted messages based on locale.
/**
* Interface for translating keys to messages
*/
interface Translator {
/**
* Translates a key to a message format
* @param key the translation key
* @param locale the target locale
* @return the message format or null if not found
*/
@Nullable MessageFormat translate(String key, Locale locale);
/**
* Creates an empty translator
* @return empty translator
*/
static Translator empty();
/**
* Creates a translator from a resource bundle
* @param bundle the resource bundle
* @return new translator
*/
static Translator translator(ResourceBundle bundle);
/**
* Creates a translator from a map of translations
* @param translations the translation map (key -> message)
* @return new translator
*/
static Translator translator(Map<String, String> translations);
}Global translation service that manages multiple translation sources.
/**
* Global translation service for the entire application
*/
interface GlobalTranslator extends Translator {
/**
* Adds a translation source
* @param translator the translator to add
*/
void addSource(Translator translator);
/**
* Removes a translation source
* @param translator the translator to remove
*/
void removeSource(Translator translator);
/**
* Gets the default global translator instance
* @return the global translator
*/
static GlobalTranslator translator();
}Registry interface for managing translations with locale support.
/**
* Registry for translation keys and their translations
*/
interface TranslationRegistry extends Translator {
/**
* Registers a translation
* @param key the translation key
* @param locale the locale
* @param format the message format
*/
void register(String key, Locale locale, MessageFormat format);
/**
* Registers a simple string translation
* @param key the translation key
* @param locale the locale
* @param message the message string
*/
void register(String key, Locale locale, String message);
/**
* Unregisters a translation
* @param key the translation key
*/
void unregister(String key);
/**
* Gets all registered keys
* @return set of translation keys
*/
Set<String> keys();
/**
* Creates a new translation registry
* @return new registry
*/
static TranslationRegistry create();
}Storage system for translations with hierarchy support.
/**
* Storage system for translations
*/
interface TranslationStore extends Translator {
/**
* Gets all available locales
* @return set of locales
*/
Set<Locale> locales();
/**
* Gets all translation keys
* @return set of keys
*/
Set<String> keys();
/**
* Checks if a translation exists
* @param key the translation key
* @param locale the locale
* @return true if translation exists
*/
boolean contains(String key, Locale locale);
}
/**
* Abstract base for translation storage implementations
*/
abstract class AbstractTranslationStore implements TranslationStore {
/**
* Template method for loading translations
* @param key the translation key
* @param locale the locale
* @return the message format or null
*/
protected abstract @Nullable MessageFormat loadTranslation(String key, Locale locale);
}Usage Examples:
import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.*;
import java.util.Locale;
import java.text.MessageFormat;
// Basic translatable component
Component welcome = Component.translatable("welcome.message");
// Translatable with arguments
Component playerJoined = Component.translatable("player.joined",
Component.text("PlayerName").color(NamedTextColor.YELLOW));
// With fallback for missing translations
Component customMessage = Component.translatable()
.key("custom.greeting")
.fallback("Hello, {0}!")
.arguments(TranslationArgument.string("World"))
.build();
// Register translations globally
GlobalTranslator global = GlobalTranslator.translator();
TranslationRegistry registry = TranslationRegistry.create();
registry.register("welcome.message", Locale.ENGLISH, "Welcome to the server!");
registry.register("welcome.message", Locale.FRENCH, "Bienvenue sur le serveur!");
registry.register("player.joined", Locale.ENGLISH, "{0} joined the game");
registry.register("player.joined", Locale.FRENCH, "{0} a rejoint la partie");
global.addSource(registry);public class ResourceBundleTranslations {
public static void loadTranslations(String baseName) {
GlobalTranslator global = GlobalTranslator.translator();
// Load translations for different locales
for (Locale locale : getSupportedLocales()) {
try {
ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale);
Translator translator = Translator.translator(bundle);
global.addSource(translator);
} catch (MissingResourceException e) {
// Handle missing resource bundle
logger.warn("Missing resource bundle for locale: " + locale);
}
}
}
private static Set<Locale> getSupportedLocales() {
return Set.of(
Locale.ENGLISH,
Locale.FRENCH,
Locale.GERMAN,
Locale.SPANISH,
new Locale("es", "MX") // Mexican Spanish
);
}
}public class DynamicTranslations {
private final TranslationRegistry registry = TranslationRegistry.create();
public void addTranslation(String key, Locale locale, String message) {
registry.register(key, locale, message);
}
public void addFormattedTranslation(String key, Locale locale, String pattern) {
MessageFormat format = new MessageFormat(pattern, locale);
registry.register(key, locale, format);
}
public void loadFromProperties(String propertyFile, Locale locale) {
Properties props = loadProperties(propertyFile);
for (String key : props.stringPropertyNames()) {
registry.register(key, locale, props.getProperty(key));
}
}
public Component createTranslatable(String key, Object... args) {
TranslationArgument[] arguments = Arrays.stream(args)
.map(this::toTranslationArgument)
.toArray(TranslationArgument[]::new);
return Component.translatable(key, arguments);
}
private TranslationArgument toTranslationArgument(Object obj) {
if (obj instanceof Component) return TranslationArgument.component((Component) obj);
if (obj instanceof Number) return TranslationArgument.numeric((Number) obj);
if (obj instanceof Boolean) return TranslationArgument.bool((Boolean) obj);
return TranslationArgument.string(String.valueOf(obj));
}
}public class LocalizedMessaging {
private final GlobalTranslator translator = GlobalTranslator.translator();
public void sendLocalizedMessage(Audience audience, String key, Object... args) {
// Get player's locale (implementation-specific)
Locale playerLocale = getPlayerLocale(audience);
// Create translatable component
Component message = Component.translatable(key,
Arrays.stream(args)
.map(TranslationArgument::string)
.toArray(TranslationArgument[]::new));
// Send with locale context
audience.sendMessage(message);
}
public void broadcastLocalized(Collection<? extends Audience> audiences, String key, Object... args) {
Component message = Component.translatable(key,
Arrays.stream(args)
.map(TranslationArgument::string)
.toArray(TranslationArgument[]::new));
for (Audience audience : audiences) {
audience.sendMessage(message);
}
}
public Component formatNumber(Number number, Locale locale) {
NumberFormat formatter = NumberFormat.getInstance(locale);
return Component.text(formatter.format(number));
}
public Component formatTime(long timestamp, Locale locale) {
DateFormat formatter = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
return Component.text(formatter.format(new Date(timestamp)));
}
}public class TranslationValidator {
public void validateTranslations(TranslationStore store) {
Set<String> keys = store.keys();
Set<Locale> locales = store.locales();
for (String key : keys) {
for (Locale locale : locales) {
if (!store.contains(key, locale)) {
logger.warn("Missing translation: {} for locale: {}", key, locale);
}
// Validate message format
MessageFormat format = store.translate(key, locale);
if (format != null) {
validateMessageFormat(key, locale, format);
}
}
}
}
private void validateMessageFormat(String key, Locale locale, MessageFormat format) {
try {
// Test with sample arguments
format.format(new Object[]{"test", 123, true});
} catch (IllegalArgumentException e) {
logger.error("Invalid message format for {}: {} ({})", key, locale, e.getMessage());
}
}
public Set<String> findMissingTranslations(Set<String> requiredKeys, TranslationStore store) {
return requiredKeys.stream()
.filter(key -> store.locales().stream()
.noneMatch(locale -> store.contains(key, locale)))
.collect(Collectors.toSet());
}
}menu.main.title, error.permission.deniedplugin.feature.messageInstall with Tessl CLI
npx tessl i tessl/maven-net-kyori--adventure-api