rust_i18n_support/
config.rs1use 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 config
86 .i18n
87 .available_locales
88 .insert(0, config.i18n.default_locale.clone());
89
90 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}