diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts new file mode 100644 index 0000000..887dbe6 --- /dev/null +++ b/frontend/src/i18n.ts @@ -0,0 +1,15 @@ +import i18n from 'i18next' +import { initReactI18next } from 'react-i18next' +import LanguageDetector from 'i18next-browser-languagedetector' +import en from './locales/en.json' + +i18n + .use(LanguageDetector) + .use(initReactI18next) + .init({ + resources: { en: { translation: en } }, + fallbackLng: 'en', + interpolation: { escapeValue: false }, + }) + +export default i18n diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json new file mode 100644 index 0000000..48dab23 --- /dev/null +++ b/frontend/src/locales/en.json @@ -0,0 +1,1067 @@ +{ + "app": { + "name": "cruciverb", + "tagline": "a crossword a day", + "generating": "Generating puzzle...", + "offline": "Offline - playing from cache", + "streakKeepGoing": "{{count}} day streak - keep it going", + "skipToContent": "Skip to content", + "crosswordGrid": "Crossword grid", + "clues": "Clues", + "gameControls": "Game controls" + }, + "common": { + "loading": "Loading...", + "close": "Close", + "cancel": "Cancel", + "confirm": "Confirm", + "submit": "Submit", + "delete": "Delete", + "anonymous": "Anonymous", + "on": "on", + "off": "off", + "notSet": "not set", + "signIn": "Sign in", + "signOut": "Sign out", + "report": "Report", + "reported": "Reported", + "dismiss": "Dismiss", + "copied": "Copied!", + "copyLink": "Copy link", + "export": "Export", + "import": "Import", + "add": "Add", + "connect": "Connect", + "block": "Block", + "unblock": "Unblock", + "approve": "Approve", + "reject": "Reject", + "test": "Test", + "publish": "Publish", + "create": "Create", + "save": "Save", + "undo": "Undo", + "more": "More", + "tryAgain": "try again", + "back": "Back" + }, + "game": { + "across": "Across", + "down": "Down", + "solved": "Puzzle complete", + "timeUp": "Time's up", + "fasterThan": "Faster than {{percent}}% of solvers - #{{rank}} of {{total}}", + "solvedHint": "Know a better clue? Click the speech bubble next to any clue to suggest one.", + "marathonComplete": "Puzzle #{{count}} complete - next one loading...", + "marathonOver": "Marathon over - {{count}} puzzles solved", + "check": "Check", + "reveal": "Reveal", + "word": "Word", + "validate": "Validate", + "checkTooltip": "Check cell - counts as a hint", + "revealTooltip": "Reveal letter - counts as a hint", + "wordTooltip": "Reveal word - counts as a hint", + "validateTooltip": "Validate solution", + "hintsUsed": "{{count}} hint used", + "hintsUsed_plural": "{{count}} hints used", + "suggestClue": "Suggest alternative clue", + "clueSubmitPlaceholder": "Your alternative clue...", + "submit": "Submit", + "selectCellToBegin": "Select a cell to begin", + "clickToSwitchDirection": "Click to switch direction", + "undo": "Undo", + "redo": "Redo", + "undoTooltip": "Undo (Ctrl+Z)", + "redoTooltip": "Redo (Ctrl+Y)", + "pencilToggle": "Toggle pencil mode", + "pencilTooltip": "Pencil mode - tentative letters", + "autoCheckToggle": "Toggle auto-check", + "autoCheckTooltip": "Auto-check - marks wrong letters as you type", + "shareResult": "Share result" + }, + "grid": { + "ariaLabel": "Crossword puzzle, {{width}} by {{height}}", + "blackCell": "Black cell", + "cellLabel": "Row {{row}}, Column {{col}}", + "cellNumber": ", number {{num}}", + "cellLetter": ", letter {{letter}}", + "cellEmpty": ", empty", + "handwritingCanvas": "Handwriting input area" + }, + "welcome": { + "todaysPuzzles": "today's puzzles", + "newPuzzlesIn": "new puzzles in {{countdown}}", + "customPuzzle": "custom puzzle", + "clueStyle": "clue style", + "standard": "standard", + "cryptic": "cryptic", + "difficulty": "difficulty", + "auto": "auto", + "veryEasy": "very easy", + "easy": "easy", + "medium": "medium", + "hard": "hard", + "gameMode": "game mode", + "speedRun": "speed run", + "blind": "blind", + "marathon": "marathon", + "theme": "theme", + "none": "none", + "wordPack": "word pack", + "startMarathon": "Start marathon", + "marathonDesc": "endless 5x5 - 2 min each", + "importPuz": "Import .puz / .ipuz", + "communityPuzzles": "community puzzles", + "federatedPuzzles": "federated puzzles", + "wordOfTheDay": "word of the day", + "crypticExplainer": "British-style wordplay clues where each clue has a definition and a wordplay element (anagram, hidden word, reversal, etc.) that both lead to the answer. Much harder than standard clues.", + "blindExplainer": "Clues are hidden until you select a word. You can only see one clue at a time. Completed words reveal their clues. Forces you to work from crossings.", + "marathonExplainer": "Endless stream of Quick (5x5) puzzles. Each solved puzzle immediately loads the next. You have 2 minutes per puzzle - if time runs out, the marathon ends." + }, + "header": { + "share": "Share", + "playTogether": "Play together", + "communityClues": "Community clues", + "leaderboard": "Leaderboard", + "more": "More", + "puzzleEditor": "Puzzle editor", + "achievements": "Achievements", + "myStats": "My stats", + "settings": "Settings", + "aboutInstance": "About this instance", + "signInPasskey": "Sign in with passkey", + "keyboardShortcuts": "Keyboard shortcuts", + "narrator": "Narrator", + "narratorActive": "Narrator active", + "newPuzzle": "New puzzle", + "startNewPuzzle": "Start a new puzzle", + "custom": "Custom", + "size": "Size", + "fill": "Fill", + "generate": "Generate", + "presetGlyph": "Glyph", + "presetRune": "Rune", + "presetScroll": "Scroll", + "presetCodex": "Codex", + "presetGrimoire": "Grimoire", + "presetGlyphDesc": "5x5 very easy", + "presetRuneDesc": "5x5 easy", + "presetScrollDesc": "9x9 medium", + "presetCodexDesc": "15x15 hard", + "presetGrimoireDesc": "21x21 expert", + "history": "History", + "archive": "Archive", + "clueTrainer": "Clue trainer", + "freezeTooltip": "{{count}} streak freeze available - protects your streak if you miss a day" + }, + "shortcuts": { + "title": "Keyboard shortcuts", + "subtitle": "Available while the grid is focused", + "navigation": "Navigation", + "editing": "Editing", + "narrator": "Narrator mode", + "arrows": "Move between cells", + "tabNext": "Jump to next clue", + "tabPrev": "Jump to previous clue", + "space": "Toggle across / down", + "escape": "Deselect cell", + "letters": "Enter a letter", + "backspace": "Clear current cell", + "delete": "Clear and move back", + "ctrlZ": "Undo last entry", + "ctrlY": "Redo last entry", + "question": "Re-read current clue", + "ctrlH": "Puzzle overview", + "f1": "Puzzle overview" + }, + "difficulty": { + "veryEasy": "Very easy", + "easy": "Easy", + "medium": "Medium", + "hard": "Hard" + }, + "achievements": { + "title": "Achievements", + "count": "{{unlocked}}/{{total}}" + }, + "community": { + "title": "Community clues", + "browseSubmit": "Browse & submit", + "mySubmissions": "My submissions", + "enterWord": "Enter a word...", + "writeClueFor": "Write a clue for {{word}}...", + "noCluesYet": "No community clues for {{word}} yet. Be the first!", + "noSubmissions": "You haven't submitted any clues yet.", + "reportClue": "Report this clue", + "reasonPlaceholder": "Reason (optional)", + "submitReport": "Submit report", + "deleteSubmission": "Delete submission" + }, + "communityToday": { + "title": "Today's community puzzles", + "subtitle": "Puzzles published by the community in the last 24 hours.", + "none": "No community puzzles published today yet." + }, + "leaderboard": { + "title": "Leaderboard", + "tabs": { + "daily": "Daily", + "alltime": "All-time", + "weekly": "Weekly", + "streaks": "Streaks", + "total": "Total", + "global": "Global" + }, + "columns": { + "rank": "#", + "player": "Player", + "time": "Time", + "hints": "Hints", + "date": "Date", + "current": "Current", + "best": "Best", + "solves": "Solves", + "instance": "Instance" + }, + "cleanOnly": "clean only (0 hints)", + "yourTime": "Your time: {{time}} ({{detail}})", + "hintCount": "{{count}} hint", + "hintCount_plural": "{{count}} hints" + }, + "rating": { + "label": "Rate this puzzle", + "avg": "{{avg}} avg ({{count}} {{unit}})", + "rating": "rating", + "ratings": "ratings", + "star": "Rate {{n}} out of 5" + }, + "receipt": { + "claimTitle": "Claim this solve?", + "solvedOn": "You solved a puzzle on {{instance}}", + "time": "Time: {{time}}s", + "hints": "Hints: {{hints}}", + "date": "Date: {{date}}", + "room": "Room: {{code}} ({{mode}})", + "with": "With: {{players}}", + "claiming": "Claiming...", + "claim": "Claim", + "qrLabel": "QR code for receipt link" + }, + "share": { + "title": "Share puzzle", + "copyEmbed": "Copy embed code", + "embedOnWebsite": "Embed on your website", + "timer": "Timer", + "hintsLabel": "Hints", + "puzzle": "Puzzle", + "solution": "Solution", + "qrLabel": "QR code for puzzle link" + }, + "embed": { + "solved": "Puzzle complete", + "brand": "cruciverb", + "noPuzzleId": "No puzzle ID", + "notFound": "Puzzle not found" + }, + "onboarding": { + "welcomeTo": "Welcome to cruciverb", + "chooseName": "Choose a name", + "chooseNameDesc": "This is how you'll appear on leaderboards. Leave it blank to stay anonymous. You can change it anytime in settings.", + "progressSaved": "Your progress is saved in this browser", + "progressSavedDesc": "Your solves, streaks, and achievements are tied to a cookie in this browser on this device. If you switch browsers or clear cookies, you'll start fresh.", + "rememberFor": "Remember me for", + "playAcrossDevices": "Play across devices with a passkey", + "playAcrossDevicesDesc": "A passkey is a secure, passwordless credential stored on your device or password manager. Set one up to access your progress from any browser or device - no account or password needed.", + "settingUp": "Setting up...", + "setupPasskey": "Set up passkey", + "passkeySaved": "Passkey saved", + "passkeyFailed": "Passkey registration failed", + "comingFrom": "Coming from", + "anotherInstance": "another instance?", + "done": "Done", + "startPlaying": "Start playing", + "imported": "Imported {{count}} items.", + "skipped": "Skipped: {{items}}", + "importFailed": "Import failed" + }, + "analytics": { + "title": "My Stats", + "couldNotLoad": "Could not load analytics.", + "noPuzzlesSolved": "No puzzles solved yet. Complete a puzzle to see your stats here.", + "solved": "Solved", + "cleanRate": "Clean rate", + "totalTime": "Total time", + "streak": "Streak", + "bestStreak": "Best streak", + "avgTime": "Avg time", + "activity": "Activity", + "improvement": "Improvement", + "fasterThanLastMonth": "{{pct}}% faster than last month", + "slowerThanLastMonth": "{{pct}}% slower than last month", + "needMoreSolves": "need more solves to compare", + "completionRate": "Completion rate", + "completion": "completion", + "total": "total", + "solveTimeTrend": "Solve time trend", + "timeOfDay": "Time of day", + "consistency": "Consistency", + "veryConsistent": "very consistent", + "consistent": "consistent", + "variable": "variable", + "unpredictable": "unpredictable", + "variance": "variance", + "notEnoughData": "not enough data", + "notEnoughDataYet": "not enough data yet", + "dayOfWeek": "Day of week", + "byPreset": "By preset", + "presetMix": "Preset mix", + "hintsTrend": "Hints trend", + "cleanSolveTrend": "Clean solve trend", + "monthlyVolume": "Monthly volume", + "solveTimeDistribution": "Solve time distribution", + "difficultyProgression": "Difficulty progression", + "peakPerformance": "Peak performance", + "fastestAt": "Fastest at {{hour}}:00 - avg {{time}}", + "streakProgress": "Streak progress", + "daysCurrent": "{{count}} day current", + "daysCurrent_plural": "{{count}} days current", + "daysBest": "{{count}} day best", + "daysBest_plural": "{{count}} days best", + "bestStreakNow": "You are on your best streak right now", + "personalRecords": "Personal records", + "recentSolves": "Recent solves", + "solvesInYear": "{{count}} solve in the last year", + "solvesInYear_plural": "{{count}} solves in the last year", + "dayNames": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] + }, + "instance": { + "aboutTitle": "About this instance", + "tagline": "self-hosted crossword puzzles", + "license": "open source, CC0 public domain", + "federationActive": "Federation active", + "connectedInstances": "{{count}} connected instance", + "connectedInstances_plural": "{{count}} connected instances", + "contact": "Contact", + "rules": "Rules", + "features": "Features", + "featureList": { + "dailyPuzzles": "Daily puzzles in 4 difficulty levels", + "crypticMode": "Cryptic crossword mode with 660K clues", + "puzzleEditor": "Puzzle editor with auto-fill", + "communityClues": "Community clue contributions and voting", + "themedCategories": "11 themed word categories", + "gameModes": "Speed run, blind, and marathon game modes", + "achievements": "Achievements and streaks", + "pwa": "PWA offline support", + "federation": "ActivityPub federation" + }, + "federationHealth": "Federation health", + "deliveryRate": "delivery rate", + "peers": "peers", + "queueDepth": "queue depth", + "status": "status", + "frozen": "Frozen", + "active": "Active", + "rssFeed": "RSS feed", + "atomFeed": "Atom feed", + "jsonFeed": "JSON feed", + "healthTitle": "Instance health", + "healthy": "Healthy", + "server": "server" + }, + "report": { + "selectReason": "Select reason", + "offensive": "Offensive content", + "spam": "Spam", + "broken": "Broken / incorrect", + "other": "Other" + }, + "datepicker": { + "months": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + "weekdays": ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + "calendarLabel": "Calendar - {{month}} {{year}}", + "prevMonth": "Previous month", + "nextMonth": "Next month" + }, + "zoom": { + "tryAgain": "try again" + }, + "party": { + "title": "Party mode", + "roomCode": "Room code", + "players": "Players ({{count}})", + "waitingForPlayers": "Waiting for more players to join...", + "needMorePlayers": "Need 2+ players", + "startGame": "Start game", + "waitingForHost": "Waiting for the host to start...", + "leaveRoom": "Leave room", + "leave": "Leave", + "copied": "Copied", + "leaveEndGame": "Leaving will end the game for everyone.", + "leaveConfirm": "Leave this game?", + "cancel": "Cancel", + "endAndLeave": "End game and leave", + "round": "Round {{current}} / {{total}}", + "scores": "Scores", + "results": "Results", + "vote": "vote", + "votes": "votes", + "voteLocked": "Vote locked in", + "lockedIn": "Locked in", + "correct": "Correct!", + "submittedWaiting": "Submitted - waiting for others...", + "typeAnswer": "Type the answer - be fast!", + "go": "Go", + "yourAnswer": "Your answer...", + "pickDefinition": "Pick the real definition", + "submit": "Submit", + "subModes": { + "wrong": "Wrong answers", + "bluff": "Bluff", + "reverse": "Reverse clue", + "speed": "Speed", + "roulette": "Roulette" + }, + "prompts": { + "wrongAnswer": "Write a wrong answer", + "fakeDefinition": "Write a fake definition", + "writeClue": "Write a clue for this word", + "voteFunniest": "Vote for the funniest", + "realDefinition": "Which is the real definition?", + "voteBestClue": "Vote for the best clue" + }, + "placeholders": { + "funniestWrong": "Your funniest wrong answer...", + "soundReal": "Make it sound real...", + "bestClue": "Your best clue..." + }, + "resultLabels": { + "realAnswer": "Real answer", + "realDefinition": "Real definition", + "originalClue": "Original clue" + }, + "firstPlace": "1st place", + "phaseAnnounce": "{{phase}} phase - round {{current}} of {{total}}", + "resultsAnnounce": "Results - the answer was {{answer}}", + "gameOver": "Game over" + }, + "multiplayer": { + "playTogether": "Play together", + "createRoom": "Create a room", + "coop": "Co-op", + "race": "Race", + "partyLabel": "Party", + "createRoomBtn": "Create room", + "startParty": "Start party", + "loadPuzzleFirst": "Load a puzzle first", + "creating": "Creating...", + "joinRoom": "Join a room", + "roomCode": "Room code", + "recentRooms": "Recent rooms", + "copied": "Copied", + "turnBased": "turn-based", + "wrongAnswersOnly": "wrong answers only", + "bluff": "bluff", + "reverseClue": "reverse clue", + "speedRound": "speed round", + "roulette": "roulette", + "yourTurn": "Your turn", + "playerTurn": "{{name}}'s turn", + "waitingToStart": "Waiting to start", + "playing": "playing", + "you": "you", + "waitingForPlayersEllipsis": "Waiting for players...", + "passTurn": "Pass turn", + "needMorePlayers": "Need 2+ players", + "startGame": "Start game", + "leaveRoom": "Leave room", + "disconnected": "Disconnected", + "joinFailed": "Could not find that room. Check the code and try again.", + "createFailed": "Could not create the room. Try again.", + "partyModes": { + "wrongDesc": "Write funny wrong answers to real crossword clues and vote on the funniest one. The real answer is hidden among the fakes as a trap.", + "bluffDesc": "An obscure word is shown - everyone writes a fake definition. The real definition is mixed in. Fool others with yours, or spot the real one.", + "reverseDesc": "You see the answer word - now write the cleverest clue for it. Everyone votes on their favorite.", + "speedDesc": "A clue appears and everyone races to type the correct answer. First three to get it right score points.", + "rouletteDesc": "A word is shown with four possible definitions - only one is real. The other three are definitions for completely different words. Pick the right one." + } + }, + "settings": { + "title": "Settings", + "tabs": { + "profile": "Profile", + "display": "Display", + "session": "Session", + "visibility": "Visibility", + "data": "Data", + "federation": "Federation", + "accessibility": "Accessibility", + "account": "Account" + }, + "accessibility": { + "colorblindHint": "Adjust the color palette for different types of color vision", + "textSizeHint": "Scale all text up or down to match your preference" + }, + "profile": { + "displayName": "Display name", + "placeholder": "Anonymous", + "hint": "This is how you appear on leaderboards and in community features", + "sessionLabel": "Session", + "hasPasskey": "You have a passkey set up - your progress is portable across browsers.", + "noPasskey": "No passkey yet. Add one in the Session tab to access your progress from other devices." + }, + "display": { + "theme": "Theme", + "system": "System", + "light": "Light", + "dark": "Dark", + "inputMode": "Input mode", + "inputAuto": "Auto (keyboard + pen)", + "inputKeyboard": "Keyboard", + "inputStylus": "Stylus", + "colorblind": "Colorblind mode", + "colorblindNone": "None", + "protanopia": "Protanopia (red-blind)", + "deuteranopia": "Deuteranopia (green-blind)", + "tritanopia": "Tritanopia (blue-blind)", + "textSize": "Text size: {{scale}}%", + "torFriendly": "Tor-friendly mode", + "torHint": "Uses system fonts - no external requests to Google", + "language": "Language", + "pushNotifications": "Push notifications", + "pushHint": "Get notified when the daily puzzle is ready", + "pushComingSoon": "Coming soon" + }, + "narrator": { + "title": "Narrator", + "narratorMode": "Narrator mode", + "hint": "Speaks cell positions, clues, and game events aloud", + "speechRate": "Speech rate: {{rate}}x", + "pitch": "Pitch: {{pitch}}x", + "voice": "Voice", + "defaultVoice": "Default", + "verbosity": "Verbosity", + "brief": "Brief", + "normal": "Normal", + "verbose": "Verbose", + "soundEffects": "Sound effects", + "notSupported": "Your browser does not support speech synthesis" + }, + "session": { + "duration": "Session duration", + "durationHint": "How long this browser remembers your progress", + "passkeys": "Passkeys", + "noPasskeys": "No passkeys yet. Add one to access your progress from other browsers or devices.", + "addPasskey": "Add passkey", + "registering": "Registering...", + "noSupport": "Your browser does not support passkeys.", + "sessionBinding": "Session binding", + "sessionBindingHint": "Lock this session to your current browser - invalidates if user-agent changes", + "requiresPasskey": "Requires a passkey to be set up", + "thisSession": "This session", + "oneDay": "1 day", + "oneWeek": "1 week", + "thirtyDays": "30 days", + "ninetyDays": "90 days", + "oneYear": "1 year" + }, + "visibility": { + "quickSetup": "Quick setup", + "open": "Open", + "openDesc": "Everything visible, all features on", + "private": "Private", + "privateDesc": "Hidden from leaderboards, anonymous, local only", + "fortress": "Fortress", + "fortressDesc": "Maximum privacy, encrypted, auto-purge", + "visibilityLabel": "Visibility", + "hideLeaderboards": "Hide from leaderboards", + "hideLeaderboardsHint": "Your solve times won't appear on any leaderboard", + "privateProfile": "Private profile", + "privateProfileHint": "Your display name is hidden - you appear as Anonymous", + "anonSolve": "Anonymous solve mode", + "anonSolveHint": "No achievements triggered, no stats recorded for your profile" + }, + "data": { + "title": "Data protection", + "noTrack": "No-track mode", + "noTrackHint": "Solves are not recorded on the server at all", + "anonTimes": "Anonymize solve times", + "anonTimesHint": "Rounded to nearest minute to prevent fingerprinting", + "encrypt": "Encrypt solve data", + "encryptHint": "Progress stored as AES-256-GCM ciphertext at rest - the server needs DATA_ENC_KEY set for this to take effect", + "ephemeral": "Ephemeral session", + "ephemeralHint": "All server-side data deleted when your cookie expires", + "autoPurge": "Auto-purge old data", + "purgeAfter": "Purge after", + "purgeDays": { + "7": "7 days", + "30": "30 days", + "60": "60 days", + "90": "90 days" + } + }, + "federation": { + "fedContent": "Federated content", + "showAll": "Show all content", + "approvedOnly": "Approved content only", + "localOnly": "Local content only", + "blockedInstances": "Blocked instances", + "block": "Block", + "exportBlocklist": "Export blocklist", + "importBlocklist": "Import blocklist" + }, + "account": { + "yourData": "Your data", + "dataHint": "Download a copy of everything cruciverb knows about you, or restore from a previous export. Achievements, streaks, settings, and clue submissions carry over. Solve records and puzzle ratings are instance-specific and can't be transferred.", + "export": "Export", + "import": "Import", + "claimSolve": "Claim a solve", + "claimHint": "Paste a receipt URL or receipt JSON from another instance to claim a solve on this one.", + "claim": "Claim", + "claiming": "Claiming...", + "solveClaimed": "Solve claimed", + "failedToClaim": "Failed to claim", + "moveInstance": "Move to another instance", + "moveHint": "Migrate to a different cruciverb instance. This notifies all connected peers and exports your data.", + "migrate": "Migrate", + "migrating": "Migrating...", + "migrateSuccess": "Your data has been exported. Upload this file on your new instance to restore everything.", + "migrateFailed": "Migration failed", + "signOut": "Sign out", + "signOutHint": "Clears your session cookie and starts fresh. Your data remains on the server unless you delete your account.", + "signOutEverywhere": "Sign out everywhere", + "deleteAccount": "Delete account", + "deleteHint": "Permanently delete all your data - solves, streaks, achievements, clue submissions, settings, passkeys, and this session. This cannot be undone.", + "confirmDelete": "Are you absolutely sure?", + "yesDelete": "Yes, delete everything" + } + }, + "editor": { + "title": "Puzzle editor", + "createNew": "Create new", + "customSize": "Custom size", + "yourPuzzles": "Your puzzles", + "importExport": "Import / Export", + "exportAll": "Export all", + "untitled": "Untitled", + "puzzleTitle": "Puzzle title...", + "design": "Design", + "fill": "Fill", + "clues": "Clues", + "exitTest": "Exit test", + "autoFill": "Auto-fill", + "filling": "Filling...", + "undoCtrlZ": "Undo (Ctrl+Z)", + "clearLetters": "Clear letters", + "clearEverything": "Clear everything", + "exportIpuz": "Export as .ipuz", + "unpublish": "Unpublish", + "unpublished": "Unpublished - puzzle is now a draft", + "designHint": "click cells to toggle black/white", + "fillHint": "filling {{dir}} - click same cell to switch", + "testHint": "solve the puzzle to test it", + "clueHint": "click a cell or clue to edit", + "rotationalSymmetry": "Rotational symmetry", + "mirrorSymmetry": "Mirror symmetry", + "noSymmetry": "No symmetry", + "checkAnswers": "Check answers", + "cluesClickToEdit": "Clues (click to edit)", + "writeClue": "Write a clue...", + "clickToAddClue": "click to add clue...", + "emptyHint1": "Design your grid pattern, then click Auto-fill to populate words and clues.", + "emptyHint2": "Or switch to Fill mode to type your own words, then click Clues to write clues.", + "publishTitle": "Publish your puzzle?", + "publishDesc": "Your puzzle will be visible to everyone on the welcome screen under \"community puzzles.\" Anyone can play it - it loads just like a regular puzzle with timer, hints, and validation.", + "publishNote": "You can unpublish later by changing the status back to draft from your puzzle list.", + "isThemed": "This is a themed puzzle", + "themePlaceholder": "Theme name (e.g. Space, Movies, Food)", + "dontShowAgain": "Don't show this again", + "published": "Published! Your puzzle is now available for everyone to play.", + "autoFillFailed": "Auto-fill failed", + "titleRequired": "Give your puzzle a title before publishing", + "cluesRequired": "All clues must be filled in before publishing", + "testPassed": "Test passed - puzzle solves correctly", + "testFailed": "Some answers are incorrect", + "twoLetterSlots": "{{count}} two-letter slot (min 3 letters)", + "twoLetterSlots_plural": "{{count}} two-letter slots (min 3 letters)", + "offensiveContent": "\"{{word}}\" contains offensive content", + "unpublishToDelete": "unpublish to delete", + "yesDelete": "yes, delete" + }, + "admin": { + "title": "cruciverb admin", + "invalidCredentials": "Invalid credentials", + "username": "Username", + "password": "Password", + "setup": { + "title": "Create admin account", + "desc": "No admin account exists yet. Create one to get started.", + "confirmPassword": "Confirm password", + "createAccount": "Create admin account", + "usernameTooShort": "Username must be at least 3 characters", + "passwordTooShort": "Password must be at least 8 characters", + "passwordMismatch": "Passwords do not match", + "success": "Admin account created - you can now sign in.", + "failed": "Setup failed" + }, + "tabs": { + "overview": "Overview", + "content": "Content", + "users": "Users", + "achievements": "Achievements", + "integrations": "Integrations", + "federation": "Federation", + "auditLog": "Audit log" + }, + "overview": { + "totalUsers": "total users", + "activeToday": "{{count}} active today", + "puzzlesSolved": "puzzles solved", + "today": "{{count}} today", + "avgSolveTime": "avg solve time", + "fastest": "fastest: {{time}}", + "avgPuzzleRating": "avg puzzle rating", + "ratingCount": "{{count}} ratings", + "puzzlesGenerated": "puzzles generated", + "dailyCount": "{{count}} daily", + "communityPuzzles": "community puzzles", + "drafts": "{{count}} drafts", + "communityClues": "community clues", + "pending": "{{count}} pending", + "badgesEarned": "badges earned", + "passkeys": "passkeys", + "webhooks": "webhooks", + "solveStatsByDifficulty": "Solve stats by difficulty", + "preset": "Preset", + "solves": "Solves", + "last7Days": "Last 7 days", + "topClueContributors": "Top clue contributors", + "user": "User", + "submitted": "Submitted", + "approved": "Approved", + "rate": "Rate", + "system": "System", + "dictionaryWords": "dictionary words", + "clueDbEntries": "clue DB entries", + "crypticClues": "cryptic clues", + "cachedInMemory": "cached in memory" + }, + "content": { + "puzzles": "Puzzles", + "id": "ID", + "type": "Type", + "size": "Size", + "rating": "Rating", + "created": "Created", + "random": "random", + "pendingClues": "Pending clues", + "noCluesPending": "No clues pending moderation", + "reports": "Reports", + "noPendingReports": "No pending reports", + "removeContent": "Remove content", + "blockedWords": "Blocked words", + "blockedWordsDesc": "Words added here are excluded from puzzle generation. Built-in words from the server wordlist cannot be removed from here.", + "blockedWordPlaceholder": "Word to block...", + "wordBlocked": "Word added to blocklist", + "wordBlockFailed": "Failed to add word", + "filter_all": "All", + "filter_daily": "Daily", + "filter_custom": "Custom", + "deleteSelected": "Delete selected", + "deleteSelectedMsg": "Delete {{count}} selected items? This cannot be undone.", + "puzzlesDeleted": "{{count}} puzzles deleted", + "communityPuzzles": "Community puzzles", + "noCommunityPuzzles": "No published community puzzles", + "title": "Title", + "author": "Author", + "deleteCommunityPuzzle": "Delete community puzzle", + "deleteCommunityPuzzleMsg": "Delete this community puzzle? This cannot be undone.", + "communityPuzzleDeleted": "Community puzzle deleted" + }, + "users": { + "searchPlaceholder": "Search by name or token...", + "purgeInactive": "Purge inactive", + "userCount": "{{count}} users", + "name": "Name", + "solves": "Solves", + "streak": "Streak", + "lastActive": "Last active", + "created": "Created", + "status": "Status", + "banned": "banned", + "activeStatus": "active", + "ban": "Ban", + "unban": "Unban", + "banUser": "Ban user", + "banMsg": "Ban {{name}}? This removes their published content and prevents future submissions.", + "userBanned": "User banned", + "userUnbanned": "User unbanned", + "deleteSession": "Delete session", + "deleteSessionMsg": "This will remove the user session and their settings. Solve records and achievements will remain.", + "sessionDeleted": "Session deleted", + "purgeTitle": "Purge inactive sessions", + "purgeMsg": "Delete all sessions that have been inactive for more than this many days:", + "purged": "Purged {{count}} sessions" + }, + "achievements": { + "badge": "Badge", + "description": "Description", + "unlockedBy": "Unlocked by", + "playerCount": "{{count}} player", + "playerCount_plural": "{{count}} players" + }, + "integrations": { + "webhooks": "Webhooks", + "webhookUrl": "Webhook URL", + "signingSecret": "Signing secret", + "noWebhooks": "No webhooks configured", + "url": "URL", + "events": "Events", + "format": "Format", + "failures": "Failures", + "webhookCreated": "Webhook created", + "webhookDeleted": "Webhook deleted", + "testSent": "Test event sent", + "deleteWebhook": "Delete webhook", + "deleteWebhookMsg": "Remove this webhook? It will no longer receive events.", + "dailyPublished": "Daily puzzle published", + "puzzleCompleted": "Puzzle completed", + "achievementUnlocked": "Achievement unlocked", + "allEvents": "All events", + "environment": "Environment", + "instanceRules": "Instance rules", + "instanceRulesDesc": "Rules shown on the about page. Supports basic markdown formatting.", + "adminPathDesc": "URL path to admin panel (requires restart)", + "preview": "Preview", + "mdBold": "Bold", + "mdItalic": "Italic", + "mdHeading": "Heading", + "mdList": "Bullet list" + }, + "federation": { + "status": "Status", + "active": "Active", + "notConfigured": "Not configured", + "followers": "followers", + "deliveryQueue": "delivery queue", + "inboundActivities": "inbound activities", + "outboundDeliveries": "outbound deliveries", + "blockedInstances": "blocked instances", + "frozen": "Frozen", + "federationPaused": "federation paused", + "inboxReturns503": "inbox returns 503", + "keyChanges": "key changes detected", + "checkAuditLog": "check audit log", + "controls": "Controls", + "unfreeze": "Unfreeze", + "freezeFederation": "Freeze federation", + "freezeTitle": "Freeze federation", + "unfreezeTitle": "Unfreeze federation", + "freezeMsg": "This will pause all inbound/outbound federation. Inbox returns 503, delivery and discovery workers pause.", + "unfreezeMsg": "Resume all federation activity?", + "federationFrozen": "Federation frozen", + "federationUnfrozen": "Federation unfrozen", + "frozenWarning": "Federation is frozen - inbox returns 503, delivery and discovery paused", + "allowlistMode": "Allowlist mode ON", + "followerApproval": "Follower approval ON", + "newPeerReview": "New peer review ON", + "outboxLimit": "Outbox limit: {{limit}}", + "peersTitle": "Peers", + "peerDomainPlaceholder": "Domain (e.g., other-cruciverb.example.com)", + "domain": "Domain", + "lastSeen": "Last seen", + "never": "never", + "peerConnected": "Peer connected", + "disconnectPeer": "Disconnect peer", + "disconnectMsg": "Stop federating with {{domain}}? This removes the peer and any pending deliveries.", + "peerDisconnected": "Peer disconnected", + "followersTitle": "Followers", + "approvedFollowers": "{{count}} approved follower", + "approvedFollowers_plural": "{{count}} approved followers", + "pendingApproval": "{{count}} pending approval:", + "actor": "Actor", + "requested": "Requested", + "followerApproved": "Follower approved", + "followerRejected": "Follower rejected", + "security": "Security", + "domainToBlock": "Domain to block", + "reasonOptional": "Reason (optional)", + "blocked": "Blocked", + "reason": "Reason", + "blockTitle": "Block instance", + "blockMsg": "Block {{domain}}? This removes all federated content from this domain and prevents future federation.", + "instanceBlocked": "Instance blocked", + "unblockTitle": "Unblock instance", + "unblockMsg": "Allow {{domain}} to federate again?", + "instanceUnblocked": "Instance unblocked", + "exportBlocklist": "Export blocklist", + "importBlocklist": "Import blocklist", + "importedBlocked": "Imported {{count}} blocked domains", + "reputationDomain": "Domain", + "reputationScore": "Score", + "reputationClues": "Clues", + "reputationRejected": "Rejected", + "reputationReports": "Reports", + "reputationUpdated": "Updated", + "profile": "Profile", + "actorUsername": "Actor display name", + "bio": "Bio / summary", + "bioPlaceholder": "Daily crossword puzzles in five sizes, four game modes, cryptic clues, multiplayer rooms, party games, a built-in puzzle editor for constructors, and a clue trainer for the stubborn.", + "avatar": "Avatar", + "banner": "Banner", + "saveProfile": "Save profile", + "profileSaved": "Profile saved", + "uploading": "Uploading...", + "uploadSuccess": "Image uploaded", + "uploadFailed": "Upload failed", + "configuration": "Configuration", + "activityPubTitle": "ActivityPub federation", + "activityPubSetup": "Set AP_DOMAIN to enable. Required env vars:", + "activityPubDomain": "your public domain", + "activityPubActor": "optional, defaults to \"cruciverb\"", + "activityPubReady": "Database tables are ready. RSA keys generate on first startup.", + "apHumor": "AP post humor", + "apHumorDesc": "Custom taglines, sign-offs, and community middles added to daily ActivityPub posts. One entry per line.", + "dailyTaglines": "Daily taglines", + "dailySignoffs": "Daily sign-offs", + "communityMiddles": "Community digest middles", + "onePerLine": "One per line...", + "humorSaved": "AP humor lines saved", + "entries": "entries" + }, + "audit": { + "noEntries": "No audit log entries", + "action": "Action", + "target": "Target", + "detail": "Detail", + "time": "Time" + }, + "dialog": { + "deletePuzzle": "Delete puzzle", + "deletePuzzleMsg": "This will permanently delete the puzzle and all associated solve records. This cannot be undone.", + "puzzleDeleted": "Puzzle deleted", + "reportRemoved": "Report removed", + "reportDismissed": "Report dismissed" + } + }, + "history": { + "title": "Puzzle history", + "empty": "No solves yet. Complete a puzzle to see it here.", + "hint": "hint", + "hints": "hints", + "playAgain": "Play again" + }, + "archive": { + "title": "Puzzle archive", + "all": "All", + "solved": "Solved", + "loadMore": "Load more", + "empty": "No daily puzzles found.", + "play": "Play" + }, + "challenge": { + "todaysChallenge": "Today's challenge", + "completed": "Challenge completed - {{challenge}}", + "notMet": "Challenge not met - {{challenge}}", + "types": { + "purist": "Purist", + "speed_demon": "Speed demon", + "surgeon": "Surgeon", + "no_regrets": "No regrets", + "iron_will": "Iron will", + "trust_fall": "Trust fall", + "inkwell": "Inkwell", + "night_owl": "Night owl", + "early_bird": "Early bird", + "rush_hour": "Rush hour", + "night_shift": "Night shift", + "afternoon_tea": "Afternoon tea", + "contrarian": "Contrarian", + "sunday_stroll": "Sunday stroll", + "blitz": "Blitz", + "opera_singer": "Opera singer", + "tongue_twister": "Tongue twister", + "mic_drop": "Mic drop", + "jackrabbit": "Jackrabbit", + "metronome": "Metronome", + "quickdraw": "Quickdraw", + "bookends": "Bookends", + "long_haul": "Long haul", + "fast_fingers": "Fast fingers", + "three_in_a_row": "Three in a row", + "alphabetical": "Alphabetical", + "lucky_seven": "Lucky seven", + "even_steven": "Even Steven", + "minimalist": "Minimalist", + "overtime": "Overtime", + "no_hints": "Purist", + "clean_sweep": "Surgeon", + "first_try": "Surgeon" + }, + "descriptions": { + "purist": "Solve without using any hints, reveals, or checks", + "speed_demon": "Finish before the speedrun time limit", + "surgeon": "Complete without entering a single wrong letter", + "no_regrets": "Never delete, backspace, or undo any letter", + "iron_will": "Solve without pausing", + "trust_fall": "Solve without using auto-check", + "inkwell": "Solve entirely in pen mode, never use pencil", + "night_owl": "Solve between midnight and 5am", + "early_bird": "Solve between 5am and 8am", + "rush_hour": "Solve between 12pm and 1pm", + "night_shift": "Solve between 8pm and midnight", + "afternoon_tea": "Solve between 3pm and 5pm", + "contrarian": "First cell you select must be in the bottom-right quadrant", + "sunday_stroll": "Take at least 3 minutes to solve regardless of size", + "blitz": "Solve in under 60 seconds", + "opera_singer": "At some point type 5 vowels in a row", + "tongue_twister": "At some point type 6 consonants in a row", + "mic_drop": "The final letter you type completes the puzzle", + "jackrabbit": "Fill 10 cells in the first 30 seconds", + "metronome": "Never go more than 15 seconds between typing letters", + "quickdraw": "Never spend more than 10 seconds on a single cell", + "bookends": "The first and last letter you type must both be correct", + "long_haul": "Spend at least 30 seconds studying clues before typing your first letter", + "fast_fingers": "Fill the first row completely within 20 seconds", + "three_in_a_row": "Type the same letter three times consecutively", + "alphabetical": "The first letter you type must be A, B, C, D, or E", + "lucky_seven": "Solve in exactly 7 minutes (within 10 seconds)", + "even_steven": "Your solve time in seconds must be an even number", + "minimalist": "Use fewer than 3 hint checks (not zero, but almost)", + "overtime": "Take longer than 10 minutes to solve", + "no_hints": "Solve without using any hints, reveals, or checks", + "clean_sweep": "Complete without entering a single wrong letter", + "first_try": "Complete without entering a single wrong letter" + } + }, + "heatmap": { + "title": "Solve heatmap", + "show": "View heatmap", + "hide": "Hide heatmap", + "clean": "Clean", + "hinted": "Hinted", + "wrong": "Wrong" + }, + "constructorAnalytics": { + "title": "Puzzle analytics", + "totalSolvers": "Total solvers", + "avgTime": "Avg solve time", + "completionRate": "Completion rate", + "hintHeatmap": "Hint heatmap", + "heatmapDesc": "Cells where solvers needed the most hints", + "noData": "No solve data yet. Wait for people to play your puzzle." + }, + "trainer": { + "title": "Clue trainer", + "easy": "Easy", + "medium": "Medium", + "hard": "Hard", + "score": "{{correct}} / {{total}} correct", + "letters": "{{count}} letters", + "placeholder": "Type your answer...", + "next": "Next", + "correctMsg": "Correct!", + "wrongMsg": "Wrong - the answer was {{answer}}", + "noClues": "No clues available" + } +}