Internationalize UI text w/ german as first second lang
Adds rust-i18n and refactors all of the text copy in the app to use the translation function so that the UI language can be dynamically updated in the settings.
This commit is contained in:
@@ -4,6 +4,7 @@ use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use ratatui::widgets::{Block, Paragraph, Widget};
|
||||
|
||||
use crate::i18n::t;
|
||||
use crate::session::result::DrillResult;
|
||||
use crate::ui::layout::pack_hint_lines;
|
||||
use crate::ui::theme::Theme;
|
||||
@@ -32,8 +33,9 @@ impl Widget for Dashboard<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let colors = &self.theme.colors;
|
||||
|
||||
let title_text = t!("dashboard.title");
|
||||
let block = Block::bordered()
|
||||
.title(" Drill Complete ")
|
||||
.title(title_text.to_string())
|
||||
.border_style(Style::default().fg(colors.accent()))
|
||||
.style(Style::default().bg(colors.bg()));
|
||||
let inner = block.inner(area);
|
||||
@@ -42,12 +44,17 @@ impl Widget for Dashboard<'_> {
|
||||
let footer_line_count = if self.input_lock_remaining_ms.is_some() {
|
||||
1u16
|
||||
} else {
|
||||
let hint_continue = t!("dashboard.hint_continue");
|
||||
let hint_retry = t!("dashboard.hint_retry");
|
||||
let hint_menu = t!("dashboard.hint_menu");
|
||||
let hint_stats = t!("dashboard.hint_stats");
|
||||
let hint_delete = t!("dashboard.hint_delete");
|
||||
let hints = [
|
||||
"[c/Enter/Space] Continue",
|
||||
"[r] Retry",
|
||||
"[q] Menu",
|
||||
"[s] Stats",
|
||||
"[x] Delete",
|
||||
hint_continue.as_ref(),
|
||||
hint_retry.as_ref(),
|
||||
hint_menu.as_ref(),
|
||||
hint_stats.as_ref(),
|
||||
hint_delete.as_ref(),
|
||||
];
|
||||
pack_hint_lines(&hints, inner.width as usize).len().max(1) as u16
|
||||
};
|
||||
@@ -65,25 +72,32 @@ impl Widget for Dashboard<'_> {
|
||||
])
|
||||
.split(inner);
|
||||
|
||||
let results_label = t!("dashboard.results");
|
||||
let mut title_spans = vec![Span::styled(
|
||||
"Results",
|
||||
results_label.to_string(),
|
||||
Style::default()
|
||||
.fg(colors.accent())
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)];
|
||||
if !self.result.ranked {
|
||||
let unranked_note = format!(
|
||||
"{}\u{2014}{}",
|
||||
t!("dashboard.unranked_note_prefix"),
|
||||
t!("dashboard.unranked_note_suffix")
|
||||
);
|
||||
title_spans.push(Span::styled(
|
||||
" (Unranked \u{2014} does not count toward skill tree)",
|
||||
unranked_note,
|
||||
Style::default().fg(colors.text_pending()),
|
||||
));
|
||||
}
|
||||
let title = Paragraph::new(Line::from(title_spans)).alignment(Alignment::Center);
|
||||
title.render(layout[0], buf);
|
||||
|
||||
let speed_label = t!("dashboard.speed");
|
||||
let wpm_text = format!("{:.0} WPM", self.result.wpm);
|
||||
let cpm_text = format!(" ({:.0} CPM)", self.result.cpm);
|
||||
let wpm_line = Line::from(vec![
|
||||
Span::styled(" Speed: ", Style::default().fg(colors.fg())),
|
||||
Span::styled(speed_label.to_string(), Style::default().fg(colors.fg())),
|
||||
Span::styled(
|
||||
&*wpm_text,
|
||||
Style::default()
|
||||
@@ -101,31 +115,31 @@ impl Widget for Dashboard<'_> {
|
||||
} else {
|
||||
colors.error()
|
||||
};
|
||||
let accuracy_label = t!("dashboard.accuracy_label");
|
||||
let acc_text = format!("{:.1}%", self.result.accuracy);
|
||||
let acc_detail = format!(
|
||||
" ({}/{} correct)",
|
||||
self.result.correct, self.result.total_chars
|
||||
);
|
||||
let acc_detail = t!("dashboard.correct_detail", correct = self.result.correct, total = self.result.total_chars);
|
||||
let acc_line = Line::from(vec![
|
||||
Span::styled(" Accuracy: ", Style::default().fg(colors.fg())),
|
||||
Span::styled(accuracy_label.to_string(), Style::default().fg(colors.fg())),
|
||||
Span::styled(
|
||||
&*acc_text,
|
||||
Style::default().fg(acc_color).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::styled(&*acc_detail, Style::default().fg(colors.text_pending())),
|
||||
Span::styled(acc_detail.to_string(), Style::default().fg(colors.text_pending())),
|
||||
]);
|
||||
Paragraph::new(acc_line).render(layout[2], buf);
|
||||
|
||||
let time_label = t!("dashboard.time_label");
|
||||
let time_text = format!("{:.1}s", self.result.elapsed_secs);
|
||||
let time_line = Line::from(vec![
|
||||
Span::styled(" Time: ", Style::default().fg(colors.fg())),
|
||||
Span::styled(time_label.to_string(), Style::default().fg(colors.fg())),
|
||||
Span::styled(&*time_text, Style::default().fg(colors.fg())),
|
||||
]);
|
||||
Paragraph::new(time_line).render(layout[3], buf);
|
||||
|
||||
let errors_label = t!("dashboard.errors_label");
|
||||
let error_text = format!("{}", self.result.incorrect);
|
||||
let chars_line = Line::from(vec![
|
||||
Span::styled(" Errors: ", Style::default().fg(colors.fg())),
|
||||
Span::styled(errors_label.to_string(), Style::default().fg(colors.fg())),
|
||||
Span::styled(
|
||||
&*error_text,
|
||||
Style::default().fg(if self.result.incorrect == 0 {
|
||||
@@ -138,25 +152,32 @@ impl Widget for Dashboard<'_> {
|
||||
Paragraph::new(chars_line).render(layout[4], buf);
|
||||
|
||||
let help = if let Some(ms) = self.input_lock_remaining_ms {
|
||||
let input_blocked_label = t!("dashboard.input_blocked");
|
||||
let input_blocked_ms = t!("dashboard.input_blocked_ms", ms = ms);
|
||||
Paragraph::new(Line::from(vec![
|
||||
Span::styled(
|
||||
" Input temporarily blocked ",
|
||||
input_blocked_label.to_string(),
|
||||
Style::default().fg(colors.warning()),
|
||||
),
|
||||
Span::styled(
|
||||
format!("({ms}ms remaining)"),
|
||||
input_blocked_ms.to_string(),
|
||||
Style::default()
|
||||
.fg(colors.warning())
|
||||
.add_modifier(Modifier::BOLD),
|
||||
),
|
||||
]))
|
||||
} else {
|
||||
let hint_continue = t!("dashboard.hint_continue");
|
||||
let hint_retry = t!("dashboard.hint_retry");
|
||||
let hint_menu = t!("dashboard.hint_menu");
|
||||
let hint_stats = t!("dashboard.hint_stats");
|
||||
let hint_delete = t!("dashboard.hint_delete");
|
||||
let hints = [
|
||||
"[c/Enter/Space] Continue",
|
||||
"[r] Retry",
|
||||
"[q] Menu",
|
||||
"[s] Stats",
|
||||
"[x] Delete",
|
||||
hint_continue.as_ref(),
|
||||
hint_retry.as_ref(),
|
||||
hint_menu.as_ref(),
|
||||
hint_stats.as_ref(),
|
||||
hint_delete.as_ref(),
|
||||
];
|
||||
let lines: Vec<Line> = pack_hint_lines(&hints, inner.width as usize)
|
||||
.into_iter()
|
||||
|
||||
Reference in New Issue
Block a user