koin.js 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import React5, { memo, useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
- import { Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Power, Square, Keyboard, X, Palette, ChevronUp, Gauge, VolumeX, Volume1, Volume2, Loader2, Trophy, AlertTriangle, List, Settings, PauseCircle, Check, Clock, User, Copy, Lock, Zap, HardDrive, Trash2, Cpu, AlertCircle, FileCode, ExternalLink, EyeOff, Eye, Shield, CheckCircle, LogOut, Info, XCircle } from 'lucide-react';
1
+ import React2, { memo, createContext, useState, useRef, useEffect, useMemo, useCallback, useContext } from 'react';
2
+ import { Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, X, Palette, ChevronUp, Gauge, VolumeX, Volume1, Volume2, Loader2, Trophy, AlertTriangle, List, PauseCircle, Check, Clock, User, Copy, Lock, Zap, HardDrive, Trash2, Cpu, AlertCircle, FileCode, Globe, ExternalLink, EyeOff, Eye, Shield, CheckCircle, LogOut, Info, XCircle } from 'lucide-react';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { Nostalgist } from 'nostalgist';
5
5
 
@@ -33,20 +33,23 @@ var ControlButton = memo(function ControlButton2({
33
33
  onTouchEnd,
34
34
  disabled,
35
35
  className: `
36
- group relative flex flex-col items-center gap-1 px-2 sm:px-3 py-2 rounded-lg
37
- transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed
38
- select-none flex-shrink-0
39
- ${active ? "" : danger ? "hover:bg-red-500/20 text-gray-400 hover:text-red-400" : "hover:bg-white/10 text-gray-400 hover:text-white"}
40
- ${className}
41
- `,
36
+ flex flex-col items-center justify-center gap-1.5
37
+ px-3 py-2 rounded-lg
38
+ transition-all duration-200
39
+ disabled:opacity-50 disabled:cursor-not-allowed
40
+ hover:bg-white/10 active:bg-white/20
41
+ ${active ? "bg-white/10 ring-1 ring-inset" : ""}
42
+ ${danger ? "hover:bg-red-500/20 text-red-400" : "text-gray-400 hover:text-white"}
43
+ ${className}
44
+ `,
42
45
  style: active ? {
43
46
  backgroundColor: `${systemColor}20`,
44
47
  color: systemColor
45
48
  } : {},
46
49
  title: label,
47
50
  children: [
48
- /* @__PURE__ */ jsx(Icon, { size: iconSize, className: "transition-transform group-hover:scale-110" }),
49
- /* @__PURE__ */ jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider opacity-70", children: label })
51
+ /* @__PURE__ */ jsx(Icon, { size: iconSize, className: `transition-transform duration-200 ${active ? "scale-110" : "group-hover:scale-110"}`, style: active ? { color: systemColor } : void 0 }),
52
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider whitespace-nowrap", style: { color: active ? systemColor : void 0 }, children: label })
50
53
  ]
51
54
  }
52
55
  );
@@ -54,10 +57,21 @@ var ControlButton = memo(function ControlButton2({
54
57
  var SPEED_OPTIONS = [1, 2];
55
58
  function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
56
59
  const [showMenu, setShowMenu] = useState(false);
60
+ const buttonRef = React2.useRef(null);
61
+ const getMenuPosition = () => {
62
+ if (!buttonRef.current) return {};
63
+ const rect = buttonRef.current.getBoundingClientRect();
64
+ return {
65
+ bottom: `${window.innerHeight - rect.top + 8}px`,
66
+ left: `${rect.left + rect.width / 2}px`,
67
+ transform: "translateX(-50%)"
68
+ };
69
+ };
57
70
  return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
58
71
  /* @__PURE__ */ jsxs(
59
72
  "button",
60
73
  {
74
+ ref: buttonRef,
61
75
  onClick: () => setShowMenu(!showMenu),
62
76
  disabled,
63
77
  className: `
@@ -84,25 +98,32 @@ function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
84
98
  }
85
99
  ),
86
100
  showMenu && /* @__PURE__ */ jsxs(Fragment, { children: [
87
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-40", onClick: () => setShowMenu(false) }),
88
- /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 bg-black/90 backdrop-blur-md border border-white/20 rounded-lg p-1.5 shadow-xl z-50 flex flex-col gap-1 min-w-[80px]", children: SPEED_OPTIONS.map((s) => /* @__PURE__ */ jsxs(
89
- "button",
101
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setShowMenu(false) }),
102
+ /* @__PURE__ */ jsx(
103
+ "div",
90
104
  {
91
- onClick: () => {
92
- onSpeedChange(s);
93
- setShowMenu(false);
94
- },
95
- className: `
105
+ className: "fixed z-[9999] bg-black/90 backdrop-blur-md border border-white/20 rounded-lg p-1.5 shadow-xl flex flex-col gap-1 min-w-[80px]",
106
+ style: getMenuPosition(),
107
+ children: SPEED_OPTIONS.map((s) => /* @__PURE__ */ jsxs(
108
+ "button",
109
+ {
110
+ onClick: () => {
111
+ onSpeedChange(s);
112
+ setShowMenu(false);
113
+ },
114
+ className: `
96
115
  px-3 py-2 rounded text-xs font-mono font-bold text-center transition-colors
97
116
  ${speed === s ? "bg-white/20 text-white" : "hover:bg-white/10 text-gray-400 hover:text-white"}
98
117
  `,
99
- children: [
100
- s,
101
- "x"
102
- ]
103
- },
104
- s
105
- )) })
118
+ children: [
119
+ s,
120
+ "x"
121
+ ]
122
+ },
123
+ s
124
+ ))
125
+ }
126
+ )
106
127
  ] })
107
128
  ] });
108
129
  }
@@ -171,6 +192,260 @@ function HardcoreTooltip({ show, message = "Disabled in Hardcore mode", children
171
192
  /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-amber-500/90 text-black text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50", children: message })
172
193
  ] });
173
194
  }
195
+
196
+ // src/locales/en.ts
197
+ var en = {
198
+ controls: {
199
+ play: "Play",
200
+ pause: "Pause",
201
+ reset: "Reset",
202
+ rewind: "Rewind",
203
+ save: "Save",
204
+ load: "Load",
205
+ snap: "Snap",
206
+ rec: "Rec",
207
+ stopRec: "Stop",
208
+ startRecord: "Start recording",
209
+ stopRecord: "Stop recording",
210
+ mute: "Mute",
211
+ unmute: "Unmute",
212
+ help: "Help",
213
+ full: "Full",
214
+ keys: "Keys",
215
+ menuOpen: "Open menu",
216
+ menuClose: "Close menu",
217
+ gamepadConnected: "{{count}} controller{{plural}} connected - click to configure",
218
+ noGamepad: "No controller detected - press any button on your gamepad to connect",
219
+ press: "Press",
220
+ startBtn: "START",
221
+ selectBtn: "SEL"
222
+ },
223
+ common: {
224
+ disabledInHardcore: "Disabled in Hardcore mode",
225
+ notSupported: "Not supported on this console",
226
+ playToEnableRewind: "Play for a few seconds to enable rewind"
227
+ },
228
+ settings: {
229
+ title: "Settings",
230
+ general: "General",
231
+ audio: "Audio",
232
+ video: "Video",
233
+ input: "Input",
234
+ advanced: "Advanced",
235
+ fullscreen: "Fullscreen",
236
+ controls: "Controls",
237
+ gamepad: "Gamepad",
238
+ cheats: "Cheats",
239
+ retroAchievements: "RetroAchievements",
240
+ shortcuts: "Shortcuts",
241
+ exit: "Exit",
242
+ language: "Language",
243
+ selectLanguage: "Select Language"
244
+ },
245
+ overlay: {
246
+ play: "PLAY",
247
+ systemFirmware: "SYSTEM FIRMWARE",
248
+ loading: "Loading {{system}}",
249
+ initializing: "Initializing emulator",
250
+ loadingSave: "Loading Save",
251
+ preparingSlot: "Preparing slot {{num}}",
252
+ systemError: "System Error",
253
+ failedInit: "Failed to initialize emulator",
254
+ retry: "Retry",
255
+ slotReady: "Slot {{num}} ready",
256
+ paused: "Paused"
257
+ },
258
+ notifications: {
259
+ saved: "State saved",
260
+ loaded: "State loaded",
261
+ error: "Error",
262
+ recordingStarted: "Recording started",
263
+ recordingSaved: "Recording saved",
264
+ downloaded: "State downloaded",
265
+ loadedFile: "State loaded from file",
266
+ savedSlot: "Saved to slot {{num}}",
267
+ loadedSlot: "Loaded from slot {{num}}",
268
+ deletedSlot: "Deleted slot {{num}}",
269
+ emptySlot: "Empty slot",
270
+ noSaveFound: "No save found",
271
+ failedSave: "Failed to save",
272
+ failedLoad: "Failed to load",
273
+ failedDelete: "Failed to delete",
274
+ failedFetch: "Failed to load save slots",
275
+ controllerConnected: "Controller Connected",
276
+ controllerDisconnected: "Controller Lost",
277
+ controllerReady: "Controller ready to use",
278
+ insertCoin: "Press SHIFT to insert coin",
279
+ insertCoinTitle: "\u{1FA99} Insert Coin",
280
+ controlsSaved: "Controls saved",
281
+ controlsReset: "Controls reset to defaults"
282
+ },
283
+ modals: {
284
+ shortcuts: {
285
+ title: "Keyboard Shortcuts",
286
+ playerShortcuts: "Player Shortcuts",
287
+ overlays: "Overlays",
288
+ recording: "Recording",
289
+ showHelp: "Show Help",
290
+ perfOverlay: "Performance Overlay",
291
+ inputDisplay: "Input Display",
292
+ toggleRec: "Toggle Recording",
293
+ toggleMute: "Toggle Mute",
294
+ pressEsc: "Press ESC to close"
295
+ },
296
+ controls: {
297
+ title: "Controls",
298
+ keyboard: "Keyboard Mapping",
299
+ description: "Click a button and press a key to remap",
300
+ pressKey: "Press...",
301
+ reset: "Reset to Default",
302
+ save: "Save Controls"
303
+ },
304
+ gamepad: {
305
+ title: "Gamepad Settings",
306
+ noGamepad: "No gamepad detected",
307
+ connected: "{{count}} controller{{s}} connected",
308
+ none: "No controllers detected",
309
+ player: "Player:",
310
+ noController: "No controller detected",
311
+ pressAny: "Press any button on your gamepad to connect",
312
+ waiting: "Waiting for input...",
313
+ pressButton: "Press a button on your controller for {{button}}",
314
+ pressEsc: "Press Escape to cancel",
315
+ reset: "Reset to Default",
316
+ save: "Save Settings"
317
+ },
318
+ cheats: {
319
+ title: "Cheats",
320
+ addCheat: "Add Cheat",
321
+ available: "{{count}} cheat{{s}} available",
322
+ emptyTitle: "No cheats available",
323
+ emptyDesc: "No cheat codes found for this game",
324
+ copy: "Copy code",
325
+ active: "{{count}} cheat{{s}} active",
326
+ toggleHint: "Click a cheat to toggle it on/off"
327
+ },
328
+ saveSlots: {
329
+ title: "Save States",
330
+ saveTitle: "Save Game",
331
+ loadTitle: "Load Game",
332
+ emptySlot: "Empty Slot",
333
+ subtitleSave: "Choose a slot to save your progress",
334
+ subtitleLoad: "Select a save to restore",
335
+ loading: "Loading saves...",
336
+ locked: "Slot {{num}} Locked",
337
+ upgrade: "Upgrade to unlock more save slots",
338
+ autoSave: "Auto-Save Slot",
339
+ autoSaveDesc: "Reserved for automatic saves",
340
+ noData: "No save data",
341
+ slot: "Slot {{num}}",
342
+ footerSave: "Saves are stored in the cloud and sync across devices",
343
+ footerLoad: "Your progress will be restored to the selected save point"
344
+ },
345
+ bios: {
346
+ title: "BIOS Selection",
347
+ description: "Select a BIOS file to use for this game",
348
+ warningTitle: "Note:",
349
+ warning: "Changing BIOS requires the emulator to restart. Your unsaved progress will be lost.",
350
+ systemDefault: "System Default",
351
+ active: "Active",
352
+ defaultDesc: "Use the emulator's built-in or default BIOS",
353
+ emptyTitle: "No BIOS files found for this console.",
354
+ emptyDesc: "Upload BIOS files in your Dashboard Library.",
355
+ footer: "System Firmware Settings"
356
+ }
357
+ },
358
+ retroAchievements: {
359
+ title: "RetroAchievements",
360
+ login: "Login",
361
+ logout: "Logout",
362
+ username: "Username",
363
+ password: "Password",
364
+ hardcore: "Hardcore Mode",
365
+ achievements: "Achievements",
366
+ locked: "Locked",
367
+ unlocked: "Unlocked",
368
+ mastered: "Mastered",
369
+ identifying: "Identifying game...",
370
+ achievementsAvailable: "{{count}} achievements available",
371
+ gameNotSupported: "Connected - Game not in RA database",
372
+ connect: "Connect RetroAchievements",
373
+ connected: "RetroAchievements (connected)",
374
+ createAccount: "Create an account",
375
+ privacy: "Privacy:",
376
+ privacyText: "Your password is only used to authenticate with RetroAchievements. It is never stored.",
377
+ connecting: "Connecting...",
378
+ connectAccount: "Connect your account to track achievements",
379
+ poweredBy: "Powered by",
380
+ connectedStatus: "Connected",
381
+ yourUsername: "Your RA username",
382
+ yourPassword: "Your RA password",
383
+ usernameRequired: "Username and Password are required",
384
+ noGame: "No game loaded",
385
+ loadGame: "Load a game to see achievements",
386
+ noAchievements: "No achievements found",
387
+ notSupported: "This game may not be supported",
388
+ ptsRemaining: "{{count}} pts remaining",
389
+ filters: {
390
+ all: "All",
391
+ locked: "Locked",
392
+ unlocked: "Unlocked"
393
+ }
394
+ },
395
+ overlays: {
396
+ performance: {
397
+ title: "Stats",
398
+ fps: "FPS",
399
+ frameTime: "FT",
400
+ memory: "MEM",
401
+ core: "Core",
402
+ input: "INPUT",
403
+ active: "ACTIVE"
404
+ },
405
+ toast: {
406
+ saved: "Game Saved",
407
+ loaded: "Game Loaded",
408
+ error: "Error"
409
+ },
410
+ recording: {
411
+ started: "Recording Started",
412
+ stopped: "Recording Stopped",
413
+ saved: "Recording Saved",
414
+ paused: "PAUSED",
415
+ recording: "RECORDING",
416
+ resume: "Resume recording",
417
+ pause: "Pause recording",
418
+ stop: "Stop and save recording",
419
+ hover: "Hover for controls"
420
+ }
421
+ }
422
+ };
423
+ var KoinI18nContext = createContext(en);
424
+ function deepMerge(target, source) {
425
+ const result = { ...target };
426
+ for (const key in source) {
427
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
428
+ const sourceValue = source[key];
429
+ const targetValue = result[key];
430
+ if (sourceValue && typeof sourceValue === "object" && targetValue && typeof targetValue === "object" && !Array.isArray(sourceValue)) {
431
+ result[key] = deepMerge(targetValue, sourceValue);
432
+ } else if (sourceValue !== void 0) {
433
+ result[key] = sourceValue;
434
+ }
435
+ }
436
+ }
437
+ return result;
438
+ }
439
+ var KoinI18nProvider = ({ children, translations }) => {
440
+ const value = useMemo(() => {
441
+ if (!translations) return en;
442
+ return deepMerge(en, translations);
443
+ }, [translations]);
444
+ return /* @__PURE__ */ jsx(KoinI18nContext.Provider, { value, children });
445
+ };
446
+ function useKoinTranslation() {
447
+ return useContext(KoinI18nContext);
448
+ }
174
449
  var PlaybackControls = memo(function PlaybackControls2({
175
450
  isPaused,
176
451
  isRunning,
@@ -190,20 +465,30 @@ var PlaybackControls = memo(function PlaybackControls2({
190
465
  systemColor = "#00FF41",
191
466
  hardcoreRestrictions
192
467
  }) {
468
+ const t = useKoinTranslation();
193
469
  const hasRewindHistory = rewindBufferSize > 0;
194
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [
470
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4 w-full sm:w-auto sm:flex-nowrap sm:gap-3 flex-shrink-0", children: [
195
471
  /* @__PURE__ */ jsx(
196
472
  ControlButton,
197
473
  {
198
474
  onClick: onPauseToggle,
199
475
  icon: !isRunning || isPaused ? Play : Pause,
200
- label: !isRunning || isPaused ? "Play" : "Pause",
476
+ label: !isRunning || isPaused ? t.controls.play : t.controls.pause,
201
477
  active: isPaused,
202
478
  disabled,
203
479
  systemColor
204
480
  }
205
481
  ),
206
- /* @__PURE__ */ jsx(ControlButton, { onClick: onRestart, icon: RotateCcw, label: "Reset", disabled, systemColor }),
482
+ /* @__PURE__ */ jsx(
483
+ ControlButton,
484
+ {
485
+ onClick: onRestart,
486
+ icon: RotateCcw,
487
+ label: t.controls.reset,
488
+ disabled,
489
+ systemColor
490
+ }
491
+ ),
207
492
  /* @__PURE__ */ jsx(SpeedMenu, { speed, onSpeedChange, disabled }),
208
493
  /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
209
494
  /* @__PURE__ */ jsx(
@@ -215,7 +500,7 @@ var PlaybackControls = memo(function PlaybackControls2({
215
500
  onTouchStart: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
216
501
  onTouchEnd: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
217
502
  icon: Rewind,
218
- label: "Rewind",
503
+ label: t.controls.rewind,
219
504
  active: isRewinding,
220
505
  disabled: disabled || !hasRewindHistory || hardcoreRestrictions?.canUseRewind === false,
221
506
  systemColor
@@ -226,10 +511,10 @@ var PlaybackControls = memo(function PlaybackControls2({
226
511
  HardcoreTooltip,
227
512
  {
228
513
  show: hardcoreRestrictions?.canUseRewind === false,
229
- message: hardcoreRestrictions?.isHardcore ? "Disabled in Hardcore mode" : "Not supported on this console"
514
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
230
515
  }
231
516
  ),
232
- hardcoreRestrictions?.canUseRewind !== false && !hasRewindHistory && /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-black/90 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50", children: "Play for a few seconds to enable rewind" })
517
+ hardcoreRestrictions?.canUseRewind !== false && !hasRewindHistory && /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-black/90 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50", children: t.common.playToEnableRewind })
233
518
  ] }),
234
519
  /* @__PURE__ */ jsx("div", { className: "w-px h-8 bg-white/10 mx-1" }),
235
520
  /* @__PURE__ */ jsx(
@@ -432,7 +717,8 @@ var SaveLoadControls = memo(function SaveLoadControls2({
432
717
  autoSavePaused = false,
433
718
  onAutoSaveToggle
434
719
  }) {
435
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 sm:px-4 border-x border-white/10 flex-shrink-0", children: [
720
+ const t = useKoinTranslation();
721
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4 w-full sm:w-auto sm:flex-nowrap sm:gap-3 px-3 sm:px-4 sm:border-x sm:border-white/10 flex-shrink-0", children: [
436
722
  autoSaveEnabled && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
437
723
  AutoSaveIndicator,
438
724
  {
@@ -449,12 +735,18 @@ var SaveLoadControls = memo(function SaveLoadControls2({
449
735
  {
450
736
  onClick: hardcoreRestrictions?.canUseSaveStates === false ? void 0 : onSave,
451
737
  icon: Save,
452
- label: "Save",
738
+ label: t.controls.save,
453
739
  disabled: disabled || saveDisabled || hardcoreRestrictions?.canUseSaveStates === false,
454
740
  systemColor
455
741
  }
456
742
  ),
457
- /* @__PURE__ */ jsx(HardcoreTooltip, { show: hardcoreRestrictions?.canUseSaveStates === false })
743
+ /* @__PURE__ */ jsx(
744
+ HardcoreTooltip,
745
+ {
746
+ show: hardcoreRestrictions?.canUseSaveStates === false,
747
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
748
+ }
749
+ )
458
750
  ] }),
459
751
  /* @__PURE__ */ jsxs("div", { className: "relative group flex-shrink-0", children: [
460
752
  /* @__PURE__ */ jsx(
@@ -462,19 +754,25 @@ var SaveLoadControls = memo(function SaveLoadControls2({
462
754
  {
463
755
  onClick: hardcoreRestrictions?.canUseSaveStates === false ? void 0 : onLoad,
464
756
  icon: Download,
465
- label: "Load",
757
+ label: t.controls.load,
466
758
  disabled: disabled || loadDisabled || hardcoreRestrictions?.canUseSaveStates === false,
467
759
  systemColor
468
760
  }
469
761
  ),
470
- /* @__PURE__ */ jsx(HardcoreTooltip, { show: hardcoreRestrictions?.canUseSaveStates === false })
762
+ /* @__PURE__ */ jsx(
763
+ HardcoreTooltip,
764
+ {
765
+ show: hardcoreRestrictions?.canUseSaveStates === false,
766
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
767
+ }
768
+ )
471
769
  ] }),
472
770
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
473
771
  ControlButton,
474
772
  {
475
773
  onClick: onScreenshot,
476
774
  icon: Camera,
477
- label: "Snap",
775
+ label: t.controls.snap,
478
776
  disabled: disabled || saveDisabled,
479
777
  systemColor
480
778
  }
@@ -485,7 +783,7 @@ var SaveLoadControls = memo(function SaveLoadControls2({
485
783
  onClick: onRecordToggle,
486
784
  disabled,
487
785
  className: `flex flex-col items-center gap-1 px-2 sm:px-3 py-2 rounded-lg transition-all duration-200 hover:bg-white/10 ${disabled ? "opacity-50 cursor-not-allowed" : ""}`,
488
- title: isRecording ? "Stop recording" : "Start recording",
786
+ title: isRecording ? t.controls.stopRecord : t.controls.startRecord,
489
787
  children: [
490
788
  /* @__PURE__ */ jsx("div", { className: "relative", children: isRecording ? /* @__PURE__ */ jsxs(Fragment, { children: [
491
789
  /* @__PURE__ */ jsx(Video, { size: 20, className: "text-red-500" }),
@@ -496,7 +794,7 @@ var SaveLoadControls = memo(function SaveLoadControls2({
496
794
  {
497
795
  className: "text-[10px] font-bold uppercase tracking-wider",
498
796
  style: { color: isRecording ? "#FF3333" : void 0 },
499
- children: isRecording ? "REC" : "Rec"
797
+ children: t.controls.rec
500
798
  }
501
799
  )
502
800
  ]
@@ -629,6 +927,9 @@ function RAButton({
629
927
  achievementCount,
630
928
  className = ""
631
929
  }) {
930
+ const t = useKoinTranslation();
931
+ const title = isGameFound ? `${t.retroAchievements.title} (${achievementCount} achievements)` : isConnected ? t.retroAchievements.connected : t.retroAchievements.title;
932
+ const tooltip = isIdentifying ? t.retroAchievements.identifying : isGameFound ? t.retroAchievements.achievementsAvailable.replace("{{count}}", achievementCount.toString()) : isConnected ? t.retroAchievements.gameNotSupported : t.retroAchievements.connect;
632
933
  return /* @__PURE__ */ jsxs("div", { className: `relative group ${className}`, children: [
633
934
  /* @__PURE__ */ jsxs(
634
935
  "button",
@@ -641,7 +942,7 @@ function RAButton({
641
942
  select-none
642
943
  ${isGameFound ? "bg-gradient-to-b from-yellow-500/30 to-orange-500/20 text-yellow-400 ring-1 ring-yellow-500/50 shadow-[0_0_12px_rgba(234,179,8,0.3)]" : isConnected ? "bg-yellow-500/10 text-yellow-400/70 ring-1 ring-yellow-500/30" : "hover:bg-white/10 text-gray-400 hover:text-white"}
643
944
  `,
644
- title: isGameFound ? `RetroAchievements (${achievementCount} achievements)` : isConnected ? "RetroAchievements (connected)" : "RetroAchievements",
945
+ title,
645
946
  children: [
646
947
  isIdentifying ? /* @__PURE__ */ jsx(Loader2, { size: 20, className: "animate-spin text-yellow-400" }) : /* @__PURE__ */ jsx(
647
948
  Trophy,
@@ -662,13 +963,14 @@ function RAButton({
662
963
  absolute -top-1 -right-1 w-3 h-3 rounded-full border-2 border-black
663
964
  ${isGameFound ? "bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.8)]" : "bg-yellow-500 shadow-[0_0_6px_rgba(234,179,8,0.6)]"}
664
965
  `, children: isGameFound && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-green-400 rounded-full animate-ping opacity-50" }) }),
665
- /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900/95 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 border border-white/10", children: isIdentifying ? "Identifying game..." : isGameFound ? `${achievementCount} achievements available` : isConnected ? "Connected - Game not in RA database" : "Connect RetroAchievements" })
966
+ /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900/95 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 border border-white/10", children: tooltip })
666
967
  ] });
667
968
  }
668
969
  var SettingsControls = memo(function SettingsControls2({
669
970
  onFullscreen,
670
971
  onControls,
671
972
  onGamepadSettings,
973
+ onSettings,
672
974
  onCheats,
673
975
  onRetroAchievements,
674
976
  onShowShortcuts,
@@ -685,8 +987,10 @@ var SettingsControls = memo(function SettingsControls2({
685
987
  raAchievementCount = 0,
686
988
  raIsIdentifying = false
687
989
  }) {
990
+ const t = useKoinTranslation();
688
991
  const gamepadIndicatorText = gamepadCount > 0 ? Array.from({ length: gamepadCount }, (_, i) => `P${i + 1}`).join(" ") : "";
689
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [
992
+ const gamepadConnectedTitle = t.controls.gamepadConnected.replace("{{count}}", gamepadCount.toString()).replace("{{plural}}", gamepadCount > 1 ? "s" : "");
993
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4 w-full sm:w-auto sm:flex-nowrap sm:gap-3 flex-shrink-0", children: [
690
994
  /* @__PURE__ */ jsx(
691
995
  ShaderDropdown_default,
692
996
  {
@@ -697,15 +1001,15 @@ var SettingsControls = memo(function SettingsControls2({
697
1001
  disabled
698
1002
  }
699
1003
  ),
700
- onShowShortcuts && /* @__PURE__ */ jsx(ControlButton, { onClick: onShowShortcuts, icon: HelpCircle, label: "Help", disabled, className: "hidden sm:flex", systemColor }),
701
- /* @__PURE__ */ jsx(ControlButton, { onClick: onFullscreen, icon: Maximize, label: "Full", disabled, className: "hidden sm:flex", systemColor }),
702
- /* @__PURE__ */ jsx(ControlButton, { onClick: onControls, icon: Gamepad2, label: "Keys", disabled, className: "hidden sm:flex", systemColor }),
1004
+ onShowShortcuts && /* @__PURE__ */ jsx(ControlButton, { onClick: onShowShortcuts, icon: HelpCircle, label: t.controls.help, disabled, className: "hidden sm:flex", systemColor }),
1005
+ /* @__PURE__ */ jsx(ControlButton, { onClick: onFullscreen, icon: Maximize, label: t.controls.full, disabled, className: "hidden sm:flex", systemColor }),
1006
+ /* @__PURE__ */ jsx(ControlButton, { onClick: onControls, icon: Gamepad2, label: t.controls.keys, disabled, className: "hidden sm:flex", systemColor }),
703
1007
  gamepadCount > 0 ? /* @__PURE__ */ jsxs(
704
1008
  "button",
705
1009
  {
706
1010
  onClick: onGamepadSettings,
707
1011
  className: "relative group flex flex-col items-center gap-1 px-2 sm:px-3 py-2 rounded-lg transition-all duration-200 hover:bg-white/10 flex-shrink-0 hidden sm:flex",
708
- title: `${gamepadCount} controller${gamepadCount > 1 ? "s" : ""} connected - click to configure`,
1012
+ title: gamepadConnectedTitle,
709
1013
  children: [
710
1014
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
711
1015
  /* @__PURE__ */ jsx(Joystick, { size: 20, style: { color: systemColor }, className: "transition-transform group-hover:scale-110" }),
@@ -726,15 +1030,15 @@ var SettingsControls = memo(function SettingsControls2({
726
1030
  "button",
727
1031
  {
728
1032
  onClick: onGamepadSettings,
729
- className: "relative group flex-col items-center gap-1 px-3 py-2 transition-all duration-200 flex-shrink-0 hidden sm:flex",
730
- title: "No controller detected - press any button on your gamepad to connect",
1033
+ className: "relative group flex-col items-center gap-1 px-3 py-2 transition-all duration-200 flex-shrink-0",
1034
+ title: t.controls.noGamepad,
731
1035
  style: {
732
1036
  border: "2px dashed #6b7280",
733
1037
  backgroundColor: "transparent"
734
1038
  },
735
1039
  children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-2", children: [
736
1040
  /* @__PURE__ */ jsx(Gamepad2, { size: 18, className: "text-gray-400 transition-transform group-hover:scale-110 group-hover:text-white" }),
737
- /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold uppercase tracking-wider text-gray-400 group-hover:text-white whitespace-nowrap", children: "Press" }),
1041
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold uppercase tracking-wider text-gray-400 group-hover:text-white whitespace-nowrap", children: t.controls.press }),
738
1042
  /* @__PURE__ */ jsx("div", { className: "w-5 h-5 rounded-full border-2 border-gray-400 group-hover:border-white flex items-center justify-center animate-pulse", children: /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-gray-400 group-hover:bg-white" }) })
739
1043
  ] })
740
1044
  }
@@ -746,12 +1050,18 @@ var SettingsControls = memo(function SettingsControls2({
746
1050
  {
747
1051
  onClick: hardcoreRestrictions?.canUseCheats === false ? void 0 : onCheats,
748
1052
  icon: Code,
749
- label: "Cheats",
1053
+ label: t.settings.cheats,
750
1054
  disabled: disabled || hardcoreRestrictions?.canUseCheats === false,
751
1055
  systemColor
752
1056
  }
753
1057
  ),
754
- /* @__PURE__ */ jsx(HardcoreTooltip, { show: hardcoreRestrictions?.canUseCheats === false })
1058
+ /* @__PURE__ */ jsx(
1059
+ HardcoreTooltip,
1060
+ {
1061
+ show: hardcoreRestrictions?.canUseCheats === false,
1062
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
1063
+ }
1064
+ )
755
1065
  ] }),
756
1066
  /* @__PURE__ */ jsx(
757
1067
  RAButton,
@@ -765,14 +1075,26 @@ var SettingsControls = memo(function SettingsControls2({
765
1075
  className: "hidden sm:flex"
766
1076
  }
767
1077
  ),
1078
+ onSettings && /* @__PURE__ */ jsx(
1079
+ ControlButton,
1080
+ {
1081
+ onClick: onSettings,
1082
+ icon: Settings,
1083
+ label: t.settings.title,
1084
+ disabled,
1085
+ systemColor,
1086
+ className: "hidden sm:flex"
1087
+ }
1088
+ ),
768
1089
  /* @__PURE__ */ jsx("div", { className: "w-px h-8 bg-white/10 mx-2 hidden sm:block" }),
769
- /* @__PURE__ */ jsx(ControlButton, { onClick: onExit, icon: Power, label: "Exit", danger: true, disabled, systemColor })
1090
+ /* @__PURE__ */ jsx(ControlButton, { onClick: onExit, icon: Power, label: t.settings.exit, danger: true, disabled, systemColor })
770
1091
  ] });
771
1092
  });
772
1093
  var MobileControlDrawer = memo(function MobileControlDrawer2({
773
1094
  children,
774
1095
  systemColor = "#00FF41"
775
1096
  }) {
1097
+ const t = useKoinTranslation();
776
1098
  const [isExpanded, setIsExpanded] = useState(false);
777
1099
  useEffect(() => {
778
1100
  if (isExpanded) {
@@ -807,7 +1129,7 @@ var MobileControlDrawer = memo(function MobileControlDrawer2({
807
1129
  boxShadow: isExpanded ? `0 0 20px ${systemColor}60, 0 4px 20px rgba(0,0,0,0.5)` : "0 4px 20px rgba(0,0,0,0.5)",
808
1130
  borderColor: isExpanded ? systemColor : void 0
809
1131
  },
810
- "aria-label": isExpanded ? "Close menu" : "Open menu",
1132
+ "aria-label": isExpanded ? t.controls.menuClose : t.controls.menuOpen,
811
1133
  children: /* @__PURE__ */ jsxs(
812
1134
  "svg",
813
1135
  {
@@ -848,7 +1170,7 @@ var MobileControlDrawer = memo(function MobileControlDrawer2({
848
1170
  /* @__PURE__ */ jsx(
849
1171
  "div",
850
1172
  {
851
- className: "relative flex flex-wrap items-center justify-center gap-3 px-4 py-6 pb-20",
1173
+ className: "relative flex flex-col items-center px-4 py-6 pb-20",
852
1174
  style: { paddingBottom: "calc(env(safe-area-inset-bottom, 20px) + 80px)" },
853
1175
  children
854
1176
  }
@@ -896,6 +1218,7 @@ var PlayerControls = memo(function PlayerControls2({
896
1218
  systemColor = "#00FF41",
897
1219
  gamepadCount = 0,
898
1220
  onGamepadSettings,
1221
+ onSettings,
899
1222
  volume = 100,
900
1223
  isMuted = false,
901
1224
  onVolumeChange,
@@ -928,6 +1251,7 @@ var PlayerControls = memo(function PlayerControls2({
928
1251
  hardcoreRestrictions
929
1252
  }
930
1253
  ),
1254
+ /* @__PURE__ */ jsx("div", { className: "w-full h-px bg-white/10 sm:hidden my-4" }),
931
1255
  /* @__PURE__ */ jsx(
932
1256
  SaveLoadControls,
933
1257
  {
@@ -948,12 +1272,14 @@ var PlayerControls = memo(function PlayerControls2({
948
1272
  onAutoSaveToggle
949
1273
  }
950
1274
  ),
1275
+ /* @__PURE__ */ jsx("div", { className: "w-full h-px bg-white/10 sm:hidden my-4" }),
951
1276
  /* @__PURE__ */ jsx(
952
1277
  SettingsControls,
953
1278
  {
954
1279
  onFullscreen,
955
1280
  onControls,
956
1281
  onGamepadSettings,
1282
+ onSettings,
957
1283
  onCheats,
958
1284
  onRetroAchievements,
959
1285
  onShowShortcuts,
@@ -974,7 +1300,7 @@ var PlayerControls = memo(function PlayerControls2({
974
1300
  ] });
975
1301
  return /* @__PURE__ */ jsxs(Fragment, { children: [
976
1302
  /* @__PURE__ */ jsx(MobileControlDrawer_default, { systemColor, children: controlsContent }),
977
- /* @__PURE__ */ jsx("div", { className: "hidden sm:flex w-full items-center justify-between gap-2 px-4 py-2 bg-black backdrop-blur-sm border-t border-white/10 shrink-0 overflow-x-auto", children: controlsContent })
1303
+ /* @__PURE__ */ jsx("div", { className: "hidden sm:flex w-full bg-black/90 backdrop-blur-md border-t border-white/10 shrink-0 z-50 overflow-x-auto no-scrollbar", children: /* @__PURE__ */ jsx("div", { className: "flex items-center min-w-max mx-auto gap-4 px-8 py-4", children: controlsContent }) })
978
1304
  ] });
979
1305
  });
980
1306
  var PlayerControls_default = PlayerControls;
@@ -1137,6 +1463,7 @@ var PerformanceOverlay = memo(function PerformanceOverlay2({
1137
1463
  coreName = "Unknown",
1138
1464
  systemColor = "#00FF41"
1139
1465
  }) {
1466
+ const t = useKoinTranslation();
1140
1467
  const [fps, setFps] = useState(0);
1141
1468
  const [frameTime, setFrameTime] = useState(0);
1142
1469
  const frameTimesRef = useRef([]);
@@ -1188,12 +1515,12 @@ var PerformanceOverlay = memo(function PerformanceOverlay2({
1188
1515
  },
1189
1516
  children: [
1190
1517
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1191
- /* @__PURE__ */ jsx("span", { className: "opacity-60", children: "FPS" }),
1518
+ /* @__PURE__ */ jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.fps }),
1192
1519
  /* @__PURE__ */ jsx("span", { className: "font-bold", children: fps })
1193
1520
  ] }),
1194
1521
  /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-white/20" }),
1195
1522
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1196
- /* @__PURE__ */ jsx("span", { className: "opacity-60", children: "Frame" }),
1523
+ /* @__PURE__ */ jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.frameTime }),
1197
1524
  /* @__PURE__ */ jsxs("span", { className: "font-bold", children: [
1198
1525
  frameTime,
1199
1526
  "ms"
@@ -1201,7 +1528,7 @@ var PerformanceOverlay = memo(function PerformanceOverlay2({
1201
1528
  ] }),
1202
1529
  /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-white/20" }),
1203
1530
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1204
- /* @__PURE__ */ jsx("span", { className: "opacity-60", children: "Core" }),
1531
+ /* @__PURE__ */ jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.core }),
1205
1532
  /* @__PURE__ */ jsx("span", { className: "font-bold text-white/80", children: coreName })
1206
1533
  ] })
1207
1534
  ]
@@ -1375,6 +1702,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1375
1702
  onStop,
1376
1703
  systemColor = "#FF3333"
1377
1704
  }) {
1705
+ const t = useKoinTranslation();
1378
1706
  const [isHovered, setIsHovered] = useState(false);
1379
1707
  if (!isRecording) return null;
1380
1708
  const minutes = Math.floor(duration / 60);
@@ -1422,7 +1750,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1422
1750
  {
1423
1751
  className: "text-xs font-bold",
1424
1752
  style: { color: isPaused ? "#FFA500" : "#FF3333" },
1425
- children: isPaused ? "PAUSED" : "RECORDING"
1753
+ children: isPaused ? t.overlays.recording.paused : t.overlays.recording.recording
1426
1754
  }
1427
1755
  ),
1428
1756
  /* @__PURE__ */ jsx("span", { className: "text-[10px] text-white/60", children: timeString })
@@ -1433,7 +1761,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1433
1761
  {
1434
1762
  onClick: onResume,
1435
1763
  className: "p-1.5 rounded hover:bg-white/20 transition-colors",
1436
- title: "Resume recording",
1764
+ title: t.overlays.recording.resume,
1437
1765
  children: /* @__PURE__ */ jsx(Play, { size: 14, className: "text-orange-400", fill: "#FFA500" })
1438
1766
  }
1439
1767
  ) : /* @__PURE__ */ jsx(
@@ -1441,7 +1769,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1441
1769
  {
1442
1770
  onClick: onPause,
1443
1771
  className: "p-1.5 rounded hover:bg-white/20 transition-colors",
1444
- title: "Pause recording",
1772
+ title: t.overlays.recording.pause,
1445
1773
  children: /* @__PURE__ */ jsx(Pause, { size: 14, className: "text-white/80" })
1446
1774
  }
1447
1775
  ),
@@ -1450,7 +1778,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1450
1778
  {
1451
1779
  onClick: onStop,
1452
1780
  className: "p-1.5 rounded hover:bg-red-500/30 transition-colors flex items-center gap-1",
1453
- title: "Stop and save recording",
1781
+ title: t.overlays.recording.stop,
1454
1782
  children: [
1455
1783
  /* @__PURE__ */ jsx(Square, { size: 12, fill: "#FF3333", className: "text-red-500" }),
1456
1784
  /* @__PURE__ */ jsx(Download, { size: 12, className: "text-white/60" })
@@ -1461,39 +1789,40 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1461
1789
  ]
1462
1790
  }
1463
1791
  ),
1464
- !isHovered && /* @__PURE__ */ jsx("div", { className: "text-[9px] text-white/40 text-center mt-1", children: "Hover for controls" })
1792
+ !isHovered && /* @__PURE__ */ jsx("div", { className: "text-[9px] text-white/40 text-center mt-1", children: t.overlays.recording.hover })
1465
1793
  ]
1466
1794
  }
1467
1795
  );
1468
1796
  });
1469
1797
  var RecordingIndicator_default = RecordingIndicator;
1470
- var SHORTCUTS = [
1471
- {
1472
- section: "Overlays",
1473
- items: [
1474
- { key: "F1", description: "Show this help" },
1475
- { key: "F3", description: "Performance Overlay (FPS)" },
1476
- { key: "F4", description: "Input Display" }
1477
- ]
1478
- },
1479
- {
1480
- section: "Recording",
1481
- items: [
1482
- { key: "F5", description: "Start/Stop Recording" }
1483
- ]
1484
- },
1485
- {
1486
- section: "Audio",
1487
- items: [
1488
- { key: "F9", description: "Toggle Mute" }
1489
- ]
1490
- }
1491
- ];
1492
1798
  var ShortcutsModal = memo(function ShortcutsModal2({
1493
1799
  isOpen,
1494
1800
  onClose,
1495
1801
  systemColor = "#00FF41"
1496
1802
  }) {
1803
+ const t = useKoinTranslation();
1804
+ const shortcuts = useMemo(() => [
1805
+ {
1806
+ section: t.modals.shortcuts.overlays,
1807
+ items: [
1808
+ { key: "F1", description: t.modals.shortcuts.showHelp },
1809
+ { key: "F3", description: t.modals.shortcuts.perfOverlay },
1810
+ { key: "F4", description: t.modals.shortcuts.inputDisplay }
1811
+ ]
1812
+ },
1813
+ {
1814
+ section: t.modals.shortcuts.recording,
1815
+ items: [
1816
+ { key: "F5", description: t.modals.shortcuts.toggleRec }
1817
+ ]
1818
+ },
1819
+ {
1820
+ section: t.settings.audio,
1821
+ items: [
1822
+ { key: "F9", description: t.modals.shortcuts.toggleMute }
1823
+ ]
1824
+ }
1825
+ ], [t]);
1497
1826
  if (!isOpen) return null;
1498
1827
  return /* @__PURE__ */ jsx(
1499
1828
  "div",
@@ -1515,7 +1844,7 @@ var ShortcutsModal = memo(function ShortcutsModal2({
1515
1844
  children: [
1516
1845
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1517
1846
  /* @__PURE__ */ jsx(Keyboard, { size: 18, style: { color: systemColor } }),
1518
- /* @__PURE__ */ jsx("span", { className: "font-bold text-white", children: "Player Shortcuts" })
1847
+ /* @__PURE__ */ jsx("span", { className: "font-bold text-white", children: t.modals.shortcuts.playerShortcuts })
1519
1848
  ] }),
1520
1849
  /* @__PURE__ */ jsx(
1521
1850
  "button",
@@ -1529,7 +1858,7 @@ var ShortcutsModal = memo(function ShortcutsModal2({
1529
1858
  }
1530
1859
  ),
1531
1860
  /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
1532
- SHORTCUTS.map(({ section, items }) => /* @__PURE__ */ jsxs("div", { children: [
1861
+ shortcuts.map(({ section, items }) => /* @__PURE__ */ jsxs("div", { children: [
1533
1862
  /* @__PURE__ */ jsx(
1534
1863
  "h3",
1535
1864
  {
@@ -1563,20 +1892,16 @@ var ShortcutsModal = memo(function ShortcutsModal2({
1563
1892
  ] }, section)),
1564
1893
  /* @__PURE__ */ jsxs("div", { className: "pt-2 border-t border-white/10 text-xs text-white/40", children: [
1565
1894
  "Game controls can be configured in ",
1566
- /* @__PURE__ */ jsx("strong", { className: "text-white/60", children: "Keys" }),
1895
+ /* @__PURE__ */ jsx("strong", { className: "text-white/60", children: t.controls.keys }),
1567
1896
  " settings."
1568
1897
  ] })
1569
1898
  ] }),
1570
- /* @__PURE__ */ jsxs(
1899
+ /* @__PURE__ */ jsx(
1571
1900
  "div",
1572
1901
  {
1573
1902
  className: "px-4 py-2 text-center text-xs text-white/40 border-t",
1574
1903
  style: { borderColor: `${systemColor}20` },
1575
- children: [
1576
- "Press ",
1577
- /* @__PURE__ */ jsx("kbd", { className: "px-1 bg-white/10 rounded font-mono", children: "ESC" }),
1578
- " to close"
1579
- ]
1904
+ children: t.modals.shortcuts.pressEsc
1580
1905
  }
1581
1906
  )
1582
1907
  ]
@@ -2037,7 +2362,7 @@ function getButtonStyles(buttonType, isPressed) {
2037
2362
  };
2038
2363
  }
2039
2364
  }
2040
- var VirtualButton = React5.memo(function VirtualButton2({
2365
+ var VirtualButton = React2.memo(function VirtualButton2({
2041
2366
  config,
2042
2367
  isPressed,
2043
2368
  onPress,
@@ -2051,10 +2376,14 @@ var VirtualButton = React5.memo(function VirtualButton2({
2051
2376
  systemColor = "#00FF41"
2052
2377
  // Default retro green
2053
2378
  }) {
2379
+ const t = useKoinTranslation();
2054
2380
  const buttonRef = useRef(null);
2055
2381
  const isSystemButton = config.type === "start" || config.type === "select";
2056
2382
  const displayX = customPosition ? customPosition.x : config.x;
2057
2383
  const displayY = customPosition ? customPosition.y : config.y;
2384
+ let label = config.label;
2385
+ if (config.type === "start") label = t.controls.startBtn;
2386
+ if (config.type === "select") label = t.controls.selectBtn;
2058
2387
  const {
2059
2388
  handleTouchStart,
2060
2389
  handleTouchMove,
@@ -2132,9 +2461,9 @@ var VirtualButton = React5.memo(function VirtualButton2({
2132
2461
  userSelect: "none",
2133
2462
  ...pressedStyle
2134
2463
  },
2135
- "aria-label": config.label,
2464
+ "aria-label": label,
2136
2465
  onContextMenu: (e) => e.preventDefault(),
2137
- children: config.label
2466
+ children: label
2138
2467
  }
2139
2468
  );
2140
2469
  });
@@ -2751,7 +3080,7 @@ function dispatchKeyboardEvent(type, code) {
2751
3080
  canvas.dispatchEvent(event);
2752
3081
  return true;
2753
3082
  }
2754
- var Dpad = React5.memo(function Dpad2({
3083
+ var Dpad = React2.memo(function Dpad2({
2755
3084
  size,
2756
3085
  x,
2757
3086
  y,
@@ -3263,9 +3592,10 @@ function GameOverlay({
3263
3592
  isLoadingSave,
3264
3593
  onSelectBios
3265
3594
  }) {
3595
+ const t = useKoinTranslation();
3266
3596
  const isLoading = status === "loading" || status === "ready" && isLoadingSave;
3267
3597
  if (isLoading) {
3268
- const message = status === "loading" ? { title: `Loading ${system}`, subtitle: "Initializing emulator" } : { title: "Loading Save", subtitle: `Preparing slot ${pendingSlot}` };
3598
+ const message = status === "loading" ? { title: t.overlay.loading.replace("{{system}}", system || ""), subtitle: t.overlay.initializing } : { title: t.overlay.loadingSave, subtitle: t.overlay.preparingSlot.replace("{{num}}", String(pendingSlot || "")) };
3269
3599
  return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-black z-20", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
3270
3600
  /* @__PURE__ */ jsx(LoadingSpinner, { color: systemColor, size: "lg" }),
3271
3601
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
@@ -3303,12 +3633,11 @@ function GameOverlay({
3303
3633
  children: /* @__PURE__ */ jsx(Play, { className: "w-8 h-8 ml-1", style: { color: systemColor }, fill: systemColor })
3304
3634
  }
3305
3635
  ),
3306
- /* @__PURE__ */ jsx("span", { className: "font-mono text-lg uppercase tracking-wider", style: { color: systemColor }, children: hasPendingSave ? "Continue" : "Play" }),
3636
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-lg uppercase tracking-wider", style: { color: systemColor }, children: t.overlay.play }),
3307
3637
  hasPendingSave && /* @__PURE__ */ jsxs("span", { className: "text-gray-400 text-xs flex items-center gap-1", children: [
3308
3638
  /* @__PURE__ */ jsx(Save, { size: 12 }),
3309
- " Slot ",
3310
- pendingSlot,
3311
- " ready"
3639
+ " ",
3640
+ t.overlay.slotReady.replace("{{num}}", String(pendingSlot))
3312
3641
  ] })
3313
3642
  ]
3314
3643
  }
@@ -3320,7 +3649,7 @@ function GameOverlay({
3320
3649
  className: "mt-6 flex items-center gap-2 px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white hover:bg-white/10 transition-colors",
3321
3650
  children: [
3322
3651
  /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full", style: { backgroundColor: systemColor } }),
3323
- /* @__PURE__ */ jsx("span", { className: "font-mono uppercase tracking-wider text-xs", children: "System Firmware" })
3652
+ /* @__PURE__ */ jsx("span", { className: "font-mono uppercase tracking-wider text-xs", children: t.overlay.systemFirmware })
3324
3653
  ]
3325
3654
  }
3326
3655
  )
@@ -3330,8 +3659,8 @@ function GameOverlay({
3330
3659
  return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-black/90 z-20", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
3331
3660
  /* @__PURE__ */ jsx(AlertTriangle, { className: "w-12 h-12 text-red-500" }),
3332
3661
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
3333
- /* @__PURE__ */ jsx("p", { className: "text-red-400 font-mono uppercase tracking-widest text-sm", children: "System Error" }),
3334
- /* @__PURE__ */ jsx("p", { className: "text-gray-500 text-xs mt-1 max-w-xs", children: error || "Failed to initialize emulator" })
3662
+ /* @__PURE__ */ jsx("p", { className: "text-red-400 font-mono uppercase tracking-widest text-sm", children: t.overlay.systemError }),
3663
+ /* @__PURE__ */ jsx("p", { className: "text-gray-500 text-xs mt-1 max-w-xs", children: error || t.overlay.failedInit })
3335
3664
  ] }),
3336
3665
  /* @__PURE__ */ jsx(
3337
3666
  "button",
@@ -3339,7 +3668,7 @@ function GameOverlay({
3339
3668
  onClick: onStart,
3340
3669
  className: "mt-2 px-4 py-2 text-sm font-bold rounded-lg transition-colors",
3341
3670
  style: { backgroundColor: systemColor, color: "#000" },
3342
- children: "Retry"
3671
+ children: t.overlay.retry
3343
3672
  }
3344
3673
  )
3345
3674
  ] }) });
@@ -3357,7 +3686,7 @@ function GameOverlay({
3357
3686
  ] })
3358
3687
  }
3359
3688
  ),
3360
- /* @__PURE__ */ jsx("p", { className: "font-mono uppercase tracking-wider text-sm", style: { color: systemColor }, children: "Paused" })
3689
+ /* @__PURE__ */ jsx("p", { className: "font-mono uppercase tracking-wider text-sm", style: { color: systemColor }, children: t.overlay.paused })
3361
3690
  ] }) });
3362
3691
  }
3363
3692
  return null;
@@ -3466,6 +3795,7 @@ function ControlMapper({
3466
3795
  onClose,
3467
3796
  system
3468
3797
  }) {
3798
+ const t = useKoinTranslation();
3469
3799
  const [localControls, setLocalControls] = useState(controls);
3470
3800
  const [listeningFor, setListeningFor] = useState(null);
3471
3801
  const activeButtons = useMemo(() => {
@@ -3525,8 +3855,8 @@ function ControlMapper({
3525
3855
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3526
3856
  /* @__PURE__ */ jsx(Gamepad2, { className: "text-retro-primary", size: 24 }),
3527
3857
  /* @__PURE__ */ jsxs("div", { children: [
3528
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: "Control Mapping" }),
3529
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: "Click a button and press a key to remap" })
3858
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.controls.title }),
3859
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: t.modals.controls.description })
3530
3860
  ] })
3531
3861
  ] }),
3532
3862
  /* @__PURE__ */ jsx(
@@ -3557,7 +3887,7 @@ function ControlMapper({
3557
3887
  px-2 py-1 rounded text-xs font-mono
3558
3888
  ${listeningFor === btn ? "bg-retro-primary/30 text-retro-primary animate-pulse" : "bg-black/50 text-white"}
3559
3889
  `,
3560
- children: listeningFor === btn ? "Press..." : formatKeyCode(localControls[btn] || "")
3890
+ children: listeningFor === btn ? t.modals.controls.pressKey : formatKeyCode(localControls[btn] || "")
3561
3891
  }
3562
3892
  )
3563
3893
  ]
@@ -3573,7 +3903,7 @@ function ControlMapper({
3573
3903
  className: "flex items-center gap-2 px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white hover:bg-white/10 transition-colors",
3574
3904
  children: [
3575
3905
  /* @__PURE__ */ jsx(RotateCcw, { size: 16 }),
3576
- "Reset to Default"
3906
+ t.modals.controls.reset
3577
3907
  ]
3578
3908
  }
3579
3909
  ),
@@ -3584,7 +3914,7 @@ function ControlMapper({
3584
3914
  className: "flex items-center gap-2 px-6 py-2 rounded-lg bg-retro-primary text-black font-bold text-sm hover:bg-retro-primary/90 transition-colors",
3585
3915
  children: [
3586
3916
  /* @__PURE__ */ jsx(Check, { size: 16 }),
3587
- "Save Controls"
3917
+ t.modals.controls.save
3588
3918
  ]
3589
3919
  }
3590
3920
  )
@@ -3764,6 +4094,7 @@ function GamepadMapper({
3764
4094
  onSave,
3765
4095
  systemColor = "#00FF41"
3766
4096
  }) {
4097
+ const t = useKoinTranslation();
3767
4098
  const [selectedPlayer, setSelectedPlayer] = useState(1);
3768
4099
  const [bindings, setBindings] = useState({});
3769
4100
  const [listeningFor, setListeningFor] = useState(null);
@@ -3860,8 +4191,8 @@ function GamepadMapper({
3860
4191
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3861
4192
  /* @__PURE__ */ jsx(Joystick, { className: "text-retro-primary", size: 24, style: { color: systemColor } }),
3862
4193
  /* @__PURE__ */ jsxs("div", { children: [
3863
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: "Gamepad Settings" }),
3864
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: gamepads.length > 0 ? `${gamepads.length} controller${gamepads.length > 1 ? "s" : ""} connected` : "No controllers detected" })
4194
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.gamepad.title }),
4195
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: gamepads.length > 0 ? t.modals.gamepad.connected.replace("{{count}}", gamepads.length.toString()) : t.modals.gamepad.none })
3865
4196
  ] })
3866
4197
  ] }),
