cadmus_core/
i18n.rs

1use i18n_embed::{
2    fluent::{fluent_language_loader, FluentLanguageLoader},
3    LanguageLoader,
4};
5use rust_embed::RustEmbed;
6use std::sync::OnceLock;
7use unic_langid::LanguageIdentifier;
8
9include!(concat!(env!("OUT_DIR"), "/locales.rs"));
10
11pub const DEFAULT_LOCALE: &str = "en-GB";
12
13#[derive(RustEmbed)]
14#[folder = "i18n/"]
15struct Localizations;
16
17/// Trait for types that can provide localized string representations
18pub trait I18nDisplay {
19    /// Returns a localized string representation
20    fn to_i18n_string(&self) -> String;
21}
22
23/// Returns the global [`FluentLanguageLoader`], initialising it on first call.
24///
25/// The fallback language ([DEFAULT_LOCALE]) is loaded automatically, so the loader is
26/// always usable even before [`init`] is called.
27pub fn language_loader() -> &'static FluentLanguageLoader {
28    static LOADER: OnceLock<FluentLanguageLoader> = OnceLock::new();
29
30    LOADER.get_or_init(|| {
31        let loader = fluent_language_loader!();
32        loader
33            .load_fallback_language(&Localizations)
34            .expect("fallback language (en-GB) FTL assets must be present at compile time");
35        loader
36    })
37}
38
39/// Selects the active UI language from the [`LanguageIdentifier`] stored in [`Settings`].
40///
41/// Call once at startup, passing `settings.locale.as_ref()`. Passing `None`
42/// keeps the English fallback active.
43///
44/// [`Settings`]: crate::settings::Settings
45pub fn init(locale: Option<&LanguageIdentifier>) {
46    let requested: Vec<LanguageIdentifier> = locale.cloned().into_iter().collect();
47
48    i18n_embed::select(language_loader(), &Localizations, &requested)
49        .expect("failed to select i18n language");
50}
51
52/// Looks up a Fluent message by ID using the active language loader.
53///
54/// # Usage
55///
56/// ```ignore
57/// // This example uses the crate-internal fl! macro.
58/// let label = crate::fl!("startup-loading");
59/// ```
60#[macro_export]
61macro_rules! fl {
62    ($message_id:literal) => {{
63        i18n_embed_fl::fl!($crate::i18n::language_loader(), $message_id)
64    }};
65    ($message_id:literal, $($key:ident = $value:expr),* $(,)?) => {{
66        i18n_embed_fl::fl!($crate::i18n::language_loader(), $message_id, $($key = $value),*)
67    }};
68}