Localization (Display Names & Translations)
Give your content player-facing names and translate them per language, keyed by the stable identifier.
Every piece of content has a stable identifier (name / id) — the key recipes, drops, Lua and save files point at. What a player reads is a separate thing: an optional English display name, and optional per-language translations. Both layers are additive — add neither and the UI title-cases the identifier — so nothing you already ship breaks.
The shown label is resolved in this order, first hit wins:
lang/<locale>.yaml— a translation for the player’s language, keyed by identifier.display-name— the English name authored in the content config.- Title-cased identifier — the mod prefix stripped and words capitalized (
cherry_log→Cherry Log).
The identifier never changes with language: a translated build changes labels, not keys. This is also why you should rename before you translate, never after — renaming an identifier orphans every translation of it (and, for milestones, its achievement).
display-name (the English label)
display-name is an optional, client-only field on every player-visible category:
# mods/YourMod/affix-config.yaml
affixes:
- name: searing # identifier — stable, referenced everywhere
display-name: Searing # English label the player sees
tags: [sword]
Categories that take display-name: items (and the blocks/consumables that register as items), mobs, mounts, affixes, infusions, weather, and per-placement mob overrides in structures. Structural identifiers (loot tables, presets, world builders, terrain, biomes) are plumbing and have none.
Status effects are translation-only. Their config is server-side and only the effect’s id reaches the client, so a
display-namethere is not shown; give status effects a translated name throughlang/<locale>.yaml(below), and English falls back to the title-cased id.
lang/<locale>.yaml (translations)
Ship one file per language your mod translates, in a lang/ directory at your mod’s root. No manifest entry is needed — it is a directory convention, like assets/.
mods/YourMod/lang/fr.yaml # French
mods/YourMod/lang/de.yaml # German
Each file is a flat map of identifier → translated string. Key on the bare identifier (the engine namespaces it to your mod automatically, exactly like a name:); prefix another mod’s name only to translate its content:
# mods/YourMod/lang/fr.yaml — French names, keyed by identifier
searing: "<your French translation>" # translates YourMod:searing
OtherMod:frostbite: "<your French translation>" # translates another mod's content
Rules and behavior:
- Keys are identifiers, values are the translated text. Do not translate the key.
- A second string on the same content takes a suffix, so it never collides with the content’s name: a lore entry’s body is
<id>.body, a milestone’s banner name is<id>.milestone. - A file for a language you have not translated can simply be omitted — any identifier with no entry falls back to
display-name, then the title-cased id, so a partial file is valid. - English needs no file. English names come from
display-name;lang/en.yamlis not read. - Only the identifier is required to be stable; you may add, remove, or complete translations freely between versions.
Creation ships localized in the Latin tier — fr, es, de, it, pt, pl — the languages the bundled pixel font covers. Other scripts (Cyrillic, CJK, Hangul) need a wider font asset and are out of scope for now.
Example
A mod adds a weather and translates its name to French and German:
# mods/Tempest/weather-config.yaml
weathers:
- name: ashfall
display-name: Ashfall # English
timing: { min-ms: 60000, max-ms: 180000, transition-out-ms: 4000 }
# mods/Tempest/lang/fr.yaml
ashfall: "<your French translation>"
# mods/Tempest/lang/de.yaml
ashfall: "<your German translation>"
A player on French sees the French name; on German, the German one; on English (or any language with no entry), Ashfall; and if display-name were omitted too, Ashfall from the title-cased identifier.
Last updated