3867
4198
  /* @__PURE__ */ jsx(
@@ -3875,7 +4206,7 @@ function GamepadMapper({
3875
4206
  ] }),
3876
4207
  gamepads.length > 1 && /* @__PURE__ */ jsxs("div", { className: "px-6 py-3 border-b border-white/10 bg-black/30", children: [
3877
4208
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3878
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 font-medium", children: "Player:" }),
4209
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 font-medium", children: t.modals.gamepad.player }),
3879
4210
  /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: gamepads.map((gp) => /* @__PURE__ */ jsxs(
3880
4211
  "button",
3881
4212
  {
@@ -3908,10 +4239,10 @@ function GamepadMapper({
3908
4239
  /* @__PURE__ */ jsx(Joystick, { size: 56, className: "text-gray-600 animate-pulse" }),
3909
4240
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 -m-2 rounded-full border-2 border-dashed border-gray-600 animate-spin", style: { animationDuration: "8s" } })
3910
4241
  ] }),
3911
- /* @__PURE__ */ jsx("p", { className: "text-gray-300 font-medium mb-2", children: "No controller detected" }),
3912
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mb-4", children: "Press any button on your gamepad to connect" }),
4242
+ /* @__PURE__ */ jsx("p", { className: "text-gray-300 font-medium mb-2", children: t.modals.gamepad.noController }),
4243
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mb-4", children: t.modals.gamepad.pressAny }),
3913
4244
  /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-white/5 border border-white/10", children: [
3914
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: "Waiting for input..." }),
4245
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: t.modals.gamepad.waiting }),
3915
4246
  /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
3916
4247
  /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-gray-500 animate-bounce", style: { animationDelay: "0ms" } }),
3917
4248
  /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-gray-500 animate-bounce", style: { animationDelay: "150ms" } }),
@@ -3921,11 +4252,8 @@ function GamepadMapper({
3921
4252
  ] }),
