379 lines
12 KiB
JavaScript
379 lines
12 KiB
JavaScript
import { writable, derived } from 'svelte/store';
|
||
|
||
export const locale = writable('ru');
|
||
|
||
export const translations = {
|
||
ru: {
|
||
// Navigation
|
||
nav: {
|
||
timer: 'Таймер',
|
||
setup: 'Участники',
|
||
history: 'История',
|
||
settings: 'Настройки',
|
||
},
|
||
|
||
// Setup page
|
||
setup: {
|
||
title: 'Название собрания',
|
||
participants: 'Участники',
|
||
addParticipant: 'Добавить участника',
|
||
namePlaceholder: 'Имя участника',
|
||
noParticipants: 'Добавьте участников для начала собрания',
|
||
selectAll: 'Выбрать всех',
|
||
deselectAll: 'Снять выбор',
|
||
startMeeting: 'Начать собрание',
|
||
speakerTime: 'Время на спикера',
|
||
totalTime: 'Общее время',
|
||
minutes: 'мин',
|
||
unlimited: 'Без ограничения',
|
||
dragHint: 'перетащите для изменения порядка, ✓/✗ присутствие',
|
||
},
|
||
|
||
// Timer page
|
||
timer: {
|
||
currentSpeaker: 'Текущий спикер',
|
||
currentlySpeaking: 'Сейчас говорит',
|
||
speakerTime: 'Время спикера',
|
||
totalTime: 'Общее время',
|
||
remaining: 'Осталось',
|
||
queue: 'Очередь',
|
||
participants: 'Участники',
|
||
finished: 'Выступили',
|
||
noSpeaker: 'Нет спикера',
|
||
noActiveMeeting: 'Собрание не запущено',
|
||
goToParticipants: 'Перейдите в раздел Участники, чтобы добавить участников',
|
||
readyToStart: 'Всё готово к началу',
|
||
editParticipants: 'Редактировать участников',
|
||
noParticipants: 'Участники не добавлены',
|
||
registeredParticipants: 'Зарегистрированные участники',
|
||
},
|
||
|
||
// Controls
|
||
controls: {
|
||
next: 'Следующий',
|
||
skip: 'Пропустить',
|
||
pause: 'Пауза',
|
||
resume: 'Продолжить',
|
||
stop: 'Завершить',
|
||
},
|
||
|
||
// History page
|
||
history: {
|
||
title: 'История собраний',
|
||
noHistory: 'История пуста',
|
||
date: 'Дата',
|
||
duration: 'Длительность',
|
||
participants: 'Участники',
|
||
avgTime: 'Среднее время',
|
||
export: 'Экспорт',
|
||
exportJSON: 'Экспорт JSON',
|
||
exportCSV: 'Экспорт CSV',
|
||
delete: 'Удалить',
|
||
deleteAll: 'Удалить историю',
|
||
deleteSession: 'Удалить запись',
|
||
confirmDelete: 'Удалить эту запись?',
|
||
confirmDeleteTitle: 'Подтверждение удаления',
|
||
confirmDeleteSession: 'Вы уверены, что хотите удалить эту запись? Действие необратимо.',
|
||
confirmDeleteAllTitle: 'Удалить всю историю?',
|
||
confirmDeleteAll: 'Вы уверены, что хотите удалить ВСЮ историю собраний? Это действие необратимо!',
|
||
overtimeRate: 'Процент превышения',
|
||
avgAttendance: 'Средняя явка',
|
||
recentSessions: 'Последние собрания',
|
||
noSessions: 'Собраний пока нет',
|
||
participantBreakdown: 'Статистика по участникам',
|
||
name: 'Имя',
|
||
sessions: 'Собрания',
|
||
overtime: 'Превышение',
|
||
attendance: 'Явка',
|
||
},
|
||
|
||
// Settings page
|
||
settings: {
|
||
title: 'Настройки собрания',
|
||
language: 'Язык',
|
||
sound: 'Звуковые уведомления',
|
||
soundEnabled: 'Включены',
|
||
soundDisabled: 'Выключены',
|
||
warningTime: 'Предупреждение за',
|
||
seconds: 'сек',
|
||
defaultSpeakerTime: 'Время на спикера по умолчанию',
|
||
defaultTotalTime: 'Общее время собрания (мин)',
|
||
theme: 'Тема оформления',
|
||
themeDark: 'Тёмная',
|
||
themeLight: 'Светлая',
|
||
save: 'Сохранить',
|
||
saved: 'Сохранено!',
|
||
windowWidth: 'Настройка окна',
|
||
windowWidthHint: 'Ширина окна (мин. 480 пикселей)',
|
||
windowFullHeight: 'Окно на всю высоту экрана',
|
||
},
|
||
|
||
// Updates
|
||
updates: {
|
||
title: 'Обновления',
|
||
currentVersion: 'Текущая версия',
|
||
checkingForUpdates: 'Проверка обновлений...',
|
||
updateAvailable: 'Доступно обновление',
|
||
rebuildAvailable: 'Доступна пересборка',
|
||
upToDate: 'У вас последняя версия',
|
||
downloadAndInstall: 'Скачать и установить',
|
||
downloading: 'Загрузка...',
|
||
installing: 'Установка...',
|
||
restartRequired: 'Для завершения обновления требуется перезапуск',
|
||
restart: 'Перезапустить',
|
||
later: 'Позже',
|
||
error: 'Ошибка проверки обновлений',
|
||
downloadError: 'Ошибка загрузки обновления',
|
||
checkNow: 'Проверить сейчас',
|
||
},
|
||
|
||
// Participant management
|
||
participants: {
|
||
title: 'Управление участниками',
|
||
add: 'Добавить',
|
||
edit: 'Редактировать',
|
||
delete: 'Удалить',
|
||
name: 'Имя',
|
||
stats: 'Статистика',
|
||
avgSpeakTime: 'Среднее время выступления',
|
||
totalMeetings: 'Всего собраний',
|
||
confirmDelete: 'Удалить участника?',
|
||
},
|
||
|
||
// Common
|
||
common: {
|
||
cancel: 'Отмена',
|
||
confirm: 'Подтвердить',
|
||
save: 'Сохранить',
|
||
close: 'Закрыть',
|
||
delete: 'Удалить',
|
||
yes: 'Да',
|
||
no: 'Нет',
|
||
loading: 'Загрузка...',
|
||
error: 'Ошибка',
|
||
errorStartMeeting: 'Ошибка запуска планёрки',
|
||
},
|
||
|
||
// Time formats
|
||
time: {
|
||
hours: 'ч',
|
||
minutes: 'мин',
|
||
seconds: 'сек',
|
||
},
|
||
|
||
// Hotkeys
|
||
hotkeys: {
|
||
title: 'Горячие клавиши',
|
||
next: 'Следующий спикер',
|
||
skip: 'Пропустить',
|
||
pauseResume: 'Пауза/Продолжить',
|
||
stop: 'Остановить митинг',
|
||
},
|
||
},
|
||
|
||
en: {
|
||
// Navigation
|
||
nav: {
|
||
timer: 'Timer',
|
||
setup: 'Participants',
|
||
history: 'History',
|
||
settings: 'Settings',
|
||
},
|
||
|
||
// Setup page
|
||
setup: {
|
||
title: 'Meeting Name',
|
||
participants: 'Participants',
|
||
addParticipant: 'Add Participant',
|
||
namePlaceholder: 'Participant name',
|
||
noParticipants: 'Add participants to start a meeting',
|
||
selectAll: 'Select All',
|
||
deselectAll: 'Deselect All',
|
||
startMeeting: 'Start Meeting',
|
||
speakerTime: 'Speaker Time',
|
||
totalTime: 'Total Time',
|
||
minutes: 'min',
|
||
unlimited: 'Unlimited',
|
||
dragHint: 'drag to reorder, ✓/✗ attendance',
|
||
},
|
||
|
||
// Timer page
|
||
timer: {
|
||
currentSpeaker: 'Current Speaker',
|
||
currentlySpeaking: 'Currently speaking',
|
||
speakerTime: 'Speaker Time',
|
||
totalTime: 'Total Time',
|
||
remaining: 'Remaining',
|
||
queue: 'Queue',
|
||
participants: 'Participants',
|
||
finished: 'Finished',
|
||
noSpeaker: 'No speaker',
|
||
noActiveMeeting: 'No active meeting',
|
||
goToParticipants: 'Go to Participants to add participants',
|
||
readyToStart: 'Ready to start',
|
||
editParticipants: 'Edit participants',
|
||
noParticipants: 'No participants added',
|
||
registeredParticipants: 'Registered participants',
|
||
},
|
||
|
||
// Controls
|
||
controls: {
|
||
next: 'Next',
|
||
skip: 'Skip',
|
||
pause: 'Pause',
|
||
resume: 'Resume',
|
||
stop: 'Stop',
|
||
},
|
||
|
||
// History page
|
||
history: {
|
||
title: 'Meeting History',
|
||
noHistory: 'No history yet',
|
||
date: 'Date',
|
||
duration: 'Duration',
|
||
participants: 'Participants',
|
||
avgTime: 'Avg Time',
|
||
export: 'Export',
|
||
exportJSON: 'Export JSON',
|
||
exportCSV: 'Export CSV',
|
||
delete: 'Delete',
|
||
deleteAll: 'Delete History',
|
||
deleteSession: 'Delete session',
|
||
confirmDelete: 'Delete this record?',
|
||
confirmDeleteTitle: 'Confirm Deletion',
|
||
confirmDeleteSession: 'Are you sure you want to delete this session? This action cannot be undone.',
|
||
confirmDeleteAllTitle: 'Delete All History?',
|
||
confirmDeleteAll: 'Are you sure you want to delete ALL meeting history? This action cannot be undone!',
|
||
overtimeRate: 'Overtime Rate',
|
||
avgAttendance: 'Avg. Attendance',
|
||
recentSessions: 'Recent Sessions',
|
||
noSessions: 'No sessions yet',
|
||
participantBreakdown: 'Participant Breakdown',
|
||
name: 'Name',
|
||
sessions: 'Sessions',
|
||
overtime: 'Overtime',
|
||
attendance: 'Attendance',
|
||
},
|
||
|
||
// Settings page
|
||
settings: {
|
||
title: 'Meeting Settings',
|
||
language: 'Language',
|
||
sound: 'Sound Notifications',
|
||
soundEnabled: 'Enabled',
|
||
soundDisabled: 'Disabled',
|
||
warningTime: 'Warning before',
|
||
seconds: 'sec',
|
||
defaultSpeakerTime: 'Default Speaker Time',
|
||
defaultTotalTime: 'Total meeting time (min)',
|
||
theme: 'Theme',
|
||
themeDark: 'Dark',
|
||
themeLight: 'Light',
|
||
save: 'Save',
|
||
saved: 'Saved!',
|
||
windowWidth: 'Window Settings',
|
||
windowWidthHint: 'Window width (min. 480 pixels)',
|
||
windowFullHeight: 'Full screen height window',
|
||
},
|
||
|
||
// Updates
|
||
updates: {
|
||
title: 'Updates',
|
||
currentVersion: 'Current version',
|
||
checkingForUpdates: 'Checking for updates...',
|
||
updateAvailable: 'Update available',
|
||
rebuildAvailable: 'Rebuild available',
|
||
upToDate: 'You have the latest version',
|
||
downloadAndInstall: 'Download and install',
|
||
downloading: 'Downloading...',
|
||
installing: 'Installing...',
|
||
restartRequired: 'Restart required to complete the update',
|
||
restart: 'Restart',
|
||
later: 'Later',
|
||
error: 'Error checking for updates',
|
||
downloadError: 'Error downloading update',
|
||
checkNow: 'Check now',
|
||
},
|
||
|
||
// Participant management
|
||
participants: {
|
||
title: 'Manage Participants',
|
||
add: 'Add',
|
||
edit: 'Edit',
|
||
delete: 'Delete',
|
||
name: 'Name',
|
||
stats: 'Statistics',
|
||
avgSpeakTime: 'Avg Speaking Time',
|
||
totalMeetings: 'Total Meetings',
|
||
confirmDelete: 'Delete participant?',
|
||
},
|
||
|
||
// Common
|
||
common: {
|
||
cancel: 'Cancel',
|
||
confirm: 'Confirm',
|
||
save: 'Save',
|
||
close: 'Close',
|
||
delete: 'Delete',
|
||
yes: 'Yes',
|
||
no: 'No',
|
||
loading: 'Loading...',
|
||
error: 'Error',
|
||
errorStartMeeting: 'Failed to start meeting',
|
||
},
|
||
|
||
// Time formats
|
||
time: {
|
||
hours: 'h',
|
||
minutes: 'min',
|
||
seconds: 'sec',
|
||
},
|
||
|
||
// Hotkeys
|
||
hotkeys: {
|
||
title: 'Hotkeys',
|
||
next: 'Next speaker',
|
||
skip: 'Skip speaker',
|
||
pauseResume: 'Pause/Resume',
|
||
stop: 'Stop meeting',
|
||
},
|
||
},
|
||
};
|
||
|
||
export const t = derived(locale, ($locale) => {
|
||
return (key) => {
|
||
const keys = key.split('.');
|
||
let value = translations[$locale];
|
||
|
||
for (const k of keys) {
|
||
if (value && typeof value === 'object' && k in value) {
|
||
value = value[k];
|
||
} else {
|
||
console.warn(`Translation missing: ${key} for locale ${$locale}`);
|
||
return key;
|
||
}
|
||
}
|
||
|
||
return value;
|
||
};
|
||
});
|
||
|
||
export function setLocale(lang) {
|
||
if (translations[lang]) {
|
||
locale.set(lang);
|
||
localStorage.setItem('daily-timer-locale', lang);
|
||
}
|
||
}
|
||
|
||
export function initLocale() {
|
||
const saved = localStorage.getItem('daily-timer-locale');
|
||
if (saved && translations[saved]) {
|
||
locale.set(saved);
|
||
} else {
|
||
const browserLang = navigator.language.split('-')[0];
|
||
if (translations[browserLang]) {
|
||
locale.set(browserLang);
|
||
}
|
||
}
|
||
}
|