forked from
smokesignal.events/smokesignal
i18n+filtering fork - fluent-templates v2
1use anyhow::Result;
2use fluent::{bundle::FluentBundle, FluentArgs, FluentResource};
3use std::collections::HashMap;
4use unic_langid::LanguageIdentifier;
5
6// Define error types here instead of importing them
7// Using errors from i18n module
8use crate::i18n::errors::I18nError;
9
10pub type Bundle = FluentBundle<FluentResource, intl_memoizer::concurrent::IntlLangMemoizer>;
11
12pub struct Locales(pub HashMap<LanguageIdentifier, Bundle>);
13
14impl Locales {
15 pub fn new(locales: Vec<LanguageIdentifier>) -> Self {
16 let mut store = HashMap::new();
17 for locale in &locales {
18 let bundle: FluentBundle<FluentResource, intl_memoizer::concurrent::IntlLangMemoizer> =
19 FluentBundle::new_concurrent(vec![locale.clone()]);
20 store.insert(locale.clone(), bundle);
21 }
22 Self(store)
23 }
24
25 pub fn add_bundle(
26 &mut self,
27 locale: LanguageIdentifier,
28 content: String,
29 ) -> Result<(), I18nError> {
30 let bundle = self.0.get_mut(&locale).ok_or(I18nError::InvalidLanguage)?;
31
32 let resource = FluentResource::try_new(content)
33 .map_err(|(_, errors)| I18nError::LanguageResourceFailed(errors))?;
34
35 bundle
36 .add_resource(resource)
37 .map_err(I18nError::BundleLoadFailed)?;
38
39 Ok(())
40 }
41
42 pub fn format_error(&self, locale: &LanguageIdentifier, bare: &str, partial: &str) -> String {
43 let bundle = self.0.get(locale);
44 if bundle.is_none() {
45 return partial.to_string();
46 }
47
48 let bundle = bundle.unwrap();
49
50 let bundle_message = bundle.get_message(bare);
51
52 if bundle_message.is_none() {
53 return partial.to_string();
54 }
55
56 let bundle_message = bundle_message.unwrap();
57
58 let mut errors = Vec::new();
59
60 if bundle_message.value().is_none() {
61 return partial.to_string();
62 }
63 let bundle_message_value = bundle_message.value().unwrap();
64
65 let formatted_pattern = bundle.format_pattern(bundle_message_value, None, &mut errors);
66
67 formatted_pattern.to_string()
68 }
69
70 pub fn format_message(
71 &self,
72 locale: &LanguageIdentifier,
73 message: &str,
74 args: FluentArgs,
75 ) -> String {
76 let bundle = self.0.get(locale);
77 if bundle.is_none() {
78 return message.to_string();
79 }
80
81 let bundle = bundle.unwrap();
82
83 let bundle_message = bundle.get_message(message);
84
85 if bundle_message.is_none() {
86 return message.to_string();
87 }
88
89 let bundle_message = bundle_message.unwrap();
90
91 let mut errors = Vec::new();
92
93 if bundle_message.value().is_none() {
94 return message.to_string();
95 }
96 let bundle_message_value = bundle_message.value().unwrap();
97
98 let formatted_pattern =
99 bundle.format_pattern(bundle_message_value, Some(&args), &mut errors);
100
101 formatted_pattern.to_string()
102 }
103}
104
105#[cfg(feature = "embed")]
106pub mod embed {
107 use rust_embed::Embed;
108 use unic_langid::LanguageIdentifier;
109
110 use crate::i18n::{errors::I18nError, Locales};
111
112 #[derive(Embed)]
113 #[folder = "i18n/"]
114 struct I18nAssets;
115
116 pub fn populate_locale(
117 supported_locales: &Vec<LanguageIdentifier>,
118 locales: &mut Locales,
119 ) -> Result<(), I18nError> {
120 let locale_files = vec!["errors"];
121
122 for locale in supported_locales {
123 for file in &locale_files {
124 let source_file = format!("{}/{}.ftl", locale.to_string().to_lowercase(), file);
125 let i18n_asset = I18nAssets::get(&source_file).expect("locale file not found");
126 let content = std::str::from_utf8(i18n_asset.data.as_ref())
127 .expect("invalid utf-8 in locale file");
128 locales.add_bundle(locale.clone(), content.to_string())?;
129 }
130 }
131 Ok(())
132 }
133}
134
135#[cfg(feature = "reload")]
136pub mod reload {
137 use std::path::PathBuf;
138 use unic_langid::LanguageIdentifier;
139
140 use crate::i18n::{errors::I18nError, Locales};
141
142 pub fn populate_locale(
143 supported_locales: &Vec<LanguageIdentifier>,
144 locales: &mut Locales,
145 ) -> Result<(), I18nError> {
146 let locale_files = vec!["errors"];
147
148 for locale in supported_locales {
149 let locale_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
150 .join("i18n")
151 .join(locale.to_string().to_lowercase());
152 for file in &locale_files {
153 let source_file = locale_dir.join(format!("{}.ftl", file));
154 tracing::info!("Loading locale file: {:?}", source_file);
155 let i18n_asset = std::fs::read(source_file).expect("failed to read locale file");
156 let content =
157 std::str::from_utf8(&i18n_asset).expect("invalid utf-8 in locale file");
158 locales.add_bundle(locale.clone(), content.to_string())?;
159 }
160 }
161 Ok(())
162 }
163}