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
npx -y skills add chrisgliddon/bevy-skills --skill bevy-fluentAssembled 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
| Topic | Reference |
|---|---|
Why types must live in the lib target; cargo es-fluent generate invisibility footgun; full layout | references/lib-target-layout.md |
BevyFluentText derive vs FluentText<T> component; Text::new("") | references/components.md |
LocaleChangeEvent (request) vs LocaleChangedEvent (confirmation); RequestedLanguageId; Messages vs Events | references/locale-events.md |
i18n.toml full schema, assets_dir, I18nPluginConfig runtime override | references/i18n-toml.md |
generate, watch, check, clean, sync, tree, format — dev and CI workflows | references/cli.md |
rustc 1.96+ requirement; rust-toolchain.toml pin; failure modes on older toolchains | references/toolchain.md |
Gotchas
-
cargo es-fluent generateonly sees the lib target. Message enums declared only insrc/main.rsare invisible to the CLI and produce empty.ftloutput with no error. Move everyBevyFluentText/EsFluent-derived type tosrc/lib.rs(or a module reachable from it). See references/lib-target-layout.md. -
BevyFluentTextis a derive macro;FluentText<T>is the component. The derive registers refresh systems viainventory. The component is what you spawn on UI entities. In 0.19.2 the message type needsFluentMessage + Clone + Send + Sync + 'static; it does not deriveComponentitself. See references/components.md. -
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. -
Events are Messages. Use
MessageWriter<LocaleChangeEvent>andMessageReader<LocaleChangedEvent>— notEventWriter/EventReader(renamed in Bevy 0.17). See references/locale-events.md.
See also
bevy-ui—FluentText<T>is used alongsideText,Node, andButton.bevy-ecs-components—#[derive(Component)]patterns required byFluentText<T>.bevy-assets— how Bevy'sAssetServerloads.ftlfiles; relevant for hot-reload.