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