3922
4253
  gamepads.length > 0 && /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-6 max-h-[400px] overflow-y-auto", children: [
3923
4254
  listeningFor && /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-lg bg-black/50 border border-retro-primary/50 text-center animate-pulse", style: { borderColor: `${systemColor}50` }, children: [
3924
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-white mb-1", children: [
3925
- "Press a button on your controller for ",
3926
- /* @__PURE__ */ jsx("strong", { children: BUTTON_LABELS[listeningFor] })
3927
- ] }),
3928
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: "Press Escape to cancel" })
4255
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-white mb-1", children: t.modals.gamepad.pressButton.replace("{{button}}", BUTTON_LABELS[listeningFor]) }),
4256
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: t.modals.gamepad.pressEsc })
3929
4257
  ] }),
3930
4258
  BUTTON_GROUPS.map((group) => /* @__PURE__ */ jsxs("div", { children: [
3931
4259
  /* @__PURE__ */ jsx("h3", { className: "text-xs font-bold text-gray-500 uppercase tracking-wider mb-2", children: group.label }),
@@ -3957,7 +4285,7 @@ function GamepadMapper({
3957
4285
  backgroundColor: `${systemColor}30`,
3958
4286
  color: systemColor
3959
4287
  } : {},
3960
- children: listeningFor === btn ? "Press..." : formatGamepadButton(currentBindings[btn])
4288
+ children: listeningFor === btn ? t.controls.press : formatGamepadButton(currentBindings[btn])
3961
4289
  }
3962
4290
  )
3963
4291
  ]
@@ -3975,7 +4303,7 @@ function GamepadMapper({
3975
4303
  className: "flex items-center gap-2 px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white hover:bg-white/10 transition-colors disabled:opacity-50",
3976
4304
  children: [
3977
4305
  /* @__PURE__ */ jsx(RotateCcw, { size: 16 }),
3978
- "Reset to Default"
4306
+ t.modals.gamepad.reset
3979
4307
  ]
3980
4308
  }
3981
4309
  ),
@@ -3988,7 +4316,7 @@ function GamepadMapper({
3988
4316
  style: { backgroundColor: systemColor },
3989
4317
  children: [
3990
4318
  /* @__PURE__ */ jsx(Check, { size: 16 }),
3991
- "Save Settings"
4319
+ t.modals.gamepad.save
3992
4320
  ]
3993
4321
  }
3994
4322
  )
@@ -4003,7 +4331,8 @@ function CheatModal({
4003
4331
  onToggle,
4004
4332
  onClose
4005
4333
  }) {
4006
- const [copiedId, setCopiedId] = React5.useState(null);
4334
+ const t = useKoinTranslation();
4335
+ const [copiedId, setCopiedId] = React2.useState(null);
4007
4336
  if (!isOpen) return null;
4008
4337
  const handleCopy = async (code, id) => {
4009
4338
  await navigator.clipboard.writeText(code);
@@ -4023,13 +4352,8 @@ function CheatModal({
4023
4352
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4024
4353
  /* @__PURE__ */ jsx(Code, { className: "text-purple-400", size: 24 }),
4025
4354
  /* @__PURE__ */ jsxs("div", { children: [
4026
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: "Cheat Codes" }),
4027
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-400", children: [
4028
- cheats.length,
4029
- " cheat",
4030
- cheats.length !== 1 ? "s" : "",
4031
- " available"
4032
- ] })
4355
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.cheats.title }),
4356
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: t.modals.cheats.available.replace("{{count}}", cheats.length.toString()) })
4033
4357
  ] })
