cfa3959fb3
Add TipTap editing in the editor and an Electron window on the control panel to read the current scene description. Co-authored-by: Cursor <cursoragent@cursor.com>
872 lines
74 KiB
TypeScript
872 lines
74 KiB
TypeScript
export type EditorLocale = 'ru' | 'en';
|
||
|
||
export const EDITOR_LOCALE_STORAGE_KEY = 'dnd_editor_locale';
|
||
|
||
function primaryLanguageTag(lang: string): string {
|
||
const trimmed = lang.trim().toLowerCase();
|
||
if (!trimmed) return '';
|
||
const sep = trimmed.search(/[-_]/);
|
||
return sep === -1 ? trimmed : trimmed.slice(0, sep);
|
||
}
|
||
|
||
/**
|
||
* Выбор `ru` / `en` по языку ОС/браузера, если пользователь ещё не сохранил язык в `localStorage`.
|
||
* В Electron совпадает с локалью системы (Chromium подставляет `navigator.languages`).
|
||
*/
|
||
export function inferEditorLocaleFromSystem(languages?: readonly string[]): EditorLocale {
|
||
let list: string[];
|
||
if (languages !== undefined) {
|
||
list = [...languages];
|
||
} else if (typeof navigator !== 'undefined') {
|
||
list = [...navigator.languages];
|
||
if (navigator.language) {
|
||
list.push(navigator.language);
|
||
}
|
||
list = list.filter((x) => x.trim() !== '');
|
||
} else {
|
||
list = [];
|
||
}
|
||
for (const lang of list) {
|
||
const tag = primaryLanguageTag(lang);
|
||
if (tag === 'en') return 'en';
|
||
if (tag === 'ru') return 'ru';
|
||
}
|
||
return 'ru';
|
||
}
|
||
|
||
export function normalizeEditorLocale(raw: string | null | undefined): EditorLocale {
|
||
if (raw == null) {
|
||
return inferEditorLocaleFromSystem();
|
||
}
|
||
const trimmed = raw.trim();
|
||
if (trimmed === '') {
|
||
return inferEditorLocaleFromSystem();
|
||
}
|
||
const s = trimmed.toLowerCase();
|
||
if (s === 'en') return 'en';
|
||
if (s === 'ru') return 'ru';
|
||
return inferEditorLocaleFromSystem();
|
||
}
|
||
|
||
/** Flat message table; `{name}` placeholders supported in `translate`. */
|
||
export const EDITOR_MESSAGES: Record<EditorLocale, Record<string, string>> = {
|
||
ru: {
|
||
'common.close': 'Закрыть',
|
||
'common.cancel': 'Отмена',
|
||
'common.save': 'Сохранить',
|
||
'common.edit': 'Редактировать',
|
||
'common.understood': 'Понятно',
|
||
'common.message': 'Сообщение',
|
||
'common.error': 'Ошибка',
|
||
'common.delete': 'Удалить',
|
||
'common.closeMenu': 'Закрыть меню',
|
||
|
||
'app.brandTitle': 'НРИ Плеер',
|
||
|
||
'notice.campaignAudioEmpty': 'Аудио не добавлено. Проверьте формат файла.',
|
||
|
||
'license.checkingTitle': 'Проверка лицензии…',
|
||
'license.checkingWait': 'Подождите.',
|
||
'license.requiredTitle': 'Требуется лицензия',
|
||
'license.requiredHint':
|
||
'Укажите ключ в меню «Настройки» → «Указать ключ». До активации доступно только меню «Настройки».',
|
||
'license.tokenTitle': 'Указать ключ',
|
||
'license.tokenKey': 'КЛЮЧ',
|
||
'license.tokenPlaceholder': 'Продуктовый ключ TTRPG-... или DND-...',
|
||
'license.tokenSaving': 'Сохранение…',
|
||
'license.eulaTitle': 'Лицензионное соглашение',
|
||
'license.eulaReject': 'Не принимаю',
|
||
'license.eulaAccept': 'Принимаю условия',
|
||
'license.eulaNoteEn':
|
||
'The binding legal text below is in Russian. If you need an English summary, contact support.',
|
||
'license.aboutTitle': 'О лицензии',
|
||
'license.aboutDevSkip': 'Режим разработки: проверка лицензии отключена (DND_SKIP_LICENSE).',
|
||
'license.aboutStatus': 'СТАТУС',
|
||
'license.aboutProduct': 'ПРОДУКТ',
|
||
'license.aboutLicenseId': 'ID ЛИЦЕНЗИИ',
|
||
'license.aboutExpiry': 'ОКОНЧАНИЕ',
|
||
'license.aboutDevice': 'УСТРОЙСТВО',
|
||
'license.aboutNoData': 'Нет данных лицензии.',
|
||
'license.reason.ok': 'Активна',
|
||
'license.reason.none': 'Ключ не указан',
|
||
'license.reason.expired': 'Срок действия истёк',
|
||
'license.reason.bad_signature': 'Недействительная подпись',
|
||
'license.reason.bad_payload': 'Неверный формат токена',
|
||
'license.reason.malformed': 'Повреждённый токен',
|
||
'license.reason.not_yet_valid': 'Ещё не действует',
|
||
'license.reason.wrong_device': 'Другой привязанный компьютер',
|
||
'license.reason.revoked_remote': 'Отозвана на сервере',
|
||
|
||
'presentation.overlay': 'Презентация запущена',
|
||
'presentation.title': 'Презентация запущена',
|
||
'presentation.body':
|
||
'Редактор заблокирован. Закройте окна «Презентация» и «Панель управления», чтобы продолжить.',
|
||
|
||
'zip.progress': 'Прогресс операции',
|
||
'zip.importTitle': 'Импорт проекта',
|
||
'zip.exportTitle': 'Экспорт проекта',
|
||
|
||
'top.settings': 'Настройки',
|
||
'top.project': 'Проект',
|
||
'top.file': 'Файл',
|
||
'top.backToProjects': 'К списку проектов',
|
||
'top.appVersion': 'Версия приложения',
|
||
'top.run': 'Запустить',
|
||
'top.afterLicense': 'Доступно после активации лицензии',
|
||
'top.setStartScene': 'Назначьте начальную сцену на графе (ПКМ по узлу)',
|
||
'top.runHelpAria': 'Как разблокировать кнопку «Запустить»',
|
||
|
||
'menu.enterKey': 'Указать ключ',
|
||
'menu.aboutLicense': 'О лицензии',
|
||
'menu.checkUpdates': 'Проверить обновления',
|
||
'menu.language': 'Язык',
|
||
'menu.langRu': 'Русский',
|
||
'menu.langEn': 'English',
|
||
'menu.aboutProgram': 'О программе',
|
||
'menu.instructions': 'Инструкция',
|
||
|
||
'top.aboutApp': 'О приложении',
|
||
|
||
'app.about.title': 'О программе',
|
||
'app.about.tagline': 'Редактор кампаний и пульт мастера для настольных ролевых игр',
|
||
'app.about.description':
|
||
'TTRPG Player (НРИ Плеер) — настольное приложение для мастера: вы собираете кампанию как граф сцен с картами, музыкой и ветвлениями, а во время сессии управляете атмосферой с пульта. Игроки видят только презентацию на втором экране — без панелей редактора.',
|
||
'app.about.versionLabel': 'ВЕРСИЯ',
|
||
'app.about.developerLabel': 'РАЗРАБОТЧИК',
|
||
'app.about.developer':
|
||
'Независимая разработка. По вопросам лицензии, покупки и поддержки — электронная почта ниже.',
|
||
'app.about.supportLabel': 'ПОДДЕРЖКА',
|
||
'app.about.supportEmail': 'player.ttrpg@gmail.com',
|
||
'app.about.websiteLabel': 'САЙТ И ОБНОВЛЕНИЯ',
|
||
'app.about.websiteUrl': 'https://ttrpgplayer.ru/',
|
||
|
||
'help.title': 'Инструкция',
|
||
'help.navAria': 'Разделы инструкции',
|
||
|
||
'help.section.overview.title': 'Обзор приложения',
|
||
'help.section.overview.body':
|
||
'При запуске вы попадаете в Редактор — здесь готовите кампанию: сцены, картинки, музыку и связи между эпизодами. Когда придёт время играть, нажмите «Запустить» — откроются «Презентация» для игроков и «Пульт управления» для вас.\n\nВ редакторе слева — список сцен, по центру — карта связей, справа — настройки игры и выбранной сцены.\n\nПока идёт показ, редактор временно нельзя менять — это нормально. Закройте презентацию и пульт, чтобы снова редактировать кампанию.\n\nИнтернет нужен только для активации лицензии и проверки обновлений. Все проекты и файлы хранятся на вашем компьютере.',
|
||
|
||
'help.section.license.title': 'Лицензия и первый запуск',
|
||
'help.section.license.body':
|
||
'Перед работой с проектами один раз активируйте лицензию.\n\n1) Откройте «Настройки» → «Указать ключ».\n\n2) Если появится лицензионное соглашение — прочитайте и примите его.\n\n3) Вставьте ключ из письма (формат TTRPG-… или старый DND-…) и нажмите «Сохранить».\n\nДо активации доступны только настройки. После успешного сохранения ключа откроются проекты, сцены и запуск сессии.\n\nСтатус, срок действия и привязка к компьютеру — в «Настройки» → «О лицензии». Ключ привязан к этому ПК; на другом компьютере понадобится отдельная активация по условиям покупки.',
|
||
|
||
'help.section.projects.title': 'Проекты',
|
||
'help.section.projects.body':
|
||
'Проект — вся кампания целиком: сцены, медиа и связи между ними.\n\nСоздать новую кампанию:\n\n1) На начальном экране введите название в поле слева.\n\n2) Нажмите «Создать проект».\n\nОткрыть существующую — кликните по названию в списке. Вернуться к списку: «Проект» → «Начальный экран» или клик по названию приложения в шапке.\n\nПеренести кампанию на другой компьютер:\n\n1) «Проект» → «Экспорт» — сохраните копию как .ttrpg.zip.\n\n2) На другом ПК — «Проект» → «Импорт» и выберите этот файл.\n\nПереименовать открытый проект: «Файл» → «Переименовать проект» (минимум 3 символа; в имени файла нельзя использовать <>:"/\\|?*).\n\nУдалить проект с диска:\n\n1) На карточке проекта нажмите «⋮».\n\n2) В меню выберите «Удалить».\n\n3) Подтвердите удаление в диалоге.\n\nПосле этого файл проекта и кэш будут стёрты без восстановления. Если кампания может понадобиться снова — сначала сделайте экспорт.',
|
||
|
||
'help.section.scenes.title': 'Сцены',
|
||
'help.section.scenes.body':
|
||
'Сцена — отдельный эпизод: локация, кадр истории, диалог. У неё есть название, картинка или видео для игроков, описание для мастера и своя музыка.\n\nДобавить сцену:\n\n1) В левой колонке нажмите «+ Новая сцена».\n\n2) Задайте название и настройте свойства справа (см. «Свойства сцены»).\n\n«Поиск сцен…» помогает быстро найти нужную. Клик по карточке выделяет сцену — она же подсветится на карте связей.\n\nУдалить: правый клик по карточке в списке → «Удалить». Сцена исчезнет из списка и с карты вместе со связями.\n\nПеретащите сцену из списка на карту — так она появится как узел (подробнее в «Граф сцен»).',
|
||
|
||
'help.section.graph.title': 'Граф сцен',
|
||
'help.section.graph.body':
|
||
'Карта в центре показывает, как эпизоды связаны. Каждый прямоугольник — место на схеме; одна сцена может встретиться несколько раз (например, игроки снова возвращаются в таверну).\n\nДобавить сцену на карту:\n\n1) Возьмите сцену в левом списке.\n\n2) Перетащите на свободное место на карте.\n\nСделать переход между сценами:\n\n1) Наведите на нижнюю точку первой карточки.\n\n2) Потяните линию к верхней точке второй и отпустите — появится стрелка.\n\nИз одной сцены может выходить несколько стрелок — так вы делаете ветвление сюжета. Вторую стрелку к той же паре сцен провести нельзя.\n\nУдалить стрелку: правый клик по линии → «Удалить». Обычный клик по линии ничего не делает.\n\nС чего начинается игра: правый клик по карточке → «Начальная сцена» (появится метка «НАЧАЛО»), затем «Запустить» в шапке. Можно начать с любой карточки основного сюжета: ПКМ → «Запустить с этой сцены» — откроются презентация и пульт с выбранного места (метку «НАЧАЛО» ставить не обязательно). Для карточек побочных линий этот пункт недоступен.\n\nУбрать карточку с карты, не удаляя сцену из списка: ПКМ → «Удалить».\n\nМасштаб — кнопки внизу или колёсико мыши. «Показать всё» вместит всю схему на экран.',
|
||
|
||
'help.section.sideStorylines.title': 'Побочные сюжетные линии',
|
||
'help.section.sideStorylines.body':
|
||
'Побочная линия — отдельная ветка сюжета, не связанная с основным сюжетом. Она нужна для ответвлений, флешбэков, побочных квестов и сцен «вне основного пути».\n\nСоздать побочную линию в редакторе:\n\n1) Добавьте сцены на карту и соедините их стрелками в отдельной группе — она не должна касаться основного сюжета (фиолетовые связи) и других побочных линий.\n\n2) Правый клик по стартовой карточке побочной ветки → «Начальная сцена побочной линии». Появится синяя метка «ПОБОЧНАЯ».\n\n3) В свойствах сцены задайте «Название побочной линии» — оно будет видно на пульте.\n\nПункт «Начальная сцена побочной линии» скрыт, если карточка уже связана с основным сюжетом (где есть фиолетовое «НАЧАЛО») или с другой побочной линией (где есть синее «ПОБОЧНАЯ»).\n\nСвязи внутри побочной линии и выделение её карточек — синего цвета (#0078d4). Между основным и побочным сюжетом, а также между разными побочными линиями, стрелки провести нельзя.\n\nСнять метку: ПКМ → «Снять метку «Начальная сцена побочной линии»». Название очистится, плитка исчезнет с пульта.\n\nУдалить стартовую карточку: если есть следующая сцена по стрелке — метка переносится на неё; если нет — вся побочная линия удаляется с карты.\n\nВо время игры на пульте под блоком «Музыка» появляется «Побочные сюжетные линии» — плитки с превью и названием. Клик переносит партию на первую сцену линии. Программа запоминает, с какой сцены основного сюжета вы ушли.\n\nПока идёт побочная линия, в «Варианты ветвления» первой опцией всегда «Вернуться в основной сюжет» — возврат на запомненную сцену. История «Сюжетная линия» продолжает записывать все шаги, включая побочную ветку.\n\nЗапустить побочную линию из редактора нельзя — только с пульта во время сессии.',
|
||
|
||
'help.section.sceneProps.title': 'Свойства сцены',
|
||
'help.section.sceneProps.body':
|
||
'Выберите сцену в списке слева — справа откроются её свойства.\n\n«Название сцены» и «Описание» — для мастера. Описание видно на пульте в блоке сюжетной линии.\n\nЕсли у сцены на карте есть карточка с синей меткой «ПОБОЧНАЯ», появится поле «Название побочной линии».\n\nКартинка или видео для игроков:\n\n1) В «Превью сцены» нажмите «Загрузить» или «Изменить».\n\n2) Выберите файл (PNG, JPG, WebP, GIF и др.).\n\n3) Для картинки можно «Повернуть» (шаг 90°). «Очистить» — убрать превью.\n\n4) Для картинки можно включить «Затемнить сцену»: при показе игроки сначала увидят карту в полной темноте, а вы снимете её с пульта «Кистью Открытия» (см. «Эффекты»).\n\nДля видео включите «Автостарт», если ролик должен сам начаться на экране игроков. На видео-сценах эффекты кистью недоступны.\n\n«Аудио сцены» — музыка и звуки этого эпизода. «Загрузить» добавляет файлы. У каждого трека: «Авто» (играть при входе в сцену) и «Цикл». Корзина удаляет трек.\n\nСвязи между сценами задаются на карте, а не здесь — см. «Граф сцен».',
|
||
|
||
'help.section.campaignAudio.title': 'Аудио игры',
|
||
'help.section.campaignAudio.body':
|
||
'«Аудио игры» в блоке «Свойства игры» — музыка всей кампании: тема, фон, атмосфера. Она не привязана к одной сцене.\n\n1) Нажмите «Загрузить» и выберите файлы.\n\n2) Для каждого трека отметьте «Авто» и «Цикл» по желанию.\n\n3) Удалить трек — иконка корзины.\n\nНа пульте музыка сцены важнее общей: пока играет трек сцены, кампанийная музыка приглушается. Когда у сцены нет своего звука или вы переключитесь вручную — общая музыка снова может играть.',
|
||
|
||
'help.section.session.title': 'Запуск сессии',
|
||
'help.section.session.body':
|
||
'Когда кампания готова, можно начать игру.\n\nОбычный запуск:\n\n1) На карте связей щёлкните правой кнопкой по карточке старта → «Начальная сцена».\n\n2) Нажмите «Запустить» в шапке редактора.\n\nБыстрый запуск с любой карточки: правый клик по нужной карточке на карте → «Запустить с этой сцены». Презентация и пульт откроются сразу с выбранного места.\n\nОткроются «Презентация» (для игроков) и «Пульт управления» (для вас). Редактор на время показа затемняется — так и должно быть.\n\nВернуться к подготовке:\n\n1) На пульте нажмите «Выключить демонстрацию» или «Завершить показ» (если дальше некуда переходить).\n\n2) Дождитесь закрытия обоих окон.\n\nОкно «Презентация» перенесите на второй монитор, проектор или ТВ и разверните на весь экран (F11). Игроки увидят только картинку, видео и эффекты — без ваших кнопок.',
|
||
|
||
'help.section.controlPanel.title': 'Пульт управления',
|
||
'help.section.controlPanel.body':
|
||
'Пульт — ваш стол во время игры. Игроки смотрят на презентацию, вы управляете всем отсюда.\n\nСлева — эффекты и ход сюжета, справа — мини-копия экрана игроков, варианты переходов и музыка.\n\n«Предпросмотр экрана» показывает то же, что видят игроки. На сценах с картинкой здесь же рисуют эффекты — они сразу появляются на большом экране.\n\n«Сюжетная линия» — цепочка пройденных эпизодов. Текущая сцена помечена «ТЕКУЩАЯ СЦЕНА». Клик по прошлому шагу переносит партию к тому месту на карте и добавляет новый шаг в историю.\n\n«Выключить демонстрацию» — закончить показ и вернуться в редактор.',
|
||
|
||
'help.section.transitions.title': 'Переходы между сценами',
|
||
'help.section.transitions.body':
|
||
'Куда можно пойти дальше — видно на пульте в «Варианты ветвления». Это исходящие стрелки с текущей карточки на карте.\n\n1) Посмотрите карточки «ОПЦИЯ 1», «ОПЦИЯ 2» — там названия целевых сцен.\n\n2) Нажмите «Переключить» у нужного варианта.\n\n3) На презентации сменятся картинка и музыка, в сюжетной линии появится новый шаг.\n\nЕсли вариантов нет (конец ветки) — «Нет вариантов перехода». Нажмите «Завершить показ», чтобы закрыть окна.\n\nВарианты появляются только там, где вы провели стрелки на карте в редакторе.',
|
||
|
||
'help.section.music.title': 'Музыка на пульте',
|
||
'help.section.music.body':
|
||
'Блок «Музыка» повторяет треки из редактора — отдельно «Музыка сцены» и «Музыка игры».\n\n▶ — воспроизвести, ⏸ — пауза, ⏹ — остановить. «Авто»/«Ручн.» и «Цикл»/«Один раз» показывают, как трек настроен в редакторе.\n\nКлик по полоске прогресса — перемотка (если известна длина). Стрелки ← → на полоске (когда она в фокусе) — на 5 секунд назад или вперёд.\n\nЕсли музыка не стартует сама — один раз нажмите ▶: после вашего действия звук обычно разрешается. Не играет файл — проверьте формат (MP3, WAV и т.п.).',
|
||
|
||
'help.section.effects.title': 'Эффекты поля и действий',
|
||
'help.section.effects.body':
|
||
'Эффекты работают на сценах с картинкой, не на видео. Рисуйте в «Предпросмотр экрана» — игроки увидят то же на презентации.\n\nВыберите инструмент слева:\n• Эффекты поля (туман, дождь, огонь, вода) — зажмите левую кнопку и ведите по карте.\n• Эффекты действий (молния, луч света, заморозка, тьма, облако яда) — короткий клик или штрих; у некоторых есть звук.\n\nЕсли у сцены в свойствах включено «Затемнить сцену», появится блок «Управление затемнением» с «Кистью Открытия» 🔦. Водите по предпросмотру — тьма снимается на обоих экранах сразу. У игроков нераскрытое остаётся чёрным, у вас на пульте — полузатемнённым. Уже открытые участки сохраняются, пока идёт показ и вы снова попадаете на ту же карточку сцены на карте. Это не то же самое, что эффект «Тьма» 🌑 в блоке действий.\n\nЛастик 🧹 — для эффектов поля (туман, дождь, огонь, вода) водите кистью, как «Кистью Открытия» для затемнения: стирается только пройденный участок. Эффекты действий (молния, луч и т.д.) убираются целиком при клике или проведении по ним. «Очистить эффекты» — снять всё сразу.\n\n«Радиус кисти» под панелью — чем больше число, тем шире мазок.',
|
||
|
||
'help.section.presentation.title': 'Экран презентации',
|
||
'help.section.presentation.body':
|
||
'«Презентация» — то, что видят игроки: картинка сцены (с учётом поворота из редактора) или видео по вашим настройкам.\n\nЭффекты с пульта накладываются поверх. Меню и кнопки мастера здесь не показываются.\n\nЕсли у сцены включено «Затемнить сцену», игроки сначала видят полностью чёрный экран. Мастер открывает карту «Кистью Открытия» на пульте.\n\nПри смене сцены с пульта картинка обновляется сама. Перенесите окно на экран для игроков и при необходимости спрячьте панель задач.\n\nПустой или тёмный экран — скорее всего, у сцены нет превью. Добавьте картинку в свойствах сцены в редакторе.',
|
||
|
||
'help.section.importExport.title': 'Импорт и экспорт',
|
||
'help.section.importExport.body':
|
||
'Проект хранится как файл .ttrpg.zip — внутри уже все картинки, звуки и настройки.\n\nРезервная копия или перенос на другой ПК:\n\n1) «Проект» → «Экспорт».\n\n2) Выберите проект и куда сохранить.\n\n3) Скопируйте .ttrpg.zip на флешку, в облако или на другой компьютер.\n\nЗагрузить бэкап:\n\n1) «Проект» → «Импорт».\n\n2) Выберите .ttrpg.zip.\n\n3) Проект появится в списке на начальном экране.\n\nПри больших архивах показывается прогресс. Картинки и музыку отдельно переносить не нужно — всё внутри архива.',
|
||
|
||
'help.section.settings.title': 'Настройки, язык и обновления',
|
||
'help.section.settings.body':
|
||
'«Настройки» → «Указать ключ» — ввести или сменить лицензию. «О лицензии» — проверить статус и срок.\n\n«Проверить обновления» (в установленной версии при активной лицензии) — если есть новая версия, можно скачать и перезапустить программу.\n\n«Язык» → «Русский» или «English» — меняет язык интерфейса. Выбор сохраняется между запусками.\n\nНомер версии — в шапке справа от меню. «О приложении» → «О программе» — о продукте и контактах поддержки.',
|
||
|
||
'updates.dialogTitle': 'Обновления',
|
||
'updates.checking': 'Проверка наличия обновлений…',
|
||
'updates.available': 'Доступна новая версия {version}.',
|
||
'updates.current': 'У вас установлена актуальная версия ({version}).',
|
||
'updates.error': 'Не удалось проверить обновления: {message}',
|
||
'updates.notPackaged': 'Проверка доступна только в установленной версии приложения.',
|
||
'updates.noLicense': 'Нужна активная лицензия.',
|
||
'updates.download': 'Обновить',
|
||
'updates.downloading': 'Загрузка…',
|
||
'updates.stageLine': 'Этап: {stage}',
|
||
'updates.stage.checking': 'проверка обновлений',
|
||
'updates.stage.available': 'доступна версия {version}',
|
||
'updates.stage.not-available': 'актуальная версия',
|
||
'updates.stage.downloading': 'загрузка{percent}',
|
||
'updates.stage.installing': 'установка и перезапуск',
|
||
'updates.stage.error': 'ошибка',
|
||
'updates.stagePercent': ' ({percent}%)',
|
||
|
||
'projectMenu.home': 'Начальный экран',
|
||
'projectMenu.import': 'Импорт',
|
||
'projectMenu.export': 'Экспорт',
|
||
'projectMenu.noProjects': 'Нет сохранённых проектов',
|
||
|
||
'fileMenu.rename': 'Переименовать проект',
|
||
|
||
'scenes.search': 'Поиск сцен…',
|
||
'scenes.new': '+ Новая сцена',
|
||
'scenes.dropHint': 'Перетащите изображения или видео',
|
||
'scenes.batchTitle': 'Создание сцен',
|
||
'scenes.batchProgress': 'Сцена {current} из {total}',
|
||
'scenes.dropSkippedTitle': 'Часть файлов не добавлена',
|
||
'scenes.dropSkippedIntro': 'Эти файлы пропущены:',
|
||
'scenes.dropSkippedUnsupported': 'неподдерживаемый формат',
|
||
'scenes.dropSkippedNoPath': 'не удалось получить путь к файлу',
|
||
'scenes.inspectorGame': 'Свойства игры',
|
||
'scenes.inspectorScene': 'Свойства сцены',
|
||
'scenes.selectHint': 'Выберите сцену слева, чтобы редактировать её свойства.',
|
||
'scenes.openProjectHint': 'Откройте проект, чтобы редактировать кампанию и сцены.',
|
||
|
||
'rename.title': 'Переименовать проект',
|
||
'rename.projectName': 'НАЗВАНИЕ ПРОЕКТА',
|
||
'rename.projectPlaceholder': 'Название проекта…',
|
||
'rename.projectMin': 'Минимум 3 символа.',
|
||
'rename.projectDup': 'Проект с таким названием уже существует.',
|
||
'rename.fileName': 'НАЗВАНИЕ ФАЙЛА ПРОЕКТА',
|
||
'rename.fileInvalid': 'Минимум 3 символа, без символов <>:"/\\|?*',
|
||
'rename.fileDup': 'Файл проекта с таким названием уже существует.',
|
||
'rename.saving': 'Сохранение…',
|
||
|
||
'export.title': 'Экспорт проекта',
|
||
'export.project': 'ПРОЕКТ',
|
||
'export.hint':
|
||
'Выберите сюжетные линии для экспорта. В архив попадут только выбранные линии, их сцены и материалы. Далее откроется окно сохранения .ttrpg.zip.',
|
||
'export.exporting': 'Экспорт…',
|
||
'export.saveAs': 'Сохранить как…',
|
||
|
||
'storyline.section': 'СЮЖЕТНАЯ ЛИНИЯ',
|
||
'storyline.main': 'Основная линия',
|
||
'storyline.loading': 'Загрузка линий…',
|
||
'storyline.empty': 'В проекте нет сюжетных линий с метками «НАЧАЛО» или «ПОБОЧНАЯ».',
|
||
'storyline.mainExistsHint': 'в проекте уже есть основная линия',
|
||
|
||
'importSource.title': 'Импорт',
|
||
'importSource.type': 'ТИП ИМПОРТА',
|
||
'importSource.fromProject': 'Из проекта',
|
||
'importSource.fromFile': 'Из файла',
|
||
'importSource.project': 'ПРОЕКТ',
|
||
'importSource.file': 'ФАЙЛ',
|
||
'importSource.chooseFile': 'Выбрать файл',
|
||
'importSource.noFileSelected': 'Файл не выбран',
|
||
'importSource.noOtherProjects': 'Нет других проектов для импорта.',
|
||
'importSource.fileOnlyHint': 'Выберите файл проекта (.ttrpg.zip) для полного импорта.',
|
||
|
||
'importStoryline.title': 'Импорт сюжетных линий',
|
||
'importStoryline.source': 'ИСТОЧНИК',
|
||
'importStoryline.continue': 'Далее',
|
||
'importStoryline.import': 'Импортировать',
|
||
'importStoryline.conflictsTitle': 'Совпадение названий сцен',
|
||
'importStoryline.conflictsHint':
|
||
'В импортируемых линиях есть сцены с такими же названиями, как в текущем проекте. Выберите действие для каждой.',
|
||
'importStoryline.createNewScene': 'Создать новую сцену',
|
||
'importStoryline.useExistingScene': 'Использовать «{title}»',
|
||
'importStoryline.reportTitle': 'Импорт завершён',
|
||
'importStoryline.reportLines': 'Импортировано линий: {count}',
|
||
'importStoryline.reportScenesCreated': 'Создано новых сцен: {count}',
|
||
'importStoryline.reportScenesReused': 'Использовано существующих сцен: {count}',
|
||
'importStoryline.reportNodes': 'Добавлено карточек на граф: {count}',
|
||
'importStoryline.reportEdges': 'Добавлено связей: {count}',
|
||
'importStoryline.reportAssetsCopied': 'Скопировано файлов материалов: {count}',
|
||
'importStoryline.reportAssetsReused': 'Повторно использовано материалов: {count}',
|
||
'importStoryline.reportRenamedSides': 'Переименованы побочные линии: {names}',
|
||
|
||
'confirmDelete.title': 'Удаление проекта',
|
||
'confirmDelete.body': 'Удалить проект «{name}» безвозвратно? Файл и кэш будут стёрты с диска.',
|
||
'confirmDelete.failedTitle': 'Не удалось удалить',
|
||
|
||
'picker.title': 'Проекты',
|
||
'picker.newPlaceholder': 'Название нового проекта…',
|
||
'picker.create': 'Создать проект',
|
||
'picker.existing': 'СУЩЕСТВУЮЩИЕ',
|
||
'picker.lockedHint':
|
||
'Открытие и создание — после активации лицензии. Список показывает файлы в папке приложения.',
|
||
'picker.empty': 'Пока нет проектов.',
|
||
'picker.projectMenu': 'Меню проекта',
|
||
'picker.openDisabled': 'Открытие проекта — после активации лицензии',
|
||
'picker.opening': 'Открытие…',
|
||
'picker.defaultName': 'Моя кампания',
|
||
|
||
'campaign.label': 'АУДИО ИГРЫ',
|
||
'campaign.noFiles': 'Файлов пока нет. Добавьте аудио.',
|
||
'campaign.auto': 'Авто',
|
||
'campaign.loop': 'Цикл',
|
||
'campaign.removeTitle': 'Убрать из кампании',
|
||
'campaign.upload': 'Загрузить',
|
||
'drop.hintAudio': 'Перетащите аудиофайлы сюда',
|
||
'drop.hintPreview': 'Перетащите изображение или видео',
|
||
|
||
'scene.title': 'НАЗВАНИЕ СЦЕНЫ',
|
||
'scene.description': 'ОПИСАНИЕ',
|
||
'scene.descriptionEmpty': 'описание отсутствует',
|
||
'scene.descriptionModalTitle': 'Описание сцены',
|
||
'scene.descriptionPlaceholder': 'Введите описание сцены…',
|
||
'scene.descriptionToolbar': 'Форматирование',
|
||
'scene.descriptionBold': 'Жирный',
|
||
'scene.descriptionItalic': 'Курсив',
|
||
'scene.descriptionUnderline': 'Подчёркнутый',
|
||
'scene.descriptionHeading2': 'Заголовок',
|
||
'scene.descriptionHeading3': 'Подзаголовок',
|
||
'scene.descriptionQuote': 'Цитата',
|
||
'scene.descriptionBulletList': 'Маркированный список',
|
||
'scene.descriptionOrderedList': 'Нумерованный список',
|
||
'scene.descriptionLink': 'Ссылка',
|
||
'scene.descriptionLinkPrompt': 'URL ссылки',
|
||
'scene.preview': 'ПРЕВЬЮ СЦЕНЫ',
|
||
'scene.previewHint': 'Файл изображения (PNG, JPG, WebP, GIF и т.д.).',
|
||
'scene.previewEmpty': 'Превью не задано',
|
||
'scene.previewBusy': 'Загрузка и оптимизация изображения…',
|
||
'scene.previewBusySelecting': 'Выберите файл…',
|
||
'scene.previewOptimizing': 'Превью уже доступно. Оптимизируем в фоне…',
|
||
'scene.previewReady': 'Превью готово',
|
||
'scene.previewFailed': 'Превью добавлено, но оптимизация не удалась',
|
||
'scene.change': 'Изменить',
|
||
'scene.clear': 'Очистить',
|
||
'scene.autostart': 'Автостарт',
|
||
'scene.darkenScene': 'Затемнить сцену',
|
||
'scene.rotate': 'Повернуть',
|
||
'scene.audio': 'АУДИО СЦЕНЫ',
|
||
'scene.removeTitle': 'Убрать из сцены',
|
||
'scene.branching': 'ВЕТВЛЕНИЯ',
|
||
'scene.branchingHint':
|
||
'Перетащите сцену из списка на граф. С одной карточки можно задать несколько вариантов — по одной связи на каждую целевую сцену. Повторно к той же сцене (включая вторую карточку той же сцены на графе) подключить нельзя.',
|
||
|
||
'sceneCard.current': 'ТЕКУЩАЯ',
|
||
'sceneCard.menu': 'Меню сцены',
|
||
|
||
'graph.badgeStart': 'НАЧАЛО',
|
||
'graph.badgeSideStory': 'ПОБОЧНАЯ',
|
||
'graph.untitled': 'Без названия',
|
||
'graph.videoBadge': 'Видео',
|
||
'graph.audioBadge': 'Аудио',
|
||
'graph.loop': 'Цикл',
|
||
'graph.autoplay': 'Автостарт',
|
||
'graph.previewAutostart': 'Авто превью',
|
||
'graph.videoLoop': 'Цикл видео',
|
||
'graph.zoomBar': 'Масштаб графа',
|
||
'graph.zoomIn': 'Увеличить',
|
||
'graph.zoomOut': 'Уменьшить',
|
||
'graph.fitAll': 'Показать всё',
|
||
'graph.startScene': 'Начальная сцена',
|
||
'graph.unsetStartScene': 'Снять метку «Начальная сцена»',
|
||
'graph.sideStoryStartScene': 'Начальная сцена побочной линии',
|
||
'graph.unsetSideStoryStartScene': 'Снять метку «Начальная сцена побочной линии»',
|
||
'graph.runFromScene': 'Запустить с этой сцены',
|
||
|
||
'scene.sideStoryLineTitle': 'Название побочной линии',
|
||
|
||
'control.remoteTitle': 'ПУЛЬТ УПРАВЛЕНИЯ',
|
||
'control.instruments': 'ИНСТРУМЕНТЫ',
|
||
'control.descriptionTool': 'Описание',
|
||
'control.descriptionMissing': 'Описание отсутствует',
|
||
'control.effects': 'ЭФФЕКТЫ',
|
||
'control.tools': 'Инструменты',
|
||
'control.fieldEffects': 'Эффекты поля',
|
||
'control.actionEffects': 'Эффекты действий',
|
||
'control.eraser': 'Ластик',
|
||
'control.clearEffects': 'Очистить эффекты',
|
||
'control.fog': 'Туман',
|
||
'control.rain': 'Дождь',
|
||
'control.fire': 'Огонь',
|
||
'control.water': 'Вода',
|
||
'control.darkness': 'Тьма',
|
||
'control.darknessControl': 'Управление затемнением',
|
||
'control.explorerBrush': 'Кисть Открытия',
|
||
'control.lightning': 'Молния',
|
||
'control.sunbeam': 'Луч света',
|
||
'control.freeze': 'Заморозка',
|
||
'control.poisonCloud': 'Облако яда',
|
||
'control.brushRadius': 'Радиус кисти',
|
||
'control.storyLine': 'СЮЖЕТНАЯ ЛИНИЯ',
|
||
'control.gotoScene': 'Перейти к этой сцене',
|
||
'control.currentSceneBadge': 'ТЕКУЩАЯ СЦЕНА',
|
||
'control.passed': 'Пройдено',
|
||
'control.noActiveScene': 'Нет активной сцены.',
|
||
'control.screenPreview': 'Предпросмотр экрана',
|
||
'control.stopPresentation': 'Выключить демонстрацию',
|
||
'control.videoBrushHint':
|
||
'Видео-превью: кисть эффектов отключена (как на экране демонстрации — оверлей только для изображения).',
|
||
'control.branches': 'Варианты ветвления',
|
||
'control.returnToMainStory': 'Вернуться в основной сюжет',
|
||
'control.sideStoryLines': 'Побочные сюжетные линии',
|
||
'control.option': 'ОПЦИЯ {n}',
|
||
'control.unnamed': 'Без названия',
|
||
'control.switchScene': 'Переключить',
|
||
'control.noBranches': 'Нет вариантов перехода.',
|
||
'control.endPresentation': 'Завершить показ',
|
||
'control.music': 'Музыка',
|
||
'control.sceneMusic': 'МУЗЫКА СЦЕНЫ',
|
||
'control.gameMusic': 'МУЗЫКА ИГРЫ',
|
||
'control.noSceneAudio': 'В текущей сцене нет аудио.',
|
||
'control.noGameAudio': 'В игре нет аудио.',
|
||
'control.modeAuto': 'Авто',
|
||
'control.modeManual': 'Ручн.',
|
||
'control.once': 'Один раз',
|
||
'control.loop': 'Цикл',
|
||
'control.scrubSeek': 'Клик — перемотка',
|
||
'control.durationUnknown': 'Длительность неизвестна',
|
||
'control.pauseSceneMusic': 'Пауза (сцена)',
|
||
'control.pauseSceneMusicTitle': 'В сцене есть музыка',
|
||
'control.pauseCampaignTitle': 'Пауза: в сцене есть музыка',
|
||
'control.playFailed': 'Не удалось запустить.',
|
||
'control.audioAutoplayBlocked':
|
||
'Автозапуск заблокирован (нужно действие пользователя) или ошибка воспроизведения.',
|
||
'control.audioNoUrl': 'URL не получен',
|
||
'control.audioNoUrlDetail': 'Не удалось получить dnd://asset URL для аудио.',
|
||
'control.audioBlocked': 'Ошибка/блок',
|
||
'control.audioError': 'Ошибка',
|
||
'control.audioMediaError': 'MediaError code={code} (1=ABORTED, 2=NETWORK, 3=DECODE, 4=SRC_NOT_SUPPORTED)',
|
||
'control.audioLoading': 'Загрузка…',
|
||
'control.audioPlaying': 'Играет',
|
||
'control.audioPaused': 'Пауза',
|
||
'control.audioStopped': 'Остановлено',
|
||
'control.previewTrackLabel': 'Превью без субтитров',
|
||
'control.transportPlay': 'Воспроизведение',
|
||
'control.transportPause': 'Пауза',
|
||
'control.transportStop': 'Стоп',
|
||
},
|
||
en: {
|
||
'common.close': 'Close',
|
||
'common.cancel': 'Cancel',
|
||
'common.save': 'Save',
|
||
'common.edit': 'Edit',
|
||
'common.understood': 'OK',
|
||
'common.message': 'Message',
|
||
'common.error': 'Error',
|
||
'common.delete': 'Delete',
|
||
'common.closeMenu': 'Close menu',
|
||
|
||
'app.brandTitle': 'TTRPG Player',
|
||
|
||
'notice.campaignAudioEmpty': 'No audio was added. Check the file format.',
|
||
|
||
'license.checkingTitle': 'Checking license…',
|
||
'license.checkingWait': 'Please wait.',
|
||
'license.requiredTitle': 'License required',
|
||
'license.requiredHint':
|
||
'Enter your key via Settings → Enter license key. Until activation, only Settings is available.',
|
||
'license.tokenTitle': 'Enter license key',
|
||
'license.tokenKey': 'KEY',
|
||
'license.tokenPlaceholder': 'TTRPG- or DND- product key…',
|
||
'license.tokenSaving': 'Saving…',
|
||
'license.eulaTitle': 'End User License Agreement',
|
||
'license.eulaReject': 'Decline',
|
||
'license.eulaAccept': 'I accept the terms',
|
||
'license.eulaNoteEn':
|
||
'The binding legal text below is in Russian. If you need an English summary, contact support.',
|
||
'license.aboutTitle': 'About license',
|
||
'license.aboutDevSkip': 'Development mode: license checks are disabled (DND_SKIP_LICENSE).',
|
||
'license.aboutStatus': 'STATUS',
|
||
'license.aboutProduct': 'PRODUCT',
|
||
'license.aboutLicenseId': 'LICENSE ID',
|
||
'license.aboutExpiry': 'EXPIRES',
|
||
'license.aboutDevice': 'DEVICE',
|
||
'license.aboutNoData': 'No license data.',
|
||
'license.reason.ok': 'Active',
|
||
'license.reason.none': 'No key provided',
|
||
'license.reason.expired': 'Expired',
|
||
'license.reason.bad_signature': 'Invalid signature',
|
||
'license.reason.bad_payload': 'Invalid token format',
|
||
'license.reason.malformed': 'Malformed token',
|
||
'license.reason.not_yet_valid': 'Not yet valid',
|
||
'license.reason.wrong_device': 'Wrong bound device',
|
||
'license.reason.revoked_remote': 'Revoked on server',
|
||
|
||
'presentation.overlay': 'Presentation running',
|
||
'presentation.title': 'Presentation running',
|
||
'presentation.body': 'The editor is locked. Close the Presentation and Control windows to continue.',
|
||
|
||
'zip.progress': 'Operation progress',
|
||
'zip.importTitle': 'Import project',
|
||
'zip.exportTitle': 'Export project',
|
||
|
||
'top.settings': 'Settings',
|
||
'top.project': 'Project',
|
||
'top.file': 'File',
|
||
'top.backToProjects': 'Back to projects',
|
||
'top.appVersion': 'App version',
|
||
'top.run': 'Run',
|
||
'top.afterLicense': 'Available after license activation',
|
||
'top.setStartScene': 'Set a start scene on the graph (right‑click a node)',
|
||
'top.runHelpAria': 'How to enable the Run button',
|
||
|
||
'menu.enterKey': 'Enter license key',
|
||
'menu.aboutLicense': 'About license',
|
||
'menu.checkUpdates': 'Check for updates',
|
||
'menu.language': 'Language',
|
||
'menu.langRu': 'Русский',
|
||
'menu.langEn': 'English',
|
||
'menu.aboutProgram': 'About',
|
||
'menu.instructions': 'Instructions',
|
||
|
||
'top.aboutApp': 'About',
|
||
|
||
'app.about.title': 'About',
|
||
'app.about.tagline': 'Campaign editor and GM control panel for tabletop RPGs',
|
||
'app.about.description':
|
||
'TTRPG Player is a desktop app for game masters: build your campaign as a scene graph with maps, music and branching paths, then run the session from the control panel. Players see only the presentation on a second screen — no editor panels.',
|
||
'app.about.versionLabel': 'VERSION',
|
||
'app.about.developerLabel': 'DEVELOPER',
|
||
'app.about.developer':
|
||
'Independent development. For licensing, purchase and support questions, use the email below.',
|
||
'app.about.supportLabel': 'SUPPORT',
|
||
'app.about.supportEmail': 'player.ttrpg@gmail.com',
|
||
'app.about.websiteLabel': 'WEBSITE & UPDATES',
|
||
'app.about.websiteUrl': 'https://ttrpgplayer.ru/',
|
||
|
||
'help.title': 'Instructions',
|
||
'help.navAria': 'Instruction sections',
|
||
|
||
'help.section.overview.title': 'App overview',
|
||
'help.section.overview.body':
|
||
'When you launch the app, you land in the Editor — where you prepare your campaign: scenes, images, music, and how episodes connect. When it is time to play, click Run. That opens Presentation for your players and the Control panel for you.\n\nIn the editor, the left column lists scenes, the center shows the story map, and the right column holds game and scene settings.\n\nWhile a session is running, the editor is temporarily locked — that is normal. Close presentation and the control panel to edit again.\n\nInternet is only needed for license activation and update checks. All projects and files stay on your computer.',
|
||
|
||
'help.section.license.title': 'License and first launch',
|
||
'help.section.license.body':
|
||
'Before you can work with projects, activate your license once.\n\n1) Open Settings → Enter license key.\n\n2) If you see the license agreement, read and accept it.\n\n3) Paste the key from your email (TTRPG-… or legacy DND-…) and click Save.\n\nUntil activation succeeds, only Settings is available. After that, projects, scenes, and running a session unlock.\n\nStatus, expiry, and device binding are under Settings → About license. The key is tied to this PC; another computer may need a separate activation per your purchase terms.',
|
||
|
||
'help.section.projects.title': 'Projects',
|
||
'help.section.projects.body':
|
||
'A project is your whole campaign: scenes, media, and connections between them.\n\nCreate a new campaign:\n\n1) On the home screen, type a name in the field on the left.\n\n2) Click Create project.\n\nOpen an existing one — click its name in the list. Return to the list: Project → Home, or click the app title in the header.\n\nMove a campaign to another computer:\n\n1) Project → Export — save a copy as .ttrpg.zip.\n\n2) On the other PC — Project → Import and pick that file.\n\nRename an open project: File → Rename project (at least 3 characters; file names cannot contain <>:"/\\|?*).\n\nTo delete a project from disk:\n\n1) On the project card, click ⋮.\n\n2) In the menu, choose Delete.\n\n3) Confirm deletion in the dialog.\n\nThe project file and cache are then removed permanently. If you might need the campaign again, export a backup first.',
|
||
|
||
'help.section.scenes.title': 'Scenes',
|
||
'help.section.scenes.body':
|
||
'A scene is one episode: a location, story beat, or dialogue. It has a title, an image or video for players, notes for the GM, and its own music.\n\nAdd a scene:\n\n1) In the left column, click + New scene.\n\n2) Set the title and adjust properties on the right (see Scene properties).\n\nSearch scenes… helps you find one quickly. Click a card to select it — it will also highlight on the story map.\n\nDelete: right-click a list card → Delete. The scene disappears from the list and map, including all links.\n\nDrag a scene from the list onto the map to place it as a node (see Scene graph).',
|
||
|
||
'help.section.graph.title': 'Scene graph',
|
||
'help.section.graph.body':
|
||
'The map in the center shows how episodes connect. Each box is a spot on your story; the same scene can appear more than once (for example, players return to the tavern).\n\nPlace a scene on the map:\n\n1) Grab a scene in the left list.\n\n2) Drag it onto empty space on the map.\n\nConnect two scenes:\n\n1) Point at the bottom dot on the first card.\n\n2) Drag a line to the top dot on the second and release — an arrow appears.\n\nOne scene can have several outgoing arrows — that is how you branch the story. You cannot draw a second arrow between the same pair.\n\nRemove an arrow: right-click the line → Delete. A normal click on the line does nothing.\n\nSet where the game starts: right-click a card → Start scene (a START badge appears), then click Run in the header. You can also start from any main-story card: right-click → Start from this scene — presentation and the control panel open at that spot (no START badge required). Side-story cards do not offer this menu item.\n\nRemove a card from the map without deleting the scene from the list: right-click → Delete.\n\nZoom with the buttons at the bottom or the mouse wheel. Fit view shows the whole map.',
|
||
|
||
'help.section.sideStorylines.title': 'Side storylines',
|
||
'help.section.sideStorylines.body':
|
||
'A side storyline is a separate branch not connected to the main plot — for detours, flashbacks, side quests, and scenes off the main path.\n\nCreate one in the editor:\n\n1) Place scenes on the map and link them in an isolated group — it must not touch the main story (purple links) or other side storylines.\n\n2) Right-click the starting card → Side storyline start scene. A blue SIDE badge appears.\n\n3) In scene properties, set Side storyline title — it appears on the control panel.\n\nThe menu item is hidden if the card is already linked to the main story (purple START anywhere in the group) or another side storyline (blue SIDE in the group).\n\nLinks inside a side storyline and card selection use blue (#0078d4). You cannot link main to side, or one side storyline to another.\n\nClear the mark: right-click → Clear side storyline start mark. The title is cleared and the tile disappears from the control panel.\n\nDeleting the start card: if there is a next scene along an arrow, the mark moves to it; otherwise the whole side storyline is removed from the map.\n\nDuring play, Side storylines appears under Music on the control panel — tiles with preview and title. Clicking jumps to the first scene. The app remembers which main-story scene you left from.\n\nWhile in a side storyline, Branch options always lists Return to main story first — back to the remembered scene. Storyline history keeps recording all steps, including inside side branches.\n\nYou cannot launch a side storyline from the editor — only from the control panel during a session.',
|
||
|
||
'help.section.sceneProps.title': 'Scene properties',
|
||
'help.section.sceneProps.body':
|
||
'Select a scene in the left list — its properties open on the right.\n\nScene title and Description are for the GM. The description appears in the storyline on the control panel.\n\nIf the scene has a graph card with a blue SIDE badge, a Side storyline title field appears.\n\nImage or video for players:\n\n1) Under Scene preview, click Upload or Change.\n\n2) Pick a file (PNG, JPG, WebP, GIF, etc.).\n\n3) For images, use Rotate (90° steps). Clear removes the preview.\n\n4) For images, enable Darken scene so players start in full darkness and you reveal the map with the Opening brush on the control panel (see Effects).\n\nFor video, enable Autostart if the clip should start on its own on the player screen. Brush effects are not available on video scenes.\n\nScene audio — music and sounds for this episode. Upload adds files. Per track: Auto (play when entering the scene) and Loop. The trash icon removes a track.\n\nLinks between scenes are drawn on the map, not here — see Scene graph.',
|
||
|
||
'help.section.campaignAudio.title': 'Game audio',
|
||
'help.section.campaignAudio.body':
|
||
'Game audio under Game properties is music for the whole campaign: theme, ambience, background. It is not tied to one scene.\n\n1) Click Upload and choose files.\n\n2) Set Auto and Loop per track as you like.\n\n3) Remove a track with the trash icon.\n\nOn the control panel, scene music comes first: while a scene track plays, campaign music pauses. When the scene has no track or you take manual control, campaign music can play again.',
|
||
|
||
'help.section.session.title': 'Starting a session',
|
||
'help.section.session.body':
|
||
'When your campaign is ready, you can start playing.\n\nStandard start:\n\n1) On the story map, right-click the starting card → Start scene.\n\n2) Click Run in the editor header.\n\nQuick start from any card: right-click the card on the map → Start from this scene. Presentation and the control panel open at that spot right away.\n\nPresentation (for players) and the Control panel (for you) open. The editor dims while the show runs — that is expected.\n\nReturn to prep:\n\n1) On the control panel, click Stop presentation or End presentation (when there is nowhere left to go).\n\n2) Wait until both windows close.\n\nMove the Presentation window to a second monitor, projector, or TV and go fullscreen (F11). Players see only the image, video, and effects — not your buttons.',
|
||
|
||
'help.section.controlPanel.title': 'Control panel',
|
||
'help.section.controlPanel.body':
|
||
'The control panel is your desk during play. Players watch presentation; you run everything from here.\n\nOn the left: effects and storyline. On the right: a mini copy of the player screen, branch options, and music.\n\nScreen preview shows what players see. On image scenes, paint effects here — they appear on the big screen right away.\n\nStoryline lists episodes you have visited. The current scene is marked CURRENT SCENE. Click an earlier step to move the party back to that spot and add a new history entry.\n\nStop presentation ends the show and unlocks the editor.',
|
||
|
||
'help.section.transitions.title': 'Scene transitions',
|
||
'help.section.transitions.body':
|
||
'Where you can go next appears under Branch options on the control panel. These are outgoing arrows from the current card on the map.\n\n1) Read the OPTION 1, OPTION 2 cards — they show target scene names.\n\n2) Click Switch on the choice you want.\n\n3) Presentation updates image and music; storyline adds a new step.\n\nIf there are no options (end of a branch), you will see No transitions available. Click End presentation to close the show windows.\n\nOptions only exist where you drew arrows on the map in the editor.',
|
||
|
||
'help.section.music.title': 'Music on the control panel',
|
||
'help.section.music.body':
|
||
'The Music section mirrors what you set in the editor — Scene music and Game music separately.\n\n▶ play, ⏸ pause, ⏹ stop. Auto/Manual and Loop/Once show how each track was configured in the editor.\n\nClick the progress bar to seek (when duration is known). ← → on the bar (when focused) skip 5 seconds back or forward.\n\nIf music does not start on its own, press ▶ once — after your click, sound is usually allowed. If a file still will not play, check the format (MP3, WAV, etc.).',
|
||
|
||
'help.section.effects.title': 'Field and action effects',
|
||
'help.section.effects.body':
|
||
'Effects work on image scenes, not video. Paint in Screen preview — players see the same on presentation.\n\nPick a tool on the left:\n• Field effects (fog, rain, fire, water) — hold the left button and brush on the map.\n• Action effects (lightning, sunbeam, freeze, darkness, poison cloud) — click or short stroke; some include sound.\n\nIf Darken scene is enabled in scene properties, a Darkness control section appears with the Opening brush 🔦. Brush on the preview to clear darkness on both screens at once. Unrevealed areas stay fully black for players and half-dark on your preview. Revealed areas are remembered while the show runs and you return to the same graph card. This is not the same as the Darkness 🌑 action effect.\n\nEraser 🧹 — for field effects (fog, rain, fire, water), brush like the Opening brush for darkness: only the stroke area is erased. Action effects (lightning, sunbeam, etc.) are removed whole when you click or drag over them. Clear effects removes everything at once.\n\nBrush radius under the panel — higher values mean a wider stroke.',
|
||
|
||
'help.section.presentation.title': 'Presentation screen',
|
||
'help.section.presentation.body':
|
||
'Presentation is what players see: the scene image (with rotation from the editor) or video according to your settings.\n\nEffects from the control panel draw on top. There are no GM menus or buttons here.\n\nIf Darken scene is enabled, players first see a fully black screen. The GM reveals the map with the Opening brush on the control panel.\n\nWhen you switch scenes from the control panel, the image updates automatically. Move the window to the display players watch and hide the taskbar if needed.\n\nA blank or dark screen usually means the scene has no preview — add one in scene properties in the editor.',
|
||
|
||
'help.section.importExport.title': 'Import and export',
|
||
'help.section.importExport.body':
|
||
'A project is stored as a .ttrpg.zip file — images, sounds, and settings are already inside.\n\nBackup or move to another PC:\n\n1) Project → Export.\n\n2) Choose the project and where to save.\n\n3) Copy the .ttrpg.zip to a USB drive, cloud, or another computer.\n\nLoad a backup:\n\n1) Project → Import.\n\n2) Select the .ttrpg.zip.\n\n3) The project appears on the home screen.\n\nLarge archives show a progress bar. You do not need to move images and music separately — everything is in the archive.',
|
||
|
||
'help.section.settings.title': 'Settings, language and updates',
|
||
'help.section.settings.body':
|
||
'Settings → Enter license key — add or change your license. About license — check status and expiry.\n\nCheck for updates (installed app with active license) — if a new version exists, you can download and restart.\n\nLanguage → Русский or English changes the interface. Your choice is saved between launches.\n\nThe version number is in the header, right of the menus. About → About opens product info and support contacts.',
|
||
|
||
'updates.dialogTitle': 'Updates',
|
||
'updates.checking': 'Checking for updates…',
|
||
'updates.available': 'A new version is available: {version}.',
|
||
'updates.current': 'You have the latest version ({version}).',
|
||
'updates.error': 'Could not check for updates: {message}',
|
||
'updates.notPackaged': 'Updates can only be checked in the installed application.',
|
||
'updates.noLicense': 'An active license is required.',
|
||
'updates.download': 'Update',
|
||
'updates.downloading': 'Downloading…',
|
||
'updates.stageLine': 'Stage: {stage}',
|
||
'updates.stage.checking': 'checking for updates',
|
||
'updates.stage.available': 'version {version} available',
|
||
'updates.stage.not-available': 'up to date',
|
||
'updates.stage.downloading': 'downloading{percent}',
|
||
'updates.stage.installing': 'installing and restarting',
|
||
'updates.stage.error': 'error',
|
||
'updates.stagePercent': ' ({percent}%)',
|
||
|
||
'projectMenu.home': 'Home',
|
||
'projectMenu.import': 'Import',
|
||
'projectMenu.export': 'Export',
|
||
'projectMenu.noProjects': 'No saved projects',
|
||
|
||
'fileMenu.rename': 'Rename project',
|
||
|
||
'scenes.search': 'Search scenes…',
|
||
'scenes.new': '+ New scene',
|
||
'scenes.dropHint': 'Drop images or videos',
|
||
'scenes.batchTitle': 'Creating scenes',
|
||
'scenes.batchProgress': 'Scene {current} of {total}',
|
||
'scenes.dropSkippedTitle': 'Some files were not added',
|
||
'scenes.dropSkippedIntro': 'These files were skipped:',
|
||
'scenes.dropSkippedUnsupported': 'unsupported format',
|
||
'scenes.dropSkippedNoPath': 'could not resolve file path',
|
||
'scenes.inspectorGame': 'Game properties',
|
||
'scenes.inspectorScene': 'Scene properties',
|
||
'scenes.selectHint': 'Select a scene on the left to edit its properties.',
|
||
'scenes.openProjectHint': 'Open a project to edit the campaign and scenes.',
|
||
|
||
'rename.title': 'Rename project',
|
||
'rename.projectName': 'PROJECT NAME',
|
||
'rename.projectPlaceholder': 'Project name…',
|
||
'rename.projectMin': 'At least 3 characters.',
|
||
'rename.projectDup': 'A project with this name already exists.',
|
||
'rename.fileName': 'PROJECT FILE NAME',
|
||
'rename.fileInvalid': 'At least 3 characters; forbidden characters <>:"/\\|?*',
|
||
'rename.fileDup': 'A project file with this name already exists.',
|
||
'rename.saving': 'Saving…',
|
||
|
||
'export.title': 'Export project',
|
||
'export.project': 'PROJECT',
|
||
'export.hint':
|
||
'Select storylines to export. The archive will include only the chosen lines, their scenes, and assets. Then choose where to save the .ttrpg.zip file.',
|
||
'export.exporting': 'Exporting…',
|
||
'export.saveAs': 'Save as…',
|
||
|
||
'storyline.section': 'STORYLINE',
|
||
'storyline.main': 'Main storyline',
|
||
'storyline.loading': 'Loading storylines…',
|
||
'storyline.empty': 'This project has no storylines marked with START or SIDE badges.',
|
||
'storyline.mainExistsHint': 'main storyline already exists in this project',
|
||
|
||
'importSource.title': 'Import',
|
||
'importSource.type': 'IMPORT TYPE',
|
||
'importSource.fromProject': 'From project',
|
||
'importSource.fromFile': 'From file',
|
||
'importSource.project': 'PROJECT',
|
||
'importSource.file': 'FILE',
|
||
'importSource.chooseFile': 'Choose file',
|
||
'importSource.noFileSelected': 'No file selected',
|
||
'importSource.noOtherProjects': 'No other projects available to import from.',
|
||
'importSource.fileOnlyHint': 'Choose a project file (.ttrpg.zip) for a full import.',
|
||
|
||
'importStoryline.title': 'Import storylines',
|
||
'importStoryline.source': 'SOURCE',
|
||
'importStoryline.continue': 'Continue',
|
||
'importStoryline.import': 'Import',
|
||
'importStoryline.conflictsTitle': 'Duplicate scene titles',
|
||
'importStoryline.conflictsHint':
|
||
'Imported storylines contain scenes with the same titles as in the current project. Choose what to do for each.',
|
||
'importStoryline.createNewScene': 'Create new scene',
|
||
'importStoryline.useExistingScene': 'Use existing «{title}»',
|
||
'importStoryline.reportTitle': 'Import complete',
|
||
'importStoryline.reportLines': 'Storylines imported: {count}',
|
||
'importStoryline.reportScenesCreated': 'New scenes created: {count}',
|
||
'importStoryline.reportScenesReused': 'Existing scenes reused: {count}',
|
||
'importStoryline.reportNodes': 'Graph cards added: {count}',
|
||
'importStoryline.reportEdges': 'Connections added: {count}',
|
||
'importStoryline.reportAssetsCopied': 'Asset files copied: {count}',
|
||
'importStoryline.reportAssetsReused': 'Assets reused: {count}',
|
||
'importStoryline.reportRenamedSides': 'Renamed side storylines: {names}',
|
||
|
||
'confirmDelete.title': 'Delete project',
|
||
'confirmDelete.body':
|
||
'Permanently delete project “{name}”? The file and cache will be removed from disk.',
|
||
'confirmDelete.failedTitle': 'Could not delete',
|
||
|
||
'picker.title': 'Projects',
|
||
'picker.newPlaceholder': 'New project name…',
|
||
'picker.create': 'Create project',
|
||
'picker.existing': 'EXISTING',
|
||
'picker.lockedHint':
|
||
'Opening and creating projects require an active license. The list still shows files in the app folder.',
|
||
'picker.empty': 'No projects yet.',
|
||
'picker.projectMenu': 'Project menu',
|
||
'picker.openDisabled': 'Open project — after license activation',
|
||
'picker.opening': 'Opening…',
|
||
'picker.defaultName': 'My campaign',
|
||
|
||
'campaign.label': 'GAME AUDIO',
|
||
'campaign.noFiles': 'No files yet. Add audio.',
|
||
'campaign.auto': 'Auto',
|
||
'campaign.loop': 'Loop',
|
||
'campaign.removeTitle': 'Remove from campaign',
|
||
'campaign.upload': 'Upload',
|
||
'drop.hintAudio': 'Drop audio files here',
|
||
'drop.hintPreview': 'Drop an image or video',
|
||
|
||
'scene.title': 'SCENE TITLE',
|
||
'scene.description': 'DESCRIPTION',
|
||
'scene.descriptionEmpty': 'no description',
|
||
'scene.descriptionModalTitle': 'Scene description',
|
||
'scene.descriptionPlaceholder': 'Enter scene description…',
|
||
'scene.descriptionToolbar': 'Formatting',
|
||
'scene.descriptionBold': 'Bold',
|
||
'scene.descriptionItalic': 'Italic',
|
||
'scene.descriptionUnderline': 'Underline',
|
||
'scene.descriptionHeading2': 'Heading',
|
||
'scene.descriptionHeading3': 'Subheading',
|
||
'scene.descriptionQuote': 'Quote',
|
||
'scene.descriptionBulletList': 'Bullet list',
|
||
'scene.descriptionOrderedList': 'Numbered list',
|
||
'scene.descriptionLink': 'Link',
|
||
'scene.descriptionLinkPrompt': 'Link URL',
|
||
'scene.preview': 'SCENE PREVIEW',
|
||
'scene.previewHint': 'Image file (PNG, JPG, WebP, GIF, etc.).',
|
||
'scene.previewEmpty': 'No preview',
|
||
'scene.previewBusy': 'Loading and optimizing image…',
|
||
'scene.previewBusySelecting': 'Choose a file…',
|
||
'scene.previewOptimizing': 'Preview is ready. Optimizing in the background…',
|
||
'scene.previewReady': 'Preview is ready',
|
||
'scene.previewFailed': 'Preview was added, but optimization failed',
|
||
'scene.change': 'Change',
|
||
'scene.clear': 'Clear',
|
||
'scene.autostart': 'Autostart',
|
||
'scene.darkenScene': 'Darken scene',
|
||
'scene.rotate': 'Rotate',
|
||
'scene.audio': 'SCENE AUDIO',
|
||
'scene.sideStoryLineTitle': 'Side storyline title',
|
||
'scene.removeTitle': 'Remove from scene',
|
||
'scene.branching': 'BRANCHING',
|
||
'scene.branchingHint':
|
||
'Drag a scene from the list onto the graph. One card can branch to several targets — one link per target scene. You cannot link twice to the same target (including a second card of the same scene).',
|
||
|
||
'sceneCard.current': 'CURRENT',
|
||
'sceneCard.menu': 'Scene menu',
|
||
|
||
'graph.badgeStart': 'START',
|
||
'graph.badgeSideStory': 'SIDE',
|
||
'graph.untitled': 'Untitled',
|
||
'graph.videoBadge': 'Video',
|
||
'graph.audioBadge': 'Audio',
|
||
'graph.loop': 'Loop',
|
||
'graph.autoplay': 'Autoplay',
|
||
'graph.previewAutostart': 'Preview autostart',
|
||
'graph.videoLoop': 'Video loop',
|
||
'graph.zoomBar': 'Graph zoom',
|
||
'graph.zoomIn': 'Zoom in',
|
||
'graph.zoomOut': 'Zoom out',
|
||
'graph.fitAll': 'Fit view',
|
||
'graph.startScene': 'Start scene',
|
||
'graph.unsetStartScene': 'Clear start scene mark',
|
||
'graph.sideStoryStartScene': 'Side storyline start scene',
|
||
'graph.unsetSideStoryStartScene': 'Clear side storyline start mark',
|
||
'graph.runFromScene': 'Start from this scene',
|
||
|
||
'control.remoteTitle': 'CONTROL PANEL',
|
||
'control.instruments': 'TOOLS',
|
||
'control.descriptionTool': 'Description',
|
||
'control.descriptionMissing': 'No description',
|
||
'control.effects': 'EFFECTS',
|
||
'control.tools': 'Tools',
|
||
'control.fieldEffects': 'Field effects',
|
||
'control.actionEffects': 'Action effects',
|
||
'control.eraser': 'Eraser',
|
||
'control.clearEffects': 'Clear effects',
|
||
'control.fog': 'Fog',
|
||
'control.rain': 'Rain',
|
||
'control.fire': 'Fire',
|
||
'control.water': 'Water',
|
||
'control.darkness': 'Darkness',
|
||
'control.darknessControl': 'Darkness control',
|
||
'control.explorerBrush': 'Opening brush',
|
||
'control.lightning': 'Lightning',
|
||
'control.sunbeam': 'Sunbeam',
|
||
'control.freeze': 'Freeze',
|
||
'control.poisonCloud': 'Poison cloud',
|
||
'control.brushRadius': 'Brush radius',
|
||
'control.storyLine': 'STORYLINE',
|
||
'control.gotoScene': 'Go to this scene',
|
||
'control.currentSceneBadge': 'CURRENT SCENE',
|
||
'control.passed': 'Visited',
|
||
'control.noActiveScene': 'No active scene.',
|
||
'control.screenPreview': 'Screen preview',
|
||
'control.stopPresentation': 'Stop presentation',
|
||
'control.videoBrushHint':
|
||
'Video preview: effect brush is disabled (like on the presentation screen — overlay is for images only).',
|
||
'control.branches': 'Branch options',
|
||
'control.returnToMainStory': 'Return to main story',
|
||
'control.sideStoryLines': 'Side storylines',
|
||
'control.option': 'OPTION {n}',
|
||
'control.unnamed': 'Untitled',
|
||
'control.switchScene': 'Switch',
|
||
'control.noBranches': 'No transitions available.',
|
||
'control.endPresentation': 'End presentation',
|
||
'control.music': 'Music',
|
||
'control.sceneMusic': 'SCENE MUSIC',
|
||
'control.gameMusic': 'GAME MUSIC',
|
||
'control.noSceneAudio': 'No audio in the current scene.',
|
||
'control.noGameAudio': 'No game audio.',
|
||
'control.modeAuto': 'Auto',
|
||
'control.modeManual': 'Manual',
|
||
'control.once': 'Once',
|
||
'control.loop': 'Loop',
|
||
'control.scrubSeek': 'Click to seek',
|
||
'control.durationUnknown': 'Duration unknown',
|
||
'control.pauseSceneMusic': 'Paused (scene)',
|
||
'control.pauseSceneMusicTitle': 'Scene has music',
|
||
'control.pauseCampaignTitle': 'Paused: scene has music',
|
||
'control.playFailed': 'Could not start playback.',
|
||
'control.audioAutoplayBlocked': 'Autoplay was blocked (user gesture required) or playback failed.',
|
||
'control.audioNoUrl': 'No URL',
|
||
'control.audioNoUrlDetail': 'Could not get dnd://asset URL for audio.',
|
||
'control.audioBlocked': 'Error / blocked',
|
||
'control.audioError': 'Error',
|
||
'control.audioMediaError': 'MediaError code={code} (1=ABORTED, 2=NETWORK, 3=DECODE, 4=SRC_NOT_SUPPORTED)',
|
||
'control.audioLoading': 'Loading…',
|
||
'control.audioPlaying': 'Playing',
|
||
'control.audioPaused': 'Paused',
|
||
'control.audioStopped': 'Stopped',
|
||
'control.previewTrackLabel': 'Preview (no captions)',
|
||
'control.transportPlay': 'Play',
|
||
'control.transportPause': 'Pause',
|
||
'control.transportStop': 'Stop',
|
||
},
|
||
};
|
||
|
||
export function translateEditorMessage(
|
||
locale: EditorLocale,
|
||
key: string,
|
||
vars?: Record<string, string | number>,
|
||
): string {
|
||
let s = EDITOR_MESSAGES[locale][key] ?? EDITOR_MESSAGES.ru[key] ?? key;
|
||
if (vars) {
|
||
for (const [k, v] of Object.entries(vars)) {
|
||
s = s.split(`{${k}}`).join(String(v));
|
||
}
|
||
}
|
||
return s;
|
||
}
|