use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::read_dir;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::languages::negotiate_languages;
use crate::FluentBundle;
use fluent_bundle::{FluentResource, FluentValue};
use crate::error::{LoaderError, LookupError};
pub use unic_langid::LanguageIdentifier;
type Customize = Option<Box<dyn FnMut(&mut FluentBundle<Arc<FluentResource>>)>>;
pub struct ArcLoaderBuilder<'a, 'b> {
location: &'a Path,
fallback: LanguageIdentifier,
shared: Option<&'b [PathBuf]>,
customize: Customize,
}
impl<'a, 'b> ArcLoaderBuilder<'a, 'b> {
pub fn shared_resources<'b2>(
self,
shared: Option<&'b2 [PathBuf]>,
) -> ArcLoaderBuilder<'a, 'b2> {
ArcLoaderBuilder {
location: self.location,
fallback: self.fallback,
shared,
customize: self.customize,
}
}
pub fn customize(
mut self,
customize: impl FnMut(&mut FluentBundle<Arc<FluentResource>>) + 'static,
) -> Self {
self.customize = Some(Box::new(customize));
self
}
pub fn build(mut self) -> Result<ArcLoader, Box<dyn std::error::Error>> {
let mut resources: HashMap<LanguageIdentifier, Vec<Arc<FluentResource>>> = HashMap::new();
for entry in read_dir(self.location)? {
let entry = entry?;
let file_type = entry.file_type()?;
if file_type.is_dir() {
if let Ok(lang) = entry.file_name().into_string() {
let lang = lang.parse::<LanguageIdentifier>()?;
let lang_resources = crate::fs::read_from_dir(entry.path())?
.into_iter()
.map(Arc::new)
.collect::<Vec<_>>();
resources.entry(lang).or_default().extend(lang_resources);
}
} else if file_type.is_file()
&& entry.path().extension().is_some_and(|e| e == "ftl")
{
if let Some(stem) = entry.path().file_stem().and_then(|s| s.to_str()) {
if let Ok(lang) = stem.parse::<LanguageIdentifier>() {
let res = Arc::new(crate::fs::read_from_file(entry.path())?);
resources.entry(lang).or_default().push(res);
}
}
}
}
let mut bundles = HashMap::new();
for (lang, v) in resources.iter() {
let mut bundle = FluentBundle::new_concurrent(vec![lang.clone()]);
for shared_resource in self.shared.unwrap_or(&[]) {
bundle
.add_resource(Arc::new(crate::fs::read_from_file(shared_resource)?))
.map_err(|errors| LoaderError::FluentBundle { errors })?;
}
for res in v {
bundle
.add_resource(res.clone())
.map_err(|errors| LoaderError::FluentBundle { errors })?;
}
if let Some(customize) = self.customize.as_mut() {
(customize)(&mut bundle);
}
bundles.insert(lang.clone(), bundle);
}
let fallbacks = super::build_fallbacks(&resources.keys().cloned().collect::<Vec<_>>());
Ok(ArcLoader {
bundles,
fallbacks,
fallback: self.fallback,
})
}
}
pub struct ArcLoader {
bundles: HashMap<LanguageIdentifier, FluentBundle<Arc<FluentResource>>>,
fallback: LanguageIdentifier,
fallbacks: HashMap<LanguageIdentifier, Vec<LanguageIdentifier>>,
}
impl super::Loader for ArcLoader {
fn lookup_complete(
&self,
lang: &LanguageIdentifier,
text_id: &str,
args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
) -> String {
for lang in negotiate_languages(&[lang], &self.bundles.keys().collect::<Vec<_>>(), None) {
if let Ok(val) = self.lookup_single_language(lang, text_id, args) {
return val;
}
}
if *lang != self.fallback {
if let Ok(val) = self.lookup_single_language(&self.fallback, text_id, args) {
return val;
}
}
format!("Unknown localization key: {text_id:?}")
}
fn try_lookup_complete(
&self,
lang: &LanguageIdentifier,
text_id: &str,
args: Option<&HashMap<Cow<'static, str>, FluentValue>>,
) -> Option<String> {
for lang in negotiate_languages(&[lang], &self.bundles.keys().collect::<Vec<_>>(), None) {
if let Ok(val) = self.lookup_single_language(lang, text_id, args) {
return Some(val);
}
}
if *lang != self.fallback {
if let Ok(val) = self.lookup_single_language(&self.fallback, text_id, args) {
return Some(val);
}
}
None
}
fn locales(&self) -> Box<dyn Iterator<Item = &LanguageIdentifier> + '_> {
Box::new(self.fallbacks.keys())
}
}
impl ArcLoader {
pub fn builder<'a, P: AsRef<Path> + ?Sized>(
location: &'a P,
fallback: LanguageIdentifier,
) -> ArcLoaderBuilder<'a, 'static> {
ArcLoaderBuilder {
location: location.as_ref(),
fallback,
shared: None,
customize: None,
}
}
pub fn lookup_single_language<T: AsRef<str>>(
&self,
lang: &LanguageIdentifier,
text_id: &str,
args: Option<&HashMap<T, FluentValue>>,
) -> Result<String, LookupError> {
super::shared::lookup_single_language(&self.bundles, lang, text_id, args)
}
pub fn lookup_no_default_fallback<S: AsRef<str>>(
&self,
lang: &LanguageIdentifier,
text_id: &str,
args: Option<&HashMap<S, FluentValue>>,
) -> Option<String> {
super::shared::lookup_no_default_fallback(
&self.bundles,
&self.fallbacks,
lang,
text_id,
args,
)
}
pub fn fallback(&self) -> &LanguageIdentifier {
&self.fallback
}
}