use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use crate::localization::MessageId;
use crate::palette;
use crate::tui::app::App;
pub const LANGUAGE_OPTIONS: &[(char, &str, &str, &str)] = &[
('1', "auto", "Auto-detect", "(LC_ALL / LANG)"),
('2', "en", "English", ""),
('3', "ja", "日本語", "(Japanese)"),
('4', "zh-Hans", "简体中文", "(Simplified Chinese)"),
('5', "zh-Hant", "繁體中文", "(Traditional Chinese)"),
('6', "pt-BR", "Português (Brasil)", "(Brazilian Portuguese)"),
(
'7',
"es-419",
"Español (Latinoamérica)",
"(Latin American Spanish)",
),
('8', "vi", "Tiếng Việt", "(Vietnamese)"),
('9', "ko", "한국어", "(Korean)"),
('a', "ca", "Català", "(Catalan)"),
('b', "de", "Deutsch", "(German)"),
('c', "fr", "Français", "(French)"),
('d', "id", "Bahasa Indonesia", "(Indonesian)"),
('e', "hi", "हिन्दी", "(Hindi)"),
('f', "ru", "Русский", "(Russian)"),
('g', "uk", "Українська", "(Ukrainian)"),
];
pub fn lines(app: &App) -> Vec<Line<'static>> {
let current_owned = app.current_locale_tag();
let current = current_owned.as_str();
let mut out: Vec<Line<'static>> = vec![
Line::from(Span::styled(
app.tr(MessageId::OnboardLanguageTitle).to_string(),
Style::default()
.fg(palette::WHALE_INFO)
.add_modifier(Modifier::BOLD),
)),
Line::from(""),
Line::from(Span::styled(
app.tr(MessageId::OnboardLanguageBlurb).to_string(),
Style::default().fg(palette::TEXT_MUTED),
)),
Line::from(""),
];
for (hotkey, tag, native, english) in LANGUAGE_OPTIONS {
let is_current = current == *tag;
let bullet = if is_current {
crate::tui::glyphs::CURRENT
} else {
crate::tui::glyphs::AVAILABLE
};
let bullet_color = if is_current {
palette::WHALE_ACTION
} else {
palette::TEXT_MUTED
};
let mut spans: Vec<Span<'static>> = vec![
Span::styled(format!(" {bullet} "), Style::default().fg(bullet_color)),
Span::styled(
format!("[{hotkey}] "),
Style::default()
.fg(palette::TEXT_PRIMARY)
.add_modifier(Modifier::BOLD),
),
Span::styled(
native.to_string(),
Style::default().fg(palette::TEXT_PRIMARY),
),
];
if !english.is_empty() {
spans.push(Span::styled(
format!(" {english}"),
Style::default().fg(palette::TEXT_MUTED),
));
}
out.push(Line::from(spans));
}
out.push(Line::from(""));
out.push(Line::from(Span::styled(
app.tr(MessageId::OnboardLanguageFooter).to_string(),
Style::default().fg(palette::TEXT_MUTED),
)));
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::localization::Locale;
#[test]
fn picker_offers_every_shipped_locale() {
let offered: Vec<&str> = LANGUAGE_OPTIONS.iter().map(|(_, tag, _, _)| *tag).collect();
assert!(
offered.contains(&"auto"),
"picker must keep the auto-detect entry"
);
for locale in Locale::shipped() {
let tag = locale.tag();
assert!(
offered.contains(&tag),
"shipped locale {tag} is not offered in the language picker"
);
}
}
#[test]
fn picker_hotkeys_are_contiguous_digits_then_letters() {
for (idx, (hotkey, tag, _, _)) in LANGUAGE_OPTIONS.iter().enumerate() {
let expected = if idx < 9 {
char::from_digit((idx + 1) as u32, 10).expect("digit")
} else {
char::from_u32('a' as u32 + (idx - 9) as u32).expect("letter")
};
assert_eq!(
*hotkey, expected,
"option {tag} should use hotkey {expected}, not {hotkey}"
);
}
}
}