veloren_common_i18n/
lib.rs

1use hashbrown::HashMap;
2use serde::{Deserialize, Serialize};
3
4// TODO: expose convinience macros ala 'fluent_args!'?
5
6/// The type to represent generic localization request, to be sent from server
7/// to client and then localized (or internationalized) there.
8// TODO: Ideally we would need to fully cover API of our `i18n::Language`, including
9// Fluent values.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub enum Content {
12    /// Plain(text)
13    ///
14    /// The content is a plaintext string that should be shown to the user
15    /// verbatim.
16    Plain(String),
17    /// Key(i18n_key)
18    ///
19    /// The content is defined just by the key
20    Key(String),
21    /// Attr(i18n_key, attr)
22    ///
23    /// The content is the attribute of the key
24    Attr(String, String),
25    /// The content is a localizable message with the given arguments.
26    // TODO: reduce usages of random i18n as much as possible
27    //
28    // It's ok to have random messages, just not at i18n step.
29    // Look for issue on `get_vartion` at Gitlab for more.
30    Localized {
31        /// i18n key
32        key: String,
33        /// Pseudorandom seed value that allows frontends to select a
34        /// deterministic (but pseudorandom) localised output
35        #[serde(default = "random_seed")]
36        seed: u16,
37        /// i18n arguments
38        #[serde(default)]
39        args: HashMap<String, LocalizationArg>,
40    },
41}
42
43/// A localisation argument for localised content (see [`Content::Localized`]).
44// TODO: Do we want it to be Enum or just wrapper around Content, to add
45// additional `impl From<T>` for our arguments?
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
47pub enum LocalizationArg {
48    /// The localisation argument is itself a section of content.
49    ///
50    /// Note that this allows [`Content`] to recursively refer to itself. It may
51    /// be tempting to decide to parameterise everything, having dialogue
52    /// generated with a compact tree. "It's simpler!", you might say. False.
53    /// Over-parameterisation is an anti-pattern that hurts translators. Where
54    /// possible, prefer fewer levels of nesting unless doing so would result
55    /// in an intractably larger number of combinations. See [here] for the
56    /// guidance provided by the docs for `fluent`, the localisation library
57    /// used by clients.
58    ///
59    /// [here]: https://github.com/projectfluent/fluent/wiki/Good-Practices-for-Developers#prefer-wet-over-dry
60    Content(Content),
61    /// The localisation argument is a natural number
62    Nat(u64),
63}
64
65impl From<Content> for LocalizationArg {
66    fn from(content: Content) -> Self { Self::Content(content) }
67}
68
69// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
70// discourage it)
71//
72// Or not?
73impl From<String> for LocalizationArg {
74    fn from(text: String) -> Self { Self::Content(Content::Plain(text)) }
75}
76
77// TODO: Remove impl and make use of `Content(Plain(...))` explicit (to
78// discourage it)
79//
80// Or not?
81impl<'a> From<&'a str> for LocalizationArg {
82    fn from(text: &'a str) -> Self { Self::Content(Content::Plain(text.to_string())) }
83}
84
85impl From<u64> for LocalizationArg {
86    fn from(n: u64) -> Self { Self::Nat(n) }
87}
88
89fn random_seed() -> u16 { rand::random() }
90
91impl Content {
92    pub fn localized(key: impl ToString) -> Self {
93        Self::Localized {
94            key: key.to_string(),
95            seed: random_seed(),
96            args: HashMap::default(),
97        }
98    }
99
100    pub fn localized_with_args<'a, A: Into<LocalizationArg>>(
101        key: impl ToString,
102        args: impl IntoIterator<Item = (&'a str, A)>,
103    ) -> Self {
104        Self::Localized {
105            key: key.to_string(),
106            seed: rand::random(),
107            args: args
108                .into_iter()
109                .map(|(k, v)| (k.to_string(), v.into()))
110                .collect(),
111        }
112    }
113
114    pub fn as_plain(&self) -> Option<&str> {
115        match self {
116            Self::Plain(text) => Some(text.as_str()),
117            Self::Localized { .. } | Self::Attr { .. } | Self::Key { .. } => None,
118        }
119    }
120}