kasl/libs/locale.rs
1//! Built-in localization for hourly (SiServer-style) daily reports.
2//!
3//! ## Usage
4//!
5//! ```rust,no_run
6//! use kasl::libs::locale::{Language, Locale};
7//!
8//! let locale = Locale::for_language(Language::from_code("en"));
9//! assert_eq!(locale.months[0], "January");
10//! ```
11
12/// Supported report languages.
13///
14/// The default (and fallback for unknown codes) is [`Language::En`]. Russian
15/// was the original default, but the shipped product speaks English; set
16/// `report.language = "ru"` in the config to get the previous wording back.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Language {
19 /// Russian - opt in through the report configuration.
20 Ru,
21 /// English - the default output.
22 En,
23}
24
25impl Language {
26 /// Parses a language code (case-insensitive) into a [`Language`].
27 ///
28 /// Recognizes `ru` and `en`. Any other value falls back to
29 /// [`Language::En`] so that a typo never breaks report generation.
30 pub fn from_code(code: &str) -> Language {
31 match code.trim().to_ascii_lowercase().as_str() {
32 "ru" | "rus" | "russian" => Language::Ru,
33 _ => Language::En,
34 }
35 }
36}
37
38/// A complete set of localized strings for one language.
39///
40/// All fields are `&'static str` because the tables are compiled into the
41/// binary. The `work_on_task` field contains a `{task}` placeholder that is
42/// replaced with the actual task name at render time.
43pub struct Locale {
44 /// Report title shown in the merged header cell (e.g. "Отчет за день").
45 pub report_title: &'static str,
46 /// Label for a regular working day (e.g. "рабочий").
47 pub day_type_working: &'static str,
48 /// Caption for the workday-length header cell.
49 pub workday_length: &'static str,
50 /// Column header spanning the start/end time columns (e.g. "День").
51 pub header_day: &'static str,
52 /// Header for the interval start-time column.
53 pub header_start: &'static str,
54 /// Header for the interval end-time column.
55 pub header_end: &'static str,
56 /// Header for the optional "hours" column.
57 pub header_hours: &'static str,
58 /// Header for the optional "result" column.
59 pub header_result: &'static str,
60 /// Label for the total worked-hours footer row.
61 pub total_worked: &'static str,
62 /// Label preceding the free-form comment box.
63 pub comment: &'static str,
64 /// Work description template; `{task}` is replaced with the task name.
65 pub work_on_task: &'static str,
66 /// Generic work label used when a task has no name.
67 pub work_generic: &'static str,
68 /// Label written for hours (or parts of hours) spent on a break/pause.
69 pub break_label: &'static str,
70 /// Nominative month names, indexed 0 (January) through 11 (December).
71 pub months: [&'static str; 12],
72 /// Weekday names, indexed 0 (Monday) through 6 (Sunday).
73 pub weekdays: [&'static str; 7],
74 /// `chrono` date format string used for the date header cell.
75 pub date_format: &'static str,
76}
77
78impl Locale {
79 /// Returns the static [`Locale`] table for the given language.
80 pub fn for_language(language: Language) -> &'static Locale {
81 match language {
82 Language::Ru => &RU,
83 Language::En => &EN,
84 }
85 }
86
87 /// Builds a work description for a task name using the `work_on_task`
88 /// template, falling back to [`Locale::work_generic`] for empty names.
89 pub fn work_text(&self, task_name: &str) -> String {
90 if task_name.trim().is_empty() {
91 self.work_generic.to_string()
92 } else {
93 self.work_on_task.replace("{task}", task_name)
94 }
95 }
96}
97
98/// Russian locale table.
99static RU: Locale = Locale {
100 report_title: "Отчет за день",
101 day_type_working: "рабочий",
102 workday_length: "Продолжительность рабочего дня",
103 header_day: "День",
104 header_start: "Начало",
105 header_end: "Конец",
106 header_hours: "Часы",
107 header_result: "Результат",
108 total_worked: "Отработано часов:",
109 comment: "Комментарий:",
110 work_on_task: "Работа по задаче [{task}]",
111 work_generic: "Работа",
112 break_label: "Перерыв",
113 months: [
114 "Январь",
115 "Февраль",
116 "Март",
117 "Апрель",
118 "Май",
119 "Июнь",
120 "Июль",
121 "Август",
122 "Сентябрь",
123 "Октябрь",
124 "Ноябрь",
125 "Декабрь",
126 ],
127 weekdays: ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"],
128 date_format: "%d.%m.%Y",
129};
130
131/// English locale table.
132static EN: Locale = Locale {
133 report_title: "Daily report",
134 day_type_working: "working",
135 workday_length: "Workday length",
136 header_day: "Day",
137 header_start: "Start",
138 header_end: "End",
139 header_hours: "Hours",
140 header_result: "Result",
141 total_worked: "Hours worked:",
142 comment: "Comment:",
143 work_on_task: "Work on task [{task}]",
144 work_generic: "Work",
145 break_label: "Break",
146 months: [
147 "January",
148 "February",
149 "March",
150 "April",
151 "May",
152 "June",
153 "July",
154 "August",
155 "September",
156 "October",
157 "November",
158 "December",
159 ],
160 weekdays: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
161 date_format: "%Y-%m-%d",
162};