4034
4358
  ] }),
4035
4359
  /* @__PURE__ */ jsx(
@@ -4043,8 +4367,8 @@ function CheatModal({
4043
4367
  ] }),
4044
4368
  /* @__PURE__ */ jsx("div", { className: "p-4 space-y-2 max-h-[400px] overflow-y-auto", children: cheats.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-12 text-gray-500", children: [
4045
4369
  /* @__PURE__ */ jsx(Code, { size: 48, className: "mx-auto mb-3 opacity-50" }),
4046
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "No cheats available" }),
4047
- /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "No cheat codes found for this game" })
4370
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: t.modals.cheats.emptyTitle }),
4371
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: t.modals.cheats.emptyDesc })
4048
4372
  ] }) : cheats.map((cheat) => {
4049
4373
  const isActive = activeCheats.has(cheat.id);
4050
4374
  return /* @__PURE__ */ jsxs(
@@ -4078,7 +4402,7 @@ function CheatModal({
4078
4402
  handleCopy(cheat.code, cheat.id);
4079
4403
  },
4080
4404
  className: "p-1.5 rounded hover:bg-white/10 text-gray-500 hover:text-white transition-colors",
4081
- title: "Copy code",
4405
+ title: t.modals.cheats.copy,
4082
4406
  children: copiedId === cheat.id ? /* @__PURE__ */ jsx(Check, { size: 14, className: "text-green-400" }) : /* @__PURE__ */ jsx(Copy, { size: 14 })
4083
4407
  }
4084
4408
  )
@@ -4089,7 +4413,7 @@ function CheatModal({
4089
4413
  cheat.id
4090
4414
  );
4091
4415
  }) }),
4092
- /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center", children: activeCheats.size > 0 ? `${activeCheats.size} cheat${activeCheats.size !== 1 ? "s" : ""} active` : "Click a cheat to toggle it on/off" }) })
4416
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center", children: activeCheats.size > 0 ? t.modals.cheats.active.replace("{{count}}", activeCheats.size.toString()) : t.modals.cheats.toggleHint }) })
4093
4417
  ] })
4094
4418
  ] });
4095
4419
  }
@@ -4102,18 +4426,22 @@ function formatBytes(bytes) {
4102
4426
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
4103
4427
  }
4104
4428
  function formatTimestamp(timestamp) {
4105
- const date = new Date(timestamp);
4106
- const now = /* @__PURE__ */ new Date();
4107
- const diff = now.getTime() - date.getTime();
4108
- if (diff < 6e4) return "Just now";
4109
- if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
4110
- if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
4111
- return date.toLocaleDateString("en-US", {
4112
- month: "short",
4113
- day: "numeric",
4114
- hour: "2-digit",
4115
- minute: "2-digit"
4116
- });
4429
+ try {
4430
+ const date = new Date(timestamp);
4431
+ const now = /* @__PURE__ */ new Date();
4432
+ const diff = now.getTime() - date.getTime();
4433
+ if (diff < 6e4) return "Just now";
4434
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
4435
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
4436
+ return date.toLocaleDateString("en-US", {
4437
+ month: "short",
4438
+ day: "numeric",
4439
+ hour: "2-digit",
4440
+ minute: "2-digit"
4441
+ });
4442
+ } catch {
4443
+ return "Unknown";
4444
+ }
4117
4445
  }
4118
4446
  function SaveSlotModal({
4119
4447
  isOpen,
@@ -4127,6 +4455,7 @@ function SaveSlotModal({
4127
4455
  maxSlots = 5,
4128
4456
  onUpgrade
4129
4457
  }) {
4458
+ const t = useKoinTranslation();
4130
4459
  if (!isOpen) return null;
4131
4460
  const isSaveMode = mode === "save";
4132
4461
  const allSlots = [1, 2, 3, 4, 5];
@@ -4155,8 +4484,8 @@ function SaveSlotModal({
4155
4484
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4156
4485
  isSaveMode ? /* @__PURE__ */ jsx(Save, { className: "text-retro-primary", size: 24 }) : /* @__PURE__ */ jsx(Download, { className: "text-retro-primary", size: 24 }),
4157
4486
  /* @__PURE__ */ jsxs("div", { children: [
4158
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: isSaveMode ? "Save Game" : "Load Game" }),
4159
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: isSaveMode ? "Choose a slot to save your progress" : "Select a save to restore" })
4487
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: isSaveMode ? t.modals.saveSlots.saveTitle : t.modals.saveSlots.loadTitle }),
4488
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: isSaveMode ? t.modals.saveSlots.subtitleSave : t.modals.saveSlots.subtitleLoad })
4160
4489
  ] })
4161
4490
  ] }),
4162
4491
  /* @__PURE__ */ jsx(
@@ -4170,7 +4499,7 @@ function SaveSlotModal({
4170
4499
  ] }),
4171
4500
  /* @__PURE__ */ jsx("div", { className: "p-4 space-y-2 max-h-[400px] overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-gray-400", children: [
4172
4501
  /* @__PURE__ */ jsx(Loader2, { className: "w-8 h-8 animate-spin mb-3" }),
4173
- /* @__PURE__ */ jsx("span", { className: "text-sm", children: "Loading saves..." })
4502
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: t.modals.saveSlots.loading })
4174
4503
  ] }) : displaySlots.map((slotNum) => {
4175
4504
  const slotData = getSlotData(slotNum);
4176
4505
  const isEmpty = !slotData;
@@ -4187,14 +4516,10 @@ function SaveSlotModal({
4187
4516
  children: [
4188
4517
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-12 h-12 rounded-lg flex items-center justify-center font-bold text-xl bg-gray-700 text-gray-500", children: /* @__PURE__ */ jsx(Lock, { size: 20 }) }),
4189
4518
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4190
- /* @__PURE__ */ jsxs("p", { className: "font-medium text-gray-400 group-hover:text-white transition-colors", children: [
4191
- "Slot ",
4192
- slotNum,
4193
- " Locked"
4194
- ] }),
4519
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-400 group-hover:text-white transition-colors", children: t.modals.saveSlots.locked.replace("{{num}}", slotNum.toString()) }),
4195
4520
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 group-hover:text-retro-primary transition-colors flex items-center gap-1", children: [
4196
4521
  /* @__PURE__ */ jsx(Zap, { size: 10 }),
4197
- "Upgrade to unlock more save slots"
4522
+ t.modals.saveSlots.upgrade
4198
4523
  ] })
4199
4524
  ] })
4200
4525
  ]
@@ -4218,8 +4543,8 @@ function SaveSlotModal({
4218
4543
  /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: isAutoSaveSlot ? (
4219
4544
  // Auto-save slot - special display
4220
4545
  isEmpty ? /* @__PURE__ */ jsxs("div", { className: "text-retro-primary/70", children: [
4221
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Auto-Save Slot" }),
4222
- /* @__PURE__ */ jsx("p", { className: "text-xs text-retro-primary/50", children: "Reserved for automatic saves" })
4546
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: t.modals.saveSlots.autoSave }),
4547
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-retro-primary/50", children: t.modals.saveSlots.autoSaveDesc })
4223
4548
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4224
4549
  slotData.screenshot && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-16 h-12 rounded border border-retro-primary/30 overflow-hidden bg-black", children: /* @__PURE__ */ jsx(
4225
4550
  "img",
@@ -4233,7 +4558,7 @@ function SaveSlotModal({
4233
4558
  }
4234
4559
  ) }),
4235
4560
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4236
- /* @__PURE__ */ jsx("p", { className: "font-medium text-retro-primary truncate", children: "Auto-Save" }),
4561
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-retro-primary truncate", children: t.modals.saveSlots.autoSave }),
4237
4562
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-retro-primary/60 mt-1", children: [
4238
4563
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4239
4564
  /* @__PURE__ */ jsx(Clock, { size: 12 }),
@@ -4247,8 +4572,8 @@ function SaveSlotModal({
4247
4572
  ] })
4248
4573
  ] })
