Skip to content

Localization

Petrus Pradella edited this page Jul 23, 2026 · 3 revisions

Localization

What this page covers: the message system built on @FCLocale + LocaleMessage. You declare a message once, inline, with one translation per language; EverNifeCore syncs it to a per-plugin language file, resolves the plugin's active language, replaces %placeholders%, and sends it as a FancyText (so hover/click come for free). This is how commands, argument parsers, and access validations talk to players.


The 30-second version

import br.com.finalcraft.evernifecore.locale.FCLocale;
import br.com.finalcraft.evernifecore.locale.LocaleMessage;
import br.com.finalcraft.evernifecore.locale.LocaleType;

public class TemplateMessages {

    @FCLocale(lang = LocaleType.EN_US, text = "&eYour profile &7- &fLevel: &a%level% &7| &fCoins: &6%coins%")
    @FCLocale(lang = LocaleType.PT_BR, text = "&eSeu perfil &7- &fNível: &a%level% &7| &fCoins: &6%coins%")
    public static LocaleMessage PROFILE_INFO;
}

Send it, filling the placeholders:

TemplateMessages.PROFILE_INFO
        .addPlaceholder("%level%", profile.level)
        .addPlaceholder("%coins%", profile.coins)
        .send(sender);

The field is a public static LocaleMessage; it starts null and is populated when the class is scanned (see Loading). Each @FCLocale on the field is one translation.


@FCLocale - one translation

Stack one @FCLocale per language on the same field (the annotation is @Repeatable, so you just write several):

Attribute Type Default Purpose
text String "" The message text (color codes with & or §).
lang String EN_US Which language this translation is for.
hover String "" Hover tooltip text.
runCommand String "" Click action payload (a command by default).
clickActionType ClickActionType RUN_COMMAND RUN_COMMAND / SUGGEST_COMMAND / OPEN_URL / NONE.
children Child[] {} Extra appended segments, each with its own text/hover/click.
@FCLocale(
        lang = LocaleType.EN_US,
        text = "&aClick here",
        hover = "&7Opens the wiki",
        runCommand = "https://github.com/EverNife/EverNifeCore/wiki",
        clickActionType = ClickActionType.OPEN_URL
)
public static LocaleMessage WIKI_LINK;

LocaleType ships two constants - LocaleType.EN_US and LocaleType.PT_BR - but lang is a plain String, so any language tag (e.g. "ES_ES") is valid; add another @FCLocale with that tag to provide the translation.


Loading a message holder

The static fields are populated by a scan. Command classes, argument parsers, and access validations are scanned automatically when you register them - their @FCLocale fields just work. For a plain holder class (like TemplateMessages above), scan it once at startup:

import br.com.finalcraft.evernifecore.locale.scanner.FCLocaleScanner;

FCLocaleScanner.scanForLocale(ecPluginData, false, TemplateMessages.class);

The scan derives each message's key from ClassName.FIELD_NAME, then syncs the hardcoded defaults to a language file so server owners can edit the wording. From then on the file is the source of truth for the text.

⚠️ Gotcha - LocaleMessage fields must be public static. A non-static field is logged as an error and skipped, and a field with no @FCLocale annotation warns.


Language files & switching language

Each plugin gets its own language files under its data folder:

plugins/<YourPlugin>/localization/
├── localization_config.yml     # selects the active file (Localization.fileName)
├── lang_EN_US.yml              # generated from the @FCLocale(EN_US) defaults, editable
└── lang_PT_BR.yml              # generated from the @FCLocale(PT_BR) defaults, editable

A message resolves to the plugin's active language - the same language for every viewer, chosen by the server owner (there is no per-player language). Admins switch it with the built-in command:

/eclocale list                       # show every plugin and its current locale (clickable to switch)
/eclocale set <PluginName> <Locale>  # set one plugin's language
/eclocale setall <Locale>            # set every plugin's language at once

If the active language has no translation for a given message, EverNifeCore falls back to the first @FCLocale declared on that field.


Placeholders

addPlaceholder(token, value) does a literal substitution of token inside the resolved text. The house convention is %name%, but the token is whatever literal you pass:

TemplateMessages.COINS_RECEIVED
        .addPlaceholder("%amount%", amount)   // %amount% -> "50"
        .addPlaceholder("%coins%", profile.coins)
        .send(sender);

For a value that depends on the receiving player, pass a Function<PlayerData, Object> - it's evaluated per recipient (using the player's cached data, so it never blocks):

message.addPlaceholder("%balance%", playerData -> economy.getBalance(playerData)).send(player);

Context placeholders like %label% (the command label the player typed) are injected automatically when a LocaleMessage is sent from a command.

📌 Note - %name%-style tokens here are a plain string replace. The regex-driven placeholder engine (%ident% / {ident} closures, PlaceholderAPI bridging) is a separate system - see Placeholders. You can feed one into a message with addReplacer(compoundReplacer).


Customizing a message inline

Call .custom() (or any of the add* methods, which start a custom copy) to tweak a single send without touching the shared field: override the hover/click, add a replacer, or concatenate messages.

message.addHover("&7extra tooltip").send(sender);          // per-send hover
message.addSuggest("/pay " + name + " ").send(sender);     // click suggests a command
messageA.concat(messageB).send(sender);                    // join two messages into one line
message.broadcast();                                       // send to every online player

Each add*/custom() returns a fresh SendCustom, so the underlying LocaleMessage field is never mutated - concurrent sends with different placeholders are safe.


See also

  • FancyText - what a resolved LocaleMessage becomes; hover/click building blocks.
  • Command Framework - @FCLocale as a command/sub-command description, and message fields on commands.
  • Argument Parsing - localized error messages inside a custom ArgParser.
  • Placeholders - the regex Replacer/RegexReplacer engine and PlaceholderAPI bridging.
  • Quick Start - a minimal plugin wiring a command, a config, and a locale together.

Clone this wiki locally