Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

  1. Set "locale" in game.json to the language of your base files (the ones without a suffix).
  2. List switcher locales under i18n.locales if settings should offer a language row.
  3. Add script_en.txt (or whatever locale id) when you translate the story; keep the same moment ids.
  4. Use text_key / label_key / display_name_key only when you want a shared catalog; literals still work without one.

Where this lives

IdeaFile / surfacePath
Base locale + switcher listgame.jsonlocale, i18n.locales
Overlay filesProject tree*_<locale>.* next to each base file
Key → stringstory/strings.json (+ strings_<locale>.json)catalog object
Keyed UI copyScreens, choices, buttons, packstext_key, label_key, suffix_key (sliders), display_name_key, title_key
Settings switcher rowscreens.jsoncontent { "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

  1. Player / settings override (set_locale), if any.
  2. 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.

AssetBaseLocale fileHow it combines
Gamegame.jsongame_<locale>.jsonDeep merge
Themethemes/default.jsondefault_<locale>.jsonDeep merge
Style packsthemes/<name>.json<name>_<locale>.jsonDeep merge
Screensscreens.jsonscreens_<locale>.jsonScreens merge (below)
Keybindingskeybindings.jsonkeybindings_<locale>.jsonDeep merge
Cursorscursors.jsoncursors_<locale>.jsonDeep merge
Character packscharacters/<id>/<pack>.json<pack>_<locale>.jsonDeep merge
Animation presetsanimations/presets.jsonpresets_<locale>.jsonDeep merge
Scriptstory/script.txtscript_<locale>.txtWhole-file pick (no merge)
Story JSONstory/story.jsonstory_<locale>.jsonWhole-file pick
Stringsstory/strings.jsonstrings_<locale>.jsonCatalog pick (below)

Missing overlay → keep the base file. No crash.

Merge rules

FamilyRule
Most JSON overlaysDeep-merge objects; arrays replace wholesale; null deletes a key
ScreensSame, but arrays of objects merge by id if present, else by order. Patch only what changed
Script / story JSONPrefer the locale file if it exists; otherwise the base file

Strings catalog

Optional. Used when fields carry *_key.

SituationCatalog used
Locale equals project base localestrings.json only
Other locale, strings_<locale>.json existsThat file
Other locale, postfix missingFall back to strings.json if present
Nothing foundEmpty 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

SurfaceKeyLiteral
game.jsontitle_keytitle
Screens (heading, text, links, …)text_keytext
Buttons / menus / chromelabel_keylabel
Settings sliderssuffix_keysuffix
Choicestext_keytext
Character packsdisplay_name_keydisplay_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 if label_key is set (icon-only).
  • Labelplate prefers a non-empty literal text / prefix / suffix before 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:

  1. Config and theme reload for the new overlays.
  2. Story reloads (script_<locale> / story_<locale> + strings).
  3. Play restores the same node id and flags without replaying commands.
  4. If that node id is missing in the new story, the previous locale is restored and a locale_unavailable notification shows.
  5. On success: backlog relocalizes and open screens rebuild.

Keep node ids aligned across locale scripts if players can switch mid-game.

Usually you need

WantDo
One languageSet locale; skip overlays
Second languageOverlay files + i18n.locales
Shared UI stringsstory/strings.json + *_key fields

Strings catalog · Title, pause and settings screens · Theme and the dialogue box · Pictures, sound, and folders