From a970a241dcf72db2847a5c9941fbdf2500279157 Mon Sep 17 00:00:00 2001 From: Luke Frisken Date: Sat, 14 Jan 2023 17:12:45 +1100 Subject: [PATCH] Re-implmentation of #59 (a rebase and cleanup of #88) Adds a single new method lang. This methods allows creating a shallow copy of the FluentLanguageLoader which can than be used just like the original loader but with a different current language setting. That makes it possible to use the fl! macro without any changes and is a far more elegant implementation than adding multiple get_lang* methods as done in #84. Co-authored-by: Michael P. Jung --- Cargo.lock | 7 + i18n-embed/Cargo.toml | 1 + i18n-embed/src/fluent.rs | 452 ++++++++++++++++++++----------------- i18n-embed/tests/loader.rs | 24 +- 4 files changed, 260 insertions(+), 224 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c878ad..566b9e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + [[package]] name = "atty" version = "0.2.14" @@ -440,6 +446,7 @@ dependencies = [ name = "i18n-embed" version = "0.13.5" dependencies = [ + "arc-swap", "doc-comment", "env_logger", "fluent", diff --git a/i18n-embed/Cargo.toml b/i18n-embed/Cargo.toml index af108f9..d2991f4 100644 --- a/i18n-embed/Cargo.toml +++ b/i18n-embed/Cargo.toml @@ -19,6 +19,7 @@ maintenance = { status = "actively-developed" } [dependencies] fluent = { version = "0.16", optional = true } +arc-swap = "1.6" fluent-langneg = "0.13" fluent-syntax = { version = "0.11", optional = true } gettext_system = { package = "gettext", version = "0.4", optional = true } diff --git a/i18n-embed/src/fluent.rs b/i18n-embed/src/fluent.rs index c3025f1..f0e46ba 100644 --- a/i18n-embed/src/fluent.rs +++ b/i18n-embed/src/fluent.rs @@ -7,6 +7,7 @@ use crate::{I18nAssets, I18nEmbedError, LanguageLoader}; +use arc_swap::ArcSwap; pub use i18n_embed_impl::fluent_language_loader; use fluent::{ @@ -21,25 +22,22 @@ use unic_langid::LanguageIdentifier; struct LanguageBundle { language: LanguageIdentifier, bundle: FluentBundle, IntlLangMemoizer>, - resources: Vec>, + resource: Arc, } impl LanguageBundle { - fn new(language: LanguageIdentifier, resources: Vec>) -> Self { + fn new(language: LanguageIdentifier, resource: FluentResource) -> Self { let mut bundle = FluentBundle::new_concurrent(vec![language.clone()]); - - for resource in &resources { - if let Err(errors) = bundle.add_resource(Arc::clone(resource)) { - errors.iter().for_each(|error | { - log::error!(target: "i18n_embed::fluent", "Error while adding resource to bundle: {0:?}.", error); - }) - } + let resource = Arc::new(resource); + if let Err(errors) = bundle.add_resource(resource.clone()) { + errors.iter().for_each(|error | { + log::error!(target: "i18n_embed::fluent", "Error while adding resource to bundle: {0:?}.", error); + }) } - Self { language, bundle, - resources, + resource, } } } @@ -52,13 +50,27 @@ impl Debug for LanguageBundle { #[derive(Debug)] struct LanguageConfig { - current_language: LanguageIdentifier, language_bundles: Vec, /// This maps a `LanguageIdentifier` to the index inside the /// `language_bundles` vector. language_map: HashMap, } +#[derive(Debug)] +struct CurrentLanguages { + /// Languages currently selected. + languages: Vec, + /// Indexes into the [`LanguageConfig::language_bundles`] associated the + /// currently selected [`CurrentLanguages::languages`]. + indices: Vec, +} + +#[derive(Debug)] +struct FluentLanguageLoaderInner { + language_config: Arc>, + current_languages: CurrentLanguages, +} + /// [LanguageLoader] implemenation for the `fluent` localization /// system. Also provides methods to access localizations which have /// been loaded. @@ -66,7 +78,7 @@ struct LanguageConfig { /// ⚠️ *This API requires the following crate features to be activated: `fluent-system`.* #[derive(Debug)] pub struct FluentLanguageLoader { - language_config: RwLock, + inner: ArcSwap, domain: String, fallback_language: unic_langid::LanguageIdentifier, } @@ -81,26 +93,37 @@ impl FluentLanguageLoader { fallback_language: unic_langid::LanguageIdentifier, ) -> Self { let config = LanguageConfig { - current_language: fallback_language.clone(), language_bundles: Vec::new(), language_map: HashMap::new(), }; Self { - language_config: RwLock::new(config), + inner: ArcSwap::new(Arc::new(FluentLanguageLoaderInner { + language_config: Arc::new(RwLock::new(config)), + current_languages: CurrentLanguages { + languages: vec![fallback_language.clone()], + indices: vec![], + }, + })), domain: domain.into(), fallback_language, } } - /// The languages associated with each actual loaded language bundle. + fn current_language_impl( + &self, + inner: &FluentLanguageLoaderInner, + ) -> unic_langid::LanguageIdentifier { + inner + .current_languages + .languages + .first() + .map_or_else(|| self.fallback_language.clone(), Clone::clone) + } + + /// The languages associated with each actual currently loaded language bundle. pub fn current_languages(&self) -> Vec { - self.language_config - .read() - .language_bundles - .iter() - .map(|b| b.language.clone()) - .collect() + self.inner.load().current_languages.languages.clone() } /// Get a localized message referenced by the `message_id`. @@ -124,13 +147,39 @@ impl FluentLanguageLoader { message_id: &str, args: Option<&'args FluentArgs<'args>>, ) -> String { - let language_config = self.language_config.read(); - self._get( - language_config.language_bundles.iter(), - &language_config.current_language, - message_id, - args, - ) + let inner = self.inner.load(); + let language_config = inner.language_config.read(); + inner + .current_languages + .indices + .iter() + .map(|&idx| &language_config.language_bundles[idx]) + .find_map(|language_bundle| language_bundle + .bundle + .get_message(message_id) + .and_then(|m: FluentMessage<'_>| m.value()) + .map(|pattern: &Pattern<&str>| { + let mut errors = Vec::new(); + let value = language_bundle.bundle.format_pattern(pattern, args, &mut errors); + if !errors.is_empty() { + log::error!( + target:"i18n_embed::fluent", + "Failed to format a message for language \"{}\" and id \"{}\".\nErrors\n{:?}.", + inner.current_languages.languages.first().unwrap_or(&self.fallback_language), message_id, errors + ) + } + value.into() + }) + ) + .unwrap_or_else(|| { + log::error!( + target:"i18n_embed::fluent", + "Unable to find localization for language \"{}\" and id \"{}\".", + inner.current_languages.languages.first().unwrap_or(&self.fallback_language), + message_id + ); + format!("No localization for id: \"{}\"", message_id) + }) } /// Get a localized message referenced by the `message_id`, and @@ -170,14 +219,43 @@ impl FluentLanguageLoader { attribute_id: &str, args: Option<&'args FluentArgs<'args>>, ) -> String { - let language_config = self.language_config.read(); - self._get_attribute( - language_config.language_bundles.iter(), - &language_config.current_language, - message_id, - attribute_id, - args, - ) + let inner = self.inner.load(); + let language_config = inner.language_config.read(); + let current_language = self.current_language_impl(&*inner); + + language_config.language_bundles.iter().find_map(|language_bundle| { + language_bundle + .bundle + .get_message(message_id) + .and_then(|m: FluentMessage<'_>| { + m.get_attribute(attribute_id) + .map(|a: FluentAttribute<'_>| { + a.value() + }) + }) + .map(|pattern: &Pattern<&str>| { + let mut errors = Vec::new(); + let value = language_bundle.bundle.format_pattern(pattern, args, &mut errors); + if !errors.is_empty() { + log::error!( + target:"i18n_embed::fluent", + "Failed to format a message for language \"{}\" and id \"{}\".\nErrors\n{:?}.", + current_language, message_id, errors + ) + } + value.into() + }) + }) + .unwrap_or_else(|| { + log::error!( + target:"i18n_embed::fluent", + "Unable to find localization for language \"{}\", message id \"{}\" and attribute id \"{}\".", + current_language, + message_id, + attribute_id + ); + format!("No localization for message id: \"{message_id}\" and attribute id: \"{attribute_id}\"") + }) } /// Get a localized attribute referenced by the `message_id` and `attribute_id`, and @@ -200,53 +278,46 @@ impl FluentLanguageLoader { } /// Get a localized message referenced by the `message_id`. + #[deprecated(since = "0.13.6", note = "Please use `lang(...).get(...)` instead")] pub fn get_lang(&self, lang: &[&LanguageIdentifier], message_id: &str) -> String { - self.get_lang_args_fluent(lang, message_id, None) + self.lang(lang).get(message_id) } /// A non-generic version of [FluentLanguageLoader::get_lang_args()]. + #[deprecated( + since = "0.13.6", + note = "Please use `lang(...).get_args_concrete(...)` instead" + )] pub fn get_lang_args_concrete<'source>( &self, lang: &[&LanguageIdentifier], message_id: &str, args: HashMap<&'source str, FluentValue<'source>>, ) -> String { - self.get_lang_args_fluent(lang, message_id, hash_map_to_fluent_args(args).as_ref()) + self.lang(lang).get_args_concrete(message_id, args) } /// A non-generic version of [FluentLanguageLoader::get_lang_args()] /// accepting [FluentArgs] instead of a [HashMap]. + #[deprecated( + since = "0.13.6", + note = "Please use `lang(...).get_args_fluent(...)` instead" + )] pub fn get_lang_args_fluent<'args>( &self, lang: &[&LanguageIdentifier], message_id: &str, args: Option<&'args FluentArgs<'args>>, ) -> String { - let current_language = if lang.is_empty() { - &self.fallback_language - } else { - lang[0] - }; - let fallback_language = if lang.contains(&&self.fallback_language) { - None - } else { - Some(&self.fallback_language) - }; - let config_lock = self.language_config.read(); - let language_bundles = lang - .iter() - .chain(fallback_language.as_ref().into_iter()) - .filter_map(|id| { - config_lock - .language_map - .get(id) - .map(|idx| &config_lock.language_bundles[*idx]) - }); - self._get(language_bundles, current_language, message_id, args) + self.lang(lang).get_args_fluent(message_id, args) } /// Get a localized message for the given language identifiers, referenced /// by the `message_id` and formatted with the specified `args`. + #[deprecated( + since = "0.13.6", + note = "Please use `lang(...).get_args(...)` instead" + )] pub fn get_lang_args<'a, S, V>( &self, lang: &[&LanguageIdentifier], @@ -257,21 +328,28 @@ impl FluentLanguageLoader { S: Into> + Clone, V: Into> + Clone, { - let fluent_args = hash_map_to_fluent_args(args); - self.get_lang_args_fluent(lang, id, fluent_args.as_ref()) + self.lang(lang).get_args(id, args) } /// Get a localized attribute referenced by the `Message_id` and `attribute_id`. + #[deprecated( + since = "0.13.6", + note = "Please use `lang(...).get_attr(...)` instead" + )] pub fn get_lang_attr( &self, lang: &[&LanguageIdentifier], message_id: &str, attribute_id: &str, ) -> String { - self.get_lang_attr_args_fluent(lang, message_id, attribute_id, None) + self.lang(lang).get_attr(message_id, attribute_id) } /// A non-generic version of [FluentLanguageLoader::get_lang_attr_args()]. + #[deprecated( + since = "0.13.6", + note = "Please use `lang(...).get_attr_args_concrete(...)` instead" + )] pub fn get_lang_attr_args_concrete<'source>( &self, lang: &[&LanguageIdentifier], @@ -279,16 +357,16 @@ impl FluentLanguageLoader { attribute_id: &str, args: HashMap<&'source str, FluentValue<'source>>, ) -> String { - self.get_lang_attr_args_fluent( - lang, - message_id, - attribute_id, - hash_map_to_fluent_args(args).as_ref(), - ) + self.lang(lang) + .get_attr_args_concrete(message_id, attribute_id, args) } /// A non-generic version of [FluentLanguageLoader::get_lang_attr_args()] /// accepting [FluentArgs] instead of a [HashMap]. + #[deprecated( + since = "0.13.6", + note = "Please use `lang(...).get_attr_args_fluent(...)` instead" + )] pub fn get_lang_attr_args_fluent<'args>( &self, lang: &[&LanguageIdentifier], @@ -296,37 +374,16 @@ impl FluentLanguageLoader { attribute_id: &str, args: Option<&'args FluentArgs<'args>>, ) -> String { - let current_language = if lang.is_empty() { - &self.fallback_language - } else { - lang[0] - }; - let fallback_language = if lang.contains(&&self.fallback_language) { - None - } else { - Some(&self.fallback_language) - }; - let config_lock = self.language_config.read(); - let language_bundles = lang - .iter() - .chain(fallback_language.as_ref().into_iter()) - .filter_map(|id| { - config_lock - .language_map - .get(id) - .map(|idx| &config_lock.language_bundles[*idx]) - }); - self._get_attribute( - language_bundles, - current_language, - message_id, - attribute_id, - args, - ) + self.lang(lang) + .get_attr_args_fluent(message_id, attribute_id, args) } /// Get a localized attribute referenced by the `message_id` and `attribute_id` /// and formatted with the `args`. + #[deprecated( + since = "0.13.6", + note = "Please use `lang(...).get_attr_args(...)` instead" + )] pub fn get_lang_attr_args<'a, S, V>( &self, lang: &[&LanguageIdentifier], @@ -338,98 +395,19 @@ impl FluentLanguageLoader { S: Into> + Clone, V: Into> + Clone, { - let fluent_args = hash_map_to_fluent_args(args); - self.get_lang_attr_args_fluent(lang, message_id, attribute_id, fluent_args.as_ref()) - } - - fn _get<'a, 'args>( - &'a self, - language_bundles: impl Iterator, - current_language: &LanguageIdentifier, - message_id: &str, - args: Option<&'args FluentArgs<'args>>, - ) -> String { - language_bundles.filter_map(|language_bundle| { - language_bundle - .bundle - .get_message(message_id) - .and_then(|m: FluentMessage<'_>| m.value()) - .map(|pattern: &Pattern<&str>| { - let mut errors = Vec::new(); - let value = language_bundle.bundle.format_pattern(pattern, args, &mut errors); - if !errors.is_empty() { - log::error!( - target:"i18n_embed::fluent", - "Failed to format a message for language \"{}\" and id \"{}\".\nErrors\n{:?}.", - current_language, message_id, errors - ) - } - value.into() - }) - }) - .next() - .unwrap_or_else(|| { - log::error!( - target:"i18n_embed::fluent", - "Unable to find localization for language \"{}\" and id \"{}\".", - current_language, - message_id - ); - format!("No localization for id: \"{message_id}\"") - }) - } - - fn _get_attribute<'a, 'args>( - &'a self, - language_bundles: impl Iterator, - current_language: &LanguageIdentifier, - message_id: &str, - attribute_id: &str, - args: Option<&'args FluentArgs<'args>>, - ) -> String { - language_bundles.filter_map(|language_bundle| { - language_bundle - .bundle - .get_message(message_id) - .and_then(|m: FluentMessage<'_>| { - m.get_attribute(attribute_id) - .map(|a: FluentAttribute<'_>| { - a.value() - }) - }) - .map(|pattern: &Pattern<&str>| { - let mut errors = Vec::new(); - let value = language_bundle.bundle.format_pattern(pattern, args, &mut errors); - if !errors.is_empty() { - log::error!( - target:"i18n_embed::fluent", - "Failed to format a message for language \"{}\" and id \"{}\".\nErrors\n{:?}.", - current_language, message_id, errors - ) - } - value.into() - }) - }) - .next() - .unwrap_or_else(|| { - log::error!( - target:"i18n_embed::fluent", - "Unable to find localization for language \"{}\", message id \"{}\" and attribute id \"{}\".", - current_language, - message_id, - attribute_id - ); - format!("No localization for message id: \"{message_id}\" and attribute id: \"{attribute_id}\"") - }) + self.lang(lang) + .get_attr_args(message_id, attribute_id, args) } /// available in any of the languages currently loaded (including /// the fallback language). pub fn has(&self, message_id: &str) -> bool { - let config_lock = self.language_config.read(); let mut has_message = false; - config_lock + self.inner + .load() + .language_config + .read() .language_bundles .iter() .for_each(|language_bundle| { @@ -448,9 +426,10 @@ impl FluentLanguageLoader { /// Note that this also returns false if the `message_id` could not be found; /// use [FluentLanguageLoader::has()] to determine if the `message_id` is available. pub fn has_attr(&self, message_id: &str, attribute_id: &str) -> bool { - let config_lock = self.language_config.read(); - - config_lock + self.inner + .load() + .language_config + .read() .language_bundles .iter() .find_map(|bundle| { @@ -471,9 +450,10 @@ impl FluentLanguageLoader { where C: Fn(fluent::FluentMessage<'_>) -> OUT, { - let config_lock = self.language_config.read(); - - config_lock + self.inner + .load() + .language_config + .read() .language_bundles .iter() .find_map(|language_bundle| language_bundle.bundle.get_message(message_id)) @@ -488,19 +468,21 @@ impl FluentLanguageLoader { where C: Fn(&mut dyn Iterator>) -> OUT, { - let config_lock = self.language_config.read(); + let inner = self.inner.load(); + let config_lock = inner.language_config.read(); let mut iter = config_lock .language_bundles .iter() .filter(|language_bundle| &language_bundle.language == language) .flat_map(|language_bundle| { - language_bundle.resources.iter().flat_map(|resource| { - resource.entries().filter_map(|entry| match entry { + language_bundle + .resource + .entries() + .filter_map(|entry| match entry { ast::Entry::Message(message) => Some(message), _ => None, }) - }) }); (closure)(&mut iter) @@ -528,10 +510,50 @@ impl FluentLanguageLoader { where F: Fn(&mut FluentBundle, IntlLangMemoizer>), { - for bundle in self.language_config.write().language_bundles.as_mut_slice() { + for bundle in self + .inner + .load() + .language_config + .write() + .language_bundles + .as_mut_slice() + { f(&mut bundle.bundle); } } + + /// Create a new loader with a different current language setting. + /// This is a rather cheap operation and does not require any + /// extensive copy operations. Cheap does not mean free so you + /// should not call this message repeatedly in order to translate + /// multiple strings for the same language. + pub fn lang(&self, languages: &[&LanguageIdentifier]) -> FluentLanguageLoader { + let inner = self.inner.load(); + let config_lock = inner.language_config.read(); + let fallback_language = if languages.contains(&&self.fallback_language) { + None + } else { + Some(&self.fallback_language) + }; + let indices = languages + .iter() + .cloned() + .chain(fallback_language) + .filter_map(|lang| config_lock.language_map.get(lang)) + .cloned() + .collect(); + FluentLanguageLoader { + inner: ArcSwap::new(Arc::new(FluentLanguageLoaderInner { + current_languages: CurrentLanguages { + languages: languages.iter().map(|&lang| lang.clone()).collect(), + indices, + }, + language_config: self.inner.load().language_config.clone(), + })), + domain: self.domain.clone(), + fallback_language: self.fallback_language.clone(), + } + } } impl LanguageLoader for FluentLanguageLoader { @@ -550,9 +572,9 @@ impl LanguageLoader for FluentLanguageLoader { format!("{}.ftl", self.domain()) } - /// Get the language which is currently loaded for this loader. + /// Get the language which is currently selected for this loader. fn current_language(&self) -> unic_langid::LanguageIdentifier { - self.language_config.read().current_language.clone() + self.current_language_impl(&*self.inner.load()) } /// Load the languages `language_ids` using the resources packaged @@ -566,21 +588,22 @@ impl LanguageLoader for FluentLanguageLoader { i18n_assets: &dyn I18nAssets, language_ids: &[&unic_langid::LanguageIdentifier], ) -> Result<(), I18nEmbedError> { - let current_language = *language_ids - .get(0) - .ok_or(I18nEmbedError::RequestedLanguagesEmpty)?; + if language_ids.is_empty() { + return Err(I18nEmbedError::RequestedLanguagesEmpty); + } // The languages to load - let mut load_language_ids = language_ids.to_vec(); + let mut load_language_ids: Vec = + language_ids.iter().map(|id| (**id).clone()).collect(); if !load_language_ids.contains(&&self.fallback_language) { - load_language_ids.push(&self.fallback_language); + load_language_ids.push(self.fallback_language.clone()); } let mut language_bundles = Vec::with_capacity(language_ids.len()); - for language in load_language_ids { - let (path, file) = self.language_file(language, i18n_assets); + for language in &load_language_ids { + let (path, file) = self.language_file(&language, i18n_assets); if let Some(file) = file { log::debug!(target:"i18n_embed::fluent", "Loaded language file: \"{0}\" for language: \"{1}\"", path, language); @@ -601,8 +624,7 @@ impl LanguageLoader for FluentLanguageLoader { } }; - let resources = vec![Arc::new(resource)]; - let language_bundle = LanguageBundle::new(language.clone(), resources); + let language_bundle = LanguageBundle::new(language.clone(), resource); language_bundles.push(language_bundle); } else { @@ -613,16 +635,20 @@ impl LanguageLoader for FluentLanguageLoader { } } - let mut config_lock = self.language_config.write(); - config_lock.current_language = current_language.clone(); - config_lock.language_bundles = language_bundles; - config_lock.language_map = config_lock - .language_bundles - .iter() - .enumerate() - .map(|(i, language_bundle)| (language_bundle.language.clone(), i)) - .collect(); - drop(config_lock); + self.inner.swap(Arc::new(FluentLanguageLoaderInner { + current_languages: CurrentLanguages { + languages: language_ids.iter().map(|&lang| lang.to_owned()).collect(), + indices: (0..load_language_ids.len()).collect(), + }, + language_config: Arc::new(RwLock::new(LanguageConfig { + language_map: language_bundles + .iter() + .enumerate() + .map(|(i, language_bundle)| (language_bundle.language.clone(), i)) + .collect(), + language_bundles, + })), + })); Ok(()) } diff --git a/i18n-embed/tests/loader.rs b/i18n-embed/tests/loader.rs index 83956d1..b0a1b0a 100644 --- a/i18n-embed/tests/loader.rs +++ b/i18n-embed/tests/loader.rs @@ -215,7 +215,7 @@ mod fluent { } #[test] - fn get_lang_default_fallback() { + fn lang_get_default_fallback() { setup(); let ru: LanguageIdentifier = "ru".parse().unwrap(); let en_gb: LanguageIdentifier = "en-GB".parse().unwrap(); @@ -226,15 +226,15 @@ mod fluent { .load_languages(&Localizations, &[&ru, &en_gb]) .unwrap(); - let msg = loader.get_lang(&[&ru], "only-ru"); + let msg = loader.lang(&[&ru]).get("only-ru"); assert_eq!("только русский", msg); - let msg = loader.get_lang(&[&ru], "only-gb"); + let msg = loader.lang(&[&ru]).get("only-gb"); assert_eq!("only GB (US Version)", msg); } #[test] - fn get_lang_args_default_fallback() { + fn lang_get_args_default_fallback() { setup(); let ru: LanguageIdentifier = "ru".parse().unwrap(); let en_gb: LanguageIdentifier = "en-GB".parse().unwrap(); @@ -250,7 +250,7 @@ mod fluent { "argTwo" => "2", }; - let msg = loader.get_lang_args(&[&ru], "multi-line-args", args); + let msg = loader.lang(&[&ru]).get_args("multi-line-args", args); assert_eq!( "Это многострочное сообщение с параметрами.\n\n\ \u{2068}1\u{2069}\n\n\ @@ -262,7 +262,7 @@ mod fluent { } #[test] - fn get_lang_custom_fallback() { + fn lang_get_custom_fallback() { setup(); let ru: LanguageIdentifier = "ru".parse().unwrap(); let en_gb: LanguageIdentifier = "en-GB".parse().unwrap(); @@ -273,15 +273,15 @@ mod fluent { .load_languages(&Localizations, &[&ru, &en_gb]) .unwrap(); - let msg = loader.get_lang(&[&ru, &en_gb], "only-gb"); + let msg = loader.lang(&[&ru, &en_gb]).get("only-gb"); assert_eq!("only GB", msg); - let msg = loader.get_lang(&[&ru, &en_gb], "only-us"); + let msg = loader.lang(&[&ru, &en_gb]).get("only-us"); assert_eq!("only US", msg); } #[test] - fn get_lang_args_custom_fallback() { + fn lang_get_args_custom_fallback() { setup(); let ru: LanguageIdentifier = "ru".parse().unwrap(); let en_gb: LanguageIdentifier = "en-GB".parse().unwrap(); @@ -296,10 +296,12 @@ mod fluent { "userName" => "username", }; - let msg = loader.get_lang_args(&[&ru], "only-gb-args", args.clone()); + let msg = loader.lang(&[&ru]).get_args("only-gb-args", args.clone()); assert_eq!("Hello \u{2068}username\u{2069}! (US Version)", msg); - let msg = loader.get_lang_args(&[&ru, &en_gb], "only-gb-args", args.clone()); + let msg = loader + .lang(&[&ru, &en_gb]) + .get_args("only-gb-args", args.clone()); assert_eq!("Hello \u{2068}username\u{2069}!", msg); } }