agentsclimarketplace

Bevy fluent

Skill chrisgliddon/bevy-skills/skills/bevy-fluent

Use when adding localization to a Bevy 0.19 app with `es-fluent-manager-bevy`, defining typed messages with `#[derive(EsFluent)]`, wrapping UI text in `FluentText<T>`, auto-registering locale updates with `#[derive(BevyFluentText)]`, or reacting to locale switches via `LocaleChangeEvent`. Covers `I18nPlugin`, `RequestedLanguageId`, `i18n.toml` config, and hot-reload of `.ftl` assets.From its SKILL.md

Install
npx -y skills add chrisgliddon/bevy-skills --skill bevy-fluent

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 10 stars10 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

5.5 KB, as published. Nobody here has run it

Bevy 0.19 — Localization (es-fluent)

When to use this skill

  • Adding Fluent-based i18n to a Bevy app (es-fluent-manager-bevy = "0.19.2").
  • Defining typed UI messages with #[derive(EsFluent)] and #[derive(BevyFluentText)].
  • Wrapping a UI text entity with FluentText<T> for automatic locale-driven refresh.
  • Switching locales at runtime via LocaleChangeEvent.
  • Reading the current locale from RequestedLanguageId.

Canonical pattern — 5-file minimum shape

Cargo.toml:

[dependencies]
bevy                   = "0.19"
es-fluent              = { version = "0.18.1", features = ["derive"] }
es-fluent-manager-bevy = { version = "0.19.2", features = ["macros"] }
unic-langid            = "0.9"

i18n.toml (crate root, read at compile time):

fallback_language = "en"
assets_dir = "assets/locales"

src/i18n.rs:

es_fluent_manager_bevy::define_i18n_module!();

src/lib.rs — message types must live here (see Gotchas):

use bevy::prelude::*;
use es_fluent::EsFluent;
use es_fluent_manager_bevy::{
    BevyFluentText, FluentText, I18nPlugin, LocaleChangeEvent, RequestedLanguageId,
};
use unic_langid::langid;

pub mod i18n;

#[derive(BevyFluentText, Clone, EsFluent)]
#[fluent(namespace = "ui")]
pub enum UiMessage { StartGame, Settings, QuitGame }

pub fn build_i18n_plugin() -> I18nPlugin {
    I18nPlugin::with_language(langid!("en"))
}

pub fn setup_ui(mut commands: Commands) {
    commands.spawn(Camera2d);
    // FluentText<T> writes translations into a sibling Text component.
    commands.spawn((FluentText::new(UiMessage::StartGame), Text::new("")));
}

pub fn switch_locale_on_keypress(
    keys: Res<ButtonInput<KeyCode>>,
    requested: Res<RequestedLanguageId>,
    mut locale_events: MessageWriter<LocaleChangeEvent>,
) {
    if keys.just_pressed(KeyCode::KeyL) {
        let next = if requested.0.to_string() == "en" { langid!("fr") } else { langid!("en") };
        locale_events.write(LocaleChangeEvent(next));
    }
}

src/main.rs — thin binary entry point:

use bevy::prelude::*;
use my_game::{build_i18n_plugin, setup_ui, switch_locale_on_keypress};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(build_i18n_plugin())
        .add_systems(Startup, setup_ui)
        .add_systems(Update, switch_locale_on_keypress)
        .run();
}

Replace my_game with your package name (hyphens become underscores). The full walkthrough — including the lib-vs-binary rationale, package-name placeholder, and pure-binary-crate workarounds — is in references/lib-target-layout.md.

Topics

TopicReference
Why types must live in the lib target; cargo es-fluent generate invisibility footgun; full layoutreferences/lib-target-layout.md
BevyFluentText derive vs FluentText<T> component; Text::new("")references/components.md
LocaleChangeEvent (request) vs LocaleChangedEvent (confirmation); RequestedLanguageId; Messages vs Eventsreferences/locale-events.md
i18n.toml full schema, assets_dir, I18nPluginConfig runtime overridereferences/i18n-toml.md
generate, watch, check, clean, sync, tree, format — dev and CI workflowsreferences/cli.md
rustc 1.96+ requirement; rust-toolchain.toml pin; failure modes on older toolchainsreferences/toolchain.md

Gotchas

  1. cargo es-fluent generate only sees the lib target. Message enums declared only in src/main.rs are invisible to the CLI and produce empty .ftl output with no error. Move every BevyFluentText / EsFluent-derived type to src/lib.rs (or a module reachable from it). See references/lib-target-layout.md.

  2. BevyFluentText is a derive macro; FluentText<T> is the component. The derive registers refresh systems via inventory. The component is what you spawn on UI entities. In 0.19.2 the message type needs FluentMessage + Clone + Send + Sync + 'static; it does not derive Component itself. See references/components.md.

  3. Minimum rustc 1.96. Older toolchains fail with cryptic trait-resolution errors that do not mention the version requirement. Pin with rust-toolchain.toml ([toolchain] / channel = "1.96"). See references/toolchain.md.

  4. Events are Messages. Use MessageWriter<LocaleChangeEvent> and MessageReader<LocaleChangedEvent> — not EventWriter/EventReader (renamed in Bevy 0.17). See references/locale-events.md.

See also

  • bevy-ui — FluentText<T> is used alongside Text, Node, and Button.
  • bevy-ecs-components — #[derive(Component)] patterns required by FluentText<T>.
  • bevy-assets — how Bevy's AssetServer loads .ftl files; relevant for hot-reload.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.