Localization
Choosing the server language, your own translations and overriding individual strings.
Commands 1#
Examples#
Switch the server to Russian#
The language is set by a single code; players do not need to install anything.
/language list
/language ru_ru
/language stats
list prints the codes from the jar. /language ru_ru is the same as /language set ru_ru and writes localization.language to the config. stats shows how many keys are covered relative to en_us. An unknown code does not break chat: missing strings are taken from en_us.
Fix one string without touching the jar#
Your own wording for a kit or ban message that survives a mod update.
/language override set commands.neoessentials.kits.claimed You received kit {0}
/language override get commands.neoessentials.kits.claimed
/language override list
The override is stored in the DataStore (language_overrides), not in a language file. get prints the current override or says the key has none. Double any ' quote in a value with {0}, otherwise MessageFormat swallows the substitution.
Your own full translation#
Start a file in languages/custom/ instead of copying from the jar by hand.
/language template ru_ru
/language validate ru_ru
/language reload
template writes a skeleton into languages/custom/. It is an overlay: keys missing from the file are still taken from the jar. validate checks placeholders and §-codes. After editing the file by hand, run reload, not a restart.
Edge cases#
OP 4 and neoessentials.language are different things#
Brigadier checks hasPermission(4). No PermissionRegistry node is attached to this command. A player with neoessentials.language in LuckPerms but without OP 4 will not see the command.
What to do: Either grant OP 4 / use the console, or accept that an external provider cannot open this command by node.
reload does not re-register commands#
/language reload re-reads catalogues and overrides. The commands.language section is read at startup: a disabled command will not appear after reload.
What to do: Enabling /language itself needs a server restart.
Known issues#
A single quote in a string with {0} prints a literal {0}#
This is MessageFormat: ' starts an escaped section and swallows the placeholder. en_us/ru_ru already account for this by doubling; your own override is easy to break.
What to do: Write '' instead of '. After the edit, run /language override reload or /language reload.
Detailed guide#
The section below is the project's original documentation (docs/Wiki/LocalizationSystem.md). Links to other wiki pages are rewritten to this site; the guides themselves are not edited, so upstream syncs stay conflict-free.
Version: 1.0.4+build.16 · Last verified: 2026-07-23
Added / overhauled in build.62 · Language config setting added in build.187 (build numbers below build.16 predate the NeoForge 26.1 port's build-counter reset)
Overview#
NeoEssentials ships a fully featured, server-side localization system. Every in-game message is driven by translation keys stored in language JSON files. Server admins can:
- Set the active language with one line in
config.json — no file renaming needed.
- Override individual messages without editing any bundled file.
- Add or swap entire languages by dropping a
.json file in the custom language directory.
- Validate coverage of any language file against the English base.
- Regenerate a language file from the JAR while preserving user edits.
All tooling is exposed through the /language in-game command (OP level 4).
Choosing the Active Language#
In-game (recommended): /language set <code> (or the bare shortcut /language <code>) validates the code against the currently deployed language files, writes it to config.json, and reloads translations immediately — no restart or separate reload step needed.
/language set fr_fr
Manually: Set the server language in config.json under the localization section:
"localization": {
"language": "fr_fr"
}
Reload in-game after a manual edit: /neoessentials reload
Available built-in language codes:
| Code |
Language |
en_us |
English (US) — default |
fr_fr |
French |
de_de |
German |
es_es |
Spanish |
pt_br |
Portuguese (Brazil) |
zh_cn |
Chinese (Simplified) |
nl_nl |
Dutch |
pl_pl |
Polish |
ru_ru |
Russian |
Fallback: Any key not present in the configured language file automatically falls back to en_us. You will never see a raw translation key in-game.
File Locations#
| File |
Purpose |
neoessentials/languages/custom/<language>.json |
Active language file — auto-deployed from JAR when first selected, auto-merged on updates (e.g. fr_fr.json when "language": "fr_fr") |
neoessentials/languages/custom/en_us.json |
Fallback English file (always present) |
neoessentials/languages/custom/<code>.json |
Any additional custom / community language files |
neoessentials/languages/overrides.json |
Legacy admin message overrides file — only consulted for a one-time migration into the DataStore on first boot (see note below) |
neoessentials/languages/templates/<code>_template.json |
Auto-generated templates for translators |
How the System Works#
Startup / Boot#
ConfigManager.getServerLanguage() reads localization.language from config.json (default: en_us).
MessageUtil.loadTranslations() (private, called internally on startup) resolves the configured language code. MessageUtil.reloadTranslations() (public) is the equivalent used by /language reload / /language set / /neoessentials reload.
- The language file is looked for at
neoessentials/languages/custom/<code>.json.
- If not found (first run), it is deployed from the JAR's bundled
<code>.json.
- If the
fr_fr (or other) JAR file does not cover 100% of keys, missing keys are filled from en_us as fallback.
- If the deployed file's
_langVersion is older than the current JAR version, new keys are merged in without overwriting existing values.
overrides.json is loaded. Overrides take top priority over all files.
Key Resolution Order (highest → lowest priority)#
1. Admin overrides (overrides.json)
2. Configured language file (e.g. fr_fr.json when "language": "fr_fr")
3. en_us fallback (for keys not covered by the configured language)
4. Human-readable fallback (generated from the key name)
Human-Readable Fallback#
If a key is not found in any file, instead of showing the raw key string (commands.neoessentials.home.not_found), the system strips common prefixes and converts dots/underscores to spaces:
commands.neoessentials.home.not_found → "Home not found"
neoessentials.moderation.player_only_command → "Moderation player only command"
This ensures players always see readable English even if a key is missing.
Version Tracking#
Each language file contains a _langVersion metadata key:
{
"_langVersion": "23",
...
}
MessageUtil.java maintains a constant CURRENT_LANG_VERSION. On startup, if the deployed file's version is lower, new keys are merged automatically. The file version is then bumped to the current value and saved.
Current version: 23 (MessageUtil.CURRENT_LANG_VERSION, matches the shipped en_us.json's own _langVersion).
When you add new translation keys to en_us.json, increment _langVersion by 1 in both:
- src/main/resources/data/lang/en_us.json (the _langVersion value)
- MessageUtil.java → CURRENT_LANG_VERSION constant
/language Command Reference#
All subcommands require OP level 4.
| Command |
Description |
/language list |
List all custom language files loaded |
/language reload |
Reload all language files and overrides from disk |
/language stats |
Show statistics (loaded languages, missing keys tracked, overrides) |
/language info |
Show full command reference |
/language set <code> |
Set the active server language and reload translations immediately |
/language <code> |
Shortcut for /language set <code> |
Templates & Missing Keys#
| Command |
Description |
/language template <code> |
Generate a translator-ready template for language <code> |
/language exportmissing |
Export all runtime-tracked missing keys to a JSON template file |
/language clearmissing |
Reset the missing-keys tracker |
Validation & Regeneration#
| Command |
Description |
/language validate <code> |
Compare <code>.json against en_us.json; shows coverage %, missing keys, and extra keys |
/language regenerate <code> |
Re-deploy <code>.json from JAR, merging user translations, auto-backup to .bak |
/language validate example output:
═══ Language Validation: fr_fr ═══
Total base keys: 1058
Translated keys: 987
Coverage: §e93%
Missing keys: 71
Extra keys (not in base): 3
First 10 missing key(s):
- commands.neoessentials.teleport.request.sent
- commands.neoessentials.teleport.request.received
...
Run /language regenerate fr_fr to update the file from JAR.
Admin Overrides#
Override any message key permanently. Overrides are persisted through the active DataStore (see Storage Backend) as a single document — with the default json backend that's neoessentials/store/language_overrides.json; with sqlite/mysql it's a table in the configured database. Overrides survive reloads.
Storage note: neoessentials/languages/overrides.json (the old flat file) is now only read for a one-time legacy migration — if the DataStore has no override document yet and storage.autoMigrate is enabled, any overrides found in that file are imported once and the DataStore becomes authoritative from then on.
| Command |
Description |
/language override set <key> <value> |
Set a message override |
/language override get <key> |
Show the current override for a key |
/language override remove <key> |
Remove a single override |
/language override list |
List all active overrides |
/language override clear |
Remove all overrides |
/language override reload |
Reload overrides from disk |
Example — customize the server's welcome message:
/language override set neoessentials.server.welcome §aWelcome to MyServer! Type /help to get started.
Creating a Custom Language#
-
Generate a template:
/language template zh_tw
This creates neoessentials/languages/templates/zh_tw_template.json.
-
Translate all values in the template (replace [TRANSLATE] English text with your translation). Keep {0}, {1} placeholders exactly as-is — they are substituted at runtime with player names, counts, etc.
-
Add metadata at the top of your file:
json
{
"_nativeName": "繁體中文",
"_englishName": "Traditional Chinese",
"_languageCode": "zh_tw",
"_author": "YourName",
"_version": "1.0",
...
}
-
Save the file as neoessentials/languages/custom/zh_tw.json.
-
Set it active in config.json:
json
"localization": {
"language": "zh_tw"
}
-
Run /neoessentials reload.
-
Verify coverage with /language validate zh_tw.
Bundled Language Files#
The following languages are bundled in the JAR and can be activated via config.json:
| Code |
Language |
en_us |
English (base) |
fr_fr |
French |
de_de |
German |
es_es |
Spanish |
pt_br |
Portuguese (Brazil) |
zh_cn |
Chinese (Simplified) |
nl_nl |
Dutch |
pl_pl |
Polish |
ru_ru |
Russian |
Developer Notes#
Adding a New Translation Key#
- Add the key + English value to
src/main/resources/data/lang/en_us.json.
- Use the key via
MessageUtil.localize("your.new.key", arg0, arg1...) in Java.
- Increment
_langVersion in en_us.json and CURRENT_LANG_VERSION in MessageUtil.java.
- On the next server start, the new key is automatically merged into all deployed language files.
Key Naming Convention#
commands.neoessentials.<category>.<action> (command feedback messages)
neoessentials.<category>.<action> (system / non-command messages)
Examples:
- commands.neoessentials.teleport.home.not_found
- commands.neoessentials.moderation.ban.success
- neoessentials.moderation.ban_message
- neoessentials.error.no_server
MessageUtil API#
// Simple lookup with args
String msg = MessageUtil.localize("commands.neoessentials.home.not_found", homeName);
// With explicit English fallback (use when you're certain of the English text)
String msg = MessageUtil.localize("commands.neoessentials.home.not_found", "Home ''{0}'' not found.", homeName);
// As a Component (for sendSuccess / sendFailure)
Component comp = MessageUtil.component("commands.neoessentials.home.not_found", homeName);
// Convenience coloured components
Component success = MessageUtil.success("commands.neoessentials.home.set", name);
Component error = MessageUtil.error("commands.neoessentials.home.not_found", name);
Component warning = MessageUtil.warning("commands.neoessentials.home.limit_reached", max);
Component info = MessageUtil.info("commands.neoessentials.home.list_header", count);
Troubleshooting#
| Problem |
Fix |
| Language not changing after config edit |
Run /neoessentials reload — language is re-read on every reload. |
Message shows raw key (e.g. commands.neoessentials.xyz) |
The key is missing from en_us.json. Add it and bump _langVersion. |
| Message shows human-readable fallback instead of proper text |
Same as above — key is missing. The humanizer is a safety net, not the final output. |
| Bundled language file not updating |
Run /language regenerate <code>. |
| Override not showing in-game |
Run /language override reload or /language reload. Overrides live in the DataStore now — use /language override set/get/remove rather than hand-editing overrides.json (which is legacy-import-only). |
| Language file corrupted |
Delete it and run /language regenerate <code> to redeploy from JAR. |
| Language falls back to English for some messages |
That key isn't translated in the language file yet. Run /language validate <code> to see missing keys. |
Last updated: 1.0.4+build.16 — 2026-07-23