1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use super::resource::load_fluent_resource;
use anyhow::Result;
use bevy::{
asset::{AssetLoader, AssetPath, LoadContext, LoadedAsset},
reflect::TypeUuid,
utils::BoxedFuture,
};
use fluent::{bundle::FluentBundle, FluentArgs, FluentResource};
use intl_memoizer::concurrent::IntlLangMemoizer;
use log::{error, log_enabled, warn, Level};
use serde::Deserialize;
use std::{
ops::{Deref, DerefMut},
path::PathBuf,
str,
};
use typed_builder::TypedBuilder;
use unic_langid::LanguageIdentifier;
#[derive(TypeUuid)]
#[uuid = "929113bb-9187-44c3-87be-6027fc3b7ac5"]
pub struct Bundle(FluentBundle<FluentResource, IntlLangMemoizer>);
impl Bundle {
pub fn content(&self, query: &Query) -> Option<String> {
let message = self.get_message(&query.id)?;
let pattern = match &query.attribute {
None => message.value()?,
Some(key) => message.get_attribute(key)?.value(),
};
let mut errors = Vec::new();
let content = self
.format_pattern(pattern, query.args.as_ref(), &mut errors)
.to_string();
if log_enabled!(Level::Error) {
for error in errors {
error!("{}", error);
}
}
Some(content)
}
}
impl Deref for Bundle {
type Target = FluentBundle<FluentResource, IntlLangMemoizer>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Bundle {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Default)]
pub struct Loader;
impl AssetLoader for Loader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<()>> {
Box::pin(async move { load_bundle(bytes, load_context).await })
}
fn extensions(&self) -> &[&str] {
&["bundle", "ron"]
}
}
async fn load_bundle<'a, 'b>(bytes: &'a [u8], load_context: &'a mut LoadContext<'b>) -> Result<()> {
let Intermediate { locales, resources } = ron::de::from_bytes(bytes)?;
let mut fluent_bundle = FluentBundle::new_concurrent(locales);
let mut asset_paths = Vec::new();
let parent = load_context.path().with_file_name("");
for mut path in resources {
if path.is_relative() {
path = parent.join(path);
}
let bytes = load_context.read_asset_bytes(&path).await?;
let fluent_resource = load_fluent_resource(&bytes).await?;
if let Err(error) = fluent_bundle.add_resource(fluent_resource) {
warn!("overriding fluent message: {:?}", error);
}
let asset_path = AssetPath::new(path, None);
asset_paths.push(asset_path.clone());
}
load_context
.set_default_asset(LoadedAsset::new(Bundle(fluent_bundle)).with_dependencies(asset_paths));
Ok(())
}
#[derive(TypedBuilder)]
pub struct Query<'a> {
#[builder(setter(into))]
id: String,
#[builder(default, setter(into, strip_option))]
attribute: Option<String>,
#[builder(default, setter(into, strip_option))]
args: Option<FluentArgs<'a>>,
}
#[derive(Deserialize)]
struct Intermediate {
locales: Vec<LanguageIdentifier>,
resources: Vec<PathBuf>,
}