hydrogen_i18n/
lib.rs

1#![warn(missing_docs)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! # Nashira Deer // Hydrogen I18n
5//!
6//! Translation utilities for server-side applications that need to deal with different languages at the same time.
7//!
8//! [![PayPal](https://img.shields.io/badge/Paypal-003087?style=for-the-badge&logo=paypal&logoColor=%23fff)](https://www.paypal.com/donate/?business=QQGMTC3FQAJF6&no_recurring=0&item_name=Thanks+for+donating+for+me%2C+this+helps+me+a+lot+to+continue+developing+and+maintaining+my+projects.&currency_code=USD)
9//! [![GitHub Sponsor](https://img.shields.io/badge/GitHub%20Sponsor-181717?style=for-the-badge&logo=github&logoColor=%23fff)](https://github.com/sponsors/nashiradeer)
10//! [![Crates.io](https://img.shields.io/crates/v/hydrogen-i18n?style=for-the-badge&logo=rust&logoColor=%23fff&label=Crates.io&labelColor=%23000&color=%23000)](https://crates.io/crates/hydrogen-i18n)
11//! [![docs.rs](https://img.shields.io/docsrs/hydrogen-i18n?style=for-the-badge&logo=docsdotrs&logoColor=%23fff&label=Docs.rs&labelColor=%23000&color=%23000)](https://docs.rs/hydrogen-i18n/)
12//!
13//! Server-side applications that deal directly with users will need to be prepared to deal with different languages too, so Hydrogen I18n comes with utilities focused on making this task easier, loading and managing all the languages supported by the application in the memory, avoiding unnecessary disk and CPU usage.
14//!
15//! Hydrogen I18n is part of [Hydrogen Framework](https://github.com/users/nashiradeer/projects/8), this means that this crate is designed to be used on Discord bots created using [serenity](https://crates.io/crates/serenity) and [twilight](https://crates.io/crates/twilight), but is sufficiently generic to be used by any other application that does not use these Discord libraries or even Discord bots.
16//!
17//! ## Donating
18//!
19//! Consider donating to make Hydrogen I18n development possible. You can donate through Nashira Deer's [PayPal](https://www.paypal.com/donate/?business=QQGMTC3FQAJF6&no_recurring=0&item_name=Thanks+for+donating+for+me%2C+this+helps+me+a+lot+to+continue+developing+and+maintaining+my+projects.&currency_code=USD) or [GitHub Sponsor](https://github.com/sponsors/nashiradeer).
20//!
21//! ## Features
22//!
23//! - `serenity`: Enables functions that make it easy to use the library in Discord apps and bots built with [serenity](https://crates.io/crates/serenity).
24//! - `tokio`: Enables [tokio](https://crates.io/crates/tokio)-based builder.
25//! - `simd`: Enables [simd-json](https://crates.io/crates/simd-json)-based parser and use it by default.
26//!
27//! ## Credits
28//!
29//! Hydrogen I18n is a Nashira Deer project licensed under the [MIT License](https://github.com/nashiradeer/hydrogen-i18n/blob/main/LICENSE.txt) and licensed under the [GNU Lesser General Public License v3](https://github.com/nashiradeer/hydrogen-i18n/blob/c00b016356dc9263571e6cc6ede87969bf31bf02/LICENSE.txt) until v1.0.1.
30
31use std::{
32    collections::HashMap,
33    fmt::{self, Display, Formatter},
34    io, result,
35};
36
37pub mod builders;
38pub mod parsers;
39
40mod i18n;
41pub use i18n::*;
42
43/// Groups all the errors that can be returned by Hydrogen I18n.
44#[derive(Debug)]
45pub enum Error {
46    /// An error related to IO.
47    Io(io::Error),
48
49    /// An error related to JSON parsing.
50    Json(serde_json::Error),
51
52    /// The language was not found.
53    LanguageNotFound(String),
54
55    /// An invalid file name.
56    InvalidFileName,
57
58    /// An error related to UTF-8 parsing.
59    Utf8(std::str::Utf8Error),
60
61    #[cfg(feature = "tokio")]
62    #[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
63    /// An error related to Tokio.
64    Tokio(tokio::task::JoinError),
65
66    #[cfg(feature = "simd")]
67    #[cfg_attr(docsrs, doc(cfg(feature = "simd")))]
68    /// An error related to SIMD JSON parsing.
69    SimdJson(simd_json::Error),
70}
71
72impl Display for Error {
73    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
74        match self {
75            Self::Io(error) => write!(f, "IO error: {}", error),
76            Self::Json(error) => write!(f, "JSON error: {}", error),
77            Self::InvalidFileName => write!(f, "Invalid file name"),
78            Self::LanguageNotFound(language) => {
79                write!(f, "Language {} not found", language)
80            }
81            Self::Utf8(error) => write!(f, "UTF-8 error: {}", error),
82
83            #[cfg(feature = "tokio")]
84            Self::Tokio(error) => write!(f, "Tokio error: {}", error),
85
86            #[cfg(feature = "simd")]
87            Self::SimdJson(error) => write!(f, "SIMD JSON error: {}", error),
88        }
89    }
90}
91
92/// A result with the error type of Hydrogen I18n.
93pub type Result<T> = result::Result<T, Error>;
94
95/// A single category containing translations as key-value pairs.
96pub type Category = HashMap<String, String>;
97
98/// A language containing categories as key-value pairs or a link to another language.
99#[derive(Clone)]
100pub enum Language {
101    /// A link to another language.
102    Link(String),
103
104    /// A language containing categories as key-value pairs.
105    Data(HashMap<String, Category>),
106}
107
108/// Removes all the translations that are equal to the base language.
109#[deprecated(
110    since = "2.2.0",
111    note = "There's no need to deduplicate languages anymore, use a builder instead"
112)]
113fn deduplicate_language(
114    base: &HashMap<String, Category>,
115    deduplicating: &mut HashMap<String, Category>,
116) {
117    #[allow(deprecated)]
118    deduplicating.retain(|category_name, category| {
119        category.retain(|key, value| {
120            if let Some(default_translation) = resolve_translation(base, category_name, key) {
121                if *default_translation == *value {
122                    return false;
123                }
124            }
125
126            true
127        });
128
129        !category.is_empty()
130    });
131}
132
133/// Resolves category and key to a translation without getting the ownership.
134fn resolve_translation<'a>(
135    language: &'a HashMap<String, Category>,
136    category: &str,
137    key: &str,
138) -> Option<&'a String> {
139    language.get(category)?.get(key)
140}