Files
duty-teller/webapp/js/i18n.js
Nikolay Tatarinov b60111462a feat: implement day detail panel for calendar
- Introduced a new `dayDetail.js` module to manage the day detail panel functionality, allowing users to view detailed information about duties and events for each calendar day.
- Enhanced the calendar rendering in `calendar.js` to include visual indicators for duties and events, improving user interaction and experience.
- Updated CSS in `style.css` to style the day detail panel and its components, ensuring a responsive design for both desktop and mobile views.
- Refactored `hints.js` to export the `getDutyMarkerRows` function, facilitating better integration with the new day detail features.
- Added localization support for the day detail panel in `i18n.js`, including new translations for close button and event titles.
- Enhanced the initialization process in `main.js` to set up the day detail panel on application load.
2026-02-19 16:23:46 +03:00

183 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Internationalization: language detection and translations for webapp.
*/
import { getInitData } from "./auth.js";
/** @type {Record<string, Record<string, string>>} */
export const MESSAGES = {
en: {
"app.title": "Duty Calendar",
loading: "Loading…",
access_denied: "Access denied.",
error_load_failed: "Load failed",
error_network: "Could not load data. Check your connection.",
error_generic: "Could not load data.",
"nav.prev_month": "Previous month",
"nav.next_month": "Next month",
"weekdays.mon": "Mon",
"weekdays.tue": "Tue",
"weekdays.wed": "Wed",
"weekdays.thu": "Thu",
"weekdays.fri": "Fri",
"weekdays.sat": "Sat",
"weekdays.sun": "Sun",
"month.jan": "January",
"month.feb": "February",
"month.mar": "March",
"month.apr": "April",
"month.may": "May",
"month.jun": "June",
"month.jul": "July",
"month.aug": "August",
"month.sep": "September",
"month.oct": "October",
"month.nov": "November",
"month.dec": "December",
"aria.duty": "On duty",
"aria.unavailable": "Unavailable",
"aria.vacation": "Vacation",
"aria.day_info": "Day info",
"event_type.duty": "Duty",
"event_type.unavailable": "Unavailable",
"event_type.vacation": "Vacation",
"duty.now_on_duty": "On duty now",
"duty.none_this_month": "No duties this month.",
"duty.today": "Today",
"duty.until": "until {time}",
"hint.from": "from",
"hint.to": "until",
"hint.duty_title": "Duty:",
"hint.events": "Events:",
"day_detail.close": "Close"
},
ru: {
"app.title": "Календарь дежурств",
loading: "Загрузка…",
access_denied: "Доступ запрещён.",
error_load_failed: "Ошибка загрузки",
error_network: "Не удалось загрузить данные. Проверьте интернет.",
error_generic: "Не удалось загрузить данные.",
"nav.prev_month": "Предыдущий месяц",
"nav.next_month": "Следующий месяц",
"weekdays.mon": "Пн",
"weekdays.tue": "Вт",
"weekdays.wed": "Ср",
"weekdays.thu": "Чт",
"weekdays.fri": "Пт",
"weekdays.sat": "Сб",
"weekdays.sun": "Вс",
"month.jan": "Январь",
"month.feb": "Февраль",
"month.mar": "Март",
"month.apr": "Апрель",
"month.may": "Май",
"month.jun": "Июнь",
"month.jul": "Июль",
"month.aug": "Август",
"month.sep": "Сентябрь",
"month.oct": "Октябрь",
"month.nov": "Ноябрь",
"month.dec": "Декабрь",
"aria.duty": "Дежурные",
"aria.unavailable": "Недоступен",
"aria.vacation": "Отпуск",
"aria.day_info": "Информация о дне",
"event_type.duty": "Дежурство",
"event_type.unavailable": "Недоступен",
"event_type.vacation": "Отпуск",
"duty.now_on_duty": "Сейчас дежурит",
"duty.none_this_month": "В этом месяце дежурств нет.",
"duty.today": "Сегодня",
"duty.until": "до {time}",
"hint.from": "с",
"hint.to": "до",
"hint.duty_title": "Дежурство:",
"hint.events": "События:",
"day_detail.close": "Закрыть"
}
};
const MONTH_KEYS = [
"month.jan", "month.feb", "month.mar", "month.apr", "month.may", "month.jun",
"month.jul", "month.aug", "month.sep", "month.oct", "month.nov", "month.dec"
];
const WEEKDAY_KEYS = [
"weekdays.mon", "weekdays.tue", "weekdays.wed", "weekdays.thu",
"weekdays.fri", "weekdays.sat", "weekdays.sun"
];
/**
* Normalize language code to 'ru' or 'en'.
* @param {string} code - e.g. 'ru', 'en', 'uk'
* @returns {'ru'|'en'}
*/
function normalizeLang(code) {
if (!code || typeof code !== "string") return "ru";
const lower = code.toLowerCase();
if (lower.startsWith("ru")) return "ru";
return "en";
}
/**
* Detect language: Telegram initData user.language_code → navigator → fallback 'ru'.
* @returns {'ru'|'en'}
*/
export function getLang() {
const initData = getInitData();
if (initData) {
try {
const params = new URLSearchParams(initData);
const userStr = params.get("user");
if (userStr) {
const user = JSON.parse(decodeURIComponent(userStr));
const code = user && user.language_code;
if (code) return normalizeLang(code);
}
} catch (e) {
/* ignore */
}
}
const nav = navigator.language || (navigator.languages && navigator.languages[0]) || "";
return normalizeLang(nav);
}
/**
* Get translated string; fallback to en if key missing in lang. Supports {placeholder}.
* @param {'ru'|'en'} lang
* @param {string} key - e.g. 'app.title', 'duty.until'
* @param {Record<string, string>} [params] - e.g. { time: '14:00' }
* @returns {string}
*/
export function t(lang, key, params = {}) {
const dict = MESSAGES[lang] || MESSAGES.en;
let s = dict[key];
if (s === undefined) s = MESSAGES.en[key];
if (s === undefined) return key;
Object.keys(params).forEach((k) => {
s = s.replace(new RegExp("\\{" + k + "\\}", "g"), params[k]);
});
return s;
}
/**
* Get month name by 0-based index.
* @param {'ru'|'en'} lang
* @param {number} month - 011
* @returns {string}
*/
export function monthName(lang, month) {
const key = MONTH_KEYS[month];
return key ? t(lang, key) : "";
}
/**
* Get weekday short labels (MonSun order) for given lang.
* @param {'ru'|'en'} lang
* @returns {string[]}
*/
export function weekdayLabels(lang) {
return WEEKDAY_KEYS.map((k) => t(lang, k));
}