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//! [](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.¤cy_code=USD)
9//! [](https://github.com/sponsors/nashiradeer)
10//! [](https://crates.io/crates/hydrogen-i18n)
11//! [](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.¤cy_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}