Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 234 additions & 6 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ ctrlc = "3"
bevy_simple_subsecond_system = { version = "0.2", optional = true }
winit.workspace = true
image.workspace = true
jackdaw_localization = { version = "0.4.1", path = "crates/jackdaw_localization" }

[workspace.dependencies]
bevy = { version = "0.18", features = [
Expand All @@ -156,6 +157,7 @@ jackdaw_remote = { version = "0.4.1", path = "crates/jackdaw_remote" }
jackdaw_node_graph = { version = "0.4.1", path = "crates/jackdaw_node_graph" }
jackdaw_animation = { version = "0.4.1", path = "crates/jackdaw_animation" }
jackdaw_fuzzy = { version = "0.4.1", path = "crates/jackdaw_fuzzy" }
jackdaw_localization = { version = "0.4.1", path = "crates/jackdaw_localization" }
noise = "0.9"
rand = "0.9"
serde = "1"
Expand Down Expand Up @@ -195,6 +197,10 @@ tracing = "0.1"
tracing-subscriber = "0.3"
winit = "0.30.13"
image = { version = "0.25.10", default-features = false, features = ["png"] }
fluent_content = "0.0.5"
bevy_fluent = { git = "https://github.com/ThierryBerger/bevy_fluent.git", branch = "asset_path_resolve" }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targeting my git branch, PR is kgv/bevy_fluent#58

unic-langid = { version = "0.9", features = ["macros"] }
sys-locale = "0.2"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sys-locale is not too maintained, but useful and not much "easy" alternatives to my knowledge


# Idiomatic Bevy code often triggers these lints, and the CI workflow treats them as errors.
[workspace.lints.clippy]
Expand Down
1 change: 1 addition & 0 deletions crates/jackdaw_animation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jackdaw_feathers.workspace = true
jackdaw_commands.workspace = true
jackdaw_node_graph.workspace = true
jackdaw_jsn.workspace = true
jackdaw_localization.workspace = true
lucide-icons = { workspace = true }
serde = { workspace = true, features = ["derive"] }

Expand Down
3 changes: 2 additions & 1 deletion crates/jackdaw_animation/src/timeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use jackdaw_feathers::button::{
};
use jackdaw_feathers::icons::IconFont;
use jackdaw_feathers::tokens;
use jackdaw_localization::LocalizedText;
use lucide_icons::Icon;

use crate::blend_graph::AnimationBlendGraph;
Expand Down Expand Up @@ -358,7 +359,7 @@ fn spawn_placeholder(commands: &mut Commands, parent: Entity) {
.id();

commands.spawn((
Text::new("No animation clip on selection. Pick a named entity and create one."),
LocalizedText::new("no-animation-clip-on-selection"),
TextColor(tokens::TEXT_MUTED_COLOR.into()),
TextFont {
font_size: tokens::FONT_SM,
Expand Down
1 change: 1 addition & 0 deletions crates/jackdaw_feathers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jackdaw_fuzzy.workspace = true
jackdaw_jsn.workspace = true
jackdaw_widgets.workspace = true
lucide-icons.workspace = true
jackdaw_localization.workspace = true

[lints]
workspace = true
3 changes: 2 additions & 1 deletion crates/jackdaw_feathers/src/status_bar.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bevy::{feathers::theme::ThemedText, prelude::*};
use jackdaw_localization::LocalizedText;

use crate::tokens;

Expand Down Expand Up @@ -36,7 +37,7 @@ pub fn status_bar() -> impl Bundle {
children![
(
StatusBarLeft,
Text::new("Ready"),
LocalizedText::new("ready"),
TextFont {
font_size: tokens::FONT_SM,
..Default::default()
Expand Down
19 changes: 19 additions & 0 deletions crates/jackdaw_localization/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "jackdaw_localization"
version = "0.4.1"
edition = "2024"
description = "Internal crate for Jackdaw localization"
license = "MIT OR Apache-2.0"
repository = "https://github.com/jbuehler23/jackdaw"

[features]

[dependencies]
bevy.workspace = true
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll want to split this in multiple crates, but that's what @janhohenheim is exploring on another PR, last to be merged gets to update :')

fluent_content.workspace = true
bevy_fluent.workspace = true
unic-langid.workspace = true
sys-locale.workspace = true

[lints]
workspace = true
129 changes: 129 additions & 0 deletions crates/jackdaw_localization/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//! Localization for jackdaw Editor.

use bevy::asset::{LoadedFolder, embedded_asset};
use bevy::prelude::*;
use bevy_fluent::prelude::*;
use fluent_content::Content;
use unic_langid::langid;

macro_rules! supported_languages {
($($lang:literal),+ $(,)?) => {
pub const SUPPORTED_LANGUAGES: &[&str] = &[$($lang),+];

/// Macro-generated boilerplate to register our locale assets.
fn embed_locale_assets(app: &mut App) {
$(
embedded_asset!(app, concat!("locales/", $lang, "/main.ftl"));
embedded_asset!(app, concat!("locales/", $lang, "/main.ftl.yml"));
)+
}
};
}

supported_languages!("en-US");

pub struct LocalizationPlugin;

impl Plugin for LocalizationPlugin {
fn build(&self, app: &mut App) {
embed_locale_assets(app);

// TODO: Offer a way for user to customize language + persist to disk.
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
let parsed_locale = locale.parse().unwrap_or(langid!("en-US"));
app.insert_resource(SelectedLocale { locale });
app.insert_resource(Locale::new(parsed_locale).with_default(langid!("en-US")));

app.add_plugins(FluentPlugin);
app.init_resource::<LocaleFolder>();
app.init_resource::<Localization>();

app.add_systems(PreStartup, load_editor_locales);
app.add_systems(
Update,
(
update_used_locale.run_if(resource_changed::<SelectedLocale>),
rebuild_localization.run_if(localization_needs_rebuild),
update_all_text.run_if(resource_changed::<Localization>),
update_changed_text,
)
.chain(),
);
}
}

#[derive(Resource)]
pub struct SelectedLocale {
pub locale: String,
}

#[derive(Resource, Default)]
pub struct LocaleFolder(pub Option<Handle<LoadedFolder>>);

#[derive(Component, Default, Reflect)]
#[require(Text)]
pub struct LocalizedText(pub String);

impl LocalizedText {
pub fn new(request: impl Into<String>) -> Self {
Self(request.into())
}
}

fn load_editor_locales(asset_server: Res<AssetServer>, mut folder: ResMut<LocaleFolder>) {
folder.0 = Some(asset_server.load_folder("embedded://jackdaw_localization/locales"));
}

/// Parse [`SelectedLocale`] and store it into [`Locale`].
pub fn update_used_locale(selected: Res<SelectedLocale>, mut locale: ResMut<Locale>) {
locale.requested = selected.locale.parse().unwrap_or(langid!("en-US"));
}

fn localization_needs_rebuild(
locale: Res<Locale>,
mut folder_events: MessageReader<AssetEvent<LoadedFolder>>,
mut bundle_events: MessageReader<AssetEvent<BundleAsset>>,
) -> bool {
let folder_ready = folder_events
.read()
.any(|e| matches!(e, AssetEvent::LoadedWithDependencies { .. }));
let bundle_ready = bundle_events.read().any(|e| {
matches!(
e,
AssetEvent::LoadedWithDependencies { .. } | AssetEvent::Modified { .. }
)
});
locale.is_changed() || folder_ready || bundle_ready
}

fn rebuild_localization(
builder: LocalizationBuilder,
folder: Res<LocaleFolder>,
mut localization: ResMut<Localization>,
) {
let Some(handle) = folder.0.as_ref() else {
return;
};
*localization = builder.build(handle);
}

/// Re-resolve every [`LocalizedText`] after the active locale changes.
fn update_all_text(localization: Res<Localization>, mut q: Query<(&LocalizedText, &mut Text)>) {
for (loc, mut text) in q.iter_mut() {
text.0 = localization
.content(&loc.0)
.unwrap_or_else(|| loc.0.clone());
}
}

/// Resolve newly inserted or mutated [`LocalizedText`] entries.
fn update_changed_text(
localization: Res<Localization>,
mut q: Query<(&LocalizedText, &mut Text), Changed<LocalizedText>>,
) {
for (loc, mut text) in q.iter_mut() {
text.0 = localization
.content(&loc.0)
.unwrap_or_else(|| loc.0.clone());
}
}
12 changes: 12 additions & 0 deletions crates/jackdaw_localization/src/locales/en-US/main.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cancel = Cancel
submit = Submit
add-node = Add Node
add-entity = Add Entity
add-component = Add Component
ready = Ready
no-animation-clip-on-selection = No animation clip on selection. Pick a named entity and create one.
new-workspace = New Workspace
preparing-build = Preparing build...
source-checkout = Source checkout
read-only = (read-only)
material-not-loaded = (material not loaded)
4 changes: 4 additions & 0 deletions crates/jackdaw_localization/src/locales/en-US/main.ftl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Locale files may be in YAML as in this example, or in RON
locale: en-US
resources:
- embedded://jackdaw_localization/locales/en-US/main.ftl
1 change: 1 addition & 0 deletions crates/jackdaw_node_graph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jackdaw_widgets.workspace = true
jackdaw_feathers.workspace = true
jackdaw_commands.workspace = true
jackdaw_jsn.workspace = true
jackdaw_localization.workspace = true
serde = { workspace = true, features = ["derive"] }

[lints]
Expand Down
3 changes: 2 additions & 1 deletion crates/jackdaw_node_graph/src/add_node_popover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use bevy::picking::pointer::PointerButton;
use bevy::prelude::*;
use bevy::ui::UiGlobalTransform;
use jackdaw_commands::CommandHistory;
use jackdaw_localization::LocalizedText;

use crate::canvas::{GraphCanvasViewport, GraphCanvasWorld};
use crate::commands::AddGraphNodeCmd;
Expand Down Expand Up @@ -150,7 +151,7 @@ pub fn spawn_popover(
BackgroundColor(HEADER_BG),
ChildOf(popover_entity),
children![(
Text::new("Add Node"),
LocalizedText::new("add-node"),
TextFont {
font_size: 12.0,
..default()
Expand Down
3 changes: 2 additions & 1 deletion src/inspector/component_display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use jackdaw_feathers::{
icons::{EditorFont, Icon, IconFont},
tokens,
};
use jackdaw_localization::LocalizedText;
use jackdaw_widgets::collapsible::{
CollapsibleBody, CollapsibleHeader, CollapsibleSection, ToggleCollapsible,
};
Expand Down Expand Up @@ -445,7 +446,7 @@ pub(crate) fn build_inspector_displays(

// Fallback: no reflection data
commands.spawn((
Text::new("(read-only)"),
LocalizedText::new("read-only"),
TextFont {
font_size: tokens::FONT_SM,
..Default::default()
Expand Down
3 changes: 2 additions & 1 deletion src/inspector/material_display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use jackdaw_feathers::{
text_edit::{self, TextEditCommitEvent, TextEditProps},
tokens,
};
use jackdaw_localization::LocalizedText;

/// Marker for material field UI entities
#[derive(Component)]
Expand Down Expand Up @@ -46,7 +47,7 @@ fn spawn_material_fields(world: &mut World, body_entity: Entity, source_entity:
mat_data
else {
world.spawn((
Text::new("(material not loaded)"),
LocalizedText::new("material-not-loaded"),
TextFont {
font_size: tokens::FONT_SM,
..Default::default()
Expand Down
13 changes: 7 additions & 6 deletions src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use jackdaw_feathers::{
tokens,
tree_view::tree_container_drop_observers,
};
use jackdaw_localization::LocalizedText;

use crate::{
EditorEntity,
Expand Down Expand Up @@ -637,7 +638,7 @@ pub fn hierarchy_content(icon_font: Handle<Font>) -> impl Bundle {
TextColor(tokens::TEXT_PRIMARY),
),
(
Text::new("Add Entity"),
LocalizedText::new("add-entity"),
TextFont {
font_size: tokens::TEXT_SIZE,
weight: FontWeight::MEDIUM,
Expand All @@ -663,7 +664,7 @@ pub fn hierarchy_content(icon_font: Handle<Font>) -> impl Bundle {
),
(
crate::status_bar::SceneStatsText,
Text::new(""),
Text::default(),
TextFont {
font_size: tokens::FONT_SM,
..Default::default()
Expand Down Expand Up @@ -850,7 +851,7 @@ fn editor_status_bar() -> impl Bundle {
children![
(
status_bar::StatusBarLeft,
Text::new("Ready"),
LocalizedText::new("ready"),
TextFont {
font_size: tokens::FONT_SM,
..Default::default()
Expand All @@ -859,7 +860,7 @@ fn editor_status_bar() -> impl Bundle {
),
(
status_bar::StatusBarCenter,
Text::new(""),
Text::default(),
TextFont {
font_size: tokens::FONT_SM,
..Default::default()
Expand All @@ -877,7 +878,7 @@ fn editor_status_bar() -> impl Bundle {
children![
(
status_bar::StatusBarRight,
Text::new(""),
Text::default(),
TextFont {
font_size: tokens::FONT_SM,
..Default::default()
Expand Down Expand Up @@ -959,7 +960,7 @@ pub fn inspector_components_content(icon_font: Handle<Font>) -> impl Bundle {
TextColor(tokens::TEXT_PRIMARY),
),
(
Text::new("Add Component"),
LocalizedText::new("add-component"),
TextFont {
font_size: tokens::TEXT_SIZE,
weight: FontWeight::MEDIUM,
Expand Down
Loading
Loading