fix: automatically reconnect after losing a WebSocket connection
Some checks failed
hadolint check / hadolint check (pull_request) Successful in 13s
actionlint check / actionlint check (pull_request) Successful in 7s
conventional pull request title check / conventional pull request title check (pull_request) Successful in 3s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 6s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 7s
GitLeaks check / GitLeaks check (pull_request) Successful in 13s
markdownlint check / markdownlint check (pull_request) Failing after 54s
Prettier check / Prettier check (pull_request) Failing after 51s
htmlhint check / htmlhint check (pull_request) Successful in 1m3s
checkov check / checkov check (pull_request) Failing after 2m26s
ShellCheck check / ShellCheck check (pull_request) Successful in 1m14s
Stylelint check / Stylelint check (pull_request) Successful in 1m27s
Rust check / Rust check (pull_request) Failing after 11m40s
yamllint check / yamllint check (pull_request) Successful in 13m36s
Some checks failed
hadolint check / hadolint check (pull_request) Successful in 13s
actionlint check / actionlint check (pull_request) Successful in 7s
conventional pull request title check / conventional pull request title check (pull_request) Successful in 3s
conventional commit messages check / conventional commit messages check (pull_request) Successful in 6s
dotenv-linter check / dotenv-linter check (pull_request) Successful in 7s
GitLeaks check / GitLeaks check (pull_request) Successful in 13s
markdownlint check / markdownlint check (pull_request) Failing after 54s
Prettier check / Prettier check (pull_request) Failing after 51s
htmlhint check / htmlhint check (pull_request) Successful in 1m3s
checkov check / checkov check (pull_request) Failing after 2m26s
ShellCheck check / ShellCheck check (pull_request) Successful in 1m14s
Stylelint check / Stylelint check (pull_request) Successful in 1m27s
Rust check / Rust check (pull_request) Failing after 11m40s
yamllint check / yamllint check (pull_request) Successful in 13m36s
This commit is contained in:
703
dioxus-i18n/src/use_i18n.rs
Normal file
703
dioxus-i18n/src/use_i18n.rs
Normal file
@@ -0,0 +1,703 @@
|
||||
use super::error::Error;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use fluent::{FluentArgs, FluentBundle, FluentResource};
|
||||
use unic_langid::LanguageIdentifier;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// `Locale` is a "place-holder" around what will eventually be a `fluent::FluentBundle`
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct Locale {
|
||||
id: LanguageIdentifier,
|
||||
resource: LocaleResource,
|
||||
}
|
||||
|
||||
impl Locale {
|
||||
pub fn new_static(id: LanguageIdentifier, str: &'static str) -> Self {
|
||||
Self {
|
||||
id,
|
||||
resource: LocaleResource::Static(str),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_dynamic(id: LanguageIdentifier, path: impl Into<PathBuf>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
resource: LocaleResource::Path(path.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<(LanguageIdentifier, T)> for Locale
|
||||
where
|
||||
T: Into<LocaleResource>,
|
||||
{
|
||||
fn from((id, resource): (LanguageIdentifier, T)) -> Self {
|
||||
let resource = resource.into();
|
||||
Self { id, resource }
|
||||
}
|
||||
}
|
||||
|
||||
/// A `LocaleResource` can be static text, or a filesystem file (not supported in WASM).
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum LocaleResource {
|
||||
Static(&'static str),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Path(PathBuf),
|
||||
}
|
||||
|
||||
impl LocaleResource {
|
||||
pub fn try_to_resource_string(&self) -> Result<String, Error> {
|
||||
match self {
|
||||
Self::Static(str) => Ok(str.to_string()),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
Self::Path(path) => std::fs::read_to_string(path)
|
||||
.map_err(|e| Error::LocaleResourcePathReadFailed(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_resource_string(&self) -> String {
|
||||
let result = self.try_to_resource_string();
|
||||
match result {
|
||||
Ok(string) => string,
|
||||
Err(err) => panic!("failed to create resource string {:?}: {}", self, err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for LocaleResource {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self::Static(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl From<PathBuf> for LocaleResource {
|
||||
fn from(value: PathBuf) -> Self {
|
||||
Self::Path(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration for `I18n`.
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct I18nConfig {
|
||||
/// The initial language, can be later changed with [`I18n::set_language`]
|
||||
id: LanguageIdentifier,
|
||||
|
||||
/// The final fallback language if no other locales are found for `id`.
|
||||
/// A `Locale` must exist in `locales' if `fallback` is defined.
|
||||
fallback: Option<LanguageIdentifier>,
|
||||
|
||||
/// The locale_resources added to the configuration.
|
||||
locale_resources: Vec<LocaleResource>,
|
||||
|
||||
/// The locales added to the configuration.
|
||||
locales: HashMap<LanguageIdentifier, usize>,
|
||||
}
|
||||
|
||||
impl I18nConfig {
|
||||
/// Create an i18n config with the selected [LanguageIdentifier].
|
||||
pub fn new(id: LanguageIdentifier) -> Self {
|
||||
Self {
|
||||
id,
|
||||
fallback: None,
|
||||
locale_resources: Vec::new(),
|
||||
locales: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a fallback [LanguageIdentifier].
|
||||
pub fn with_fallback(mut self, fallback: LanguageIdentifier) -> Self {
|
||||
self.fallback = Some(fallback);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add [Locale].
|
||||
/// It is possible to share locales resources. If this locale's resource
|
||||
/// matches a previously added one, then this locale will use the existing one.
|
||||
/// This is primarily for the static locale_resources to avoid string duplication.
|
||||
pub fn with_locale<T>(mut self, locale: T) -> Self
|
||||
where
|
||||
T: Into<Locale>,
|
||||
{
|
||||
let locale = locale.into();
|
||||
let locale_resources_len = self.locale_resources.len();
|
||||
|
||||
let index = self
|
||||
.locale_resources
|
||||
.iter()
|
||||
.position(|r| *r == locale.resource)
|
||||
.unwrap_or(locale_resources_len);
|
||||
|
||||
if index == locale_resources_len {
|
||||
self.locale_resources.push(locale.resource)
|
||||
};
|
||||
|
||||
self.locales.insert(locale.id, index);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple locales from given folder, based on their filename.
|
||||
///
|
||||
/// If the path represents a folder, then the folder will be deep traversed for
|
||||
/// all '*.ftl' files. If the filename represents a [LanguageIdentifier] then it
|
||||
/// will be added to the config.
|
||||
///
|
||||
/// If the path represents a file, then the filename must represent a
|
||||
/// unic_langid::LanguageIdentifier for it to be added to the config.
|
||||
///
|
||||
/// The method is not available for `wasm32` builds.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn try_with_auto_locales(self, path: PathBuf) -> Result<Self, Error> {
|
||||
if path.is_dir() {
|
||||
let files = find_ftl_files(&path)?;
|
||||
files
|
||||
.into_iter()
|
||||
.try_fold(self, |acc, file| acc.with_auto_pathbuf(file))
|
||||
} else if is_ftl_file(&path) {
|
||||
self.with_auto_pathbuf(path)
|
||||
} else {
|
||||
Err(Error::InvalidPath(path.to_string_lossy().to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn with_auto_pathbuf(self, file: PathBuf) -> Result<Self, Error> {
|
||||
assert!(is_ftl_file(&file));
|
||||
|
||||
let stem = file.file_stem().ok_or_else(|| {
|
||||
Error::InvalidLanguageId(format!("No file stem: '{}'", file.display()))
|
||||
})?;
|
||||
|
||||
let id_str = stem.to_str().ok_or_else(|| {
|
||||
Error::InvalidLanguageId(format!("Cannot convert: {}", stem.to_string_lossy()))
|
||||
})?;
|
||||
|
||||
let id = LanguageIdentifier::from_bytes(id_str.as_bytes())
|
||||
.map_err(|e| Error::InvalidLanguageId(e.to_string()))?;
|
||||
|
||||
Ok(self.with_locale((id, file)))
|
||||
}
|
||||
|
||||
/// Add multiple locales from given folder, based on their filename.
|
||||
///
|
||||
/// Will panic! on error.
|
||||
///
|
||||
/// The method is not available for `wasm32` builds.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn with_auto_locales(self, path: PathBuf) -> Self {
|
||||
let path_name = path.display().to_string();
|
||||
let result = self.try_with_auto_locales(path);
|
||||
match result {
|
||||
Ok(result) => result,
|
||||
Err(err) => panic!(
|
||||
"with_auto_locales must have valid pathbuf {}: {}",
|
||||
path_name, err
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn find_ftl_files(folder: &PathBuf) -> Result<Vec<PathBuf>, Error> {
|
||||
let ftl_files: Vec<PathBuf> = WalkDir::new(folder)
|
||||
.into_iter()
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| is_ftl_file(entry.path()))
|
||||
.map(|entry| entry.path().to_path_buf())
|
||||
.collect();
|
||||
|
||||
Ok(ftl_files)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn is_ftl_file(entry: &Path) -> bool {
|
||||
entry.is_file() && entry.extension().map(|ext| ext == "ftl").unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Initialize an i18n provider.
|
||||
pub fn try_use_init_i18n(init: impl FnOnce() -> I18nConfig) -> Result<I18n, Error> {
|
||||
use_context_provider(move || {
|
||||
// Coverage false -ve: See https://github.com/xd009642/tarpaulin/issues/1675
|
||||
let I18nConfig {
|
||||
id,
|
||||
fallback,
|
||||
locale_resources,
|
||||
locales,
|
||||
} = init();
|
||||
|
||||
I18n::try_new(id, fallback, locale_resources, locales)
|
||||
})
|
||||
}
|
||||
|
||||
/// Initialize an i18n provider.
|
||||
pub fn use_init_i18n(init: impl FnOnce() -> I18nConfig) -> I18n {
|
||||
use_context_provider(move || {
|
||||
// Coverage false -ve: See https://github.com/xd009642/tarpaulin/issues/1675
|
||||
let I18nConfig {
|
||||
id,
|
||||
fallback,
|
||||
locale_resources,
|
||||
locales,
|
||||
} = init();
|
||||
|
||||
match I18n::try_new(id, fallback, locale_resources, locales) {
|
||||
Ok(i18n) => i18n,
|
||||
Err(e) => panic!("Failed to create I18n context: {}", e),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct I18n {
|
||||
selected_language: Signal<LanguageIdentifier>,
|
||||
fallback_language: Signal<Option<LanguageIdentifier>>,
|
||||
locale_resources: Signal<Vec<LocaleResource>>,
|
||||
locales: Signal<HashMap<LanguageIdentifier, usize>>,
|
||||
active_bundle: Signal<FluentBundle<FluentResource>>,
|
||||
}
|
||||
|
||||
impl I18n {
|
||||
pub fn try_new(
|
||||
selected_language: LanguageIdentifier,
|
||||
fallback_language: Option<LanguageIdentifier>,
|
||||
locale_resources: Vec<LocaleResource>,
|
||||
locales: HashMap<LanguageIdentifier, usize>,
|
||||
) -> Result<Self, Error> {
|
||||
let bundle = try_create_bundle(
|
||||
&selected_language,
|
||||
&fallback_language,
|
||||
&locale_resources,
|
||||
&locales,
|
||||
)?;
|
||||
Ok(Self {
|
||||
selected_language: Signal::new(selected_language),
|
||||
fallback_language: Signal::new(fallback_language),
|
||||
locale_resources: Signal::new(locale_resources),
|
||||
locales: Signal::new(locales),
|
||||
active_bundle: Signal::new(bundle),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
selected_language: LanguageIdentifier,
|
||||
fallback_language: Option<LanguageIdentifier>,
|
||||
locale_resources: Vec<LocaleResource>,
|
||||
locales: HashMap<LanguageIdentifier, usize>,
|
||||
) -> Self {
|
||||
let result = Self::try_new(
|
||||
selected_language,
|
||||
fallback_language,
|
||||
locale_resources,
|
||||
locales,
|
||||
);
|
||||
match result {
|
||||
Ok(i18n) => i18n,
|
||||
Err(err) => panic!("I18n cannot be created: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_translate_with_args(
|
||||
&self,
|
||||
msg: &str,
|
||||
args: Option<&FluentArgs>,
|
||||
) -> Result<String, Error> {
|
||||
let (message_id, attribute_name) = Self::decompose_identifier(msg)?;
|
||||
|
||||
let bundle = self.active_bundle.read();
|
||||
|
||||
let message = bundle
|
||||
.get_message(message_id)
|
||||
.ok_or_else(|| Error::MessageIdNotFound(message_id.into()))?;
|
||||
|
||||
let pattern = if let Some(attribute_name) = attribute_name {
|
||||
let attribute = message
|
||||
.get_attribute(attribute_name)
|
||||
.ok_or_else(|| Error::AttributeIdNotFound(msg.to_string()))?;
|
||||
attribute.value()
|
||||
} else {
|
||||
message
|
||||
.value()
|
||||
.ok_or_else(|| Error::MessagePatternNotFound(message_id.into()))?
|
||||
};
|
||||
|
||||
let mut errors = vec![];
|
||||
let translation = bundle
|
||||
.format_pattern(pattern, args, &mut errors)
|
||||
.to_string();
|
||||
|
||||
(errors.is_empty())
|
||||
.then_some(translation)
|
||||
.ok_or_else(|| Error::FluentErrorsDetected(format!("{:#?}", errors)))
|
||||
}
|
||||
|
||||
pub fn decompose_identifier(msg: &str) -> Result<(&str, Option<&str>), Error> {
|
||||
let parts: Vec<&str> = msg.split('.').collect();
|
||||
match parts.as_slice() {
|
||||
[message_id] => Ok((message_id, None)),
|
||||
[message_id, attribute_name] => Ok((message_id, Some(attribute_name))),
|
||||
_ => Err(Error::InvalidMessageId(msg.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate_with_args(&self, msg: &str, args: Option<&FluentArgs>) -> String {
|
||||
let result = self.try_translate_with_args(msg, args);
|
||||
match result {
|
||||
Ok(translation) => translation,
|
||||
Err(err) => panic!("Failed to translate {}: {}", msg, err),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn try_translate(&self, msg: &str) -> Result<String, Error> {
|
||||
self.try_translate_with_args(msg, None)
|
||||
}
|
||||
|
||||
pub fn translate(&self, msg: &str) -> String {
|
||||
let result = self.try_translate(msg);
|
||||
match result {
|
||||
Ok(translation) => translation,
|
||||
Err(err) => panic!("Failed to translate {}: {}", msg, err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the selected language.
|
||||
#[inline]
|
||||
pub fn language(&self) -> LanguageIdentifier {
|
||||
self.selected_language.read().clone()
|
||||
}
|
||||
|
||||
/// Get the fallback language.
|
||||
pub fn fallback_language(&self) -> Option<LanguageIdentifier> {
|
||||
self.fallback_language.read().clone()
|
||||
}
|
||||
|
||||
/// Update the selected language.
|
||||
pub fn try_set_language(&mut self, id: LanguageIdentifier) -> Result<(), Error> {
|
||||
*self.selected_language.write() = id;
|
||||
self.try_update_active_bundle()
|
||||
}
|
||||
|
||||
/// Update the selected language.
|
||||
pub fn set_language(&mut self, id: LanguageIdentifier) {
|
||||
let id_name = id.to_string();
|
||||
let result = self.try_set_language(id);
|
||||
match result {
|
||||
Ok(()) => (),
|
||||
Err(err) => panic!("cannot set language {}: {}", id_name, err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the fallback language.
|
||||
pub fn try_set_fallback_language(&mut self, id: LanguageIdentifier) -> Result<(), Error> {
|
||||
self.locales
|
||||
.read()
|
||||
.get(&id)
|
||||
.ok_or_else(|| Error::FallbackMustHaveLocale(id.to_string()))?;
|
||||
|
||||
*self.fallback_language.write() = Some(id);
|
||||
self.try_update_active_bundle()
|
||||
}
|
||||
|
||||
/// Update the fallback language.
|
||||
pub fn set_fallback_language(&mut self, id: LanguageIdentifier) {
|
||||
let id_name = id.to_string();
|
||||
let result = self.try_set_fallback_language(id);
|
||||
match result {
|
||||
Ok(()) => (),
|
||||
Err(err) => panic!("cannot set fallback language {}: {}", id_name, err),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_update_active_bundle(&mut self) -> Result<(), Error> {
|
||||
let bundle = try_create_bundle(
|
||||
&self.selected_language.peek(),
|
||||
&self.fallback_language.peek(),
|
||||
&self.locale_resources.peek(),
|
||||
&self.locales.peek(),
|
||||
)?;
|
||||
|
||||
self.active_bundle.set(bundle);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn try_create_bundle(
|
||||
selected_language: &LanguageIdentifier,
|
||||
fallback_language: &Option<LanguageIdentifier>,
|
||||
locale_resources: &[LocaleResource],
|
||||
locales: &HashMap<LanguageIdentifier, usize>,
|
||||
) -> Result<FluentBundle<FluentResource>, Error> {
|
||||
let add_resource = move |bundle: &mut FluentBundle<FluentResource>,
|
||||
langid: &LanguageIdentifier,
|
||||
locale_resources: &[LocaleResource]| {
|
||||
if let Some(&i) = locales.get(langid) {
|
||||
let resource = &locale_resources[i];
|
||||
let resource =
|
||||
FluentResource::try_new(resource.try_to_resource_string()?).map_err(|e| {
|
||||
Error::FluentErrorsDetected(format!("resource langid: {}\n{:#?}", langid, e))
|
||||
})?;
|
||||
bundle.add_resource_overriding(resource);
|
||||
};
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let mut bundle = FluentBundle::new(vec![selected_language.clone()]);
|
||||
if let Some(fallback_language) = fallback_language {
|
||||
add_resource(&mut bundle, fallback_language, locale_resources)?;
|
||||
}
|
||||
|
||||
let (language, script, region, variants) = selected_language.clone().into_parts();
|
||||
let variants_lang = LanguageIdentifier::from_parts(language, script, region, &variants);
|
||||
let region_lang = LanguageIdentifier::from_parts(language, script, region, &[]);
|
||||
let script_lang = LanguageIdentifier::from_parts(language, script, None, &[]);
|
||||
let language_lang = LanguageIdentifier::from_parts(language, None, None, &[]);
|
||||
|
||||
add_resource(&mut bundle, &language_lang, locale_resources)?;
|
||||
add_resource(&mut bundle, &script_lang, locale_resources)?;
|
||||
add_resource(&mut bundle, ®ion_lang, locale_resources)?;
|
||||
add_resource(&mut bundle, &variants_lang, locale_resources)?;
|
||||
|
||||
/* Add this code when the fluent crate includes FluentBundle::add_builtins.
|
||||
* This will allow the use of built-in functions like `NUMBER` and `DATETIME`.
|
||||
* See [Fluent issue](https://github.com/projectfluent/fluent-rs/issues/181) for more information.
|
||||
bundle
|
||||
.add_builtins()
|
||||
.map_err(|e| Error::FluentErrorsDetected(e.to_string()))?;
|
||||
*/
|
||||
|
||||
Ok(bundle)
|
||||
}
|
||||
|
||||
pub fn i18n() -> I18n {
|
||||
consume_context()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use unic_langid::langid;
|
||||
|
||||
#[test]
|
||||
fn can_add_locale_to_config_explicit_locale() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||
|
||||
let config = I18nConfig::new(LANG_A)
|
||||
.with_locale(Locale::new_static(LANG_B, "lang = lang_b"))
|
||||
.with_locale(Locale::new_dynamic(LANG_C, PathBuf::new()));
|
||||
|
||||
assert_eq!(
|
||||
config,
|
||||
I18nConfig {
|
||||
id: LANG_A,
|
||||
fallback: None,
|
||||
locale_resources: vec![
|
||||
LocaleResource::Static("lang = lang_b"),
|
||||
LocaleResource::Path(PathBuf::new()),
|
||||
],
|
||||
locales: HashMap::from([(LANG_B, 0), (LANG_C, 1)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_add_locale_to_config_implicit_locale() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||
|
||||
let config = I18nConfig::new(LANG_A)
|
||||
.with_locale((LANG_B, "lang = lang_b"))
|
||||
.with_locale((LANG_C, PathBuf::new()));
|
||||
|
||||
assert_eq!(
|
||||
config,
|
||||
I18nConfig {
|
||||
id: LANG_A,
|
||||
fallback: None,
|
||||
locale_resources: vec![
|
||||
LocaleResource::Static("lang = lang_b"),
|
||||
LocaleResource::Path(PathBuf::new())
|
||||
],
|
||||
locales: HashMap::from([(LANG_B, 0), (LANG_C, 1)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_add_locale_string_to_config() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||
|
||||
let config = I18nConfig::new(LANG_A).with_locale((LANG_B, "lang = lang_b"));
|
||||
|
||||
assert_eq!(
|
||||
config,
|
||||
I18nConfig {
|
||||
id: LANG_A,
|
||||
fallback: None,
|
||||
locale_resources: vec![LocaleResource::Static("lang = lang_b")],
|
||||
locales: HashMap::from([(LANG_B, 0)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_add_shared_locale_string_to_config() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||
|
||||
let shared_string = "lang = a language";
|
||||
let config = I18nConfig::new(LANG_A)
|
||||
.with_locale((LANG_B, shared_string))
|
||||
.with_locale((LANG_C, shared_string));
|
||||
|
||||
assert_eq!(
|
||||
config,
|
||||
I18nConfig {
|
||||
id: LANG_A,
|
||||
fallback: None,
|
||||
locale_resources: vec![LocaleResource::Static(shared_string)],
|
||||
locales: HashMap::from([(LANG_B, 0), (LANG_C, 0)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_add_locale_pathbuf_to_config() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||
|
||||
let config = I18nConfig::new(LANG_A)
|
||||
.with_locale((LANG_C, PathBuf::from("./test/data/fallback/la.ftl")));
|
||||
|
||||
assert_eq!(
|
||||
config,
|
||||
I18nConfig {
|
||||
id: LANG_A,
|
||||
fallback: None,
|
||||
locale_resources: vec![LocaleResource::Path(PathBuf::from(
|
||||
"./test/data/fallback/la.ftl"
|
||||
))],
|
||||
locales: HashMap::from([(LANG_C, 0)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_add_shared_locale_pathbuf_to_config() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
const LANG_B: LanguageIdentifier = langid!("la-LB");
|
||||
const LANG_C: LanguageIdentifier = langid!("la-LC");
|
||||
|
||||
let shared_pathbuf = PathBuf::from("./test/data/fallback/la.ftl");
|
||||
|
||||
let config = I18nConfig::new(LANG_A)
|
||||
.with_locale((LANG_B, shared_pathbuf.clone()))
|
||||
.with_locale((LANG_C, shared_pathbuf.clone()));
|
||||
|
||||
assert_eq!(
|
||||
config,
|
||||
I18nConfig {
|
||||
id: LANG_A,
|
||||
fallback: None,
|
||||
locale_resources: vec![LocaleResource::Path(shared_pathbuf)],
|
||||
locales: HashMap::from([(LANG_B, 0), (LANG_C, 0)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_auto_add_locales_folder_to_config() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
|
||||
let root_path_str = &format!("{}/tests/data/fallback/", env!("CARGO_MANIFEST_DIR"));
|
||||
let pathbuf = PathBuf::from(root_path_str);
|
||||
|
||||
let config = I18nConfig::new(LANG_A)
|
||||
.try_with_auto_locales(pathbuf)
|
||||
.ok()
|
||||
.unwrap();
|
||||
|
||||
let expected_locales = [
|
||||
"fb-FB",
|
||||
"la",
|
||||
"la-Scpt",
|
||||
"la-Scpt-LA",
|
||||
"la-Scpt-LA-variants",
|
||||
];
|
||||
|
||||
assert_eq!(config.locales.len(), expected_locales.len());
|
||||
assert_eq!(config.locale_resources.len(), expected_locales.len());
|
||||
|
||||
expected_locales.into_iter().for_each(|l| {
|
||||
let expected_filename = format!("{root_path_str}/{l}.ftl");
|
||||
let id = LanguageIdentifier::from_bytes(l.as_bytes()).unwrap();
|
||||
assert!(config.locales.get(&id).is_some());
|
||||
assert!(config
|
||||
.locale_resources
|
||||
.contains(&LocaleResource::Path(PathBuf::from(expected_filename))));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_auto_add_locales_file_to_config() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
|
||||
let path_str = &format!(
|
||||
"{}/tests/data/fallback/fb-FB.ftl",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
let pathbuf = PathBuf::from(path_str);
|
||||
|
||||
let config = I18nConfig::new(LANG_A)
|
||||
.try_with_auto_locales(pathbuf.clone())
|
||||
.ok()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(config.locales.len(), 1);
|
||||
assert!(config.locales.get(&langid!("fb-FB")).is_some());
|
||||
|
||||
assert_eq!(config.locale_resources.len(), 1);
|
||||
assert!(config
|
||||
.locale_resources
|
||||
.contains(&LocaleResource::Path(pathbuf)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn will_fail_auto_locales_with_invalid_folder() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
|
||||
let root_path_str = &format!("{}/non_existing_path/", env!("CARGO_MANIFEST_DIR"));
|
||||
let pathbuf = PathBuf::from(root_path_str);
|
||||
|
||||
let config = I18nConfig::new(LANG_A).try_with_auto_locales(pathbuf);
|
||||
assert_eq!(config.is_err(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn will_fail_auto_locales_with_invalid_file() {
|
||||
const LANG_A: LanguageIdentifier = langid!("la-LA");
|
||||
|
||||
let path_str = &format!(
|
||||
"{}/tests/data/fallback/invalid_language_id.ftl",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
);
|
||||
let pathbuf = PathBuf::from(path_str);
|
||||
|
||||
let config = I18nConfig::new(LANG_A).try_with_auto_locales(pathbuf);
|
||||
assert_eq!(config.is_err(), true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user