rust_i18n_support/
config.rs

1//! This crate defines `struct`s that can be deserialized with Serde
2//! to load and inspect `Cargo.toml` metadata.
3//!
4//! See `Manifest::from_slice`.
5
6use itertools::Itertools;
7use serde::{Deserialize, Serialize};
8use std::fs;
9use std::io;
10use std::io::Read;
11use std::path::Path;
12
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(rename_all = "kebab-case")]
15pub struct I18nConfig {
16    #[serde(default = "default_locale")]
17    pub default_locale: String,
18    #[serde(default = "available_locales")]
19    pub available_locales: Vec<String>,
20    #[serde(default = "load_path")]
21    pub load_path: String,
22    #[serde(default = "fallback")]
23    pub fallback: Vec<String>,
24    #[serde(default = "minify_key")]
25    pub minify_key: bool,
26    #[serde(default = "minify_key_len")]
27    pub minify_key_len: usize,
28    #[serde(default = "minify_key_prefix")]
29    pub minify_key_prefix: String,
30    #[serde(default = "minify_key_thresh")]
31    pub minify_key_thresh: usize,
32}
33
34impl Default for I18nConfig {
35    fn default() -> Self {
36        Self {
37            default_locale: "en".to_string(),
38            available_locales: vec!["en".to_string()],
39            load_path: "./locales".to_string(),
40            fallback: vec![],
41            minify_key: crate::DEFAULT_MINIFY_KEY,
42            minify_key_len: crate::DEFAULT_MINIFY_KEY_LEN,
43            minify_key_prefix: crate::DEFAULT_MINIFY_KEY_PREFIX.to_string(),
44            minify_key_thresh: crate::DEFAULT_MINIFY_KEY_THRESH,
45        }
46    }
47}
48
49impl I18nConfig {
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    pub fn load(cargo_root: &Path) -> io::Result<Self> {
55        let cargo_file = cargo_root.join("Cargo.toml");
56        let mut file = fs::File::open(&cargo_file)
57            .unwrap_or_else(|e| panic!("Fail to open {}, {}", cargo_file.display(), e));
58
59        let mut contents = String::new();
60        file.read_to_string(&mut contents)?;
61
62        Self::parse(&contents)
63    }
64
65    pub fn parse(contents: &str) -> io::Result<Self> {
66        let package_metadata = contents.contains("[package.metadata.i18n]");
67        let workspace_metadata = contents.contains("[workspace.metadata.i18n]");
68
69        if !contents.contains("[i18n]") && !package_metadata && !workspace_metadata {
70            return Ok(I18nConfig::default());
71        }
72
73        let contents = if package_metadata {
74            contents.replace("[package.metadata.i18n]", "[i18n]")
75        } else if workspace_metadata {
76            contents.replace("[workspace.metadata.i18n]", "[i18n]")
77        } else {
78            contents.to_string()
79        };
80
81        let mut config: MainConfig = toml::from_str(&contents)
82            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
83
84        // Push default_locale
85        config
86            .i18n
87            .available_locales
88            .insert(0, config.i18n.default_locale.clone());
89
90        // unqiue
91        config.i18n.available_locales =
92            config.i18n.available_locales.into_iter().unique().collect();
93
94        Ok(config.i18n)
95    }
96}
97
98fn default_locale() -> String {
99    I18nConfig::default().default_locale
100}
101
102fn available_locales() -> Vec<String> {
103    I18nConfig::default().available_locales
104}
105
106fn load_path() -> String {
107    I18nConfig::default().load_path
108}
109
110fn fallback() -> Vec<String> {
111    I18nConfig::default().fallback
112}
113
114fn minify_key() -> bool {
115    I18nConfig::default().minify_key
116}
117
118fn minify_key_len() -> usize {
119    I18nConfig::default().minify_key_len
120}
121
122fn minify_key_prefix() -> String {
123    I18nConfig::default().minify_key_prefix
124}
125
126fn minify_key_thresh() -> usize {
127    I18nConfig::default().minify_key_thresh
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131#[serde(rename_all = "kebab-case")]
132pub struct MainConfig {
133    pub i18n: I18nConfig,
134}
135
136#[test]
137fn test_parse() {
138    let contents = r#"
139        [i18n]
140        default-locale = "en"
141        available-locales = ["zh-CN"]
142        load-path = "./my-locales"
143        fallback = ["zh"]
144        minify-key = true
145        minify-key-len = 12
146        minify-key-prefix = "T_"
147        minify-key-thresh = 16
148    "#;
149
150    let cfg = I18nConfig::parse(contents).unwrap();
151    assert_eq!(cfg.default_locale, "en");
152    assert_eq!(cfg.available_locales, vec!["en", "zh-CN"]);
153    assert_eq!(cfg.load_path, "./my-locales");
154    assert_eq!(cfg.fallback, vec!["zh"]);
155    assert!(cfg.minify_key);
156    assert_eq!(cfg.minify_key_len, 12);
157    assert_eq!(cfg.minify_key_prefix, "T_");
158    assert_eq!(cfg.minify_key_thresh, 16);
159
160    let contents = r#"
161        [i18n]
162        available-locales = ["zh-CN", "de", "de"]
163        load-path = "./my-locales"
164    "#;
165    let cfg = I18nConfig::parse(contents).unwrap();
166    assert_eq!(cfg.default_locale, "en");
167    assert_eq!(cfg.available_locales, vec!["en", "zh-CN", "de"]);
168    assert_eq!(cfg.load_path, "./my-locales");
169
170    let contents = "";
171    let cfg = I18nConfig::parse(contents).unwrap();
172    assert_eq!(cfg.default_locale, "en");
173    assert_eq!(cfg.available_locales, vec!["en"]);
174    assert_eq!(cfg.load_path, "./locales");
175}
176
177#[test]
178fn test_parse_with_metadata() {
179    let contents = r#"
180        [package.metadata.i18n]
181        default-locale = "en"
182        available-locales = ["zh-CN"]
183        load-path = "./my-locales"
184        fallback = ["zh"]
185        minify-key = true
186        minify-key-len = 12
187        minify-key-prefix = "T_"
188        minify-key-thresh = 16
189    "#;
190
191    let cfg = I18nConfig::parse(contents).unwrap();
192    assert_eq!(cfg.default_locale, "en");
193    assert_eq!(cfg.available_locales, vec!["en", "zh-CN"]);
194    assert_eq!(cfg.load_path, "./my-locales");
195    assert_eq!(cfg.fallback, vec!["zh"]);
196    assert!(cfg.minify_key);
197    assert_eq!(cfg.minify_key_len, 12);
198    assert_eq!(cfg.minify_key_prefix, "T_");
199    assert_eq!(cfg.minify_key_thresh, 16);
200}
201
202#[test]
203fn test_load_default() {
204    let workdir = Path::new(env!["CARGO_MANIFEST_DIR"]);
205
206    let cfg = I18nConfig::load(workdir).unwrap();
207    assert_eq!(cfg.default_locale, "en");
208    assert_eq!(cfg.available_locales, vec!["en"]);
209    assert_eq!(cfg.load_path, "./locales");
210}
211
212#[test]
213fn test_load() {
214    let workdir = Path::new(env!["CARGO_MANIFEST_DIR"]);
215    let cargo_root = workdir.join("../../examples/foo");
216
217    let cfg = I18nConfig::load(&cargo_root).unwrap();
218    assert_eq!(cfg.default_locale, "en");
219    assert_eq!(cfg.available_locales, vec!["en", "zh-CN"]);
220}