localizer_rs/lib.rs
1#![doc = include_str!("../.github/README.md")]
2// localizer-rs
3// Version: 1.2.0
4
5// Copyright (c) 2023-present ElBe Development.
6
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the 'Software'),
9// to deal in the Software without restriction, including without limitation
10// the rights to use, copy, modify, merge, publish, distribute, sublicense,
11// and/or sell copies of the Software, and to permit persons to whom the
12// Software is furnished to do so, subject to the following conditions:
13
14// The above copyright notice and this permission notice shall be included in
15// all copies or substantial portions of the Software.
16
17// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
18// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23// DEALINGS IN THE SOFTWARE.
24
25/////////////
26// EXPORTS //
27/////////////
28
29pub mod errors;
30
31
32////////////////////////////////
33// IMPORTS AND USE STATEMENTS //
34////////////////////////////////
35
36use std::fs::File;
37use std::io::BufReader;
38use std::path::Path;
39
40use serde_json;
41
42
43///////////////////
44// CONFIG OBJECT //
45///////////////////
46
47/// Localization config object.
48///
49/// Use [`Config::new()`] to create config objects instead of using this struct.
50///
51/// # Parameters
52///
53/// - `path`: The directory containing the translation files.
54/// The directory is relative to the path the executable was executed from.
55/// - `language`: The language to translate to.
56///
57/// # Returns
58///
59/// A new `Config` object with the specified path and language.
60///
61/// # Examples
62///
63/// ```rust
64/// # use localizer_rs;
65/// localizer_rs::Config {
66/// path: "path".to_owned(),
67/// language: "language".to_owned()
68/// };
69/// ```
70#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
71pub struct Config {
72 /// The directory containing the translation files. The directory is relative to the path the
73 /// executable was executed from.
74 pub path: String,
75 /// The language to translate to.
76 pub language: String,
77}
78
79
80//////////////////////
81// CONFIG FUNCTIONS //
82//////////////////////
83
84impl Config {
85 /// Creates a new config object.
86 ///
87 /// # Parameters
88 ///
89 /// - `path`: The directory containing the translation files.
90 /// The directory is relative to the path the executable was executed from.
91 /// - `language`: The language to translate to.
92 ///
93 /// # Returns
94 ///
95 /// A new `Config` object with the specified path and language.
96 ///
97 /// # Panics
98 ///
99 /// Panics if the Path provided is invalid.
100 ///
101 /// # Examples
102 ///
103 /// ```rust
104 /// # use localizer_rs;
105 /// localizer_rs::Config::new("examples/translations", "language");
106 /// ```
107 ///
108 /// # See also
109 ///
110 /// - [`Config`]
111 pub fn new(path: &str, language: &str) -> Config {
112 let mut config: Config = Config {
113 path: "".to_string(),
114 language: "".to_string(),
115 }
116 .to_owned();
117 config = config.set_language(language).to_owned();
118 config = config.set_path(path).to_owned();
119
120 return config;
121 }
122
123 /// Sets the path for the config object.
124 ///
125 /// # Parameters
126 ///
127 /// - `self`: The config object. This must be mutable.
128 /// - `str_path`: The directory containing the translation files.
129 /// The directory is relative to the path the executable was executed from.
130 ///
131 /// # Returns
132 ///
133 /// The modified `Config` object with the specified path.
134 ///
135 /// # Panics
136 ///
137 /// Panics if the Path provided is invalid.
138 ///
139 /// # Examples
140 ///
141 /// ```rust
142 /// # use localizer_rs;
143 /// # let mut config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "language");
144 /// config.set_path("examples");
145 /// ```
146 ///
147 /// # See also
148 ///
149 /// - [`Config`]
150 pub fn set_path(&mut self, str_path: &str) -> &Config {
151 let path: &Path = Path::new(str_path);
152
153 match path.try_exists() {
154 Ok(value) => {
155 if !value {
156 let error: errors::Error =
157 errors::Error::new("OS Error", "Translation path was not found", 1);
158 error.raise(format!("Path: {:?}", str_path).as_str());
159 }
160 }
161 Err(_error) => {
162 let error: errors::Error = errors::Error::new("OS Error", "Could not open path", 2);
163 error.raise(format!("Path: {:?}\nDetails: {}", str_path, _error).as_str());
164 }
165 }
166
167 self.path = String::from(match path.to_owned().to_str() {
168 Some(value) => value,
169 None => {
170 let error: errors::Error =
171 errors::Error::new("OS Error", "Path does not seem to be valid", 3);
172 error.raise(format!("Path: {:?}", str_path).as_str());
173 ""
174 }
175 });
176 return self;
177 }
178
179 /// Sets the language for the config object.
180 ///
181 /// # Parameters
182 ///
183 /// - `self`: The config object. This must be mutable.
184 /// - `language`: The language to translate to.
185 ///
186 /// # Returns
187 ///
188 /// The modified `Config` object with the specified language.
189 ///
190 /// # Examples
191 ///
192 /// ```rust
193 /// # use localizer_rs;
194 /// # let mut config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "language");
195 /// config.set_language("en");
196 /// ```
197 ///
198 /// # See also
199 ///
200 /// - [`Config`]
201 pub fn set_language(&mut self, language: &str) -> &Config {
202 self.language = language.to_string();
203 return self;
204 }
205
206 /// Translates the specified key in the language specified in the config.
207 ///
208 /// # Parameters
209 ///
210 /// - `self`: The config object.
211 /// - `key`: The key to translate to.
212 /// - `arguments`: The arguments to replace.
213 ///
214 /// # Returns
215 ///
216 /// A `String` containing the translated value.
217 ///
218 /// # Examples
219 ///
220 /// ```rust
221 /// # use localizer_rs;
222 /// # let config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "en");
223 /// config.t("test", vec![]);
224 /// ```
225 ///
226 /// # See also
227 ///
228 /// - [`t!()`]
229 /// - [`Config`]
230 pub fn t(&self, key: &str, arguments: Vec<(&str, &str)>) -> String {
231 return self.translate(key, arguments);
232 }
233
234 /// Translates the specified key in the language specified in the config.
235 ///
236 /// # Parameters
237 ///
238 /// - `self`: The config object.
239 /// - `key`: The key to translate to.
240 /// - `arguments`: The arguments to replace.
241 ///
242 /// # Returns
243 ///
244 /// A `String` containing the translated value.
245 ///
246 /// # Raises
247 ///
248 /// This method throws an exception and exits if
249 ///
250 /// - The translation file could not be found
251 /// - The translation file could not be opened
252 /// - The translation file could not be parsed
253 /// - The parsed json could not be converted to a json value
254 /// - The converted json could not be indexed
255 ///
256 /// # Examples
257 ///
258 /// ```rust
259 /// # use localizer_rs;
260 /// # let config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "en");
261 /// config.translate("test", vec![]);
262 /// ```
263 ///
264 /// # See also
265 ///
266 /// - [`t!()`]
267 /// - [`Config`]
268 /// - [`Config::t()`]
269 /// - [`serde_json`]
270 pub fn translate(&self, key: &str, mut arguments: Vec<(&str, &str)>) -> String {
271 let mut colors: Vec<(&str, &str)> = vec![
272 // Formatting codes
273 ("end", "\x1b[0m"),
274 ("bold", "\x1b[1m"),
275 ("italic", "\x1b[3m"),
276 ("underline", "\x1b[4m"),
277 ("overline", "\x1b[53m"),
278
279 // Foreground colors
280 ("color.black", "\x1b[30m"),
281 ("color.red", "\x1b[31m"),
282 ("color.green", "\x1b[32m"),
283 ("color.yellow", "\x1b[33m"),
284 ("color.blue", "\x1b[34m"),
285 ("color.magenta", "\x1b[35m"),
286 ("color.cyan", "\x1b[36m"),
287 ("color.white", "\x1b[37m"),
288
289 // Bright foreground colors
290 ("color.bright_black", "\x1b[90m"),
291 ("color.bright_red", "\x1b[91m"),
292 ("color.bright_green", "\x1b[92m"),
293 ("color.bright_yellow", "\x1b[93m"),
294 ("color.bright_blue", "\x1b[94m"),
295 ("color.bright_magenta", "\x1b[95m"),
296 ("color.bright_cyan", "\x1b[96m"),
297 ("color.bright_white", "\x1b[97m"),
298
299 // Background colors
300 ("back.black", "\x1b[40m"),
301 ("back.red", "\x1b[41m"),
302 ("back.green", "\x1b[42m"),
303 ("back.yellow", "\x1b[43m"),
304 ("back.blue", "\x1b[44m"),
305 ("back.magenta", "\x1b[45m"),
306 ("back.cyan", "\x1b[46m"),
307 ("back.white", "\x1b[47m"),
308
309 // Bright background colors
310 ("back.bright_black", "\x1b[100m"),
311 ("back.bright_red", "\x1b[101m"),
312 ("back.bright_green", "\x1b[102m"),
313 ("back.bright_yellow", "\x1b[103m"),
314 ("back.bright_blue", "\x1b[104m"),
315 ("back.bright_magenta", "\x1b[105m"),
316 ("back.bright_cyan", "\x1b[106m"),
317 ("back.bright_white", "\x1b[107m"),
318 ];
319 arguments.append(&mut colors);
320
321 let file: File = match File::open(Path::new(
322 format!("./{}/{}.json", &self.path, &self.language).as_str(),
323 )) {
324 Ok(value) => value,
325 Err(_error) => {
326 let error: errors::Error =
327 errors::Error::new("OS Error", "Could not open translation file", 4);
328 error.raise(
329 format!(
330 "File: ./{}/{}.json\nError: {}",
331 &self.path, &self.language, _error
332 )
333 .as_str(),
334 );
335
336 return "".to_owned();
337 }
338 };
339 let reader: BufReader<File> = BufReader::new(file);
340
341 let json: serde_json::Value = match serde_json::to_value::<serde_json::Value>(
342 match serde_json::from_reader::<BufReader<File>, serde_json::Value>(reader) {
343 Ok(value) => value,
344 Err(_error) => {
345 let error: errors::Error = errors::Error::new(
346 "Parsing error",
347 "Translation file could not be parsed",
348 5,
349 );
350 error.raise(
351 format!(
352 "File: ./{}/{}.json\nError: {}",
353 &self.path, &self.language, _error
354 )
355 .as_str(),
356 );
357
358 return "".to_owned();
359 }
360 },
361 ) {
362 Ok(value) => value,
363 Err(_error) => {
364 let error: errors::Error =
365 errors::Error::new("Converting error", "Could not convert to json value", 6);
366 error.raise(
367 format!(
368 "File: ./{}/{}.json\nError: {}",
369 &self.path, &self.language, _error
370 )
371 .as_str(),
372 );
373
374 return "".to_owned();
375 }
376 }
377 .to_owned();
378 let mut result: String = match json[key].as_str() {
379 Some(value) => value.to_string(),
380 None => {
381 let error: errors::Error =
382 errors::Error::new("Indexing error", "Could not index json value", 6);
383 error.raise(
384 format!(
385 "Index: {}\nFile: ./{}/{}.json",
386 key, &self.path, &self.language
387 )
388 .as_str(),
389 );
390
391 return "".to_owned();
392 }
393 };
394
395 for (key, value) in arguments {
396 result = result.replace(("{{".to_owned() + key + "}}").as_str(), value);
397 }
398
399 return result;
400 }
401}
402
403
404/// Translates the specified key in the language specified in the config.
405///
406/// # Parameters
407///
408/// - `config`: The config object.
409/// - `key`: The key to translate to.
410/// - `arguments`: Optional parameter. The arguments to replace. Has to be of type `"name" = "value"`.
411///
412/// # Returns
413///
414/// A `String` containing the translated value.
415///
416/// # Examples
417///
418/// ```rust
419/// # use localizer_rs;
420/// # let config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "en");
421/// localizer_rs::t!(config, "test");
422/// localizer_rs::t!(config, "test", "variable" = "content");
423/// ```
424///
425/// # See also
426///
427/// - [`Config`]
428/// - [`Config::t()`]
429#[macro_export]
430macro_rules! t {
431 ($config:expr, $key:expr) => {
432 {
433 $config.t($key, vec![])
434 }
435 };
436
437 ($config:expr, $key:expr, $($argument_name:literal = $argument_value:literal),* $(,)?) => {
438 {
439 let mut arguments: Vec<(&str, &str)> = vec![];
440
441 $(
442 arguments.push(($argument_name, $argument_value));
443 )*
444
445 $config.t($key, arguments)
446 }
447 };
448}