Skip to main content

globetrotter_model/
json.rs

1use crate::{Language, TemplateEngine, diagnostics::Spanned};
2use indexmap::IndexMap;
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
6pub enum Translation {
7    #[serde(rename = "literal")]
8    Literal(String),
9    #[serde(rename = "template")]
10    Template(String),
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
14#[derive(Default)]
15pub enum Version {
16    #[serde(rename = "1", alias = "v1")]
17    #[default]
18    V1,
19    // #[serde(rename = "latest")]
20    // Latest,
21}
22
23
24#[derive(thiserror::Error, Debug)]
25pub enum Error {
26    #[error("missing translation of key {key:?} for language {language:?}")]
27    MissingKey {
28        key: Spanned<String>,
29        language: Language,
30    },
31    #[error(transparent)]
32    Json(#[from] serde_json::Error),
33}
34
35#[derive(Debug, Clone, Deserialize, Serialize)]
36pub struct Translations {
37    #[serde(default)]
38    pub version: Version,
39    pub template_engine: Option<TemplateEngine>,
40    pub language: Language,
41    pub translations: IndexMap<String, Translation>,
42}
43
44impl crate::Translations {
45    /// Write translations for a single language as pretty-printed JSON.
46    ///
47    /// # Errors
48    ///
49    /// Returns an error if JSON serialization fails or if a required
50    /// translation is missing while `strict` is enabled.
51    pub fn write_translations_json(
52        &self,
53        language: Language,
54        template_engine: Option<TemplateEngine>,
55        strict: bool,
56        writer: impl std::io::Write,
57    ) -> Result<Translations, Error> {
58        let translations = self.translations_json(language, strict, template_engine)?;
59        serde_json::to_writer_pretty(writer, &translations)?;
60        Ok(translations)
61    }
62
63    /// Build the JSON representation for a single language without writing it.
64    ///
65    /// # Errors
66    ///
67    /// Returns an error if a required translation is missing while `strict`
68    /// is enabled.
69    pub fn translations_json(
70        &self,
71        language: Language,
72        strict: bool,
73        template_engine: Option<TemplateEngine>,
74    ) -> Result<Translations, Error> {
75        let translations = self
76            .0
77            .iter()
78            .map(
79                |(key, translation)| match translation.language.get(&language) {
80                    Some(t) => {
81                        let value = if translation.is_template() {
82                            Translation::Template(t.as_ref().clone())
83                        } else {
84                            Translation::Literal(t.as_ref().clone())
85                        };
86                        Ok((key.clone().into_inner(), value))
87                    }
88                    None if strict => Err(Error::MissingKey {
89                        key: key.clone(),
90                        language,
91                    }),
92                    None => Ok((
93                        key.clone().into_inner(),
94                        Translation::Literal(format!("missing translation {key} for {language:?}")),
95                    )),
96                },
97            )
98            .collect::<Result<IndexMap<_, _>, _>>()?;
99        Ok(Translations {
100            version: Version::V1,
101            template_engine,
102            translations,
103            language,
104        })
105    }
106}