browser_automation_cli/i18n/
ftl.rs1use fluent::{FluentBundle, FluentResource};
5use unic_langid::LanguageIdentifier;
6
7use super::idioma::Idioma;
8use super::mensagem::{ftl_id, Mensagem};
9
10const EN_FTL: &str = include_str!("../../locales/en.ftl");
11const PT_BR_FTL: &str = include_str!("../../locales/pt-BR.ftl");
12
13pub fn ftl_source(idioma: Idioma) -> &'static str {
15 match idioma {
16 Idioma::En => EN_FTL,
17 Idioma::PtBr => PT_BR_FTL,
18 }
19}
20
21pub fn bundle_for(idioma: Idioma) -> Result<FluentBundle<FluentResource>, String> {
23 let lang: LanguageIdentifier = idioma.language_identifier();
24 let mut bundle = FluentBundle::new(vec![lang]);
25 bundle.set_use_isolating(false);
27 let res = FluentResource::try_new(ftl_source(idioma).to_string())
28 .map_err(|(_, errs)| format!("FTL parse errors for {}: {errs:?}", idioma.bcp47()))?;
29 bundle
30 .add_resource(res)
31 .map_err(|errs| format!("FTL add_resource {}: {errs:?}", idioma.bcp47()))?;
32 Ok(bundle)
33}
34
35pub fn format_ftl(idioma: Idioma, msg: Mensagem) -> String {
37 let id = ftl_id(msg);
38 match bundle_for(idioma) {
39 Ok(bundle) => {
40 if let Some(message) = bundle.get_message(id) {
41 if let Some(pattern) = message.value() {
42 let mut errors = vec![];
43 let s = bundle.format_pattern(pattern, None, &mut errors);
44 if errors.is_empty() && !s.is_empty() {
45 return s.to_string();
46 }
47 }
48 }
49 msg.texto(idioma).to_string()
50 }
51 Err(_) => msg.texto(idioma).to_string(),
52 }
53}
54
55pub fn ftl_keys(source: &str) -> Vec<String> {
57 let mut keys = Vec::new();
58 for line in source.lines() {
59 let t = line.trim();
60 if t.is_empty() || t.starts_with('#') {
61 continue;
62 }
63 if let Some((key, _)) = t.split_once('=') {
64 let key = key.trim();
65 if !key.is_empty() && !key.starts_with('-') {
66 keys.push(key.to_string());
67 }
68 }
69 }
70 keys.sort();
71 keys.dedup();
72 keys
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn ftl_en_pt_key_parity() {
81 let en = ftl_keys(EN_FTL);
82 let pt = ftl_keys(PT_BR_FTL);
83 assert_eq!(en, pt, "en.ftl and pt-BR.ftl key sets must match");
84 assert!(!en.is_empty());
85 }
86
87 #[test]
88 fn ftl_keys_cover_all_mensagem() {
89 let en = ftl_keys(EN_FTL);
90 for m in Mensagem::ALL {
91 let id = ftl_id(*m);
92 assert!(
93 en.iter().any(|k| k == id),
94 "missing FTL key {id} for {m:?}"
95 );
96 }
97 }
98
99 #[test]
100 fn fluent_parses_both_packs() {
101 bundle_for(Idioma::En).expect("en FTL");
102 bundle_for(Idioma::PtBr).expect("pt-BR FTL");
103 }
104
105 #[test]
106 fn ftl_format_matches_enum_for_usage() {
107 let en = format_ftl(Idioma::En, Mensagem::UsageSuggestion);
108 assert_eq!(en, Mensagem::UsageSuggestion.texto(Idioma::En));
109 let pt = format_ftl(Idioma::PtBr, Mensagem::VisionRequired);
110 assert!(pt.contains("invocação"), "{pt}");
111 }
112}