Translations
Lives in: game.json → locale / i18n · overlay files · optional story/strings.json
VN-Mikro ships one base language as the unsuffixed files, then optional locale overlays and an optional strings catalog for keyed text.
Most stories
- Set
"locale"ingame.jsonto the language of your base files (the ones without a suffix). - List switcher locales under
i18n.localesif settings should offer a language row. - Add
script_en.txt(or whatever locale id) when you translate the story; keep the same moment ids. - Use
text_key/label_key/display_name_keyonly when you want a shared catalog; literals still work without one.
Where this lives
| Idea | File / surface | Path |
|---|---|---|
| Base locale + switcher list | game.json | locale, i18n.locales |
| Overlay files | Project tree | *_<locale>.* next to each base file |
| Key → string | story/strings.json (+ strings_<locale>.json) | catalog object |
| Keyed UI copy | Screens, choices, buttons, packs | text_key, label_key, suffix_key (sliders), display_name_key, title_key |
| Settings switcher row | screens.json | content { "bind": "locale", "label_key": "…" } |
{
"locale": "uk",
"title": "Мій візуальний роман",
"title_key": "game.title",
"i18n": {
"locales": [
{ "id": "uk", "label": "UK", "label_key": "screen.settings.locale_uk" },
{ "id": "en", "label": "EN", "label_key": "screen.settings.locale_en" }
]
}
}
project/
game.json
game_en.json
themes/default.json
themes/default_en.json
screens.json
screens_en.json
story/
script.txt
script_en.txt
strings.json
strings_en.json
characters/hana/default.json
characters/hana/default_en.json
{
"game.title": "Мій візуальний роман",
"screen.settings.locale_uk": "Українська",
"choice.help": "Допомогти Хані"
}
{
"settings": {
"content": {
"blocks": [
{
"bind": "locale",
"label": "Language",
"label_key": "screen.settings.language",
"style": "locale_button",
"active_style": "locale_button_active"
},
{
"type": "button",
"label": "Back",
"label_key": "screen.settings.back"
}
]
}
}
}
Catalog shape: Strings catalog. Map: game.json.
Effective locale
- Player / settings override (
set_locale), if any. - Else
game.json→locale.
That value picks which overlay files load. An overlay may contain a "locale" field, but it does not re-choose which files were loaded in that pass.
File naming
The locale id is inserted before the extension: foo.json → foo_uk.json, script.txt → script_uk.txt.
| Asset | Base | Locale file | How it combines |
|---|---|---|---|
| Game | game.json | game_<locale>.json | Deep merge |
| Theme | themes/default.json | default_<locale>.json | Deep merge |
| Style packs | themes/<name>.json | <name>_<locale>.json | Deep merge |
| Screens | screens.json | screens_<locale>.json | Screens merge (below) |
| Keybindings | keybindings.json | keybindings_<locale>.json | Deep merge |
| Cursors | cursors.json | cursors_<locale>.json | Deep merge |
| Character packs | characters/<id>/<pack>.json | <pack>_<locale>.json | Deep merge |
| Animation presets | animations/presets.json | presets_<locale>.json | Deep merge |
| Script | story/script.txt | script_<locale>.txt | Whole-file pick (no merge) |
| Story JSON | story/story.json | story_<locale>.json | Whole-file pick |
| Strings | story/strings.json | strings_<locale>.json | Catalog pick (below) |
Missing overlay → keep the base file. No crash.
Merge rules
| Family | Rule |
|---|---|
| Most JSON overlays | Deep-merge objects; arrays replace wholesale; null deletes a key |
| Screens | Same, but arrays of objects merge by id if present, else by order. Patch only what changed |
| Script / story JSON | Prefer the locale file if it exists; otherwise the base file |
Strings catalog
Optional. Used when fields carry *_key.
| Situation | Catalog used |
|---|---|
Locale equals project base locale | strings.json only |
Other locale, strings_<locale>.json exists | That file |
| Other locale, postfix missing | Fall back to strings.json if present |
| Nothing found | Empty catalog — keys miss, literals win |
Missing key → use the literal field (or the surface’s default). Games can ship with no catalog at all.
Key fields vs literals
| Surface | Key | Literal |
|---|---|---|
game.json | title_key | title |
| Screens (heading, text, links, …) | text_key | text |
| Buttons / menus / chrome | label_key | label |
| Settings sliders | suffix_key | suffix |
| Choices | text_key | text |
| Character packs | display_name_key | display_name |
Prefer key-only: set the *_key and put the copy in story/strings.json. Omit the sibling literal so the catalog is the single source. Keep a literal only as a standalone fallback when the key is missing.
Usual order: non-empty key and found in catalog → catalog value; else literal; else surface default.
check reports a keyed field that has no literal and no entry in the base catalog (i18n.missing_key).
Special cases:
- Button
label: ""stays blank even iflabel_keyis set (icon-only). - Labelplate prefers a non-empty literal
text/prefix/suffixbefore keys. - Character display name: catalog →
display_name→ capitalized folder name.
Settings locale switcher
A settings { "bind": "locale" } block expands into the locales in i18n.locales as chips. The active one uses active_style (default locale_button_active) and is not clickable. Locales not listed in i18n.locales are rejected by set_locale.
Mid-session language switch
When the player picks another locale:
- Config and theme reload for the new overlays.
- Story reloads (
script_<locale>/story_<locale>+ strings). - Play restores the same node id and flags without replaying commands.
- If that node id is missing in the new story, the previous locale is restored and a
locale_unavailablenotification shows. - On success: backlog relocalizes and open screens rebuild.
Keep node ids aligned across locale scripts if players can switch mid-game.
Usually you need
| Want | Do |
|---|---|
| One language | Set locale; skip overlays |
| Second language | Overlay files + i18n.locales |
| Shared UI strings | story/strings.json + *_key fields |
Related
Strings catalog · Title, pause and settings screens · Theme and the dialogue box · Pictures, sound, and folders