feat: implement internationalization for duty calendar
All checks were successful
CI / lint-and-test (push) Successful in 14s

- Introduced a new `i18n.js` module for handling translations and language detection, supporting both Russian and English.
- Updated various components to utilize the new translation functions, enhancing user experience by providing localized messages for errors, hints, and UI elements.
- Refactored existing code in `api.js`, `calendar.js`, `dutyList.js`, `hints.js`, and `ui.js` to replace hardcoded strings with translated messages, improving maintainability and accessibility.
- Enhanced the initialization process in `main.js` to set the document language and update UI elements based on the user's language preference.
This commit is contained in:
2026-02-19 16:00:00 +03:00
parent a5f7a5a0ef
commit 4c2d95e776
9 changed files with 262 additions and 51 deletions

View File

@@ -2,8 +2,8 @@
* Calendar grid and events-by-date mapping.
*/
import { calendarEl, monthTitleEl } from "./dom.js";
import { MONTHS } from "./constants.js";
import { calendarEl, monthTitleEl, state } from "./dom.js";
import { monthName, t } from "./i18n.js";
import { escapeHtml } from "./utils.js";
import {
localDateString,
@@ -116,6 +116,7 @@ export function renderCalendar(
d.getDate() +
'</span><div class="day-markers">';
if (showMarkers) {
const lang = state.lang;
if (dutyList.length) {
html +=
'<button type="button" class="duty-marker" data-event-type="duty" data-date="' +
@@ -124,23 +125,23 @@ export function renderCalendar(
namesAttr(dutyList) +
'" data-duty-items=\'' +
dutyItemsJson +
"' aria-label=\"Дежурные\">Д</button>";
"' aria-label=\"" + escapeHtml(t(lang, "aria.duty")) + "\">Д</button>";
}
if (unavailableList.length) {
html +=
'<button type="button" class="unavailable-marker" data-event-type="unavailable" data-names="' +
namesAttr(unavailableList) +
'" aria-label="Недоступен">Н</button>';
'" aria-label="' + escapeHtml(t(lang, "aria.unavailable")) + '">Н</button>';
}
if (vacationList.length) {
html +=
'<button type="button" class="vacation-marker" data-event-type="vacation" data-names="' +
namesAttr(vacationList) +
'" aria-label="Отпуск">О</button>';
'" aria-label="' + escapeHtml(t(lang, "aria.vacation")) + '">О</button>';
}
if (hasEvent) {
html +=
'<button type="button" class="info-btn" aria-label="Информация о дне" data-summary="' +
'<button type="button" class="info-btn" aria-label="' + escapeHtml(t(lang, "aria.day_info")) + '" data-summary="' +
escapeHtml(eventSummaries.join("\n")) +
'">i</button>';
}
@@ -151,7 +152,7 @@ export function renderCalendar(
d.setDate(d.getDate() + 1);
}
monthTitleEl.textContent = MONTHS[month] + " " + year;
monthTitleEl.textContent = monthName(state.lang, month) + " " + year;
bindInfoButtonTooltips();
bindDutyMarkerTooltips();
}