| 1 | //! Language picker for first-run onboarding (#566). |
| 2 | //! |
| 3 | //! Surfaces every locale the TUI ships translations for, plus an `auto` |
| 4 | //! option that defers to `LC_ALL` / `LANG`. Selection persists via |
| 5 | //! `Settings::save` immediately so the rest of onboarding (and every |
| 6 | //! subsequent session) reads the chosen tag. |
| 7 | |
| 8 | use ratatui::style::{Modifier, Style}; |
| 9 | use ratatui::text::{Line, Span}; |
| 10 | |
| 11 | use crate::palette; |
| 12 | use crate::tui::app::App; |
| 13 | |
| 14 | /// Locale options shown in the picker. Order matches the keyboard hotkeys |
| 15 | /// (1-5). Each entry is `(hotkey, settings_tag, native_name, english_label)`. |
| 16 | /// `settings_tag` is what `Settings::set("locale", …)` accepts and what |
| 17 | /// `localization::Locale` resolves on next read. |
| 18 | pub const LANGUAGE_OPTIONS: &[(char, &str, &str, &str)] = &[ |
| 19 | ('1', "auto", "Auto-detect", "(LC_ALL / LANG)"), |
| 20 | ('2', "en", "English", ""), |
| 21 | ('3', "ja", "日本語", "(Japanese)"), |
| 22 | ('4', "zh-Hans", "简体中文", "(Simplified Chinese)"), |
| 23 | ('5', "pt-BR", "Português (Brasil)", "(Brazilian Portuguese)"), |
| 24 | ]; |
| 25 | |
| 26 | pub fn lines(app: &App) -> Vec<Line<'static>> { |
| 27 | let current_owned = app.current_locale_tag(); |
| 28 | let current = current_owned.as_str(); |
| 29 | |
| 30 | let mut out: Vec<Line<'static>> = vec![ |
| 31 | Line::from(Span::styled( |
| 32 | "Choose your language", |
| 33 | Style::default() |
| 34 | .fg(palette::DEEPSEEK_SKY) |
| 35 | .add_modifier(Modifier::BOLD), |
| 36 | )), |
| 37 | Line::from(""), |
| 38 | Line::from(Span::styled( |
| 39 | "Pick the UI language. You can change it any time with `/settings set locale <tag>`.", |
| 40 | Style::default().fg(palette::TEXT_MUTED), |
| 41 | )), |
| 42 | Line::from(""), |
| 43 | ]; |
| 44 | |
| 45 | for (hotkey, tag, native, english) in LANGUAGE_OPTIONS { |
| 46 | let is_current = current == *tag; |
| 47 | let bullet = if is_current { "●" } else { "○" }; |
| 48 | let bullet_color = if is_current { |
| 49 | palette::DEEPSEEK_BLUE |
| 50 | } else { |
| 51 | palette::TEXT_MUTED |
| 52 | }; |
| 53 | let mut spans: Vec<Span<'static>> = vec![ |
| 54 | Span::styled(format!(" {bullet} "), Style::default().fg(bullet_color)), |
| 55 | Span::styled( |
| 56 | format!("[{hotkey}] "), |
| 57 | Style::default() |
| 58 | .fg(palette::TEXT_PRIMARY) |
| 59 | .add_modifier(Modifier::BOLD), |
| 60 | ), |
| 61 | Span::styled( |
| 62 | native.to_string(), |
| 63 | Style::default().fg(palette::TEXT_PRIMARY), |
| 64 | ), |
| 65 | ]; |
| 66 | if !english.is_empty() { |
| 67 | spans.push(Span::styled( |
| 68 | format!(" {english}"), |
| 69 | Style::default().fg(palette::TEXT_MUTED), |
| 70 | )); |
| 71 | } |
| 72 | out.push(Line::from(spans)); |
| 73 | } |
| 74 | |
| 75 | out.push(Line::from("")); |
| 76 | out.push(Line::from(vec![ |
| 77 | Span::styled("Press ", Style::default().fg(palette::TEXT_MUTED)), |
| 78 | Span::styled( |
| 79 | "1-5", |
| 80 | Style::default() |
| 81 | .fg(palette::TEXT_PRIMARY) |
| 82 | .add_modifier(Modifier::BOLD), |
| 83 | ), |
| 84 | Span::styled(" to choose, or ", Style::default().fg(palette::TEXT_MUTED)), |
| 85 | Span::styled( |
| 86 | "Enter", |
| 87 | Style::default() |
| 88 | .fg(palette::TEXT_PRIMARY) |
| 89 | .add_modifier(Modifier::BOLD), |
| 90 | ), |
| 91 | Span::styled( |
| 92 | " to keep the current setting", |
| 93 | Style::default().fg(palette::TEXT_MUTED), |
| 94 | ), |
| 95 | ])); |
| 96 | |
| 97 | out |
| 98 | } |
| 99 |