i18n_embed_impl/
lib.rs

1/// A procedural macro to create a new `GettextLanguageLoader` using
2/// the current crate's `i18n.toml` configuration, and domain.
3///
4/// ⚠️ *This API requires the following crate features to be
5/// activated: `gettext-system`.*
6///
7/// ## Example
8///
9/// ```ignore
10/// use i18n_embed::gettext::{gettext_language_loader, GettextLanguageLoader};
11/// let my_language_loader: GettextLanguageLoader = gettext_language_loader!();
12/// ```
13#[proc_macro]
14#[cfg(feature = "gettext-system")]
15pub fn gettext_language_loader(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
16    let manifest = find_crate::Manifest::new().expect("Error reading Cargo.toml");
17    let current_crate_package_name = {
18        manifest.crate_package().map(|pkg| pkg.name).unwrap_or(
19            std::env::var("CARGO_PKG_NAME").expect("Error fetching `CARGO_PKG_NAME` env"),
20        )
21    };
22
23    // Special case for when this macro is invoked in i18n-embed tests/docs
24    let i18n_embed_crate_name = if current_crate_package_name == "i18n_embed" {
25        "i18n_embed".to_string()
26    } else {
27        manifest
28            .find(|s| s == "i18n-embed")
29            .expect("i18n-embed should be an active dependency in your Cargo.toml")
30            .name
31    };
32
33    let i18n_embed_crate_ident =
34        syn::Ident::new(&i18n_embed_crate_name, proc_macro2::Span::call_site());
35
36    let config_file_path = i18n_config::locate_crate_paths()
37        .unwrap_or_else(|error| {
38            panic!(
39                "gettext_language_loader!() is unable to locate i18n config file: {}",
40                error
41            )
42        })
43        .i18n_config_file;
44
45    let config = i18n_config::I18nConfig::from_file(&config_file_path).unwrap_or_else(|err| {
46        panic!(
47            "gettext_language_loader!() had a problem reading i18n config file {0:?}: {1}",
48            std::fs::canonicalize(&config_file_path).unwrap_or_else(|_| config_file_path.clone()),
49            err
50        )
51    });
52
53    if config.gettext.is_none() {
54        panic!(
55            "gettext_language_loader!() had a problem parsing i18n config file {0:?}: there is no `[gettext]` section",
56            std::fs::canonicalize(&config_file_path).unwrap_or(config_file_path)
57        )
58    }
59
60    let fallback_language = syn::LitStr::new(
61        &config.fallback_language.to_string(),
62        proc_macro2::Span::call_site(),
63    );
64
65    let gen = quote::quote! {
66        #i18n_embed_crate_ident::gettext::GettextLanguageLoader::new(
67            module_path!(),
68            #fallback_language.parse().unwrap(),
69        )
70    };
71
72    gen.into()
73}
74
75/// A procedural macro to create a new `FluentLanguageLoader` using
76/// the current crate's `i18n.toml` configuration, and domain.
77///
78/// ⚠️ *This API requires the following crate features to be
79/// activated: `fluent-system`.*
80///
81/// ## Example
82///
83/// ```ignore
84/// use i18n_embed::fluent::{fluent_language_loader, FluentLanguageLoader};
85/// let my_language_loader: FluentLanguageLoader = fluent_language_loader!();
86/// ```
87#[proc_macro]
88#[cfg(feature = "fluent-system")]
89pub fn fluent_language_loader(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
90    let manifest = find_crate::Manifest::new().expect("Error reading Cargo.toml");
91    let current_crate_package_name = manifest
92        .crate_package()
93        .map(|pkg| pkg.name)
94        .unwrap_or(std::env::var("CARGO_PKG_NAME").expect("Error fetching `CARGO_PKG_NAME` env"));
95
96    // Special case for when this macro is invoked in i18n-embed tests/docs
97    let i18n_embed_crate_name = if current_crate_package_name == "i18n_embed" {
98        "i18n_embed".to_string()
99    } else {
100        manifest
101            .find(|s| s == "i18n-embed")
102            .expect("i18n-embed should be an active dependency in your Cargo.toml")
103            .name
104    };
105
106    let i18n_embed_crate_ident =
107        syn::Ident::new(&i18n_embed_crate_name, proc_macro2::Span::call_site());
108
109    let config_file_path = i18n_config::locate_crate_paths()
110        .unwrap_or_else(|error| {
111            panic!(
112                "fluent_language_loader!() is unable to locate i18n config file: {}",
113                error
114            )
115        })
116        .i18n_config_file;
117
118    let config = i18n_config::I18nConfig::from_file(&config_file_path).unwrap_or_else(|err| {
119        panic!(
120            "fluent_language_loader!() had a problem reading i18n config file {0:?}: {1}",
121            std::fs::canonicalize(&config_file_path).unwrap_or_else(|_| config_file_path.clone()),
122            err
123        )
124    });
125
126    if config.fluent.is_none() {
127        panic!(
128            "fluent_language_loader!() had a problem parsing i18n config file {0:?}: there is no `[fluent]` section",
129            std::fs::canonicalize(&config_file_path).unwrap_or(config_file_path)
130        )
131    }
132
133    let fallback_language = syn::LitStr::new(
134        &config.fallback_language.to_string(),
135        proc_macro2::Span::call_site(),
136    );
137
138    let domain_str = config
139        .fluent
140        .and_then(|f| f.domain)
141        .unwrap_or(current_crate_package_name);
142    let domain = syn::LitStr::new(&domain_str, proc_macro2::Span::call_site());
143
144    let gen = quote::quote! {
145        #i18n_embed_crate_ident::fluent::FluentLanguageLoader::new(
146            #domain,
147            #fallback_language.parse().unwrap(),
148        )
149    };
150
151    gen.into()
152}