1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! This module contains the types and functions to interact with the
//! `gettext` localization system.
//!
//! Most important is the [GettextLanguageLoader].
//!
//! ⚠️ *This module requires the following crate features to be activated: `gettext-system`.*

use crate::{domain_from_module, I18nAssets, I18nEmbedError, LanguageLoader};

pub use i18n_embed_impl::gettext_language_loader;

use parking_lot::RwLock;
use unic_langid::LanguageIdentifier;

/// [LanguageLoader] implementation for the `gettext` localization
/// system.
///
/// ⚠️ *This API requires the following crate features to be activated: `gettext-system`.*
#[derive(Debug)]
pub struct GettextLanguageLoader {
    current_language: RwLock<LanguageIdentifier>,
    module: &'static str,
    fallback_language: LanguageIdentifier,
}

impl GettextLanguageLoader {
    /// Create a new `GettextLanguageLoader`.
    ///
    /// # Example
    ///
    /// ```
    /// use i18n_embed::gettext::GettextLanguageLoader;
    ///
    /// GettextLanguageLoader::new(module_path!(), "en".parse().unwrap());
    /// ```
    pub fn new(module: &'static str, fallback_language: unic_langid::LanguageIdentifier) -> Self {
        Self {
            current_language: RwLock::new(fallback_language.clone()),
            module,
            fallback_language,
        }
    }

    fn load_src_language(&self) {
        let catalog = gettext_system::Catalog::empty();
        tr::internal::set_translator(self.module, catalog);
        *(self.current_language.write()) = self.fallback_language().clone();
    }
}

impl LanguageLoader for GettextLanguageLoader {
    /// The fallback language for the module this loader is responsible
    /// for.
    fn fallback_language(&self) -> &LanguageIdentifier {
        &self.fallback_language
    }

    /// The domain for the translation that this loader is associated with.
    fn domain(&self) -> &'static str {
        domain_from_module(self.module)
    }

    /// The language file name to use for this loader's domain.
    fn language_file_name(&self) -> String {
        format!("{}.mo", self.domain())
    }

    /// Get the language which is currently loaded for this loader.
    fn current_language(&self) -> LanguageIdentifier {
        self.current_language.read().clone()
    }

    /// Load the languages `language_ids` using the resources packaged
    /// in the `i18n_assets` in order of fallback preference. This
    /// also sets the [LanguageLoader::current_language()] to the
    /// first in the `language_ids` slice. You can use
    /// [select()](super::select()) to determine which fallbacks are
    /// actually available for an arbitrary slice of preferences.
    ///
    /// **Note:** Gettext doesn't support loading multiple languages
    /// as multiple fallbacks. We only load the first of the requested
    /// languages, and the fallback is the src language.
    fn load_languages(
        &self,
        i18n_assets: &dyn I18nAssets,
        language_ids: &[&unic_langid::LanguageIdentifier],
    ) -> Result<(), I18nEmbedError> {
        let language_id = *language_ids
            .get(0)
            .ok_or(I18nEmbedError::RequestedLanguagesEmpty)?;

        if language_id == self.fallback_language() {
            self.load_src_language();
            return Ok(());
        }

        let (_path, file) = match self.language_file(&language_id, i18n_assets) {
            (path, Some(f)) => (path, f),
            (path, None) => {
                log::error!(
                    target:"i18n_embed::gettext", 
                    "{} Setting current_language to fallback locale: \"{}\".", 
                    I18nEmbedError::LanguageNotAvailable(path, language_id.clone()),
                    self.fallback_language);
                self.load_src_language();
                return Ok(());
            }
        };

        let catalog = gettext_system::Catalog::parse(&*file).expect("could not parse the catalog");
        tr::internal::set_translator(self.module, catalog);
        *(self.current_language.write()) = language_id.clone();

        Ok(())
    }
}