Files
duty-teller/webapp/js/i18n.js
Nikolay Tatarinov e3240d0981 feat: enhance duty information handling with contact details and current duty view
- Added `bot_username` to settings for dynamic retrieval of the bot's username.
- Implemented `_resolve_bot_username` function to fetch the bot's username if not set, improving user experience in group chats.
- Updated `DutyWithUser` schema to include optional `phone` and `username` fields for enhanced duty information.
- Enhanced API responses to include contact details for users, ensuring better communication.
- Introduced a new current duty view in the web app, displaying active duty information along with contact options.
- Updated CSS styles for better presentation of contact information in duty cards.
- Added unit tests to verify the inclusion of contact details in API responses and the functionality of the current duty view.
2026-03-02 16:09:08 +03:00

201 lines
6.3 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",
"contact.label": "Contact",
"contact.show": "Contacts",
"contact.back": "Back",
"contact.phone": "Phone",
"contact.telegram": "Telegram",
"current_duty.title": "Current Duty",
"current_duty.no_duty": "No one is on duty right now",
"current_duty.shift": "Shift",
"current_duty.back": "Back to calendar"
},
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": "Закрыть",
"contact.label": "Контакт",
"contact.show": "Контакты",
"contact.back": "Назад",
"contact.phone": "Телефон",
"contact.telegram": "Telegram",
"current_duty.title": "Текущее дежурство",
"current_duty.no_duty": "Сейчас никто не дежурит",
"current_duty.shift": "Смена",
"current_duty.back": "Назад к календарю"
}
};
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));
}