4249
4574
  ) : isEmpty ? /* @__PURE__ */ jsxs("div", { className: "text-gray-500", children: [
4250
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Empty Slot" }),
4251
- /* @__PURE__ */ jsx("p", { className: "text-xs", children: "No save data" })
4575
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: t.modals.saveSlots.emptySlot }),
4576
+ /* @__PURE__ */ jsx("p", { className: "text-xs", children: t.modals.saveSlots.noData })
4252
4577
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4253
4578
  slotData.screenshot && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-16 h-12 rounded border border-white/10 overflow-hidden bg-black", children: /* @__PURE__ */ jsx(
4254
4579
  "img",
@@ -4262,10 +4587,7 @@ function SaveSlotModal({
4262
4587
  }
4263
4588
  ) }),
4264
4589
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4265
- /* @__PURE__ */ jsxs("p", { className: "font-medium text-white truncate", children: [
4266
- "Slot ",
4267
- slotNum
4268
- ] }),
4590
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-white truncate", children: t.modals.saveSlots.slot.replace("{{num}}", slotNum.toString()) }),
4269
4591
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-gray-400 mt-1", children: [
4270
4592
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4271
4593
  /* @__PURE__ */ jsx(Clock, { size: 12 }),
@@ -4295,7 +4617,7 @@ function SaveSlotModal({
4295
4617
  slotNum
4296
4618
  );
4297
4619
  }) }),
4298
- /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center", children: isSaveMode ? "Saves are stored in the cloud and sync across devices" : "Your progress will be restored to the selected save point" }) })
4620
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center", children: isSaveMode ? t.modals.saveSlots.footerSave : t.modals.saveSlots.footerLoad }) })
4299
4621
  ] })
4300
4622
  ] });
4301
4623
  }
@@ -4307,6 +4629,7 @@ function BiosSelectionModal({
4307
4629
  onSelectBios,
4308
4630
  systemColor = "#00FF41"
4309
4631
  }) {
4632
+ const t = useKoinTranslation();
4310
4633
  if (!isOpen) return null;
4311
4634
  return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
4312
4635
  /* @__PURE__ */ jsx(
@@ -4326,8 +4649,8 @@ function BiosSelectionModal({
4326
4649
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4327
4650
  /* @__PURE__ */ jsx(Cpu, { size: 24, style: { color: systemColor } }),
4328
4651
  /* @__PURE__ */ jsxs("div", { children: [
4329
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white uppercase tracking-wide", children: "BIOS Selection" }),
4330
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: "Select a BIOS file to use for this game" })
4652
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white uppercase tracking-wide", children: t.modals.bios.title }),
4653
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: t.modals.bios.description })
4331
4654
  ] })
4332
4655
  ] }),
4333
4656
  /* @__PURE__ */ jsx(
@@ -4343,8 +4666,9 @@ function BiosSelectionModal({
4343
4666
  /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 rounded bg-yellow-500/10 border border-yellow-500/20 flex items-start gap-3", children: [
4344
4667
  /* @__PURE__ */ jsx(AlertCircle, { className: "shrink-0 text-yellow-500 mt-0.5", size: 16 }),
4345
4668
  /* @__PURE__ */ jsxs("div", { className: "text-xs text-yellow-200/80", children: [
4346
- /* @__PURE__ */ jsx("strong", { children: "Note:" }),
4347
- " Changing BIOS requires the emulator to restart. Your unsaved progress will be lost."
4669
+ /* @__PURE__ */ jsx("strong", { children: t.modals.bios.warningTitle }),
4670
+ " ",
4671
+ t.modals.bios.warning
4348
4672
  ] })
4349
4673
  ] }),
4350
4674
  /* @__PURE__ */ jsxs(
@@ -4374,10 +4698,10 @@ function BiosSelectionModal({
4374
4698
  ),
4375
4699
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4376
4700
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4377
- /* @__PURE__ */ jsx("span", { className: `font-mono font-bold truncate ${!currentBiosId ? "text-white" : "text-gray-300"}`, children: "System Default" }),
4378
- !currentBiosId && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children: "Active" })
4701
+ /* @__PURE__ */ jsx("span", { className: `font-mono font-bold truncate ${!currentBiosId ? "text-white" : "text-gray-300"}`, children: t.modals.bios.systemDefault }),
4702
+ !currentBiosId && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children: t.modals.bios.active })
4379
4703
  ] }),
4380
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: "Use the emulator's built-in or default BIOS" })
4704
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: t.modals.bios.defaultDesc })
4381
4705
  ] }),
4382
4706
  !currentBiosId && /* @__PURE__ */ jsx(Check, { size: 20, style: { color: systemColor } })
4383
4707
  ]
@@ -4385,8 +4709,8 @@ function BiosSelectionModal({
4385
4709
  ),
4386
4710
  biosOptions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-8 text-gray-500", children: [
4387
4711
  /* @__PURE__ */ jsx(FileCode, { size: 48, className: "mx-auto mb-3 opacity-30" }),
4388
- /* @__PURE__ */ jsx("p", { className: "text-sm", children: "No BIOS files found for this console." }),
4389
- /* @__PURE__ */ jsx("p", { className: "text-[10px] mt-1 text-gray-600", children: "Upload BIOS files in your Dashboard Library." })
4712
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: t.modals.bios.emptyTitle }),
4713
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] mt-1 text-gray-600", children: t.modals.bios.emptyDesc })
4390
4714
  ] }) : biosOptions.map((bios) => {
4391
4715
  const isSelected = bios.id === currentBiosId;
4392
4716
  return /* @__PURE__ */ jsxs(
@@ -4417,7 +4741,7 @@ function BiosSelectionModal({
4417
4741
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4418
4742
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4419
4743
  /* @__PURE__ */ jsx("span", { className: `font-mono font-bold truncate ${isSelected ? "text-white" : "text-gray-300"}`, children: bios.name }),
4420
- isSelected && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children: "Active" })
4744
+ isSelected && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children: t.modals.bios.active })
4421
4745
  ] }),
4422
4746
  bios.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: bios.description })
4423
4747
  ] }),
@@ -4428,12 +4752,84 @@ function BiosSelectionModal({
4428
4752
  );
4429
4753
  })
4430
4754
  ] }),
4431
- /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-500 uppercase tracking-widest", children: "System Firmware Settings" }) })
4755
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-500 uppercase tracking-widest", children: t.modals.bios.footer }) })
4432
4756
  ]
4433
4757
  }
4434
4758
  )
4435
4759
  ] });
4436
4760
  }
4761
+ function SettingsModal({
4762
+ isOpen,
4763
+ onClose,
4764
+ currentLanguage,
4765
+ onLanguageChange,
4766
+ systemColor = "#00FF41"
4767
+ }) {
4768
+ const t = useKoinTranslation();
4769
+ if (!isOpen) return null;
4770
+ const languages = [
4771
+ { code: "en", name: "English" },
4772
+ { code: "es", name: "Espa\xF1ol" },
4773
+ { code: "fr", name: "Fran\xE7ais" }
4774
+ ];
4775
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
4776
+ /* @__PURE__ */ jsx(
4777
+ "div",
4778
+ {
4779
+ className: "absolute inset-0 bg-black/80 backdrop-blur-sm",
4780
+ onClick: onClose
4781
+ }
4782
+ ),
4783
+ /* @__PURE__ */ jsxs("div", { className: "relative bg-gray-900 border border-white/10 rounded-xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden animate-in fade-in zoom-in-95 duration-200", children: [
4784
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/10 bg-black/50", children: [
4785
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4786
+ /* @__PURE__ */ jsx(Settings, { className: "text-white", size: 20 }),
4787
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: t.settings.title })
4788
+ ] }),
4789
+ /* @__PURE__ */ jsx(
4790
+ "button",
4791
+ {
4792
+ onClick: onClose,
4793
+ className: "p-2 rounded-lg hover:bg-white/10 text-gray-400 hover:text-white transition-colors",
4794
+ children: /* @__PURE__ */ jsx(X, { size: 20 })
4795
+ }
4796
+ )
4797
+ ] }),
4798
+ /* @__PURE__ */ jsx("div", { className: "p-6 space-y-6", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
4799
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
4800
+ /* @__PURE__ */ jsx(Globe, { size: 16 }),
4801
+ /* @__PURE__ */ jsx("span", { children: t.settings.language })
4802
+ ] }),
4803
+ /* @__PURE__ */ jsx("div", { className: "grid gap-2", children: languages.map((lang) => {
4804
+ const isActive = currentLanguage === lang.code;
4805
+ return /* @__PURE__ */ jsxs(
4806
+ "button",
4807
+ {
4808
+ onClick: () => onLanguageChange(lang.code),
4809
+ className: `
4810
+ flex items-center justify-between px-4 py-3 rounded-lg border transition-all
4811
+ ${isActive ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
4812
+ `,
4813
+ children: [
4814
+ /* @__PURE__ */ jsx("span", { children: lang.name }),
4815
+ isActive && /* @__PURE__ */ jsx(Check, { size: 16, style: { color: systemColor } })
4816
+ ]
4817
+ },
4818
+ lang.code
4819
+ );
4820
+ }) })
4821
+ ] }) }),
4822
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-4 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsx(
4823
+ "button",
4824
+ {
4825
+ onClick: onClose,
4826
+ className: "text-sm text-gray-500 hover:text-white transition-colors",
4827
+ children: t.modals.shortcuts.pressEsc
4828
+ }
4829
+ ) })
4830
+ ] })
4831
+ ] });
4832
+ }
4437
4833
  function GameModals({
4438
4834
  controlsModalOpen,
4439
4835
  setControlsModalOpen,
@@ -4465,7 +4861,11 @@ function GameModals({
4465
4861
  setBiosModalOpen,
4466
4862
  availableBios,
4467
4863
  currentBiosId,
4468
- onSelectBios
4864
+ onSelectBios,
4865
+ settingsModalOpen,
4866
+ setSettingsModalOpen,
4867
+ currentLanguage,
4868
+ onLanguageChange
4469
4869
  }) {
4470
4870
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4471
4871
  /* @__PURE__ */ jsx(
@@ -4541,6 +4941,19 @@ function GameModals({
4541
4941
  },
4542
4942
  systemColor
4543
4943
  }
4944
+ ),
4945
+ /* @__PURE__ */ jsx(
4946
+ SettingsModal,
4947
+ {
4948
+ isOpen: settingsModalOpen,
4949
+ onClose: () => {
4950
+ setSettingsModalOpen(false);
4951
+ onResume();
4952
+ },
4953
+ currentLanguage,
4954
+ onLanguageChange,
4955
+ systemColor
4956
+ }
4544
4957
  )
4545
4958
  ] });
4546
4959
  }
@@ -4555,10 +4968,11 @@ function LoginForm({
4555
4968
  error,
4556
4969
  onSubmit
4557
4970
  }) {
4971
+ const t = useKoinTranslation();
4558
4972
  return /* @__PURE__ */ jsxs("form", { onSubmit, className: "p-4 space-y-3", children: [
4559
4973
  /* @__PURE__ */ jsxs("div", { className: "text-center mb-4", children: [
4560
4974
  /* @__PURE__ */ jsx("div", { className: "w-16 h-16 mx-auto mb-3 rounded-full bg-gradient-to-br from-yellow-500/20 to-orange-500/20 border border-yellow-500/30 flex items-center justify-center", children: /* @__PURE__ */ jsx(Trophy, { className: "text-yellow-400", size: 32 }) }),
4561
- /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-xs", children: "Connect your account to track achievements" }),
4975
+ /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-xs", children: t.retroAchievements.connectAccount }),
4562
4976
  /* @__PURE__ */ jsxs(
4563
4977
  "a",
4564
4978
  {
@@ -4567,14 +4981,14 @@ function LoginForm({
4567
4981
  rel: "noopener noreferrer",
4568
4982
  className: "text-yellow-400 hover:text-yellow-300 text-xs inline-flex items-center gap-1 mt-1",
4569
4983
  children: [
4570
- "Create an account",
4984
+ t.retroAchievements.createAccount,
4571
4985
  /* @__PURE__ */ jsx(ExternalLink, { size: 10 })
4572
4986
  ]
4573
4987
  }
4574
4988
  )
4575
4989
  ] }),
4576
4990
  /* @__PURE__ */ jsxs("div", { children: [
4577
- /* @__PURE__ */ jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: "Username" }),
4991
+ /* @__PURE__ */ jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: t.retroAchievements.username }),
4578
4992
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4579
4993
  /* @__PURE__ */ jsx(User, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500", size: 14 }),
4580
4994
  /* @__PURE__ */ jsx(
@@ -4583,7 +4997,7 @@ function LoginForm({
4583
4997
  type: "text",
4584
4998
  value: username,
4585
4999
  onChange: (e) => setUsername(e.target.value),
4586
- placeholder: "Your RA username",
5000
+ placeholder: t.retroAchievements.yourUsername,
4587
5001
  className: "w-full pl-8 pr-3 py-2 text-sm bg-black/50 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-yellow-500/50 focus:outline-none transition-colors",
4588
5002
  disabled: isLoading
4589
5003
  }
@@ -4591,7 +5005,7 @@ function LoginForm({
4591
5005
  ] })
4592
5006
  ] }),
4593
5007
  /* @__PURE__ */ jsxs("div", { children: [
4594
- /* @__PURE__ */ jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: "Password" }),
5008
+ /* @__PURE__ */ jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: t.retroAchievements.password }),
4595
5009
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4596
5010
  /* @__PURE__ */ jsx(Lock, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500", size: 14 }),
4597
5011
  /* @__PURE__ */ jsx(
@@ -4600,7 +5014,7 @@ function LoginForm({
4600
5014
  type: showPassword ? "text" : "password",
4601
5015
  value: password,
4602
5016
  onChange: (e) => setPassword(e.target.value),
4603
- placeholder: "Your RA password",
5017
+ placeholder: t.retroAchievements.yourPassword,
4604
5018
  className: "w-full pl-8 pr-9 py-2 text-sm bg-black/50 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-yellow-500/50 focus:outline-none transition-colors",
4605
5019
  disabled: isLoading
4606
5020
  }
@@ -4628,16 +5042,20 @@ function LoginForm({
4628
5042
  className: "w-full flex items-center justify-center gap-2 px-3 py-2.5 bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-400 hover:to-orange-400 text-black text-sm font-bold rounded-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed",
4629
5043
  children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
4630
5044
  /* @__PURE__ */ jsx(Loader2, { className: "animate-spin", size: 14 }),
4631
- /* @__PURE__ */ jsx("span", { children: "Connecting..." })
5045
+ /* @__PURE__ */ jsx("span", { children: t.retroAchievements.connecting })
4632
5046
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4633
5047
  /* @__PURE__ */ jsx(Trophy, { size: 14 }),
4634
- /* @__PURE__ */ jsx("span", { children: "Connect" })
5048
+ /* @__PURE__ */ jsx("span", { children: t.retroAchievements.login })
4635
5049
  ] })
4636
5050
  }
4637
5051
  ),
4638
5052
  /* @__PURE__ */ jsx("div", { className: "p-2 bg-blue-500/10 border border-blue-500/20 rounded-lg", children: /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-blue-300 leading-relaxed", children: [
4639
- /* @__PURE__ */ jsx("strong", { children: "\u{1F512} Privacy:" }),
4640
- " Your password is only used to authenticate with RetroAchievements. It is never stored."
5053
+ /* @__PURE__ */ jsxs("strong", { children: [
5054
+ "\u{1F512} ",
5055
+ t.retroAchievements.privacy
5056
+ ] }),
5057
+ " ",
5058
+ t.retroAchievements.privacyText
4641
5059
  ] }) })
4642
5060
  ] });
4643
5061
  }
@@ -4661,6 +5079,7 @@ function SettingsTab({
4661
5079
  progress,
4662
5080
  onLogout
4663
5081
  }) {
5082
+ const t = useKoinTranslation();
4664
5083
  return /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
4665
5084
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3 bg-black/30 rounded-lg border border-yellow-500/20", children: [
4666
5085
  /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-lg bg-gray-800 overflow-hidden border-2 border-yellow-500/50 flex-shrink-0", children: credentials.avatarUrl ? /* @__PURE__ */ jsx(
@@ -4730,7 +5149,7 @@ function SettingsTab({
4730
5149
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4731
5150
  /* @__PURE__ */ jsx(Shield, { className: hardcoreEnabled ? "text-red-500" : "text-gray-500", size: 16 }),
4732
5151
  /* @__PURE__ */ jsxs("div", { children: [
4733
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-white", children: "Hardcore Mode" }),
5152
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-white", children: t.retroAchievements.hardcore }),
4734
5153
  /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-500", children: "2x points, no savestates" })
4735
5154
  ] })
4736
5155
  ] }),
