Skip to main content

cloudiful_bevy_localization/
localization.rs

1use crate::loader::load_tables;
2use crate::validation::validate_tables;
3use crate::{
4    Locale, LocalizationDefinition, LocalizationLoadError, TextKey, definition_registry,
5    locale_name_key_id,
6};
7use bevy::prelude::*;
8use std::collections::HashMap;
9
10#[derive(Resource, Debug, Clone)]
11pub struct Localization {
12    definition: &'static LocalizationDefinition,
13    locale: Locale,
14    fallback_locale: Locale,
15    tables: HashMap<&'static str, HashMap<String, String>>,
16}
17
18impl Default for Localization {
19    fn default() -> Self {
20        Self::load().expect("failed to load localization TOML files")
21    }
22}
23
24impl Localization {
25    pub fn load() -> Result<Self, LocalizationLoadError> {
26        Self::from_definition(definition_registry::active_definition())
27    }
28
29    pub fn from_definition(
30        definition: &'static LocalizationDefinition,
31    ) -> Result<Self, LocalizationLoadError> {
32        let fallback_locale = definition.fallback_locale();
33        let tables = load_tables(definition)?;
34        validate_tables(definition, &tables)?;
35
36        Ok(Self {
37            definition,
38            locale: fallback_locale,
39            fallback_locale,
40            tables,
41        })
42    }
43
44    pub fn text(&self, key: TextKey) -> &str {
45        self.lookup(self.locale, key)
46            .or_else(|| self.lookup(self.fallback_locale, key))
47            .unwrap_or_else(|| panic!("missing localization key: {}", key.id()))
48    }
49
50    pub fn format_text<'a, I>(&self, key: TextKey, values: I) -> String
51    where
52        I: IntoIterator<Item = (&'a str, &'a str)>,
53    {
54        let mut text = self.text(key).to_string();
55        for (name, value) in values {
56            text = text.replace(&format!("{{{name}}}"), value);
57        }
58        text
59    }
60
61    pub fn current_locale(&self) -> Locale {
62        self.locale
63    }
64
65    pub fn locale(&self) -> Locale {
66        self.current_locale()
67    }
68
69    pub fn available_locales(&self) -> Vec<Locale> {
70        self.definition.locales()
71    }
72
73    pub fn set_locale(&mut self, locale: Locale) {
74        self.locale = locale;
75    }
76
77    pub fn locale_display_text(&self, locale: Locale) -> &str {
78        let key_id = locale_name_key_id(locale.id());
79        self.lookup_id(self.locale, &key_id)
80            .or_else(|| self.lookup_id(self.fallback_locale, &key_id))
81            .unwrap_or(locale.id())
82    }
83
84    pub fn lookup(&self, locale: Locale, key: TextKey) -> Option<&str> {
85        self.tables
86            .get(locale.id())
87            .and_then(|table| table.get(key.id()))
88            .map(String::as_str)
89    }
90
91    pub fn lookup_id(&self, locale: Locale, key_id: &str) -> Option<&str> {
92        self.tables
93            .get(locale.id())
94            .and_then(|table| table.get(key_id))
95            .map(String::as_str)
96    }
97
98    pub fn table(&self, locale: Locale) -> &HashMap<String, String> {
99        self.tables
100            .get(locale.id())
101            .unwrap_or_else(|| panic!("missing locale table for {}", locale.id()))
102    }
103}
104
105pub struct LocalizationPlugin {
106    definition: &'static LocalizationDefinition,
107}
108
109impl LocalizationPlugin {
110    pub const fn new(definition: &'static LocalizationDefinition) -> Self {
111        Self { definition }
112    }
113}
114
115impl Plugin for LocalizationPlugin {
116    fn build(&self, app: &mut App) {
117        definition_registry::register_definition(self.definition);
118        app.insert_resource(
119            Localization::from_definition(self.definition)
120                .expect("failed to load localization TOML files"),
121        );
122    }
123}