Skip to content
Open
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
1 change: 1 addition & 0 deletions examples/embedded/assets/locales/en-US/hello_world.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello-world = hello world (embedded)
4 changes: 4 additions & 0 deletions examples/embedded/assets/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:
- hello_world.ftl
69 changes: 69 additions & 0 deletions examples/embedded/main.rs
Original file line number Diff line number Diff line change
@@ -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<AssetServer>,
assets: Res<Assets<BundleAsset>>,
mut handle: Local<Option<Handle<BundleAsset>>>,
mut exit: MessageWriter<AppExit>,
) {
// `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);
}
}
1 change: 1 addition & 0 deletions examples/fallback_chain/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
4 changes: 3 additions & 1 deletion examples/minimal/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
18 changes: 10 additions & 8 deletions src/assets/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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<BundleAsset> {
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::<ResourceAsset>().unwrap();
if let Err(errors) = bundle.add_resource(resource.0.clone()) {
Expand Down
4 changes: 3 additions & 1 deletion src/assets/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bevy::asset::LoadDirectError;
use bevy::asset::{LoadDirectError, ParseAssetPathError};
use ron::error::SpannedError;
use std::io;
use thiserror::Error;
Expand All @@ -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),
Expand Down