@@ -4748,11 +5167,11 @@ function SettingsTab({
4748
5167
  }
4749
5168
  )
4750
5169
  ] }),
4751
- hardcoreEnabled && /* @__PURE__ */ jsx("div", { className: "mt-2 p-2 bg-red-500/10 border border-red-500/20 rounded text-[10px] text-red-300", children: "Savestates, rewind, cheats & slow-mo disabled" })
5170
+ hardcoreEnabled && /* @__PURE__ */ jsx("div", { className: "mt-2 p-2 bg-red-500/10 border border-red-500/20 rounded text-[10px] text-red-300", children: t.common.disabledInHardcore })
4752
5171
  ] }),
4753
5172
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs px-1", children: [
4754
5173
  /* @__PURE__ */ jsx(CheckCircle, { className: "text-green-500", size: 12 }),
4755
- /* @__PURE__ */ jsx("span", { className: "text-green-400", children: "Connected" })
5174
+ /* @__PURE__ */ jsx("span", { className: "text-green-400", children: t.retroAchievements.connectedStatus })
4756
5175
  ] }),
4757
5176
  /* @__PURE__ */ jsxs(
4758
5177
  "button",
@@ -4761,7 +5180,7 @@ function SettingsTab({
4761
5180
  className: "w-full flex items-center justify-center gap-2 px-3 py-2 bg-gray-800 hover:bg-gray-700 text-white text-xs rounded-lg transition-colors border border-white/10",
4762
5181
  children: [
4763
5182
  /* @__PURE__ */ jsx(LogOut, { size: 14 }),
4764
- /* @__PURE__ */ jsx("span", { children: "Sign Out" })
5183
+ /* @__PURE__ */ jsx("span", { children: t.retroAchievements.logout })
4765
5184
  ]
4766
5185
  }
4767
5186
  )
@@ -4778,18 +5197,19 @@ function AchievementsTab({
4778
5197
  earnedPoints,
4779
5198
  totalPoints
4780
5199
  }) {
5200
+ const t = useKoinTranslation();
4781
5201
  if (!currentGame) {
4782
5202
  return /* @__PURE__ */ jsxs("div", { className: "p-6 text-center", children: [
4783
5203
  /* @__PURE__ */ jsx("div", { className: "w-12 h-12 mx-auto mb-3 rounded-full bg-gray-800 flex items-center justify-center", children: /* @__PURE__ */ jsx(Trophy, { className: "text-gray-600", size: 24 }) }),
4784
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: "No game loaded" }),
4785
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Load a game to see achievements" })
5204
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: t.retroAchievements.noGame }),
5205
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: t.retroAchievements.loadGame })
4786
5206
  ] });
4787
5207
  }
4788
5208
  if (achievements.length === 0) {
4789
5209
  return /* @__PURE__ */ jsxs("div", { className: "p-6 text-center", children: [
4790
5210
  /* @__PURE__ */ jsx("div", { className: "w-12 h-12 mx-auto mb-3 rounded-full bg-gray-800 flex items-center justify-center", children: /* @__PURE__ */ jsx(Trophy, { className: "text-gray-600", size: 24 }) }),
4791
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: "No achievements found" }),
4792
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "This game may not be supported" })
5211
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: t.retroAchievements.noAchievements }),
5212
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: t.retroAchievements.notSupported })
4793
5213
  ] });
4794
5214
  }
4795
5215
  return /* @__PURE__ */ jsxs("div", { children: [
@@ -4842,7 +5262,7 @@ function AchievementsTab({
4842
5262
  {
4843
5263
  onClick: () => setFilter(f),
4844
5264
  className: `px-2 py-0.5 text-[10px] rounded-full border transition-colors ${filter === f ? "bg-yellow-500/20 border-yellow-500/50 text-yellow-400" : "border-gray-700 text-gray-500 hover:text-gray-300"}`,
4845
- children: f.charAt(0).toUpperCase() + f.slice(1)
5265
+ children: t.retroAchievements.filters[f]
4846
5266
  },
4847
5267
  f
4848
5268
  )) })
@@ -4881,8 +5301,7 @@ function AchievementsTab({
4881
5301
  /* @__PURE__ */ jsxs("div", { className: "p-2 text-[10px] text-gray-500 text-center border-t border-gray-800/50", children: [
4882
5302
  progress,
4883
5303
  "% complete \u2022 ",
4884
- totalPoints - earnedPoints,
4885
- " pts remaining"
5304
+ t.retroAchievements.ptsRemaining.replace("{{count}}", (totalPoints - earnedPoints).toString())
4886
5305
  ] })
4887
5306
  ] });
4888
5307
  }
