cloudiful_bevy_localization/
lib.rs1mod definition;
2mod definition_registry;
3mod error;
4mod loader;
5mod locale;
6mod localization;
7mod text_key;
8mod validation;
9
10pub use definition::{LocaleSource, LocalizationDefinition};
11pub use definition_registry::register_definition;
12pub use error::LocalizationLoadError;
13pub use locale::Locale;
14pub use localization::{Localization, LocalizationPlugin};
15pub use text_key::TextKey;
16
17pub fn locale_name_key_id(locale_id: &str) -> String {
18 format!(
19 "common.locale_name.{}",
20 locale_id.replace('-', "_").to_ascii_lowercase()
21 )
22}
23
24#[cfg(test)]
25mod tests {
26 use super::*;
27
28 const BASE_KEYS: &[TextKey] = &[
29 TextKey::new("common.greeting"),
30 TextKey::new("common.nested.label"),
31 ];
32 const BASE_SOURCES: &[LocaleSource] = &[
33 LocaleSource {
34 locale: "en-US",
35 namespace: "common",
36 contents: r#"
37greeting = "Hello {name}"
38[locale_name]
39en_us = "English"
40zh_cn = "Chinese"
41[nested]
42label = "Nested"
43"#,
44 },
45 LocaleSource {
46 locale: "zh-CN",
47 namespace: "common",
48 contents: r#"
49greeting = "你好 {name}"
50[locale_name]
51en_us = "英语"
52zh_cn = "中文"
53[nested]
54label = "嵌套"
55"#,
56 },
57 ];
58 static BASE_DEFINITION: LocalizationDefinition = LocalizationDefinition {
59 fallback_locale: "en-US",
60 locales: &["en-US", "zh-CN"],
61 sources: BASE_SOURCES,
62 keys: BASE_KEYS,
63 };
64
65 #[test]
66 fn locale_files_are_flattened() {
67 let localization =
68 Localization::from_definition(&BASE_DEFINITION).expect("base definition should load");
69
70 assert_eq!(
71 localization.lookup_id(Locale::new("en-US"), "common.nested.label"),
72 Some("Nested")
73 );
74 }
75
76 #[test]
77 fn missing_keys_fail_validation() {
78 const KEYS: &[TextKey] = &[TextKey::new("common.greeting"), TextKey::new("common.bye")];
79 static DEFINITION: LocalizationDefinition = LocalizationDefinition {
80 fallback_locale: "en-US",
81 locales: &["en-US"],
82 sources: &[LocaleSource {
83 locale: "en-US",
84 namespace: "common",
85 contents: r#"
86greeting = "Hello"
87[locale_name]
88en_us = "English"
89"#,
90 }],
91 keys: KEYS,
92 };
93
94 let err = Localization::from_definition(&DEFINITION).expect_err("missing key should fail");
95 assert!(
96 err.to_string()
97 .contains("missing localization key 'common.bye'")
98 );
99 }
100
101 #[test]
102 fn placeholder_mismatch_fails_validation() {
103 static DEFINITION: LocalizationDefinition = LocalizationDefinition {
104 fallback_locale: "en-US",
105 locales: &["en-US", "zh-CN"],
106 sources: &[
107 LocaleSource {
108 locale: "en-US",
109 namespace: "common",
110 contents: r#"
111greeting = "Hello {name}"
112[locale_name]
113en_us = "English"
114zh_cn = "Chinese"
115[nested]
116label = "Nested"
117"#,
118 },
119 LocaleSource {
120 locale: "zh-CN",
121 namespace: "common",
122 contents: r#"
123greeting = "你好 {user}"
124[locale_name]
125en_us = "英语"
126zh_cn = "中文"
127[nested]
128label = "嵌套"
129"#,
130 },
131 ],
132 keys: BASE_KEYS,
133 };
134
135 let err = Localization::from_definition(&DEFINITION)
136 .expect_err("placeholder mismatch should fail");
137 assert!(err.to_string().contains("placeholder mismatch"));
138 }
139
140 #[test]
141 fn text_lookup_falls_back_to_fallback_locale() {
142 let mut localization =
143 Localization::from_definition(&BASE_DEFINITION).expect("base definition should load");
144
145 localization.set_locale(Locale::new("fr-FR"));
146
147 assert_eq!(
148 localization.text(TextKey::new("common.greeting")),
149 "Hello {name}"
150 );
151 assert_eq!(
152 localization.format_text(TextKey::new("common.greeting"), [("name", "Alex")]),
153 "Hello Alex"
154 );
155 }
156}