dioxus-rust-i18n 0.1.0

rust-i18n bindings for Dioxus
Documentation
use dioxus::prelude::*;

use crate::{I18nError, I18nRuntime, global};

/// Initializes a Dioxus i18n session from a fully constructed runtime.
///
/// This is the lowest-level hook-based initialization path. Most consumers
/// should prefer the zero-argument `use_init_i18n()` function generated by the
/// [`crate::i18n!`] macro.
pub fn use_init_i18n_from(init: impl FnOnce() -> I18nRuntime + 'static) -> I18nSession {
    use_context_provider(move || I18nSession::new(init()))
}

/// Retrieves the current Dioxus i18n session from context.
#[must_use]
pub fn use_i18n() -> I18nSession {
    consume_context()
}

/// Reactive Dioxus handle for reading and switching the current language.
#[derive(Clone, Copy)]
pub struct I18nSession {
    selected_locale: Signal<String>,
    runtime: Signal<I18nRuntime>,
}

impl I18nSession {
    /// Creates a new Dioxus session from a runtime.
    #[must_use]
    pub fn new(runtime: I18nRuntime) -> Self {
        let initial_locale = runtime.initial_locale().to_string();
        global::install_global_runtime(runtime.clone());

        Self {
            selected_locale: Signal::new(initial_locale),
            runtime: Signal::new(runtime),
        }
    }

    /// Returns the active locale tracked by this Dioxus session.
    #[must_use]
    pub fn language(&self) -> String {
        self.selected_locale.read().clone()
    }

    /// Updates the active locale and triggers Dioxus rerenders.
    pub fn set_language(&mut self, locale: impl Into<String>) {
        let locale = locale.into();
        self.selected_locale.set(locale.clone());
        global::set_global_locale(locale);
    }

    /// Returns the locales available through the current runtime.
    #[must_use]
    pub fn available_locales(&self) -> Vec<String> {
        self.runtime.read().available_locales().to_vec()
    }

    /// Tries to translate a key using the session's active locale.
    pub fn try_translate(&self, key: &str) -> Result<String, I18nError> {
        self.try_translate_in(&self.language(), key)
    }

    /// Tries to translate a key using an explicit locale override.
    pub fn try_translate_in(&self, locale: &str, key: &str) -> Result<String, I18nError> {
        self.runtime.read().translate(locale, key)
    }

    fn unwrap_translation(key: &str, result: Result<String, I18nError>) -> String {
        result.unwrap_or_else(|error| panic!("Failed to translate {key}: {error}"))
    }

    /// Translates a key using the session's active locale, panicking on
    /// failure.
    #[must_use]
    pub fn translate(&self, key: &str) -> String {
        Self::unwrap_translation(key, self.try_translate(key))
    }

    /// Translates a key using an explicit locale override, panicking on
    /// failure.
    #[must_use]
    pub fn translate_in(&self, locale: &str, key: &str) -> String {
        Self::unwrap_translation(key, self.try_translate_in(locale, key))
    }

    /// Tries to translate a key with pattern/value slices using the active
    /// locale.
    pub fn try_translate_with_args(
        &self,
        key: &str,
        patterns: &[&str],
        values: &[String],
    ) -> Result<String, I18nError> {
        self.try_translate_with_args_in(&self.language(), key, patterns, values)
    }

    /// Tries to translate a key with pattern/value slices using an explicit
    /// locale.
    pub fn try_translate_with_args_in(
        &self,
        locale: &str,
        key: &str,
        patterns: &[&str],
        values: &[String],
    ) -> Result<String, I18nError> {
        self.runtime
            .read()
            .translate_with_args(locale, key, patterns, values)
    }

    /// Tries to translate a key with `(&str, String)` interpolation pairs.
    pub fn try_translate_kv_args(
        &self,
        key: &str,
        args: &[(&str, String)],
    ) -> Result<String, I18nError> {
        self.try_translate_kv_args_in(&self.language(), key, args)
    }

    /// Tries to translate a key with `(&str, String)` interpolation pairs using
    /// an explicit locale.
    pub fn try_translate_kv_args_in(
        &self,
        locale: &str,
        key: &str,
        args: &[(&str, String)],
    ) -> Result<String, I18nError> {
        self.runtime
            .read()
            .translate_with_kv_args(locale, key, args)
    }

    /// Translates a key with pattern/value slices using the active locale,
    /// panicking on failure.
    #[must_use]
    pub fn translate_with_args(&self, key: &str, patterns: &[&str], values: &[String]) -> String {
        Self::unwrap_translation(key, self.try_translate_with_args(key, patterns, values))
    }

    /// Translates a key with pattern/value slices using an explicit locale,
    /// panicking on failure.
    #[must_use]
    pub fn translate_with_args_in(
        &self,
        locale: &str,
        key: &str,
        patterns: &[&str],
        values: &[String],
    ) -> String {
        Self::unwrap_translation(
            key,
            self.try_translate_with_args_in(locale, key, patterns, values),
        )
    }

    /// Translates a key with `(&str, String)` interpolation pairs, panicking on
    /// failure.
    #[must_use]
    pub fn translate_kv_args(&self, key: &str, args: &[(&str, String)]) -> String {
        Self::unwrap_translation(key, self.try_translate_kv_args(key, args))
    }

    /// Translates a key with `(&str, String)` interpolation pairs using an
    /// explicit locale.
    #[must_use]
    pub fn translate_kv_args_in(&self, locale: &str, key: &str, args: &[(&str, String)]) -> String {
        Self::unwrap_translation(key, self.try_translate_kv_args_in(locale, key, args))
    }
}

#[cfg(test)]
mod tests {
    use dioxus::prelude::*;

    use crate::I18nSession;
    use crate::runtime::test_runtime;

    #[test]
    fn locale_switch_updates_translation_state() {
        let runtime = test_runtime();

        let mut dom = VirtualDom::new(|| rsx! { div {} });
        let _ = dom.rebuild_to_vec();

        dom.in_scope(ScopeId::APP, || {
            let mut i18n = I18nSession::new(runtime);

            assert_eq!(i18n.translate("greeting"), "Hello");

            i18n.set_language("zh-CN");

            assert_eq!(i18n.translate("greeting"), "你好");
        });
    }
}