-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add FluentMultiLanguageLoader to allow easy translations access #62
Changes from 3 commits
e5281c6
a49dcf0
931a7ff
93e7a6e
3afe879
66464a9
cd93cff
7d4c07a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
hello-world = Hello World Localisation! | ||
only-gb = only GB | ||
only-gb-args = Hello {$userName}! | ||
different-args = this message has {$different} {$args} in different languages |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,16 +5,20 @@ | |
//! | ||
//! ⚠️ *This module requires the following crate features to be activated: `fluent-system`.* | ||
|
||
use crate::{I18nAssets, I18nEmbedError, LanguageLoader}; | ||
|
||
pub use i18n_embed_impl::fluent_language_loader; | ||
use std::{borrow::Cow, collections::HashMap, fmt::Debug, sync::Arc}; | ||
|
||
use fluent::{concurrent::FluentBundle, FluentArgs, FluentMessage, FluentResource, FluentValue}; | ||
use fluent_syntax::ast::{self, Pattern}; | ||
use parking_lot::RwLock; | ||
use std::{borrow::Cow, collections::HashMap, fmt::Debug, sync::Arc}; | ||
use unic_langid::LanguageIdentifier; | ||
|
||
pub use i18n_embed_impl::fluent_language_loader; | ||
pub use multi::FluentMultiLanguageLoader; | ||
|
||
use crate::{I18nAssets, I18nEmbedError, LanguageLoader}; | ||
|
||
mod multi; | ||
|
||
lazy_static::lazy_static! { | ||
static ref CURRENT_LANGUAGE: RwLock<LanguageIdentifier> = { | ||
let language = LanguageIdentifier::default(); | ||
|
@@ -112,7 +116,7 @@ impl FluentLanguageLoader { | |
pub fn get_args_concrete<'source>( | ||
&self, | ||
message_id: &str, | ||
args: HashMap<&'source str, FluentValue<'source>>, | ||
args: HashMap<Cow<'source, str>, FluentValue<'source>>, | ||
) -> String { | ||
let args_option = if args.is_empty() { | ||
None | ||
|
@@ -176,28 +180,7 @@ impl FluentLanguageLoader { | |
S: Into<Cow<'a, str>> + Clone, | ||
V: Into<FluentValue<'a>> + Clone, | ||
{ | ||
let mut keys: Vec<Cow<'a, str>> = Vec::new(); | ||
|
||
let mut map: HashMap<&str, FluentValue<'_>> = HashMap::with_capacity(args.len()); | ||
|
||
let mut values = Vec::new(); | ||
|
||
for (key, value) in args.into_iter() { | ||
keys.push(key.into()); | ||
values.push(value.into()); | ||
} | ||
|
||
for (i, key) in keys.iter().rev().enumerate() { | ||
let value = values.pop().unwrap_or_else(|| { | ||
panic!( | ||
"expected a value corresponding with key \"{}\" at position {}", | ||
key, i | ||
) | ||
}); | ||
|
||
map.insert(&*key, value); | ||
} | ||
|
||
let map = prepare_args_map(args); | ||
self.get_args_concrete(id, map) | ||
} | ||
|
||
|
@@ -329,40 +312,10 @@ impl LanguageLoader for FluentLanguageLoader { | |
} | ||
|
||
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); | ||
|
||
if let Some(file) = file { | ||
log::debug!(target:"i18n_embed::fluent", "Loaded language file: \"{0}\" for language: \"{1}\"", path, language); | ||
|
||
let file_string = String::from_utf8(file.to_vec()) | ||
.map_err(|err| I18nEmbedError::ErrorParsingFileUtf8(path.clone(), err))? | ||
// TODO: Workaround for https://github.com/kellpossible/cargo-i18n/issues/57 | ||
// remove when https://github.com/projectfluent/fluent-rs/issues/213 is resolved. | ||
.replace("\u{000D}\n", "\n"); | ||
|
||
let resource = match FluentResource::try_new(file_string) { | ||
Ok(resource) => resource, | ||
Err((resource, errors)) => { | ||
errors.iter().for_each(|err| { | ||
log::error!(target: "i18n_embed::fluent", "Error while parsing fluent language file \"{0}\": \"{1:?}\".", path, err); | ||
}); | ||
resource | ||
} | ||
}; | ||
|
||
let mut resources = Vec::new(); | ||
resources.push(Arc::new(resource)); | ||
let language_bundle = LanguageBundle::new(language.clone(), resources); | ||
|
||
language_bundles.push(language_bundle); | ||
} else { | ||
log::debug!(target:"i18n_embed::fluent", "Unable to find language file: \"{0}\" for language: \"{1}\"", path, language); | ||
if language == &self.fallback_language { | ||
return Err(I18nEmbedError::LanguageNotAvailable(path, language.clone())); | ||
} | ||
} | ||
let fluent_bundle = | ||
files_to_fluent_bundle(self, i18n_assets, language, &self.fallback_language)?; | ||
kellpossible marked this conversation as resolved.
Show resolved
Hide resolved
|
||
language_bundles.push(fluent_bundle); | ||
} | ||
|
||
let mut config_lock = self.language_config.write(); | ||
|
@@ -373,3 +326,68 @@ impl LanguageLoader for FluentLanguageLoader { | |
Ok(()) | ||
} | ||
} | ||
|
||
/// Retrieve and load the file for the designated `LanguageIdentifier` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see the unused variable has been removed but I haven't spent the time to see how the error logic has been altered as a result of this. |
||
fn files_to_fluent_bundle( | ||
loader: &dyn LanguageLoader, | ||
i18n_assets: &dyn I18nAssets, | ||
language: &LanguageIdentifier, | ||
fallback_language: &LanguageIdentifier, | ||
) -> Result<LanguageBundle, I18nEmbedError> { | ||
let (path, file) = loader.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); | ||
|
||
let file_string = String::from_utf8(file.to_vec()) | ||
.map_err(|err| I18nEmbedError::ErrorParsingFileUtf8(path.clone(), err))? | ||
// TODO: Workaround for https://github.com/kellpossible/cargo-i18n/issues/57 | ||
// remove when https://github.com/projectfluent/fluent-rs/issues/213 is resolved. | ||
.replace("\u{000D}\n", "\n"); | ||
|
||
let resource = match FluentResource::try_new(file_string) { | ||
Ok(resource) => resource, | ||
Err((resource, errors)) => { | ||
errors.iter().for_each(|err| { | ||
log::error!(target: "i18n_embed::fluent", "Error while parsing fluent language file \"{0}\": \"{1:?}\".", path, err); | ||
}); | ||
resource | ||
} | ||
}; | ||
|
||
let mut resources = Vec::new(); | ||
resources.push(Arc::new(resource)); | ||
Ok(LanguageBundle::new(language.clone(), resources)) | ||
} else { | ||
log::debug!(target:"i18n_embed::fluent", "Unable to find language file: \"{0}\" for language: \"{1}\"", path, language); | ||
kellpossible marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Err(I18nEmbedError::LanguageNotAvailable(path, language.clone())) | ||
} | ||
} | ||
|
||
/// Transforms a regular hashmap into Fluent ready args map. | ||
fn prepare_args_map<'a, S, V>(args: HashMap<S, V>) -> HashMap<Cow<'a, str>, FluentValue<'a>> | ||
where | ||
S: Into<Cow<'a, str>> + Clone, | ||
V: Into<FluentValue<'a>> + Clone, | ||
{ | ||
let mut keys: Vec<Cow<'a, str>> = Vec::new(); | ||
let mut map: HashMap<Cow<'a, str>, FluentValue<'_>> = HashMap::with_capacity(args.len()); | ||
let mut values = Vec::new(); | ||
|
||
for (key, value) in args.into_iter() { | ||
keys.push(key.into()); | ||
values.push(value.into()); | ||
} | ||
|
||
for (i, key) in keys.into_iter().rev().enumerate() { | ||
let value = values.pop().unwrap_or_else(|| { | ||
panic!( | ||
"expected a value corresponding with key \"{}\" at position {}", | ||
key, i | ||
) | ||
}); | ||
|
||
map.insert(key, value); | ||
} | ||
map | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change (see fl_macro failing unit test), was there are particular motivation for using
Cow
over&str
?