1use crate::prelude::*;
5
6use super::{Lang, LangCode};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[non_exhaustive]
11pub enum TextPart {
12 Screen,
14 Data,
17 Dialogue,
20 LangName,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[non_exhaustive]
28pub struct TextAt<'a> {
29 pub lang: Option<&'a LangCode>,
31 pub part: TextPart,
33 pub key: &'a str,
36 pub file: &'a str,
40 source: &'a LangCode,
42}
43
44const NAME_KEY: &str = "name";
46
47impl TextAt<'_> {
48 pub fn path(&self) -> String {
58 let Some(code) = self.lang else {
59 return if self.part == TextPart::Dialogue && !self.file.is_empty() {
60 format!("{}.dlg", self.file)
61 } else {
62 self.source.to_string()
63 };
64 };
65 match self.part {
66 TextPart::Screen => format!("{code}/{}", super::UI_FILE),
67 TextPart::Data => format!("{code}/{}", super::DATA_FILE),
68 TextPart::LangName => format!("{code}/{}", super::INFO_FILE),
69 TextPart::Dialogue if self.file.is_empty() => format!("{code}/{}", super::DIALOGUE_DIR),
70 TextPart::Dialogue => format!(
71 "{code}/{}{}{}",
72 super::DIALOGUE_DIR,
73 self.file,
74 super::DIALOGUE_EXTENSION
75 ),
76 }
77 }
78}
79
80impl Lang {
81 pub fn texts(&self) -> Vec<(TextAt<'_>, &str)> {
88 let source = &self.written_in.code;
89 let at = |lang, part, key, file| TextAt {
90 lang,
91 part,
92 key,
93 file,
94 source,
95 };
96 let mut texts = Vec::new();
97 for (key, text) in &self.ui {
98 texts.push((at(None, TextPart::Screen, key, ""), text.as_str()));
99 }
100 for (key, text) in &self.source.data {
101 texts.push((at(None, TextPart::Data, key, ""), text.as_str()));
102 }
103 for (key, line) in &self.source.lines {
104 let file = line.file.as_str();
105 texts.push((at(None, TextPart::Dialogue, key, file), line.text.as_str()));
106 }
107 for (code, pack) in &self.packs {
108 let lang = Some(code);
109 texts.push((
110 at(lang, TextPart::LangName, NAME_KEY, ""),
111 pack.info.name.as_str(),
112 ));
113 for (key, entry) in &pack.ui {
114 texts.push((at(lang, TextPart::Screen, key, ""), entry.text.as_str()));
115 }
116 for (key, entry) in &pack.data {
117 texts.push((at(lang, TextPart::Data, key, ""), entry.text.as_str()));
118 }
119 for (key, entry) in &pack.lines {
120 let file = self.source.lines.get(key).map_or("", |l| l.file.as_str());
121 for text in entry.text.all() {
122 texts.push((at(lang, TextPart::Dialogue, key, file), text));
123 }
124 }
125 }
126 texts
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use alloc::collections::BTreeMap;
133
134 use super::*;
135 use crate::lang::{Entry, LangInfo, LangPack, LineEntry, LineText, MadeBy};
136 use crate::lang::{LineKind, SourceLang, SourceLine, SourceText};
137 use crate::names::Names;
138
139 fn owned(pairs: &[(&str, &str)]) -> BTreeMap<String, String> {
140 pairs
141 .iter()
142 .map(|&(k, v)| (k.to_owned(), v.to_owned()))
143 .collect()
144 }
145
146 fn entry(key: &str, text: &str) -> (String, Entry) {
147 let entry = Entry {
148 key: key.to_owned(),
149 source: String::new(),
150 text: text.to_owned(),
151 };
152 (key.to_owned(), entry)
153 }
154
155 fn line(key: &str, text: LineText) -> (String, LineEntry) {
156 let source = String::new();
157 let key = key.to_owned();
158 (key.clone(), LineEntry { key, source, text })
159 }
160
161 fn lang() -> Lang {
165 let names = Names {
166 names: owned(&[("king", "Osgar")]),
167 };
168 let mut source = SourceText::new(owned(&[("items.rope", "Rope")]), names);
169 let english_line = SourceLine {
170 scene: "gate".to_owned(),
171 text: "Halt.".to_owned(),
172 kind: LineKind::Speech,
173 file: "gate".to_owned(),
174 };
175 source.lines.insert("gate.l1".to_owned(), english_line);
176 let pack = LangPack {
177 info: LangInfo {
178 name: "Русский".to_owned(),
179 made_by: MadeBy::Machine,
180 wide_font: None,
181 wide_punctuation: false,
182 },
183 ui: [entry("menu.title", "Жa")].into(),
184 data: [entry("names.king", "Осгар")].into(),
185 lines: [
186 line(
187 "gate.l1",
188 LineText::Variants(owned(&[("a", "Стой."), ("b", "Стой!")])),
189 ),
190 line("gate.old", LineText::One("Был.".to_owned())),
191 ]
192 .into(),
193 };
194 let code = LangCode::new("ru").unwrap();
195 Lang::with_source(
196 owned(&[("menu.title", "Start")]),
197 source,
198 [(code, pack)].into(),
199 )
200 }
201
202 #[test]
203 fn texts_list_english_then_each_pack_with_part_key_and_file() {
204 use TextPart::{Data, Dialogue, LangName, Screen};
205 let lang = lang();
206 let ru = LangCode::new("ru").unwrap();
207 let found: Vec<(Option<&str>, TextPart, &str, &str, &str)> = lang
208 .texts()
209 .into_iter()
210 .map(|(at, text)| {
211 (
212 at.lang.map(LangCode::as_str),
213 at.part,
214 at.key,
215 at.file,
216 text,
217 )
218 })
219 .collect();
220 let (en, ru) = (None, Some(ru.as_str()));
221 assert_eq!(
222 found,
223 [
224 (en, Screen, "menu.title", "", "Start"),
225 (en, Data, "items.rope", "", "Rope"),
226 (en, Data, "names.king", "", "Osgar"),
227 (en, Dialogue, "gate.l1", "gate", "Halt."),
228 (ru, LangName, "name", "", "Русский"),
229 (ru, Screen, "menu.title", "", "Жa"),
230 (ru, Data, "names.king", "", "Осгар"),
231 (ru, Dialogue, "gate.l1", "gate", "Стой."),
232 (ru, Dialogue, "gate.l1", "gate", "Стой!"),
233 (ru, Dialogue, "gate.old", "", "Был."),
234 ]
235 );
236 assert_eq!(Lang::default().texts(), []);
237 }
238
239 #[test]
240 fn each_text_names_the_file_it_is_from() {
241 let lang = lang();
242 let paths: Vec<String> = lang.texts().iter().map(|(at, _)| at.path()).collect();
243 assert_eq!(
244 paths,
245 [
246 "en",
247 "en",
248 "en",
249 "gate.dlg",
250 "ru/lang.ron",
251 "ru/ui.ron",
252 "ru/data.ron",
253 "ru/dialogue/gate.ron",
254 "ru/dialogue/gate.ron",
255 "ru/dialogue/",
256 ]
257 );
258 let en = LangCode::default();
260 let unfiled = TextAt {
261 lang: None,
262 part: TextPart::Dialogue,
263 key: "a.l1",
264 file: "",
265 source: &en,
266 };
267 assert_eq!(unfiled.path(), "en");
268 }
269
270 #[test]
271 fn a_declared_sources_texts_name_its_folder() {
272 let ja = SourceLang::new(LangCode::new("ja").unwrap(), "日本語");
273 let lang = lang().with_source_lang(ja);
274 let paths: Vec<String> = lang.texts().iter().map(|(at, _)| at.path()).collect();
275 assert_eq!(paths[..4], ["ja", "ja", "ja", "gate.dlg"]);
276 }
277}