#![doc = include_str!("../YEW.md")]
use crate::config::{I18n, I18nConfig, StorageType};
use std::collections::HashMap;
#[cfg(target_arch = "wasm32")]
use web_sys::window;
use yew::prelude::*;
#[derive(Debug, Clone, PartialEq, Properties, Default)]
pub struct I18nProviderConfig {
#[prop_or_default]
pub translations: HashMap<&'static str, &'static str>,
pub children: Html,
#[prop_or_default]
pub storage_type: StorageType,
#[prop_or("i18nrs".to_string())]
pub storage_name: String,
#[prop_or("en".to_string())]
pub default_language: String,
#[prop_or_default]
pub onchange: Callback<String>,
#[prop_or_default]
pub onerror: Callback<String>,
}
#[function_component(I18nProvider)]
pub fn i18n_provider(props: &I18nProviderConfig) -> Html {
let initial_language = get_initial_language(&props.storage_type, &props.storage_name)
.unwrap_or_else(|| Some(props.default_language.clone()));
#[cfg(target_arch = "wasm32")]
let is_rtl_language =
|lang: &str| matches!(lang, "ar" | "he" | "fa" | "ur" | "ps" | "ku" | "sd");
let update_text_direction = move |_lang: &str| {
#[cfg(target_arch = "wasm32")]
{
if let Some(document) = window().and_then(|win| win.document()) {
let dir = if is_rtl_language(_lang) { "rtl" } else { "ltr" };
if let Some(html_element) = document.document_element() {
let _ = html_element.set_attribute("dir", dir);
}
}
}
};
update_text_direction(&initial_language.clone().unwrap_or_else(|| "en".to_string()));
let i18n = I18n::new(
I18nConfig {
translations: props.translations.clone(),
},
props.translations.clone(),
)
.map(|mut instance| {
if let Err(err) = instance.set_translation_language(
&initial_language.clone().unwrap_or_default(),
&props.storage_type,
&props.storage_name,
) {
props.onerror.emit(err);
}
instance
})
.unwrap_or_else(|err| {
props.onerror.emit(err.clone());
panic!("Failed to initialize I18n: {}", err);
});
let ctx = use_state(|| i18n);
let onchange = props.onchange.clone();
let storage_type = props.storage_type.clone();
let storage_name = props.storage_name.clone();
let set_language = {
let ctx = ctx.clone();
Callback::from(move |language: String| {
let mut i18n = (*ctx).clone();
update_text_direction(&language);
if i18n
.set_translation_language(&language, &storage_type, &storage_name)
.is_ok()
{
ctx.set(i18n);
onchange.emit(language);
}
})
};
html! {
<ContextProvider<I18n> context={(*ctx).clone()}>
<ContextProvider<Callback<String>> context={set_language}>
{ props.children.clone() }
</ContextProvider<Callback<String>>>
</ContextProvider<I18n>>
}
}
#[hook]
pub fn use_translation() -> (I18n, Callback<String>) {
let i18n = use_context::<I18n>().expect("No I18n context provided");
let set_language = use_context::<Callback<String>>().expect("No set_language context found");
(i18n, set_language)
}
fn get_initial_language(_storage_type: &StorageType, _key: &str) -> Option<Option<String>> {
#[cfg(target_arch = "wasm32")]
{
let value: Option<String> = match _storage_type {
StorageType::LocalStorage => window()
.expect("No window object")
.local_storage()
.expect("Failed to access localStorage")
.and_then(|s| s.get_item(_key).ok())
.expect("Stored language not found in localStorage"),
StorageType::SessionStorage => window()
.expect("No window object")
.session_storage()
.expect("Failed to access sessionStorage")
.and_then(|s| s.get_item(_key).ok())
.expect("Stored language not found in sessionStorage"),
};
Some(value)
}
#[cfg(not(target_arch = "wasm32"))]
{
Some(Some("en".to_string()))
}
}