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