1#![doc = include_str!("../README.md")]
2
3use std::{
4 borrow::Cow,
5 collections::HashMap,
6 fs,
7 marker::PhantomPinned,
8 path::{Path, PathBuf},
9};
10
11macro_rules! include_mods {
12 ($($mod:ident),*) => {
13 $(
14 mod $mod;
15 #[allow(unused_imports)]
16 pub use $mod::*;
17 )*
18 };
19}
20
21include_mods!(cache, error);
22
23pub type Locale = json::Value;
24pub(crate) type Sources = HashMap<String, PathBuf>;
25
26pub struct Localizer<'this> {
28 locales: Sources,
29 cache: LocaleCache<'this>,
31 default: Option<&'static str>,
32 debug: bool,
33 _pin: PhantomPinned,
34}
35
36impl<'this> Localizer<'this> {
37 pub fn new(locales_path: impl AsRef<Path>) -> Self {
39 let locales = walkdir::WalkDir::new(locales_path)
40 .into_iter()
41 .filter_map(|de| match de {
42 Ok(de)
43 if de.file_type().is_file()
44 && de.file_name().to_string_lossy().ends_with(".json") =>
45 {
46 log::trace!("Found locale: `{:?}`", de.path());
47 Some((
48 de.file_name()
49 .to_str()
50 .unwrap()
51 .split_once(".json")
52 .unwrap()
53 .0
54 .into(),
55 de.path().to_path_buf(),
56 ))
57 }
58 _ => None,
59 })
60 .collect();
61
62 Self {
63 locales,
64 cache: LocaleCache::new(),
65 debug: false,
66 _pin: PhantomPinned::default(),
67 default: None,
68 }
69 }
70
71 #[must_use]
73 pub fn precache_all(mut self) -> Self {
74 for (s, _) in unsafe { std::mem::transmute::<_, &'_ Sources>(&self.locales) }.iter() {
75 self.cache.prefer_recache(s, &self.locales).unwrap();
76 }
77 self
78 }
79
80 #[must_use]
83 #[inline]
84 pub fn debug(mut self, enable: bool) -> Self {
85 self.debug = enable;
86 self
87 }
88
89 pub fn default_locale(mut self, default_locale: &'static str) -> Self {
90 self.default = Some(default_locale);
91 self.cache.1 = Some(default_locale);
92 self
93 }
94
95 pub fn localize<'w>(&mut self, tag: &'w str) -> Result<&Locale>
102 where
103 'w: 'this,
104 {
105 if self.debug {
106 self.cache.prefer_recache(tag, &self.locales)
107 } else {
108 self.cache.look_up_or_cache(tag, &self.locales)
109 }
110 }
111
112 pub fn localize_no_cache<'w>(&self, tag: &'w str) -> Result<Cow<Locale>>
119 where
120 'w: 'this,
121 {
122 if self.debug {
123 log::trace!("Running debug mode, fetching locale `{}` from disk", tag);
124
125 if let Some(locale) = self.locales.get(tag) {
126 log::trace!("Found locale `{}` in sources, fetching from disk.", tag);
127
128 Ok(
129 Cow::Owned(
130 json::from_reader(fs::File::open(locale)?)?
131 )
132 )
133 } else if let Some(default_tag) = self.default {
134 log::trace!("Locale `{}` missing in sources, falling back to default locale `{}`", tag,default_tag);
135
136 Ok(
137 Cow::Owned(
138 json::from_reader(fs::File::open(
139 self.locales.get(default_tag).ok_or(
140 LocaleError::FallbackToDefaultFailed
141 )?
142 )?)?
143 )
144 )
145 } else {
146 log::error!("Locale `{}` wasn't found in sources, and default was `None`. Aborted.", tag);
147
148 Err(LocaleError::SourceLookupFailed)
149 }
150 } else {
151 self.cache.look_up_or_fetch(tag, &self.locales)
152 }
153 }
154}