1use std::hash::Hash;
2use std::{collections::HashMap, fmt::Display, str::FromStr};
5
6use bevy_reflect::Reflect;
7use itertools::{iproduct, Itertools};
8use poise::{Command, CommandParameter, CommandParameterChoice};
9use rusty18n::{I18NAccess, I18NFallback, I18NReflected, I18NTrait, I18NWrapper, R};
10use strum::{Display, EnumIter, IntoEnumIterator};
11
12pub trait PoiseI18NMeta<
13 'a,
14 K: Eq + Hash + Default + Copy + ToString + FromStr + Display,
15 V: I18NFallback,
16>
17{
18 fn locales(&self) -> &'a I18NWrapper<K, V>;
20}
21
22pub trait PoiseI18NTrait<
24 'a,
25 K: Eq + Hash + Default + Copy + ToString + FromStr + Display,
26 V: I18NFallback,
27>
28{
29 fn i18n(&'a self) -> I18NAccess<I18NWrapper<K, V>>;
31 fn i18n_explicit(&'a self, wrapper: &'a I18NWrapper<K, V>)
32 -> I18NAccess<'a, I18NWrapper<K, V>>;
33}
34
35impl<
36 'a,
37 K: Eq + Hash + Default + Copy + ToString + FromStr + Display + 'a,
38 V: I18NFallback,
39 U,
40 E,
41 > PoiseI18NTrait<'a, K, V> for poise::Context<'a, U, E>
42where
43 Self: PoiseI18NMeta<'a, K, V>,
44{
45 fn i18n(&'a self) -> I18NAccess<I18NWrapper<K, V>> {
46 self.i18n_explicit(self.locales())
47 }
48
49 fn i18n_explicit(
50 &'a self,
51 wrapper: &'a I18NWrapper<K, V>,
52 ) -> I18NAccess<'a, I18NWrapper<K, V>> {
53 wrapper.get(K::from_str(self.locale().unwrap_or_default()).unwrap_or_default())
54 }
55}
56
57#[derive(Display, EnumIter, Clone)]
58#[strum(serialize_all = "snake_case")]
59enum CommandLocalization {
60 Name,
61 Description,
62}
63
64struct I18NAccesses<'a, L: I18NTrait>(Vec<(String, I18NAccess<'a, L>)>);
65
66pub fn apply_translations<
67 K: Eq + Hash + Default + Copy + ToString + FromStr + Display,
68 V: I18NFallback + Reflect,
69 U,
70 E,
71>(
72 commands: &mut [Command<U, E>],
73 wrapper: &I18NWrapper<K, V>,
74) {
75 apply_translation(
76 commands,
77 &I18NAccesses(
78 wrapper
79 .store
80 .0
81 .keys()
82 .map(|key| (key.to_string(), wrapper.get(*key)))
83 .collect_vec(),
84 ),
85 )
86}
87
88trait PoiseI18NLocalizable {
89 fn name_localizations(&mut self) -> &mut HashMap<String, String>;
90 fn description_localizations(&mut self) -> Option<&mut HashMap<String, String>>;
91}
92
93macro_rules! impl_localizable {
94 ($struct:ident) => {
95 impl<U, E> PoiseI18NLocalizable for $struct<U, E> {
96 fn name_localizations(&mut self) -> &mut HashMap<String, String> {
97 &mut self.name_localizations
98 }
99
100 fn description_localizations(&mut self) -> Option<&mut HashMap<String, String>> {
101 Some(&mut self.description_localizations)
102 }
103 }
104 };
105}
106
107impl_localizable!(Command);
108impl_localizable!(CommandParameter);
109
110impl PoiseI18NLocalizable for CommandParameterChoice {
111 fn name_localizations(&mut self) -> &mut HashMap<String, String> {
112 &mut self.localizations
113 }
114
115 fn description_localizations(&mut self) -> Option<&mut HashMap<String, String>> {
116 None
117 }
118}
119
120fn apply_localization<L: I18NTrait>(
121 path: &mut Vec<String>,
122 next_tag: String,
123 localizable: &mut impl PoiseI18NLocalizable,
124 locale_accesses: &I18NAccesses<'_, L>,
125) where
126 L::K: Display,
127 L::V: Reflect,
128{
129 path.push(next_tag);
130
131 let locale_tags = CommandLocalization::iter()
132 .map(|l| {
133 let mut path_new = path.clone();
134
135 path_new.push(l.to_string());
136
137 let path_string = path.iter().join(".");
138
139 (l, path_string)
140 })
141 .collect_vec();
142
143 let permutations = iproduct!(&locale_accesses.0, &locale_tags);
146
147 for ((lang_key, access), (locale_type, tag)) in permutations {
148 let possible_resource = access.by_path::<R>(tag);
149
150 let Some(localized_key) = possible_resource else {
151 continue;
152 };
153
154 let lang_key = lang_key.clone();
155 let localized_key = localized_key.clone();
156
157 match locale_type {
158 CommandLocalization::Name => {
159 localizable
160 .name_localizations()
161 .insert(lang_key, localized_key);
162 }
163 CommandLocalization::Description => {
164 match localizable.description_localizations() {
165 Some(v) => v.insert(lang_key, localized_key),
166 None => {
167 continue;
168 }
169 };
170 }
171 };
172 }
173}
174
175fn apply_translation<L: I18NTrait, U, E>(
176 commands: &mut [Command<U, E>],
177 locale_accesses: &I18NAccesses<L>,
178) where
179 L::K: Display,
180 L::V: Reflect,
181{
182 for command in commands {
183 let mut path_vec = vec![];
184
185 apply_translation(&mut command.subcommands, locale_accesses);
187
188 apply_localization(
192 &mut path_vec,
193 command.name.clone(),
194 command,
195 locale_accesses,
196 );
197
198 for param in &mut command.parameters {
199 apply_localization(&mut path_vec, param.name.clone(), param, locale_accesses);
200
201 for choice in &mut param.choices {
202 apply_localization(&mut path_vec, choice.name.clone(), choice, locale_accesses)
203 }
204 }
205 }
206}