Translations โ Code Structure
File locationโ
All translation files are located in core/src/main/resources/lang/ and organized by domain:
lang/
โโโ errors.{lang}.json # Error messages
โโโ global.{lang}.json # Global prefixes (info, warning, error, debug)
โโโ messages.{lang}.json # General gameplay messages
โโโ area/
โ โโโ messages.{lang}.json # Area CRUD messages
โโโ commands/
โ โโโ area.{lang}.json # /le area command descriptions
โ โโโ clipboard.{lang}.json # /le clipboard command descriptions
โ โโโ component.{lang}.json # /le component command descriptions
โ โโโ componentinfo.{lang}.json # /le info command & component inspector texts
โ โโโ editor.{lang}.json # /le editor command description
โ โโโ help.{lang}.json # /le help command description
โ โโโ lang.{lang}.json # /le lang command descriptions
โ โโโ schematics.{lang}.json # /le schematic command descriptions
โ โโโ stats.{lang}.json # /le stats command descriptions
โโโ race/ # Race builder
โโโ shortcutbar/ # Editor hotbar & GUI menu translations
โ โโโ shortcutbar.{lang}.json
โ โโโ elevator.{lang}.json
โ โโโ lock-key_chest.{lang}.json
โ โโโ music_block.{lang}.json
โ โโโ range.{lang}.json
โ โโโ rotation.{lang}.json
โ โโโ area_configuration/
โ โโโ color_selection/
โ โโโ component_configuration/
โ โโโ delete_area/
โ โโโ place_component/
โ โโโ sync/
โ โโโ victory_area/
โโโ stats/
โโโ gui.{lang}.json # Statistics GUI menu
โโโ messages.{lang}.json # Statistics chat messages
Naming conventionโ
Each file follows the pattern <domain>.<languageCode>.json (e.g., errors.en.json, errors.fr.json).
Translation codes inside a file follow a hierarchical snake_case pattern:
errors.<domain>.<what>โ e.g.errors.area.too_small_exceptionmessages.<action>โ e.g.messages.create_area_success<feature>.command.<verb>.<part>โ e.g.component.command.look.area_iditems.<name>.{name,description}โ e.g.items.portal_surface.description
Place new entries next to thematically related codes, not at the bottom of the file.
Supported languages (25)โ
ar (Arabic), bn (Bengali), cs (Czech), da (Danish), de (German), el (Greek), en (English, default), es (Spanish), fi (Finnish), fr (French), hi (Hindi), hu (Hungarian), it (Italian), ja (Japanese), ko (Korean), nl (Dutch), no (Norwegian), pl (Polish), pt (Portuguese), ro (Romanian), ru (Russian), sv (Swedish), tr (Turkish), uk (Ukrainian), zh (Chinese).
Translation completeness โ required everywhereโ
Every translation code MUST exist in every <domain>.<languageCode>.json file of its domain. The plugin emits a "Translations are missing" warning at startup for any code missing from a language file. The same warning fires for the opposite case: a key still present in non-en files but removed from en.json. When you remove a key, remove it from every language file too.
Translate for real in each language. Never leave the English string as a placeholder in non-English files. en.json is itself a real translation that ships to English players. If you're unsure about a language, translate as best you can and flag those languages so a reviewer can spot-check.
Modifying a published translation value โ forceUpdatePluginVersionโ
When you change the value of a translation code that already shipped in a prior release, you must add forceUpdatePluginVersion to that entry โ otherwise admins who already installed the plugin keep their previously-loaded value and your edit has no effect on live servers.
TranslationLib writes its JSON into plugins/<plugin>/lang/ on first load and then treats the admin's stored copy as authoritative. forceUpdatePluginVersion tells the library: for any server upgrading from a plugin version strictly lower than X, overwrite the admin's stored value.
{
"code": "area.command.victory",
"translation": "ยงaยงlVictory!",
"forceUpdatePluginVersion": "8.26.1"
}
Rules:
- Value to write: current
gradle.propertieswith-SNAPSHOTstripped (e.g.8.26.1-SNAPSHOTโ"8.26.1"). Never include-SNAPSHOTโ the release tooling strips it, and the value must match the final published version. - Apply in every language file you edit (en + all non-en).
- Bundle the value change and
forceUpdatePluginVersionin the same Edit so you cannot ship one without the other. - Not needed for brand-new codes โ those are written on first load.
- If the entry already has a
forceUpdatePluginVersion, overwrite it with the current version. If the existing one is higher than the current version, stop and ask โ someone else is mid-flight on the same key.
defaultForceUpdatePluginVersionโ
Files often start with a defaultForceUpdatePluginVersion at the top. Do not bump that โ it force-overwrites every unmarked key, trampling unrelated admin customisations. Always mark the single entry you changed via the per-entry forceUpdatePluginVersion.
Mid-feature staging workflowโ
When introducing or modifying translations as part of a larger feature/fix, do not edit the 25 language files inline. Instead:
- Buffer each entry into
.translation-staging.jsonat the project root (gitignored). - At end-of-task โ after the Java implementation is stable, before build/commit โ write all staged entries to the language files in one batched pass (one Edit per file containing every staged entry).
Staging entry shape:
[
{
"operation": "add",
"domain": "area/messages",
"code": "messages.area_resize_success",
"en": "ยง7Area ยง6{0}ยง7 resized to ยง6{1}x{2}x{3}ยง7.",
"context": "Sent once after a successful resize via the area edit menu. {0}=area name (admin string, may contain spaces); {1}/{2}/{3}=new X/Y/Z dimensions in blocks. Not sent on cancel or invalid resize."
}
]
The context field is mandatory โ it tells translators when the code fires, what each {N} semantically represents and what tone to use, so non-English translations don't drift into wrong literals. The plugin will warn "Translations are missing" between staging and flush; that is expected and disappears once the flush runs.
โ ๏ธ Orphan check: if
.translation-staging.jsonexists at the start of a session, stop and decide whether to flush it (write staged entries to language files) or discard it (git cleanthe file). A stale staging file from a previous session must never be silently merged with new work.โ ๏ธ Never commit the staging file โ it must be flushed first.
For one-off ad-hoc changes (1โ2 codes, no other translation work in flight), it's still fine to edit the language files directly without staging.
Style conventions (Minecraft formatting codes)โ
Use the role-based prefix for every new code; reset with ยงr whenever a style shouldn't bleed into the next text chunk.
| Role | Style | Example |
|---|---|---|
| Error | ยงc prefix | "ยงcThe area is too small." |
| Warning | ยง6 prefix | "ยง6Warning: โฆ" |
| Debug | ยงe prefix | "ยงeDebug: โฆ" |
| Item name | ยงf (often ยงl bold) | "ยงfPlace cube creation button" |
| Item description | ยง7, \nยง7 per line | "ยง7Creates a button.\nยง7Right-click to use." |
| Interpolated param | ยง6{N} | "ยง7Area ID: ยง6{0}" |
| Label : value | ยงlยงf<label>ยงrยง8: ยง6<value> | "ยง8 - ยงlยงfArea IDยงrยง8: ยง6{0}" |
These conventions apply to new codes. Older entries that are uncolored or inconsistent don't need to be retrofitted, but don't follow them as a model either โ converge on the table above.
MessageFormat placeholders and escapingโ
The plugin uses Java's MessageFormat for parameter substitution:
{0},{1}, โฆ are positional placeholders. The Java caller passes parameters in order:TranslationUtils.sendTranslatedMessage(player, "code", arg0, arg1).- Every language file must keep the same indices in the same semantic roles. Word order may differ (e.g. Japanese), but every
{N}must be present in every translation. Same count, same meaning. '{0}'renders as the literal{0}(single quotes escape the placeholder).- A single quote is written
''โ e.g. French apostrophes are doubled:l''aire. Un-doubling them breaks the format string at runtime.
Editing safety โ Edit tool onlyโ
Never edit translation files with Write, sed, -replace, or any full rewrite. The 25 files mix CRLF/LF line endings and with/without UTF-8 BOM. Full rewrites silently corrupt Arabic / Bengali / Greek / Hindi / Japanese / Korean / Russian / Ukrainian / Chinese glyphs into mojibake that still parses as JSON but renders as ??? in-game.
Use Edit only, one file at a time (or batched per file when staging-flushing multiple entries).
Adding a new languageโ
To add a built-in language:
- Create a
*.<lang>.jsoncounterpart for each existing*.en.jsonfile (currently ~30 files). - Set the
languageblock with the correctlanguageCode,languagename, and"isDefault": false. - Update
ConfigData.CONFIG_HEADERto list the new language. - Update the wiki pages:
Installation/Config.mdandInstallation/Translations.md.
Translation libraryโ
The plugin uses TranslationLib (fr.skytale.translationlib) which auto-discovers translation files from the lang/ resource directory. No manual registration is needed when adding new files.
AI assistant skillsโ
Four Claude Code skills under .claude/skills/ automate the most repetitive translation operations and enforce the rules above:
| Skill | Purpose |
|---|---|
stage-translation | Buffer a new or modified code into .translation-staging.json mid-feature (preferred path for โฅ 3 codes per task) |
flush-translation-staging | At end-of-task, write all staged entries to every language file in one batched pass; auto-applies forceUpdatePluginVersion for modify entries |
add-translation-code | Add a single brand-new code directly into all 25 language files (ad-hoc, 1โ2 codes) |
modify-translation-value | Change a published code value directly with forceUpdatePluginVersion (ad-hoc, 1โ2 codes) |
See AI Prompts and Skills for the full list.