1use std::sync::Arc;
2
3use glossa::{
4 LangID, LocaleContext, fallback::dbg_ref, misc::normalize_glossa_lang,
5 sys::new_once_lock, traits::ChainProvider,
6};
7use glossa_codegen::glossa_shared::{
8 ToCompactString,
9 load_bincode::{self, list_static_bincode_files, try_load_files},
10 tap::{Pipe, Tap},
11 type_aliases::L10nMaps,
12};
13
14use crate::{envs::static_glossa_lang, static_data::bincode_dir};
15
16pub(crate) trait GetL10nText: ChainProvider {
17 fn try_get_bincode<'t>(&self, map_name: &str, key: &str) -> Option<&'t str> {
18 let key_bytes = key.as_bytes();
19 let fallback_to_builtin =
20 || static_locale_context().try_get_bulitin_cli_data(key_bytes);
21
22 let lookup =
23 |(language, map_name, key)| match static_bincode_resource()?.get(language) {
24 Some(x) => x
25 .get(&(map_name, key))
26 .map(|x| x.as_str())
27 .or_else(fallback_to_builtin),
28 _ => {
29 log::trace!("fallback to builtin data");
30 fallback_to_builtin()
31 }
32 };
33
34 self
35 .provide_chain()?
36 .iter()
37 .map(|lang| (lang, map_name.to_compact_string(), key.to_compact_string()))
38 .find_map(lookup)
39 }
40
41 fn try_get_bulitin_cli_data<'t>(&self, key: &[u8]) -> Option<&'t str> {
42 let lookup = |language| match crate::l10n::router::map(language, key) {
43 "" => None,
44 s => Some(s),
45 };
46 self
47 .provide_chain()?
48 .iter()
49 .map(|lang| lang.as_bytes())
50 .find_map(lookup)
51 }
52
53 fn try_get<'t>(&self, map_name: &str, key: &str) -> Option<&'t str> {
54 match static_bincode_resource() {
55 Some(_) => self.try_get_bincode(map_name, key),
56 _ => self.try_get_bulitin_cli_data(key.as_bytes()),
57 }
58 }
59}
60
61impl GetL10nText for LocaleContext {}
62
63pub fn bincode_exists() -> Option<()> {
66 bincode_dir()
67 .pipe(list_static_bincode_files)
68 .map(|_| ())
69}
70
71pub fn static_bincode_resource() -> Option<&'static Arc<L10nMaps>> {
72 bincode_exists()?;
73
74 new_once_lock!(L: Option<Arc<L10nMaps>>);
75
76 L.get_or_init(|| {
77 bincode_dir()
78 .pipe(list_static_bincode_files)
79 .map(try_load_files)
80 .and_then(|x| x.ok())
81 .map(Arc::new)
82 })
83 .as_ref()
84}
85
86fn compatible_with_gnu_language_colon_style(locale_context: &mut LocaleContext) {
87 let _ =
88 locale_context.try_push_front_with_colon_separated_str(static_glossa_lang());
89}
90
91pub fn static_bincode_context() -> Option<&'static LocaleContext> {
92 bincode_exists()?;
93 new_once_lock!(L: Option<LocaleContext>);
94
95 L.get_or_init(|| {
96 let locales = merge_locales()?;
97 dbg_ref!(locales);
98 LocaleContext::default()
99 .with_current_locale(normalize_glossa_lang(static_glossa_lang()))
100 .with_all_locales(locales)
101 .tap_mut(compatible_with_gnu_language_colon_style)
102 .pipe(Some)
103 })
104 .as_ref()
105}
106
107pub(crate) fn merge_locales() -> Option<Box<[LangID]>> {
109 load_bincode::merge_locales(
110 static_bincode_resource().map(|v| v.as_ref()),
111 static_locale_context()
112 .get_all_locales()
113 .as_deref(),
114 )
115}
116
117pub(crate) fn static_locale_context() -> &'static LocaleContext {
118 new_once_lock!(L: LocaleContext);
119 L.get_or_init(|| {
120 LocaleContext::default()
121 .with_current_locale(normalize_glossa_lang(static_glossa_lang()))
122 .with_all_locales(crate::l10n::locale_registry::all_locales())
123 .tap_mut(compatible_with_gnu_language_colon_style)
124 })
125}
126
127pub fn get_text<'a>(key: &str, map_name: Option<&str>) -> &'a str {
128 let map_name = map_name.unwrap_or("cli");
129
130 match static_bincode_context() {
131 Some(ctx) => ctx,
132 _ => static_locale_context(),
133 }
134 .try_get(map_name, key)
135 .unwrap_or_else(|| {
136 log::warn!("{}", glossa::Error::new_map_text_not_found(map_name, key));
137 ""
138 })
139}