cloudiful_bevy_localization/
localization.rs1use 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}