| Crates.io | bevy-intl |
| lib.rs | bevy-intl |
| version | 0.2.1 |
| created_at | 2025-08-17 21:25:19.457881+00 |
| updated_at | 2025-09-02 20:43:50.876096+00 |
| description | A custom Bevy plugin for adding traductions |
| homepage | |
| repository | https://github.com/DelsarteAdam/bevy-intl |
| max_upload_size | |
| id | 1799720 |
| size | 177,956 |
A simple internationalization (i18n) plugin for Bevy to manage translations from JSON files. Supports fallback languages, placeholders, plurals, gendered translations, and full WASM compatibility with bundled translations.
bundle-only feature to force bundled translations on any platformAdd to your Cargo.toml:
[dependencies]
bevy = "0.16"
bevy-intl = "0.2.1"
# Optional: Force bundled translations on all platforms
# bevy-intl = { version = "0.2.0", features = ["bundle-only"] }
Initialize the plugin in your Bevy app:
use bevy::prelude::*;
use bevy_intl::{I18nPlugin, I18nConfig};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// Default setup - auto-detects WASM vs desktop
.add_plugins(I18nPlugin::default())
.add_systems(Startup, setup_ui)
.run();
}
// Or with custom configuration
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(I18nPlugin::with_config(I18nConfig {
use_bundled_translations: false, // Force filesystem loading This gets ignored when bundle-only feature is enabled
messages_folder: "locales".to_string(), // Custom folder
default_lang: "fr".to_string(),
fallback_lang: "en".to_string(),
}))
.add_systems(Startup, setup_ui)
.run();
}
messages/
├── en/
│ ├── test.json
│ └── another_file.json
├── fr/
│ ├── test.json
│ └── another_file.json
└── es/
├── test.json
└── another_file.json
assets/
src/
Desktop/Native:
messages/ folder at runtimeWASM/Web:
Force Bundled Mode:
bevy-intl = { version = "0.2.1", features = ["bundle-only"] }
This forces bundled translations on all platforms
Each JSON file can contain either simple strings or nested maps for plurals/genders:
{
"greeting": "Hello",
"farewell": {
"male": "Goodbye, sir",
"female": "Goodbye, ma'am"
},
"apples": {
"zero": "No apples",
"one": "One apple",
"two": "A couple of apples",
"few": "A few apples",
"many": "{{count}} apples",
"other": "{{count}} apples"
},
"items": {
"0": "No items",
"1": "One item",
"2": "Two items",
"5": "Exactly five items",
"other": "{{count}} items"
}
}
"0", "1", "2", "5", etc."zero", "one", "two", "few", "many""one" vs "other""many" as last resortThis supports complex plural rules for languages like:
one, otherone, manyone, few, manyone, few, manyzero, one, two, few, manyuse bevy::prelude::*;
use bevy_intl::{I18n, LanguageAppExt};
fn translation_system(i18n: Res<I18n>) {
// Load a translation file
let text = i18n.translation("test");
// Basic translation
let greeting = text.t("greeting");
// Translation with arguments
let apple_count = text.t_with_arg("apples", &[&5]);
// Plural translation
let plural_text = text.t_with_plural("apples", 5);
// Gendered translation
let farewell = text.t_with_gender("farewell", "female");
// Gendered translation with arguments
let farewell_with_name = text.t_with_gender_and_arg("farewell", "male", &[&"John"]);
}
// Method 1: Using App extension trait
fn setup_language(mut app: ResMut<App>) {
app.set_lang_i18n("fr"); // Set current language
app.set_fallback_lang("en"); // Set fallback language
}
// Method 2: Direct resource access
fn change_language_system(mut i18n: ResMut<I18n>) {
i18n.set_lang("en"); // Set current language
let current = i18n.get_lang(); // Get current language
let available = i18n.available_languages(); // Get all available languages
}
use bevy::prelude::*;
use bevy_intl::{I18nPlugin, I18n, LanguageAppExt};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(I18nPlugin::default())
.add_systems(Startup, (setup_ui, setup_language))
.add_systems(Update, language_switcher)
.run();
}
fn setup_language(mut app: ResMut<App>) {
app.set_lang_i18n("en");
app.set_fallback_lang("en");
}
fn setup_ui(mut commands: Commands, i18n: Res<I18n>) {
let text = i18n.translation("ui");
commands.spawn((
Text::new(text.t("welcome_message")),
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(5.0),
right: Val::Px(5.0),
..default()
},
));
}
fn language_switcher(
input: Res<ButtonInput<KeyCode>>,
mut i18n: ResMut<I18n>
) {
if input.just_pressed(KeyCode::F1) {
i18n.set_lang("en");
}
if input.just_pressed(KeyCode::F2) {
i18n.set_lang("fr");
}
}
Missing translation files or invalid locales are warned in the console.
If a translation is missing, the fallback language will be used, or an "Error missing text" placeholder is returned.
This crate is licensed under either of the following, at your option:
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, shall be dual licensed as above, without any additional terms or conditions.