cloudiful-bevy-localization 0.1.2

Reusable Bevy localization runtime built around app-provided static registries.
Documentation

cloudiful-bevy-localization

Reusable Bevy localization runtime for apps that generate their own static locale registry at build time.

What it provides

  • LocalizationDefinition: static description of fallback locale, supported locales, locale sources, and declared keys
  • LocaleSource: one TOML payload bound to a locale + namespace
  • Locale: runtime locale handle with serialization support
  • TextKey: runtime text key handle with serialization support
  • Localization: Bevy resource for lookup, formatting, locale switching, and table access
  • LocalizationPlugin: registers a definition and inserts the Localization resource
  • LocalizationLoadError: structured load/validation failure
  • register_definition(...): definition registry hook used by the plugin and helper types
  • locale_name_key_id(...): helper for common.locale_name.<locale> key generation

What it does not provide

  • scanning downstream assets/i18n
  • generating app-specific key constants
  • embedding downstream OUT_DIR artifacts inside the crate
  • editor tooling, extraction, or translation workflows

This crate expects the downstream app to generate or hand-author a static registry and then pass that definition into the plugin.

Usage

Generate a static registry in the downstream app and pass it into the plugin. LocalizationPlugin::new(...) registers the definition first and then inserts the Localization resource built from that definition.

use bevy::prelude::*;
use cloudiful_bevy_localization::{
    Locale, LocaleSource, Localization, LocalizationDefinition,
    LocalizationPlugin, TextKey,
};

const KEYS: &[TextKey] = &[TextKey::new("common.hello")];
const SOURCES: &[LocaleSource] = &[
    LocaleSource {
        locale: "en-US",
        namespace: "common",
        contents: r#"
hello = "Hello"
[locale_name]
en_us = "English"
zh_cn = "Chinese"
"#,
    },
    LocaleSource {
        locale: "zh-CN",
        namespace: "common",
        contents: r#"
hello = "你好"
[locale_name]
en_us = "英语"
zh_cn = "中文"
"#,
    },
];

static LOCALIZATION: LocalizationDefinition = LocalizationDefinition {
    fallback_locale: "en-US",
    locales: &["en-US", "zh-CN"],
    sources: SOURCES,
    keys: KEYS,
};

fn main() {
    App::new()
        .add_plugins(LocalizationPlugin::new(&LOCALIZATION))
        .add_systems(Update, read_text);
}

fn read_text(localization: Res<Localization>) {
    assert_eq!(localization.current_locale(), Locale::new("en-US"));
    assert_eq!(localization.text(TextKey::new("common.hello")), "Hello");
}

At runtime, use the resource for lookup, formatting, locale switching, and locale display text:

use bevy::prelude::*;
use cloudiful_bevy_localization::{Locale, Localization, TextKey};

fn ui_text(mut localization: ResMut<Localization>) {
    let text = localization.text(TextKey::new("common.hello")).to_string();
    let formatted = localization.format_text(TextKey::new("common.hello"), []);

    localization.set_locale(Locale::new("zh-CN"));

    let current = localization.current_locale();
    let available = localization.available_locales();
    let display = localization.locale_display_text(current);

    println!("{text} / {formatted} / {display} / {available:?}");
}

Validation Rules

Definitions are validated at load time. A load fails when any of these rules are broken:

  • the fallback locale table is missing
  • a listed locale has no table
  • a declared TextKey is missing from any locale
  • a common.locale_name.<locale> entry is missing for any supported locale
  • a locale table contains keys that are not declared
  • placeholder names differ from the fallback locale for the same key
  • a locale source references a locale not listed in LocalizationDefinition
  • a TOML value is not a string or nested table
  • the same flattened key appears more than once

LocaleSource.contents TOML is flattened by namespace, so:

[nested]
label = "Nested"

under namespace common becomes common.nested.label.

Runtime Behavior

  • Localization::text(...) looks up the current locale first, then the fallback locale, and panics only if both are missing
  • Localization::format_text(...) performs simple {name} replacement on the selected text
  • Localization::lookup(...) and lookup_id(...) return Option<&str> without panicking
  • Localization::table(...) returns the flattened table for one locale
  • Localization::locale_display_text(...) looks up common.locale_name.<locale> in the current locale, then fallback locale, and finally returns the raw locale id

Locale and Key Helpers

  • Locale::new(...) and TextKey::new(...) create static handles
  • Locale::available() reads available locales from the active registered definition
  • Locale::from_serialized(...) normalizes case and punctuation when matching serialized locale ids
  • TextKey::from_id(...) resolves an id against the active registered definition