visual-game.js 36.8 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
export default {
  appName: 'voko.world',
  productBrandSub: 'Your kosmo is calling.',
  mobileCredit: { title: 'My Credits', buyCredits: 'Buy Credits', history: 'Credit History', purchaseHistory: 'Purchases', usageHistory: 'Usage', emptyHistory: 'No credit history yet.', loadMore: 'Load more', balanceAfter: 'Balance: {points}', pending: 'Pending', paid: 'Paid', canceled: 'Canceled', failed: 'Failed', refunded: 'Refunded', disputed: 'Disputed', autoReload: 'Auto-Reload', bonusCredits: 'Bonus Credits', buyNow: 'Buy Now', lowBalance: 'Low Balance', enabled: 'Enabled', whenBelow: 'When my balance falls below', choosePack: 'Choose a reload pack', consent: 'I authorize Voko to charge this pack automatically whenever my balance reaches the threshold. Purchases are final except where required by law.', terms: 'Terms of Use', continueSecurely: 'Continue securely', save: 'Save settings', disable: 'Turn off Auto-Reload', setupComplete: 'Payment method saved. Auto-Reload is now enabled.', setupCanceled: 'Auto-Reload setup was canceled.', actionRequired: 'The last reload failed. Set up your payment method again to resume.', howTitle: 'How Auto-Reload works', howBalance: 'We buy the selected pack when your balance reaches or falls below the threshold.', howSecure: 'Payment details are collected and stored securely by Stripe; Voko never stores your card number.', howCancel: 'You can turn Auto-Reload off at any time. Turning it off does not refund previous purchases.' },
  nav: { play: 'Play', notes: 'Clues', state: 'State', editor: 'Authoring' },
  scenario: { title: 'Scenario', available: 'Scenarios', assetManifest: 'Assets' },
  character: { title: 'Character', start: 'Start game', current: 'Current character' },
  stage: { empty: 'Choose a scenario and character', turn: 'Turn {turn}', ending: 'Ending', transcript: 'Transcript' },
  composer: { act: 'Act', say: 'Say', placeholder: 'Enter an action or line' },
  actions: { refresh: 'Refresh', start: 'Start', home: 'Home', backHome: 'Back', newRun: 'Restart', send: 'Send', save: 'Save', compile: 'Compile', publish: 'Publish', rollback: 'Rollback', upload: 'Upload', select: 'Select', endingText: 'Ending text', load: 'Load', backToGame: 'Back', cancel: 'Cancel', confirm: 'Confirm' },
  account: {
    signIn: 'Sign in',
    signedIn: 'Signed in',
    signedOut: 'Signed out',
    sessionExpired: 'Your session expired. Please sign in again.',
    logoutFailed: 'Log out failed. Please try again.',
    registered: 'Account created',
    email: 'Email',
    emailPlaceholder: 'Email address',
    password: 'Password',
    passwordPlaceholder: 'Enter password',
    confirmPassword: 'Confirm password',
    confirmPasswordPlaceholder: 'Enter password again',
    verificationCode: 'Verification code',
    verificationCodePlaceholder: 'Enter the 6-digit email code',
    inviteToken: 'Invite code',
    inviteTokenPlaceholder: 'Enter the 8-character invite code',
    sendCode: 'Send code',
    resendCode: 'Resend',
    codeSent: 'Code sent. Check your email.',
    codeRequired: 'Enter the email verification code first.',
    inviteInvalid: 'Enter a valid 8-character invite code.',
    emailRequired: 'Enter your email first.',
    emailInvalid: 'Enter a valid email address.',
    passwordRequired: 'Enter your password first.',
    confirmPasswordRequired: 'Confirm your password first.',
    loginFieldsRequired: 'Enter your email and password.',
    inviteEmailMismatch: 'This email does not match the application email linked to the invite code.',
    passwordMismatch: 'Passwords do not match.',
    createAccount: 'Create account',
    hasAccount: 'Already have an account?',
    noAccount: 'No account yet?',
    termsHint: 'By continuing, you confirm that this email belongs to you. Your account will save your progress, orders, and credits.',
    login: 'Log in',
    register: 'Register',
    reset: 'Reset',
    forgotPassword: 'Forgot password?',
    resetPassword: 'Reset password',
    resetTitle: 'Reset password',
    resetBody: 'Verify your email code, then set a new password. Existing sessions for this account will be revoked.',
    resetCodeSent: 'Reset code sent. Check your email.',
    resetFieldsRequired: 'Enter your email, verification code, new password, and confirmation password.',
    passwordResetDone: 'Password reset. Log in with the new password.',
    logout: 'Log out',
    logoutAll: 'Log out everywhere',
    credits: 'Credits',
    buy: 'Buy credits',
    billing: 'Billing',
    promptTitle: 'Sign in or register to start',
    promptBody: 'Your account saves progress, save slots, endings, credits, and orders so you can continue on another device.',
    signedInBody: 'Your progress, save slots, and credits are saved to your account.',
    registerAndStart: 'Create an account and choose a character',
    guestStart: 'Try as guest',
    savedHint: 'Sign in to auto-save progress and save slots',
    currentAccount: 'Current account',
    saveKicker: 'Cloud save',
    saveTitle: 'Save your progress and purchases',
    loginTitle: 'Log in',
    loginBody: 'After you log in, we will merge any guest progress from this device when possible.',
    registerTitle: 'Create account',
    registerBody: 'Create an account with an invite code, then verify your email. You will be signed in, receive 100 credits, and keep any guest progress from this device.',
    openBilling: 'View credits and orders',
    mergePending: 'Merging guest progress from this device…',
    mergeSuccess: 'Guest data merged: {details}.',
    mergeSeparator: ', ',
    mergeRuns: '{count} runs',
    mergeTurns: '{count} turns',
    mergeProgress: '{count} progress records',
    mergeSaveSlots: '{count} save slots',
    mergeEndingRecords: '{count} endings',
    mergeEvents: '{count} events',
    mergeAgentCalls: '{count} AI calls',
    mergeBillingLedger: '{count} ledger entries',
    mergeBillingUnlocks: '{count} paid unlocks',
    mergeBillingFreeQuota: '{count} free quotas',
    mergePaymentOrders: '{count} orders',
    mergeEmpty: 'No local guest progress to merge.',
    mergeFailed: 'You are signed in, but we could not merge your guest progress. Refresh the page and try again later.',
  },
  billing: {
    title: 'Credits and orders',
    subtitle: 'View your balance, credit purchases, transaction history, and orders.',
    guestSubtitle: 'You can browse credit packs as a guest. Sign in to make a purchase or view your balance, orders, and transaction history.',
    balance: 'Current balance',
    products: 'Credit packs',
    ledger: 'Credit ledger',
    orders: 'Orders',
    refresh: 'Refresh',
    testModeTitle: 'Stripe test environment',
    testModeBody: 'Checkout uses test payments. No real charge will be made.',
    testPurchase: 'Test purchase',
    buy: 'Buy {points} credits',
    checkoutReady: 'Checkout link is ready.',
    openCheckout: 'Open checkout',
    selectedPack: 'Selected',
    selectedProduct: '{title} selected. Your account balance will increase by {points} credits.',
    signInToBuy: 'Sign in to buy',
    readyToBuy: 'Ready to buy credits',
    readyToBuyBody: 'Choose a credit pack and continue to checkout. Orders may show processing before confirmation.',
    checkoutPending: 'Redirecting to checkout…',
    checkoutReturnedSuccess: 'Welcome back. We are refreshing your balance and orders. Credits will be added once payment is confirmed.',
    checkoutReturnedCancel: 'Checkout was canceled or not completed. No credits were added.',
    signInHint: 'Sign in to view balance, orders, and ledger entries.',
    emptyProducts: 'No credit packs.',
    emptyLedger: 'No ledger entries.',
    emptyOrders: 'No orders.',
    productFallback: 'Credit pack',
    points: '{points} credits',
    price: '{amount} {currency}',
    ledgerAmount: '{amount} credits',
    orderStatus: 'Status {status}',
    orderStatusPending: 'Processing',
    orderStatusPaid: 'Paid',
    orderStatusRefunded: 'Refunded',
    orderStatusDisputed: 'Disputed',
    orderStatusUnknown: 'Unknown status',
    ledgerReasonSignupGift: 'Signup gift',
    ledgerReasonCheckout: 'Credit purchase',
    ledgerReasonRefund: 'Order refund',
    ledgerReasonChargeback: 'Chargeback reversal',
    ledgerReasonUnlock: 'Content unlock',
    paywallTitle: 'Unlock with credits',
    paywallTarget: 'Target',
    paywallReason: 'Reason',
    paywallGenericReason: 'Credits are required to continue this content.',
    paywallFreeContinue: 'You still have a free play available. You can continue without spending credits.',
    paywallInsufficient: 'You do not have enough credits. Buy more credits to continue.',
    paywallPrice: 'Required credits',
    paywallFreeQuota: 'Free quota left',
    paywallBalance: 'Available balance',
  },
  endings: {
    title: 'Ending collection',
    unlocked: 'Unlocked',
    locked: 'Locked',
    collected: 'Collected {count}/{total}',
    lockedHint: 'Reach this ending to unlock it.',
  },
  saves: {
    title: 'Save slots',
    progress: 'Current progress',
    activeRun: 'Current run',
    slot: 'Slot {slotNo}',
    savedRun: 'Saved progress',
    emptySlot: 'Empty slot',
    emptyHint: 'Save your current progress here so you can return to it later.',
    saveHere: 'Save',
    overwrite: 'Overwrite save',
    restore: 'Load',
    delete: 'Delete',
    saved: 'Game saved.',
    restored: 'Save loaded.',
    deleted: 'Save deleted.',
    conflict: 'This slot already contains a save. Confirm that you want to overwrite it.',
    restoreConfirm: 'Loading this save will replace your current game state.',
    deleteConfirm: 'Deleting this save point cannot be undone.',
    noRunHint: 'There is no active game to save. Start or continue a game first.',
  },
  map: {
    title: 'Map',
    current: 'Current location',
    route: 'Available route',
    routes: 'Available destinations',
    locked: 'Locked',
    moveTo: 'Go to {location}',
    objects: 'Inspectable objects',
    inspect: 'Inspect',
    inspected: 'Inspected',
    inspectObject: 'Inspect {object}',
  },
  videoCenter: {
    eyebrow: 'Player playthroughs',
    title: 'Video Center',
    backHome: 'Back',
    chooseScenario: 'Choose scenario',
    allScenarios: 'All scenarios',
    searchScenario: 'Search scenarios',
    scenarioPaintedSkin: 'Painted Skin',
    scenarioGuian: 'Guian Heart-Loss Case',
    nowPlaying: 'Now playing',
    player: 'Player',
    views: 'Views',
    ending: 'Ending',
    duration: 'Duration',
    libraryEyebrow: 'More from this scenario',
    allVideos: 'All videos',
    sort: 'Sort videos',
    recommended: 'Recommended',
    latest: 'Latest',
    videoMeta: '{player} · {date}',
    emptyTitle: 'No player videos yet',
    emptyText: 'No public playthroughs are available for this scenario.',
    viewAll: 'View all player videos',
    discoverStories: 'See how other players shaped the story',
    backPrevious: 'Back', aiSummary: 'AI summary:', endingCount: ' endings', publicSeriesCount: ' public series', totalPlays: ' total plays', seriesList: 'Player series', completed: 'Complete', updating: 'Episode {count} · Ongoing', seriesMetrics: 'Episodes: {episodes} · ▶ {plays} · ♥ {likes}', watchSeries: 'Watch this series', playOwnSeries: 'Create your own story', minutes: '{count} min', share: 'Share', shareCopied: 'Link copied', shareFailed: 'Could not share this video', like: 'Like', liked: 'Liked', save: 'Save', saved: 'Saved', storyHighlight: 'Story highlights', startPlay: 'Play now · Rewrite this ending', episodeList: 'Episodes', episodeTotal: 'Episodes: {count}', episodeTitle: 'Episode {count} · {title}',
  },
  runHistory: {
    eyebrow: 'Your play history',
    title: 'My Runs',
    allRuns: 'All runs',
    count: 'Games: {count}',
    empty: 'You have not played any games yet. Start a game and it will appear here.',
    loadFailed: 'Could not load your games: {error}',
    noTime: 'Unknown time',
    statusActive: 'Active',
    statusCompleted: 'Ended',
    videoNone: 'No video',
    videoQueued: 'Video queued',
    videoRunning: 'Generating video',
    videoReady: 'Video ready',
    videoFailed: 'Video generation failed',
    videoRetry: 'Generate again',
    untitledEnding: 'No ending yet',
    meta: '{scenario} · Turn {turn} · {date}',
    openRun: 'Continue game',
    openNotes: 'Notes',
    watchVideo: 'Watch video',
  },
  mobileProfile: { title: 'MY', settings: 'Settings', content: 'Content', benefits: 'Benefits', creator: 'Creator', support: 'Inquiries and support', contact: 'Customer support', terms: 'Terms of Use', privacy: 'Privacy Policy', social: 'Social media' },
  profileEditor: { editProfile: 'Edit profile', nameSection: 'Profile name', nameHint: 'This name is shown with your games and videos.', name: 'Name', saveName: 'Save name', nameRequired: 'Enter your name.', nameSaved: 'Your name has been updated.', passwordSection: 'Change password', passwordHint: 'Verify your account using the code sent to your email.', email: 'Email', verificationCode: 'Email verification code', sendCode: 'Send code', codeSent: 'A verification code was sent to your email.', newPassword: 'New password', confirmPassword: 'Confirm new password', savePassword: 'Update password', passwordRequirements: 'Enter the verification code and a password of at least 8 characters.', passwordMismatch: 'The passwords do not match.', passwordSaved: 'Your password has been updated.' },
  myGames: { recent: 'Recently played', popular: 'Popular stories', emptyRecent: 'Your recently played stories will appear here.', loadError: 'Could not load your games. Please try again.' },
  storyDetail: { title: 'Story Information', description: 'Story Description', episodes: '{count} Episodes', endings: '{count} Endings', by: 'By {name}', lastUpdate: 'Last Update', newPlay: 'New Play', notFound: 'This story could not be found.', loadError: 'Could not load the story. Please try again.' },
  accountCenter: { loadFailed: 'Could not load your profile', loadTimeout: 'The request timed out. Check your connection and try again.', title: 'Account', eyebrow: 'Player profile', runs: 'My games', videos: 'My videos', favorites: 'Saved videos', watchSeason: 'Watch season', emptyGames: 'You have not started any games yet.', emptyVideos: 'You have not generated any videos yet.', emptyFavorites: 'You have not saved any videos yet.', lastPlayed: 'Last played · {date}', currentTurn: 'In progress · Turn {turn}', noActiveRun: 'No game in progress', endingsUnlocked: 'Endings: {unlocked}/{total}', runsPlayed: 'Games played: {count}', continueGame: 'Continue game', startGame: 'Start game', viewHistory: 'View history', levelProgress: 'Levels played: {played}/{total}', viewSeasonDetails: 'View season details', hideSeasonDetails: 'Hide season details', levelInProgress: 'In progress', levelCompleted: 'Complete', endingLabel: 'Ending reached', cluesLabel: 'Clues found', clueCount: 'Clues: {count}', viewRunRecords: 'View game history', runRecordSummary: 'Clues: {clues} · Endings: {ending}', noEnding: 'No ending reached yet', noClues: 'No clues found yet', seasonDetailsLoadFailed: 'Could not load this season. Try again.', public: 'Public', private: 'Only me', deleteVideo: 'Delete video', confirmDelete: 'Delete video?', signInTitle: 'Sign in to continue', signInBody: 'Sign in to manage your games and videos.' },
  visual: {
    eyebrow: 'TRPG Visual',
    chooseScript: 'Choose script',
    actTitle: 'Act {count} · {title}',
    actLocked: '{title} is locked',
    actSealed: 'Act Sealed',
    toBeContinued: 'To be continued',
    finishPreviousAct: 'Reach an ending in the previous act',
    revealAfterAct: 'Revealed after completing Act {count}',
    moreScripts: 'More levels',
    comingSoon: 'Coming soon',
    watchScriptVideo: 'Watch story video',
    scenarioKicker: 'Scenario',
    hiddenTitle: 'Tianyou',
    revealedTitle: 'Painted Skin',
    objectives: 'Current objectives',
    playHistory: 'Play history',
    objectiveLabel: 'Objective',
    close: 'Close',
    noObjectives: 'No active objectives.',
    taskProgress: 'Steps {done}/{total}',
    entryKicker: 'Enter game',
    entryTitle: 'Enter the room tonight',
    emptyScenarioEyebrow: 'Scenario unavailable',
    emptyScenarioTitle: 'No playable scenario',
    emptyScenarioText: 'The server did not return a published scenario. Refresh or check publishing status.',
    emptyScenarioActionTitle: 'Waiting for publication',
    emptyScenarioActionText: 'A game cannot start until a published scenario is available.',
    continueTitle: 'Continue your last game',
    entryBody: 'Choose a character before entering the old room.',
    chooseCharacter: 'Choose character',
    enterStory: 'Enter the story',
    back: 'Back',
    skip: 'Skip',
    editor: 'Editor',
    debugPage: 'Debug',
    state: 'State',
    settings: 'Settings',
    language: 'Language',
    languageUnavailable: 'This level does not have an available translation for that language yet.',
    turnVideoMatching: 'Smart CG matching',
    turnVideoMatchingHint: 'When enabled, similar story beats can be matched to this turn. When off, only exact CG bindings play.',
    turnVideoMatchingOn: 'On',
    turnVideoMatchingOff: 'Off',
    exitGame: 'Exit game',
    exitGameHint: 'Leave the current session and return to scenario selection.',
    saveAndExit: 'Save and exit',
    exitWithoutSaving: 'Exit without saving',
    notes: 'Notes',
    saves: 'Saves',
    continueRun: 'Continue game',
    balanceEntry: 'Balance {credits}',
    endingCollection: 'Ending collection',
    narrator: 'Narrator',
    npcName: 'Woman in the room',
    npcLine: '"{line}"',
    notStarted: 'Not started',
    startHint: 'Select Start to enter the old house.',
    debug: 'Debug stats',
    debugOnly: 'Debug only',
    waiting: 'Ready.',
    modeAct: 'Do',
    modeSay: 'Say',
    inputMethod: 'Input method',
    textInputMode: 'Text',
    voiceInputMode: 'Voice',
    choiceGroupLabel: 'Story choices',
    inputPlaceholder: 'Example: I lift the bronze mirror toward her and the portrait.',
    mobileInputPlaceholder: 'Say or do something…',
    voiceStart: 'Start voice input',
    voiceStop: 'Stop voice input',
    voiceListening: 'Listening…',
    voiceRecognizing: 'Recognizing…',
    voiceUnsupported: 'Voice input is not supported in this browser',
    voicePermissionDenied: 'Microphone access was denied. Check your browser permissions',
    voiceMicrophoneUnavailable: 'No available microphone was found',
    voiceNoSpeech: 'No speech was recognized. Try again',
    voiceNoAudioCaptured: 'No microphone audio was captured. Check your browser permissions',
    voiceRecognitionFailed: 'Voice recognition failed. Try again',
    voiceHoldToTalk: 'Hold to talk',
    voicePreparing: 'Preparing microphone…',
    voiceShortcutLabel: 'Shortcut',
    voiceReleaseSend: 'Release to send',
    voiceSlideCancel: 'Slide up to cancel',
    voiceReleaseCancel: 'Release to cancel',
    voiceUnderstanding: 'Understanding your command…',
    ending: 'Ending',
    endingLabel: 'Ending',
    endingLine: 'Ending: {title}\n{description}',
    endingSummary: 'Ending summary',
    endingPoem: 'Ending poem',
    endingFallbackPoem: '...',
    endingBackgroundLoadFailed: 'The ending scene failed to load. Please refresh and try again.',
    recordSeal: 'Case',
    recordTitle: 'Constable\'s report',
    recordKicker: 'County archive',
    recordMeta: 'After the rainy night',
    continue: 'Continue',
    enterNextEpisode: 'Enter next episode',
    viewCurrentSeasonStory: 'View this season',
    played: 'Played',
    nextPage: 'Next page',
    statusCreating: 'Preparing your story…',
    statusStarted: 'Game started. Enter dialogue or an action.',
    statusHistoryRun: 'You are viewing a previous game. Continuing will follow your latest saved progress.',
    statusKp: 'The Keeper is evaluating your action…',
    statusDone: 'Turn complete.',
    contentFiltered: 'This action cannot be described. Try acting another way.',
    turnFailed: 'We couldn\'t complete this step. Please try again.',
    audioFailed: 'Speech playback failed. Please try again.',
    actionRejected: 'That action cannot take effect yet. Try another approach.',
    statusEnded: 'Game ended.',
    statusEndingText: 'Writing your ending…',
    statusEndingLocked: 'Ending determined. Preparing the finale…',
    statusNpcAction: 'A character is taking action. The Keeper is evaluating the outcome…',
    statusNpcDone: 'NPC responded.',
    clueGained: 'Clue gained',
    landscapeTitle: 'Rotate your device',
    landscapeBody: 'This story is designed for landscape play so the full scene stays visible.',
    stats: {
      suspicion: 'Suspicion',
      charm: 'Charm',
      fear: 'Fear',
    },
    guide: {
      label: 'Guide',
      skip: 'Skip',
      next: 'Next',
      done: 'Got it',
      optionsTitle: 'Choose a suggestion',
      optionsText: 'After a character responds, two suggested choices appear below. Select Say or Do to continue.',
      keyboardTitle: 'Switch to keyboard input',
      keyboardText: 'Voice input is the default. Select the keyboard button whenever you want to type what you say or do.',
      inputTitle: 'Choose Say or Do',
      inputText: 'Before typing, choose whether you are speaking or acting. The Keeper will evaluate either choice.',
      notesTitle: 'Check notes',
      notesText: 'The notes button records discovered clues and unlocked endings.',
      goalTitle: 'Survive',
      goalText: 'This is more than a conversation. Listen carefully, explore the room, and investigate clues and anything unusual.',
      videoTitle: 'Complete the story and turn your adventure into a video of your own',
      videoExampleLabel: 'Example video',
      videoExampleNotice: 'This video is only an example and does not represent the content generated from your playthrough.',
      videoLoading: 'Loading an example video…',
      videoUnavailable: 'This story does not have an example video yet.',
      videoDone: 'Start my story',
    },
    openingOptions: {
      say: 'Sorry, I did not mean to break in. Something outside was chasing me.',
      act: 'Keep your voice low and inspect the doors, windows, lamp, and her position.',
    },
  },
  panels: { clues: 'Clues', conclusions: 'Conclusions', info: 'Info', inventory: 'Inventory', events: 'Events', stats: 'Stats', flags: 'Flags', objects: 'Objects', npcs: 'NPCs', endingRecords: 'Unlocked endings' },
  standalone: {
    summary: 'Summary',
    availableClues: 'Available clues',
    recentTurns: 'Recent turns',
    audit: {
      title: 'AI audit',
      subtitle: 'Costs and errors for Keeper, character, ending, and text-to-speech calls.',
      totalCalls: 'Calls',
      failedCalls: 'Failed',
      tokens: 'Input/output tokens',
      cost: 'Cost',
      roles: 'Role summary',
      models: 'Model summary',
      recentCalls: 'Recent calls',
      empty: 'No AI calls recorded.',
      calls: '{count} calls',
      tokenPair: 'Tokens: {tokens}',
    },
    state: {
      title: 'State view',
      waiting: 'Waiting for game data.',
      noRun: 'No game ID was provided. Open this page from a game or enter an ID manually.',
      loading: 'Loading game {runId}…',
      loaded: 'Game {runId} loaded',
    },
    notes: {
      title: 'Notes',
      waiting: 'Loading notes.',
      noRun: 'No current save found. Start or continue a run first.',
      loading: 'Loading notes for game {runId}…',
      loaded: 'Notes for game {runId} loaded',
      unavailable: 'Notes for this game are temporarily unavailable.',
      emptyClues: 'No clues yet',
      emptyConclusions: 'No conclusions yet',
      emptyEndings: 'No endings yet',
      tabs: 'Note categories',
      list: 'Entry list',
      detail: 'Entry detail',
      locked: 'Locked',
      lockedHint: 'This ending has not been recorded yet.',
      recorded: 'Recorded',
      importance: { minor: 'Minor', major: 'Major', critical: 'Critical' },
      relatedClues: 'Related clues',
      noRelatedClues: 'No supporting clue has been found yet.',
    },
  },
  editor: {
    title: 'Scenario authoring',
    pack: 'Pack',
    section: 'Section',
    raw: 'JSON',
    structured: 'Structured editor',
    debugJson: 'Debug JSON',
    item: 'Item',
    noStructuredFields: 'This section has no direct structured fields yet.',
    updated: 'Saved',
    assetUploaded: 'Asset uploaded to draft',
    compiled: 'Draft compiled',
    published: 'Version published',
    rolledBack: 'Rolled back to version',
    versions: 'Versions',
    assets: 'Asset index',
    current: 'Current',
    currentVersion: 'Current version',
    noPublishedVersion: 'Not published',
    noVersions: 'No versions yet. Compile a draft first.',
    noAssets: 'No asset index yet. Compile the asset manifest first.',
    noFilteredAssets: 'No assets match the current filters.',
    assetSearch: 'Search',
    assetSearchPlaceholder: 'Tag, file, or objectKey',
    assetTypeFilter: 'Type',
    assetSourceFilter: 'Source',
    assetFilterAll: 'All',
    assetFilterResult: '{shown}/{total}',
    assetSource: {
      uploaded: 'Uploaded object',
      static: 'Static file',
      missing: 'Missing locator',
    },
    uploadFile: 'File',
    uploadTag: 'Tag',
    uploadTagPlaceholder: 'opening_cg',
    uploadType: 'Type',
    noUploadFile: 'No file selected',
  },
  waitingList: {
    eyebrow: 'Early access',
    apply: 'Join Waitlist',
    useInvite: 'Use invite code',
    invitedRegister: 'Have an invite? Create account',
    title: 'Join the voko.world waitlist',
    body: 'Leave your email. Once you are approved for early access, we will send you a personal invitation.',
    displayName: 'Name',
    displayNamePlaceholder: 'Optional',
    useCase: 'Use case',
    useCaseOptional: 'Use case (optional)',
    useCasePlaceholder: 'For example: creator preview, team test, or live event',
    useCaseRequired: 'Use case is required.',
    consent: 'I agree to receive emails about my invite request and voko.world product updates.',
    join: 'Join the waitlist',
    joined: 'We received your request. We will email you when there is an update.',
    joinedTitle: 'Request submitted',
    changeEmail: 'Use another email',
    accountAlreadyRegistered: 'This email is already registered. Please sign in.',
    emailRequired: 'Email is required.',
    consentRequired: 'Consent is required before joining the list.',
    inviteEyebrow: 'Invite',
    inviteTitle: 'Create your voko.world account',
    inviteBody: 'This invite is linked to your email address. Set a password to finish creating your account.',
    inviteMissing: 'No invite code was provided.',
    inviteEnterCode: 'Enter the 8-character invite code you received to create an account.',
    inviteValidate: 'Verify invite code',
    inviteReenter: 'Enter another code',
    inviteInvalid: 'This invite is invalid or expired.',
    codeSent: 'Verification code sent.',
    passwordMismatch: 'Passwords do not match.',
    acceptInvite: 'Create account',
  },
  landing: { officialVideo: 'Watch the official video', officialVideoEyebrow: 'Meet VOKO', officialVideoTitle: 'Your kosmo is calling.', officialVideoBody: 'Create your own world, play through stories that call you back, and share every moment as video worth watching.', homeLabel: 'voko.world home', navHome: 'Home', navScripts: 'Stories', navVideos: 'Drama', featuredStories: 'Featured Games', featuredStoriesEyebrow: 'Start playing', endingCount: 'Endings: {count}', allStories: 'All Stories', dramaTitle: 'Drama', brandSub: 'Interactive stories', navLabel: 'Main navigation', navStories: 'Games', navGallery: 'Gallery', eyebrow: 'AI-powered visual role-playing', loadError: 'Could not load the home page. Please try again.', retry: 'Try again', emptyGames: 'No games are available yet.', scenarioFallback: 'Step into this world and reshape the story with every choice.', publicVideos: 'Player videos', publicGames: 'Public games', watchDrama: 'Watch story', enterPlay: 'Play now', comingSoon: 'Coming soon', carouselLabel: 'Featured games', previousGame: 'Previous game', nextGame: 'Next game', showcaseTitle: 'Stories shaped by players', showcaseBody: 'Watch the endings other players reached, then step into the game and create your own.', searchLabel: 'Search stories', searchPlaceholder: 'Search by game or episode title', sortLabel: 'Sort stories', latest: 'Latest', popular: 'Popular', videoCount: 'Videos: {count}', levelCount: 'Episodes: {count}', levelNumber: 'Episode {count}', singleLevel: 'Standalone story', publicGame: 'Public game', noSearchResults: 'No stories match your search', clearSearch: 'Clear filters', finalEyebrow: 'Your story awaits', finalTitle: 'Who will you become?', finalBody: 'Cross the threshold and make your first choice.', begin: 'Begin your story', footer: '© 2026 voko.world', earlyAccess: 'Early access', footerSocial: 'Community', footerLegal: 'Legal & contact', privacy: 'Privacy Policy', terms: 'Terms of Service', contact: 'Contact us' },
  landingIntro: { title: 'voko.world', apply: 'Request access', watchVideo: 'Watch stories', start: 'Start game', continue: 'Continue game', viewJourney: 'Play history', continueProgress: 'Season {season} · {scenario}', storyLine: 'Storyline', viewMore: 'View more' },
  landingCarousel: { introEyebrow: 'Generative drama game', introSummary: 'Watch a short drama, step into the role, and let every choice reshape the story.', dramaEyebrow: 'Featured drama {count}', controls: 'Home carousel', goToSlide: 'Go to slide {count}' },
  landingCinema: { navHome: 'Home', navDramas: 'Game', navPlayerStories: 'Dramas', navMy: 'My', featuredDramas: 'Games', playerStories: 'Dramas' },
  searchPage: { placeholder: 'Search stories, dramas, or accounts', popular: 'Popular Searches', history: 'Recent Searches', clearHistory: 'Clear', resultTypes: 'Search result types', all: 'All', accounts: 'Account', accountStats: '{titles} titles · {plays} plays' },
  storyTree: {
    eyebrow: 'Season storylines',
    subtitle: 'Each line represents a season. Each node represents an episode.',
    root: 'Story',
    branch: 'Branch',
    active: 'In progress',
    completed: 'Complete',
    locked: 'Not reached',
    empty: 'No season storylines yet.',
    loadError: 'Could not load your season storylines.',
    navigation: 'Season range',
    range: 'Seasons {first}–{last} · Total: {total}',
    newer: 'Previous',
    older: 'Next',
    startPlaying: 'Play now',
    turnProgress: 'Turn {turn}',
    continuePlaying: 'Continue playing',
    playLog: 'Play history',
    seasonEnding: 'Season ending',
    currentSeason: 'Current season',
    logLoadError: 'Could not load your play history.',
    logEmpty: 'There is no play history for this season yet.',
    turnNumber: 'Turn {turn}',
    playerSaid: 'You said',
    playerDid: 'You did',
    storyOpening: 'Story opening',
    turnVideos: 'Matched videos',
    playActionVideo: 'Action video',
    playPlayerVideo: 'Player video',
    playNpcVideo: 'NPC video',
    replayTurn: 'Replay',
    replayTurnTitle: 'Turn {turn} replay',
    replayProgress: 'Segment {current}/{total}',
    actionSegment: 'Action',
    playerSpeechSegment: 'Player speech',
    npcSpeechSegment: 'NPC speech',
    pauseReplay: 'Pause',
    continueReplay: 'Continue',
    restartReplay: 'Replay again',
    exportReplay: 'Export full replay',
    replayQueued: 'Queued',
    replayExporting: 'Exporting',
    playFullReplay: 'Play full replay',
    retryReplayExport: 'Retry export',
    downloadReplay: 'Download MP4',
    downloadSegment: 'Download this segment',
    downloadTurn: 'Download full turn',
    turnExporting: 'Preparing turn video',
    turnExportError: 'Turn video export failed.',
    segmentExporting: 'Preparing segment',
    segmentExportError: 'Segment export failed.',
    fullReplayTitle: '{title} · Full replay',
    replayExportError: 'Full replay export failed.',
    videoLoadError: 'The video could not be loaded.',
    tools: 'Search, filter, and sort season storylines',
    search: 'Search',
    searchPlaceholder: 'Search episodes, endings, or recent actions',
    filter: 'Status',
    filterAll: 'All',
    filterInProgress: 'In progress',
    filterCompleted: 'Complete',
    sort: 'Sort',
    sortRecent: 'Recently played',
    sortSeasonDesc: 'Newest season first',
    sortSeasonAsc: 'Oldest season first',
    resultCount: 'Seasons: {count}',
    noResults: 'No season storylines match your filters.',
    canvasHint: 'Drag or scroll horizontally to explore more episodes',
    canvasLabel: 'Scrollable season storyline',
    storySwitch: 'Story',
  },
  storyTreeCurrentSeasonHint: 'Latest progress',
  storyTreeViewDetails: 'View Season {number} details',
  storyTreeDelete: { action: 'Delete branch', confirm: 'Delete Season {number} and all branches created from it? This cannot be undone.', deleting: 'Deleting…', error: 'Could not delete this branch.' },
  storyTreeVideoUnavailable: 'Video unavailable',
  storyTreeFork: { action: 'Play again from here', confirm: 'A new story branch will be created from turn {turn}. Your current story history will not be changed.', failed: 'Failed to create the new story branch. Please try again.', creating: 'Creating…' },
  runVideoGeneration: { title: 'Generate full video', description: 'Generate a full video from this run. Points are charged after confirmation and refunded if generation fails.', generate: 'Generate video', regenerate: 'Generate again', retry: 'Try again', queued: 'Queued', running: 'Generating', price: 'Points required', balance: 'Current balance', balanceAfter: 'Balance after charge' },
  landingShowcase: { title: 'Lives Unfold', body: 'Witness the paths chosen by other players, then enter the story and write your own ending.' },
  landingMetrics: { playCount: 'Plays', likeCount: 'Likes' },
  landingCard: { level: 'Episode', searchPlaceholder: 'Find a story, video, or player', scriptFilter: 'Drama', scriptAll: 'All drama', playerVideoCount: 'Public videos: {count}', publishedAt: 'Published', publishedAll: 'Any time', publishedToday: 'Today', publishedWeek: 'Past 7 days', publishedMonth: 'Past 30 days', expandSynopsis: 'Show more', collapseSynopsis: 'Show less' },
  playerPage: { loadError: 'Could not load this player\'s videos.', notFound: 'Player not found or has no public videos.', searchPlaceholder: 'Search dramas, levels, or season titles' },
  season: { choose: 'Choose a season', chooseBody: 'This episode will continue the story in the selected season.', number: 'Season {number}', empty: 'Complete the previous episode in a season before continuing.' },
  status: { loading: 'Loading…', empty: 'Nothing here yet', error: 'Could not load data', streaming: 'In progress' },
  storiesPage: { tags: 'Story tags', allTags: 'All', swipeForNextStory: 'Swipe up for the next story' },
  creator: { nav: 'Create', queued: 'Queued for review', attachmentTypeError: 'Upload one TXT, PDF, Word, PNG, JPG, or WEBP file.', attachmentSizeError: 'The attachment must be no larger than 10 MB.', attachmentReadError: 'The attachment could not be read.', generationFailed: 'Generation failed', reviewRejected: 'Review rejected', published: 'Published', archived: 'Archived', composerTitle: 'What kind of world will you create today?', submitIdea: 'Submit idea', examplesNote: 'Real published games shown only as layout references.', exampleStatus: 'Published example', projectsEyebrow: 'Visual Game Creator', projectsTitle: 'My games', projectsBody: 'Manage your games and follow their generation and review status.', newGame: 'Create new game', draftStatus: 'Draft', continueEditing: 'Continue editing', generatedScopes: 'generation items', emptyTitle: 'Create your first game', emptyBody: 'Start with an idea, script, or world setting. The creator system will turn it into a playable game.', eyebrow: 'Visual Game Creator', title: 'Turn your story into a playable world', subtitle: 'Share an idea, script, or world setting. The creator system will design the game, produce its media, validate every route, and send it for review.', stepIdea: 'Idea', stepGenerate: 'Generate', stepReview: 'Review', ideaTitle: 'Describe your game', ideaBody: 'Write naturally. You do not need to understand game schemas or state machines.', ideaLabel: 'Idea or script', ideaHint: 'Include the world, characters, conflict, and the experience you want.', ideaPlaceholder: 'A traveler wakes in an inn where every guest remembers a different version of last night…', ideaError: 'Please enter at least 20 characters.', languageLabel: 'Game language', languages: { zh: 'Chinese', en: 'English', ja: 'Japanese', ko: 'Korean' }, scopeTitle: 'Generate this time', scopeStory: 'Story & levels', scopeStoryBody: 'Characters, quests, states, and endings', scopeVisuals: 'Visual media', scopeVisualsBody: 'Characters, scenes, props, and videos', scopeVoice: 'Voice', scopeVoiceBody: 'Player, NPC, and ending narration', scopeError: 'Select at least one generation item.', assetsTitle: 'Reference material', assetsBody: 'Optional references help keep characters and worlds consistent.', uploadAction: 'Add reference files', uploadHint: 'One file · TXT, PDF, Word, PNG, JPG or WEBP · up to 10 MB', supportedTitle: 'Supported material', supportedBody: 'Character, scene, prop, and style images; scripts and world documents; video or audio references.', removeFile: 'Remove {name}', saved: 'Draft saved on this device.', draftNote: 'This first version saves a local draft. Generation will be connected next.', save: 'Save draft' },
  labels: { scenario: 'Scenario', asset: 'Asset', mode: 'Mode', phase: 'Phase', location: 'Location', noRun: 'Not started', none: 'None' },
}