Skip to main content

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};