es_fluent_manager_macros/
lib.rs1#![doc = include_str!("../README.md")]
2
3use heck::ToPascalCase as _;
4use proc_macro::TokenStream;
5use quote::quote;
6use std::fs;
7
8#[proc_macro]
17pub fn define_embedded_i18n_module(_input: TokenStream) -> TokenStream {
18 let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME must be set");
19 let assets_struct_name = syn::Ident::new(
20 &format!(
21 "{}I18nAssets",
22 &crate_name.replace('-', "_").to_pascal_case()
23 ),
24 proc_macro2::Span::call_site(),
25 );
26
27 let module_data_name = syn::Ident::new(
28 &format!(
29 "{}_I18N_MODULE_DATA",
30 &crate_name.to_uppercase().replace('-', "_")
31 ),
32 proc_macro2::Span::call_site(),
33 );
34
35 let config = match es_fluent_toml::I18nConfig::read_from_manifest_dir() {
36 Ok(config) => config,
37 Err(es_fluent_toml::I18nConfigError::NotFound) => {
38 panic!(
39 "No i18n.toml configuration file found in project root. Please create one with the required settings."
40 );
41 },
42 Err(e) => {
43 panic!("Failed to read i18n.toml configuration: {}", e);
44 },
45 };
46
47 let i18n_root_path = match config.assets_dir_from_manifest() {
48 Ok(path) => path,
49 Err(e) => {
50 panic!(
51 "Failed to resolve assets directory from configuration: {}",
52 e
53 );
54 },
55 };
56
57 if let Err(e) = config.validate_assets_dir() {
58 panic!("Assets directory validation failed: {}", e);
59 }
60
61 let mut languages = Vec::new();
62 let entries = fs::read_dir(&i18n_root_path).unwrap_or_else(|e| {
63 panic!(
64 "Failed to read i18n directory at {:?}: {}",
65 i18n_root_path, e
66 )
67 });
68
69 for entry in entries {
70 let entry = entry.expect("Failed to read directory entry");
71 let path = entry.path();
72 if path.is_dir()
73 && let Some(lang_code) = path.file_name().and_then(|s| s.to_str())
74 {
75 let ftl_file_name = format!("{}.ftl", crate_name);
76 let ftl_path = path.join(ftl_file_name);
77
78 if ftl_path.exists() {
79 languages.push(lang_code.to_string());
80 }
81 }
82 }
83
84 let language_identifiers = languages.iter().map(|lang| {
85 quote! { es_fluent::unic_langid::langid!(#lang) }
86 });
87
88 let i18n_root_str = i18n_root_path.to_string_lossy();
89
90 let expanded = quote! {
91 #[derive(es_fluent::__rust_embed::RustEmbed)]
92 #[folder = #i18n_root_str]
93 struct #assets_struct_name;
94
95 impl es_fluent::__manager_core::EmbeddedAssets for #assets_struct_name {
96 fn domain() -> &'static str {
97 #crate_name
98 }
99 }
100
101 static #module_data_name: es_fluent::__manager_core::EmbeddedModuleData =
102 es_fluent::__manager_core::EmbeddedModuleData {
103 name: #crate_name,
104 domain: #crate_name,
105 supported_languages: &[
106 #(#language_identifiers),*
107 ],
108 };
109
110 es_fluent::__inventory::submit!(
111 &es_fluent::__manager_core::EmbeddedI18nModule::<#assets_struct_name>::new(&#module_data_name)
112 as &dyn es_fluent::__manager_core::I18nModule
113 );
114 };
115
116 TokenStream::from(expanded)
117}
118
119#[proc_macro]
127pub fn define_bevy_i18n_module(_input: TokenStream) -> TokenStream {
128 let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME must be set");
129 let static_data_name = syn::Ident::new(
130 &format!(
131 "{}_I18N_ASSET_MODULE_DATA",
132 &crate_name.to_uppercase().replace('-', "_")
133 ),
134 proc_macro2::Span::call_site(),
135 );
136
137 let config = match es_fluent_toml::I18nConfig::read_from_manifest_dir() {
138 Ok(config) => config,
139 Err(es_fluent_toml::I18nConfigError::NotFound) => {
140 panic!(
141 "No i18n.toml configuration file found in project root. Please create one with the required settings."
142 );
143 },
144 Err(e) => {
145 panic!("Failed to read i18n.toml configuration: {}", e);
146 },
147 };
148
149 let i18n_root_path = match config.assets_dir_from_manifest() {
150 Ok(path) => path,
151 Err(e) => {
152 panic!(
153 "Failed to resolve assets directory from configuration: {}",
154 e
155 );
156 },
157 };
158
159 if let Err(e) = config.validate_assets_dir() {
160 panic!("Assets directory validation failed: {}", e);
161 }
162
163 let mut languages = Vec::new();
164 let entries = fs::read_dir(&i18n_root_path).unwrap_or_else(|e| {
165 panic!(
166 "Failed to read i18n directory at {:?}: {}",
167 i18n_root_path, e
168 )
169 });
170
171 for entry in entries {
172 let entry = entry.expect("Failed to read directory entry");
173 let path = entry.path();
174 if path.is_dir()
175 && let Some(lang_code) = path.file_name().and_then(|s| s.to_str())
176 {
177 let ftl_file_name = format!("{}.ftl", crate_name);
178 let ftl_path = path.join(ftl_file_name);
179
180 if ftl_path.exists() {
181 languages.push(lang_code.to_string());
182 }
183 }
184 }
185
186 let language_identifiers = languages.iter().map(|lang| {
187 quote! { es_fluent::unic_langid::langid!(#lang) }
188 });
189
190 let expanded = quote! {
191 static #static_data_name: es_fluent::__manager_core::AssetModuleData = es_fluent::__manager_core::AssetModuleData {
192 name: #crate_name,
193 domain: #crate_name,
194 supported_languages: &[
195 #(#language_identifiers),*
196 ],
197 };
198
199 es_fluent::__inventory::submit!(
200 &es_fluent::__manager_core::AssetI18nModule::new(&#static_data_name)
201 as &dyn es_fluent::__manager_core::I18nAssetModule
202 );
203 };
204
205 TokenStream::from(expanded)
206}