Skip to content
This repository has been archived by the owner on Jan 23, 2025. It is now read-only.

LanguageManager and MessageKey System

Lars edited this page Oct 18, 2024 · 3 revisions

Overview

The LanguageManager and MessageKey system provides a robust and flexible way to handle localization in your Minecraft plugin. This system allows you to define structured message keys, load localized messages from YAML files, and retrieve those messages based on either the player's language settings or the server's default locale for system-wide messages.

The system is designed to simplify the process of adding translations for different languages, ensuring that both in-game messages for players and system-level messages (such as those in the console) are consistently localized.

Table of Contents


Defining Message Keys

Basic Structure

The MessageKey interface allows you to define message keys in a structured, hierarchical way. By creating nested classes, you can mirror the structure of your YAML files and organize message keys logically.

This hierarchy makes it easier to manage large numbers of messages, and it ensures that your keys are easy to locate both in your code and in the language files.

Each message key is an object that corresponds to a specific path in the YAML file. If a message is missing from the YAML file, the key's name will be used as a fallback.

Example

Here’s an example of how to define message keys using the MessageKey interface. This example creates a structure for GUI-related messages:

open class Main : MessageKey {
    open class Gui : Main() {
        open class Title : Gui() {
            object MAIN_BOARD : Title()
            object SETTINGS : Title()
        }
        object BUTTONS : Gui() {
            object CLICK : BUTTONS()
            object HOVER : BUTTONS()
        }
    }
}

Explanation:

  • Main serves as the top-level class for all message keys related to the main plugin.
  • Inside Gui, there are further nested classes for different parts of the GUI.
    • Title contains keys for titles like MAIN_BOARD and SETTINGS.
    • BUTTONS contains keys for button-related actions like CLICK and HOVER.

In the corresponding YAML file, you would define these keys like this:

gui:
  title:
    main_board: "Main Board"
    settings: "Settings Menu"
  buttons:
    click: "Click me!"
    hover: "Hover over the button"

This structure allows you to easily manage and organize your messages in a logical, hierarchical way.


MessageKey Interface Methods

The MessageKey interface defines two important methods for handling localized messages: c() and t(Player). These methods ensure that there is always a fallback if a localized message is missing or undefined.

c(): Default Message (Fallback)

The c() method returns the default message or the key name itself when no localized message is found. This acts as a fallback mechanism, ensuring that if the YAML file does not contain a specific translation for a message, the name of the MessageKey class (or object) is returned instead.

fun c(): TextComponent {
    return Component.text(this.javaClass.simpleName)
}
  • Use case: This method is particularly useful when localization is incomplete or when a message is not defined in the language files. The key name will serve as a placeholder to indicate which message is missing.
  • Example: If a MessageKey object like Main.Gui.Title.MAIN_BOARD is missing from the YAML file, the fallback message will display "MainBoard" (or similar, based on class naming conventions).

t(Player): Localized Message for Players

The t(Player) method retrieves a localized message for a given player, based on the language settings of the player's Minecraft client. It first looks for the corresponding message in the YAML file. If the message is not found, it falls back to the key name using the c() method.

fun t(player: Player): TextComponent {
    return LanguageManager.getMessage(player, this)
}
  • Use case: This method is used when you want to send a message to a specific player in their preferred language. It ensures that the correct localized string is retrieved from the LanguageManager, providing a player-specific experience.
  • Example: If the player has their language set to Japanese and the corresponding translation exists in the YAML file, t(player) will return the Japanese translation of Main.Gui.Title.MAIN_BOARD. If not, the fallback will be the key name ("MainBoard").

LanguageManager

The LanguageManager class is responsible for loading messages from YAML files and mapping them to MessageKey objects. It also provides methods for retrieving messages based on a player's language settings or the server’s default locale for system-wide messages.

Loading Language Files

When the plugin loads, LanguageManager scans the plugin’s specified package for any MessageKey implementations. These keys are automatically mapped to their corresponding messages in the YAML files.

The processYamlAndMapMessageKeys() method is responsible for reading the YAML data and matching it to the appropriate MessageKey. It ensures that the structure in the YAML file mirrors the class hierarchy defined in your code.


Retrieving Messages

Player-Specific Messages

You can retrieve localized messages for individual players using the getMessage() method. This method uses the player's language settings to return the appropriate message from the loaded YAML file.

val message: TextComponent = LanguageManager.getMessage(player, Main.Gui.Title.MAIN_BOARD)

This will return the message defined in the YAML for the Main.Gui.Title.MAIN_BOARD key, in the language that the player has set in their client.

System-Wide Messages

getSysMessage() is used to retrieve localized messages that are not player-specific, such as those intended for the server console or system logs. It uses the server’s default locale (as defined by Locale.getDefault()) to select the appropriate language for the message.

This method is useful when you want to send messages to the console or perform logging in the system's language, ensuring that even system-level interactions can be localized properly.

val systemMessage: String = LanguageManager.getSysMessage(Main.Gui.Title.MAIN_BOARD)

In this case, the message defined in the YAML for Main.Gui.Title.MAIN_BOARD will be retrieved using the system's locale. If the message is missing or not found for the system’s language, the key itself will be used as the default message.


Handling Missing Keys

The findMissingKeys() method scans the YAML files for any missing message keys. If a key is defined in your code but missing in the YAML file, a warning is logged, and the key’s name is used as a fallback message.

This method ensures that all expected keys are present in the language files, preventing missing translations during runtime.

val missingKeys = LanguageManager.findMissingKeys("en")
if (missingKeys.isNotEmpty()) {
    missingKeys.forEach { key ->
        logger.warn("Missing key: $key in language file")
    }
}

This helps you maintain complete and consistent localization coverage across all languages.

Hint: Output to the log is automatic, meaning there’s no need to manually output missing keys to the log.


YAML File Structure

The YAML files should follow the same hierarchical structure as the MessageKey classes. Each key in the file corresponds to a MessageKey object in the code.

Example YAML File

gui:
  title:
    main_board: "Main Board"
    settings: "Settings"
  buttons:
    click: "Click!"
    hover: "Hover!"

In this file:

  • gui.title.main_board corresponds to Main.Gui.Title.MAIN_BOARD.
  • gui.buttons.click corresponds to Main.Gui.BUTTONS.CLICK.

Notes on Key Matching

  • Case Sensitivity: All keys in the YAML file should be written in lowercase. The LanguageManager automatically converts class names to lowercase for key matching, so mismatched case may result in keys not being found.
  • Fallback: If a message is not found for a given key, the MessageKey name will be used as the fallback message.