From 84fbf955b4028bfedc9b4778f957c24dad550ae6 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Fri, 22 May 2026 14:46:21 +0200 Subject: [PATCH] use AssetPath to resolve bundle path --- .../assets/locales/en-US/hello_world.ftl | 1 + .../assets/locales/en-US/main.ftl.yml | 4 ++ examples/embedded/main.rs | 69 +++++++++++++++++++ examples/fallback_chain/main.rs | 1 + examples/minimal/main.rs | 4 +- src/assets/bundle.rs | 18 ++--- src/assets/error.rs | 4 +- 7 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 examples/embedded/assets/locales/en-US/hello_world.ftl create mode 100644 examples/embedded/assets/locales/en-US/main.ftl.yml create mode 100644 examples/embedded/main.rs diff --git a/examples/embedded/assets/locales/en-US/hello_world.ftl b/examples/embedded/assets/locales/en-US/hello_world.ftl new file mode 100644 index 0000000..87c7388 --- /dev/null +++ b/examples/embedded/assets/locales/en-US/hello_world.ftl @@ -0,0 +1 @@ +hello-world = hello world (embedded) diff --git a/examples/embedded/assets/locales/en-US/main.ftl.yml b/examples/embedded/assets/locales/en-US/main.ftl.yml new file mode 100644 index 0000000..2ee8ca5 --- /dev/null +++ b/examples/embedded/assets/locales/en-US/main.ftl.yml @@ -0,0 +1,4 @@ +# Locale files may be in YAML as in this example, or in RON +locale: en-US +resources: + - hello_world.ftl diff --git a/examples/embedded/main.rs b/examples/embedded/main.rs new file mode 100644 index 0000000..e6f751e --- /dev/null +++ b/examples/embedded/main.rs @@ -0,0 +1,69 @@ +//! Loads a Fluent bundle from `embedded://` assets — headless, no window. +//! +//! Demonstrates that the bundle loader preserves the `embedded` asset source +//! when resolving the YAML's sibling `.ftl` dependency. + +use bevy::{ + app::AppExit, + asset::{embedded_asset, AssetPlugin, LoadState}, + log::LogPlugin, + prelude::*, +}; +use bevy_fluent::prelude::*; +use fluent_content::Content; +use unic_langid::langid; + +pub fn main() { + App::new() + .insert_resource(Locale::new(langid!("en-US"))) + .add_plugins(( + MinimalPlugins, + AssetPlugin::default(), + LogPlugin::default(), + EmbeddedLocalesPlugin, + FluentPlugin, + )) + .add_systems(Update, localized_hello_world) + .run(); +} + +/// See the comments below — `embedded_asset!` from inside a cargo example +/// has two non-obvious quirks (src prefix and crate name). +struct EmbeddedLocalesPlugin; + +impl Plugin for EmbeddedLocalesPlugin { + fn build(&self, app: &mut App) { + // 3-arg form: examples aren't under `src/`, so override the prefix + // that gets stripped from the calling file's path. + embedded_asset!( + app, + "examples/embedded", + "assets/locales/en-US/main.ftl.yml" + ); + embedded_asset!( + app, + "examples/embedded", + "assets/locales/en-US/hello_world.ftl" + ); + } +} + +fn localized_hello_world( + asset_server: Res, + assets: Res>, + mut handle: Local>>, + mut exit: MessageWriter, +) { + // `module_path!()` resolves to the example's own crate name (`embedded`), + // not `bevy_fluent` — that's why the URL's host segment is `embedded`. + let handle = &*handle.get_or_insert_with(|| { + asset_server.load("embedded://embedded/assets/locales/en-US/main.ftl.yml") + }); + if let Some(LoadState::Loaded) = asset_server.get_load_state(handle) { + let bundle = assets.get(handle).unwrap(); + let content = bundle.content("hello-world"); + info!(?content, "loaded embedded bundle"); + assert!(matches!(&content, Some(s) if s == "hello world (embedded)")); + exit.write(AppExit::Success); + } +} diff --git a/examples/fallback_chain/main.rs b/examples/fallback_chain/main.rs index 5b6cb28..dcb92b2 100644 --- a/examples/fallback_chain/main.rs +++ b/examples/fallback_chain/main.rs @@ -37,5 +37,6 @@ fn localized_hello_world( assert!( matches!(localization.content("hello-world"), Some(content) if content == "hello world") ); + info_once!("fallback chain resolved; assertions passed"); } } diff --git a/examples/minimal/main.rs b/examples/minimal/main.rs index e04f61a..99f10e7 100644 --- a/examples/minimal/main.rs +++ b/examples/minimal/main.rs @@ -25,6 +25,8 @@ fn localized_hello_world( let handle = &*handle.get_or_insert_with(|| asset_server.load("locales/en-US/main.ftl.yml")); if let Some(LoadState::Loaded) = asset_server.get_load_state(handle) { let bundle = assets.get(handle).unwrap(); - assert!(matches!(bundle.content("hello-world"), Some(content) if content == "hello world")); + let content = bundle.content("hello-world"); + assert!(matches!(&content, Some(s) if s == "hello world")); + info_once!(?content, "bundle loaded; assertions passed"); } } diff --git a/src/assets/bundle.rs b/src/assets/bundle.rs index 9a9bb50..c98e607 100644 --- a/src/assets/bundle.rs +++ b/src/assets/bundle.rs @@ -3,7 +3,7 @@ use super::{Error, Result}; use crate::ResourceAsset; use bevy::{ - asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext}, + asset::{io::Reader, AssetLoader, AssetPath, AsyncReadExt, LoadContext}, prelude::*, reflect::TypePath, }; @@ -73,17 +73,19 @@ struct Data { #[instrument(fields(path = %load_context.path().path().display()), skip_all)] async fn load(data: Data, load_context: &mut LoadContext<'_>) -> Result { let mut bundle = FluentBundle::new_concurrent(vec![data.locale.clone()]); - for mut path in data.resources { - if path.is_relative() { - if let Some(parent) = load_context.path().path().parent() { - path = parent.join(path); - } - } + let base = load_context.path().clone(); + for path in data.resources { + let path_str = path.to_string_lossy(); + let resolved: AssetPath<'static> = if path.is_relative() { + base.resolve_embed(&path_str)?.into_owned() + } else { + AssetPath::parse(&path_str).clone_owned() + }; let loaded = load_context .loader() .immediate() .with_unknown_type() - .load(path) + .load(resolved) .await?; let resource = loaded.get::().unwrap(); if let Err(errors) = bundle.add_resource(resource.0.clone()) { diff --git a/src/assets/error.rs b/src/assets/error.rs index 6d83c22..b1c9ac9 100644 --- a/src/assets/error.rs +++ b/src/assets/error.rs @@ -1,4 +1,4 @@ -use bevy::asset::LoadDirectError; +use bevy::asset::{LoadDirectError, ParseAssetPathError}; use ron::error::SpannedError; use std::io; use thiserror::Error; @@ -14,6 +14,8 @@ pub enum Error { #[error(transparent)] LoadDirect(#[from] LoadDirectError), #[error(transparent)] + ParseAssetPath(#[from] ParseAssetPathError), + #[error(transparent)] Ron(#[from] SpannedError), #[error(transparent)] Yaml(#[from] serde_yaml::Error),