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}