This guide explains how localization works in ImGuiX with LangStore, PluralRules, and language-change events.
ImGuiX::I18N::LangStore- Loads localized strings from JSON.
- Loads markdown documents (
.md) lazily. - Supports formatting and plural forms.
ImGuiX::I18N::PluralRules- Resolves plural category (
one,few,many,other, ...). - Can load rules from JSON (
plurals.json). - Has built-in fallback for
enandru.
- Resolves plural category (
ImGuiX::Events::LangChangeEvent- Requests language switch globally or for one window.
From imguix/config/i18n.hpp:
IMGUIX_I18N_DIR-> defaultdata/resources/i18nIMGUIX_I18N_JSON_BASENAME-> defaultstrings.json(name hint only; loader reads all*.jsonin lang directory)IMGUIX_I18N_PLURALS_FILENAME-> defaultplurals.jsonIMGUIX_RESOLVE_PATHS_REL_TO_EXE-> resolve paths relative to executable when nonzero
Practical layout used by current implementation:
data/resources/i18n/
plurals.json
en/
ui_core.json
ui_format.json
Docs.GettingStarted.md
About.App.md
ru/
ui_core.json
Docs.GettingStarted.md
About.App.md
Notes:
LangStoreloads every*.jsonfile inside<base>/<lang>/and merges objects.- Keep keys unique across files in the same language folder.
- Do not rely on cross-file override order when duplicate keys exist.
- Markdown docs are resolved as
<base>/<lang>/<doc_key>.md. doc("Docs.GettingStarted")looks forDocs.GettingStarted.md.
{
"Menu.File": "File",
"Menu.Edit": "Edit"
}{
"Polyglot.Example": {
"en": "Example",
"ru": "Пример"
}
}{
"Banner.Text": ["Hello", " ", "World"]
}LangStore strips JSON comments before parsing, so comments are allowed in practice.
text(key)- Returns localized short string.
label(key)- Returns cached
"<text>##<key>"for stable ImGui IDs.
- Returns cached
doc(doc_key)- Returns markdown content from language folder with fallback.
textf_key(key, ...)- Formats localized template string.
text_plural(base_key, n)- Resolves pluralized key by category.
textf_plural(base_key, n, ...)- Same as above with formatting.
Example:
ImGui::TextUnformatted(langStore().text("Menu.File").c_str());
ImGui::InputText(langStore().label("Settings.General.Username"), buf, IM_ARRAYSIZE(buf));
ImGui::Text("%s", langStore().textf_key("Build.Version", 1, 2, 3).c_str());
ImGui::Text("%s", langStore().textf_plural("Items", n, fmt::arg("n", n)).c_str());- Current language map.
- Default language map (
enby default). - Internal missing marker (
"##null").
<base_key>.<suffix>in current language.<base_key>.<suffix>in default language.<base_key>in current language.<base_key>in default language.- Missing marker.
plurals.json uses per-language cardinal arrays. Supported condition keys:
trueall(array of sub-rules)any(array of sub-rules)not(single sub-rule)eqin(list)range([a, b])mod_eq({"mod": M, "eq": E})mod_in_list({"mod": M, "list": [...]})mod_in_range({"mod": M, "range": [a, b]})
Example fragment:
{
"en": {
"cardinal": [
{ "cat": "one", "eq": 1 },
{ "cat": "other", "true": true }
]
}
}Use data/resources/i18n/plurals.json (or your configured
IMGUIX_I18N_PLURALS_FILENAME) to define per-language plural categories.
Authoring rules:
- Top-level keys are language codes (
"en","ru", ...). - Each language provides a
cardinalarray. - Each item must contain
catand a condition (eq,in,range,mod_*,all/any/not, ortrue). - Rules are evaluated top-to-bottom.
- First matching rule wins.
- Always keep a catch-all tail rule:
{ "cat": "other", "true": true }.
Category-to-text contract:
text_plural("Items", n)resolves a category and then looks upItems.<cat>.- If your rules emit
one/few/many/other, your language JSON must provide these keys.
Failure behavior:
- If
plurals.jsonis missing or invalid, loaded custom rules are skipped. - Plural category falls back to built-in behavior (
en,ru, then English-like default). LangStorefallback chain still applies for missing localized keys.
Example plurals.json:
{
"en": {
"cardinal": [
{ "cat": "one", "eq": 1 },
{ "cat": "other", "true": true }
]
},
"ru": {
"cardinal": [
{
"cat": "one",
"all": [
{ "mod_eq": { "mod": 10, "eq": 1 } },
{ "not": { "mod_eq": { "mod": 100, "eq": 11 } } }
]
},
{
"cat": "few",
"all": [
{ "mod_in_list": { "mod": 10, "list": [2, 3, 4] } },
{ "not": { "mod_in_range": { "mod": 100, "range": [12, 14] } } }
]
},
{
"cat": "many",
"any": [
{ "mod_eq": { "mod": 10, "eq": 0 } },
{ "mod_in_range": { "mod": 10, "range": [5, 9] } },
{ "mod_in_range": { "mod": 100, "range": [11, 14] } }
]
},
{ "cat": "other", "true": true }
]
}
}Matching short-string keys:
{
"Items.one": "{n} item",
"Items.few": "{n} items",
"Items.many": "{n} items",
"Items.other": "{n} items"
}notify(ImGuiX::Events::LangChangeEvent::ForAll("ru"));- Queues
LangChangeEvent. - During frame processing, applies event to all windows or target window.
- Calls for each target:
requestLanguageChange(lang)applyPendingLanguageChange()
Inside applyPendingLanguageChange():
- Calls
onBeforeLanguageApply(lang)hook. - Calls
m_lang_store.set_language(lang). - Calls
m_font_manager.rebuildIfNeeded().
- Create resources under
data/resources/i18n/<lang>/(*.json+ optional<doc_key>.md), keep keys unique. - Read localized text in controllers/widgets via
langStore().text(...),label(...),doc(...),text_plural(...). - Emit language change event:
notify(ImGuiX::Events::LangChangeEvent::ForAll("ru"));. - In window hook, map language to fonts locale:
onBeforeLanguageApply(lang) { fontsControl().setLocale(lang); }. - Let
WindowInstancecallrebuildIfNeeded(); atlas rebuild applies new glyph coverage.
Language change and font glyph coverage are separate concerns:
LangStoreswitches text source.- Font atlas must still contain glyphs for the new language.
Recommended window hook:
void onBeforeLanguageApply(const std::string& lang) override {
fontsControl().setLocale(lang); // mark font atlas dirty
}Then rebuildIfNeeded() (already called by WindowInstance) rebuilds atlas when needed.
If locale glyph ranges are not present, text can appear as squares.
examples/smoke/i18n_demo.cppexamples/smoke/font_switch_demo.cppexamples/smoke/widgets_demo.cpp
- Key is absent in current and default language maps.
- Verify JSON files are loaded from expected
<base>/<lang>/path.
- Missing
<doc_key>.mdin current and fallback language folders.
- Missing category keys (
Items.few, etc.) for target language. - Broken or missing
plurals.json.
- Font atlas not rebuilt for new locale ranges.
- Update font locale (
fontsControl().setLocale(lang)) and rebuild.