Files
scrabble-game/ui/src/lib/i18n/ru.ts
T
Ilia Denisov 42a6308261
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m11s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
feat(hint): idle-gated wallet-free vs_ai hint on a monotonic clock (B4)
A vs_ai hint is now unlimited and wallet-free, but idle-gated as an anti-frustration
aid: it unlocks only after the player has been stuck ~30 min on a turn (timed from the
robot's last move; the human's first move, before the robot has played, is exempt).
While gated the hint button carries a small lock badge and a tap shows the remaining
minutes ('Available in N min.'); the lock lifts live at the mark.

The gate is enforced CLIENT-SIDE against a MONOTONIC clock (performance.now()), never a
wall-clock timestamp: a device clock the player controls (or an auto-sync) must not be
able to open or freeze it. The wait is therefore session-scoped -- a reload restarts it
(there is no tamper-proof way to carry idle time across a relaunch without a wall clock).
An online vs_ai game will gate the same way but from the server's clock (a follow-up).

- lib/hints.ts: HINT_GATE_MS + pure hintGateRemainingMs (monotonic) + hintLockMinutes;
  removed the wall-clock hintRemainingMs. Unit-tested red->green.
- Game.svelte: the monotonic gate (hintGateStart/monoNow, armed on the turn change), a
  vs_ai hint button (plain: no confirm, no count; lock badge + gated tap -> toast), a
  live 10s tick.
- localgame: removed the wall-clock hint gate and the now-dead robotLastMoveAtUnix field
  from source.ts/serialize.ts (the client is authoritative); hint() just serves the top
  move.
- i18n game.hintLockedIn.
- offline.spec.ts: assert the first move is un-gated and the lock arms after the robot moves.
- Docs FUNCTIONAL(+_ru) + ARCHITECTURE; bundle budget 114->115 (game-screen feature).
2026-07-06 22:03:27 +02:00