@@ -4901,6 +5320,7 @@ function RASidebar({
4901
5320
  achievements,
4902
5321
  unlockedIds
4903
5322
  }) {
5323
+ const t = useKoinTranslation();
4904
5324
  const defaultTab = isLoggedIn && currentGame && achievements.length > 0 ? "achievements" : "settings";
4905
5325
  const [activeTab, setActiveTab] = useState(defaultTab);
4906
5326
  const [filter, setFilter] = useState("all");
@@ -4926,7 +5346,7 @@ function RASidebar({
4926
5346
  e.preventDefault();
4927
5347
  setLocalError(null);
4928
5348
  if (!username.trim() || !password.trim()) {
4929
- setLocalError("Username and Password are required");
5349
+ setLocalError(t.retroAchievements.usernameRequired);
4930
5350
  return;
4931
5351
  }
4932
5352
  const success = await onLogin(username.trim(), password.trim());
@@ -4969,7 +5389,7 @@ function RASidebar({
4969
5389
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
4970
5390
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4971
5391
  /* @__PURE__ */ jsx(Trophy, { className: "text-yellow-400", size: 18 }),
4972
- /* @__PURE__ */ jsx("span", { className: "font-heading text-sm text-white", children: "RetroAchievements" })
5392
+ /* @__PURE__ */ jsx("span", { className: "font-heading text-sm text-white", children: t.retroAchievements.title })
4973
5393
  ] }),
4974
5394
  /* @__PURE__ */ jsx(
4975
5395
  "button",
@@ -4988,7 +5408,7 @@ function RASidebar({
4988
5408
  className: `flex-1 flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs rounded-md transition-colors ${activeTab === "achievements" ? "bg-yellow-500/20 text-yellow-400" : "text-gray-400 hover:text-white"}`,
4989
5409
  children: [
4990
5410
  /* @__PURE__ */ jsx(List, { size: 12 }),
4991
- "Achievements"
5411
+ t.retroAchievements.achievements
4992
5412
  ]
4993
5413
  }
4994
5414
  ),
@@ -4999,7 +5419,7 @@ function RASidebar({
4999
5419
  className: `flex-1 flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs rounded-md transition-colors ${activeTab === "settings" ? "bg-yellow-500/20 text-yellow-400" : "text-gray-400 hover:text-white"}`,
5000
5420
  children: [
5001
5421
  /* @__PURE__ */ jsx(Settings, { size: 12 }),
5002
- "Settings"
5422
+ t.settings.title
5003
5423
  ]
5004
5424
  }
5005
5425
  )
@@ -5046,7 +5466,7 @@ function RASidebar({
5046
5466
  }
5047
5467
  ) }),
5048
5468
  /* @__PURE__ */ jsxs("div", { className: "p-2 border-t border-gray-800 bg-gray-900/80 text-[10px] text-gray-500 text-center flex-shrink-0", children: [
5049
- "Powered by",
5469
+ t.retroAchievements.poweredBy,
5050
5470
  " ",
5051
5471
  /* @__PURE__ */ jsx(
5052
5472
  "a",
@@ -5656,22 +6076,36 @@ async function getCachedRom(romId) {
5656
6076
  }
5657
6077
  return null;
5658
6078
  }
6079
+ var pendingFetches = /* @__PURE__ */ new Map();
5659
6080
  async function fetchAndCacheRom(romId, url) {
5660
- const response = await fetch(url);
5661
- if (!response.ok) {
5662
- throw new Error(`Failed to fetch ROM: ${response.statusText}`);
6081
+ if (pendingFetches.has(romId)) {
6082
+ console.log(`[Cache] Joining in-flight fetch for ${romId}`);
6083
+ return pendingFetches.get(romId);
5663
6084
  }
5664
- const blob = await response.blob();
5665
- if (typeof caches !== "undefined") {
6085
+ const fetchPromise = (async () => {
5666
6086
  try {
5667
- const cache = await caches.open(CACHE_NAME);
5668
- await cache.put(romId, new Response(blob));
5669
- console.log(`[Cache] Cached ROM ${romId}`);
5670
- } catch (e) {
5671
- console.warn("[Cache] Write failed:", e);
6087
+ const cached = await getCachedRom(romId);
6088
+ if (cached) return cached;
6089
+ console.log(`[Cache] Fetching ROM ${romId}...`);
6090
+ const response = await fetch(url);
6091
+ if (!response.ok) throw new Error(`Failed to fetch ROM: ${response.statusText}`);
6092
+ const blob = await response.blob();
6093
+ if (typeof caches !== "undefined") {
6094
+ try {
6095
+ const cache = await caches.open(CACHE_NAME);
6096
+ await cache.put(romId, new Response(blob));
6097
+ console.log(`[Cache] Cached ROM ${romId}`);
6098
+ } catch (e) {
6099
+ console.warn("[Cache] Write failed:", e);
6100
+ }
6101
+ }
6102
+ return blob;
6103
+ } finally {
6104
+ pendingFetches.delete(romId);
5672
6105
  }
5673
- }
5674
- return blob;
6106
+ })();
6107
+ pendingFetches.set(romId, fetchPromise);
6108
+ return fetchPromise;
5675
6109
  }
5676
6110
 
5677
6111
  // src/hooks/emulator/useEmulatorCore.ts
@@ -6636,6 +7070,7 @@ function useVolume({
6636
7070
  };
6637
7071
  }
6638
7072
  function useControls(system, onNotify) {
7073
+ const t = useKoinTranslation();
6639
7074
  const defaultControls = getConsoleKeyboardDefaults(system || "SNES");
6640
7075
  const [controls, setControls] = useState(() => {
6641
7076
  if (typeof window !== "undefined") {
@@ -6650,13 +7085,13 @@ function useControls(system, onNotify) {
6650
7085
  const saveControls = useCallback((newControls) => {
6651
7086
  setControls(newControls);
6652
7087
  saveKeyboardMapping(newControls, system);
6653
- onNotify?.("Controls saved", "success");
6654
- }, [system, onNotify]);
7088
+ onNotify?.(t.notifications.controlsSaved, "success");
7089
+ }, [system, onNotify, t]);
6655
7090
  const resetToDefaults = useCallback(() => {
6656
7091
  setControls(defaultControls);
6657
7092
  saveKeyboardMapping(defaultControls, system);
6658
- onNotify?.("Controls reset to defaults", "info");
6659
- }, [defaultControls, system, onNotify]);
7093
+ onNotify?.(t.notifications.controlsReset, "info");
7094
+ }, [defaultControls, system, onNotify, t]);
6660
7095
  return {
6661
7096
  controls,
6662
7097
  saveControls,
@@ -6682,16 +7117,17 @@ function useGameSession(props) {
6682
7117
  canvasRef,
6683
7118
  showToast
6684
7119
  } = props;
7120
+ const t = useKoinTranslation();
6685
7121
  const { controls, saveControls } = useControls(system, showToast);
6686
7122
  const [gamepadModalOpen, setGamepadModalOpen] = useState(false);
6687
7123
  const [controlsModalOpen, setControlsModalOpen] = useState(false);
6688
7124
  const { gamepads, connectedCount } = useGamepad({
6689
7125
  onConnect: (gamepad) => {
6690
7126
  showToast(
6691
- gamepad.name || "Controller ready to use",
7127
+ gamepad.name || t.notifications.controllerReady,
6692
7128
  "gamepad",
6693
7129
  {
6694
- title: "Controller Connected",
7130
+ title: t.notifications.controllerConnected,
6695
7131
  duration: 4e3,
6696
7132
  action: {
6697
7133
  label: "Configure",
@@ -6702,10 +7138,11 @@ function useGameSession(props) {
6702
7138
  },
6703
7139
  onDisconnect: () => {
6704
7140
  showToast(
6705
- "Controller was disconnected",
7141
+ t.notifications.controllerDisconnected,
6706
7142
  "warning",
6707
7143
  {
6708
- title: "Controller Lost",
7144
+ title: t.notifications.controllerDisconnected,
7145
+ // Title repeats or generic? Using same for now
6709
7146
  duration: 3e3
6710
7147
  }
6711
7148
  );
@@ -6735,10 +7172,10 @@ function useGameSession(props) {
6735
7172
  if (arcadeSystems.includes(system.toLowerCase())) {
6736
7173
  setTimeout(() => {
6737
7174
  showToast(
6738
- "Press SHIFT to insert coin",
7175
+ t.notifications.insertCoin,
6739
7176
  "info",
6740
7177
  {
6741
- title: "\u{1FA99} Insert Coin",
7178
+ title: t.notifications.insertCoinTitle,
6742
7179
  duration: 5e3
6743
7180
  }
6744
7181
  );
@@ -6976,6 +7413,7 @@ function useGameSaves({
6976
7413
  onDeleteSaveState,
6977
7414
  autoSaveInterval
6978
7415
  }) {
7416
+ const t = useKoinTranslation();
6979
7417
  const [saveModalOpen, setSaveModalOpen] = useState(false);
6980
7418
  const [saveModalMode, setSaveModalMode] = useState("save");
6981
7419
  const [saveSlots, setSaveSlots] = useState([]);
@@ -7002,7 +7440,7 @@ function useGameSaves({
7002
7440
  setSaveSlots(slots);
7003
7441
  } catch (err) {
7004
7442
  console.error("Failed to fetch save slots:", err);
7005
- showToast("Failed to load save slots", "error");
7443
+ showToast(t.notifications.failedFetch, "error", { title: t.overlays.toast.error });
7006
7444
  } finally {
7007
7445
  setIsSlotLoading(false);
7008
7446
  }
@@ -7019,7 +7457,7 @@ function useGameSaves({
7019
7457
  const result = await nostalgist.saveStateWithBlob();
7020
7458
  if (result) {
7021
7459
  await onSaveState(0, result.blob, void 0);
7022
- showToast("State saved successfully", "success");
7460
+ showToast(t.notifications.saved, "success", { title: t.overlays.toast.saved });
7023
7461
  }
7024
7462
  });
7025
7463
  } else {
@@ -7033,7 +7471,7 @@ function useGameSaves({
7033
7471
  a.download = `${fileName}.state`;
7034
7472
  a.click();
7035
7473
  URL.revokeObjectURL(url);
7036
- showToast("State downloaded", "success");
7474
+ showToast(t.notifications.downloaded, "success", { title: t.overlays.toast.saved });
7037
7475
  }
7038
7476
  });
7039
7477
  }
@@ -7052,9 +7490,9 @@ function useGameSaves({
7052
7490
  await queueRef.current.add(async () => {
7053
7491
  await nostalgist.loadState(new Uint8Array(buffer));
7054
7492
  });
7055
- showToast("State loaded successfully", "success");
7493
+ showToast(t.notifications.loaded, "success", { title: t.overlays.toast.loaded });
7056
7494
  } else {
7057
- showToast("No save found", "error");
7495
+ showToast(t.notifications.noSaveFound, "error", { title: t.overlays.toast.error });
7058
7496
  }
7059
7497
  } else {
7060
7498
  const input = document.createElement("input");
@@ -7067,7 +7505,7 @@ function useGameSaves({
7067
7505
  await queueRef.current.add(async () => {
7068
7506
  await nostalgist.loadState(new Uint8Array(buffer));
7069
7507
  });
7070
- showToast("State loaded from file", "success");
7508
+ showToast(t.notifications.loadedFile, "success", { title: t.overlays.toast.loaded });
7071
7509
  }
7072
7510
  };
7073
7511
  input.click();
@@ -7092,14 +7530,14 @@ function useGameSaves({
7092
7530
  console.warn("Screenshot failed", e);
7093
7531
  }
7094
7532
  await onSaveState(slot, result.blob, screen);
7095
- showToast(`Saved to slot ${slot}`, "success");
7533
+ showToast(t.notifications.savedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.saved });
7096
7534
  setSaveModalOpen(false);
7097
7535
  resume();
7098
7536
  }
7099
7537
  });
7100
7538
  } catch (err) {
7101
7539
  console.error("Save failed:", err);
7102
- showToast("Failed to save", "error");
7540
+ showToast(t.notifications.failedSave, "error", { title: t.overlays.toast.error });
7103
7541
  } finally {
7104
7542
  setActioningSlot(null);
7105
7543
  }
@@ -7113,15 +7551,15 @@ function useGameSaves({
7113
7551
  await queueRef.current.add(async () => {
7114
7552
  await nostalgist.loadState(new Uint8Array(buffer));
7115
7553
  });
7116
- showToast(`Loaded from slot ${slot}`, "success");
7554
+ showToast(t.notifications.loadedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.loaded });
7117
7555
  setSaveModalOpen(false);
7118
7556
  resume();
7119
7557
  } else {
7120
- showToast("Empty slot", "error");
7558
+ showToast(t.notifications.emptySlot, "error", { title: t.overlays.toast.error });
7121
7559
  }
7122
7560
  } catch (err) {
7123
7561
  console.error("Load failed:", err);
7124
- showToast("Failed to load", "error");
7562
+ showToast(t.notifications.failedLoad, "error", { title: t.overlays.toast.error });
7125
7563
  } finally {
7126
7564
  setActioningSlot(null);
7127
7565
  }
@@ -7133,11 +7571,11 @@ function useGameSaves({
7133
7571
  setActioningSlot(slot);
7134
7572
  try {
7135
7573
  await onDeleteSaveState(slot);
7136
- showToast(`Deleted slot ${slot}`, "success");
7574
+ showToast(t.notifications.deletedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.saved });
7137
7575
  refreshSlots();
7138
7576
  } catch (err) {
7139
7577
  console.error("Delete failed:", err);
7140
- showToast("Failed to delete", "error");
7578
+ showToast(t.notifications.failedDelete, "error", { title: t.overlays.toast.error });
7141
7579
  } finally {
7142
7580
  setActioningSlot(null);
7143
7581
  }
@@ -7515,7 +7953,485 @@ var sendTelemetry = (eventName, params = {}) => {
7515
7953
  } catch (e) {
7516
7954
  }
7517
7955
  };
7518
- var GamePlayer = memo(function GamePlayer2(props) {
7956
+
7957
+ // src/locales/es.ts
7958
+ var es = {
7959
+ controls: {
7960
+ play: "Jugar",
7961
+ pause: "Pausar",
7962
+ reset: "Reiniciar",
7963
+ rewind: "Rebobinar",
7964
+ save: "Guardar",
7965
+ load: "Cargar",
7966
+ snap: "Capturar",
7967
+ rec: "Grabar",
7968
+ stopRec: "Detener",
7969
+ startRecord: "Comenzar grabaci\xF3n",
7970
+ stopRecord: "Detener grabaci\xF3n",
7971
+ mute: "Silenciar",
7972
+ unmute: "Activar sonido",
7973
+ help: "Ayuda",
7974
+ full: "Pantalla comp.",
7975
+ // Abbreviated
7976
+ keys: "Teclas",
7977
+ menuOpen: "Abrir men\xFA",
7978
+ menuClose: "Cerrar men\xFA",
7979
+ gamepadConnected: "{{count}} mando{{plural}} conectado(s) - haz clic para configurar",
7980
+ noGamepad: "No hay mando detectado - presiona cualquier bot\xF3n para conectar",
7981
+ press: "Pulsa",
7982
+ startBtn: "START",
7983
+ selectBtn: "SELECT"
7984
+ },
7985
+ common: {
7986
+ disabledInHardcore: "Desactivado en modo Hardcore",
7987
+ notSupported: "No compatible con esta consola",
7988
+ playToEnableRewind: "Juega unos segundos para activar el rebobinado"
7989
+ },
7990
+ settings: {
7991
+ title: "Ajustes",
7992
+ general: "General",
7993
+ audio: "Sonido",
7994
+ video: "V\xEDdeo",
7995
+ input: "Entrada",
7996
+ advanced: "Avanzado",
7997
+ fullscreen: "Pantalla completa",
7998
+ controls: "Controles",
7999
+ gamepad: "Mando",
8000
+ cheats: "Trucos",
8001
+ retroAchievements: "RetroAchievements",
8002
+ shortcuts: "Atajos",
8003
+ exit: "Salir",
8004
+ language: "Idioma",
8005
+ selectLanguage: "Seleccionar idioma"
8006
+ },
8007
+ overlay: {
8008
+ play: "JUGAR",
8009
+ systemFirmware: "FIRMWARE DEL SISTEMA",
8010
+ loading: "Cargando {{system}}",
8011
+ initializing: "Inicializando emulador",
8012
+ loadingSave: "Cargando partida",
8013
+ preparingSlot: "Preparando ranura {{num}}",
8014
+ systemError: "Error del sistema",
8015
+ failedInit: "Error al inicializar el emulador",
8016
+ retry: "Reintentar",
8017
+ slotReady: "Ranura {{num}} lista",
8018
+ paused: "Pausado"
8019
+ },
8020
+ notifications: {
8021
+ saved: "Estado guardado",
8022
+ loaded: "Estado cargado",
8023
+ error: "Error",
8024
+ recordingStarted: "Grabaci\xF3n iniciada",
8025
+ recordingSaved: "Grabaci\xF3n guardada",
8026
+ downloaded: "Estado descargado",
8027
+ loadedFile: "Estado cargado desde archivo",
8028
+ savedSlot: "Guardado en ranura {{num}}",
8029
+ loadedSlot: "Cargado desde ranura {{num}}",
8030
+ deletedSlot: "Ranura {{num}} eliminada",
8031
+ emptySlot: "Ranura vac\xEDa",
8032
+ noSaveFound: "No se encontr\xF3 partida guardada",
8033
+ failedSave: "Error al guardar",
8034
+ failedLoad: "Error al cargar",
8035
+ failedDelete: "Error al eliminar",
8036
+ failedFetch: "Error al cargar ranuras",
8037
+ controllerConnected: "Mando conectado",
8038
+ controllerDisconnected: "Mando desconectado",
8039
+ controllerReady: "Mando listo para usar",
8040
+ insertCoin: "Pulsa SHIFT para insertar moneda",
8041
+ insertCoinTitle: "\u{1FA99} Insertar Moneda",
8042
+ controlsSaved: "Controles guardados",
8043
+ controlsReset: "Controles restablecidos"
8044
+ },
8045
+ modals: {
8046
+ shortcuts: {
8047
+ title: "Atajos de teclado",
8048
+ playerShortcuts: "Atajos del reproductor",
8049
+ overlays: "Superposiciones",
8050
+ recording: "Grabaci\xF3n",
8051
+ showHelp: "Mostrar ayuda",
8052
+ perfOverlay: "Superposici\xF3n de rendimiento",
8053
+ inputDisplay: "Mostrar entrada",
8054
+ toggleRec: "Alternar grabaci\xF3n",
8055
+ toggleMute: "Alternar silencio",
8056
+ pressEsc: "Pulsa ESC para cerrar"
8057
+ },
8058
+ controls: {
8059
+ title: "Controles",
8060
+ keyboard: "Asignaci\xF3n de teclado",
8061
+ description: "Haz clic en un bot\xF3n y pulsa una tecla",
8062
+ pressKey: "Pulsa una tecla...",
8063
+ reset: "Restablecer",
8064
+ save: "Guardar controles"
8065
+ },
8066
+ gamepad: {
8067
+ title: "Ajustes de mando",
8068
+ noGamepad: "No se detecta mando",
8069
+ connected: "{{count}} mando{{s}} conectado(s)",
8070
+ none: "Ning\xFAn mando detectado",
8071
+ player: "Jugador:",
8072
+ noController: "Sin mando",
8073
+ pressAny: "Pulsa cualquier bot\xF3n para conectar",
8074
+ waiting: "Esperando entrada...",
8075
+ pressButton: "Pulsa bot\xF3n para {{button}}",
8076
+ pressEsc: "Pulsa Escape para cancelar",
8077
+ reset: "Restablecer",
8078
+ save: "Guardar ajustes"
8079
+ },
8080
+ cheats: {
8081
+ title: "Trucos",
8082
+ addCheat: "A\xF1adir truco",
8083
+ available: "{{count}} truco{{s}} disponible(s)",
8084
+ emptyTitle: "No hay trucos disponibles",
8085
+ emptyDesc: "No se encontraron c\xF3digos para este juego",
8086
+ copy: "Copiar c\xF3digo",
8087
+ active: "{{count}} truco{{s}} activo(s)",
8088
+ toggleHint: "Haz clic para activar/desactivar"
8089
+ },
8090
+ saveSlots: {
8091
+ title: "Guardar partida",
8092
+ saveTitle: "Guardar",
8093
+ loadTitle: "Cargar",
8094
+ emptySlot: "Ranura vac\xEDa",
8095
+ subtitleSave: "Elige una ranura para guardar",
8096
+ subtitleLoad: "Elige una ranura para cargar",
8097
+ loading: "Cargando partidas...",
8098
+ locked: "Ranura {{num}} bloqueada",
8099
+ upgrade: "Mejora para desbloquear m\xE1s ranuras",
8100
+ autoSave: "Autoguardado",
8101
+ autoSaveDesc: "Reservado para guardado autom\xE1tico",
8102
+ noData: "Sin datos",
8103
+ slot: "Ranura {{num}}",
8104
+ footerSave: "Las partidas se guardan en la nube y se sincronizan",
8105
+ footerLoad: "Tu progreso se restaurar\xE1 al punto seleccionado"
8106
+ },
8107
+ bios: {
8108
+ title: "Selecci\xF3n de BIOS",
8109
+ description: "Selecciona una BIOS para este juego",
8110
+ warningTitle: "Nota:",
8111
+ warning: "Cambiar la BIOS reiniciar\xE1 el emulador. Se perder\xE1 el progreso no guardado.",
8112
+ systemDefault: "Por defecto",
8113
+ active: "Activo",
8114
+ defaultDesc: "Usar BIOS interna del emulador",
8115
+ emptyTitle: "No se encontraron archivos de BIOS.",
8116
+ emptyDesc: "Sube archivos BIOS en tu biblioteca.",
8117
+ footer: "Firmware del sistema"
8118
+ }
8119
+ },
8120
+ retroAchievements: {
8121
+ title: "RetroAchievements",
8122
+ login: "Iniciar sesi\xF3n",
8123
+ logout: "Cerrar sesi\xF3n",
8124
+ username: "Usuario",
8125
+ password: "Password",
8126
+ hardcore: "Modo Hardcore",
8127
+ achievements: "Logros",
8128
+ locked: "Bloqueado",
8129
+ unlocked: "Desbloqueado",
8130
+ mastered: "Dominado",
8131
+ identifying: "Identificando juego...",
8132
+ achievementsAvailable: "{{count}} logros disponibles",
8133
+ gameNotSupported: "Conectado - Juego no en base de datos",
8134
+ connect: "Conectar RetroAchievements",
8135
+ connected: "RetroAchievements (conectado)",
8136
+ createAccount: "Crear cuenta",
8137
+ privacy: "Privacidad:",
8138
+ privacyText: "Tu contrase\xF1a solo se usa para autenticaci\xF3n con RA. No se almacena.",
8139
+ connecting: "Conectando...",
8140
+ connectAccount: "Conecta tu cuenta para rastrear logros",
8141
+ poweredBy: "Con la tecnolog\xEDa de",
8142
+ connectedStatus: "Conectado",
8143
+ yourUsername: "Tu usuario RA",
8144
+ yourPassword: "Tu contrase\xF1a RA",
8145
+ usernameRequired: "Usuario y contrase\xF1a requeridos",
8146
+ noGame: "Sin juego cargado",
8147
+ loadGame: "Carga un juego para ver los logros",
8148
+ noAchievements: "No se encontraron logros",
8149
+ notSupported: "Este juego puede no ser compatible",
8150
+ ptsRemaining: "{{count}} pts restantes",
8151
+ filters: {
8152
+ all: "Todos",
8153
+ locked: "Bloqueado",
8154
+ unlocked: "Desbloqueado"
8155
+ }
8156
+ },
8157
+ overlays: {
8158
+ performance: {
8159
+ title: "Estad\xEDsticas",
8160
+ fps: "FPS",
8161
+ frameTime: "FT",
8162
+ memory: "MEM",
8163
+ core: "Core",
8164
+ input: "ENTRADA",
8165
+ active: "ACTIVO"
8166
+ },
8167
+ toast: {
8168
+ saved: "Juego guardado",
8169
+ loaded: "Juego cargado",
8170
+ error: "Error"
8171
+ },
8172
+ recording: {
8173
+ started: "Grabaci\xF3n iniciada",
8174
+ stopped: "Grabaci\xF3n detenida",
8175
+ saved: "Grabaci\xF3n guardada",
8176
+ paused: "PAUSA",
8177
+ recording: "GRABANDO",
8178
+ resume: "Reanudar grabaci\xF3n",
8179
+ pause: "Pausar grabaci\xF3n",
8180
+ stop: "Detener y guardar",
8181
+ hover: "Controles"
8182
+ }
8183
+ }
8184
+ };
8185
+
8186
+ // src/locales/fr.ts
8187
+ var fr = {
8188
+ controls: {
8189
+ play: "Jouer",
8190
+ pause: "Pause",
8191
+ reset: "R\xE9initialiser",
8192
+ rewind: "Rembobiner",
8193
+ save: "Sauver",
8194
+ load: "Charger",
8195
+ snap: "Photo",
8196
+ rec: "Enr.",
8197
+ stopRec: "Arr\xEAter",
8198
+ startRecord: "D\xE9marrer l'enregistrement",
8199
+ stopRecord: "Arr\xEAter l'enregistrement",
8200
+ mute: "Muet",
8201
+ unmute: "Son",
8202
+ help: "Aide",
8203
+ full: "Plein \xE9cran",
8204
+ keys: "Touches",
8205
+ menuOpen: "Ouvrir menu",
8206
+ menuClose: "Fermer menu",
8207
+ gamepadConnected: "{{count}} manette{{plural}} connect\xE9e(s)",
8208
+ noGamepad: "Aucune manette d\xE9tect\xE9e",
8209
+ press: "Appuyez",
8210
+ startBtn: "START",
8211
+ selectBtn: "SELECT"
8212
+ },
8213
+ common: {
8214
+ disabledInHardcore: "D\xE9sactiv\xE9 en mode Hardcore",
8215
+ notSupported: "Non support\xE9 sur cette console",
8216
+ playToEnableRewind: "Jouez quelques secondes pour activer le rembobinage"
8217
+ },
8218
+ settings: {
8219
+ title: "Param\xE8tres",
8220
+ general: "G\xE9n\xE9ral",
8221
+ audio: "Audio",
8222
+ video: "Vid\xE9o",
8223
+ input: "Entr\xE9e",
8224
+ advanced: "Avanc\xE9",
8225
+ fullscreen: "Plein \xE9cran",
8226
+ controls: "Contr\xF4les",
8227
+ gamepad: "Manette",
8228
+ cheats: "Codes",
8229
+ retroAchievements: "Succ\xE8s",
8230
+ shortcuts: "Raccourcis",
8231
+ exit: "Quitter",
8232
+ language: "Langue",
8233
+ selectLanguage: "Choisir la langue"
8234
+ },
8235
+ overlay: {
8236
+ play: "JOUER",
8237
+ systemFirmware: "MICROLOGICIEL SYST\xC8ME",
8238
+ loading: "Chargement {{system}}",
8239
+ initializing: "Initialisation de l'\xE9mulateur",
8240
+ loadingSave: "Chargement de la sauvegarde",
8241
+ preparingSlot: "Pr\xE9paration de l'emplacement {{num}}",
8242
+ systemError: "Erreur syst\xE8me",
8243
+ failedInit: "\xC9chec de l'initialisation",
8244
+ retry: "R\xE9essayer",
8245
+ slotReady: "Emplacement {{num}} pr\xEAt",
8246
+ paused: "Pause"
8247
+ },
8248
+ notifications: {
8249
+ saved: "\xC9tat sauvegard\xE9",
8250
+ loaded: "\xC9tat charg\xE9",
8251
+ error: "Erreur",
8252
+ recordingStarted: "Enregistrement d\xE9marr\xE9",
8253
+ recordingSaved: "Enregistrement sauvegard\xE9",
8254
+ downloaded: "\xC9tat t\xE9l\xE9charg\xE9",
8255
+ loadedFile: "\xC9tat charg\xE9 depuis fichier",
8256
+ savedSlot: "Sauvegard\xE9 sur l'emplacement {{num}}",
8257
+ loadedSlot: "Charg\xE9 depuis l'emplacement {{num}}",
8258
+ deletedSlot: "Emplacement {{num}} supprim\xE9",
8259
+ emptySlot: "Emplacement vide",
8260
+ noSaveFound: "Aucune sauvegarde trouv\xE9e",
8261
+ failedSave: "\xC9chec de la sauvegarde",
8262
+ failedLoad: "\xC9chec du chargement",
8263
+ failedDelete: "\xC9chec de la suppression",
8264
+ failedFetch: "\xC9chec du chargement des emplacements",
8265
+ controllerConnected: "Manette connect\xE9e",
8266
+ controllerDisconnected: "Manette d\xE9connect\xE9e",
8267
+ controllerReady: "Manette pr\xEAte",
8268
+ insertCoin: "Appuyez sur SHIFT pour ins\xE9rer une pi\xE8ce",
8269
+ insertCoinTitle: "\u{1FA99} Ins\xE9rer Pi\xE8ce",
8270
+ controlsSaved: "Contr\xF4les sauvegard\xE9s",
8271
+ controlsReset: "Contr\xF4les r\xE9initialis\xE9s"
8272
+ },
8273
+ modals: {
8274
+ shortcuts: {
8275
+ title: "Raccourcis clavier",
8276
+ playerShortcuts: "Raccourcis du lecteur",
8277
+ overlays: "Superpositions",
8278
+ recording: "Enregistrement",
8279
+ showHelp: "Afficher l'aide",
8280
+ perfOverlay: "Overlay de performance",
8281
+ inputDisplay: "Afficher les entr\xE9es",
8282
+ toggleRec: "Basculer l'enregistrement",
8283
+ toggleMute: "Basculer le son",
8284
+ pressEsc: "Appuyez sur ESC pour fermer"
8285
+ },
8286
+ controls: {
8287
+ title: "Contr\xF4les",
8288
+ keyboard: "Configuration clavier",
8289
+ description: "Cliquez sur un bouton et appuyez sur une touche",
8290
+ pressKey: "Appuyez...",
8291
+ reset: "R\xE9initialiser",
8292
+ save: "Sauvegarder"
8293
+ },
8294
+ gamepad: {
8295
+ title: "Param\xE8tres manette",
8296
+ noGamepad: "Aucune manette",
8297
+ connected: "{{count}} manette{{s}} connect\xE9e(s)",
8298
+ none: "Aucune manette d\xE9tect\xE9e",
8299
+ player: "Joueur:",
8300
+ noController: "Aucune manette",
8301
+ pressAny: "Appuyez sur un bouton pour connecter",
8302
+ waiting: "En attente...",
8303
+ pressButton: "Appuyez pour {{button}}",
8304
+ pressEsc: "Echap pour annuler",
8305
+ reset: "R\xE9initialiser",
8306
+ save: "Sauvegarder"
8307
+ },
8308
+ cheats: {
8309
+ title: "Codes de triche",
8310
+ addCheat: "Ajouter un code",
8311
+ available: "{{count}} code{{s}} disponible(s)",
8312
+ emptyTitle: "Aucun code disponible",
8313
+ emptyDesc: "Aucun code trouv\xE9 pour ce jeu",
8314
+ copy: "Copier",
8315
+ active: "{{count}} actif(s)",
8316
+ toggleHint: "Cliquez pour activer/d\xE9sactiver"
8317
+ },
8318
+ saveSlots: {
8319
+ title: "Sauvegardes",
8320
+ saveTitle: "Sauvegarder",
8321
+ loadTitle: "Charger",
8322
+ emptySlot: "Vide",
8323
+ subtitleSave: "Choisir un emplacement",
8324
+ subtitleLoad: "Choisir une sauvegarde",
8325
+ loading: "Chargement...",
8326
+ locked: "Emplacement {{num}} verrouill\xE9",
8327
+ upgrade: "Am\xE9liorer pour plus d'emplacements",
8328
+ autoSave: "Auto-Sauvegarde",
8329
+ autoSaveDesc: "R\xE9serv\xE9 \xE0 la sauvegarde automatique",
8330
+ noData: "Aucune donn\xE9e",
8331
+ slot: "Emplacement {{num}}",
8332
+ footerSave: "Les sauvegardes sont synchronis\xE9es dans le cloud",
8333
+ footerLoad: "Votre progression sera restaur\xE9e"
8334
+ },
8335
+ bios: {
8336
+ title: "S\xE9lection BIOS",
8337
+ description: "S\xE9lectionnez un BIOS",
8338
+ warningTitle: "Note:",
8339
+ warning: "Changer le BIOS red\xE9marre l'\xE9mulateur. Progression non sauvegard\xE9e sera perdue.",
8340
+ systemDefault: "Par d\xE9faut",
8341
+ active: "Actif",
8342
+ defaultDesc: "Utiliser le BIOS par d\xE9faut",
8343
+ emptyTitle: "Aucun BIOS trouv\xE9.",
8344
+ emptyDesc: "T\xE9l\xE9versez des fichiers BIOS dans votre biblioth\xE8que.",
8345
+ footer: "Firmware syst\xE8me"
8346
+ }
8347
+ },
8348
+ retroAchievements: {
8349
+ title: "RetroAchievements",
8350
+ login: "Connexion",
8351
+ logout: "D\xE9connexion",
8352
+ username: "Utilisateur",
8353
+ password: "Mot de passe",
8354
+ hardcore: "Mode Hardcore",
8355
+ achievements: "Succ\xE8s",
8356
+ locked: "Verrouill\xE9",
8357
+ unlocked: "D\xE9verrouill\xE9",
8358
+ mastered: "Ma\xEEtris\xE9",
8359
+ identifying: "Identification...",
8360
+ achievementsAvailable: "{{count}} succ\xE8s disponibles",
8361
+ gameNotSupported: "Jeu non support\xE9 par RA",
8362
+ connect: "Connecter RetroAchievements",
8363
+ connected: "RetroAchievements (connect\xE9)",
8364
+ createAccount: "Cr\xE9er un compte",
8365
+ privacy: "Confidentialit\xE9:",
8366
+ privacyText: "Votre mot de passe n'est pas stock\xE9.",
8367
+ connecting: "Connexion...",
8368
+ connectAccount: "Connectez votre compte",
8369
+ poweredBy: "Propuls\xE9 par",
8370
+ connectedStatus: "Connect\xE9",
8371
+ yourUsername: "Votre utilisateur RA",
8372
+ yourPassword: "Votre mot de passe RA",
8373
+ usernameRequired: "Utilisateur et mot de passe requis",
8374
+ noGame: "Aucun jeu",
8375
+ loadGame: "Chargez un jeu pour voir les succ\xE8s",
8376
+ noAchievements: "Aucun succ\xE8s trouv\xE9",
8377
+ notSupported: "Ce jeu n'est peut-\xEAtre pas support\xE9",
8378
+ ptsRemaining: "{{count}} pts restants",
8379
+ filters: {
8380
+ all: "Tous",
8381
+ locked: "Verrouill\xE9s",
8382
+ unlocked: "D\xE9verrouill\xE9s"
8383
+ }
8384
+ },
8385
+ overlays: {
8386
+ performance: {
8387
+ title: "Stats",
8388
+ fps: "FPS",
8389
+ frameTime: "FT",
8390
+ memory: "MEM",
8391
+ core: "Core",
8392
+ input: "ENTR\xC9E",
8393
+ active: "ACTIF"
8394
+ },
8395
+ toast: {
8396
+ saved: "Sauvegard\xE9",
8397
+ loaded: "Charg\xE9",
8398
+ error: "Erreur"
8399
+ },
8400
+ recording: {
8401
+ started: "Enregistrement d\xE9marr\xE9",
8402
+ stopped: "Enregistrement arr\xEAt\xE9",
8403
+ saved: "Enregistrement sauvegard\xE9",
8404
+ paused: "PAUSE",
8405
+ recording: "ENR.",
8406
+ resume: "Reprendre",
8407
+ pause: "Pause",
8408
+ stop: "Arr\xEAter et sauver",
8409
+ hover: "Contr\xF4les"
8410
+ }
8411
+ }
8412
+ };
8413
+
8414
+ // src/lib/common-utils.ts
8415
+ function deepMerge2(target, source) {
8416
+ const isObject = (obj) => obj && typeof obj === "object";
8417
+ if (!isObject(target) || !isObject(source)) {
8418
+ return source;
8419
+ }
8420
+ const output = { ...target };
8421
+ Object.keys(source).forEach((key) => {
8422
+ const targetValue = output[key];
8423
+ const sourceValue = source[key];
8424
+ if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
8425
+ output[key] = sourceValue;
8426
+ } else if (isObject(targetValue) && isObject(sourceValue)) {
8427
+ output[key] = deepMerge2(targetValue, sourceValue);
8428
+ } else {
8429
+ output[key] = sourceValue;
8430
+ }
8431
+ });
8432
+ return output;
8433
+ }
8434
+ var GamePlayerInner = memo(function GamePlayerInner2(props) {
7519
8435
  const { settings, updateSettings, isLoaded: settingsLoaded } = usePlayerPersistence();
7520
8436
  useEffect(() => {
7521
8437
  sendTelemetry("game_start", {
@@ -7526,6 +8442,7 @@ var GamePlayer = memo(function GamePlayer2(props) {
7526
8442
  }, [props.system, props.core, props.title]);
7527
8443
  const [biosModalOpen, setBiosModalOpen] = useState(false);
7528
8444
  const [showShortcutsModal, setShowShortcutsModal] = useState(false);
8445
+ const [settingsModalOpen, setSettingsModalOpen] = useState(false);
7529
8446
  const effectiveShader = props.shader !== void 0 ? props.shader : settings.shader;
7530
8447
  const {
7531
8448
  // Refs
@@ -7660,6 +8577,10 @@ var GamePlayer = memo(function GamePlayer2(props) {
7660
8577
  pause();
7661
8578
  setGamepadModalOpen(true);
7662
8579
  }, [pause, setGamepadModalOpen]);
8580
+ const handleShowSettings = useCallback(() => {
8581
+ pause();
8582
+ setSettingsModalOpen(true);
8583
+ }, [pause, setSettingsModalOpen]);
7663
8584
  const handleExitClick = useCallback(() => {
7664
8585
  onExit?.();
7665
8586
  }, [onExit]);
@@ -7871,6 +8792,7 @@ var GamePlayer = memo(function GamePlayer2(props) {
7871
8792
  systemColor,
7872
8793
  gamepadCount: connectedCount,
7873
8794
  onGamepadSettings: handleShowGamepadSettings,
8795
+ onSettings: handleShowSettings,
7874
8796
  volume,
7875
8797
  isMuted: muted,
7876
8798
  onVolumeChange: handleVolumeChange,
@@ -7936,7 +8858,11 @@ var GamePlayer = memo(function GamePlayer2(props) {
7936
8858
  setBiosModalOpen,
7937
8859
  availableBios: props.availableBios,
7938
8860
  currentBiosId: props.currentBiosId,
7939
- onSelectBios: props.onSelectBios
8861
+ onSelectBios: props.onSelectBios,
8862
+ settingsModalOpen,
8863
+ setSettingsModalOpen,
8864
+ currentLanguage: props.currentLanguage,
8865
+ onLanguageChange: props.onLanguageChange
7940
8866
  }
7941
8867
  ),
7942
8868
  !isMobile && /* @__PURE__ */ jsx(
@@ -7964,6 +8890,27 @@ var GamePlayer = memo(function GamePlayer2(props) {
7964
8890
  }
7965
8891
  ) });
7966
8892
  });
8893
+ var GamePlayer = memo(function GamePlayer2(props) {
8894
+ const [currentLanguage, setCurrentLanguage] = useState(props.initialLanguage || "en");
8895
+ const effectiveTranslations = useMemo(() => {
8896
+ const base = currentLanguage === "es" ? es : currentLanguage === "fr" ? fr : en;
8897
+ if (props.translations) {
8898
+ return deepMerge2(base, props.translations);
8899
+ }
8900
+ return base;
8901
+ }, [currentLanguage, props.translations]);
8902
+ const handleLanguageChange = useCallback((lang) => {
8903
+ setCurrentLanguage(lang);
8904
+ }, []);
8905
+ return /* @__PURE__ */ jsx(KoinI18nProvider, { translations: effectiveTranslations, children: /* @__PURE__ */ jsx(
8906
+ GamePlayerInner,
8907
+ {
8908
+ ...props,
8909
+ currentLanguage,
8910
+ onLanguageChange: handleLanguageChange
8911
+ }
8912
+ ) });
8913
+ });
7967
8914
  var GamePlayer_default = GamePlayer;
7968
8915
  function AchievementPopup({
7969
8916
  achievement,
@@ -8125,7 +9072,7 @@ var ShaderSelector = memo(function ShaderSelector2({
8125
9072
  ] });
8126
9073
  });
8127
9074
  var ShaderSelector_default = ShaderSelector;
8128
- var SHORTCUTS2 = [
9075
+ var SHORTCUTS = [
8129
9076
  { key: "F1", description: "Help" },
8130
9077
  { key: "F3", description: "FPS Overlay" },
8131
9078
  { key: "F4", description: "Input Display" },
@@ -8160,7 +9107,7 @@ var ShortcutsReference = memo(function ShortcutsReference2({
8160
9107
  ]
8161
9108
  }
8162
9109
  ),
8163
- isExpanded && /* @__PURE__ */ jsx("div", { className: "px-3 pb-3 pt-1 space-y-1.5 border-t border-white/10", children: SHORTCUTS2.map(({ key, description }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
9110
+ isExpanded && /* @__PURE__ */ jsx("div", { className: "px-3 pb-3 pt-1 space-y-1.5 border-t border-white/10", children: SHORTCUTS.map(({ key, description }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
8164
9111
  /* @__PURE__ */ jsx("span", { className: "text-white/60", children: description }),
8165
9112
  /* @__PURE__ */ jsx(
8166
9113
  "kbd",
@@ -8181,6 +9128,6 @@ var ShortcutsReference = memo(function ShortcutsReference2({
8181
9128
  });
8182
9129
  var ShortcutsReference_default = ShortcutsReference;
8183
9130
 
8184
- export { ALL_BUTTONS, AchievementPopup, BUTTON_GROUPS, BUTTON_LABELS, CONSOLE_CAPABILITIES, CONSOLE_KEYBOARD_OVERRIDES, DEFAULT_CONTROLS, DEFAULT_GAMEPAD, DEFAULT_KEYBOARD, DPAD_BUTTONS, FACE_BUTTONS, GamePlayer_default as GamePlayer, PERFORMANCE_TIER_1_SYSTEMS, PERFORMANCE_TIER_2_SYSTEMS, RASidebar, RA_MEDIA_BASE, SHADER_PRESETS, SHOULDER_BUTTONS, STANDARD_AXIS_MAP, STANDARD_GAMEPAD_BUTTONS, STICK_BUTTONS, SUPPORTED_EXTENSIONS, SYSTEMS, SYSTEM_BUTTONS, ShaderSelector_default as ShaderSelector, ShortcutsReference_default as ShortcutsReference, TRIGGER_BUTTONS, ToastContainer, buildRetroArchConfig, clearAllControls, consoleHasButton, detectControllerBrand, detectSystem, formatGamepadButton, formatKeyCode, gamepadToRetroArchConfig, getAchievementBadgeUrl, getConsoleButtons, getConsoleCapabilities, getConsoleKeyboardDefaults, getCore, getDBSystemNames, getFullGamepadMapping, getFullKeyboardMapping, getSupportedExtensions, getSystem, getSystemByDbName, getSystemByKey, getSystemFromExtension, getSystemsList, getUserAvatarUrl, isSystemSupported, keyboardToRetroArchConfig, loadAllGamepadMappings, loadGamepadMapping, loadKeyboardMapping, normalizeSystemKey, saveGamepadMapping, saveKeyboardMapping, systemsMatch, useGameRecording, useGamepad, useNostalgist, useToast };
9131
+ export { ALL_BUTTONS, AchievementPopup, BUTTON_GROUPS, BUTTON_LABELS, CONSOLE_CAPABILITIES, CONSOLE_KEYBOARD_OVERRIDES, DEFAULT_CONTROLS, DEFAULT_GAMEPAD, DEFAULT_KEYBOARD, DPAD_BUTTONS, FACE_BUTTONS, GamePlayer_default as GamePlayer, KoinI18nProvider, PERFORMANCE_TIER_1_SYSTEMS, PERFORMANCE_TIER_2_SYSTEMS, RASidebar, RA_MEDIA_BASE, SHADER_PRESETS, SHOULDER_BUTTONS, STANDARD_AXIS_MAP, STANDARD_GAMEPAD_BUTTONS, STICK_BUTTONS, SUPPORTED_EXTENSIONS, SYSTEMS, SYSTEM_BUTTONS, ShaderSelector_default as ShaderSelector, ShortcutsReference_default as ShortcutsReference, TRIGGER_BUTTONS, ToastContainer, buildRetroArchConfig, clearAllControls, consoleHasButton, detectControllerBrand, detectSystem, en, es, fetchAndCacheRom, formatGamepadButton, formatKeyCode, fr, gamepadToRetroArchConfig, getAchievementBadgeUrl, getCachedRom, getConsoleButtons, getConsoleCapabilities, getConsoleKeyboardDefaults, getCore, getDBSystemNames, getFullGamepadMapping, getFullKeyboardMapping, getSupportedExtensions, getSystem, getSystemByDbName, getSystemByKey, getSystemFromExtension, getSystemsList, getUserAvatarUrl, isSystemSupported, keyboardToRetroArchConfig, loadAllGamepadMappings, loadGamepadMapping, loadKeyboardMapping, normalizeSystemKey, saveGamepadMapping, saveKeyboardMapping, systemsMatch, useGameRecording, useGamepad, useKoinTranslation, useNostalgist, useToast };
8185
9132
  //# sourceMappingURL=index.mjs.map
8186
9133
  //# sourceMappingURL=index.mjs.map