Creating a mod
Translate a mod
One French and English catalogue, stable identifiers and readable fallback text.
1. Add the catalogue
Prerequisites: a configured Kotlin/Native project and an existing model, service or tool window. This guide translates their visible text without changing identifiers, settings or rules.
Create assets/translations.json. The plugin copies the catalogue into the package and the SDK validates it on loading. Each mod owns its text: the same key may have different translations in two mods.
{
"fallback": "en",
"languages": {
"en": {
"maintenance": "Maintenance mode",
"maintenance.help": "Enable the maintenance mode of this signal.",
"repeat": "Repeat",
"count.one": "{count} signal",
"count.many": "{count} signals"
},
"fr": {
"maintenance": "Mode maintenance",
"maintenance.help": "Activer le mode maintenance de ce signal.",
"repeat": "Répéter",
"count.one": "{count} signal",
"count.many": "{count} signaux"
}
}
}fallback selects the fallback language; en is used when omitted. This language must exist and contain every key. Other languages may be incomplete: a missing key uses fallback text.
You choose the keys: mod.name, window.clock and title are examples, not reserved names. A key only takes effect when a tr("key") call supplies it to visible text. Adding mod.name to JSON therefore does not automatically rename the mod; adding window.clock creates neither a window nor a shortcut.
2. Translate controls
Import nimby.tr or nimby.*. Use tr in titles, labels, help and messages. Model, setting, action, window and service identifiers remain fixed strings.
package wiki.translations
import nimby.*
// Identifiers stay stable; only the displayed text is translated.
val maintenance = Checkbox(
"maintenance", tr("maintenance"), tr("maintenance.help")
)
fun summary(count: Int): String =
tr(if (count == 1) "count.one" else "count.many", "count" to count)
fun createTranslatedTool(): ToolMod = toolMod("my-translated-tool", "My tool") {
service("message.v1") { request ->
showPanel(request, summary(3), listOf(
ToolButton(request.originAction, tr("repeat"))
))
}
}
The fragment defines a reusable checkbox and a demonstration tool. Add the checkbox to your model and expose message.v1 through an optional action to display the panel. JSON alone creates no controls.
| Translate | Keep stable |
|---|---|
| metadata(name = tr(...), description = tr(...)) | modInfo.id · modId · module |
| Checkbox.label / description | Checkbox.name |
| SignalType.title · construction.name | SignalType.id · textureSet |
| ToolButton.label · ToolNumberInput.label | ToolButton.id · ToolNumberInput.id |
| ToolWindow.title · message | ToolWindow.id · action · service · whenMod |
In Options → NRF Hub, the group name comes from the title declared by toolMod or signalMod. With toolMod(modInfo), this is modInfo.title, generated from name in mod.json. metadata(name = tr("mod.name")) translates the name in the game’s lists and details; it does not replace that group title. The title passed to window names both the window and its row under Shortcuts.
For a BB Timechange group, use tr("window.clock") with “Date and time” as the window title, for example. The row stays concise without repeating “BB Timechange — Date and time”. Keep its "clock" identifier unchanged in both languages and across updates: changing the label should not create a new shortcut preference.
3. Parameters and plurals
tr("count.many", "count" to 3) replaces {count} with 3. Values use toString: format numbers and dates before supplying them when needed. A key keeps the same parameters across all languages. {{ and }} display literal braces.
The SDK does not select plurals automatically. Declare a key for each wording and choose it in Kotlin, as summary does in the fragment. An argument remains text, without a second translation lookup.
Active language and fallback
Controls follow the game language, independently of Windows or the Hub. Common codes eng and fra map to en and fr. Case and separators are normalized: FR_ca becomes fr-ca.
- Exact language, for example fr-ca.
- Then base language, for example fr.
- Then the mod fallback for this key.
- Reference still missing or invalid: [key] makes the problem visible.
An unavailable language uses the fallback. Changing language updates text without resetting settings. In an open window, it does not replace current input; a new showWindow publication does replace the form with your tool’s values.
Verify both languages
Use strict UTF-8 JSON: no duplicate keys, trailing commas or comments. verifyNativeMod also checks the packaged catalogue without opening the game. An invalid catalogue prevents mod loading and writes a diagnostic.
| Element | Limit |
|---|---|
| translations.json | 1 MiB; 64 languages. |
| One language | 4096 keys; 4096 UTF-8 bytes per text. |
| Key or parameter | 1 to 96 ASCII characters: letters, digits, dot, hyphen and underscore. |
| tr | 8 distinct arguments; reference limited to 256 UTF-8 bytes. |
- Build, verify the package, then activate it in a test game.
- Open panels in French and English; check long text, help and plurals.
- Test an untranslated language to check fallback behaviour.
- After changing JSON, rebuild and reload the mod. Switching language does not reread the file.