Files
DndGamePlayer/app/renderer/editor/i18n/editorMessages.ts
T
Ivan Fontosh ca0cf9c0ce fix(control): improve storyline history and graph launch
Append history on revisit instead of truncating, highlight the latest duplicate entry, add Start from this scene to the graph context menu, and keep storyline state isolated from project graph edits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-02 19:14:01 +08:00

707 lines
55 KiB
TypeScript
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.
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.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': 'Назначьте начальную сцену на графе (ПКМ по узлу)',
'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) Откройте «Настройки» → «Указать ключ».\n2) Если появится лицензионное соглашение — прочитайте и примите его.\n3) Вставьте ключ из письма (формат TTRPG-… или старый DND-…) и нажмите «Сохранить».\n\nДо активации доступны только настройки. После успешного сохранения ключа откроются проекты, сцены и запуск сессии.\n\nСтатус, срок действия и привязка к компьютеру — в «Настройки» → «О лицензии». Ключ привязан к этому ПК; на другом компьютере понадобится отдельная активация по условиям покупки.',
'help.section.projects.title': 'Проекты',
'help.section.projects.body':
'Проект — вся кампания целиком: сцены, медиа и связи между ними.\n\nСоздать новую кампанию:\n1) На начальном экране введите название в поле слева.\n2) Нажмите «Создать проект».\n\nОткрыть существующую — кликните по названию в списке. Вернуться к списку: «Проект» → «Начальный экран» или клик по названию приложения в шапке.\n\nПеренести кампанию на другой компьютер:\n1) «Проект» → «Экспорт» — сохраните копию как .ttrpg.zip.\n2) На другом ПК — «Проект» → «Импорт» и выберите этот файл.\n\nПереименовать открытый проект: «Файл» → «Переименовать проект» (минимум 3 символа; в имени файла нельзя использовать <>:"/\\|?*).\n\nКнопка «⋯» у карточки удаляет проект без восстановления — используйте осторожно.',
'help.section.scenes.title': 'Сцены',
'help.section.scenes.body':
'Сцена — отдельный эпизод: локация, кадр истории, диалог. У неё есть название, картинка или видео для игроков, описание для мастера и своя музыка.\n\nДобавить сцену:\n1) В левой колонке нажмите «+ Новая сцена».\n2) Задайте название и настройте свойства справа (см. «Свойства сцены»).\n\n«Поиск сцен…» помогает быстро найти нужную. Клик по карточке выделяет сцену — она же подсветится на карте связей.\n\nУдалить: правый клик по карточке в списке → «Удалить». Сцена исчезнет из списка и с карты вместе со связями.\n\nПеретащите сцену из списка на карту — так она появится как узел (подробнее в «Граф сцен»).',
'help.section.graph.title': 'Граф сцен',
'help.section.graph.body':
'Карта в центре показывает, как эпизоды связаны. Каждый прямоугольник — место на схеме; одна сцена может встретиться несколько раз (например, игроки снова возвращаются в таверну).\n\nДобавить сцену на карту:\n1) Возьмите сцену в левом списке.\n2) Перетащите на свободное место на карте.\n\nСделать переход между сценами:\n1) Наведите на нижнюю точку первой карточки.\n2) Потяните линию к верхней точке второй и отпустите — появится стрелка.\n\nИз одной сцены может выходить несколько стрелок — так вы делаете ветвление сюжета. Вторую стрелку к той же паре сцен провести нельзя.\n\nУдалить стрелку: правый клик по линии → «Удалить». Обычный клик по линии ничего не делает.\n\nС чего начинается игра: правый клик по карточке → «Начальная сцена» (появится метка «НАЧАЛО»). Убрать карточку с карты, не удаляя сцену из списка: ПКМ → «Удалить».\n\nМасштаб — кнопки внизу или колёсико мыши. «Показать всё» вместит всю схему на экран.',
'help.section.sceneProps.title': 'Свойства сцены',
'help.section.sceneProps.body':
'Выберите сцену в списке слева — справа откроются её свойства.\n\n«Название сцены» и «Описание» — для мастера. Описание видно на пульте в блоке сюжетной линии.\n\nКартинка или видео для игроков:\n1) В «Превью сцены» нажмите «Загрузить» или «Изменить».\n2) Выберите файл (PNG, JPG, WebP, GIF и др.).\n3) Для картинки можно «Повернуть» (шаг 90°). «Очистить» — убрать превью.\n\nДля видео включите «Автостарт», если ролик должен сам начаться на экране игроков. На видео-сценах эффекты кистью недоступны.\n\n«Аудио сцены» — музыка и звуки этого эпизода. «Загрузить» добавляет файлы. У каждого трека: «Авто» (играть при входе в сцену) и «Цикл». Корзина удаляет трек.\n\nСвязи между сценами задаются на карте, а не здесь — см. «Граф сцен».',
'help.section.campaignAudio.title': 'Аудио игры',
'help.section.campaignAudio.body':
'«Аудио игры» в блоке «Свойства игры» — музыка всей кампании: тема, фон, атмосфера. Она не привязана к одной сцене.\n\n1) Нажмите «Загрузить» и выберите файлы.\n2) Для каждого трека отметьте «Авто» и «Цикл» по желанию.\n3) Удалить трек — иконка корзины.\n\nНа пульте музыка сцены важнее общей: пока играет трек сцены, кампанийная музыка приглушается. Когда у сцены нет своего звука или вы переключитесь вручную — общая музыка снова может играть.',
'help.section.session.title': 'Запуск сессии',
'help.section.session.body':
'Когда кампания готова, можно начать игру.\n\n1) На карте связей щёлкните правой кнопкой по карточке старта → «Начальная сцена».\n2) Нажмите «Запустить» в шапке редактора.\n3) Откроются «Презентация» (для игроков) и «Пульт управления» (для вас).\n\nРедактор на время показа затемняется — так и должно быть.\n\nВернуться к подготовке:\n1) На пульте нажмите «Выключить демонстрацию» или «Завершить показ» (если дальше некуда переходить).\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» — там названия целевых сцен.\n2) Нажмите «Переключить» у нужного варианта.\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«Радиус кисти» под панелью — чем больше число, тем шире мазок.',
'help.section.presentation.title': 'Экран презентации',
'help.section.presentation.body':
'«Презентация» — то, что видят игроки: картинка сцены (с учётом поворота из редактора) или видео по вашим настройкам.\n\nЭффекты с пульта накладываются поверх. Меню и кнопки мастера здесь не показываются.\n\nПри смене сцены с пульта картинка обновляется сама. Перенесите окно на экран для игроков и при необходимости спрячьте панель задач.\n\nПустой или тёмный экран — скорее всего, у сцены нет превью. Добавьте картинку в свойствах сцены в редакторе.',
'help.section.importExport.title': 'Импорт и экспорт',
'help.section.importExport.body':
'Проект хранится как файл .ttrpg.zip — внутри уже все картинки, звуки и настройки.\n\nРезервная копия или перенос на другой ПК:\n1) «Проект» → «Экспорт».\n2) Выберите проект и куда сохранить.\n3) Скопируйте .ttrpg.zip на флешку, в облако или на другой компьютер.\n\nЗагрузить бэкап:\n1) «Проект» → «Импорт».\n2) Выберите .ttrpg.zip.\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.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': 'Сохранить как…',
'confirmDelete.title': 'Удаление проекта',
'confirmDelete.body': 'Удалить проект «{name}» безвозвратно? Файл и кэш будут стёрты с диска.',
'confirmDelete.failedTitle': 'Не удалось удалить',
'picker.title': 'Проекты',
'picker.newPlaceholder': 'Название нового проекта…',
'picker.create': 'Создать проект',
'picker.existing': 'СУЩЕСТВУЮЩИЕ',
'picker.lockedHint':
'Открытие и создание — после активации лицензии. Список показывает файлы в папке приложения.',
'picker.empty': 'Пока нет проектов.',
'picker.projectMenu': 'Меню проекта',
'picker.openDisabled': 'Открытие проекта — после активации лицензии',
'picker.defaultName': 'Моя кампания',
'campaign.label': 'АУДИО ИГРЫ',
'campaign.noFiles': 'Файлов пока нет. Добавьте аудио.',
'campaign.auto': 'Авто',
'campaign.loop': 'Цикл',
'campaign.removeTitle': 'Убрать из кампании',
'campaign.upload': 'Загрузить',
'scene.title': 'НАЗВАНИЕ СЦЕНЫ',
'scene.description': 'ОПИСАНИЕ',
'scene.preview': 'ПРЕВЬЮ СЦЕНЫ',
'scene.previewHint': 'Файл изображения (PNG, JPG, WebP, GIF и т.д.).',
'scene.previewEmpty': 'Превью не задано',
'scene.previewBusy': 'Загрузка и оптимизация изображения…',
'scene.change': 'Изменить',
'scene.clear': 'Очистить',
'scene.autostart': 'Автостарт',
'scene.rotate': 'Повернуть',
'scene.audio': 'АУДИО СЦЕНЫ',
'scene.removeTitle': 'Убрать из сцены',
'scene.branching': 'ВЕТВЛЕНИЯ',
'scene.branchingHint':
'Перетащите сцену из списка на граф. С одной карточки можно задать несколько вариантов — по одной связи на каждую целевую сцену. Повторно к той же сцене (включая вторую карточку той же сцены на графе) подключить нельзя.',
'sceneCard.current': 'ТЕКУЩАЯ',
'sceneCard.menu': 'Меню сцены',
'graph.badgeStart': 'НАЧАЛО',
'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.runFromScene': 'Запустить с этой сцены',
'control.remoteTitle': 'ПУЛЬТ УПРАВЛЕНИЯ',
'control.effects': 'ЭФФЕКТЫ',
'control.tools': 'Инструменты',
'control.fieldEffects': 'Эффекты поля',
'control.actionEffects': 'Эффекты действий',
'control.eraser': 'Ластик',
'control.clearEffects': 'Очистить эффекты',
'control.fog': 'Туман',
'control.rain': 'Дождь',
'control.fire': 'Огонь',
'control.water': 'Вода',
'control.darkness': 'Тьма',
'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.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.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 (rightclick a node)',
'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.\n2) If you see the license agreement, read and accept it.\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:\n1) On the home screen, type a name in the field on the left.\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:\n1) Project → Export — save a copy as .ttrpg.zip.\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\nThe ⋯ button on a project card deletes it permanently — use with care.',
'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:\n1) In the left column, click + New scene.\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:\n1) Grab a scene in the left list.\n2) Drag it onto empty space on the map.\n\nConnect two scenes:\n1) Point at the bottom dot on the first card.\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). Remove 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.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\nImage or video for players:\n1) Under Scene preview, click Upload or Change.\n2) Pick a file (PNG, JPG, WebP, GIF, etc.).\n3) For images, use Rotate (90° steps). Clear removes the preview.\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.\n2) Set Auto and Loop per track as you like.\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\n1) On the story map, right-click the starting card → Start scene.\n2) Click Run in the editor header.\n3) Presentation (for players) and the Control panel (for you) open.\n\nThe editor dims while the show runs — that is expected.\n\nReturn to prep:\n1) On the control panel, click Stop presentation or End presentation (when there is nowhere left to go).\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.\n2) Click Switch on the choice you want.\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\nEraser 🧹 — click or drag over an effect to remove it. 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\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:\n1) Project → Export.\n2) Choose the project and where to save.\n3) Copy the .ttrpg.zip to a USB drive, cloud, or another computer.\n\nLoad a backup:\n1) Project → Import.\n2) Select the .ttrpg.zip.\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.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':
'A save dialog will open: choose a name and folder for the .ttrpg.zip file — a copy of the project archive will be created.',
'export.exporting': 'Exporting…',
'export.saveAs': 'Save as…',
'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.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',
'scene.title': 'SCENE TITLE',
'scene.description': 'DESCRIPTION',
'scene.preview': 'SCENE PREVIEW',
'scene.previewHint': 'Image file (PNG, JPG, WebP, GIF, etc.).',
'scene.previewEmpty': 'No preview',
'scene.previewBusy': 'Loading and optimizing image…',
'scene.change': 'Change',
'scene.clear': 'Clear',
'scene.autostart': 'Autostart',
'scene.rotate': 'Rotate',
'scene.audio': 'SCENE AUDIO',
'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.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.runFromScene': 'Start from this scene',
'control.remoteTitle': 'CONTROL PANEL',
'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.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.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;
}