Skip to main content

cloudiful_bevy_localization/
lib.rs

1#![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
13/// Static locale sources and definitions supplied by the downstream app.
14pub use definition::{LocaleSource, LocalizationDefinition};
15/// Registers the active [`LocalizationDefinition`] for helper lookups.
16pub use definition_registry::register_definition;
17/// Error returned when loading or validating a [`LocalizationDefinition`].
18pub use error::LocalizationLoadError;
19/// Runtime locale handle used by [`Localization`].
20pub use locale::Locale;
21/// Main localization resource and plugin.
22pub use localization::{Localization, LocalizationPlugin};
23/// Runtime text-key handle used by [`Localization`].
24pub use text_key::TextKey;
25
26/// Returns the generated key id for a locale display name entry.
27pub 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}