Files
scrabble-game/ui/src/lib/i18n/en.ts
T
Ilia Denisov aa765a0c06
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s
feat: honest AI opponent in quick game
New Game's quick game gains an explicit opponent selector — 🤖 AI (default)
or 👤 Random player. AI starts a game seated with a pooled robot that joins
and moves at once: no per-move timeout (a 7-day inactivity loss reusing the
turn-timeout sweeper), chat/nudge disabled, no statistics, the opponent shown
as 🤖 everywhere. The random path (disguised robot) is unchanged.

Driven by one game flag (games.vs_ai), set only on AI-started games so the
disguised path is never revealed; Matchmaker.StartVsAI seats the robot
directly (no open pool); the robot replies event-driven via the game service's
after-commit/after-create hook. Wire: vs_ai on EnqueueRequest + GameView.
2026-06-15 20:14:24 +02:00

309 lines
12 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.
// English message catalog (authoritative). Keys are flat dotted strings; ru.ts must
// provide exactly the same keys (enforced by its type and a Vitest parity test).
// {name} placeholders are filled by t(key, params).
export const en = {
'app.title': 'Scrabble',
'connection.connecting': 'Connecting…',
'blocked.title': 'Account blocked',
'blocked.permanent': 'Your account is blocked.',
'blocked.temporary': 'Your account is blocked until {until}.',
'blocked.reason': 'Reason:',
'common.back': 'Back',
'common.cancel': 'Cancel',
'common.ok': 'OK',
'common.close': 'Close',
'common.loading': 'Loading…',
'common.retry': 'Retry',
'common.you': 'You',
'common.save': 'Save',
'login.title': 'Sign in',
'login.guest': 'Play as guest',
'login.email': 'Email',
'login.emailPlaceholder': 'you@example.com',
'login.sendCode': 'Send code',
'login.codePlaceholder': '6-digit code',
'login.signIn': 'Sign in',
'login.codeSent': 'We sent a code to {email}.',
'lobby.activeGames': 'Active games',
'lobby.finishedGames': 'Finished games',
'lobby.noActive': 'No active games yet.',
'lobby.noFinished': 'No finished games yet.',
'lobby.new': 'New',
'lobby.stats': 'Stats',
'lobby.profile': 'Profile',
'lobby.settings': 'Settings',
'lobby.about': 'About',
'lobby.yourTurn': 'Your turn',
'lobby.theirTurn': 'Their turn',
'lobby.hideGame': 'Remove from list',
'lobby.vs': 'vs {opponents}',
'new.title': 'New game',
'new.english': 'Scrabble',
'new.russian': 'Скрэббл',
'new.erudit': 'Erudite',
'new.find': 'Find a game',
'new.searching': 'Looking for an opponent…',
'new.rulesEnglish': '100 tiles · bingo +50',
'new.rulesRussian': '104 tiles · ё is a letter · bingo +50',
'new.rulesErudit': '131 tiles · ё = е · no centre ×2 · bonus +15',
'new.moveLimit': 'Move time: {n} h 00 min',
'new.searchHint':
'Finding an opponent can sometimes take a while. If you do not want to wait, close the app after starting the game and come back in a couple of minutes.',
'game.bag': '{n} in the bag',
'game.bagEmpty': 'Bag is empty',
'game.hints': 'Hints {n}',
'game.yourTurn': 'Your turn',
'game.yourTurnBy': '{name}: Your turn!',
'game.opponentsTurn': "Opponent's turn",
'game.searchingForOpponent': 'Searching for opponent…',
'game.waiting': "Waiting for {name}",
'game.makeMove': 'Make move',
'game.reset': 'Reset',
'game.draw': 'Exchange/Pass',
'game.shuffle': 'Shuffle',
'game.hint': 'Hint',
'game.chat': 'Chat',
'game.checkWord': 'Check word',
'game.dictionary': 'Dictionary',
'game.dropGame': 'Drop game',
'game.previewWords': '{words}: {n}',
'game.previewIllegal': 'Not a legal move',
'game.oneWordRule': 'One word per turn',
'game.chooseBlank': 'Choose a letter for the blank',
'game.exchangeTitle': 'Exchange or pass',
'game.exchangeConfirm': 'Exchange {n}',
'game.passNoExchange': 'Pass without exchanging',
'game.confirmResign': 'Resign this game?',
'game.hintShown': 'Best move: {word} for {n}',
'game.over': 'Game over',
'game.won': 'You won',
'game.lost': 'You lost',
'game.tied': 'Draw',
'game.abortedNote': 'This game could not be continued by the organizer and was ended in a draw.',
'game.checkWordPrompt': 'Enter a word',
'game.wordLegal': '“{word}” is valid',
'game.wordIllegal': '“{word}” is not valid',
'game.complain': 'Disagree',
'game.lookup': 'Look it up',
'game.complaintSent': 'Thanks, sent for review.',
'game.check': 'Check',
'game.checkWait': 'Please wait a moment.',
'game.noHintOptions': 'No options with your letters.',
'game.thinking': 'thinking…',
'move.pass': 'pass',
'move.exchange': 'exchange',
'move.resign': 'resign',
'move.timeout': 'timeout',
'result.victory': 'Victory',
'result.defeat': 'Defeat',
'result.draw': 'Draw',
'result.place2': 'II place',
'result.place3': 'III place',
'result.place4': 'IV place',
'result.yourMove': 'Your move',
'result.oppMove': "Opponent's move",
'chat.placeholder': 'Quick message…',
'chat.send': 'Send',
'chat.nudge': 'Waiting for your move!',
'chat.nudgeAction': 'Nudge',
'chat.awaitingReply': "Waiting for the opponent's reply",
'chat.empty': 'No messages yet.',
'chat.nudged': '{name} nudged you',
'chat.sentThisTurn': 'You can write again next turn.',
'profile.title': 'Profile',
'profile.language': 'Language',
'profile.timezone': 'Time zone',
'profile.hintBalance': 'Hint balance',
'profile.guest': 'Guest account',
'profile.edit': 'Edit profile',
'profile.displayName': 'Display name',
'profile.awayWindow': 'Away window',
'profile.awayHint': 'You are not auto-resigned during these hours.',
'profile.from': 'From',
'profile.to': 'To',
'profile.blockChat': 'Disable chat',
'profile.blockFriendRequests': 'Disable friend requests',
'profile.notificationsInAppOnly': 'Notifications in the app only',
'profile.email': 'Email',
'profile.bindEmail': 'Bind email',
'profile.emailCode': 'Confirmation code',
'profile.emailSent': 'We sent a code to {email}.',
'profile.emailBound': 'Email confirmed.',
'profile.saved': 'Profile saved.',
'profile.guestLocked': 'Sign in with email to manage your profile.',
'profile.linkAccount': 'Link an account',
'profile.linkTelegram': 'Link Telegram',
'profile.linked': 'Account linked.',
'profile.merged': 'Accounts merged.',
'profile.mergeTitle': 'Merge accounts?',
'profile.mergeBody': 'This identity already belongs to “{name}” ({games} games, {friends} friends).',
'profile.mergeIrreversible': 'Merging combines both accounts into this one and cannot be undone.',
'profile.mergeConfirm': 'Merge',
'settings.title': 'Settings',
'settings.theme': 'Theme',
'settings.themeAuto': 'Auto',
'settings.themeLight': 'Light',
'settings.themeDark': 'Dark',
'settings.language': 'Interface language',
'settings.boardStyle': 'Board style',
'settings.boardLabels': 'Bonus labels',
'settings.labelsBeginner': 'Beginner',
'settings.labelsClassic': 'Classic',
'settings.labelsNone': 'None',
'settings.boardLines': 'Grid lines',
'settings.reduceMotion': 'Reduce motion',
'about.title': 'About',
'about.tab': 'Info',
'about.description': 'A multiplatform Scrabble game.',
'about.version': 'Version {v}',
'feedback.title': 'Feedback',
'feedback.placeholder': 'Describe the problem or share your suggestion…',
'feedback.attach': 'Attach file',
'feedback.removeFile': 'Remove',
'feedback.send': 'Send',
'feedback.fileRejected': 'This file cant be attached.',
'feedback.sent': 'Your message has been sent.',
'feedback.waiting': 'We are reviewing your last message.',
'feedback.banned': 'Feedback submission is unavailable.',
'feedback.replyTitle': 'Reply to your last message',
'landing.tagline': 'Play Scrabble with friends or a smart robot — in your browser or on Telegram.',
'landing.playTelegram': 'Play in Telegram',
'lang.en': 'English',
'lang.ru': 'Русский',
'error.not_your_turn': "It is not your turn.",
'error.nudge_own_turn': 'It is your turn — there is no one to nudge.',
'error.illegal_play': 'That is not a legal play.',
'error.hint_unavailable': 'No hints available.',
'error.no_hint_available': 'No options with your letters.',
'error.chat_rejected': 'Message rejected (too long or contains contact info).',
'error.nudge_too_soon': "Please don't rush your opponent so often.",
'error.chat_not_your_turn': 'You can chat only on your turn.',
'error.chat_already_sent': 'You can send only one message per turn.',
'error.game_finished': 'This game is finished.',
'error.not_a_player': 'You are not a player in this game.',
'error.already_queued': 'You are already in the queue.',
'error.email_taken': 'That email belongs to another account.',
'error.code_invalid': 'Invalid or expired code.',
'error.invalid_email': 'Enter a valid email address.',
'error.invalid_config': 'Invalid game settings.',
'error.not_found': 'Not found.',
'error.session_invalid': 'Your session expired. Please sign in again.',
'error.unauthenticated': 'Please sign in.',
'error.rate_limited': 'Too many requests, slow down.',
'error.unavailable': 'Connection problem. Retrying…',
'error.internal': 'Something went wrong.',
'error.generic': 'Something went wrong.',
'lobby.invitations': 'Invitations',
'lobby.friends': 'Friends',
'friends.title': 'Friends',
'friends.yours': 'Your friends',
'friends.none': 'No friends yet.',
'friends.incoming': 'Friend requests',
'friends.accept': 'Accept',
'friends.decline': 'Decline',
'friends.unfriend': 'Remove',
'friends.block': 'Block',
'friends.add': 'Add a friend',
'friends.addFromGame': 'Add to friends',
'friends.requestSent': 'Friend request sent.',
'friends.getCode': 'Show my code',
'friends.codeHint': 'Give this code to a friend within 12 hours.',
'friends.codeExpires': 'Expires at {time}',
'friends.enterCode': 'Have a code? Add a friend',
'friends.codePlaceholder': '6-digit code',
'friends.redeem': 'Add',
'friends.copy': 'Copy',
'friends.codeCopied': 'Code copied.',
'friends.shareTelegram': 'Share via Telegram',
'friends.inviteText': "Let's play Scrabble!",
'friends.linkCopied': 'Link copied.',
'friends.selfInvite': "Hopefully you've been friends with yourself for a while ☺️",
'friends.staleInviteTitle': 'Link expired',
'friends.staleInvite': 'You opened the game from an outdated link. Open the bot {bot} to play and get notifications.',
'friends.added': 'Added {name}.',
'friends.blockedList': 'Blocked players',
'friends.unblock': 'Unblock',
'friends.noneBlocked': 'No blocked players.',
'invitations.none': 'No invitations.',
'invitations.from': 'From {name}',
'invitations.with': 'With {names}',
'invitations.accept': 'Accept',
'invitations.decline': 'Decline',
'invitations.cancel': 'Cancel',
'invitations.waiting': 'Waiting for replies',
'new.auto': 'Quick match',
'new.withFriends': 'Play with friends',
'new.pickFriends': 'Choose who to invite',
'new.searchFriends': 'Search friends',
'new.gameType': 'Game type',
'new.invite': 'Send invitation',
'new.moveTime': 'Move time',
'new.hintsPerPlayer': 'Hints per player',
'new.multipleWordsPerTurn': 'Multiple words per turn',
'new.start': 'Start game',
'new.invited': 'Invitation sent.',
'new.noFriends': 'Add friends first to invite them.',
'new.opponentAI': 'AI',
'new.opponentRandom': 'Random player',
'new.aiInactiveLimit': 'Loss after 7 days of inactivity',
'stats.title': 'Statistics',
'stats.wins': 'Wins',
'stats.losses': 'Losses',
'stats.draws': 'Draws',
'stats.played': 'Games',
'stats.winRate': 'Win rate',
'stats.maxGame': 'Best game',
'stats.maxWord': 'Best move',
'stats.guestHint': 'Sign in to track your statistics.',
'game.exportGcg': 'Export GCG',
'game.gcgActiveOnly': 'Available once the game is finished.',
'game.addFriendShort': 'Add friend?',
'time.minutes': '{n} min',
'time.hours': '{n} h',
'error.self_relation': 'You cannot do that to yourself.',
'error.request_exists': 'A request or friendship already exists.',
'error.request_blocked': 'This player is not accepting requests.',
'error.request_not_found': 'No matching friend request.',
'error.no_shared_game': 'You can only add someone you have played with.',
'error.request_declined': 'This player declined your request.',
'error.friend_code_invalid': 'That friend code is invalid or expired.',
'error.invalid_invitation': 'Invalid invitation.',
'error.invitation_blocked': 'You cannot invite this player.',
'error.invitation_not_found': 'Invitation not found.',
'error.invitation_not_pending': 'This invitation is no longer open.',
'error.invitation_expired': 'This invitation has expired.',
'error.not_invited': 'You were not invited.',
'error.already_responded': 'You already responded.',
'error.not_inviter': 'Only the inviter can do that.',
'error.game_active': 'Available only after the game is finished.',
'error.invalid_profile': 'Some profile fields are invalid.',
'error.already_confirmed': 'This email is already confirmed.',
} as const;
export type MessageKey = keyof typeof en;