393 lines
22 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.
// Russian message catalog. Typed as Record<MessageKey, string> so it must cover every
// key the English catalog defines (a Vitest test asserts parity too).
import type { MessageKey } from './en';
export const ru: Record<MessageKey, string> = {
'app.title': 'Scrabble',
'app.brand': 'Эрудит',
'dict.loading': 'Загрузка…',
'connection.connecting': 'Подключение…',
'maintenance.title': 'Технические работы',
'maintenance.body': 'Идёт короткое обновление игры. Мы скоро вернёмся — страницу перезагружать не нужно.',
'maintenance.retry': 'Повторить',
'blocked.title': 'Учётная запись заблокирована',
'blocked.permanent': 'Ваша учётная запись заблокирована.',
'blocked.temporary': 'Ваша учётная запись заблокирована до {until}.',
'blocked.reason': 'Причина:',
'boot.errorTitle': 'Не удалось загрузить игру',
'boot.errorBody': 'Попробуйте ещё раз или зайдите позже.',
'launch.errorTitle': 'Не открывается в Telegram',
'launch.errorBody': 'Сделайте скриншот или поделитесь с разработчиком.',
'launch.share': 'Поделиться',
'launch.copied': 'Скопировано',
'common.back': 'Назад',
'common.cancel': 'Отмена',
'common.ok': 'ОК',
'common.close': 'Закрыть',
'common.loading': 'Загрузка…',
'common.retry': 'Повторить',
'common.you': 'Вы',
'common.save': 'Сохранить',
'login.title': 'Вход',
'login.guest': 'Играть как гость',
'login.email': 'Эл. почта',
'login.emailPlaceholder': 'you@example.com',
'login.sendCode': 'Отправить код',
'login.codePlaceholder': 'Код из 6 цифр',
'login.signIn': 'Войти',
'confirm.busy': 'Подтверждаем почту…',
'confirm.linked': 'Почта подтверждена.',
'confirm.merge': 'Почти готово — завершите объединение в приложении.',
'confirm.close': 'Можно закрыть это окно и вернуться в игру.',
'confirm.expired': 'Ссылка недействительна или истекла. Запросите новый код.',
'login.codeSent': 'Мы отправили код на {email}.',
'lobby.activeGames': 'Активные игры',
'lobby.finishedGames': 'Завершённые игры',
'lobby.noActive': 'Пока нет активных игр.',
'lobby.noFinished': 'Пока нет завершённых игр.',
'lobby.limitReached': 'Вы достигли лимита одновременных партий',
'lobby.new': 'Играть',
'lobby.stats': 'Цифры',
'lobby.profile': 'Профиль',
'lobby.settings': 'Настройки',
'lobby.about': 'О программе',
'lobby.yourTurn': 'Ваш ход',
'lobby.theirTurn': 'Ход соперника',
'lobby.hideGame': 'Убрать из списка',
'lobby.vs': 'против {opponents}',
'new.title': 'Новая игра',
'new.english': 'Scrabble',
'new.russian': 'Скрэббл',
'new.erudit': 'Эрудит',
'new.find': 'Найти игру',
'new.searching': 'Ищем соперника…',
'new.rulesEnglish': '100 фишек · бинго +50',
'new.rulesRussian': '104 фишки · ё — отдельная буква · бинго +50',
'new.rulesErudit': '131 фишка · ё = е · центр не удваивает · бонус +15',
'new.moveLimit': 'Время на ход: {n} ч. 00 мин.',
'new.searchHint':
'Иногда поиск соперника может занять некоторое время. Если не захотите ждать после начала игры, можете вернуться в приложение через несколько минут.',
// Подсказки первого запуска (components/Coachmark.svelte).
'onboarding.lobbySettings': 'Друзья, настройки игры и профиля, обратная связь', // ⚙️
'onboarding.lobbyStats': 'Статистика обновляется после каждой игры с соперником', // ✏️
'onboarding.lobbyNew': 'Потренируйтесь с ИИ', // 🎲
'onboarding.gameHeader': 'История ходов, чат и проверка слова', // шапка над доской
'onboarding.gameTurn': 'Пропуск хода, обмен фишек', // 🔄
'onboarding.gameHints': 'Доступные подсказки', // 🛟
'onboarding.gameShuffle': 'Встряхните фишки', // 🔀
'onboarding.gameRack': 'Выбирайте фишку и ставьте на доску', // rack
'game.bag': '{n} в мешке',
'game.bagEmpty': 'Мешок пуст',
'game.hints': 'Подсказки {n}',
'game.yourTurn': 'Ваш ход',
'game.yourTurnBy': '{name}: Ваш ход!',
'game.opponentsTurn': 'Ход соперника',
'game.searchingForOpponent': 'Ждём соперника…',
'game.waiting': 'Ожидаем {name}',
'game.makeMove': 'Сделать ход',
'game.reset': 'Сброс',
'game.draw': 'Обмен/Пас',
'game.shuffle': 'Перемешать',
'game.hint': 'Подсказка',
'game.hintLockedIn': 'Будет доступно через {n} мин.',
'game.chat': 'Чат',
'game.checkWord': 'Проверить слово',
'game.dictionary': 'Словарь',
'game.dropGame': 'Покинуть игру',
'game.previewWords': '{words}: {n}',
'game.previewIllegal': 'Недопустимый ход',
'game.oneWordRule': 'Одно слово за ход',
'game.chooseBlank': 'Выберите букву для бланка',
'game.exchangeTitle': 'Обмен или пас',
'game.exchangeConfirm': 'Обменять {n}',
'game.passNoExchange': 'Пас без обмена',
'game.confirmResign': 'Сдаться в этой игре?',
'game.hintShown': 'Лучший ход: {word} на {n}',
'game.won': 'Вы выиграли',
'game.lost': 'Вы проиграли',
'game.tied': 'Ничья',
'game.abortedNote': 'Партия не может быть продолжена по вине организатора и завершена ничьёй.',
'game.checkWordPrompt': 'Введите слово',
'game.wordLegal': '«{word}» допустимо',
'game.wordIllegal': '«{word}» недопустимо',
'game.complain': 'Возражаю',
'game.lookup': 'Поискать в словаре',
'game.complaintSent': 'Спасибо, отправлено на проверку.',
'game.check': 'Проверить',
'game.checkWait': 'Секунду, пожалуйста.',
'game.noHintOptions': 'Нет вариантов с вашим набором.',
'game.thinking': 'думает…',
'move.pass': 'пас',
'move.exchange': 'обмен',
'move.resign': 'сдаюсь',
'move.timeout': 'тайм-аут',
'result.victory': 'Победа',
'result.defeat': 'Поражение',
'result.draw': 'Ничья',
'result.place2': 'II место',
'result.place3': 'III место',
'result.place4': 'IV место',
'result.yourMove': 'Ваш ход',
'result.oppMove': 'Ход соперника',
'chat.placeholder': 'Короткое сообщение…',
'chat.send': 'Отправить',
'chat.nudge': 'Жду Вашего хода 🤭',
'chat.nudgeBy': '{name}: Жду Вашего хода 🤭',
'chat.nudgeAction': 'Поторопить',
'chat.awaitingReply': 'Немного терпения',
'chat.empty': 'Сообщений пока нет.',
'chat.nudged': '{name} торопит вас',
'chat.sentThisTurn': 'Можно написать снова в следующем ходу.',
'profile.title': 'Профиль',
'profile.language': 'Язык',
'profile.timezone': 'Часовой пояс',
'profile.hintBalance': 'Баланс подсказок',
'profile.guest': 'Гостевой аккаунт',
'profile.edit': 'Редактировать профиль',
'profile.displayName': 'Отображаемое имя',
'profile.awayWindow': 'Окно отсутствия',
'profile.awayHint': 'В эти часы вам не засчитывают автопоражение.',
'profile.from': 'С',
'profile.to': 'До',
'profile.blockChat': 'Отключить чат',
'profile.blockFriendRequests': 'Отключить заявки в друзья',
'profile.notificationsInAppOnly': 'Уведомления только в приложении',
'profile.preferences': 'Предпочтения',
'profile.preferencesHint': 'Варианты игр, в которые вас могут подобрать. Выберите хотя бы один.',
'profile.email': 'Эл. почта',
'profile.bindEmail': 'Привязать почту',
'profile.emailCode': 'Код подтверждения',
'profile.emailSent': 'Мы отправили код на {email}.',
'profile.emailBound': 'Почта подтверждена.',
'profile.saved': 'Профиль сохранён.',
'profile.guestLocked': 'Войдите по почте, чтобы управлять профилем.',
'profile.linkAccount': 'Привязать аккаунт',
'profile.linkTelegram': 'Привязать Telegram',
'profile.linkVK': 'Привязать VK',
'profile.linked': 'Аккаунт привязан.',
'profile.merged': 'Аккаунты объединены.',
'profile.mergeTitle': 'Объединить аккаунты?',
'profile.mergeBody': 'Эта личность уже принадлежит «{name}» (игр: {games}, друзей: {friends}).',
'profile.mergeIrreversible': 'Объединение сольёт оба аккаунта в этот и необратимо.',
'profile.mergeConfirm': 'Объединить',
'profile.accountsTitle': 'Способы входа',
'profile.changeEmail': 'Изменить',
'profile.newEmailPlaceholder': 'Новый адрес e-mail',
'profile.emailChanged': 'E-mail изменён.',
'profile.emailChangeTaken': 'Проверьте правильность e-mail или обратитесь в поддержку.',
'profile.unlink': 'Отвязать',
'profile.unlinked': 'Аккаунт отвязан.',
'profile.unlinkTitle': 'Отвязать аккаунт?',
'profile.unlinkBody': 'Убрать {provider} как способ входа? Позже можно привязать снова.',
'profile.deleteAccount': 'Удалить аккаунт',
'profile.deleteTitle': 'Удалить аккаунт?',
'profile.deleteWarn': 'Аккаунт будет удалён, а способы входа освобождены. Отменить нельзя.',
'profile.deleteEmailPrompt': 'Мы отправили код на вашу почту. Введите его, чтобы удалить аккаунт.',
'profile.deletePhrasePrompt': 'Введите DELETE для подтверждения.',
'profile.deletePhrasePlaceholder': 'DELETE',
'profile.deleteConfirm': 'Удалить навсегда',
'deleted.title': 'Учётная запись удалена',
'deleted.body': 'Аккаунт удалён. Если снова откроете приложение — будет создан новый аккаунт.',
'settings.title': 'Настройки',
'settings.theme': 'Тема',
'settings.themeAuto': 'Авто',
'settings.themeLight': 'Светлая',
'settings.themeDark': 'Тёмная',
'settings.language': 'Язык интерфейса',
'settings.boardStyle': 'Стиль доски',
'settings.boardLabels': 'Подписи бонусов',
'settings.labelsBeginner': 'Новичок',
'settings.labelsClassic': 'Классика',
'settings.labelsNone': 'Без текста',
'settings.reduceMotion': 'Меньше анимаций',
'settings.offlineMode': 'Режим игры',
'settings.online': 'Онлайн',
'settings.offline': 'Оффлайн',
'settings.offlineChecking': 'Загрузка словарей…',
'settings.offlineNeedsData': 'Недостаточно данных. Необходим доступ в интернет для загрузки.',
'offline.preloadWarning': 'Плохое соединение с интернет. Некоторые функции могут быть недоступны.',
'offline.promptTitle': 'Нет связи. Включить офлайн-режим?',
'offline.promptYes': 'Включить',
'offline.promptNo': 'Ждать сеть',
'about.title': 'О программе',
'about.tab': 'Инфо',
'about.description': 'Мультиплатформенная игра в «Эрудит».',
'about.version': 'Версия {v}',
'feedback.title': 'Обратная связь',
'feedback.placeholder': 'Опишите проблему или поделитесь предложением…',
'feedback.attach': 'Прикрепить файл',
'feedback.removeFile': 'Удалить',
'feedback.send': 'Отправить',
'feedback.fileRejected': 'Этот файл нельзя прикрепить.',
'feedback.sent': 'Ваше сообщение отправлено',
'feedback.waiting': 'Ожидаем рассмотрения вашего последнего обращения',
'feedback.banned': 'Отправка обратной связи недоступна',
'feedback.replyTitle': 'Ответ на ваше последнее сообщение',
'landing.tagline': 'Играй в «Эрудита» с друзьями, случайным соперником или ИИ-роботом.',
'landing.playTelegram': 'Играть в Telegram',
'landing.playVK': 'Играть во ВКонтакте',
'landing.playWeb': 'Играть в браузере',
'landing.captionTelegram': 'Telegram',
'landing.captionVK': 'VK',
'landing.captionWeb': 'Веб-версия',
'install.title': 'Установить приложение',
'install.subtitle': 'Поместите иконку приложения на рабочий стол (домашний экран), чтобы открывать игру одним нажатием.',
'install.iosTitle': 'На экран «Домой»',
'install.iosStep1': 'Нажмите кнопку «Поделиться» на панели браузера.',
'install.iosStep2': 'Выберите «На экран „Домой“».',
'install.iosStep3': 'Нажмите «Добавить».',
'lang.en': 'English',
'lang.ru': 'Русский',
'error.not_your_turn': 'Сейчас не ваш ход.',
'error.nudge_own_turn': 'Сейчас ваш ход — некого торопить.',
'error.illegal_play': 'Это недопустимый ход.',
'error.hint_unavailable': 'Подсказки недоступны.',
'error.no_hint_available': 'Нет вариантов с вашим набором.',
'error.chat_rejected': 'Сообщение отклонено (слишком длинное или содержит контакты).',
'error.nudge_too_soon': 'Не стоит торопить соперника так часто.',
'error.chat_not_your_turn': 'Писать в чат можно только в свой ход.',
'error.chat_already_sent': 'За один ход можно отправить только одно сообщение.',
'error.game_finished': 'Эта игра уже завершена.',
'error.not_a_player': 'Вы не участник этой игры.',
'error.already_queued': 'Вы уже в очереди.',
'error.email_taken': 'Эта почта принадлежит другому аккаунту.',
'error.code_invalid': 'Неверный или истёкший код.',
'error.invalid_email': 'Введите корректный адрес почты.',
'error.invalid_config': 'Неверные настройки игры.',
'error.not_found': 'Не найдено.',
'error.session_invalid': 'Сессия истекла. Войдите снова.',
'error.unauthenticated': 'Пожалуйста, войдите.',
'error.rate_limited': 'Слишком много запросов, помедленнее.',
'error.unavailable': 'Проблема соединения. Повторяем…',
'error.internal': 'Что-то пошло не так.',
'error.generic': 'Что-то пошло не так.',
'lobby.invitations': 'Приглашения',
'lobby.friends': 'Друзья',
'friends.title': 'Друзья',
'friends.yours': 'Ваши друзья',
'friends.none': 'Друзей пока нет.',
'friends.incoming': 'Заявки в друзья',
'friends.accept': 'Принять',
'friends.decline': 'Отклонить',
'friends.unfriend': 'Удалить',
'friends.block': 'Заблокировать',
'friends.actions': 'Действия',
'friends.blockConfirm': 'Заблокировать?',
'friends.unfriendConfirm': 'Удалить из друзей?',
'friends.add': 'Добавить друга',
'friends.addFromGame': 'В друзья',
'friends.blockFromGame': 'Заблокировать',
'friends.requestSent': 'Заявка в друзья отправлена.',
'friends.getCode': 'Показать мой код',
'friends.codeHint': 'Передайте этот код другу в течение 12 часов.',
'friends.codeExpires': 'Истекает в {time}',
'friends.enterCode': 'Есть код? Добавить друга',
'friends.codePlaceholder': 'Код из 6 цифр',
'friends.redeem': 'Добавить',
'friends.copy': 'Копировать',
'friends.codeCopied': 'Код скопирован.',
'friends.shareTelegram': 'Поделиться',
'friends.inviteText': 'Давай играть в Эрудит!',
'friends.linkCopied': 'Ссылка скопирована.',
'friends.selfInvite': 'Надеюсь, что с собой Вы уже давно дружите ☺️',
'friends.staleInviteTitle': 'Ссылка устарела',
'friends.staleInvite': 'Вы открыли игру по устаревшей ссылке. Откройте бота {bot}, чтобы играть и получать уведомления.',
'friends.welcomeRedeemTitle': 'Добро пожаловать!',
'friends.welcomeRedeem': 'Добро пожаловать, {name}!\n\nВоспользуйтесь {bot}, чтобы заходить в игру наиболее удобным способом.',
'friends.added': 'Добавлен(а) {name}.',
'friends.blockedList': 'Заблокированные',
'friends.unblock': 'Разблокировать',
'friends.noneBlocked': 'Заблокированных нет.',
'invitations.none': 'Приглашений нет.',
'invitations.from': 'От {name}',
'invitations.with': 'С {names}',
'invitations.accept': 'Принять',
'invitations.decline': 'Отклонить',
'invitations.declineConfirm': 'Отклонить приглашение?',
'invitations.cancel': 'Отменить',
'invitations.waiting': 'Ожидаем ответы',
'new.auto': 'Быстрая игра',
'new.withFriends': 'Игра с друзьями',
'new.pickFriends': 'Кого пригласить',
'new.searchFriends': 'Поиск друзей',
'new.gameType': 'Вариант',
'new.invite': 'Отправить приглашение',
'new.moveTime': 'Время на ход',
'new.hintsPerPlayer': 'Подсказки',
'new.multipleWordsPerTurn': 'Несколько слов за ход',
'new.start': 'Начать игру',
'new.invited': 'Приглашение отправлено.',
'new.noFriends': 'Сначала добавьте друзей, чтобы пригласить их.',
'new.opponentAI': 'ИИ',
'new.opponentRandom': 'Случайный игрок',
'new.aiInactiveLimit': 'Поражение, если 7 дней нет активности',
'stats.title': 'Статистика',
'stats.wins': 'Победы',
'stats.losses': 'Поражения',
'stats.draws': 'Ничьи',
'stats.played': 'Игры',
'stats.moves': 'Ходы',
'stats.hintShare': 'Доля подсказок',
'stats.winRate': 'Доля побед',
'stats.maxGame': 'Лучшая игра',
'stats.maxWord': 'Лучший ход',
'stats.guestHint': 'Войдите, чтобы вести статистику.',
'game.exportGcg': 'Экспорт партии',
'game.exportChoice': 'Экспортировать завершённую партию как:',
'game.exportImageOpt': 'Картинка (PNG)',
'game.exportGcgOpt': 'Файл GCG',
'game.gcgActiveOnly': 'Доступно после завершения игры.',
'game.gcgCopied': 'GCG скопирован в буфер обмена.',
'game.addFriendShort': 'В друзья?',
'game.blockShort': 'В бан?',
'time.minutes': '{n} мин',
'time.hours': '{n} ч',
'error.self_relation': 'Нельзя сделать это с самим собой.',
'error.request_exists': 'Заявка или дружба уже существует.',
'error.request_blocked': 'Игрок не принимает заявки.',
'error.request_not_found': 'Подходящей заявки нет.',
'error.no_shared_game': 'Можно добавить только того, с кем вы играли.',
'error.request_declined': 'Игрок отклонил вашу заявку.',
'error.friend_code_invalid': 'Код недействителен или истёк.',
'error.invalid_invitation': 'Неверное приглашение.',
'error.invitation_blocked': 'Нельзя пригласить этого игрока.',
'error.invitation_not_found': 'Приглашение не найдено.',
'error.invitation_not_pending': 'Приглашение больше не открыто.',
'error.invitation_expired': 'Приглашение истекло.',
'error.not_invited': 'Вас не приглашали.',
'error.already_responded': 'Вы уже ответили.',
'error.not_inviter': 'Только пригласивший может это сделать.',
'error.game_active': 'Доступно только после завершения игры.',
'error.invalid_profile': 'Некоторые поля профиля некорректны.',
'error.already_confirmed': 'Эта почта уже подтверждена.',
};