diff --git a/skeleton/SYSTEM/res/lang/en.lang b/skeleton/SYSTEM/res/lang/en.lang new file mode 100644 index 000000000..cb0e30f3a --- /dev/null +++ b/skeleton/SYSTEM/res/lang/en.lang @@ -0,0 +1,1068 @@ +# NextUI English strings (reference / fallback) +# Format: key=value +# Lines starting with # and empty lines are ignored. +# Trailing whitespace is stripped. + +# --------------------------------------------------------------------- +# Button hints (short labels next to button glyphs) +# --------------------------------------------------------------------- +btn.back=BACK +btn.okay=OKAY +btn.open=OPEN +btn.resume=RESUME +btn.remove=REMOVE +btn.sleep=SLEEP +btn.menu=MENU +btn.power=POWER +btn.info=INFO +btn.mute=MUTE +btn.unmute=UNMUTE +btn.show_all=SHOW ALL +btn.show_locked=SHOW LOCKED + +# --------------------------------------------------------------------- +# Main launcher (nextui) +# --------------------------------------------------------------------- +launcher.recents=Recents +launcher.games=Games +launcher.wifi=Wifi +launcher.bluetooth=Bluetooth +launcher.sleep=Sleep +launcher.reboot=Reboot +launcher.poweroff=Poweroff +launcher.no_recents=No Recents +launcher.empty_folder=Empty folder +launcher.no_preview=No Preview + +# --------------------------------------------------------------------- +# In-game menu (minarch / ma_menu) +# --------------------------------------------------------------------- +menu.continue=Continue +menu.save=Save +menu.load=Load +menu.options=Options +menu.quit=Quit +menu.reset=Reset +menu.empty_slot=Empty Slot +menu.no_preview=No Preview +menu.screenshot_saved=Screenshot saved + +# Options sub-menu +menu.opt.frontend=Frontend +menu.opt.emulator=Emulator +menu.opt.shaders=Shaders +menu.opt.cheats=Cheats +menu.opt.controls=Controls +menu.opt.shortcuts=Shortcuts +menu.opt.achievements=Achievements +menu.opt.save_changes=Save Changes + +# Save changes sub-menu +menu.opt.save_for_console=Save for console +menu.opt.save_for_game=Save for game +menu.opt.restore_defaults=Restore defaults + +# Save-state config status +state.cfg.defaults=Using defaults. +state.cfg.console=Using console config. +state.cfg.game=Using game config. + +state.msg.saved_console=Saved for console. +state.msg.saved_game=Saved for game. +state.msg.restored_console=Restored console defaults. +state.msg.restored_defaults=Restored defaults. + +# Controls / set hint +controls.set_hint=Press A to set and X to clear. +controls.controller=Controller +controls.controller_desc=Select the type of controller. + +# Cheats / shaders / extras error states +error.no_options_category=This category has no options. +error.no_options_core=This core has no options. +error.no_extras=No extra settings found +error.no_shaders=No shaders available\n/Shaders folder or shader files not found +error.no_cheat_file=No cheat file loaded.\n\n +error.cheats_disabled_hardcore=Cheats disabled in Hardcore mode + +# Achievements (RetroAchievements) +ach.no_for_game=No achievements found for this game.\n\nThis ROM may need a compatibility patch\nor may not be a supported version.\n\nVisit retroachievements.org to check\nsupported game files. +ach.no_available=No achievements available for this game.\n\nThis game may not have achievements yet.\n\nVisit retroachievements.org for details. +ach.failed_load=Failed to load achievements +ach.not_available=Achievement list not available +ach.none_found=No achievements found +ach.offline_pending=Unlocked offline - pending sync +ach.muted_silenced=Muted - progress notifications silenced + +# --------------------------------------------------------------------- +# Settings (settings.cpp) +# --------------------------------------------------------------------- +# Top-level sections +settings.section.appearance=Appearance +settings.section.display=Display +settings.section.audio=Audio +settings.section.system=System +settings.section.power=Power +settings.section.network=Network +settings.section.language=Language +settings.section.advanced=Advanced + +# Common buttons +settings.reset_defaults=Reset to defaults +settings.reset_defaults_desc=Resets all options in this menu to their default values. + +# Appearance +settings.appearance.font=Font +settings.appearance.font_desc=The font to render all UI text. +settings.appearance.show_battery_pct=Show battery percentage +settings.appearance.show_battery_pct_desc=Show battery level as percent in the status pill +settings.appearance.show_anim=Show menu animations +settings.appearance.show_anim_desc=Enable or disable menu animations +settings.appearance.transitions=Menu transitions +settings.appearance.transitions_desc=Style of animated transition when navigating menus +settings.appearance.art_radius=Game art corner radius +settings.appearance.art_radius_desc=Set the radius for the rounded corners of game art +settings.appearance.art_width=Game art width +settings.appearance.art_width_desc=Set the percentage of screen width used for game art.\nUI elements might overrule this to avoid clipping. +settings.appearance.show_folder_names=Show folder names at root +settings.appearance.show_folder_names_desc=Show folder names at root directory +settings.appearance.show_recents=Show Recents +settings.appearance.show_recents_desc=Show "Recently Played" menu entry in game list. +settings.appearance.show_tools=Show Tools +settings.appearance.show_tools_desc=Show "Tools" menu entry in game list. +settings.appearance.show_art=Show game art +settings.appearance.show_art_desc=Show game artwork in the main menu +settings.appearance.folder_bg=Use folder background for ROMs +settings.appearance.folder_bg_desc=If enabled, used the emulator background image. Otherwise uses the default. +settings.appearance.qs_ui=Show Quickswitcher UI +settings.appearance.qs_ui_desc=Show/hide Quickswitcher UI elements.\nWhen hidden, will only draw background images. + +# Display +settings.display.brightness=Brightness +settings.display.brightness_desc=Display brightness (0 to 10) +settings.display.colortemp=Color temperature +settings.display.colortemp_desc=Color temperature (0 to 40) +settings.display.contrast=Contrast +settings.display.contrast_desc=Contrast enhancement (-4 to 5) +settings.display.saturation=Saturation +settings.display.saturation_desc=Saturation enhancement (-5 to 5) +settings.display.exposure=Exposure +settings.display.exposure_desc=Exposure enhancement (-4 to 5) + +# Audio +settings.audio.volume=Volume +settings.audio.volume_desc=Speaker volume +settings.audio.volume_muted=Muted + +# System +settings.system.screen_timeout=Screen timeout +settings.system.screen_timeout_desc=Period of inactivity before screen turns off (0-600s) +settings.system.suspend_timeout=Suspend timeout +settings.system.suspend_timeout_desc=Time before device goes to sleep after screen is off (5-600s) +settings.system.haptic=Haptic feedback +settings.system.haptic_desc=Enable or disable haptic feedback on certain actions in the OS +settings.system.default_view=Default view +settings.system.default_view_desc=The initial view to show on boot +settings.system.view.content_list=Content List +settings.system.view.game_switcher=Game Switcher +settings.system.view.quick_menu=Quick Menu +settings.system.clock_24h=Show 24h time format +settings.system.clock_24h_desc=Show clock in the 24hrs time format +settings.system.show_clock=Show clock +settings.system.show_clock_desc=Show clock in the status pill +settings.system.auto_time=Set time and date automatically +settings.system.auto_time_desc=Automatically adjust system time\nwith NTP (requires internet access) +settings.system.timezone=Time zone +settings.system.timezone_desc=Your time zone +settings.system.save_format=Save format +settings.system.save_format_desc=The save format to use.\nMinUI: Game.gba.sav, Retroarch: Game.srm, Generic: Game.sav +settings.system.save_state_format=Save state format +settings.system.save_state_format_desc=The save state format to use. MinUI: Game.st0, \nRetroarch-ish: Game.state.0, Retroarch: Game.state0 +settings.system.extracted_name=Use extracted file name +settings.system.extracted_name_desc=Use the extracted file name instead of the archive name.\nOnly applies to cores that do not handle archives natively +settings.system.safe_poweroff=Safe poweroff +settings.system.safe_poweroff_desc=Bypasses the stock shutdown procedure to avoid the "limbo bug".\nInstructs the PMIC directly to soft disconnect the battery. +settings.system.fan_speed=Fan Speed +settings.system.fan_speed_desc=Select the fan speed percentage (Quiet/Normal/Performance or 0-100%) +settings.system.fan.performance=Performance +settings.system.fan.normal=Normal +settings.system.fan.quiet=Quiet + +# Colors +settings.color.main=Main Color +settings.color.main_desc=The color used to render main UI elements. +settings.color.primary=Primary Accent Color +settings.color.primary_desc=The color used to highlight important things in the user interface. +settings.color.secondary=Secondary Accent Color +settings.color.secondary_desc=A secondary highlight color. +settings.color.hint=Hint info Color +settings.color.hint_desc=Color for button hints and info +settings.color.list_text=List Text +settings.color.list_text_desc=List text color +settings.color.list_text_selected=List Text Selected +settings.color.list_text_selected_desc=List selected text color +settings.color.background=Background Color +settings.color.background_desc=Background color used when no background image is set. + +# Generic values +val.on=On +val.off=Off +val.never=Never +val.unchanged=Unchanged +val.snappy=Snappy +val.comfy=Comfy +val.fullscreen=Fullscreen +val.fit=Fit +val.fill=Fill + +# --------------------------------------------------------------------- +# Language menu (new) +# --------------------------------------------------------------------- +settings.language.title=Language +settings.language.desc=UI language for menus and notifications. +settings.language.restart_hint=Some labels update after a reboot. + +# --------------------------------------------------------------------- +# Bluetooth / WiFi menus +# --------------------------------------------------------------------- +bt.title=Bluetooth +bt.scan=Scan for devices +bt.devices=Devices +bt.connecting=Connecting... +bt.connected=Connected +bt.disconnected=Disconnected +bt.failed=Connection failed +bt.pairing=Pairing... +bt.paired=Paired +bt.forget=Forget device + +wifi.title=WiFi +wifi.scan=Scan networks +wifi.networks=Networks +wifi.connecting=Connecting... +wifi.connected=Connected +wifi.disconnected=Disconnected +wifi.failed=Connection failed +wifi.password=Password +wifi.forget=Forget network + +# ===================================================================== +# Phase 2: full settings.cpp coverage +# ===================================================================== + +# --- Audio / volume / fan / mute common labels --- +val.muted=Muted + +# --- Save format labels --- +val.fmt.minui=MinUI (default) +val.fmt.retroarch_compressed=Retroarch (compressed) +val.fmt.retroarch_uncompressed=Retroarch (uncompressed) +val.fmt.generic=Generic +val.fmt.retroarch_ish_compressed=Retroarch-ish (compressed) +val.fmt.retroarch_ish_uncompressed=Retroarch-ish (uncompressed) + +# --- Notifications menu --- +settings.notif.title=Notifications +settings.notif.save_states=Save states +settings.notif.save_states_desc=Show notification when saving game state +settings.notif.load_states=Load states +settings.notif.load_states_desc=Show notification when loading game state +settings.notif.screenshots=Screenshots +settings.notif.screenshots_desc=Show notification when taking a screenshot +settings.notif.vol_display=Vol / Display Adjustments +settings.notif.vol_display_desc=Show overlay for volume, brightness,\nand color temp adjustments +settings.notif.duration=Duration +settings.notif.duration_desc=How long notifications stay on screen + +# --- FN switch / mute submenu --- +settings.fn.title=FN Switch +settings.fn.main=FN switch +settings.fn.main_desc=FN switch settings +settings.mute.volume_toggled=Volume when toggled +settings.mute.volume_toggled_desc=Speaker volume (0-20) +settings.mute.fn_disables_led=FN switch disables LED +settings.mute.fn_disables_led_desc=Switch will also disable LEDs +settings.mute.brightness_toggled=Brightness when toggled +settings.mute.brightness_toggled_desc=Display brightness (0 to 10) +settings.mute.colortemp_toggled=Color temperature when toggled +settings.mute.colortemp_toggled_desc=Color temperature (0 to 40) +settings.mute.contrast_toggled=Contrast when toggled +settings.mute.contrast_toggled_desc=Contrast enhancement (-4 to 5) +settings.mute.saturation_toggled=Saturation when toggled +settings.mute.saturation_toggled_desc=Saturation enhancement (-5 to 5) +settings.mute.exposure_toggled=Exposure when toggled +settings.mute.exposure_toggled_desc=Exposure enhancement (-4 to 5) +settings.mute.turbo.a=Turbo fire A +settings.mute.turbo.a_desc=Enable turbo fire A +settings.mute.turbo.b=Turbo fire B +settings.mute.turbo.b_desc=Enable turbo fire B +settings.mute.turbo.x=Turbo fire X +settings.mute.turbo.x_desc=Enable turbo fire X +settings.mute.turbo.y=Turbo fire Y +settings.mute.turbo.y_desc=Enable turbo fire Y +settings.mute.turbo.l1=Turbo fire L1 +settings.mute.turbo.l1_desc=Enable turbo fire L1 +settings.mute.turbo.l2=Turbo fire L2 +settings.mute.turbo.l2_desc=Enable turbo fire L2 +settings.mute.turbo.r1=Turbo fire R1 +settings.mute.turbo.r1_desc=Enable turbo fire R1 +settings.mute.turbo.r2=Turbo fire R2 +settings.mute.turbo.r2_desc=Enable turbo fire R2 +settings.mute.dpad_mode=Dpad mode when toggled +settings.mute.dpad_mode_desc=Dpad: default. Joystick: Dpad exclusively acts as analog stick.\nBoth: Dpad and Joystick inputs at the same time. +val.dpad=Dpad +val.joystick=Joystick +val.both=Both + +# --- RetroAchievements menu --- +settings.ra.title=RetroAchievements +settings.ra.in_game=RetroAchievements +settings.ra.in_game_desc=Achievement tracking settings +settings.ra.enable=Enable Achievements +settings.ra.enable_desc=Enable RetroAchievements integration +settings.ra.username=Username +settings.ra.username_desc=RetroAchievements username +settings.ra.password=Password +settings.ra.password_desc=RetroAchievements password +settings.ra.not_set=(not set) +settings.ra.authenticate=Authenticate +settings.ra.authenticate_desc=Test credentials and retrieve API token +settings.ra.err_credentials=Error: Username and password required +settings.ra.authenticating=Authenticating... +settings.ra.authenticated_as=Authenticated as %s +settings.ra.err_prefix=Error: %s +settings.ra.status=Status +settings.ra.status_desc=Authentication status +settings.ra.status_yes=Authenticated +settings.ra.status_no=Not authenticated +settings.ra.show_notif=Show Notifications +settings.ra.show_notif_desc=Show achievement unlock notifications +settings.ra.notif_duration=Notification Duration +settings.ra.notif_duration_desc=How long achievement notifications stay on screen +settings.ra.progress_duration=Progress Duration +settings.ra.progress_duration_desc=Duration for progress updates (top-left). Off to disable. +settings.ra.sort=Achievement Sort Order +settings.ra.sort_desc=How achievements are sorted in the in-game menu +settings.ra.sync=Sync Offline Unlocks +settings.ra.sync_pending_fmt=%u pending — send to RA server +settings.ra.sync_no_pending=No pending unlocks +settings.ra.sync_not_auth=Not authenticated +settings.ra.sync_inprogress_fmt=Syncing %u achievement%s...\n\n(0/%u) +settings.ra.sync_progress_fmt=Syncing achievements...\n\n(%u/%u) +settings.ra.sync_cancelling=Cancelling sync... +settings.ra.sync_cancelled=Sync cancelled +settings.ra.sync_cancelled_partial_fmt=Cancelled: %u of %u synced +settings.ra.sync_incomplete_fmt=Incomplete: %u synced, retry later +settings.ra.sync_synced_fmt=Synced %u achievement%s + +# RA sort labels +ra.sort.unlocked_first=Unlocked First +ra.sort.display_first=Display Order (First) +ra.sort.display_last=Display Order (Last) +ra.sort.won_most=Won By (Most) +ra.sort.won_least=Won By (Least) +ra.sort.points_most=Points (Most) +ra.sort.points_least=Points (Least) +ra.sort.title_az=Title (A-Z) +ra.sort.title_za=Title (Z-A) +ra.sort.type_asc=Type (Asc) +ra.sort.type_desc=Type (Desc) + +# --- Default view --- +settings.system.view.content_list=Content List + +# --- In-Game menu --- +settings.ingame.title=In-Game +settings.ingame.main=In-Game +settings.ingame.main_desc=In-game settings for MinArch +settings.ingame.notifications=Notifications +settings.ingame.notifications_desc=Save state notifications + +# --- About menu --- +settings.about.title=About +settings.about.main=About +settings.about.version=NextUI version +settings.about.platform=Platform +settings.about.os_version=Stock OS version +settings.about.busybox=Busybox version +settings.about.busybox_missing=BusyBox version not found. + +# --- Main menu --- +settings.main.appearance=Appearance +settings.main.appearance_desc=UI customization +settings.main.display=Display +settings.main.system=System +settings.main.network=Network +settings.main.bluetooth=Bluetooth + +# --- Stock OS overlay --- +settings.stock_warning=Stock OS changes detected.\nThis may cause instability or issues.\nIf you experience problems, please consider\nreverting to clean stock firmware. + +# --- Scaling labels (audio/video) --- +val.fullscreen=Fullscreen +val.fit=Fit +val.fill=Fill + +# ===================================================================== +# Phase 3: btmenu.cpp + wifimenu.cpp +# ===================================================================== + +# Both menus share the "Network" top-level slot. +bt.network=Network +bt.toggle=Bluetooth +bt.toggle_desc=Enable/disable Bluetooth +bt.diagnostics=Bluetooth diagnostics +bt.diagnostics_desc=Enable/disable Bluetooth logging +bt.samplerate_max=Maximum sampling rate +bt.samplerate_max_desc=44100 Hz: better compatibility\n48000 Hz: better quality +bt.enabling=Enabling Bluetooth... +bt.disabling=Disabling Bluetooth... +bt.options=Options +bt.pair=Pair +bt.pair_desc=Pair this device. +bt.pairing_overlay=Pairing... +bt.forget_desc=Forget this device. +bt.connect=Connect +bt.connect_desc=Connect this device. +bt.connecting_overlay=Connecting... +bt.disconnect=Disconnect +bt.disconnect_desc=Disconnect this device. + +wifi.network=Network +wifi.toggle=WiFi +wifi.toggle_desc=Enable/disable WiFi +wifi.diagnostics=WiFi diagnostics +wifi.diagnostics_desc=Enable/disable WiFi logging +wifi.enabling=Enabling WiFi... +wifi.disabling=Disabling WiFi... +wifi.options=Options +wifi.connect_known=Connect +wifi.connect_known_desc=Connect to this network. +wifi.enter_pass=Enter WiFi passcode +wifi.enter_pass_desc=Connect to this network. +wifi.connecting_overlay=Connecting... +wifi.password_prompt=Enter Wifi passcode +wifi.forget_desc=Removes credentials for this network. + +# ===================================================================== +# Phase 4: complete coverage (all remaining modules) +# ===================================================================== + +# Hardcore save/state messages +save.load_disabled_hc=Load states disabled in Hardcore mode +save.save_disabled_hc=Save states disabled in Hardcore mode + +# RA / achievements extra notifications +ach.game_mastered=Game Mastered! + +# Common extra button hints +btn.cancel=CANCEL +btn.set=SET +btn.apply=APPLY +btn.exit=EXIT +btn.enter=ENTER +btn.copy=COPY +btn.fine=FINE +btn.coarse=COARSE +btn.scroll=SCROLL +btn.up_down=U/D +btn.left_right=L/R +btn.zoom=ZOOM +btn.select_light=Select light +btn.hr_24=24 HOUR +btn.hr_12=12 HOUR +btn.ok=OK + +# Clock app +clock.am=AM +clock.pm=PM +# Date field order. Values: YMD (ISO/default), DMY (FR/UK/...), MDY (US). +clock.date_order=YMD + +# Game time tracker +gametime.total=TOTAL +gametime.average=AVERAGE +gametime.plays=# PLAYS +gametime.title_fmt=Time spent having fun: %s + +# Generic empty / no-content fallback (reused across menus) +generic.empty_folder=Empty folder + +# Keyboard prompt special keys +kb.shift=shift +kb.space=space +kb.enter=enter + +# LED control app +led.select_light=Select light +led.effect=Effect +led.color=Color +led.color2=Color2 +led.speed=Speed +led.brightness=Brightness +led.brightness_all=Brightness (All Leds) +led.info_brightness=Info brightness +led.info_brightness_all=Info brightness (All Leds) +led.trigger=Trigger +led.f1_key=F1 key +led.f2_key=F2 key +led.top_bar=Top bar +led.lr_triggers=L&R triggers +led.joystick_l=Joystick L +led.joystick_r=Joystick R +led.logo=Logo +# LED effect names — kept untranslated unless target language has natural equivalents. +led.effect.linear=Linear +led.effect.breathe=Breathe +led.effect.interval_breathe=Interval Breathe +led.effect.static=Static +led.effect.blink1=Blink 1 +led.effect.blink2=Blink 2 +led.effect.blink3=Blink 3 +led.effect.rainbow=Rainbow +led.effect.twinkle=Twinkle +led.effect.fire=Fire +led.effect.glitter=Glitter +led.effect.neonglow=NeonGlow +led.effect.firefly=Firefly +led.effect.aurora=Aurora +led.effect.reactive=Reactive +led.effect.topbar_rainbow=Topbar Rainbow +led.effect.topbar_night=Topbar night +led.effect.lr_rainbow=LR Rainbow +led.effect.lr_reactive=LR Reactive + +# minput tester labels for special non-letter buttons +minput.vol_minus=VOL. - +minput.vol_plus=VOL. + +minput.quit=QUIT + +# Hardware hints (api.c GFX_blitHardwareGroup / GFX_blitHardwareHints) +hw.brightness=BRIGHTNESS +hw.color_temp=COLOR TEMP +hw.mnu=MNU +hw.brght=BRGHT +hw.sel=SEL +hw.cltmp=CLTMP + +# Misc fallback values +val.none=none + +# ===================================================================== +# Phase 5: in-game Frontend options (ma_config.c) +# ===================================================================== +frontend.opt.screen_scaling=Screen Scaling +frontend.opt.screen_scaling.desc_overscan=Native uses integer scaling. Aspect uses core nreported aspect ratio.\nAspect screen uses screen aspect ratio\n Fullscreen has non-square\npixels. Cropped is integer scaled then cropped. +frontend.opt.screen_scaling.desc_default=Native uses integer scaling.\nAspect uses core reported aspect ratio.\nAspect screen uses screen aspect ratio\nFullscreen has non-square pixels. +frontend.opt.resampling=Audio Resampling Quality +frontend.opt.resampling.desc=Resampling quality higher takes more CPU +frontend.opt.ambient=Ambient Mode +frontend.opt.ambient.desc=Makes your leds follow on screen colors +frontend.opt.effect=Screen Effect +frontend.opt.effect.desc=Grid simulates an LCD grid.\nLine simulates CRT scanlines.\nEffects usually look best at native scaling. +frontend.opt.overlay=Overlay +frontend.opt.overlay.desc=Choose a custom overlay png from the Overlays folder +frontend.opt.offset_x=Offset screen X +frontend.opt.offset_x.desc=Offset X pixels +frontend.opt.offset_y=Offset screen Y +frontend.opt.offset_y.desc=Offset Y pixels +frontend.opt.sharpness=Screen Sharpness +frontend.opt.sharpness.desc=LINEAR smooths lines, but works better when final image is at higher resolution, so either core that outputs higher resolution or upscaling with shaders +frontend.opt.core_sync=Core Sync +frontend.opt.core_sync.desc=Choose what should be used as a\nreference for the frame rate.\n"Native" uses the emulator frame rate,\n"Screen" uses the frame rate of the screen. +frontend.opt.cpu_speed=CPU Speed +frontend.opt.cpu_speed.desc=Choose how the CPU scales.\nAuto is recommended for most users. +frontend.opt.debug_hud=Debug HUD +frontend.opt.debug_hud.desc=Show frames per second, cpu load,\nresolution, and scaler information. +frontend.opt.max_ff=Max FF Speed +frontend.opt.max_ff.desc=Fast forward will not exceed the\nselected speed (but may be less\ndepending on game and emulator). +frontend.opt.ff_audio=Fast forward audio +frontend.opt.ff_audio.desc=Play or mute audio when fast forwarding. +frontend.opt.rewind=Rewind +frontend.opt.rewind.desc=Enable in-memory rewind buffer.\nMust set a shortcut to access rewind during gameplay.\nUses extra CPU and memory. +frontend.opt.rewind_buffer=Rewind Buffer (MB) +frontend.opt.rewind_buffer.desc=Memory reserved for rewind snapshots.\nIncrease for longer rewind times. +frontend.opt.rewind_interval=Rewind Interval +frontend.opt.rewind_interval.desc=Interval between rewind snapshots.\nShorter intervals improve smoothness during rewind,\nbut increase CPU and memory usage. +frontend.opt.rewind_compression=Rewind Compression +frontend.opt.rewind_compression.desc=Compress rewind snapshots to save memory at the cost of CPU. +frontend.opt.rewind_compression_speed=Rewind Compression Speed +frontend.opt.rewind_compression_speed.desc=LZ4 acceleration used for rewind snapshots.\nLower values compress more but use more CPU. +frontend.opt.rewind_audio=Rewind audio +frontend.opt.rewind_audio.desc=Play or mute audio when rewinding. +frontend.opt.opt_shaders=Optional Shaders Settings +frontend.opt.opt_shaders.desc=If shaders have extra settings they will show up in this settings menu +frontend.opt.shader_preset=Shader / Emulator Settings Preset +frontend.opt.shader_preset.desc=Load a premade shaders/emulators config.\nTo try out a preset, exit the game without saving settings! +frontend.opt.shader_count=Number of Shaders +frontend.opt.shader_count.desc=Number of shaders 1 to 3 +frontend.opt.shader_1=Shader 1 +frontend.opt.shader_1.desc=Shader 1 program to run +frontend.opt.shader_1_filter=Shader 1 Filter +frontend.opt.shader_1_source=Shader 1 Source type +frontend.opt.shader_1_texture=Shader 1 Texture Type +frontend.opt.shader_1_scale=Shader 1 Scale +frontend.opt.shader_2=Shader 2 +frontend.opt.shader_2.desc=Shader 2 program to run +frontend.opt.shader_2_filter=Shader 2 Filter +frontend.opt.shader_2_source=Shader 2 Source type +frontend.opt.shader_2_texture=Shader 2 Texture Type +frontend.opt.shader_2_scale=Shader 2 Scale +frontend.opt.shader_3=Shader 3 +frontend.opt.shader_3.desc=Shader 3 program to run +frontend.opt.shader_3_filter=Shader 3 Filter +frontend.opt.shader_3_source=Shader 3 Source type +frontend.opt.shader_3_texture=Shader 3 Texture Type +frontend.opt.shader_3_scale=Shader 3 Scale +frontend.opt.shader_filter.desc=Method of upscaling, NEAREST or LINEAR +frontend.opt.shader_source.desc=This will choose resolution source to scale from +frontend.opt.shader_scale.desc=This will scale images x times,\nscreen scales to screens resolution (can hit performance) + +# Frontend option labels +frontend.lbl.scaling.native=Native +frontend.lbl.scaling.aspect=Aspect +frontend.lbl.scaling.aspect_screen=Aspect Screen +frontend.lbl.scaling.fullscreen=Fullscreen +frontend.lbl.scaling.cropped=Cropped +frontend.lbl.resample.low=Low +frontend.lbl.resample.medium=Medium +frontend.lbl.resample.high=High +frontend.lbl.resample.max=Max +frontend.lbl.ambient.all=All +frontend.lbl.ambient.top=Top +frontend.lbl.ambient.fn=FN +frontend.lbl.ambient.lr=LR +frontend.lbl.ambient.top_lr=Top/LR +frontend.lbl.effect.none=None +frontend.lbl.effect.line=Line +frontend.lbl.effect.grid=Grid +frontend.lbl.overlay.none=None +frontend.lbl.rewind_compression.best=1 (best ratio) +frontend.lbl.rewind_compression.default=2 (default) +frontend.lbl.rewind_compression.fast=4 (fast) +frontend.lbl.rewind_compression.faster=8 (faster) +frontend.lbl.rewind_compression.fastest=12 (fastest) + +# Root menu folder labels (main launcher) +launcher.tools=Tools +launcher.recently_played=Recently Played +launcher.collections=Collections + +# Pak / folder display names — translated via Entry_new fallthrough. +# Rule of thumb: translate generic descriptive names (Files, Gallery, +# Settings, ...) — they map cleanly across languages and read as +# function labels, not brand names. Keep author-chosen brand names +# untouched (Aesthetics, ScrapeGoat, LED'oh!, Pak Store, Updater, ...) +# so the rest of the ecosystem (Discord, README, screenshots) keeps +# referring to them consistently. +Settings=Settings +Battery=Battery +Clock=Clock +Game Tracker=Game Tracker +Bootlogo=Bootlogo +Input=Input +LedControl=LedControl +USB Mass Storage=USB Mass Storage +Files=Files +Gallery=Gallery + +# Battery tool (workspace/all/battery) +battery.calculating=calculating +battery.since_charge_fmt=Since Charge: %s +battery.current_fmt=Current: %s +battery.remaining_fmt=Remaining: %s +battery.longest_fmt=Longest: %s +battery.usage_fmt=Battery usage: Last %s +battery.range.4h=4 hours +battery.range.8h=8 hours +battery.range.16h=16 hours + +# minui-presenter (used by Gallery, Artwork Scraper, Another Cheat Downloader, ...) +mp.btn.action=ACTION +mp.btn.select=SELECT +mp.btn.back=BACK +mp.btn.other=OTHER +# Retranslate common pak-supplied labels so third-party paks inherit +# localisation without needing to change their launch.sh. +EXIT=EXIT +QUIT=QUIT +OK=OK +OKAY=OKAY +BACK=BACK +CANCEL=CANCEL +RETURN=RETURN +APPLY=APPLY +CONFIRM=CONFIRM +DELETE=DELETE +YES=YES +NO=NO + +# Common --message strings emitted by paks (Gallery, ...) +No screenshots found=No screenshots found +Screenshots directory not found=Screenshots directory not found + +# Pak Store (https://github.com/LoveRetro/nextui-pak-store) +ps.btn.continue=Continue +ps.btn.quit=Quit +ps.btn.select=Select +ps.btn.view=View +ps.btn.confirm=Confirm +ps.btn.back=Back +ps.btn.cancel=Cancel +ps.btn.install=Install +ps.btn.update=Update +ps.btn.uninstall=Uninstall +ps.btn.cycle=Cycle +ps.btn.save=Save +ps.btn.settings=Settings +ps.btn.nevermind=Nevermind +ps.btn.yes=Yes +ps.menu.updates_fmt=Available Updates (%d) +ps.menu.browse=Browse +ps.menu.manage=Manage Installed +ps.no_paks=No Paks Available +ps.title.browse=Browse Paks +ps.title.manage=Manage Installed Paks +ps.title.updates=Available Pak Updates +ps.title.settings=Settings +ps.settings.platform_filter=Platform Filter +ps.settings.platform_filter.match=Match Device +ps.settings.platform_filter.all=All +ps.settings.debug_level=Debug Level +ps.settings.debug_level.error=Error +ps.settings.debug_level.info=Info +ps.settings.debug_level.debug=Debug +ps.settings.discover=Discover Existing Installs +ps.settings.info=Info +ps.toggle.on=On +ps.toggle.off=Off +ps.info.version=Version +ps.info.commit=Commit +ps.info.build_date=Build Date +ps.info.community.title=Community Shout Out +ps.info.community.body=Pak Store exists because of the incredible NextUI community. Your creativity, passion, and dedication to building amazing paks is what makes this platform special. Every emulator, tool, and enhancement you create brings joy to our retro doo-dads! Thank you for sharing your talents and making NextUI better for everyone. +ps.info.github_repo=GitHub Repository +ps.pak.whats_new_fmt=What's new in %s? +ps.pak.description=Description +ps.pak.screenshots=Screenshots +ps.pak.info=Pak Info +ps.pak.author=Author +ps.pak.version=Version +ps.pak.current_version=Current Version +ps.pak.changelog=Changelog +ps.pak.repository=Pak Repository +ps.updates.update_all=Update All +ps.uninstall.confirm_fmt=Are you sure that you want to uninstall\n %s? +ps.uninstall.in_progress_fmt=Uninstalling %s... +ps.uninstall.error_fmt=Unable to uninstall %s +ps.install.success_fmt=%s Installed! +ps.install.extract_error_fmt=Failed to extract %s +ps.update.success_fmt=%s Updated! +ps.update.overview_fmt=The following %d paks will be updated! +ps.update.overview_title=Update Overview +ps.update.title_fmt=Update %d Paks +ps.update.download_error_fmt=Failed to download %s +ps.update.extract_error_fmt=Failed to extract %s +ps.update.pak_store_restarting=Pak Store Updated! Restarting... +ps.update.pak_store_exiting=Pak Store Updated! Exiting... +ps.update.all_success=All paks updated successfully! +ps.script.running_fmt=Running %s Script... +ps.unzip.in_progress_fmt=Unzipping %s... +ps.unzip.error_fmt=Unable to unzip %s +ps.category.experimental=Experimental +ps.experimental_unlocked=Experimental Paks Unlocked.\nUse at your own risk!\nMake sure you have backups! +ps.storefront_error=Could not load the Storefront!\nMake sure you are connected to Wi-Fi.\nIf this issue persists, check the logs. +ps.splash.fetching=Fetching Storefront... +# Storefront categories (matched on lower-snake-case of the JSON name) +ps.cat.customization=Customization +ps.cat.developer_tools=Developer Tools +ps.cat.emulators=Emulators +ps.cat.file_management=File Management +ps.cat.media=Media +ps.cat.miscellaneous_tools=Miscellaneous Tools +ps.cat.ports=Ports +ps.cat.rom_management=ROM Management +ps.cat.streaming_game_clients=Streaming Game Clients +ps.cat.system=System +ps.btn.close=Close +ps.btn.cancel_download=Cancel Download +ps.btn.cancel_all_downloads=Cancel All Downloads +ps.btn.show_speed=Show Speed +ps.btn.hide_speed=Hide Speed +ps.download.title_fmt=Downloading %s %s... + +# ScrapeGoat (https://github.com/Helaas/nextui-scrapegoat-pak) +sg.menu.artwork=Artwork +sg.menu.cheats=Cheats +sg.menu.manuals=Manuals +sg.menu.settings=Settings +sg.menu.api_usage=API Usage +sg.btn.quit=QUIT +sg.btn.select=SELECT +sg.btn.back=BACK +sg.btn.continue=CONTINUE +sg.btn.ok=OK +sg.btn.cancel=CANCEL +sg.btn.open=OPEN +sg.btn.filter=FILTER +sg.btn.queue_all=QUEUE ALL +sg.btn.track_progress=TRACK PROGRESS +sg.btn.retry=RETRY +sg.btn.yes=YES +sg.btn.no=NO +sg.btn.save=SAVE +sg.btn.start=START +sg.btn.stop=STOP +sg.btn.scan=SCAN +sg.btn.apply=APPLY +sg.btn.confirm=CONFIRM +sg.btn.edit=EDIT +sg.btn.keep_open=KEEP OPEN +sg.title.settings=Settings +sg.title.artwork_priority=Artwork Priority +sg.title.region_priority=Region Priority +sg.settings.username=Username +sg.settings.password=Password +sg.settings.artwork_options=Artwork Options +sg.settings.manual_dir=Manual download directory +sg.settings.clear_cheat_cache=Clear cheat cache +sg.settings.include_hidden=Include hidden/disabled/empty ROMs +sg.settings.artwork_priority=Artwork priority +sg.settings.region_priority=Region priority +sg.toggle.on=On +sg.toggle.off=Off +sg.queue_all.missing_only=Queue missing only +sg.queue_all.redownload_all=Re-download all (including installed) +sg.queue_all.already_installed_fmt=%d already installed +sg.quit.keep_open=Keep ScrapeGoat Open +sg.quit.exit_cancel=Exit and Cancel Downloads +sg.quit.exit_bg=Exit to Background +sg.quit.title_one=%d item still queued +sg.quit.title_many_fmt=%d items still queued +sg.value.set=(set) +sg.value.not_set=(not set) +sg.progress.track=Track Progress +sg.progress.with_total_fmt=Track Progress (%d/%d) +sg.progress.with_failed_fmt=Track Progress (%d/%d, %d failed) +sg.filter.all=All +sg.filter.missing=Missing +sg.filter.installed=Installed +sg.mode.artwork=artwork +sg.mode.cheats=cheats +sg.mode.manuals=manuals +sg.type.artwork=Artwork +sg.type.cheat=Cheat +sg.type.manual=Manual +sg.info.status=Status +sg.info.system=System +sg.info.type=Type +sg.btn.queue=QUEUE +sg.btn.queued=QUEUED +sg.btn.redownload=RE-DOWNLOAD +sg.queue.added_fmt=Queued %d ROMs for %s. +sg.queue.queued_fmt=Queued "%s" for %s. +sg.queue.requeued_fmt=Re-queued "%s" for %s. +sg.queue.already_queued=Already queued. +sg.error.no_roms=No ROMs found in this system. +sg.error.no_rom_folders=No ROM folders found. +sg.error.no_systems_artwork=No systems with artwork support found. +sg.error.no_systems_cheat=No systems with cheat support found. +sg.error.no_systems_manual=No systems with manual support found. +sg.error.oom=Out of memory. +sg.brief.no_api_data=No API data available yet. Stats update after the first artwork search. +sg.title.api_usage=API Usage +sg.api.requests_today=Requests Today +sg.api.daily_limit=Daily Limit +sg.api.remaining=Remaining +sg.api.threads=Threads +sg.title.progress=Progress +sg.title.bg_scraping=Background Scraping +sg.title.artwork_options=Artwork Options +sg.title.error=Error +sg.queue.empty=NO ITEMS IN QUEUE. +sg.queue.empty_filter=NO ITEMS MATCH THIS FILTER. +sg.queue.filter.all=ALL +sg.queue.filter.busy=BUSY +sg.queue.filter.done=DONE +sg.queue.filter.fail=FAIL +sg.status.queued=Queued +sg.status.searching=Searching... +sg.status.downloading=Downloading... +sg.status.cloning=Cloning db... +sg.status.matching=Matching... +sg.status.done=Done +sg.status.not_found=Not Found +sg.status.error=Error +sg.status.skipped=Skipped +sg.btn.reorder=REORDER +sg.btn.done=DONE +sg.btn.clear=CLEAR +sg.btn.exit=EXIT +sg.error.not_found_libretro=Not found in libretro database +sg.error.not_found_screenscraper=Not found in ScreenScraper.fr database +sg.error.unknown=An unknown error occurred +sg.error.queue_active_clear=Wait for the queue to finish before clearing the cheat cache. +sg.error.clear_failed=Failed to clear the cheat cache. +sg.error.cache_cleared_stale=Cheat cache was cleared, but the in-memory queue state\ncould not be refreshed.\n\nRestart the app before downloading\ncheats again. +sg.error.bg_stop_failed=Failed to stop background scraping. +sg.error.bg_stop_timeout=Background scraping did not stop in time. +sg.error.handoff_failed=Failed to request foreground takeover. +sg.error.handoff_timeout=Background scraping did not hand off in time. +sg.error.bg_queue_lost=Background scraping stopped, but the queue\ncould not be restored. +sg.error.bg_start_failed=Failed to start background\nscraping. Try exiting normally. +sg.warn.bg_still_active=Background scraping is still active.\n\nClose ScrapeGoat and reopen it to retry\nforeground takeover. +sg.warn.no_internet=No internet connection detected.\n\nPreviously downloaded cheats can\nstill be used offline.\n\nConnect to wifi to scrape artwork/\nmanuals or download new cheats. +sg.warn.no_credentials=No ScreenScraper.fr user credentials set.\n\nScraping will proceed at basic rate\n(~1 req/min, single-threaded).\n\nFor much faster speeds, go to Settings\nand add your username and password. +sg.warn.no_manual_dir=Manual download directory not set.\n\nGo to Settings and set a download\ndirectory for manuals.\n\nYou'll also need a Pak like\nSDLReader to view downloaded PDFs. +sg.dialog.cancel_all=Cancel all downloads?\n\nIn-progress items will be stopped\nand pending items will be skipped. +sg.dialog.clear_cheat_cache=Clear the downloaded cheat database?\n\nThis deletes the local git checkout.\nIt will be re-downloaded on next use. +sg.dialog.stop_bg=Stop background scraping?\n\nIn-progress items will be stopped\nand pending items will be skipped. +sg.dialog.exit_cancel=Exit ScrapeGoat and cancel all downloads?\n\nIn-progress items will stop and queued items\nwill be skipped. +sg.dialog.bg_warning=Background scraping keeps running\nafter ScrapeGoat closes.\n\nThis may reduce game performance.\n\nSleep pauses downloads.\nPower off stops them.\n\nProgress appears next time\nyou open ScrapeGoat. +sg.dialog.bg_warning_compact=Background scraping keeps running\nafter ScrapeGoat closes.\nThis may reduce game performance.\nSleep pauses downloads.\nPower off stops them.\nProgress appears next time\nyou open ScrapeGoat. +sg.progress.clearing_cache=Clearing cheat cache... +sg.daemon.resuming=Resuming background scraping... +sg.daemon.summary_fmt=%s.\n\n%d done, %d failed out of %d total. +sg.daemon.completed_label=Background scraping completed +sg.tag.queued=queued +sg.tag.searching=searching +sg.tag.downloading=downloading +sg.tag.cloning=cloning +sg.tag.matching=matching +sg.tag.art=art +sg.tag.cht=cht +sg.tag.pdf=pdf +sg.cheat.fallback_name_fmt=Cheat %d +sg.cheat.list_title_fmt=Cheats (%d) +sg.tag.disabled=[disabled] +sg.error.cache_inspect_failed_fmt=Failed to inspect cache entry:\n%s +sg.error.cache_open_failed_fmt=Failed to open cache directory:\n%s +sg.error.cache_rmdir_failed_fmt=Failed to remove cache directory:\n%s +sg.error.cache_unlink_failed_fmt=Failed to remove cache file:\n%s +sg.error.cheat_cache_open_failed_fmt=Failed to open cheat cache directory:\n%s +sg.error.cheat_cache_rmdir_failed_fmt=Failed to remove cheat cache directory:\n%s + +# Aesthetics (https://github.com/redria7/nextui-aesthetics) +ae.menu.download_themes=Download Themes +ae.menu.manage_themes=Manage Available Themes +ae.menu.manage_current=Manage Current Theme +ae.menu.decorations=Set Wallpapers & Icons +ae.btn.quit=Quit +ae.btn.settings=Settings +ae.btn.select=Select +ae.btn.cancel=Cancel +ae.btn.back=Back +ae.btn.options=Options +ae.btn.open_folder=Open Folder +ae.btn.decoration_options=Decoration Options +ae.btn.delete=Delete +ae.btn.confirm=Confirm +ae.btn.save=Save +ae.btn.apply=Apply +ae.btn.clear=Clear +ae.btn.yes=Yes +ae.btn.no=No +ae.btn.cycle=Cycle +ae.btn.changed_mind=I Changed My Mind +ae.btn.remove=Remove +ae.btn.download=Download +ae.btn.redownload=Re-Download +ae.btn.open=Open +ae.btn.swap_aggregation=Swap Aggregation +ae.btn.toggle=Toggle +ae.title.settings=Aesthetics Settings +ae.title.component_options=Component Options +ae.title.download_themes=Downloadable Themes +ae.title.hidden_themes=Hidden Themes +ae.title.manage_themes=Manage Themes +ae.title.manage_components_prefix=Manage +ae.title.manage_components_suffix=Components +ae.empty.no_decorations=No Decorations Found +ae.empty.no_components=No supported components! +ae.empty.no_hidden_themes=No themes hidden! +ae.empty.no_themes_manage=No themes to manage! Save or download some! +ae.help.decoration_list=Decoration List Controls +ae.help.component_options=Component Management Options +ae.help.component_management=Component Management Controls +ae.help.directory_list=Directory List Controls +ae.help.deco_apply=Open confirmation screen to apply the selected decoration +ae.help.deco_delete=Open confirmation screen to delete the selected decoration +ae.help.deco_open=Open selected aggregation to view available decorations +ae.help.deco_swap=Change aggregation style to view console or directory groupings +ae.help.save_components=Save selected components into a theme +ae.help.revert_components=Revert selected components to default settings +ae.help.apply_components=Apply selected components to device +ae.help.delete_components=Delete selected components from the theme +ae.help.toggle=Toggle a component to include in the selected action +ae.settings.log_level=Log Level +ae.settings.log_level.debug=Debug +ae.settings.log_level.error=Error +ae.settings.decoration_aggregation=Decoration Aggregation +ae.settings.aggregation.directory=On Directory +ae.settings.aggregation.console=On Console +ae.info.file_type=File Type +ae.info.author=Author +ae.info.last_updated=Last Updated +ae.deco.icon=Icon +ae.deco.wallpaper=Wallpaper +ae.deco.list_wallpaper=List Wallpaper +ae.label.current=Current +ae.progress.unzipping=Unzipping +ae.progress.downloading=Downloading +ae.progress.preview=Preview +ae.error.fetch_dirs=Unable to fetch directories! +# Theme component option labels (keys are the English const values from manage_theme_component_options.go) +Save | All=Save | All +Save | + Active Consoles=Save | + Active Consoles +Save | All | With Confirmations=Save | All | With Confirmations +Revert to Default | + Active Consoles=Revert to Default | + Active Consoles +Revert to Default | Empty Consoles Only=Revert to Default | Empty Consoles Only +Revert to Default | All=Revert to Default | All +Revert to Default | All | With Confirmations=Revert to Default | All | With Confirmations +Clear & Apply | + Active Consoles=Clear & Apply | + Active Consoles +Clear & Apply | All=Clear & Apply | All +Apply | + Active Consoles=Apply | + Active Consoles +Apply | All=Apply | All +Apply | Missing Only | + Active Consoles=Apply | Missing Only | + Active Consoles +Apply | Missing Only | All=Apply | Missing Only | All +Apply | All | With Confirmations=Apply | All | With Confirmations +Clear Wallpaper=Clear Wallpaper +Select Wallpaper=Select Wallpaper +Clear List Wallpaper=Clear List Wallpaper +Select List Wallpaper=Select List Wallpaper +Clear Default Wallpaper=Clear Default Wallpaper +Select Default Wallpaper=Select Default Wallpaper +Clear Icon=Clear Icon +Select Icon=Select Icon +Delete Theme=Delete Theme +Rename Theme=Rename Theme +ae.dl.refresh_catalog=Refresh Available Themes +ae.dl.hidden_themes=Hidden Themes +ae.dl.help_no_entries=If no entries are found, check internet connection +ae.dl.help_a=View details of a theme with option to download +ae.dl.help_x=Move theme in/out of hidden status +ae.btn.hide_theme=Hide Theme +ae.btn.unhide_theme=Unhide Theme +ae.btn.details=Details +ae.btn.trash_it=Trash It! +ae.msg.delete_theme_fmt=Delete theme: %s? +ae.msg.deleted_fmt=Deleted %s +ae.msg.delete_failed_fmt=Failed to delete %s +ae.msg.error_prefix=Error encountered: +ae.msg.name_exists_fmt=%s\ndirectory name is already in use.\nPlease choose a different name. +ae.msg.theme_renamed_fmt=Theme renamed: %s +ae.msg.select_one_component=Please select at least one component! +ae.msg.delete_components_fmt=From the theme %s\nDelete the components:\n%s +ae.msg.delete_components_failed_fmt=\nFailed to delete %d components +ae.msg.delete_components_done_fmt=Deleted %d components%s +ae.msg.update_error_fmt=Encountered error while %s\nStopping and returning\n%d updates made +ae.msg.updates_done_fmt=%d updates made +ae.msg.download_failed_fmt=Failed to download or unzip %s:\n%s +ae.msg.clear_deco_fmt=Clear this decoration from:\n%s +ae.msg.deleted_path_fmt=Deleted:\n%s +ae.msg.delete_path_failed_fmt=Failed to delete:%s +ae.msg.unsupported_action_fmt=Unsupported action selected %s!\nReport bug please. +ae.msg.delete_deco_fmt=Delete this decoration from:\n%s +ae.msg.copy_image_to_fmt=Copy image to:\n%s +ae.msg.copy_failed=Unable to copy image! +ae.msg.copy_success=Image copied successfully! diff --git a/skeleton/SYSTEM/res/lang/fr.lang b/skeleton/SYSTEM/res/lang/fr.lang new file mode 100644 index 000000000..28003e1d7 --- /dev/null +++ b/skeleton/SYSTEM/res/lang/fr.lang @@ -0,0 +1,1071 @@ +# NextUI — strings françaises +# Format: clé=valeur +# Encodage UTF-8. Les lignes vides et celles commençant par # sont ignorées. + +# --------------------------------------------------------------------- +# Hints boutons (labels courts à côté des glyphes A/B/X/Y/MENU/POWER) +# Conserver des labels TRÈS courts (≤7 lettres si possible) — la place est +# contrainte sur les hints en bas d'écran. +# --------------------------------------------------------------------- +btn.back=RETOUR +btn.okay=OK +btn.open=OUVRIR +btn.resume=REPRENDRE +btn.remove=RETIRER +btn.sleep=VEILLE +btn.menu=MENU +btn.power=POWER +btn.info=INFO +btn.mute=COUPER +btn.unmute=RÉACTIVER +btn.show_all=TOUT +btn.show_locked=VERROUILLÉS + +# --------------------------------------------------------------------- +# Launcher principal (nextui) +# --------------------------------------------------------------------- +launcher.recents=Récents +launcher.games=Jeux +launcher.wifi=WiFi +launcher.bluetooth=Bluetooth +launcher.sleep=Veille +launcher.reboot=Redémarrer +launcher.poweroff=Éteindre +launcher.no_recents=Aucun récent +launcher.empty_folder=Dossier vide +launcher.no_preview=Aucun aperçu + +# --------------------------------------------------------------------- +# Menu in-game (minarch / ma_menu) +# --------------------------------------------------------------------- +menu.continue=Reprendre +menu.save=Sauvegarder +menu.load=Charger +menu.options=Options +menu.quit=Quitter +menu.reset=Réinitialiser +menu.empty_slot=Emplacement vide +menu.no_preview=Aucun aperçu +menu.screenshot_saved=Capture enregistrée + +# Sous-menu Options +menu.opt.frontend=Frontend +menu.opt.emulator=Émulateur +menu.opt.shaders=Shaders +menu.opt.cheats=Cheats +menu.opt.controls=Contrôles +menu.opt.shortcuts=Raccourcis +menu.opt.achievements=Succès +menu.opt.save_changes=Enregistrer + +# Sous-menu Save changes +menu.opt.save_for_console=Enregistrer pour la console +menu.opt.save_for_game=Enregistrer pour le jeu +menu.opt.restore_defaults=Valeurs par défaut + +# Statut config save-state +state.cfg.defaults=Valeurs par défaut. +state.cfg.console=Config console utilisée. +state.cfg.game=Config jeu utilisée. + +state.msg.saved_console=Enregistré pour la console. +state.msg.saved_game=Enregistré pour le jeu. +state.msg.restored_console=Réglages console restaurés. +state.msg.restored_defaults=Réglages par défaut restaurés. + +# Controls / hint +controls.set_hint=A pour définir, X pour effacer. +controls.controller=Manette +controls.controller_desc=Sélectionne le type de manette. + +# Cheats / shaders / extras +error.no_options_category=Aucune option dans cette catégorie. +error.no_options_core=Ce core ne propose aucune option. +error.no_extras=Aucun réglage supplémentaire. +error.no_shaders=Aucun shader disponible\nDossier /Shaders ou fichiers introuvables +error.no_cheat_file=Aucun fichier de cheat chargé.\n\n +error.cheats_disabled_hardcore=Cheats désactivés en mode Hardcore + +# Succès (RetroAchievements) +ach.no_for_game=Aucun succès pour ce jeu.\n\nCe ROM peut nécessiter un patch\nou ne pas être une version supportée.\n\nVoir retroachievements.org pour la\nliste des versions supportées. +ach.no_available=Aucun succès disponible pour ce jeu.\n\nCe jeu n'a peut-être pas encore de succès.\n\nDétails sur retroachievements.org. +ach.failed_load=Échec du chargement des succès +ach.not_available=Liste des succès indisponible +ach.none_found=Aucun succès trouvé +ach.offline_pending=Débloqué hors-ligne — sync en attente +ach.muted_silenced=Coupé — notifications silencieuses + +# --------------------------------------------------------------------- +# Réglages (settings.cpp) +# --------------------------------------------------------------------- +# Sections +settings.section.appearance=Apparence +settings.section.display=Affichage +settings.section.audio=Audio +settings.section.system=Système +settings.section.power=Alimentation +settings.section.network=Réseau +settings.section.language=Langue +settings.section.advanced=Avancé + +# Boutons communs +settings.reset_defaults=Réinitialiser +settings.reset_defaults_desc=Restaure toutes les options de ce menu à leurs valeurs par défaut. + +# Apparence +settings.appearance.font=Police +settings.appearance.font_desc=La police utilisée pour tout le texte de l'interface. +settings.appearance.show_battery_pct=Pourcentage de batterie +settings.appearance.show_battery_pct_desc=Affiche la batterie en pourcentage dans la pilule d'état +settings.appearance.show_anim=Animations de menu +settings.appearance.show_anim_desc=Active ou désactive les animations de menu +settings.appearance.transitions=Transitions de menu +settings.appearance.transitions_desc=Style d'animation lors de la navigation entre les menus +settings.appearance.art_radius=Rayon des coins (jaquettes) +settings.appearance.art_radius_desc=Définit l'arrondi des coins des jaquettes de jeu +settings.appearance.art_width=Largeur des jaquettes +settings.appearance.art_width_desc=Pourcentage de la largeur d'écran utilisé pour la jaquette.\nDes éléments d'UI peuvent réduire cette valeur pour éviter le rognage. +settings.appearance.show_folder_names=Noms de dossiers à la racine +settings.appearance.show_folder_names_desc=Affiche les noms de dossiers dans le répertoire racine +settings.appearance.show_recents=Afficher "Récents" +settings.appearance.show_recents_desc=Affiche l'entrée "Récemment joué" dans la liste des jeux. +settings.appearance.show_tools=Afficher "Outils" +settings.appearance.show_tools_desc=Affiche l'entrée "Outils" dans la liste des jeux. +settings.appearance.show_art=Afficher les jaquettes +settings.appearance.show_art_desc=Affiche les jaquettes dans le menu principal +settings.appearance.folder_bg=Fond du dossier pour les ROMs +settings.appearance.folder_bg_desc=Si activé, utilise l'image de fond de l'émulateur. Sinon le fond par défaut. +settings.appearance.qs_ui=Interface Quickswitcher +settings.appearance.qs_ui_desc=Affiche/masque les éléments d'UI du Quickswitcher.\nMasqué, seules les images de fond seront dessinées. + +# Affichage +settings.display.brightness=Luminosité +settings.display.brightness_desc=Luminosité de l'écran (0 à 10) +settings.display.colortemp=Température de couleur +settings.display.colortemp_desc=Température de couleur (0 à 40) +settings.display.contrast=Contraste +settings.display.contrast_desc=Amélioration du contraste (-4 à 5) +settings.display.saturation=Saturation +settings.display.saturation_desc=Amélioration de la saturation (-5 à 5) +settings.display.exposure=Exposition +settings.display.exposure_desc=Amélioration de l'exposition (-4 à 5) + +# Audio +settings.audio.volume=Volume +settings.audio.volume_desc=Volume des haut-parleurs +settings.audio.volume_muted=Coupé + +# Système +settings.system.screen_timeout=Extinction écran +settings.system.screen_timeout_desc=Durée d'inactivité avant extinction de l'écran (0-600s) +settings.system.suspend_timeout=Mise en veille +settings.system.suspend_timeout_desc=Délai avant veille profonde après l'extinction de l'écran (5-600s) +settings.system.haptic=Retour haptique +settings.system.haptic_desc=Active ou désactive le retour haptique pour certaines actions +settings.system.default_view=Vue par défaut +settings.system.default_view_desc=La vue initiale affichée au démarrage +settings.system.view.content_list=Liste des contenus +settings.system.view.game_switcher=Game Switcher +settings.system.view.quick_menu=Menu rapide +settings.system.clock_24h=Format 24h +settings.system.clock_24h_desc=Affiche l'horloge au format 24 heures +settings.system.show_clock=Afficher l'horloge +settings.system.show_clock_desc=Affiche l'horloge dans la pilule d'état +settings.system.auto_time=Heure automatique +settings.system.auto_time_desc=Ajuste automatiquement l'heure système\nvia NTP (requiert un accès Internet) +settings.system.timezone=Fuseau horaire +settings.system.timezone_desc=Votre fuseau horaire +settings.system.save_format=Format de sauvegarde +settings.system.save_format_desc=Format des sauvegardes.\nMinUI: Jeu.gba.sav, Retroarch: Jeu.srm, Générique: Jeu.sav +settings.system.save_state_format=Format des save states +settings.system.save_state_format_desc=Format des save states. MinUI: Jeu.st0,\nRetroarch-ish: Jeu.state.0, Retroarch: Jeu.state0 +settings.system.extracted_name=Nom de fichier extrait +settings.system.extracted_name_desc=Utilise le nom du fichier extrait plutôt que celui de l'archive.\nNe s'applique qu'aux cores qui ne gèrent pas les archives nativement +settings.system.safe_poweroff=Extinction sûre +settings.system.safe_poweroff_desc=Contourne la procédure d'extinction d'origine pour éviter le "limbo bug".\nDéconnecte la batterie via le PMIC directement. +settings.system.fan_speed=Vitesse ventilateur +settings.system.fan_speed_desc=Sélectionne la vitesse du ventilateur (Silencieux/Normal/Performance ou 0-100%) +settings.system.fan.performance=Performance +settings.system.fan.normal=Normal +settings.system.fan.quiet=Silencieux + +# Couleurs +settings.color.main=Couleur principale +settings.color.main_desc=Couleur principale des éléments d'UI. +settings.color.primary=Accent primaire +settings.color.primary_desc=Couleur de mise en valeur des éléments importants. +settings.color.secondary=Accent secondaire +settings.color.secondary_desc=Couleur de mise en valeur secondaire. +settings.color.hint=Indices boutons +settings.color.hint_desc=Couleur des hints boutons et informations +settings.color.list_text=Texte de liste +settings.color.list_text_desc=Couleur du texte dans les listes +settings.color.list_text_selected=Texte sélectionné +settings.color.list_text_selected_desc=Couleur du texte sélectionné dans les listes +settings.color.background=Couleur de fond +settings.color.background_desc=Couleur de fond utilisée sans image de fond. + +# Valeurs génériques +val.on=Activé +val.off=Désactivé +val.never=Jamais +val.unchanged=Inchangé +val.snappy=Vif +val.comfy=Doux +val.fullscreen=Plein écran +val.fit=Adapter +val.fill=Remplir + +# --------------------------------------------------------------------- +# Menu Langue (nouveau) +# --------------------------------------------------------------------- +settings.language.title=Langue +settings.language.desc=Langue de l'interface (menus et notifications). +settings.language.restart_hint=Certains libellés ne changeront qu'au prochain redémarrage. + +# --------------------------------------------------------------------- +# Bluetooth / WiFi +# --------------------------------------------------------------------- +bt.title=Bluetooth +bt.scan=Rechercher des appareils +bt.devices=Appareils +bt.connecting=Connexion... +bt.connected=Connecté +bt.disconnected=Déconnecté +bt.failed=Échec de la connexion +bt.pairing=Appairage... +bt.paired=Appairé +bt.forget=Oublier l'appareil + +wifi.title=WiFi +wifi.scan=Rechercher des réseaux +wifi.networks=Réseaux +wifi.connecting=Connexion... +wifi.connected=Connecté +wifi.disconnected=Déconnecté +wifi.failed=Échec de la connexion +wifi.password=Mot de passe +wifi.forget=Oublier le réseau + +# ===================================================================== +# Phase 2: couverture complète settings.cpp +# ===================================================================== + +# --- Audio / volume / fan / mute (étiquettes communes) --- +val.muted=Coupé + +# --- Labels formats de sauvegarde --- +val.fmt.minui=MinUI (défaut) +val.fmt.retroarch_compressed=Retroarch (compressé) +val.fmt.retroarch_uncompressed=Retroarch (non compressé) +val.fmt.generic=Générique +val.fmt.retroarch_ish_compressed=Retroarch-ish (compressé) +val.fmt.retroarch_ish_uncompressed=Retroarch-ish (non compressé) + +# --- Menu Notifications --- +settings.notif.title=Notifications +settings.notif.save_states=Save states +settings.notif.save_states_desc=Notification lors de la sauvegarde d'un save state +settings.notif.load_states=Chargement de save state +settings.notif.load_states_desc=Notification lors du chargement d'un save state +settings.notif.screenshots=Captures d'écran +settings.notif.screenshots_desc=Notification lors d'une capture d'écran +settings.notif.vol_display=Volume / Affichage +settings.notif.vol_display_desc=Affiche un overlay pour le volume, la luminosité\net la température de couleur +settings.notif.duration=Durée +settings.notif.duration_desc=Temps d'affichage des notifications à l'écran + +# --- Interrupteur FN / mute --- +settings.fn.title=Interrupteur FN +settings.fn.main=Interrupteur FN +settings.fn.main_desc=Réglages de l'interrupteur FN +settings.mute.volume_toggled=Volume (interrupteur) +settings.mute.volume_toggled_desc=Volume des haut-parleurs (0-20) +settings.mute.fn_disables_led=FN coupe aussi les LED +settings.mute.fn_disables_led_desc=L'interrupteur désactive également les LED +settings.mute.brightness_toggled=Luminosité (interrupteur) +settings.mute.brightness_toggled_desc=Luminosité de l'écran (0 à 10) +settings.mute.colortemp_toggled=Température (interrupteur) +settings.mute.colortemp_toggled_desc=Température de couleur (0 à 40) +settings.mute.contrast_toggled=Contraste (interrupteur) +settings.mute.contrast_toggled_desc=Amélioration du contraste (-4 à 5) +settings.mute.saturation_toggled=Saturation (interrupteur) +settings.mute.saturation_toggled_desc=Amélioration de la saturation (-5 à 5) +settings.mute.exposure_toggled=Exposition (interrupteur) +settings.mute.exposure_toggled_desc=Amélioration de l'exposition (-4 à 5) +settings.mute.turbo.a=Turbo A +settings.mute.turbo.a_desc=Active le tir turbo pour A +settings.mute.turbo.b=Turbo B +settings.mute.turbo.b_desc=Active le tir turbo pour B +settings.mute.turbo.x=Turbo X +settings.mute.turbo.x_desc=Active le tir turbo pour X +settings.mute.turbo.y=Turbo Y +settings.mute.turbo.y_desc=Active le tir turbo pour Y +settings.mute.turbo.l1=Turbo L1 +settings.mute.turbo.l1_desc=Active le tir turbo pour L1 +settings.mute.turbo.l2=Turbo L2 +settings.mute.turbo.l2_desc=Active le tir turbo pour L2 +settings.mute.turbo.r1=Turbo R1 +settings.mute.turbo.r1_desc=Active le tir turbo pour R1 +settings.mute.turbo.r2=Turbo R2 +settings.mute.turbo.r2_desc=Active le tir turbo pour R2 +settings.mute.dpad_mode=Mode D-pad (interrupteur) +settings.mute.dpad_mode_desc=Dpad : défaut. Joystick : le D-pad agit comme stick analogique.\nLes deux : entrées D-pad et joystick simultanées. +val.dpad=D-pad +val.joystick=Joystick +val.both=Les deux + +# --- Menu RetroAchievements --- +settings.ra.title=RetroAchievements +settings.ra.in_game=RetroAchievements +settings.ra.in_game_desc=Réglages du suivi des succès +settings.ra.enable=Activer les succès +settings.ra.enable_desc=Active l'intégration RetroAchievements +settings.ra.username=Nom d'utilisateur +settings.ra.username_desc=Nom d'utilisateur RetroAchievements +settings.ra.password=Mot de passe +settings.ra.password_desc=Mot de passe RetroAchievements +settings.ra.not_set=(non défini) +settings.ra.authenticate=S'authentifier +settings.ra.authenticate_desc=Teste les identifiants et récupère le token API +settings.ra.err_credentials=Erreur : nom d'utilisateur et mot de passe requis +settings.ra.authenticating=Authentification... +settings.ra.authenticated_as=Authentifié en tant que %s +settings.ra.err_prefix=Erreur : %s +settings.ra.status=Statut +settings.ra.status_desc=État de l'authentification +settings.ra.status_yes=Authentifié +settings.ra.status_no=Non authentifié +settings.ra.show_notif=Afficher les notifications +settings.ra.show_notif_desc=Affiche les notifications de déblocage de succès +settings.ra.notif_duration=Durée des notifications +settings.ra.notif_duration_desc=Temps d'affichage des notifications de succès +settings.ra.progress_duration=Durée de progression +settings.ra.progress_duration_desc=Durée des mises à jour de progression (haut-gauche). Désactivé pour masquer. +settings.ra.sort=Tri des succès +settings.ra.sort_desc=Ordre de tri des succès dans le menu in-game +settings.ra.sync=Synchroniser les déblocages hors-ligne +settings.ra.sync_pending_fmt=%u en attente — envoyer au serveur RA +settings.ra.sync_no_pending=Aucun déblocage en attente +settings.ra.sync_not_auth=Non authentifié +settings.ra.sync_inprogress_fmt=Synchronisation de %u succès%s...\n\n(0/%u) +settings.ra.sync_progress_fmt=Synchronisation des succès...\n\n(%u/%u) +settings.ra.sync_cancelling=Annulation de la synchronisation... +settings.ra.sync_cancelled=Synchronisation annulée +settings.ra.sync_cancelled_partial_fmt=Annulée : %u sur %u synchronisés +settings.ra.sync_incomplete_fmt=Incomplet : %u synchronisés, réessayer plus tard +settings.ra.sync_synced_fmt=%u succès%s synchronisés + +# Tri des succès (labels) +ra.sort.unlocked_first=Débloqués en premier +ra.sort.display_first=Ordre d'affichage (premier) +ra.sort.display_last=Ordre d'affichage (dernier) +ra.sort.won_most=Gagnés (le plus) +ra.sort.won_least=Gagnés (le moins) +ra.sort.points_most=Points (le plus) +ra.sort.points_least=Points (le moins) +ra.sort.title_az=Titre (A-Z) +ra.sort.title_za=Titre (Z-A) +ra.sort.type_asc=Type (croissant) +ra.sort.type_desc=Type (décroissant) + +# --- Vue par défaut --- +settings.system.view.content_list=Liste des contenus + +# --- Menu In-Game --- +settings.ingame.title=En jeu +settings.ingame.main=En jeu +settings.ingame.main_desc=Réglages en jeu pour MinArch +settings.ingame.notifications=Notifications +settings.ingame.notifications_desc=Notifications de save states + +# --- Menu À propos --- +settings.about.title=À propos +settings.about.main=À propos +settings.about.version=Version NextUI +settings.about.platform=Plateforme +settings.about.os_version=Version OS d'origine +settings.about.busybox=Version Busybox +settings.about.busybox_missing=Version BusyBox introuvable. + +# --- Menu principal --- +settings.main.appearance=Apparence +settings.main.appearance_desc=Personnalisation de l'UI +settings.main.display=Affichage +settings.main.system=Système +settings.main.network=Réseau +settings.main.bluetooth=Bluetooth + +# --- Avertissement OS d'origine --- +settings.stock_warning=Modifications de l'OS d'origine détectées.\nCela peut causer de l'instabilité.\nEn cas de problème, restaurez un firmware\nd'origine propre. + +# --- Labels scaling (audio/vidéo) --- +val.fullscreen=Plein écran +val.fit=Adapter +val.fill=Remplir + +# ===================================================================== +# Phase 3: btmenu.cpp + wifimenu.cpp +# ===================================================================== + +# Les deux menus partagent l'emplacement de premier niveau "Réseau". +bt.network=Réseau +bt.toggle=Bluetooth +bt.toggle_desc=Active/désactive le Bluetooth +bt.diagnostics=Diagnostics Bluetooth +bt.diagnostics_desc=Active/désactive les logs Bluetooth +bt.samplerate_max=Fréquence d'échantillonnage max +bt.samplerate_max_desc=44100 Hz : meilleure compatibilité\n48000 Hz : meilleure qualité +bt.enabling=Activation du Bluetooth... +bt.disabling=Désactivation du Bluetooth... +bt.options=Options +bt.pair=Appairer +bt.pair_desc=Appairer cet appareil. +bt.pairing_overlay=Appairage... +bt.forget_desc=Oublier cet appareil. +bt.connect=Connecter +bt.connect_desc=Connecter cet appareil. +bt.connecting_overlay=Connexion... +bt.disconnect=Déconnecter +bt.disconnect_desc=Déconnecter cet appareil. + +wifi.network=Réseau +wifi.toggle=WiFi +wifi.toggle_desc=Active/désactive le WiFi +wifi.diagnostics=Diagnostics WiFi +wifi.diagnostics_desc=Active/désactive les logs WiFi +wifi.enabling=Activation du WiFi... +wifi.disabling=Désactivation du WiFi... +wifi.options=Options +wifi.connect_known=Connecter +wifi.connect_known_desc=Se connecter à ce réseau. +wifi.enter_pass=Saisir le mot de passe WiFi +wifi.enter_pass_desc=Se connecter à ce réseau. +wifi.connecting_overlay=Connexion... +wifi.password_prompt=Saisir le mot de passe WiFi +wifi.forget_desc=Supprime les identifiants enregistrés pour ce réseau. + +# ===================================================================== +# Phase 4 : couverture complète (tous modules restants) +# ===================================================================== + +# Messages Hardcore (save states) +save.load_disabled_hc=Chargement de save state désactivé en mode Hardcore +save.save_disabled_hc=Save states désactivés en mode Hardcore + +# Notifications succès supplémentaires +ach.game_mastered=Jeu maîtrisé ! + +# Hints boutons supplémentaires +btn.cancel=ANNULER +btn.set=DÉFINIR +btn.apply=APPLIQUER +btn.exit=QUITTER +btn.enter=VALIDER +btn.copy=COPIER +btn.fine=PRÉCIS +btn.coarse=RAPIDE +btn.scroll=DÉFILER +btn.up_down=H/B +btn.left_right=G/D +btn.zoom=ZOOM +btn.select_light=Choisir LED +btn.hr_24=24H +btn.hr_12=12H +btn.ok=OK + +# Horloge +clock.am=AM +clock.pm=PM +clock.date_order=DMY + +# Temps de jeu +gametime.total=TOTAL +gametime.average=MOYENNE +gametime.plays=# PARTIES +gametime.title_fmt=Temps de jeu total : %s + +# Fallback générique +generic.empty_folder=Dossier vide + +# Clavier virtuel — touches spéciales +kb.shift=maj +kb.space=espace +kb.enter=entrée + +# Contrôle des LED +led.select_light=Choisir LED +led.effect=Effet +led.color=Couleur +led.color2=Couleur 2 +led.speed=Vitesse +led.brightness=Luminosité +led.brightness_all=Luminosité (toutes LED) +led.info_brightness=Luminosité info +led.info_brightness_all=Luminosité info (toutes LED) +led.trigger=Déclencheur +led.f1_key=Touche F1 +led.f2_key=Touche F2 +led.top_bar=Barre du haut +led.lr_triggers=Gâchettes L&R +led.joystick_l=Joystick gauche +led.joystick_r=Joystick droit +led.logo=Logo +# Noms d'effets LED — conservés en anglais (terminologie tech). +led.effect.linear=Linear +led.effect.breathe=Breathe +led.effect.interval_breathe=Interval Breathe +led.effect.static=Static +led.effect.blink1=Blink 1 +led.effect.blink2=Blink 2 +led.effect.blink3=Blink 3 +led.effect.rainbow=Rainbow +led.effect.twinkle=Twinkle +led.effect.fire=Fire +led.effect.glitter=Glitter +led.effect.neonglow=NeonGlow +led.effect.firefly=Firefly +led.effect.aurora=Aurora +led.effect.reactive=Reactive +led.effect.topbar_rainbow=Topbar Rainbow +led.effect.topbar_night=Topbar night +led.effect.lr_rainbow=LR Rainbow +led.effect.lr_reactive=LR Reactive + +# Testeur minput — labels des boutons non lettres +minput.vol_minus=VOL. - +minput.vol_plus=VOL. + +minput.quit=QUITTER + +# Hints matériels (api.c GFX_blitHardwareGroup / GFX_blitHardwareHints) +hw.brightness=LUMINOSITÉ +hw.color_temp=TEMPÉRATURE +hw.mnu=MNU +hw.brght=LUMI +hw.sel=SEL +hw.cltmp=TEMP + +# Valeurs par défaut diverses +val.none=aucun + +# ===================================================================== +# Phase 5 : Options Frontend in-game (ma_config.c) +# ===================================================================== +frontend.opt.screen_scaling=Mise à l'échelle +frontend.opt.screen_scaling.desc_overscan=Native : mise à l'échelle entière. Aspect : ratio rapporté par le core.\nAspect écran : ratio de l'écran.\nPlein écran : pixels non carrés. Recadré : entier puis rogné. +frontend.opt.screen_scaling.desc_default=Native : mise à l'échelle entière.\nAspect : ratio rapporté par le core.\nAspect écran : ratio de l'écran.\nPlein écran : pixels non carrés. +frontend.opt.resampling=Qualité du rééchantillonnage audio +frontend.opt.resampling.desc=Une qualité plus élevée consomme plus de CPU +frontend.opt.ambient=Mode ambiance +frontend.opt.ambient.desc=Les LED suivent les couleurs de l'écran +frontend.opt.effect=Effet d'écran +frontend.opt.effect.desc=Grille simule une grille LCD.\nLigne simule les scanlines CRT.\nLes effets sont meilleurs en mise à l'échelle native. +frontend.opt.overlay=Overlay +frontend.opt.overlay.desc=Choisis un overlay PNG dans le dossier Overlays +frontend.opt.offset_x=Décalage écran X +frontend.opt.offset_x.desc=Décalage de X pixels +frontend.opt.offset_y=Décalage écran Y +frontend.opt.offset_y.desc=Décalage de Y pixels +frontend.opt.sharpness=Netteté écran +frontend.opt.sharpness.desc=LINEAR lisse les lignes, mais meilleur quand l'image finale est en haute résolution (core haute déf ou upscaling via shaders) +frontend.opt.core_sync=Sync du core +frontend.opt.core_sync.desc=Choisis la référence pour la fréquence d'images.\n"Native" utilise celle de l'émulateur,\n"Écran" celle de l'écran. +frontend.opt.cpu_speed=Vitesse CPU +frontend.opt.cpu_speed.desc=Choisis comment le CPU se met à l'échelle.\nAuto est recommandé. +frontend.opt.debug_hud=HUD debug +frontend.opt.debug_hud.desc=Affiche FPS, charge CPU,\nrésolution et infos du scaler. +frontend.opt.max_ff=Vitesse max avance rapide +frontend.opt.max_ff.desc=L'avance rapide ne dépassera pas la vitesse\nsélectionnée (peut être moins selon le jeu/émulateur). +frontend.opt.ff_audio=Audio avance rapide +frontend.opt.ff_audio.desc=Jouer ou couper l'audio pendant l'avance rapide. +frontend.opt.rewind=Rewind +frontend.opt.rewind.desc=Active le buffer de rewind en mémoire.\nUn raccourci doit être défini pour rembobiner en jeu.\nConsomme plus de CPU et de mémoire. +frontend.opt.rewind_buffer=Buffer Rewind (Mo) +frontend.opt.rewind_buffer.desc=Mémoire réservée aux snapshots de rewind.\nAugmente pour des durées de rewind plus longues. +frontend.opt.rewind_interval=Intervalle Rewind +frontend.opt.rewind_interval.desc=Intervalle entre les snapshots de rewind.\nDes intervalles courts améliorent la fluidité du rewind\nmais augmentent CPU et mémoire. +frontend.opt.rewind_compression=Compression Rewind +frontend.opt.rewind_compression.desc=Compresse les snapshots de rewind pour économiser de la mémoire au détriment du CPU. +frontend.opt.rewind_compression_speed=Vitesse de compression Rewind +frontend.opt.rewind_compression_speed.desc=Accélération LZ4 pour les snapshots de rewind.\nDes valeurs plus basses compressent plus mais consomment plus de CPU. +frontend.opt.rewind_audio=Audio Rewind +frontend.opt.rewind_audio.desc=Jouer ou couper l'audio pendant le rewind. +frontend.opt.opt_shaders=Réglages shaders optionnels +frontend.opt.opt_shaders.desc=Si les shaders ont des réglages supplémentaires, ils apparaîtront dans ce menu +frontend.opt.shader_preset=Préréglage shaders/émulateur +frontend.opt.shader_preset.desc=Charge une config shaders/émulateurs préfaite.\nPour tester un préréglage, quitte sans enregistrer ! +frontend.opt.shader_count=Nombre de shaders +frontend.opt.shader_count.desc=Nombre de shaders, de 1 à 3 +frontend.opt.shader_1=Shader 1 +frontend.opt.shader_1.desc=Programme shader 1 à exécuter +frontend.opt.shader_1_filter=Filtre shader 1 +frontend.opt.shader_1_source=Source shader 1 +frontend.opt.shader_1_texture=Type de texture shader 1 +frontend.opt.shader_1_scale=Échelle shader 1 +frontend.opt.shader_2=Shader 2 +frontend.opt.shader_2.desc=Programme shader 2 à exécuter +frontend.opt.shader_2_filter=Filtre shader 2 +frontend.opt.shader_2_source=Source shader 2 +frontend.opt.shader_2_texture=Type de texture shader 2 +frontend.opt.shader_2_scale=Échelle shader 2 +frontend.opt.shader_3=Shader 3 +frontend.opt.shader_3.desc=Programme shader 3 à exécuter +frontend.opt.shader_3_filter=Filtre shader 3 +frontend.opt.shader_3_source=Source shader 3 +frontend.opt.shader_3_texture=Type de texture shader 3 +frontend.opt.shader_3_scale=Échelle shader 3 +frontend.opt.shader_filter.desc=Méthode d'upscaling, NEAREST ou LINEAR +frontend.opt.shader_source.desc=Choisit la source de résolution à mettre à l'échelle +frontend.opt.shader_scale.desc=Met l'image à l'échelle x fois,\n"screen" l'adapte à la résolution de l'écran (peut affecter les perfs) + +# Labels d'options Frontend +frontend.lbl.scaling.native=Native +frontend.lbl.scaling.aspect=Aspect +frontend.lbl.scaling.aspect_screen=Aspect écran +frontend.lbl.scaling.fullscreen=Plein écran +frontend.lbl.scaling.cropped=Recadré +frontend.lbl.resample.low=Basse +frontend.lbl.resample.medium=Moyenne +frontend.lbl.resample.high=Haute +frontend.lbl.resample.max=Max +frontend.lbl.ambient.all=Tout +frontend.lbl.ambient.top=Haut +frontend.lbl.ambient.fn=FN +frontend.lbl.ambient.lr=LR +frontend.lbl.ambient.top_lr=Haut/LR +frontend.lbl.effect.none=Aucun +frontend.lbl.effect.line=Ligne +frontend.lbl.effect.grid=Grille +frontend.lbl.overlay.none=Aucun +frontend.lbl.rewind_compression.best=1 (meilleur ratio) +frontend.lbl.rewind_compression.default=2 (défaut) +frontend.lbl.rewind_compression.fast=4 (rapide) +frontend.lbl.rewind_compression.faster=8 (plus rapide) +frontend.lbl.rewind_compression.fastest=12 (le plus rapide) + +# Libellés des dossiers du menu racine +launcher.tools=Outils +launcher.recently_played=Récemment joué +launcher.collections=Collections + +# Noms affichés des paks / dossiers (traduits via Entry_new). +# Règle : traduire UNIQUEMENT les paks fournis par NextUI core +# (workspace/all/*). Les paks tiers (Updater, Aesthetics, Pak Store, +# LED'oh!, ScrapeGoat, Files, Gallery, Remove Loading, Artwork Scraper, +# Another Cheat Downloader, ...) gardent leur nom propre — c'est leur +# identité chez leurs auteurs respectifs. +Settings=Réglages +Battery=Batterie +Clock=Horloge +Game Tracker=Temps de jeu +Bootlogo=Logo de démarrage +Input=Contrôles +LedControl=Contrôle LED +# USB Mass Storage est tiers mais le nom est un terme technique +# standard ; traduit pour cohérence avec le reste de l'UI système. +USB Mass Storage=Stockage USB +# Files / Gallery sont tiers mais leur nom est un descripteur générique +# (pas une marque), équivalent direct en français. +Files=Fichiers +Gallery=Galerie + +# Outil Batterie (workspace/all/battery) +battery.calculating=calcul en cours +battery.since_charge_fmt=Depuis la charge : %s +battery.current_fmt=Actuel : %s +battery.remaining_fmt=Restant : %s +battery.longest_fmt=Plus long : %s +battery.usage_fmt=Utilisation : %s +battery.range.4h=4 dernières heures +battery.range.8h=8 dernières heures +battery.range.16h=16 dernières heures + +# minui-presenter (Gallery, Artwork Scraper, Another Cheat Downloader, ...) +mp.btn.action=ACTION +mp.btn.select=CHOISIR +mp.btn.back=RETOUR +mp.btn.other=AUTRE +# Re-traductions de labels que les paks passent en argument : +# permet de traduire les paks tiers sans modifier leur launch.sh. +EXIT=QUITTER +QUIT=QUITTER +OK=OK +OKAY=OK +BACK=RETOUR +CANCEL=ANNULER +RETURN=RETOUR +APPLY=APPLIQUER +CONFIRM=CONFIRMER +DELETE=SUPPRIMER +YES=OUI +NO=NON + +# Messages --message émis par les paks (Gallery, ...) +No screenshots found=Aucune capture trouvée +Screenshots directory not found=Dossier captures introuvable + +# Pak Store (https://github.com/LoveRetro/nextui-pak-store) +ps.btn.continue=Continuer +ps.btn.quit=Quitter +ps.btn.select=Choisir +ps.btn.view=Voir +ps.btn.confirm=Confirmer +ps.btn.back=Retour +ps.btn.cancel=Annuler +ps.btn.install=Installer +ps.btn.update=Mettre à jour +ps.btn.uninstall=Désinstaller +ps.btn.cycle=Choisir +ps.btn.save=Enregistrer +ps.btn.settings=Réglages +ps.btn.nevermind=Annuler +ps.btn.yes=Oui +ps.menu.updates_fmt=Mises à jour (%d) +ps.menu.browse=Parcourir +ps.menu.manage=Gérer les paks installés +ps.no_paks=Aucun pak disponible +ps.title.browse=Parcourir les paks +ps.title.manage=Gérer les paks installés +ps.title.updates=Mises à jour disponibles +ps.title.settings=Réglages +ps.settings.platform_filter=Filtre plateforme +ps.settings.platform_filter.match=Cet appareil +ps.settings.platform_filter.all=Tout +ps.settings.debug_level=Niveau de logs +ps.settings.debug_level.error=Erreur +ps.settings.debug_level.info=Info +ps.settings.debug_level.debug=Debug +ps.settings.discover=Détecter les paks installés +ps.settings.info=À propos +ps.toggle.on=Activé +ps.toggle.off=Désactivé +ps.info.version=Version +ps.info.commit=Commit +ps.info.build_date=Date du build +ps.info.community.title=Merci à la communauté +ps.info.community.body=Pak Store existe grâce à l'incroyable communauté NextUI. Votre créativité, votre passion et votre dévouement à créer des paks géniaux rendent cette plateforme unique. Chaque émulateur, outil et amélioration que vous créez apporte de la joie à nos petites consoles rétro ! Merci de partager vos talents et de rendre NextUI meilleur pour tout le monde. +ps.info.github_repo=Dépôt GitHub +ps.pak.whats_new_fmt=Quoi de neuf dans %s ? +ps.pak.description=Description +ps.pak.screenshots=Captures d'écran +ps.pak.info=Infos du pak +ps.pak.author=Auteur +ps.pak.version=Version +ps.pak.current_version=Version actuelle +ps.pak.changelog=Changelog +ps.pak.repository=Dépôt du pak +ps.updates.update_all=Tout mettre à jour +ps.uninstall.confirm_fmt=Désinstaller\n %s ? +ps.uninstall.in_progress_fmt=Désinstallation de %s... +ps.uninstall.error_fmt=Impossible de désinstaller %s +ps.install.success_fmt=%s installé ! +ps.install.extract_error_fmt=Impossible d'extraire %s +ps.update.success_fmt=%s mis à jour ! +ps.update.overview_fmt=Les %d paks suivants vont être mis à jour ! +ps.update.overview_title=Aperçu des mises à jour +ps.update.title_fmt=Mettre à jour %d paks +ps.update.download_error_fmt=Échec du téléchargement de %s +ps.update.extract_error_fmt=Impossible d'extraire %s +ps.update.pak_store_restarting=Pak Store mis à jour ! Redémarrage... +ps.update.pak_store_exiting=Pak Store mis à jour ! Fermeture... +ps.update.all_success=Tous les paks ont été mis à jour ! +ps.script.running_fmt=Exécution du script %s... +ps.unzip.in_progress_fmt=Décompression de %s... +ps.unzip.error_fmt=Impossible de décompresser %s +ps.category.experimental=Expérimental +ps.experimental_unlocked=Paks expérimentaux débloqués.\nÀ utiliser à vos risques !\nFaites des sauvegardes ! +ps.storefront_error=Impossible de charger le Storefront !\nVérifiez votre connexion Wi-Fi.\nSi le problème persiste, consultez les logs. +ps.splash.fetching=Chargement du Storefront... +# Catégories du storefront (clé = nom anglais en snake_case minuscule) +ps.cat.customization=Personnalisation +ps.cat.developer_tools=Outils développeur +ps.cat.emulators=Émulateurs +ps.cat.file_management=Fichiers +ps.cat.media=Multimédia +ps.cat.miscellaneous_tools=Divers +ps.cat.ports=Ports +ps.cat.rom_management=Gestion des ROM +ps.cat.streaming_game_clients=Streaming +ps.cat.system=Système +ps.btn.close=Fermer +ps.btn.cancel_download=Annuler téléchargement +ps.btn.cancel_all_downloads=Tout annuler +ps.btn.show_speed=Afficher vitesse +ps.btn.hide_speed=Masquer vitesse +ps.download.title_fmt=Téléchargement %s %s... + +# ScrapeGoat (https://github.com/Helaas/nextui-scrapegoat-pak) +sg.menu.artwork=Jaquettes +sg.menu.cheats=Codes triche +sg.menu.manuals=Manuels +sg.menu.settings=Réglages +sg.menu.api_usage=Utilisation API +sg.btn.quit=QUITTER +sg.btn.select=CHOISIR +sg.btn.back=RETOUR +sg.btn.continue=CONTINUER +sg.btn.ok=OK +sg.btn.cancel=ANNULER +sg.btn.open=OUVRIR +sg.btn.filter=FILTRER +sg.btn.queue_all=AJOUTER TOUT +sg.btn.track_progress=SUIVRE +sg.btn.retry=RÉESSAYER +sg.btn.yes=OUI +sg.btn.no=NON +sg.btn.save=ENREGISTRER +sg.btn.start=DÉMARRER +sg.btn.stop=ARRÊTER +sg.btn.scan=SCANNER +sg.btn.apply=APPLIQUER +sg.btn.confirm=CONFIRMER +sg.btn.edit=MODIFIER +sg.btn.keep_open=GARDER OUVERT +sg.title.settings=Réglages +sg.title.artwork_priority=Priorité jaquettes +sg.title.region_priority=Priorité région +sg.settings.username=Utilisateur +sg.settings.password=Mot de passe +sg.settings.artwork_options=Options jaquettes +sg.settings.manual_dir=Dossier des manuels +sg.settings.clear_cheat_cache=Vider le cache des codes +sg.settings.include_hidden=Inclure ROMs cachées/désactivées/vides +sg.settings.artwork_priority=Priorité jaquettes +sg.settings.region_priority=Priorité région +sg.toggle.on=Activé +sg.toggle.off=Désactivé +sg.queue_all.missing_only=Ajouter manquants uniquement +sg.queue_all.redownload_all=Re-télécharger tout (déjà installés inclus) +sg.queue_all.already_installed_fmt=%d déjà installés +sg.quit.keep_open=Garder ScrapeGoat ouvert +sg.quit.exit_cancel=Quitter et annuler téléchargements +sg.quit.exit_bg=Quitter en arrière-plan +sg.quit.title_one=%d élément en file +sg.quit.title_many_fmt=%d éléments en file +sg.value.set=(défini) +sg.value.not_set=(non défini) +sg.progress.track=Suivre la progression +sg.progress.with_total_fmt=Suivre la progression (%d/%d) +sg.progress.with_failed_fmt=Suivre la progression (%d/%d, %d échecs) +sg.filter.all=Tout +sg.filter.missing=Manquant +sg.filter.installed=Installé +sg.mode.artwork=jaquettes +sg.mode.cheats=codes triche +sg.mode.manuals=manuels +sg.type.artwork=Jaquette +sg.type.cheat=Code triche +sg.type.manual=Manuel +sg.info.status=Statut +sg.info.system=Système +sg.info.type=Type +sg.btn.queue=AJOUTER +sg.btn.queued=AJOUTÉ +sg.btn.redownload=RE-TÉLÉCHARGER +sg.queue.added_fmt=%d ROMs ajoutées pour %s. +sg.queue.queued_fmt=« %s » ajouté pour %s. +sg.queue.requeued_fmt=« %s » re-ajouté pour %s. +sg.queue.already_queued=Déjà en file. +sg.error.no_roms=Aucune ROM trouvée dans ce système. +sg.error.no_rom_folders=Aucun dossier de ROMs trouvé. +sg.error.no_systems_artwork=Aucun système avec jaquettes disponibles. +sg.error.no_systems_cheat=Aucun système avec codes triche disponibles. +sg.error.no_systems_manual=Aucun système avec manuels disponibles. +sg.error.oom=Mémoire insuffisante. +sg.brief.no_api_data=Pas encore de données API. Les stats arrivent après la première recherche de jaquettes. +sg.title.api_usage=Utilisation API +sg.api.requests_today=Requêtes aujourd'hui +sg.api.daily_limit=Limite journalière +sg.api.remaining=Restant +sg.api.threads=Threads +sg.title.progress=Progression +sg.title.bg_scraping=Scraping en arrière-plan +sg.title.artwork_options=Options jaquettes +sg.title.error=Erreur +sg.queue.empty=AUCUN ÉLÉMENT EN FILE. +sg.queue.empty_filter=AUCUN ÉLÉMENT NE CORRESPOND AU FILTRE. +sg.queue.filter.all=TOUT +sg.queue.filter.busy=EN COURS +sg.queue.filter.done=FAIT +sg.queue.filter.fail=ÉCHEC +sg.status.queued=En file +sg.status.searching=Recherche... +sg.status.downloading=Téléchargement... +sg.status.cloning=Clonage db... +sg.status.matching=Correspondance... +sg.status.done=Terminé +sg.status.not_found=Introuvable +sg.status.error=Erreur +sg.status.skipped=Ignoré +sg.btn.reorder=RÉORDONNER +sg.btn.done=TERMINÉ +sg.btn.clear=EFFACER +sg.btn.exit=QUITTER +sg.error.not_found_libretro=Introuvable dans la base libretro +sg.error.not_found_screenscraper=Introuvable dans la base ScreenScraper.fr +sg.error.unknown=Une erreur inconnue est survenue +sg.error.queue_active_clear=Attends la fin de la file avant d'effacer le cache des codes. +sg.error.clear_failed=Échec de l'effacement du cache des codes. +sg.error.cache_cleared_stale=Cache effacé, mais l'état de la file en mémoire\nn'a pas pu être rafraîchi.\n\nRedémarre l'app avant de retélécharger\ndes codes. +sg.error.bg_stop_failed=Échec de l'arrêt du scraping en arrière-plan. +sg.error.bg_stop_timeout=Le scraping en arrière-plan n'a pas pu s'arrêter à temps. +sg.error.handoff_failed=Échec de la reprise en premier plan. +sg.error.handoff_timeout=Le scraping en arrière-plan n'a pas pu être repris à temps. +sg.error.bg_queue_lost=Le scraping en arrière-plan s'est arrêté,\nmais la file n'a pas pu être restaurée. +sg.error.bg_start_failed=Échec du démarrage du scraping en\narrière-plan. Essaie de quitter normalement. +sg.warn.bg_still_active=Le scraping en arrière-plan est toujours actif.\n\nFerme ScrapeGoat et rouvre-le pour\nréessayer la reprise. +sg.warn.no_internet=Aucune connexion internet détectée.\n\nLes codes déjà téléchargés peuvent\nêtre utilisés hors ligne.\n\nConnecte-toi au Wi-Fi pour scraper\ndes jaquettes/manuels ou télécharger\nde nouveaux codes. +sg.warn.no_credentials=Aucun identifiant ScreenScraper.fr défini.\n\nLe scraping fonctionnera au rythme basique\n(~1 req/min, mono-thread).\n\nPour de meilleures vitesses, va dans\nles Réglages et ajoute tes identifiants. +sg.warn.no_manual_dir=Dossier de téléchargement des manuels non défini.\n\nVa dans les Réglages et configure un\ndossier de téléchargement pour les manuels.\n\nIl te faudra aussi un Pak comme\nSDLReader pour lire les PDFs. +sg.dialog.cancel_all=Annuler tous les téléchargements ?\n\nLes éléments en cours seront arrêtés\net les éléments en attente seront ignorés. +sg.dialog.clear_cheat_cache=Effacer la base de codes triche ?\n\nCela supprime le dépôt git local.\nIl sera re-téléchargé à la prochaine utilisation. +sg.dialog.stop_bg=Arrêter le scraping en arrière-plan ?\n\nLes éléments en cours seront arrêtés\net les éléments en attente seront ignorés. +sg.dialog.exit_cancel=Quitter ScrapeGoat et annuler tous les téléchargements ?\n\nLes éléments en cours seront arrêtés\net les éléments en file seront ignorés. +sg.dialog.bg_warning=Le scraping continue en arrière-plan\naprès la fermeture de ScrapeGoat.\n\nCela peut réduire les performances de jeu.\n\nLa veille met en pause les téléchargements.\nL'arrêt les stoppe.\n\nLa progression apparaît à la prochaine\nouverture de ScrapeGoat. +sg.dialog.bg_warning_compact=Le scraping continue en arrière-plan\naprès la fermeture de ScrapeGoat.\nCela peut réduire les performances de jeu.\nLa veille met en pause les téléchargements.\nL'arrêt les stoppe.\nLa progression apparaît à la prochaine\nouverture de ScrapeGoat. +sg.progress.clearing_cache=Effacement du cache... +sg.daemon.resuming=Reprise du scraping en arrière-plan... +sg.daemon.summary_fmt=%s.\n\n%d terminés, %d échecs sur %d total. +sg.daemon.completed_label=Scraping en arrière-plan terminé +sg.tag.queued=en file +sg.tag.searching=recherche +sg.tag.downloading=téléch. +sg.tag.cloning=clonage +sg.tag.matching=corresp. +sg.tag.art=jaq +sg.tag.cht=triche +sg.tag.pdf=pdf +sg.cheat.fallback_name_fmt=Code %d +sg.cheat.list_title_fmt=Codes triche (%d) +sg.tag.disabled=[désactivé] +sg.error.cache_inspect_failed_fmt=Échec de l'inspection du cache :\n%s +sg.error.cache_open_failed_fmt=Échec de l'ouverture du dossier cache :\n%s +sg.error.cache_rmdir_failed_fmt=Échec de la suppression du dossier cache :\n%s +sg.error.cache_unlink_failed_fmt=Échec de la suppression du fichier cache :\n%s +sg.error.cheat_cache_open_failed_fmt=Échec de l'ouverture du cache des codes :\n%s +sg.error.cheat_cache_rmdir_failed_fmt=Échec de la suppression du cache des codes :\n%s + +# Aesthetics (https://github.com/redria7/nextui-aesthetics) +ae.menu.download_themes=Télécharger des thèmes +ae.menu.manage_themes=Gérer les thèmes disponibles +ae.menu.manage_current=Gérer le thème actuel +ae.menu.decorations=Fonds d'écran & icônes +ae.btn.quit=Quitter +ae.btn.settings=Réglages +ae.btn.select=Choisir +ae.btn.cancel=Annuler +ae.btn.back=Retour +ae.btn.options=Options +ae.btn.open_folder=Ouvrir dossier +ae.btn.decoration_options=Options de décoration +ae.btn.delete=Supprimer +ae.btn.confirm=Confirmer +ae.btn.save=Enregistrer +ae.btn.apply=Appliquer +ae.btn.clear=Effacer +ae.btn.yes=Oui +ae.btn.no=Non +ae.btn.cycle=Choisir +ae.btn.changed_mind=J'ai changé d'avis +ae.btn.remove=Supprimer +ae.btn.download=Télécharger +ae.btn.redownload=Re-télécharger +ae.btn.open=Ouvrir +ae.btn.swap_aggregation=Changer regroupement +ae.btn.toggle=Basculer +ae.title.settings=Réglages Aesthetics +ae.title.component_options=Options composants +ae.title.download_themes=Thèmes disponibles +ae.title.hidden_themes=Thèmes cachés +ae.title.manage_themes=Gérer les thèmes +ae.title.manage_components_prefix=Gérer +ae.title.manage_components_suffix=composants +ae.empty.no_decorations=Aucune décoration trouvée +ae.empty.no_components=Aucun composant supporté ! +ae.empty.no_hidden_themes=Aucun thème caché ! +ae.empty.no_themes_manage=Aucun thème à gérer ! Enregistrez ou téléchargez-en. +ae.help.decoration_list=Contrôles liste décorations +ae.help.component_options=Options de gestion des composants +ae.help.component_management=Contrôles gestion des composants +ae.help.directory_list=Contrôles liste dossiers +ae.help.deco_apply=Écran de confirmation pour appliquer la décoration sélectionnée +ae.help.deco_delete=Écran de confirmation pour supprimer la décoration sélectionnée +ae.help.deco_open=Ouvrir le regroupement pour voir les décorations disponibles +ae.help.deco_swap=Changer le style de regroupement (console ou dossier) +ae.help.save_components=Enregistrer les composants sélectionnés dans un thème +ae.help.revert_components=Restaurer les composants sélectionnés aux réglages par défaut +ae.help.apply_components=Appliquer les composants sélectionnés à l'appareil +ae.help.delete_components=Supprimer les composants sélectionnés du thème +ae.help.toggle=Basculer un composant pour l'inclure dans l'action sélectionnée +ae.settings.log_level=Niveau de logs +ae.settings.log_level.debug=Debug +ae.settings.log_level.error=Erreur +ae.settings.decoration_aggregation=Regroupement décorations +ae.settings.aggregation.directory=Par dossier +ae.settings.aggregation.console=Par console +ae.info.file_type=Type de fichier +ae.info.author=Auteur +ae.info.last_updated=Mis à jour le +ae.deco.icon=Icône +ae.deco.wallpaper=Fond d'écran +ae.deco.list_wallpaper=Fond d'écran de liste +ae.label.current=Actuel +ae.progress.unzipping=Décompression de +ae.progress.downloading=Téléchargement de +ae.progress.preview=Aperçu +ae.error.fetch_dirs=Impossible de charger les dossiers ! +# Labels d'options de composants (clés = valeurs anglaises des const) +Save | All=Enregistrer | Tout +Save | + Active Consoles=Enregistrer | + Consoles actives +Save | All | With Confirmations=Enregistrer | Tout | Avec confirmations +Revert to Default | + Active Consoles=Restaurer | + Consoles actives +Revert to Default | Empty Consoles Only=Restaurer | Consoles vides uniquement +Revert to Default | All=Restaurer | Tout +Revert to Default | All | With Confirmations=Restaurer | Tout | Avec confirmations +Clear & Apply | + Active Consoles=Effacer & appliquer | + Consoles actives +Clear & Apply | All=Effacer & appliquer | Tout +Apply | + Active Consoles=Appliquer | + Consoles actives +Apply | All=Appliquer | Tout +Apply | Missing Only | + Active Consoles=Appliquer | Manquants uniquement | + Consoles actives +Apply | Missing Only | All=Appliquer | Manquants uniquement | Tout +Apply | All | With Confirmations=Appliquer | Tout | Avec confirmations +Clear Wallpaper=Effacer fond d'écran +Select Wallpaper=Choisir fond d'écran +Clear List Wallpaper=Effacer fond de liste +Select List Wallpaper=Choisir fond de liste +Clear Default Wallpaper=Effacer fond par défaut +Select Default Wallpaper=Choisir fond par défaut +Clear Icon=Effacer icône +Select Icon=Choisir icône +Delete Theme=Supprimer thème +Rename Theme=Renommer thème +ae.dl.refresh_catalog=Rafraîchir les thèmes disponibles +ae.dl.hidden_themes=Thèmes cachés +ae.dl.help_no_entries=Si aucune entrée, vérifiez la connexion internet +ae.dl.help_a=Voir les détails du thème avec option de téléchargement +ae.dl.help_x=Cacher/révéler un thème +ae.btn.hide_theme=Cacher +ae.btn.unhide_theme=Révéler +ae.btn.details=Détails +ae.btn.trash_it=Poubelle ! +ae.msg.delete_theme_fmt=Supprimer le thème : %s ? +ae.msg.deleted_fmt=%s supprimé +ae.msg.delete_failed_fmt=Échec suppression de %s +ae.msg.error_prefix=Erreur rencontrée : +ae.msg.name_exists_fmt=%s\nce nom est déjà utilisé.\nChoisissez un autre nom. +ae.msg.theme_renamed_fmt=Thème renommé : %s +ae.msg.select_one_component=Sélectionnez au moins un composant ! +ae.msg.delete_components_fmt=Du thème %s\nSupprimer les composants :\n%s +ae.msg.delete_components_failed_fmt=\nÉchec suppression de %d composants +ae.msg.delete_components_done_fmt=%d composants supprimés%s +ae.msg.update_error_fmt=Erreur pendant %s\nArrêt - %d mises à jour effectuées +ae.msg.updates_done_fmt=%d mises à jour effectuées +ae.msg.download_failed_fmt=Échec téléchargement/décompression %s :\n%s +ae.msg.clear_deco_fmt=Effacer cette décoration depuis :\n%s +ae.msg.deleted_path_fmt=Supprimé :\n%s +ae.msg.delete_path_failed_fmt=Échec suppression :%s +ae.msg.unsupported_action_fmt=Action non supportée %s !\nMerci de signaler le bug. +ae.msg.delete_deco_fmt=Supprimer cette décoration depuis :\n%s +ae.msg.copy_image_to_fmt=Copier l'image vers :\n%s +ae.msg.copy_failed=Impossible de copier l'image ! +ae.msg.copy_success=Image copiée avec succès ! diff --git a/workspace/all/battery/battery.c b/workspace/all/battery/battery.c index 68fae04d5..0c9c5e545 100644 --- a/workspace/all/battery/battery.c +++ b/workspace/all/battery/battery.c @@ -8,6 +8,7 @@ #include "defines.h" #include "api.h" #include "utils.h" +#include "i18n.h" #include #include @@ -108,7 +109,8 @@ static int estimation_line_size = 0; static int begining_session_index; static char session_duration[10]; static char current_percentage[10]; -static char session_left[12] = "calculating"; +// Buffer sized for translated "calculating" (e.g. FR "calcul en cours" = 15 chars). +static char session_left[32] = "calculating"; static char session_best[10]; static void sigHandler(int sig) @@ -465,16 +467,16 @@ void renderPage() drawBatteryIcon(0, (SDL_Rect){graph.layout.icon_x, graph.layout.icon4_y}); char text_line[255]; - sprintf(text_line, "Since Charge: %s", session_duration); + snprintf(text_line, sizeof(text_line), T("battery.since_charge_fmt"), session_duration); renderText(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_session_x, graph.layout.label_session_y, graph.layout.label_size_x, graph.layout.label_size_y}); - sprintf(text_line, "Current: %s", current_percentage); + snprintf(text_line, sizeof(text_line), T("battery.current_fmt"), current_percentage); renderText(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_current_x, graph.layout.label_current_y, graph.layout.label_size_x, graph.layout.label_size_y}); - sprintf(text_line, "Remaining: %s", session_left); + snprintf(text_line, sizeof(text_line), T("battery.remaining_fmt"), session_left); renderTextAlignRight(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_left_x, graph.layout.label_left_y, graph.layout.label_size_x, graph.layout.label_size_y}); - sprintf(text_line, "Longest: %s", session_best); + snprintf(text_line, sizeof(text_line), T("battery.longest_fmt"), session_best); renderTextAlignRight(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_best_x, graph.layout.label_best_y, graph.layout.label_size_x, graph.layout.label_size_y}); int half_line_width = (int)(GRAPH_LINE_WIDTH) / 2; @@ -641,6 +643,10 @@ int main(int argc, char *argv[]) device_model = PLAT_getModel(); screen = GFX_init(MODE_MAIN); + // GFX_init has loaded the active language; refresh the "calculating" + // placeholder so it shows up translated until the real estimate is ready. + strncpy(session_left, T("battery.calculating"), sizeof(session_left) - 1); + session_left[sizeof(session_left) - 1] = '\0'; PAD_init(); PWR_init(); @@ -732,16 +738,16 @@ int main(int argc, char *argv[]) switch (current_zoom) { case 0: - sprintf(display_name, "Battery usage: Last %s", "16 hours"); + snprintf(display_name, sizeof(display_name), T("battery.usage_fmt"), T("battery.range.16h")); break; case 1: - sprintf(display_name, "Battery usage: Last %s", "8 hours"); + snprintf(display_name, sizeof(display_name), T("battery.usage_fmt"), T("battery.range.8h")); break; case 2: - sprintf(display_name, "Battery usage: Last %s", "4 hours"); + snprintf(display_name, sizeof(display_name), T("battery.usage_fmt"), T("battery.range.4h")); break; default: - sprintf(display_name, "Battery usage: Last %s", "8 hours"); + snprintf(display_name, sizeof(display_name), T("battery.usage_fmt"), T("battery.range.8h")); break; } @@ -762,9 +768,9 @@ int main(int argc, char *argv[]) if (show_setting) GFX_blitHardwareHints(screen, show_setting); else - GFX_blitButtonGroup((char *[]){"L/R", "SCROLL", "L1/R1", "ZOOM", NULL}, 0, screen, 0); + GFX_blitButtonGroup((char *[]){T("btn.left_right"), T("btn.scroll"), "L1/R1", T("btn.zoom"), NULL}, 0, screen, 0); - GFX_blitButtonGroup((char *[]){"B", "BACK", NULL}, 1, screen, 1); + GFX_blitButtonGroup((char *[]){"B", T("btn.back"), NULL}, 1, screen, 1); GFX_flip(screen); dirty = 0; diff --git a/workspace/all/battery/makefile b/workspace/all/battery/makefile index 98856bfba..02fbc3e9c 100644 --- a/workspace/all/battery/makefile +++ b/workspace/all/battery/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = battery INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS += $(OPT) diff --git a/workspace/all/bootlogo/bootlogo.c b/workspace/all/bootlogo/bootlogo.c index d1201dc50..ac7c54bf2 100644 --- a/workspace/all/bootlogo/bootlogo.c +++ b/workspace/all/bootlogo/bootlogo.c @@ -9,6 +9,7 @@ #include "defines.h" #include "api.h" #include "utils.h" +#include "i18n.h" static bool quit = false; @@ -179,8 +180,8 @@ int main(int argc, char *argv[]) SDL_BlitSurface(image, NULL, screen, &image_rect); } - GFX_blitButtonGroup((char *[]){"L/R", "SCROLL", NULL}, 0, screen, 0); - GFX_blitButtonGroup((char *[]){"A", "SET", "B", "BACK", NULL}, 1, screen, 1); + GFX_blitButtonGroup((char *[]){T("btn.left_right"), T("btn.scroll"), NULL}, 0, screen, 0); + GFX_blitButtonGroup((char *[]){"A", T("btn.set"), "B", T("btn.back"), NULL}, 1, screen, 1); GFX_flip(screen); dirty = 0; diff --git a/workspace/all/bootlogo/makefile b/workspace/all/bootlogo/makefile index b9a16f2bb..521120d60 100644 --- a/workspace/all/bootlogo/makefile +++ b/workspace/all/bootlogo/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = bootlogo INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS += $(OPT) diff --git a/workspace/all/clock/clock.c b/workspace/all/clock/clock.c index 3734f6329..9fdb11e9f 100644 --- a/workspace/all/clock/clock.c +++ b/workspace/all/clock/clock.c @@ -1,6 +1,7 @@ // loosely based on https://github.com/gameblabla/clock_sdl_app #include +#include #include #include #include @@ -8,6 +9,7 @@ #include "defines.h" #include "api.h" #include "utils.h" +#include "i18n.h" enum { CURSOR_YEAR, @@ -139,6 +141,16 @@ int main(int argc , char* argv[]) { int option_count = 7; + // Date field order is localised via the clock.date_order key. + // Values: "YMD" (default / ISO), "DMY" (FR/UK/...), "MDY" (US). + int date_order[3] = { CURSOR_YEAR, CURSOR_MONTH, CURSOR_DAY }; + const char *date_order_str = T("clock.date_order"); + if (strcmp(date_order_str, "DMY") == 0) { + date_order[0] = CURSOR_DAY; date_order[1] = CURSOR_MONTH; date_order[2] = CURSOR_YEAR; + } else if (strcmp(date_order_str, "MDY") == 0) { + date_order[0] = CURSOR_MONTH; date_order[1] = CURSOR_DAY; date_order[2] = CURSOR_YEAR; + } + int dirty = 1; int show_setting = 0; int was_online = PWR_isOnline(); @@ -254,9 +266,9 @@ int main(int argc , char* argv[]) { GFX_blitHardwareGroup(screen, show_setting); if (show_setting) GFX_blitHardwareHints(screen, show_setting); - else GFX_blitButtonGroup((char*[]){ "SELECT",show_24hour?"12 HOUR":"24 HOUR", NULL }, 0, screen, 0); + else GFX_blitButtonGroup((char*[]){ "SELECT",show_24hour?T("btn.hr_12"):T("btn.hr_24"), NULL }, 0, screen, 0); - GFX_blitButtonGroup((char*[]){ "B","CANCEL", "A","SET", NULL }, 1, screen, 1); + GFX_blitButtonGroup((char*[]){ "B",T("btn.cancel"), "A",T("btn.set"), NULL }, 1, screen, 1); // 376 or 446 (@2x) // 188 or 223 (@1x) @@ -265,12 +277,15 @@ int main(int argc , char* argv[]) { // datetime int x = ox; int y = SCALE1((((FIXED_HEIGHT / FIXED_SCALE)-PILL_SIZE-DIGIT_HEIGHT)/2)); - - x = blitNumber(year_selected, x,y); - x = blit(CHAR_SLASH, x,y); - x = blitNumber(month_selected, x,y); - x = blit(CHAR_SLASH, x,y); - x = blitNumber(day_selected, x,y); + + for (int i = 0; i < 3; ++i) { + int field = date_order[i]; + int value = (field == CURSOR_YEAR) ? (int)year_selected + : (field == CURSOR_MONTH) ? month_selected + : day_selected; + x = blitNumber(value, x, y); + if (i < 2) x = blit(CHAR_SLASH, x, y); + } x += SCALE1(10); // space am_selected = hour_selected < 12; @@ -294,7 +309,7 @@ int main(int argc , char* argv[]) { int ampm_w; if (!show_24hour) { x += SCALE1(10); // space - SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, am_selected ? "AM" : "PM", COLOR_WHITE); + SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, am_selected ? T("clock.am") : T("clock.pm"), COLOR_WHITE); ampm_w = text->w + SCALE1(2); SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){x,y-SCALE1(3)}); SDL_FreeSurface(text); @@ -303,9 +318,15 @@ int main(int argc , char* argv[]) { // cursor x = ox; y += SCALE1(19); - if (select_cursor!=CURSOR_YEAR) { - x += SCALE1(50); // YYYY/ - x += (select_cursor - 1) * SCALE1(30); + if (select_cursor <= CURSOR_DAY) { + int dx = 0; + for (int i = 0; i < 3; ++i) { + if (date_order[i] == select_cursor) { x += dx; break; } + dx += (date_order[i] == CURSOR_YEAR ? SCALE1(40) : SCALE1(20)) + SCALE1(10); + } + } else { + // after the date block (always 100 wide) + space + x += SCALE1(100) + SCALE1(10) + (select_cursor - CURSOR_HOUR) * SCALE1(30); } blitBar(x,y, (select_cursor==CURSOR_YEAR ? SCALE1(40) : (select_cursor==CURSOR_AMPM ? ampm_w : SCALE1(20)))); diff --git a/workspace/all/clock/makefile b/workspace/all/clock/makefile index cd5b08bde..006ecbb75 100644 --- a/workspace/all/clock/makefile +++ b/workspace/all/clock/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = clock INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS += $(OPT) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.c index 91661f39c..6cfd029a9 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.c @@ -18,6 +18,7 @@ #include "utils.h" #include "config.h" +#include "i18n.h" #include @@ -351,6 +352,10 @@ SDL_Surface *GFX_init(int mode) CFG_init(GFX_loadSystemFont, GFX_updateColors); + // i18n loads the language saved in settings; falls back to English when + // the file is missing. Strings are dispatched at runtime via T("key"). + I18N_init(CFG_getLanguage()); + // by default, we will clear with whatever background color the user prefers // if MODE_MENU /e.g. minarch, clear with default black) if(mode == MODE_MAIN) @@ -2157,11 +2162,11 @@ void GFX_blitHardwareHints(SDL_Surface *dst, int show_setting) { if (show_setting == 1) - GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, "BRIGHTNESS", NULL}, 0, dst, 0); + GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, T("hw.brightness"), NULL}, 0, dst, 0); else if (show_setting == 3) - GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, "COLOR TEMP", NULL}, 0, dst, 0); + GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, T("hw.color_temp"), NULL}, 0, dst, 0); else - GFX_blitButtonGroup((char *[]){"MNU", "BRGHT", "SEL", "CLTMP", NULL}, 0, dst, 0); + GFX_blitButtonGroup((char *[]){T("hw.mnu"), T("hw.brght"), T("hw.sel"), T("hw.cltmp"), NULL}, 0, dst, 0); } int GFX_blitButtonGroup(char **pairs, int primary, SDL_Surface *dst, int align_right) diff --git a/workspace/all/common/config.c b/workspace/all/common/config.c index 37279d99a..8d99b1e89 100644 --- a/workspace/all/common/config.c +++ b/workspace/all/common/config.c @@ -93,6 +93,8 @@ void CFG_defaults(NextUISettings *cfg) .raProgressNotificationDuration = CFG_DEFAULT_RA_PROGRESS_NOTIFICATION_DURATION, .raAchievementSortOrder = CFG_DEFAULT_RA_ACHIEVEMENT_SORT_ORDER, + .language = "en", + }; *cfg = defaults; @@ -406,6 +408,14 @@ void CFG_init(FontLoad_callback_t cb, ColorSet_callback_t ccb) CFG_setRAAchievementSortOrder(temp_value); continue; } + { + char lang_buf[16]; + if (sscanf(line, "language=%15s", lang_buf) == 1) + { + CFG_setLanguage(lang_buf); + continue; + } + } } fclose(file); } @@ -1067,6 +1077,19 @@ void CFG_setRAAchievementSortOrder(int sortOrder) CFG_sync(); } +const char* CFG_getLanguage(void) +{ + return settings.language[0] ? settings.language : "en"; +} + +void CFG_setLanguage(const char* code) +{ + if (!code || !*code) code = "en"; + strncpy(settings.language, code, sizeof(settings.language) - 1); + settings.language[sizeof(settings.language) - 1] = '\0'; + CFG_sync(); +} + void CFG_get(const char *key, char *value) { if (strcmp(key, "font") == 0) @@ -1310,6 +1333,7 @@ void CFG_sync(void) fprintf(file, "raNotificationDuration=%i\n", settings.raNotificationDuration); fprintf(file, "raProgressNotificationDuration=%i\n", settings.raProgressNotificationDuration); fprintf(file, "raAchievementSortOrder=%i\n", settings.raAchievementSortOrder); + fprintf(file, "language=%s\n", CFG_getLanguage()); fclose(file); } diff --git a/workspace/all/common/config.h b/workspace/all/common/config.h index 3a3a4eaa5..9f8700b1d 100644 --- a/workspace/all/common/config.h +++ b/workspace/all/common/config.h @@ -163,6 +163,9 @@ typedef struct int raProgressNotificationDuration; // Duration for progress notifications (0-5 seconds, 0 = disabled) int raAchievementSortOrder; // Sort order for achievements list (RA_SORT_* enum) + // Language + char language[8]; // ISO-like code, e.g. "en", "fr" + } NextUISettings; // Transition mode constants @@ -400,6 +403,10 @@ void CFG_setRAProgressNotificationDuration(int seconds); int CFG_getRAAchievementSortOrder(void); void CFG_setRAAchievementSortOrder(int sortOrder); +// Language (i18n) +const char* CFG_getLanguage(void); +void CFG_setLanguage(const char* code); + void CFG_sync(void); void CFG_quit(void); diff --git a/workspace/all/common/i18n.c b/workspace/all/common/i18n.c new file mode 100644 index 000000000..9ce4252d3 --- /dev/null +++ b/workspace/all/common/i18n.c @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include + +#include "defines.h" +#include "utils.h" +#include "i18n.h" + +#define I18N_BUCKETS 4096 +#define I18N_ARENA_SIZE (256 * 1024) +#define I18N_LINE_MAX 1024 + +typedef struct { + uint32_t hash; + const char *key; + const char *value; +} i18n_entry; + +static i18n_entry s_table[I18N_BUCKETS]; +static char s_arena[I18N_ARENA_SIZE]; +static size_t s_arena_used; +static char s_active_code[I18N_LANG_CODE_MAX]; +static int s_inited; + +static uint32_t fnv1a(const char *s) { + uint32_t h = 0x811c9dc5u; + while (*s) { + h ^= (uint8_t)*s++; + h *= 0x01000193u; + } + return h; +} + +static const char *arena_dup(const char *s, size_t len) { + if (s_arena_used + len + 1 > I18N_ARENA_SIZE) return NULL; + char *p = s_arena + s_arena_used; + memcpy(p, s, len); + p[len] = '\0'; + s_arena_used += len + 1; + return p; +} + +static void table_clear(void) { + memset(s_table, 0, sizeof(s_table)); + s_arena_used = 0; +} + +static int table_insert(const char *key, size_t klen, const char *val, size_t vlen) { + char keybuf[256]; + if (klen >= sizeof(keybuf)) return 0; + memcpy(keybuf, key, klen); + keybuf[klen] = '\0'; + + uint32_t h = fnv1a(keybuf); + if (h == 0) h = 1; + + size_t mask = I18N_BUCKETS - 1; + size_t i = h & mask; + for (size_t probe = 0; probe < I18N_BUCKETS; ++probe) { + i18n_entry *e = &s_table[i]; + if (e->hash == 0) { + const char *dk = arena_dup(key, klen); + const char *dv = arena_dup(val, vlen); + if (!dk || !dv) return 0; + e->hash = h; + e->key = dk; + e->value = dv; + return 1; + } + if (e->hash == h && strcmp(e->key, keybuf) == 0) { + const char *dv = arena_dup(val, vlen); + if (!dv) return 0; + e->value = dv; + return 1; + } + i = (i + 1) & mask; + } + return 0; +} + +static void strip_trailing_ws(char *s, size_t *len) { + while (*len > 0 && (s[*len - 1] == ' ' || s[*len - 1] == '\t' || + s[*len - 1] == '\r' || s[*len - 1] == '\n')) { + s[--(*len)] = '\0'; + } +} + +static int parse_file(const char *path) { + FILE *f = fopen(path, "r"); + if (!f) return 0; + + char line[I18N_LINE_MAX]; + while (fgets(line, sizeof(line), f)) { + char *p = line; + while (*p == ' ' || *p == '\t') ++p; + if (*p == '\0' || *p == '#' || *p == '\n' || *p == '\r') continue; + + char *eq = strchr(p, '='); + if (!eq) continue; + + size_t klen = (size_t)(eq - p); + while (klen > 0 && (p[klen - 1] == ' ' || p[klen - 1] == '\t')) --klen; + if (klen == 0) continue; + + char *v = eq + 1; + while (*v == ' ' || *v == '\t') ++v; + + size_t vlen = strlen(v); + strip_trailing_ws(v, &vlen); + + /* Decode \n / \t / \\ escapes in place so multi-line dialog + * strings can sit on a single .lang line. Done after trimming + * so trailing literal whitespace is already gone. */ + size_t r = 0, w = 0; + while (r < vlen) { + if (v[r] == '\\' && r + 1 < vlen) { + char nxt = v[r + 1]; + if (nxt == 'n') { v[w++] = '\n'; r += 2; continue; } + if (nxt == 't') { v[w++] = '\t'; r += 2; continue; } + if (nxt == '\\') { v[w++] = '\\'; r += 2; continue; } + } + v[w++] = v[r++]; + } + v[w] = '\0'; + vlen = w; + + table_insert(p, klen, v, vlen); + } + fclose(f); + return 1; +} + +static int load_lang(const char *lang_code) { + table_clear(); + + char path[MAX_PATH]; + snprintf(path, sizeof(path), "%s/en.lang", SDCARD_PATH I18N_LANG_DIR); + parse_file(path); + + if (lang_code && *lang_code && strcmp(lang_code, "en") != 0) { + snprintf(path, sizeof(path), "%s/%s.lang", + SDCARD_PATH I18N_LANG_DIR, lang_code); + parse_file(path); + } + + strncpy(s_active_code, (lang_code && *lang_code) ? lang_code : "en", + sizeof(s_active_code) - 1); + s_active_code[sizeof(s_active_code) - 1] = '\0'; + return 1; +} + +void I18N_init(const char *lang_code) { + if (s_inited) return; + s_inited = 1; + load_lang(lang_code); +} + +int I18N_reload(const char *lang_code) { + return load_lang(lang_code); +} + +void I18N_quit(void) { + table_clear(); + s_active_code[0] = '\0'; + s_inited = 0; +} + +char *I18N_t(const char *key) { + static char empty[1] = { '\0' }; + if (!key) return empty; + if (!s_inited) return (char *)key; + + uint32_t h = fnv1a(key); + if (h == 0) h = 1; + + size_t mask = I18N_BUCKETS - 1; + size_t i = h & mask; + for (size_t probe = 0; probe < I18N_BUCKETS; ++probe) { + i18n_entry *e = &s_table[i]; + if (e->hash == 0) return (char *)key; + if (e->hash == h && strcmp(e->key, key) == 0) return (char *)e->value; + i = (i + 1) & mask; + } + return (char *)key; +} + +const char *I18N_active_code(void) { + return s_active_code[0] ? s_active_code : "en"; +} diff --git a/workspace/all/common/i18n.h b/workspace/all/common/i18n.h new file mode 100644 index 000000000..97a1a06db --- /dev/null +++ b/workspace/all/common/i18n.h @@ -0,0 +1,23 @@ +#ifndef __I18N_H__ +#define __I18N_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define I18N_LANG_CODE_MAX 8 +#define I18N_LANG_DIR "/.system/res/lang" + +void I18N_init(const char *lang_code); +void I18N_quit(void); +int I18N_reload(const char *lang_code); +char *I18N_t(const char *key); +const char *I18N_active_code(void); + +#define T(k) I18N_t(k) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/workspace/all/gametime/gametime.c b/workspace/all/gametime/gametime.c index 49f23c4ca..1c2cdbb6e 100644 --- a/workspace/all/gametime/gametime.c +++ b/workspace/all/gametime/gametime.c @@ -8,6 +8,7 @@ #include "defines.h" #include "api.h" #include "utils.h" +#include "i18n.h" #include #include @@ -273,7 +274,11 @@ void renderList(int count, int start, int end, int selected) serializeTime(average, entry->play_time_average); snprintf(plays, 24, "%d", entry->play_count); - const char *details[] = {"TOTAL ", total, " AVERAGE ", average, " # PLAYS ", plays}; + char total_label[32], avg_label[32], plays_label[32]; + snprintf(total_label, sizeof(total_label), "%s ", T("gametime.total")); + snprintf(avg_label, sizeof(avg_label), " %s ", T("gametime.average")); + snprintf(plays_label, sizeof(plays_label), " %s ", T("gametime.plays")); + const char *details[] = {total_label, total, avg_label, average, plays_label, plays}; SDL_Rect detailsRect = { layout.list_display_start_x + num_width + thumbMargin + SCALE1(IMG_MAX_WIDTH), layout.list_display_start_y + thumbMargin + textHeight + elemHeight * row, @@ -417,7 +422,7 @@ int main(int argc, char *argv[]) char play_time_total_formatted[255]; serializeTime(play_time_total_formatted, play_time_total); char display_name[256]; - sprintf(display_name, "Time spent having fun: %s", play_time_total_formatted); + snprintf(display_name, sizeof(display_name), T("gametime.title_fmt"), play_time_total_formatted); char title[256]; int text_width = GFX_truncateText(font.large, display_name, title, max_width, SCALE1(BUTTON_PADDING * 2)); @@ -435,9 +440,9 @@ int main(int argc, char *argv[]) if (show_setting) GFX_blitHardwareHints(screen, show_setting); else - GFX_blitButtonGroup((char *[]){"U/D", "SCROLL", NULL}, 0, screen, 0); + GFX_blitButtonGroup((char *[]){T("btn.up_down"), T("btn.scroll"), NULL}, 0, screen, 0); - GFX_blitButtonGroup((char *[]){"B", "BACK", NULL}, 1, screen, 1); + GFX_blitButtonGroup((char *[]){"B", T("btn.back"), NULL}, 1, screen, 1); GFX_flip(screen); dirty = 0; diff --git a/workspace/all/gametime/makefile b/workspace/all/gametime/makefile index 6448fcee6..8ccf80202 100644 --- a/workspace/all/gametime/makefile +++ b/workspace/all/gametime/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = gametime INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS += $(OPT) diff --git a/workspace/all/ledcontrol/ledcontrol.c b/workspace/all/ledcontrol/ledcontrol.c index 0452703f1..670a6c026 100644 --- a/workspace/all/ledcontrol/ledcontrol.c +++ b/workspace/all/ledcontrol/ledcontrol.c @@ -5,6 +5,7 @@ #include "defines.h" #include "api.h" #include "utils.h" +#include "i18n.h" #define NUM_OPTIONS 4 @@ -16,18 +17,27 @@ const char *lightnames[4]; const char *triggernames[] = { "B", "A", "Y", "X", "L", "R", "FN1", "FN2", "MENU", "SELECT", "START", "ALL", "LR", "DPAD"}; -const char *effect_names[] = { - "Linear", "Breathe", "Interval Breathe", "Static", - "Blink 1", "Blink 2", "Blink 3", "Rainbow", "Twinkle", - "Fire", "Glitter", "NeonGlow", "Firefly", "Aurora", "Reactive"}; -const char *topbar_effect_names[] = { - "Linear", "Breathe", "Interval Breathe", "Static", - "Blink 1", "Blink 2", "Blink 3", "Rainbow", "Twinkle", - "Fire", "Glitter", "NeonGlow", "Firefly", "Aurora", "Reactive", "Topbar Rainbow", "Topbar night"}; -const char *lr_effect_names[] = { - "Linear", "Breathe", "Interval Breathe", "Static", - "Blink 1", "Blink 2", "Blink 3", "Rainbow", "Twinkle", - "Fire", "Glitter", "NeonGlow", "Firefly", "Aurora", "Reactive", "LR Rainbow", "LR Reactive"}; +// Effect labels — populated at runtime via T(); fallback values are English. +const char *effect_names[15]; +const char *topbar_effect_names[17]; +const char *lr_effect_names[17]; + +static void init_effect_labels(void) { + const char *base[] = { + T("led.effect.linear"), T("led.effect.breathe"), T("led.effect.interval_breathe"), T("led.effect.static"), + T("led.effect.blink1"), T("led.effect.blink2"), T("led.effect.blink3"), T("led.effect.rainbow"), T("led.effect.twinkle"), + T("led.effect.fire"), T("led.effect.glitter"), T("led.effect.neonglow"), T("led.effect.firefly"), T("led.effect.aurora"), T("led.effect.reactive"), + }; + for (int i = 0; i < 15; i++) { + effect_names[i] = base[i]; + topbar_effect_names[i] = base[i]; + lr_effect_names[i] = base[i]; + } + topbar_effect_names[15] = T("led.effect.topbar_rainbow"); + topbar_effect_names[16] = T("led.effect.topbar_night"); + lr_effect_names[15] = T("led.effect.lr_rainbow"); + lr_effect_names[16] = T("led.effect.lr_reactive"); +} void save_settings() { LOG_debug("saving settings plat\n"); @@ -219,15 +229,17 @@ int main(int argc, char *argv[]) InitSettings(); PWR_setCPUSpeed(CPU_SPEED_AUTO); + SDL_Surface* screen = GFX_init(MODE_MAIN); + // GFX_init has loaded the language; we can use T() now. + init_effect_labels(); if (is_brick) { - const char *brick_names[] = {"F1 key", "F2 key", "Top bar", "L&R triggers"}; + const char *brick_names[] = {T("led.f1_key"), T("led.f2_key"), T("led.top_bar"), T("led.lr_triggers")}; memcpy(lightnames, brick_names, sizeof(brick_names)); // Copy values } else { - const char *default_names[] = {"Joystick L","Joystick R", "Logo"}; + const char *default_names[] = {T("led.joystick_l"), T("led.joystick_r"), T("led.logo")}; memcpy(lightnames, default_names, sizeof(default_names)); // Copy values } - - SDL_Surface* screen = GFX_init(MODE_MAIN); + PAD_init(); PWR_init(); @@ -298,8 +310,8 @@ int main(int argc, char *argv[]) if (show_setting) GFX_blitHardwareHints(screen, show_setting); - GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 1, screen, 1); - GFX_blitButtonGroup((char*[]){ "L/R","Select light", NULL }, 0, screen, 0); + GFX_blitButtonGroup((char*[]){ "B",T("btn.back"), NULL }, 1, screen, 1); + GFX_blitButtonGroup((char*[]){ T("btn.left_right"),T("led.select_light"), NULL }, 0, screen, 0); int max_width = screen->w - SCALE1(PADDING * 2) - ow; @@ -322,10 +334,10 @@ int main(int argc, char *argv[]) // this stuff is really not multiplatform at all, need to figure out a way so its not so TrimUI specific const char *settings_labels[5]; // Define array with correct size if (is_brick) { - const char *brick_labels[] = {"Effect", "Color", "Speed", "Brightness", "Info brightness"}; + const char *brick_labels[] = {T("led.effect"), T("led.color"), T("led.speed"), T("led.brightness"), T("led.info_brightness")}; memcpy(settings_labels, brick_labels, sizeof(brick_labels)); // Copy values } else { - const char *non_brick_labels[] = {"Effect", "Color", "Speed", "Brightness (All Leds)", "Info brightness (All Leds)"}; + const char *non_brick_labels[] = {T("led.effect"), T("led.color"), T("led.speed"), T("led.brightness_all"), T("led.info_brightness_all")}; memcpy(settings_labels, non_brick_labels, sizeof(non_brick_labels)); // Copy values } int settings_values[5] = { diff --git a/workspace/all/ledcontrol/makefile b/workspace/all/ledcontrol/makefile index c893d5787..6aab5ab24 100644 --- a/workspace/all/ledcontrol/makefile +++ b/workspace/all/ledcontrol/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = ledcontrol INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS += $(OPT) diff --git a/workspace/all/minarch/ma_config.c b/workspace/all/minarch/ma_config.c index 83ba20627..5005abf44 100644 --- a/workspace/all/minarch/ma_config.c +++ b/workspace/all/minarch/ma_config.c @@ -893,29 +893,32 @@ void initShaders() { Moved from minarch.c; these are the static data backing the config module. ----------------------------------------------------------------------- */ +// Labels stored as i18n keys; the renderer applies T() at draw time so a +// language change is picked up live. Keys with no translation entry fall back +// to the literal (so the previous English text still shows up). char* onoff_labels[] = { - "Off", - "On", + "val.off", + "val.on", NULL }; char* scaling_labels[] = { - "Native", - "Aspect", - "Aspect Screen", - "Fullscreen", - "Cropped", + "frontend.lbl.scaling.native", + "frontend.lbl.scaling.aspect", + "frontend.lbl.scaling.aspect_screen", + "frontend.lbl.scaling.fullscreen", + "frontend.lbl.scaling.cropped", NULL }; static char* resample_labels[] = { - "Low", - "Medium", - "High", - "Max", + "frontend.lbl.resample.low", + "frontend.lbl.resample.medium", + "frontend.lbl.resample.high", + "frontend.lbl.resample.max", NULL }; static char* rewind_enable_labels[] = { - "Off", - "On", + "val.off", + "val.on", NULL }; static char* rewind_buffer_labels[] = { @@ -966,31 +969,31 @@ static char* rewind_compression_accel_values[] = { NULL }; static char* rewind_compression_accel_labels[] = { - "1 (best ratio)", - "2 (default)", - "4 (fast)", - "8 (faster)", - "12 (fastest)", + "frontend.lbl.rewind_compression.best", + "frontend.lbl.rewind_compression.default", + "frontend.lbl.rewind_compression.fast", + "frontend.lbl.rewind_compression.faster", + "frontend.lbl.rewind_compression.fastest", NULL }; static char* ambient_labels[] = { - "Off", - "All", - "Top", - "FN", - "LR", - "Top/LR", + "val.off", + "frontend.lbl.ambient.all", + "frontend.lbl.ambient.top", + "frontend.lbl.ambient.fn", + "frontend.lbl.ambient.lr", + "frontend.lbl.ambient.top_lr", NULL }; static char* effect_labels[] = { - "None", - "Line", - "Grid", + "frontend.lbl.effect.none", + "frontend.lbl.effect.line", + "frontend.lbl.effect.grid", NULL }; static char* overlay_labels[] = { - "None", + "frontend.lbl.overlay.none", NULL }; // static char* sharpness_labels[] = { @@ -1289,10 +1292,10 @@ char* gamepad_values[] = { char* getScreenScalingDesc(void) { if (GFX_supportsOverscan()) { - return "Native uses integer scaling. Aspect uses core nreported aspect ratio.\nAspect screen uses screen aspect ratio\n Fullscreen has non-square\npixels. Cropped is integer scaled then cropped."; + return "frontend.opt.screen_scaling.desc_overscan"; } else { - return "Native uses integer scaling.\nAspect uses core reported aspect ratio.\nAspect screen uses screen aspect ratio\nFullscreen has non-square pixels."; + return "frontend.opt.screen_scaling.desc_default"; } } int getScreenScalingCount(void) { @@ -1306,7 +1309,7 @@ struct Config config = { .options = (Option[]){ [FE_OPT_SCALING] = { .key = "minarch_screen_scaling", - .name = "Screen Scaling", + .name = "frontend.opt.screen_scaling", .desc = NULL, // will call getScreenScalingDesc() .default_value = 1, .value = 1, @@ -1316,8 +1319,8 @@ struct Config config = { }, [FE_OPT_RESAMPLING] = { .key = "minarch__resampling_quality", - .name = "Audio Resampling Quality", - .desc = "Resampling quality higher takes more CPU", + .name = "frontend.opt.resampling", + .desc = "frontend.opt.resampling.desc", .default_value = 2, .value = 2, .count = 4, @@ -1326,8 +1329,8 @@ struct Config config = { }, [FE_OPT_AMBIENT] = { .key = "minarch_ambient", - .name = "Ambient Mode", - .desc = "Makes your leds follow on screen colors", + .name = "frontend.opt.ambient", + .desc = "frontend.opt.ambient.desc", .default_value = 0, .value = 0, .count = 6, @@ -1336,8 +1339,8 @@ struct Config config = { }, [FE_OPT_EFFECT] = { .key = "minarch_screen_effect", - .name = "Screen Effect", - .desc = "Grid simulates an LCD grid.\nLine simulates CRT scanlines.\nEffects usually look best at native scaling.", + .name = "frontend.opt.effect", + .desc = "frontend.opt.effect.desc", .default_value = 0, .value = 0, .count = 3, @@ -1346,8 +1349,8 @@ struct Config config = { }, [FE_OPT_OVERLAY] = { .key = "minarch_overlay", - .name = "Overlay", - .desc = "Choose a custom overlay png from the Overlays folder", + .name = "frontend.opt.overlay", + .desc = "frontend.opt.overlay.desc", .default_value = 0, .value = 0, .count = 1, @@ -1356,8 +1359,8 @@ struct Config config = { }, [FE_OPT_SCREENX] = { .key = "minarch_screen_offsetx", - .name = "Offset screen X", - .desc = "Offset X pixels", + .name = "frontend.opt.offset_x", + .desc = "frontend.opt.offset_x.desc", .default_value = 64, .value = 64, .count = 129, @@ -1366,8 +1369,8 @@ struct Config config = { }, [FE_OPT_SCREENY] = { .key = "minarch_screen_offsety", - .name = "Offset screen Y", - .desc = "Offset Y pixels", + .name = "frontend.opt.offset_y", + .desc = "frontend.opt.offset_y.desc", .default_value = 64, .value = 64, .count = 129, @@ -1377,8 +1380,8 @@ struct Config config = { [FE_OPT_SHARPNESS] = { // .key = "minarch_screen_sharpness", .key = "minarch_scale_filter", - .name = "Screen Sharpness", - .desc = "LINEAR smooths lines, but works better when final image is at higher resolution, so either core that outputs higher resolution or upscaling with shaders", + .name = "frontend.opt.sharpness", + .desc = "frontend.opt.sharpness.desc", .default_value = 1, .value = 1, .count = 3, @@ -1387,8 +1390,8 @@ struct Config config = { }, [FE_OPT_SYNC_REFERENCE] = { .key = "minarch_sync_reference", - .name = "Core Sync", - .desc = "Choose what should be used as a\nreference for the frame rate.\n\"Native\" uses the emulator frame rate,\n\"Screen\" uses the frame rate of the screen.", + .name = "frontend.opt.core_sync", + .desc = "frontend.opt.core_sync.desc", .default_value = SYNC_SRC_AUTO, .value = SYNC_SRC_AUTO, .count = 3, @@ -1397,8 +1400,8 @@ struct Config config = { }, [FE_OPT_OVERCLOCK] = { .key = "minarch_cpu_speed", - .name = "CPU Speed", - .desc = "Choose how the CPU scales.\nAuto is recommended for most users.", + .name = "frontend.opt.cpu_speed", + .desc = "frontend.opt.cpu_speed.desc", .default_value = 0, .value = 0, .count = 3, @@ -1407,8 +1410,8 @@ struct Config config = { }, [FE_OPT_DEBUG] = { .key = "minarch_debug_hud", - .name = "Debug HUD", - .desc = "Show frames per second, cpu load,\nresolution, and scaler information.", + .name = "frontend.opt.debug_hud", + .desc = "frontend.opt.debug_hud.desc", .default_value = 0, .value = 0, .count = 2, @@ -1417,8 +1420,8 @@ struct Config config = { }, [FE_OPT_MAXFF] = { .key = "minarch_max_ff_speed", - .name = "Max FF Speed", - .desc = "Fast forward will not exceed the\nselected speed (but may be less\ndepending on game and emulator).", + .name = "frontend.opt.max_ff", + .desc = "frontend.opt.max_ff.desc", .default_value = 3, // 4x .value = 3, // 4x .count = 8, @@ -1427,8 +1430,8 @@ struct Config config = { }, [FE_OPT_FF_AUDIO] = { .key = "minarch__ff_audio", - .name = "Fast forward audio", - .desc = "Play or mute audio when fast forwarding.", + .name = "frontend.opt.ff_audio", + .desc = "frontend.opt.ff_audio.desc", .default_value = 0, .value = 0, .count = 2, @@ -1437,8 +1440,8 @@ struct Config config = { }, [FE_OPT_REWIND_ENABLE] = { .key = "minarch_rewind_enable", - .name = "Rewind", - .desc = "Enable in-memory rewind buffer.\nMust set a shortcut to access rewind during gameplay.\nUses extra CPU and memory.", + .name = "frontend.opt.rewind", + .desc = "frontend.opt.rewind.desc", .default_value = MINARCH_DEFAULT_REWIND_ENABLE ? 1 : 0, .value = MINARCH_DEFAULT_REWIND_ENABLE ? 1 : 0, .count = 2, @@ -1447,8 +1450,8 @@ struct Config config = { }, [FE_OPT_REWIND_BUFFER] = { .key = "minarch_rewind_buffer_mb", - .name = "Rewind Buffer (MB)", - .desc = "Memory reserved for rewind snapshots.\nIncrease for longer rewind times.", + .name = "frontend.opt.rewind_buffer", + .desc = "frontend.opt.rewind_buffer.desc", .default_value = 3, // 64MB .value = 3, .count = 6, @@ -1457,8 +1460,8 @@ struct Config config = { }, [FE_OPT_REWIND_GRANULARITY] = { .key = "minarch_rewind_granularity", - .name = "Rewind Interval", - .desc = "Interval between rewind snapshots.\nShorter intervals improve smoothness during rewind,\nbut increase CPU and memory usage.", + .name = "frontend.opt.rewind_interval", + .desc = "frontend.opt.rewind_interval.desc", .default_value = 0, // 16ms .value = 0, .count = 12, @@ -1467,8 +1470,8 @@ struct Config config = { }, [FE_OPT_REWIND_COMPRESSION] = { .key = "minarch_rewind_compression", - .name = "Rewind Compression", - .desc = "Compress rewind snapshots to save memory at the cost of CPU.", + .name = "frontend.opt.rewind_compression", + .desc = "frontend.opt.rewind_compression.desc", .default_value = 1, .value = 1, .count = 2, @@ -1477,8 +1480,8 @@ struct Config config = { }, [FE_OPT_REWIND_COMPRESSION_ACCEL] = { .key = "minarch_rewind_compression_speed", - .name = "Rewind Compression Speed", - .desc = "LZ4 acceleration used for rewind snapshots.\nLower values compress more but use more CPU.", + .name = "frontend.opt.rewind_compression_speed", + .desc = "frontend.opt.rewind_compression_speed.desc", .default_value = 1, // value 2 .value = 1, .count = 5, @@ -1487,8 +1490,8 @@ struct Config config = { }, [FE_OPT_REWIND_AUDIO] = { .key = "minarch_rewind_audio", - .name = "Rewind audio", - .desc = "Play or mute audio when rewinding.", + .name = "frontend.opt.rewind_audio", + .desc = "frontend.opt.rewind_audio.desc", .default_value = MINARCH_DEFAULT_REWIND_AUDIO ? 1 : 0, .value = MINARCH_DEFAULT_REWIND_AUDIO ? 1 : 0, .count = 2, @@ -1509,8 +1512,8 @@ struct Config config = { .options = (Option[]){ [SH_EXTRASETTINGS] = { .key = "minarch_shaders_settings", - .name = "Optional Shaders Settings", - .desc = "If shaders have extra settings they will show up in this settings menu", + .name = "frontend.opt.opt_shaders", + .desc = "frontend.opt.opt_shaders.desc", .default_value = 1, .value = 1, .count = 0, @@ -1519,8 +1522,8 @@ struct Config config = { }, [SH_SHADERS_PRESET] = { .key = "minarch_shaders_preset", - .name = "Shader / Emulator Settings Preset", - .desc = "Load a premade shaders/emulators config.\nTo try out a preset, exit the game without saving settings!", + .name = "frontend.opt.shader_preset", + .desc = "frontend.opt.shader_preset.desc", .default_value = 1, .value = 1, .count = 0, @@ -1529,8 +1532,8 @@ struct Config config = { }, [SH_NROFSHADERS] = { .key = "minarch_nrofshaders", - .name = "Number of Shaders", - .desc = "Number of shaders 1 to 3", + .name = "frontend.opt.shader_count", + .desc = "frontend.opt.shader_count.desc", .default_value = 0, .value = 0, .count = 4, @@ -1540,8 +1543,8 @@ struct Config config = { [SH_SHADER1] = { .key = "minarch_shader1", - .name = "Shader 1", - .desc = "Shader 1 program to run", + .name = "frontend.opt.shader_1", + .desc = "frontend.opt.shader_1.desc", .default_value = 1, .value = 1, .count = 0, @@ -1550,8 +1553,8 @@ struct Config config = { }, [SH_SHADER1_FILTER] = { .key = "minarch_shader1_filter", - .name = "Shader 1 Filter", - .desc = "Method of upscaling, NEAREST or LINEAR", + .name = "frontend.opt.shader_1_filter", + .desc = "frontend.opt.shader_filter.desc", .default_value = 1, .value = 1, .count = 2, @@ -1560,8 +1563,8 @@ struct Config config = { }, [SH_SRCTYPE1] = { .key = "minarch_shader1_srctype", - .name = "Shader 1 Source type", - .desc = "This will choose resolution source to scale from", + .name = "frontend.opt.shader_1_source", + .desc = "frontend.opt.shader_source.desc", .default_value = 0, .value = 0, .count = 2, @@ -1570,8 +1573,8 @@ struct Config config = { }, [SH_SCALETYPE1] = { .key = "minarch_shader1_scaletype", - .name = "Shader 1 Texture Type", - .desc = "This will choose resolution source to scale from", + .name = "frontend.opt.shader_1_texture", + .desc = "frontend.opt.shader_source.desc", .default_value = 1, .value = 1, .count = 2, @@ -1580,8 +1583,8 @@ struct Config config = { }, [SH_UPSCALE1] = { .key = "minarch_shader1_upscale", - .name = "Shader 1 Scale", - .desc = "This will scale images x times,\nscreen scales to screens resolution (can hit performance)", + .name = "frontend.opt.shader_1_scale", + .desc = "frontend.opt.shader_scale.desc", .default_value = 1, .value = 1, .count = 9, @@ -1590,8 +1593,8 @@ struct Config config = { }, [SH_SHADER2] = { .key = "minarch_shader2", - .name = "Shader 2", - .desc = "Shader 2 program to run", + .name = "frontend.opt.shader_2", + .desc = "frontend.opt.shader_2.desc", .default_value = 0, .value = 0, .count = 0, @@ -1601,8 +1604,8 @@ struct Config config = { }, [SH_SHADER2_FILTER] = { .key = "minarch_shader2_filter", - .name = "Shader 2 Filter", - .desc = "Method of upscaling, NEAREST or LINEAR", + .name = "frontend.opt.shader_2_filter", + .desc = "frontend.opt.shader_filter.desc", .default_value = 0, .value = 0, .count = 2, @@ -1611,8 +1614,8 @@ struct Config config = { }, [SH_SRCTYPE2] = { .key = "minarch_shader2_srctype", - .name = "Shader 2 Source type", - .desc = "This will choose resolution source to scale from", + .name = "frontend.opt.shader_2_source", + .desc = "frontend.opt.shader_source.desc", .default_value = 0, .value = 0, .count = 2, @@ -1621,8 +1624,8 @@ struct Config config = { }, [SH_SCALETYPE2] = { .key = "minarch_shader2_scaletype", - .name = "Shader 2 Texture Type", - .desc = "This will choose resolution source to scale from", + .name = "frontend.opt.shader_2_texture", + .desc = "frontend.opt.shader_source.desc", .default_value = 1, .value = 1, .count = 2, @@ -1631,8 +1634,8 @@ struct Config config = { }, [SH_UPSCALE2] = { .key = "minarch_shader2_upscale", - .name = "Shader 2 Scale", - .desc = "This will scale images x times,\nscreen scales to screens resolution (can hit performance)", + .name = "frontend.opt.shader_2_scale", + .desc = "frontend.opt.shader_scale.desc", .default_value = 0, .value = 0, .count = 9, @@ -1641,8 +1644,8 @@ struct Config config = { }, [SH_SHADER3] = { .key = "minarch_shader3", - .name = "Shader 3", - .desc = "Shader 3 program to run", + .name = "frontend.opt.shader_3", + .desc = "frontend.opt.shader_3.desc", .default_value = 2, .value = 2, .count = 0, @@ -1652,8 +1655,8 @@ struct Config config = { }, [SH_SHADER3_FILTER] = { .key = "minarch_shader3_filter", - .name = "Shader 3 Filter", - .desc = "Method of upscaling, NEAREST or LINEAR", + .name = "frontend.opt.shader_3_filter", + .desc = "frontend.opt.shader_filter.desc", .default_value = 0, .value = 0, .count = 2, @@ -1662,8 +1665,8 @@ struct Config config = { }, [SH_SRCTYPE3] = { .key = "minarch_shader3_srctype", - .name = "Shader 3 Source type", - .desc = "This will choose resolution source to scale from", + .name = "frontend.opt.shader_3_source", + .desc = "frontend.opt.shader_source.desc", .default_value = 0, .value = 0, .count = 2, @@ -1672,8 +1675,8 @@ struct Config config = { }, [SH_SCALETYPE3] = { .key = "minarch_shader3_scaletype", - .name = "Shader 3 Texture Type", - .desc = "This will choose resolution source to scale from", + .name = "frontend.opt.shader_3_texture", + .desc = "frontend.opt.shader_source.desc", .default_value = 1, .value = 1, .count = 2, @@ -1682,8 +1685,8 @@ struct Config config = { }, [SH_UPSCALE3] = { .key = "minarch_shader3_upscale", - .name = "Shader 3 Scale", - .desc = "This will scale images x times,\nscreen scales to screens resolution (can hit performance)", + .name = "frontend.opt.shader_3_scale", + .desc = "frontend.opt.shader_scale.desc", .default_value = 0, .value = 0, .count = 9, diff --git a/workspace/all/minarch/ma_frontend_opts.c b/workspace/all/minarch/ma_frontend_opts.c index 3c786b96c..afc8d4b15 100644 --- a/workspace/all/minarch/ma_frontend_opts.c +++ b/workspace/all/minarch/ma_frontend_opts.c @@ -3,6 +3,7 @@ #include "ma_cheats.h" #include "ra_integration.h" #include "notification.h" +#include "i18n.h" #include #include @@ -140,7 +141,7 @@ static int OptionEmulator_optionDetail(MenuList* list, int i) { } else { Option* option = OptionList_getOption(&config.core, item->key); - if (option->full) return Menu_messageWithFont(option->full, (char*[]){ "B","BACK", NULL }, font.medium); + if (option->full) return Menu_messageWithFont(option->full, (char*[]){ "B",T("btn.back"), NULL }, font.medium); else return MENU_CALLBACK_NOP; } } @@ -216,10 +217,10 @@ int OptionEmulator_openMenu(MenuList* list, int index) { } else { if (list->category) { - Menu_message("This category has no options.", (char*[]){ "B","BACK", NULL }); + Menu_message(T("error.no_options_category"), (char*[]){ "B",T("btn.back"), NULL }); } else { - Menu_message("This core has no options.", (char*[]){ "B","BACK", NULL }); + Menu_message(T("error.no_options_core"), (char*[]){ "B",T("btn.back"), NULL }); } } @@ -293,6 +294,7 @@ static MenuList OptionControls_menu = { int OptionControls_openMenu(MenuList* list, int i) { LOG_info("OptionControls_openMenu\n"); + OptionControls_menu.desc = T("controls.set_hint"); if (OptionControls_menu.items==NULL) { @@ -302,8 +304,8 @@ int OptionControls_openMenu(MenuList* list, int i) { if (has_custom_controllers) { MenuItem* item = &OptionControls_menu.items[k++]; - item->name = "Controller"; - item->desc = "Select the type of controller."; + item->name = T("controls.controller"); + item->desc = T("controls.controller_desc"); item->value = gamepad_type; item->values = gamepad_labels; item->on_change = OptionControls_optionChanged; @@ -393,13 +395,14 @@ static MenuList OptionShortcuts_menu = { }; char* getSaveDesc(void) { switch (config.loaded) { - case CONFIG_NONE: return "Using defaults."; break; - case CONFIG_CONSOLE: return "Using console config."; break; - case CONFIG_GAME: return "Using game config."; break; + case CONFIG_NONE: return T("state.cfg.defaults"); break; + case CONFIG_CONSOLE: return T("state.cfg.console"); break; + case CONFIG_GAME: return T("state.cfg.game"); break; } return NULL; } int OptionShortcuts_openMenu(MenuList* list, int i) { + OptionShortcuts_menu.desc = T("controls.set_hint"); if (OptionShortcuts_menu.items==NULL) { // TODO: where do I free this? I guess I don't :sweat_smile: OptionShortcuts_menu.items = calloc(SHORTCUT_COUNT+1, sizeof(MenuItem)); @@ -432,22 +435,22 @@ static int OptionSaveChanges_onConfirm(MenuList* list, int i) { switch (i) { case 0: { Config_write(CONFIG_WRITE_ALL); - message = "Saved for console."; + message = T("state.msg.saved_console"); break; } case 1: { Config_write(CONFIG_WRITE_GAME); - message = "Saved for game."; + message = T("state.msg.saved_game"); break; } default: { Config_restore(); - if (config.loaded) message = "Restored console defaults."; - else message = "Restored defaults."; + if (config.loaded) message = T("state.msg.restored_console"); + else message = T("state.msg.restored_defaults"); break; } } - Menu_message(message, (char*[]){ "A","OKAY", NULL }); + Menu_message(message, (char*[]){ "A",T("btn.okay"), NULL }); OptionSaveChanges_updateDesc(); return MENU_CALLBACK_EXIT; } @@ -455,13 +458,16 @@ static MenuList OptionSaveChanges_menu = { .type = MENU_LIST, .on_confirm = OptionSaveChanges_onConfirm, .items = (MenuItem[]){ - {"Save for console"}, - {"Save for game"}, - {"Restore defaults"}, + {NULL}, + {NULL}, + {NULL}, {NULL}, } }; int OptionSaveChanges_openMenu(MenuList* list, int i) { + OptionSaveChanges_menu.items[0].name = T("menu.opt.save_for_console"); + OptionSaveChanges_menu.items[1].name = T("menu.opt.save_for_game"); + OptionSaveChanges_menu.items[2].name = T("menu.opt.restore_defaults"); OptionSaveChanges_updateDesc(); OptionSaveChanges_menu.desc = getSaveDesc(); Menu_options(&OptionSaveChanges_menu); @@ -480,7 +486,7 @@ static int OptionCheats_optionChanged(MenuList* list, int i) { // Block enabling cheats in RetroAchievements hardcore mode if (RA_isHardcoreModeActive() && item->value) { LOG_info("Cheat enable blocked - hardcore mode active\n"); - Notification_push(NOTIFICATION_ACHIEVEMENT, "Cheats disabled in Hardcore mode", NULL); + Notification_push(NOTIFICATION_ACHIEVEMENT, T("error.cheats_disabled_hardcore"), NULL); item->value = 0; // Revert the toggle return MENU_CALLBACK_NOP; } @@ -494,7 +500,7 @@ static int OptionCheats_optionDetail(MenuList* list, int i) { MenuItem* item = &list->items[i]; struct Cheat *cheat = &cheatcodes.cheats[i]; if (cheat->info) - return Menu_message((char*)cheat->info, (char*[]){ "B","BACK", NULL }); + return Menu_message((char*)cheat->info, (char*[]){ "B",T("btn.back"), NULL }); else return MENU_CALLBACK_NOP; } @@ -554,7 +560,7 @@ int OptionCheats_openMenu(MenuList* list, int i) { char cheats_path[CHEAT_MAX_LIST_LENGTH] = {0}; // prepend title with bounds checking - const char* title = "No cheat file loaded.\n\n"; + const char* title = T("error.no_cheat_file"); size_t title_len = strlen(title); strcpy(cheats_path, title); // Use strcpy for first string @@ -589,7 +595,7 @@ int OptionCheats_openMenu(MenuList* list, int i) { } } - Menu_messageWithFont(cheats_path, (char*[]){ "B","BACK", NULL }, font.small); + Menu_messageWithFont(cheats_path, (char*[]){ "B",T("btn.back"), NULL }, font.small); } return MENU_CALLBACK_NOP; @@ -646,7 +652,7 @@ static int OptionPragmas_openMenu(MenuList* list, int i) { if (PragmasOptions_menu.items[0].name) { Menu_options(&PragmasOptions_menu); } else { - Menu_message("No extra settings found", (char*[]){"B", "BACK", NULL}); + Menu_message(T("error.no_extras"), (char*[]){"B", T("btn.back"), NULL}); } return MENU_CALLBACK_NOP; @@ -692,7 +698,7 @@ int OptionShaders_openMenu(MenuList* list, int i) { // Check if folder read failed or no files found if (!filelist || filecount == 0) { - Menu_message("No shaders available\n/Shaders folder or shader files not found", (char*[]){"B", "BACK", NULL}); + Menu_message(T("error.no_shaders"), (char*[]){"B", T("btn.back"), NULL}); return MENU_CALLBACK_NOP; } @@ -721,7 +727,7 @@ int OptionShaders_openMenu(MenuList* list, int i) { if (ShaderOptions_menu.items[0].name) { Menu_options(&ShaderOptions_menu); } else { - Menu_message("No shaders available\n/Shaders folder or shader files not found", (char*[]){"B", "BACK", NULL}); + Menu_message(T("error.no_shaders"), (char*[]){"B", T("btn.back"), NULL}); } return MENU_CALLBACK_NOP; diff --git a/workspace/all/minarch/ma_menu.c b/workspace/all/minarch/ma_menu.c index 5a5c130d0..d139426d6 100644 --- a/workspace/all/minarch/ma_menu.c +++ b/workspace/all/minarch/ma_menu.c @@ -14,6 +14,7 @@ #include "notification.h" #include "ra_integration.h" #include "ra_badges.h" +#include "i18n.h" #include "ma_internal.h" #include "ma_cheats.h" #include "ma_saves.h" @@ -182,7 +183,13 @@ void Menu_init(void) { // always sanitized/outer name, to keep main UI from having to inspect archives sprintf(menu.slot_path, "%s/%s.txt", menu.minui_dir, game.name); - if (simple_mode) menu.items[ITEM_OPTS] = "Reset"; + menu.items[ITEM_CONT] = T("menu.continue"); + menu.items[ITEM_SAVE] = T("menu.save"); + menu.items[ITEM_LOAD] = T("menu.load"); + menu.items[ITEM_OPTS] = T("menu.options"); + menu.items[ITEM_QUIT] = T("menu.quit"); + + if (simple_mode) menu.items[ITEM_OPTS] = T("menu.reset"); if (game.m3u_path[0]) { char* tmp; @@ -435,7 +442,7 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { // Offline pending indicator if (is_offline_pending) { - SDL_Surface* offline_text = TTF_RenderUTF8_Blended(font.tiny, "Unlocked offline - pending sync", COLOR_LIGHT_TEXT); + SDL_Surface* offline_text = TTF_RenderUTF8_Blended(font.tiny, T("ach.offline_pending"), COLOR_LIGHT_TEXT); int wifi_size = SCALE1(12); int total_w = wifi_size + SCALE1(4) + offline_text->w; int icon_x = center_x - total_w / 2; @@ -488,7 +495,7 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { // Muted status below other info with gap before title if (is_muted) { - SDL_Surface* mute_text = TTF_RenderUTF8_Blended(font.tiny, "Muted - progress notifications silenced", COLOR_LIGHT_TEXT); + SDL_Surface* mute_text = TTF_RenderUTF8_Blended(font.tiny, T("ach.muted_silenced"), COLOR_LIGHT_TEXT); int mute_icon_w = SCALE1(10); int mute_icon_h = SCALE1(12); int total_w = mute_icon_w + SCALE1(4) + mute_text->w; @@ -505,7 +512,7 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { } // Button hints - update based on current mute state - char* hints[] = {"X", is_muted ? "UNMUTE" : "MUTE", "B", "BACK", NULL}; + char* hints[] = {"X", is_muted ? T("btn.unmute") : T("btn.mute"), "B", T("btn.back"), NULL}; GFX_blitButtonGroup(hints, 0, screen, 1); GFX_flip(screen); dirty = 0; @@ -521,7 +528,7 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { static int OptionAchievements_openMenu(MenuList* list, int i) { if (!RA_isGameLoaded()) { - Menu_message("No achievements found for this game.\n\nThis ROM may need a compatibility patch\nor may not be a supported version.\n\nVisit retroachievements.org to check\nsupported game files.", (char*[]){"B","BACK", NULL}); + Menu_message(T("ach.no_for_game"), (char*[]){"B",T("btn.back"), NULL}); return MENU_CALLBACK_NOP; } @@ -529,7 +536,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { RA_getAchievementSummary(&unlocked, &total); if (total == 0) { - Menu_message("No achievements available for this game.\n\nThis game may not have achievements yet.\n\nVisit retroachievements.org for details.", (char*[]){"B","BACK", NULL}); + Menu_message(T("ach.no_available"), (char*[]){"B",T("btn.back"), NULL}); return MENU_CALLBACK_NOP; } @@ -549,7 +556,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE); if (!ach_menu_list) { - Menu_message("Failed to load achievements", (char*[]){"B","BACK", NULL}); + Menu_message(T("ach.failed_load"), (char*[]){"B",T("btn.back"), NULL}); return MENU_CALLBACK_NOP; } @@ -564,7 +571,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { ach_menu_list = NULL; // This can happen with unsupported game versions where pseudo-achievements // are counted in the summary but not available in the achievement list - Menu_message("Achievement list not available", (char*[]){"B","BACK", NULL}); + Menu_message(T("ach.not_available"), (char*[]){"B",T("btn.back"), NULL}); return MENU_CALLBACK_NOP; } @@ -628,7 +635,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { ach_menu_list = NULL; ach_menu_achievements = NULL; ach_menu_count = 0; - Menu_message("No achievements found", (char*[]){"B","BACK", NULL}); + Menu_message(T("ach.none_found"), (char*[]){"B",T("btn.back"), NULL}); return MENU_CALLBACK_NOP; } @@ -880,7 +887,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { // Button hints at bottom with dynamic Y and X button text int selected_muted = (filtered_count > 0) ? RA_isAchievementMuted(filtered[selected]->id) : 0; - char* hints[] = {"Y", ach_filter_locked_only ? "SHOW ALL" : "SHOW LOCKED", "X", selected_muted ? "UNMUTE" : "MUTE", NULL}; + char* hints[] = {"Y", ach_filter_locked_only ? T("btn.show_all") : T("btn.show_locked"), "X", selected_muted ? T("btn.unmute") : T("btn.mute"), NULL}; GFX_blitButtonGroup(hints, 0, screen, 1); GFX_flip(screen); @@ -904,14 +911,14 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { static MenuList options_menu = { .type = MENU_LIST, .items = (MenuItem[]) { - {"Frontend", "NextUI (" BUILD_DATE " " BUILD_HASH ")",.on_confirm=OptionFrontend_openMenu}, - {"Emulator",.on_confirm=OptionEmulator_openMenu}, - {"Shaders",.on_confirm=OptionShaders_openMenu}, - {"Cheats",.on_confirm=OptionCheats_openMenu}, - {"Controls",.on_confirm=OptionControls_openMenu}, - {"Shortcuts",.on_confirm=OptionShortcuts_openMenu}, - {"Achievements",.on_confirm=OptionAchievements_openMenu}, - {"Save Changes",.on_confirm=OptionSaveChanges_openMenu}, + {NULL, "NextUI (" BUILD_DATE " " BUILD_HASH ")",.on_confirm=OptionFrontend_openMenu}, + {NULL,.on_confirm=OptionEmulator_openMenu}, + {NULL,.on_confirm=OptionShaders_openMenu}, + {NULL,.on_confirm=OptionCheats_openMenu}, + {NULL,.on_confirm=OptionControls_openMenu}, + {NULL,.on_confirm=OptionShortcuts_openMenu}, + {NULL,.on_confirm=OptionAchievements_openMenu}, + {NULL,.on_confirm=OptionSaveChanges_openMenu}, {NULL}, {NULL}, } @@ -922,16 +929,24 @@ static int save_changes_index = 7; // Update options menu visibility based on RA enable state static void Options_updateVisibility(void) { + // Always (re)apply translated labels for the fixed entries (0..5). + options_menu.items[0].name = T("menu.opt.frontend"); + options_menu.items[1].name = T("menu.opt.emulator"); + options_menu.items[2].name = T("menu.opt.shaders"); + options_menu.items[3].name = T("menu.opt.cheats"); + options_menu.items[4].name = T("menu.opt.controls"); + options_menu.items[5].name = T("menu.opt.shortcuts"); + if (CFG_getRAEnable()) { // RA enabled: show Achievements at index 6, Save Changes at index 7 - options_menu.items[6].name = "Achievements"; + options_menu.items[6].name = T("menu.opt.achievements"); options_menu.items[6].on_confirm = OptionAchievements_openMenu; - options_menu.items[7].name = "Save Changes"; + options_menu.items[7].name = T("menu.opt.save_changes"); options_menu.items[7].on_confirm = OptionSaveChanges_openMenu; save_changes_index = 7; } else { // RA disabled: hide Achievements, move Save Changes to index 6 - options_menu.items[6].name = "Save Changes"; + options_menu.items[6].name = T("menu.opt.save_changes"); options_menu.items[6].desc = NULL; options_menu.items[6].on_confirm = OptionSaveChanges_openMenu; options_menu.items[7].name = NULL; @@ -1212,7 +1227,7 @@ int Menu_options(MenuList* list) { if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); + text = TTF_RenderUTF8_Blended(font.small, T(item->name), text_color); SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ ox+SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+1) @@ -1259,7 +1274,7 @@ int Menu_options(MenuList* list) { while ( item->values && item->values[count]) count++; if (item->value >= 0 && item->value < count) { const char *str = item->values[item->value]; - text = TTF_RenderUTF8_Blended(font.tiny, str ? str : "none", str ? COLOR_WHITE : COLOR_GRAY); // always white + text = TTF_RenderUTF8_Blended(font.tiny, str ? str : T("val.none"), str ? COLOR_WHITE : COLOR_GRAY); // always white if (text) { SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ ox + mw - text->w - SCALE1(OPTION_PADDING), @@ -1287,7 +1302,7 @@ int Menu_options(MenuList* list) { if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); + text = TTF_RenderUTF8_Blended(font.small, T(item->name), text_color); SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ ox+SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+1) @@ -1311,7 +1326,7 @@ int Menu_options(MenuList* list) { if (!mrw || type!=MENU_INPUT) { if(item->values) { for (int j=0; item->values[j]; j++) { - TTF_SizeUTF8(font.tiny, item->values[j], &rw, NULL); + TTF_SizeUTF8(font.tiny, T(item->values[j]), &rw, NULL); if (lw+rw>w) w = lw+rw; if (rw>mrw) mrw = rw; } @@ -1358,7 +1373,7 @@ int Menu_options(MenuList* list) { if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); + text = TTF_RenderUTF8_Blended(font.small, T(item->name), text_color); SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ ox+SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+1) @@ -1372,7 +1387,7 @@ int Menu_options(MenuList* list) { int count = 0; while ( item->values && item->values[count]) count++; if (item->value >= 0 && item->value < count) { - text = TTF_RenderUTF8_Blended(font.tiny, item->values[item->value], COLOR_WHITE); // always white + text = TTF_RenderUTF8_Blended(font.tiny, T(item->values[item->value]), COLOR_WHITE); // always white SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ ox + mw - text->w - SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+3) @@ -1396,8 +1411,8 @@ int Menu_options(MenuList* list) { if (desc) { int w,h; - GFX_sizeText(font.tiny, desc, SCALE1(12), &w,&h); - GFX_blitText(font.tiny, desc, SCALE1(12), COLOR_WHITE, screen, &(SDL_Rect){ + GFX_sizeText(font.tiny, T(desc), SCALE1(12), &w,&h); + GFX_blitText(font.tiny, T(desc), SCALE1(12), COLOR_WHITE, screen, &(SDL_Rect){ (screen->w - w) / 2, screen->h - SCALE1(PADDING) - h, w,h @@ -1642,7 +1657,7 @@ void Menu_screenshot(void) { // Show notification if enabled if (CFG_getNotifyScreenshot()) { - Notification_push(NOTIFICATION_SETTING, "Screenshot saved", NULL); + Notification_push(NOTIFICATION_SETTING, T("menu.screenshot_saved"), NULL); } } void Menu_saveState(void) { @@ -1932,8 +1947,8 @@ void Menu_loop(void) { SDL_FreeSurface(text); if (show_setting && !GetHDMI()) GFX_blitHardwareHints(screen, show_setting); - else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?"POWER":"MENU","SLEEP", NULL }, 0, screen, 0); - GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OKAY", NULL }, 1, screen, 1); + else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?T("btn.power"):T("btn.menu"),T("btn.sleep"), NULL }, 0, screen, 0); + GFX_blitButtonGroup((char*[]){ "B",T("btn.back"), "A",T("btn.okay"), NULL }, 1, screen, 1); // list oy = (((DEVICE_HEIGHT / FIXED_SCALE) - PADDING * 2) - (MENU_ITEM_COUNT * PILL_SIZE)) / 2; @@ -2017,8 +2032,8 @@ void Menu_loop(void) { else { SDL_Rect preview_rect = {ox,oy,hw,hh}; SDL_FillRect(screen, &preview_rect, SDL_MapRGBA(screen->format,0,0,0,255)); - if (menu.save_exists) GFX_blitMessage(font.large, "No Preview", screen, &preview_rect); - else GFX_blitMessage(font.large, "Empty Slot", screen, &preview_rect); + if (menu.save_exists) GFX_blitMessage(font.large, T("menu.no_preview"), screen, &preview_rect); + else GFX_blitMessage(font.large, T("menu.empty_slot"), screen, &preview_rect); } // pagination diff --git a/workspace/all/minarch/ma_saves.c b/workspace/all/minarch/ma_saves.c index 8fe053e01..57156268f 100644 --- a/workspace/all/minarch/ma_saves.c +++ b/workspace/all/minarch/ma_saves.c @@ -7,6 +7,7 @@ #include "ma_internal.h" #include "ra_integration.h" #include "notification.h" +#include "i18n.h" #ifdef HAS_SRM #include "streams/rzip_stream.h" @@ -201,7 +202,7 @@ int State_read(void) { // from picoarch // Block load states in RetroAchievements hardcore mode if (RA_isHardcoreModeActive()) { LOG_info("State load blocked - hardcore mode active\n"); - Notification_push(NOTIFICATION_ACHIEVEMENT, "Load states disabled in Hardcore mode", NULL); + Notification_push(NOTIFICATION_ACHIEVEMENT, T("save.load_disabled_hc"), NULL); return 0; } @@ -305,7 +306,7 @@ int State_write(void) { // from picoarch // Block save states in RetroAchievements hardcore mode if (RA_isHardcoreModeActive()) { LOG_info("State save blocked - hardcore mode active\n"); - Notification_push(NOTIFICATION_ACHIEVEMENT, "Save states disabled in Hardcore mode", NULL); + Notification_push(NOTIFICATION_ACHIEVEMENT, T("save.save_disabled_hc"), NULL); return 0; } diff --git a/workspace/all/minarch/makefile b/workspace/all/minarch/makefile index ae10f2529..7bc654325 100644 --- a/workspace/all/minarch/makefile +++ b/workspace/all/minarch/makefile @@ -25,7 +25,7 @@ INCDIR = -I. -I./libretro-common/include/ -I../common/ -I../../$(PLATFORM)/platf SOURCE = $(TARGET).c ma_cheats.c ma_rewind.c ma_audio.c ma_input.c \ ma_options.c ma_frontend_opts.c ma_saves.c ma_video.c ma_core.c ma_game.c ma_environment.c ma_config.c ma_menu.c ma_runframe.c \ ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c \ - ../common/notification.c ../../$(PLATFORM)/platform/platform.c + ../common/notification.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c # RA support ifneq (,$(filter $(PLATFORM),tg5040 tg5050 my355 desktop)) diff --git a/workspace/all/minarch/ra_integration.c b/workspace/all/minarch/ra_integration.c index ac983ac3f..36f66bfab 100644 --- a/workspace/all/minarch/ra_integration.c +++ b/workspace/all/minarch/ra_integration.c @@ -12,6 +12,7 @@ #include "ra_sync.h" #include "defines.h" #include "api.h" +#include "i18n.h" #define RA_UTIL_NEED_SDL #include "ra_util.h" @@ -1371,7 +1372,7 @@ static void ra_event_handler(const rc_client_event_t* event, rc_client_t* client break; case RC_CLIENT_EVENT_GAME_COMPLETED: - Notification_push(NOTIFICATION_ACHIEVEMENT, "Game Mastered!", NULL); + Notification_push(NOTIFICATION_ACHIEVEMENT, T("ach.game_mastered"), NULL); RA_LOG_INFO("Game mastered!\n"); break; diff --git a/workspace/all/minput/makefile b/workspace/all/minput/makefile index cf532818a..a8855a441 100644 --- a/workspace/all/minput/makefile +++ b/workspace/all/minput/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = minput INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS += $(OPT) diff --git a/workspace/all/minput/minput.c b/workspace/all/minput/minput.c index 8c06fef5b..835f3b4fc 100644 --- a/workspace/all/minput/minput.c +++ b/workspace/all/minput/minput.c @@ -7,6 +7,7 @@ #include "defines.h" #include "api.h" #include "utils.h" +#include "i18n.h" static int getButtonWidth(char* label) { SDL_Surface* text; @@ -188,9 +189,9 @@ int main(int argc , char* argv[]) { GFX_blitPillColor(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,SCALE1(98)}, THEME_COLOR3, RGB_WHITE); x += SCALE1(BUTTON_MARGIN); y += SCALE1(BUTTON_MARGIN); - blitButton("VOL. -", screen, PAD_isPressed(BTN_MINUS), x, y, w); + blitButton(T("minput.vol_minus"), screen, PAD_isPressed(BTN_MINUS), x, y, w); x += w + SCALE1(BUTTON_MARGIN); - blitButton("VOL. +", screen, PAD_isPressed(BTN_PLUS), x, y, w); + blitButton(T("minput.vol_plus"), screen, PAD_isPressed(BTN_PLUS), x, y, w); x += w + SCALE1(BUTTON_MARGIN); } @@ -230,7 +231,7 @@ int main(int argc , char* argv[]) { blitButton("START", screen, PAD_isPressed(BTN_START), x, y, w); x += w + SCALE1(BUTTON_MARGIN); - SDL_Surface* text = TTF_RenderUTF8_Blended(font.tiny, "QUIT", COLOR_LIGHT_TEXT); + SDL_Surface* text = TTF_RenderUTF8_Blended(font.tiny, T("minput.quit"), COLOR_LIGHT_TEXT); SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){x,y+(SCALE1(BUTTON_SIZE)-text->h)/2}); SDL_FreeSurface(text); } diff --git a/workspace/all/nextui/makefile b/workspace/all/nextui/makefile index a1f2365ed..52c57edee 100644 --- a/workspace/all/nextui/makefile +++ b/workspace/all/nextui/makefile @@ -21,7 +21,7 @@ SDL?=SDL TARGET = nextui INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c ../../$(PLATFORM)/platform/platform.c +SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c CC = $(CROSS_COMPILE)gcc CFLAGS += $(OPT) diff --git a/workspace/all/nextui/nextui.c b/workspace/all/nextui/nextui.c index 4754bed78..e3e1663dc 100644 --- a/workspace/all/nextui/nextui.c +++ b/workspace/all/nextui/nextui.c @@ -11,6 +11,7 @@ #include "api.h" #include "utils.h" #include "config.h" +#include "i18n.h" #include #include #include @@ -141,7 +142,11 @@ static Entry* Entry_new(char* path, int type) { getDisplayName(path, display_name); Entry* self = malloc(sizeof(Entry)); self->path = strdup(path); - self->name = strdup(display_name); + // Pass the derived name through T() so well-known pak / folder names + // (Settings, Updater, Pak Store, ...) can be localised via the .lang + // files. T() returns the input verbatim when no entry matches, so + // ROM / arbitrary folder names are unaffected. + self->name = strdup(T(display_name)); self->unique = NULL; self->type = type; self->alpha = 0; @@ -809,19 +814,19 @@ static Array* getQuickEntries(void) { // We assume Menu_init was already called and populated this if (recents && recents->count) - Array_push(entries, Entry_newNamed(FAUX_RECENT_PATH, ENTRY_DIR, "Recents")); + Array_push(entries, Entry_newNamed(FAUX_RECENT_PATH, ENTRY_DIR, T("launcher.recents"))); if (hasCollections()) - Array_push(entries, Entry_new(COLLECTIONS_PATH, ENTRY_DIR)); + Array_push(entries, Entry_newNamed(COLLECTIONS_PATH, ENTRY_DIR, T("launcher.collections"))); // Not sure we need this, its just a button press away (B) - Array_push(entries, Entry_newNamed(ROMS_PATH, ENTRY_DIR, "Games")); + Array_push(entries, Entry_newNamed(ROMS_PATH, ENTRY_DIR, T("launcher.games"))); // Add tools if applicable if (hasTools() && !simple_mode) { char tools_path[256]; snprintf(tools_path, sizeof(tools_path), "%s/Tools/%s", SDCARD_PATH, PLATFORM); - Array_push(entries, Entry_new(tools_path, ENTRY_DIR)); + Array_push(entries, Entry_newNamed(tools_path, ENTRY_DIR, T("launcher.tools"))); } return entries; @@ -855,14 +860,14 @@ static Array* getRoot(void) { Array* root = Array_new(); if (hasRecents() && CFG_getShowRecents()) - Array_push(root, Entry_new(FAUX_RECENT_PATH, ENTRY_DIR)); + Array_push(root, Entry_newNamed(FAUX_RECENT_PATH, ENTRY_DIR, T("launcher.recently_played"))); Array *entries = getRoms(); // Handle collections if (hasCollections() && CFG_getShowCollections()) { if (entries->count) { - Array_push(root, Entry_new(COLLECTIONS_PATH, ENTRY_DIR)); + Array_push(root, Entry_newNamed(COLLECTIONS_PATH, ENTRY_DIR, T("launcher.collections"))); } else { // No visible systems, promote collections to root Array *collections = getCollections(); Array_yoink(entries, collections); @@ -876,7 +881,7 @@ static Array* getRoot(void) { if (hasTools() && CFG_getShowTools() && !simple_mode) { char tools_path[256]; snprintf(tools_path, sizeof(tools_path), "%s/Tools/%s", SDCARD_PATH, PLATFORM); - Array_push(root, Entry_new(tools_path, ENTRY_DIR)); + Array_push(root, Entry_newNamed(tools_path, ENTRY_DIR, T("launcher.tools"))); } return root; @@ -2642,9 +2647,9 @@ int main (int argc, char *argv[]) { // buttons (duped and trimmed from below) if (show_setting && !GetHDMI()) GFX_blitHardwareHints(screen, show_setting); - else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?"POWER":"MENU","SLEEP", NULL }, 0, screen, 0); + else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?T("btn.power"):T("btn.menu"),T("btn.sleep"), NULL }, 0, screen, 0); - GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OPEN", NULL }, 1, screen, 1); + GFX_blitButtonGroup((char*[]){ "B",T("btn.back"), "A",T("btn.open"), NULL }, 1, screen, 1); if(CFG_getShowQuickswitcherUI()) { #define MENU_ITEM_SIZE 72 // item size, top line @@ -2819,10 +2824,10 @@ int main (int argc, char *argv[]) { SDL_FreeSurface(text); } - if(can_resume) GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 0, screen, 0); - else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?"POWER":"MENU","SLEEP", NULL }, 0, screen, 0); + if(can_resume) GFX_blitButtonGroup((char*[]){ "B",T("btn.back"), NULL }, 0, screen, 0); + else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?T("btn.power"):T("btn.menu"),T("btn.sleep"), NULL }, 0, screen, 0); - GFX_blitButtonGroup((char*[]){ "Y", "REMOVE", "A","RESUME", NULL }, 1, screen, 1); + GFX_blitButtonGroup((char*[]){ "Y", T("btn.remove"), "A",T("btn.resume"), NULL }, 1, screen, 1); if(has_preview) { // lotta memory churn here @@ -2901,15 +2906,15 @@ int main (int argc, char *argv[]) { GFX_animateSurface(tmpsur,0-screen->w,0,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); } SDL_FreeSurface(tmpsur); - GFX_blitMessage(font.large, "No Preview", screen, &preview_rect); + GFX_blitMessage(font.large, T("launcher.no_preview"), screen, &preview_rect); } Entry_free(selectedEntry); } else { SDL_Rect preview_rect = {ox,oy,screen->w,screen->h}; SDL_FillRect(screen, &preview_rect, 0); - GFX_blitMessage(font.large, "No Recents", screen, &preview_rect); - GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 1, screen, 1); + GFX_blitMessage(font.large, T("launcher.no_recents"), screen, &preview_rect); + GFX_blitButtonGroup((char*[]){ "B",T("btn.back"), NULL }, 1, screen, 1); } GFX_flipHidden(); @@ -2999,23 +3004,23 @@ int main (int argc, char *argv[]) { // buttons if (show_setting && !GetHDMI()) GFX_blitHardwareHints(screen, show_setting); - else if (can_resume) GFX_blitButtonGroup((char*[]){ "X","RESUME", NULL }, 0, screen, 0); + else if (can_resume) GFX_blitButtonGroup((char*[]){ "X",T("btn.resume"), NULL }, 0, screen, 0); else GFX_blitButtonGroup((char*[]){ - BTN_SLEEP==BTN_POWER?"POWER":"MENU", - BTN_SLEEP==BTN_POWER||simple_mode?"SLEEP":"INFO", + BTN_SLEEP==BTN_POWER?T("btn.power"):T("btn.menu"), + BTN_SLEEP==BTN_POWER||simple_mode?T("btn.sleep"):T("btn.info"), NULL }, 0, screen, 0); if (total==0) { if (stack->count>1) { - GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 0, screen, 1); + GFX_blitButtonGroup((char*[]){ "B",T("btn.back"), NULL }, 0, screen, 1); } } else { if (stack->count>1) { - GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OPEN", NULL }, 1, screen, 1); + GFX_blitButtonGroup((char*[]){ "B",T("btn.back"), "A",T("btn.open"), NULL }, 1, screen, 1); } else { - GFX_blitButtonGroup((char*[]){ "A","OPEN", NULL }, 0, screen, 1); + GFX_blitButtonGroup((char*[]){ "A",T("btn.open"), NULL }, 0, screen, 1); } } @@ -3116,7 +3121,7 @@ int main (int argc, char *argv[]) { } else { // TODO: for some reason screen's dimensions end up being 0x0 in GFX_blitMessage... - GFX_blitMessage(font.large, "Empty folder", screen, &(SDL_Rect){0,0,screen->w,screen->h}); //, NULL); + GFX_blitMessage(font.large, T("launcher.empty_folder"), screen, &(SDL_Rect){0,0,screen->w,screen->h}); //, NULL); } lastScreen = SCREEN_GAMELIST; diff --git a/workspace/all/settings/btmenu.cpp b/workspace/all/settings/btmenu.cpp index 261971230..93a6e24e8 100644 --- a/workspace/all/settings/btmenu.cpp +++ b/workspace/all/settings/btmenu.cpp @@ -16,17 +16,17 @@ typedef std::shared_lock ReadLock; using namespace Bluetooth; using namespace std::placeholders; -Menu::Menu(const int &globalQuit, int &globalDirty) : MenuList(MenuItemType::Fixed, "Network", {}), globalQuit(globalQuit), globalDirty(globalDirty) +Menu::Menu(const int &globalQuit, int &globalDirty) : MenuList(MenuItemType::Fixed, "bt.network", {}), globalQuit(globalQuit), globalDirty(globalDirty) { - toggleItem = new MenuItem(ListItemType::Generic, "Bluetooth", "Enable/disable Bluetooth", {false, true}, {"Off", "On"}, + toggleItem = new MenuItem(ListItemType::Generic, "bt.toggle", "bt.toggle_desc", {false, true}, {"val.off", "val.on"}, std::bind(&Menu::getBtToggleState, this), std::bind(&Menu::setBtToggleState, this, std::placeholders::_1), std::bind(&Menu::resetBtToggleState, this)); - diagItem = new MenuItem(ListItemType::Generic, "Bluetooth diagnostics", "Enable/disable Bluetooth logging", {false, true}, {"Off", "On"}, + diagItem = new MenuItem(ListItemType::Generic, "bt.diagnostics", "bt.diagnostics_desc", {false, true}, {"val.off", "val.on"}, std::bind(&Menu::getBtDiagnosticsState, this), std::bind(&Menu::setBtDiagnosticsState, this, std::placeholders::_1), std::bind(&Menu::resetBtDiagnosticsState, this)); - rateItem = new MenuItem(ListItemType::Generic, "Maximum sampling rate", "44100 Hz: better compatibility\n48000 Hz: better quality", {44100, 48000}, {"44100 Hz", "48000 Hz"}, + rateItem = new MenuItem(ListItemType::Generic, "bt.samplerate_max", "bt.samplerate_max_desc", {44100, 48000}, {"44100 Hz", "48000 Hz"}, std::bind(&Menu::getSamplerateMaximum, this), std::bind(&Menu::setSamplerateMaximum, this, std::placeholders::_1), std::bind(&Menu::resetSamplerateMaximum, this)); @@ -80,7 +80,7 @@ std::any Menu::getBtToggleState() const void Menu::setBtToggleState(const std::any &on) { auto state = std::any_cast(on); - ScopedOverlay overlay(state ? "Enabling Bluetooth..." : "Disabling Bluetooth..."); + ScopedOverlay overlay(state ? "bt.enabling" : "bt.disabling"); BT_enable(state); } @@ -185,7 +185,7 @@ void Menu::updater() for (auto &[s, r] : scanMap) { MenuList *options; - options = new MenuList(MenuItemType::List, "Options", {new PairNewItem(r, selectionDirty)}); + options = new MenuList(MenuItemType::List, "bt.options", {new PairNewItem(r, selectionDirty)}); auto itm = new PairableItem{r, options}; items.push_back(itm); } @@ -195,14 +195,14 @@ void Menu::updater() MenuList *options; if (r.is_connected) { - options = new MenuList(MenuItemType::List, "Options", { + options = new MenuList(MenuItemType::List, "bt.options", { new DisconnectKnownItem(r, selectionDirty), new UnpairItem(r, selectionDirty), }); } else { - options = new MenuList(MenuItemType::List, "Options", { + options = new MenuList(MenuItemType::List, "bt.options", { new ConnectKnownItem(r, selectionDirty), new UnpairItem(r, selectionDirty), }); @@ -246,40 +246,40 @@ void Menu::updater() } PairNewItem::PairNewItem(BT_device d, bool& dirty) - : MenuItem(ListItemType::Button, "Pair", "Pair this device.", + : MenuItem(ListItemType::Button, "bt.pair", "bt.pair_desc", [&](AbstractMenuItem &item) -> InputReactionHint { - ScopedOverlay overlay("Pairing..."); - BT_pair(dev.addr); + ScopedOverlay overlay("bt.pairing_overlay"); + BT_pair(dev.addr); dirty = true; - return Exit; + return Exit; }), dev(d) {} UnpairItem::UnpairItem(BT_devicePaired d, bool& dirty) - : MenuItem(ListItemType::Button, "Forget", "Forget this device.", + : MenuItem(ListItemType::Button, "bt.forget", "bt.forget_desc", [&](AbstractMenuItem &item) -> InputReactionHint { - BT_unpair(dev.remote_addr); + BT_unpair(dev.remote_addr); dirty = true; - return Exit; + return Exit; }), dev(d) {} ConnectKnownItem::ConnectKnownItem(BT_devicePaired d, bool& dirty) - : MenuItem(ListItemType::Button, "Connect", "Connect this device.", + : MenuItem(ListItemType::Button, "bt.connect", "bt.connect_desc", [&](AbstractMenuItem &item) -> InputReactionHint { - ScopedOverlay overlay("Connecting..."); - BT_connect(dev.remote_addr); + ScopedOverlay overlay("bt.connecting_overlay"); + BT_connect(dev.remote_addr); dirty = true; - return Exit; + return Exit; }), dev(d) {} DisconnectKnownItem::DisconnectKnownItem(BT_devicePaired d, bool& dirty) - : MenuItem(ListItemType::Button, "Disconnect", "Disconnect this device.", + : MenuItem(ListItemType::Button, "bt.disconnect", "bt.disconnect_desc", [&](AbstractMenuItem &item) -> InputReactionHint { - BT_disconnect(dev.remote_addr); + BT_disconnect(dev.remote_addr); dirty = true; - return Exit; + return Exit; }), dev(d) {} diff --git a/workspace/all/settings/colorpickermenu.cpp b/workspace/all/settings/colorpickermenu.cpp index 8d5e7f1ec..ef484527b 100644 --- a/workspace/all/settings/colorpickermenu.cpp +++ b/workspace/all/settings/colorpickermenu.cpp @@ -339,17 +339,17 @@ void ColorPickerMenu::drawCustom(SDL_Surface *surface, const SDL_Rect &dst, cons // Button hints at the bottom of the screen { - char *primary_hints[] = {(char *)"B", (char *)"BACK", (char *)"A", (char *)"APPLY", nullptr}; + char *primary_hints[] = {(char *)"B", (char *)T("btn.back"), (char *)"A", (char *)T("btn.apply"), nullptr}; GFX_blitButtonGroup(primary_hints, 0, surface, 1); if (selected < NUM_SLIDERS) { - char *secondary_hints[] = {(char *)"L/R", (char *)"FINE", (char *)"L1/R1", (char *)"COARSE", nullptr}; + char *secondary_hints[] = {(char *)"L/R", (char *)T("btn.fine"), (char *)"L1/R1", (char *)T("btn.coarse"), nullptr}; GFX_blitButtonGroup(secondary_hints, 0, surface, 0); } else { - char *secondary_hints[] = {(char *)"X", (char *)"COPY", nullptr}; + char *secondary_hints[] = {(char *)"X", (char *)T("btn.copy"), nullptr}; GFX_blitButtonGroup(secondary_hints, 0, surface, 0); } } diff --git a/workspace/all/settings/keyboardprompt.cpp b/workspace/all/settings/keyboardprompt.cpp index 161af210e..a3e830adc 100644 --- a/workspace/all/settings/keyboardprompt.cpp +++ b/workspace/all/settings/keyboardprompt.cpp @@ -315,7 +315,7 @@ void KeyboardPrompt::drawKeyboard(SDL_Surface *screen, const AppState &state) const auto key = currentLayout->at(state.keyboard.row).at(state.keyboard.col); // draw the button group on the button-right - char *hints[] = {(char *)("Y"), (char *)("EXIT"), (char *)("X"), ((char *)"ENTER"), NULL}; + char *hints[] = {(char *)"Y", (char *)T("btn.exit"), (char *)"X", (char *)T("btn.enter"), NULL}; GFX_blitButtonGroup(hints, 1, screen, 1); // draw keyboard title diff --git a/workspace/all/settings/makefile b/workspace/all/settings/makefile index da1aae07b..3e2ce23b9 100644 --- a/workspace/all/settings/makefile +++ b/workspace/all/settings/makefile @@ -21,9 +21,9 @@ SDL?=SDL TARGET = settings INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = -c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/http.c ../common/ra_auth.c ../common/ra_offline.c ../common/ra_sync.c ../common/ra_event_queue.c ../../$(PLATFORM)/platform/platform.c +SOURCE = -c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../common/http.c ../common/ra_auth.c ../common/ra_offline.c ../common/ra_sync.c ../common/ra_event_queue.c ../common/i18n.c ../../$(PLATFORM)/platform/platform.c CXXSOURCE = $(TARGET).cpp menu.cpp colorpickermenu.cpp wifimenu.cpp btmenu.cpp keyboardprompt.cpp -CXXSOURCE += build/$(PLATFORM)/utils.o build/$(PLATFORM)/api.o build/$(PLATFORM)/config.o build/$(PLATFORM)/scaler.o build/$(PLATFORM)/http.o build/$(PLATFORM)/ra_auth.o build/$(PLATFORM)/ra_offline.o build/$(PLATFORM)/ra_sync.o build/$(PLATFORM)/ra_event_queue.o build/$(PLATFORM)/platform.o +CXXSOURCE += build/$(PLATFORM)/utils.o build/$(PLATFORM)/api.o build/$(PLATFORM)/config.o build/$(PLATFORM)/scaler.o build/$(PLATFORM)/http.o build/$(PLATFORM)/ra_auth.o build/$(PLATFORM)/ra_offline.o build/$(PLATFORM)/ra_sync.o build/$(PLATFORM)/ra_event_queue.o build/$(PLATFORM)/i18n.o build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc CXX = $(CROSS_COMPILE)g++ @@ -50,7 +50,7 @@ PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/msettings.h mkdir -p build/$(PLATFORM) $(CC) $(SOURCE) $(CFLAGS) $(LDFLAGS) - mv utils.o api.o config.o scaler.o http.o ra_auth.o ra_offline.o ra_sync.o ra_event_queue.o platform.o build/$(PLATFORM) + mv utils.o api.o config.o scaler.o http.o ra_auth.o ra_offline.o ra_sync.o ra_event_queue.o i18n.o platform.o build/$(PLATFORM) $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/settings/menu.cpp b/workspace/all/settings/menu.cpp index ab5915b03..6d25a8a1e 100644 --- a/workspace/all/settings/menu.cpp +++ b/workspace/all/settings/menu.cpp @@ -390,7 +390,8 @@ std::string MenuList::getSelectedItemName() const return ""; int selected_row = scope.selected - scope.start; - return items.at(selected_row)->getName(); + // Use raw name (i18n key) so selection survives a language change. + return items.at(selected_row)->getRawName(); } bool MenuList::selectByName(const std::string &name) @@ -400,7 +401,7 @@ bool MenuList::selectByName(const std::string &name) int toSelect = -1; for (int i = 0; i < items.size() && toSelect < 0; i++) - if(items.at(i)->getName() == name) + if(items.at(i)->getRawName() == name) toSelect = i; //LOG_info("Found element %s (%d) at pos %d\n", name.c_str(), scope.selected - scope.start, toSelect); if (toSelect >= 0) @@ -858,7 +859,7 @@ void MenuList::drawMain(SDL_Surface *surface, const SDL_Rect &dst, const SDL_Rec } else { - GFX_blitMessageCPP(font.large, "Empty folder", surface, dst); + GFX_blitMessageCPP(font.large, "generic.empty_folder", surface, dst); } } @@ -904,7 +905,10 @@ void MenuList::showOverlay(const std::string& message, OverlayDismissMode dismis { { WriteLock w(overlayLock); - overlayMessage = message; + // T() translates i18n keys to their current-language string; falls back + // to the input verbatim when no translation entry exists, so callers + // may freely pass either keys or already-translated literals. + overlayMessage = T(message.c_str()); overlayVisible = true; overlayDismissMode = dismissMode; } @@ -958,10 +962,10 @@ static void drawOverlayLocal(SDL_Surface* screen) { if (overlayDismissMode != OverlayDismissMode::None) { if (overlayDismissMode == OverlayDismissMode::DismissOnB) { - char *hints[] = {(char *)("B"), (char *)("BACK"), NULL}; + char *hints[] = {(char *)"B", (char *)T("btn.back"), NULL}; GFX_blitButtonGroup(hints, 1, screen, 1); } else if (overlayDismissMode == OverlayDismissMode::DismissOnA) { - char *hints[] = {(char *)("A"), (char *)("OK"), NULL}; + char *hints[] = {(char *)"A", (char *)T("btn.ok"), NULL}; GFX_blitButtonGroup(hints, 1, screen, 1); } } diff --git a/workspace/all/settings/menu.hpp b/workspace/all/settings/menu.hpp index 2c32d5e4a..7a5577dc9 100644 --- a/workspace/all/settings/menu.hpp +++ b/workspace/all/settings/menu.hpp @@ -5,6 +5,7 @@ extern "C" #include "sdl.h" #include "defines.h" #include "api.h" +#include "i18n.h" } #include @@ -178,10 +179,15 @@ class AbstractMenuItem virtual InputReactionHint handleInput(int &dirty) { return Unhandled; }; - const std::string &getName() const { return name; } - const std::string &getDesc() const { return desc; } + // getName/getDesc apply T() at each call so a language change is picked up + // live. Stored values are i18n keys (or literal English fallbacks); T() + // returns the key untouched when it has no matching translation entry. + std::string getName() const { return T(name.c_str()); } + std::string getDesc() const { return T(desc.c_str()); } void setDesc(const std::string &d) { desc = d; } const ListItemType getType() const { return type; } + // Raw internal name (i18n key) — for stable identity checks like selectByName. + const std::string &getRawName() const { return name; } virtual void drawCustomItem(SDL_Surface *surface, const SDL_Rect &dst, const AbstractMenuItem &item, bool selected) const {} @@ -250,16 +256,21 @@ class MenuItem : public AbstractMenuItem const std::any getValue() const override { - assert(valueIdx >= 0); + if (valueIdx < 0 || (size_t)valueIdx >= values.size()) return std::any{}; return values[valueIdx]; } const std::string getLabel() const override { - assert(valueIdx >= 0); - return labels[valueIdx]; + if (valueIdx < 0 || (size_t)valueIdx >= labels.size()) return ""; + return T(labels[valueIdx].c_str()); } const std::vector getValues() const override{ return values; } - const std::vector getLabels() const override { return labels; } + const std::vector getLabels() const override { + std::vector out; + out.reserve(labels.size()); + for (const auto &lbl : labels) out.push_back(T(lbl.c_str())); + return out; + } }; // A menu item for text input that shows the current value and opens a keyboard when pressed. @@ -352,8 +363,10 @@ class MenuList // Moved here to ensure MenuList is fully defined struct ScopedOverlay { - ScopedOverlay(const std::string& message, OverlayDismissMode dismissMode = OverlayDismissMode::None) { - MenuList::showOverlay(message, dismissMode); + // Accept either a translation key or a literal string. T() returns the + // input unchanged when the key has no entry, so this is safe both ways. + ScopedOverlay(const std::string& key_or_message, OverlayDismissMode dismissMode = OverlayDismissMode::None) { + MenuList::showOverlay(T(key_or_message.c_str()), dismissMode); } ~ScopedOverlay() { MenuList::hideOverlay(); diff --git a/workspace/all/settings/settings.cpp b/workspace/all/settings/settings.cpp index 18592caff..57ed8a746 100644 --- a/workspace/all/settings/settings.cpp +++ b/workspace/all/settings/settings.cpp @@ -7,9 +7,11 @@ extern "C" #include "utils.h" #include "ra_auth.h" #include "ra_sync.h" +#include "i18n.h" } #include +#include #include #include #include @@ -87,15 +89,72 @@ static const std::vector color_strings = { static const std::vector font_names = {"OG", "Next"}; +// Language support: discovered at startup by scanning .system/res/lang/*.lang. +// Always exposes at least "English" as a fallback. +static std::vector language_values; +static std::vector language_labels; +static std::vector language_codes; + +static std::string languageDisplayName(const std::string &code) +{ + if (code == "en") return "English"; + if (code == "fr") return "Français"; + if (code == "de") return "Deutsch"; + if (code == "es") return "Español"; + if (code == "it") return "Italiano"; + if (code == "pt") return "Português"; + if (code == "nl") return "Nederlands"; + if (code == "ja") return "日本語"; + if (code == "ko") return "한국어"; + if (code == "zh") return "中文"; + return code; +} + +static void discoverLanguages() +{ + language_codes.clear(); + language_codes.push_back("en"); + + DIR *d = opendir(SDCARD_PATH "/.system/res/lang"); + if (d) { + struct dirent *e; + while ((e = readdir(d)) != nullptr) { + std::string name = e->d_name; + if (name.size() < 6) continue; + if (name.substr(name.size() - 5) != ".lang") continue; + std::string code = name.substr(0, name.size() - 5); + if (code == "en") continue; + language_codes.push_back(code); + } + closedir(d); + } + + language_values.clear(); + language_labels.clear(); + for (size_t i = 0; i < language_codes.size(); ++i) { + language_values.push_back((int)i); + language_labels.push_back(languageDisplayName(language_codes[i])); + } +} + +static int currentLanguageIndex() +{ + const char *code = CFG_getLanguage(); + for (size_t i = 0; i < language_codes.size(); ++i) { + if (language_codes[i] == code) return (int)i; + } + return 0; +} + static const std::vector screen_timeout_secs = {0U, 5U, 10U, 15U, 30U, 45U, 60U, 90U, 120U, 240U, 360U, 600U}; -static const std::vector screen_timeout_labels = {"Never", "5s", "10s", "15s", "30s", "45s", "60s", "90s", "2m", "4m", "6m", "10m"}; +static std::vector screen_timeout_labels = {"Never", "5s", "10s", "15s", "30s", "45s", "60s", "90s", "2m", "4m", "6m", "10m"}; static const std::vector sleep_timeout_secs = {5U, 10U, 15U, 30U, 45U, 60U, 90U, 120U, 240U, 360U, 600U}; static const std::vector sleep_timeout_labels = {"5s", "10s", "15s", "30s", "45s", "60s", "90s", "2m", "4m", "6m", "10m"}; -static const std::vector on_off = {"Off", "On"}; +static std::vector on_off = {"Off", "On"}; -static const std::vector scaling_strings = {"Fullscreen", "Fit", "Fill"}; +static std::vector scaling_strings = {"Fullscreen", "Fit", "Fill"}; static const std::vector scaling = {(int)GFX_SCALE_FULLSCREEN, (int)GFX_SCALE_FIT, (int)GFX_SCALE_FILL}; // Notification duration options (in seconds) @@ -104,11 +163,11 @@ static const std::vector notify_duration_labels = {"1s", "2s", "3s" // Progress notification duration options (in seconds, 0 = disabled) static const std::vector progress_duration_values = {0, 1, 2, 3, 4, 5}; -static const std::vector progress_duration_labels = {"Off", "1s", "2s", "3s", "4s", "5s"}; +static std::vector progress_duration_labels = {"Off", "1s", "2s", "3s", "4s", "5s"}; // Menu transition mode options static const std::vector transition_mode_values = {(int)TRANSITION_OFF, (int)TRANSITION_SNAPPY, (int)TRANSITION_COMFY}; -static const std::vector transition_mode_labels = {"Off", "Snappy", "Comfy"}; +static std::vector transition_mode_labels = {"Off", "Snappy", "Comfy"}; // RetroAchievements sort order options static const std::vector ra_sort_values = { @@ -124,7 +183,7 @@ static const std::vector ra_sort_values = { (int)RA_SORT_TYPE_ASC, (int)RA_SORT_TYPE_DESC }; -static const std::vector ra_sort_labels = { +static std::vector ra_sort_labels = { "Unlocked First", "Display Order (First)", "Display Order (Last)", @@ -138,16 +197,40 @@ static const std::vector ra_sort_labels = { "Type (Desc)" }; +// Called once after I18N is loaded — refresh every globally-cached label vector. +// Numeric labels ("5s", "5%", etc.) and proper nouns (font names) are kept as-is. +static void initLocalizedLabels() +{ + on_off = { "val.off", "val.on" }; + screen_timeout_labels[0] = "val.never"; + scaling_strings = { "val.fullscreen", "val.fit", "val.fill" }; + progress_duration_labels[0] = "val.off"; + transition_mode_labels = { "val.off", "val.snappy", "val.comfy" }; + ra_sort_labels = { + "ra.sort.unlocked_first", + "ra.sort.display_first", + "ra.sort.display_last", + "ra.sort.won_most", + "ra.sort.won_least", + "ra.sort.points_most", + "ra.sort.points_least", + "ra.sort.title_az", + "ra.sort.title_za", + "ra.sort.type_asc", + "ra.sort.type_desc", + }; +} + namespace { - struct ColorDef { int id; const char *name; const char *desc; uint32_t defaultColor; }; + struct ColorDef { int id; const char *name_key; const char *desc_key; uint32_t defaultColor; }; static const ColorDef g_colorDefs[] = { - {1, "Main Color", "The color used to render main UI elements.", CFG_DEFAULT_COLOR1}, - {2, "Primary Accent Color", "The color used to highlight important things in the user interface.", CFG_DEFAULT_COLOR2}, - {3, "Secondary Accent Color", "A secondary highlight color.", CFG_DEFAULT_COLOR3}, - {6, "Hint info Color", "Color for button hints and info", CFG_DEFAULT_COLOR6}, - {4, "List Text", "List text color", CFG_DEFAULT_COLOR4}, - {5, "List Text Selected", "List selected text color", CFG_DEFAULT_COLOR5}, - {7, "Background Color", "Background color used when no background image is set.", CFG_DEFAULT_COLOR7}, + {1, "settings.color.main", "settings.color.main_desc", CFG_DEFAULT_COLOR1}, + {2, "settings.color.primary", "settings.color.primary_desc", CFG_DEFAULT_COLOR2}, + {3, "settings.color.secondary", "settings.color.secondary_desc", CFG_DEFAULT_COLOR3}, + {6, "settings.color.hint", "settings.color.hint_desc", CFG_DEFAULT_COLOR6}, + {4, "settings.color.list_text", "settings.color.list_text_desc", CFG_DEFAULT_COLOR4}, + {5, "settings.color.list_text_selected", "settings.color.list_text_selected_desc", CFG_DEFAULT_COLOR5}, + {7, "settings.color.background", "settings.color.background_desc", CFG_DEFAULT_COLOR7}, }; static std::vector buildColorPresets(int excludeId) @@ -156,7 +239,7 @@ namespace { for (const auto &def : g_colorDefs) { if (def.id != excludeId) - result.push_back({CFG_getColor(def.id), def.name}); + result.push_back({CFG_getColor(def.id), T(def.name_key)}); } return result; } @@ -291,6 +374,9 @@ int main(int argc, char *argv[]) ctx.dirty = 1; ctx.show_setting = 0; ctx.screen = GFX_init(MODE_MAIN); + // GFX_init has loaded the language; refresh cached label vectors before + // any MenuItem is constructed below. + initLocalizedLabels(); PAD_init(); PWR_init(); TIME_init(); @@ -336,7 +422,7 @@ int main(int argc, char *argv[]) pickers.reserve(std::size(g_colorDefs)); for (const auto &def : g_colorDefs) pickers.push_back(std::make_unique( - CFG_getColor(def.id), makeColorSetter(def.id), buildColorPresets(def.id), def.name)); + CFG_getColor(def.id), makeColorSetter(def.id), buildColorPresets(def.id), T(def.name_key))); // Build color MenuItems (loop order = g_colorDefs order = display order) std::vector colorMenuItems; @@ -346,62 +432,62 @@ int main(int argc, char *argv[]) const auto &def = g_colorDefs[i]; ColorPickerMenu *picker = pickers[i].get(); colorMenuItems.push_back(new MenuItem{ - ListItemType::Color, def.name, def.desc, colors, color_strings, + ListItemType::Color, T(def.name_key), T(def.desc_key), colors, color_strings, makeColorGetter(def.id), makeColorSetter(def.id), makeColorResetter(def.id, def.defaultColor), - makeColorOpener(picker, def.id, def.name), picker}); + makeColorOpener(picker, def.id, T(def.name_key)), picker}); } std::vector appearanceItems; - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Font", "The font to render all UI text.", {0, 1}, font_names, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.font", "settings.appearance.font_desc", {0, 1}, font_names, []() -> std::any{ return CFG_getFontId(); }, [](const std::any &value){ CFG_setFontId(std::any_cast(value)); }, []() { CFG_setFontId(CFG_DEFAULT_FONT_ID);}}); for (auto *item : colorMenuItems) appearanceItems.push_back(item); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Show battery percentage", "Show battery level as percent in the status pill", {false, true}, on_off, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.show_battery_pct", "settings.appearance.show_battery_pct_desc", {false, true}, on_off, []() -> std::any { return CFG_getShowBatteryPercent(); }, [](const std::any &value) { CFG_setShowBatteryPercent(std::any_cast(value)); }, []() { CFG_setShowBatteryPercent(CFG_DEFAULT_SHOWBATTERYPERCENT);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Show menu animations", "Enable or disable menu animations", {false, true}, on_off, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.show_anim", "settings.appearance.show_anim_desc", {false, true}, on_off, []() -> std::any{ return CFG_getMenuAnimations(); }, [](const std::any &value) { CFG_setMenuAnimations(std::any_cast(value)); }, []() { CFG_setMenuAnimations(CFG_DEFAULT_SHOWMENUANIMATIONS);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Menu transitions", "Style of animated transition when navigating menus", transition_mode_values, transition_mode_labels, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.transitions", "settings.appearance.transitions_desc", transition_mode_values, transition_mode_labels, []() -> std::any { return CFG_getMenuTransitions(); }, [](const std::any &value) { CFG_setMenuTransitions(std::any_cast(value)); }, []() { CFG_setMenuTransitions(CFG_DEFAULT_SHOWMENUTRANSITIONS); }}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Game art corner radius", "Set the radius for the rounded corners of game art", 0, 24, "px", + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.art_radius", "settings.appearance.art_radius_desc", 0, 24, "px", []() -> std::any{ return CFG_getThumbnailRadius(); }, [](const std::any &value) { CFG_setThumbnailRadius(std::any_cast(value)); }, []() { CFG_setThumbnailRadius(CFG_DEFAULT_THUMBRADIUS);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Game art width", "Set the percentage of screen width used for game art.\nUI elements might overrule this to avoid clipping.", + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.art_width", "settings.appearance.art_width_desc", 5, 100, "%", []() -> std::any{ return (int)(CFG_getGameArtWidth() * 100); }, [](const std::any &value) { CFG_setGameArtWidth((double)std::any_cast(value) / 100.0); }, []() { CFG_setGameArtWidth(CFG_DEFAULT_GAMEARTWIDTH);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Show folder names at root", "Show folder names at root directory", {false, true}, on_off, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.show_folder_names", "settings.appearance.show_folder_names_desc", {false, true}, on_off, []() -> std::any { return CFG_getShowFolderNamesAtRoot(); }, [](const std::any &value) { CFG_setShowFolderNamesAtRoot(std::any_cast(value)); }, []() { CFG_setShowFolderNamesAtRoot(CFG_DEFAULT_SHOWFOLDERNAMESATROOT);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Show Recents", "Show \"Recently Played\" menu entry in game list.", {false, true}, on_off, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.show_recents", "settings.appearance.show_recents_desc", {false, true}, on_off, []() -> std::any { return CFG_getShowRecents(); }, [](const std::any &value) { CFG_setShowRecents(std::any_cast(value)); }, []() { CFG_setShowRecents(CFG_DEFAULT_SHOWRECENTS);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Show Tools", "Show \"Tools\" menu entry in game list.", {false, true}, on_off, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.show_tools", "settings.appearance.show_tools_desc", {false, true}, on_off, []() -> std::any { return CFG_getShowTools(); }, [](const std::any &value) { CFG_setShowTools(std::any_cast(value)); }, []() { CFG_setShowTools(CFG_DEFAULT_SHOWTOOLS);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Show game art", "Show game artwork in the main menu", {false, true}, on_off, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.show_art", "settings.appearance.show_art_desc", {false, true}, on_off, []() -> std::any { return CFG_getShowGameArt(); }, [](const std::any &value) { CFG_setShowGameArt(std::any_cast(value)); }, []() { CFG_setShowGameArt(CFG_DEFAULT_SHOWGAMEART);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Use folder background for ROMs", "If enabled, used the emulator background image. Otherwise uses the default.", {false, true}, on_off, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.folder_bg", "settings.appearance.folder_bg_desc", {false, true}, on_off, []() -> std::any { return CFG_getRomsUseFolderBackground(); }, [](const std::any &value) { CFG_setRomsUseFolderBackground(std::any_cast(value)); }, []() { CFG_setRomsUseFolderBackground(CFG_DEFAULT_ROMSUSEFOLDERBACKGROUND);}}); - appearanceItems.push_back(new MenuItem{ListItemType::Generic, "Show Quickswitcher UI", "Show/hide Quickswitcher UI elements.\nWhen hidden, will only draw background images.", {false, true}, on_off, + appearanceItems.push_back(new MenuItem{ListItemType::Generic, "settings.appearance.qs_ui", "settings.appearance.qs_ui_desc", {false, true}, on_off, []() -> std::any{ return CFG_getShowQuickswitcherUI(); }, [](const std::any &value){ CFG_setShowQuickswitcherUI(std::any_cast(value)); }, []() { CFG_setShowQuickswitcherUI(CFG_DEFAULT_SHOWQUICKWITCHERUI);}}); @@ -411,11 +497,11 @@ int main(int argc, char *argv[]) // [](const std::any &value) // { CFG_setGameSwitcherScaling(std::any_cast(value)); }, // []() { CFG_setGameSwitcherScaling(CFG_DEFAULT_GAMESWITCHERSCALING);}}, - appearanceItems.push_back(new MenuItem{ListItemType::Button, "Reset to defaults", "Resets all options in this menu to their default values.", ResetCurrentMenu}); - auto *appearanceMenu = new MenuList(MenuItemType::Fixed, "Appearance", std::move(appearanceItems)); + appearanceItems.push_back(new MenuItem{ListItemType::Button, "settings.reset_defaults", "settings.reset_defaults_desc", ResetCurrentMenu}); + auto *appearanceMenu = new MenuList(MenuItemType::Fixed, "settings.section.appearance", std::move(appearanceItems)); std::vector displayItems = { - new MenuItem{ListItemType::Generic, "Brightness", "Display brightness (0 to 10)", 0, 10, "",[]() -> std::any + new MenuItem{ListItemType::Generic, "settings.display.brightness", "settings.display.brightness_desc", 0, 10, "",[]() -> std::any { return GetBrightness(); }, [](const std::any &value) { SetBrightness(std::any_cast(value)); }, []() { SetBrightness(SETTINGS_DEFAULT_BRIGHTNESS);}}, @@ -425,7 +511,7 @@ int main(int argc, char *argv[]) if(deviceInfo.hasColorTemperature()) { displayItems.push_back( - new MenuItem{ListItemType::Generic, "Color temperature", "Color temperature (0 to 40)", 0, 40, "",[]() -> std::any + new MenuItem{ListItemType::Generic, "settings.display.colortemp", "settings.display.colortemp_desc", 0, 40, "",[]() -> std::any { return GetColortemp(); }, [](const std::any &value) { SetColortemp(std::any_cast(value)); }, []() { SetColortemp(SETTINGS_DEFAULT_COLORTEMP);}}); @@ -434,12 +520,12 @@ int main(int argc, char *argv[]) if(deviceInfo.hasContrastSaturation()) { displayItems.push_back( - new MenuItem{ListItemType::Generic, "Contrast", "Contrast enhancement (-4 to 5)", -4, 5, "",[]() -> std::any + new MenuItem{ListItemType::Generic, "settings.display.contrast", "settings.display.contrast_desc", -4, 5, "",[]() -> std::any { return GetContrast(); }, [](const std::any &value) { SetContrast(std::any_cast(value)); }, []() { SetContrast(SETTINGS_DEFAULT_CONTRAST);}}); displayItems.push_back( - new MenuItem{ListItemType::Generic, "Saturation", "Saturation enhancement (-5 to 5)", -5, 5, "",[]() -> std::any + new MenuItem{ListItemType::Generic, "settings.display.saturation", "settings.display.saturation_desc", -5, 5, "",[]() -> std::any { return GetSaturation(); }, [](const std::any &value) { SetSaturation(std::any_cast(value)); }, []() { SetSaturation(SETTINGS_DEFAULT_SATURATION);}}); @@ -448,72 +534,72 @@ int main(int argc, char *argv[]) if(deviceInfo.hasExposure()) { displayItems.push_back( - new MenuItem{ListItemType::Generic, "Exposure", "Exposure enhancement (-4 to 5)", -4, 5, "",[]() -> std::any + new MenuItem{ListItemType::Generic, "settings.display.exposure", "settings.display.exposure_desc", -4, 5, "",[]() -> std::any { return GetExposure(); }, [](const std::any &value) { SetExposure(std::any_cast(value)); }, []() { SetExposure(SETTINGS_DEFAULT_EXPOSURE);}}); } displayItems.push_back( - new MenuItem{ListItemType::Button, "Reset to defaults", "Resets all options in this menu to their default values.", ResetCurrentMenu}); + new MenuItem{ListItemType::Button, "settings.reset_defaults", "settings.reset_defaults_desc", ResetCurrentMenu}); - auto displayMenu = new MenuList(MenuItemType::Fixed, "Display", displayItems); + auto displayMenu = new MenuList(MenuItemType::Fixed, "settings.section.display", displayItems); std::vector systemItems = { - new MenuItem{ListItemType::Generic, "Volume", "Speaker volume", + new MenuItem{ListItemType::Generic, "settings.audio.volume", "settings.audio.volume_desc", {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}, - {"Muted", "5%","10%","15%","20%","25%","30%","35%","40%","45%","50%","55%","60%","65%","70%","75%","80%","85%","90%","95%","100%"}, + {"val.muted", "5%","10%","15%","20%","25%","30%","35%","40%","45%","50%","55%","60%","65%","70%","75%","80%","85%","90%","95%","100%"}, []() -> std::any{ return GetVolume(); }, [](const std::any &value) { SetVolume(std::any_cast(value)); }, []() { SetVolume(SETTINGS_DEFAULT_VOLUME);}}, - new MenuItem{ListItemType::Generic, "Screen timeout", "Period of inactivity before screen turns off (0-600s)", screen_timeout_secs, screen_timeout_labels, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.system.screen_timeout", "settings.system.screen_timeout_desc", screen_timeout_secs, screen_timeout_labels, []() -> std::any { return CFG_getScreenTimeoutSecs(); }, [](const std::any &value) { CFG_setScreenTimeoutSecs(std::any_cast(value)); }, []() { CFG_setScreenTimeoutSecs(CFG_DEFAULT_SCREENTIMEOUTSECS);}}, - new MenuItem{ListItemType::Generic, "Suspend timeout", "Time before device goes to sleep after screen is off (5-600s)", sleep_timeout_secs, sleep_timeout_labels, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.system.suspend_timeout", "settings.system.suspend_timeout_desc", sleep_timeout_secs, sleep_timeout_labels, []() -> std::any { return CFG_getSuspendTimeoutSecs(); }, [](const std::any &value) { CFG_setSuspendTimeoutSecs(std::any_cast(value)); }, []() { CFG_setSuspendTimeoutSecs(CFG_DEFAULT_SUSPENDTIMEOUTSECS);}}, - new MenuItem{ListItemType::Generic, "Haptic feedback", "Enable or disable haptic feedback on certain actions in the OS", {false, true}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.system.haptic", "settings.system.haptic_desc", {false, true}, on_off, []() -> std::any { return CFG_getHaptics(); }, [](const std::any &value) { CFG_setHaptics(std::any_cast(value)); }, []() { CFG_setHaptics(CFG_DEFAULT_HAPTICS);}}, - new MenuItem{ListItemType::Generic, "Default view", "The initial view to show on boot", + new MenuItem{ListItemType::Generic, "settings.system.default_view", "settings.system.default_view_desc", {(int)SCREEN_GAMELIST, (int)SCREEN_GAMESWITCHER, (int)SCREEN_QUICKMENU}, - {"Content List","Game Switcher","Quick Menu"}, + {"settings.system.view.content_list","settings.system.view.game_switcher","settings.system.view.quick_menu"}, []() -> std::any { return CFG_getDefaultView(); }, [](const std::any &value){ CFG_setDefaultView(std::any_cast(value)); }, []() { CFG_setDefaultView(CFG_DEFAULT_VIEW);}}, - new MenuItem{ListItemType::Generic, "Show 24h time format", "Show clock in the 24hrs time format", {false, true}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.system.clock_24h", "settings.system.clock_24h_desc", {false, true}, on_off, []() -> std::any { return CFG_getClock24H(); }, [](const std::any &value) { CFG_setClock24H(std::any_cast(value)); }, []() { CFG_setClock24H(CFG_DEFAULT_CLOCK24H);}}, - new MenuItem{ListItemType::Generic, "Show clock", "Show clock in the status pill", {false, true}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.system.show_clock", "settings.system.show_clock_desc", {false, true}, on_off, []() -> std::any { return CFG_getShowClock(); }, [](const std::any &value) { CFG_setShowClock(std::any_cast(value)); }, []() { CFG_setShowClock(CFG_DEFAULT_SHOWCLOCK);}}, - new MenuItem{ListItemType::Generic, "Set time and date automatically", "Automatically adjust system time\nwith NTP (requires internet access)", {false, true}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.system.auto_time", "settings.system.auto_time_desc", {false, true}, on_off, []() -> std::any { return TIME_getNetworkTimeSync(); }, [](const std::any &value) { TIME_setNetworkTimeSync(std::any_cast(value)); }, []() { TIME_setNetworkTimeSync(false);}}, // default from stock - new MenuItem{ListItemType::Generic, "Time zone", "Your time zone", tz_values, tz_labels, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.system.timezone", "settings.system.timezone_desc", tz_values, tz_labels, []() -> std::any { return std::string(TIME_getCurrentTimezone()); }, [](const std::any &value) { TIME_setCurrentTimezone(std::any_cast(value).c_str()); }, []() { TIME_setCurrentTimezone("Asia/Shanghai");}}, // default from Stock - new MenuItem{ListItemType::Generic, "Save format", "The save format to use.\nMinUI: Game.gba.sav, Retroarch: Game.srm, Generic: Game.sav", + new MenuItem{ListItemType::Generic, "settings.system.save_format", "settings.system.save_format_desc", {(int)SAVE_FORMAT_SAV, (int)SAVE_FORMAT_SRM, (int)SAVE_FORMAT_SRM_UNCOMPRESSED, (int)SAVE_FORMAT_GEN}, - {"MinUI (default)", "Retroarch (compressed)", "Retroarch (uncompressed)", "Generic"}, []() -> std::any + {"val.fmt.minui", "val.fmt.retroarch_compressed", "val.fmt.retroarch_uncompressed", "val.fmt.generic"}, []() -> std::any { return CFG_getSaveFormat(); }, [](const std::any &value) { CFG_setSaveFormat(std::any_cast(value)); }, []() { CFG_setSaveFormat(CFG_DEFAULT_SAVEFORMAT);}}, - new MenuItem{ListItemType::Generic, "Save state format", "The save state format to use. MinUI: Game.st0, \nRetroarch-ish: Game.state.0, Retroarch: Game.state0", + new MenuItem{ListItemType::Generic, "settings.system.save_state_format", "settings.system.save_state_format_desc", {(int)STATE_FORMAT_SAV, (int)STATE_FORMAT_SRM_EXTRADOT, (int)STATE_FORMAT_SRM_UNCOMRESSED_EXTRADOT, (int)STATE_FORMAT_SRM, (int)STATE_FORMAT_SRM_UNCOMRESSED}, - {"MinUI (default)", "Retroarch-ish (compressed)", "Retroarch-ish (uncompressed)", "Retroarch (compressed)", "Retroarch (uncompressed)"}, []() -> std::any + {"val.fmt.minui", "val.fmt.retroarch_ish_compressed", "val.fmt.retroarch_ish_uncompressed", "val.fmt.retroarch_compressed", "val.fmt.retroarch_uncompressed"}, []() -> std::any { return CFG_getStateFormat(); }, [](const std::any &value) { CFG_setStateFormat(std::any_cast(value)); }, []() { CFG_setStateFormat(CFG_DEFAULT_STATEFORMAT);}}, - new MenuItem{ListItemType::Generic, "Use extracted file name", "Use the extracted file name instead of the archive name.\nOnly applies to cores that do not handle archives natively", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.system.extracted_name", "settings.system.extracted_name_desc", {false, true}, on_off, []() -> std::any{ return CFG_getUseExtractedFileName(); }, [](const std::any &value){ CFG_setUseExtractedFileName(std::any_cast(value)); }, []() { CFG_setUseExtractedFileName(CFG_DEFAULT_EXTRACTEDFILENAME);}} @@ -522,7 +608,7 @@ int main(int argc, char *argv[]) if(deviceInfo.getPlatform() == DeviceInfo::tg5040) { systemItems.push_back( - new MenuItem{ListItemType::Generic, "Safe poweroff", "Bypasses the stock shutdown procedure to avoid the \"limbo bug\".\nInstructs the PMIC directly to soft disconnect the battery.", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.system.safe_poweroff", "settings.system.safe_poweroff_desc", {false, true}, on_off, []() -> std::any { return CFG_getPowerOffProtection(); }, [](const std::any &value) { CFG_setPowerOffProtection(std::any_cast(value)); }, []() { CFG_setPowerOffProtection(CFG_DEFAULT_POWEROFFPROTECTION); }} @@ -532,34 +618,53 @@ int main(int argc, char *argv[]) if(deviceInfo.hasActiveCooling()) { systemItems.push_back( - new MenuItem{ListItemType::Generic, "Fan Speed", "Select the fan speed percentage (Quiet/Normal/Performance or 0-100%)", - {-3,-2,-1,0,10,20,30,40,50,60,70,80,90,100}, {"Performance","Normal","Quiet","0%","10%","20%","30%","40%","50%","60%","70%","80%","90%","100%"}, + new MenuItem{ListItemType::Generic, "settings.system.fan_speed", "settings.system.fan_speed_desc", + {-3,-2,-1,0,10,20,30,40,50,60,70,80,90,100}, {"settings.system.fan.performance","settings.system.fan.normal","settings.system.fan.quiet","0%","10%","20%","30%","40%","50%","60%","70%","80%","90%","100%"}, []() -> std::any { return GetFanSpeed(); }, [](const std::any &value){ SetFanSpeed(std::any_cast(value)); }, []() { SetFanSpeed(SETTINGS_DEFAULT_FAN_SPEED); }} ); } + discoverLanguages(); + systemItems.push_back( + new MenuItem{ListItemType::Generic, + (char *)T("settings.language.title"), + (char *)T("settings.language.desc"), + language_values, language_labels, + []() -> std::any { return currentLanguageIndex(); }, + [](const std::any &value) { + int idx = std::any_cast(value); + if (idx < 0 || (size_t)idx >= language_codes.size()) return; + const std::string &code = language_codes[idx]; + CFG_setLanguage(code.c_str()); + I18N_reload(code.c_str()); + }, + []() { + CFG_setLanguage("en"); + I18N_reload("en"); + }}); + systemItems.push_back( - new MenuItem{ListItemType::Button, "Reset to defaults", "Resets all options in this menu to their default values.", ResetCurrentMenu}); + new MenuItem{ListItemType::Button, "settings.reset_defaults", "settings.reset_defaults_desc", ResetCurrentMenu}); - auto systemMenu = new MenuList(MenuItemType::Fixed, "System", systemItems); + auto systemMenu = new MenuList(MenuItemType::Fixed, "settings.section.system", systemItems); std::vector muteItems = { - new MenuItem{ListItemType::Generic, "Volume when toggled", "Speaker volume (0-20)", + new MenuItem{ListItemType::Generic, "settings.mute.volume_toggled", "settings.mute.volume_toggled_desc", {(int)SETTINGS_DEFAULT_MUTE_NO_CHANGE, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}, - {"Unchanged", "Muted", "5%","10%","15%","20%","25%","30%","35%","40%","45%","50%","55%","60%","65%","70%","75%","80%","85%","90%","95%","100%"}, + {"val.unchanged", "val.muted", "5%","10%","15%","20%","25%","30%","35%","40%","45%","50%","55%","60%","65%","70%","75%","80%","85%","90%","95%","100%"}, []() -> std::any { return GetMutedVolume(); }, [](const std::any &value) { SetMutedVolume(std::any_cast(value)); }, []() { SetMutedVolume(0); }}, - new MenuItem{ListItemType::Generic, "FN switch disables LED", "Switch will also disable LEDs", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.mute.fn_disables_led", "settings.mute.fn_disables_led_desc", {false, true}, on_off, []() -> std::any { return CFG_getMuteLEDs(); }, [](const std::any &value) { CFG_setMuteLEDs(std::any_cast(value)); }, []() { CFG_setMuteLEDs(CFG_DEFAULT_MUTELEDS); }}, - new MenuItem{ListItemType::Generic, "Brightness when toggled", "Display brightness (0 to 10)", + new MenuItem{ListItemType::Generic, "settings.mute.brightness_toggled", "settings.mute.brightness_toggled_desc", {(int)SETTINGS_DEFAULT_MUTE_NO_CHANGE, 0,1,2,3,4,5,6,7,8,9,10}, - {"Unchanged","0","1","2","3","4","5","6","7","8","9","10"}, + {"val.unchanged","0","1","2","3","4","5","6","7","8","9","10"}, []() -> std::any { return GetMutedBrightness(); }, [](const std::any &value) { SetMutedBrightness(std::any_cast(value)); }, []() { SetMutedBrightness(SETTINGS_DEFAULT_MUTE_NO_CHANGE);}}, @@ -569,9 +674,9 @@ int main(int argc, char *argv[]) { if(deviceInfo.hasColorTemperature()) { muteItems.push_back( - new MenuItem{ListItemType::Generic, "Color temperature when toggled", "Color temperature (0 to 40)", + new MenuItem{ListItemType::Generic, "settings.mute.colortemp_toggled", "settings.mute.colortemp_toggled_desc", {(int)SETTINGS_DEFAULT_MUTE_NO_CHANGE, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40}, - {"Unchanged","0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40"}, + {"val.unchanged","0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40"}, []() -> std::any{ return GetMutedColortemp(); }, [](const std::any &value) { SetMutedColortemp(std::any_cast(value)); }, []() { SetMutedColortemp(SETTINGS_DEFAULT_MUTE_NO_CHANGE);}} @@ -579,15 +684,15 @@ int main(int argc, char *argv[]) } if(deviceInfo.hasContrastSaturation()) { muteItems.insert(muteItems.end(), { - new MenuItem{ListItemType::Generic, "Contrast when toggled", "Contrast enhancement (-4 to 5)", + new MenuItem{ListItemType::Generic, "settings.mute.contrast_toggled", "settings.mute.contrast_toggled_desc", {(int)SETTINGS_DEFAULT_MUTE_NO_CHANGE, -4,-3,-2,-1,0,1,2,3,4,5}, - {"Unchanged","-4","-3","-2","-1","0","1","2","3","4","5"}, + {"val.unchanged","-4","-3","-2","-1","0","1","2","3","4","5"}, []() -> std::any { return GetMutedContrast(); }, [](const std::any &value) { SetMutedContrast(std::any_cast(value)); }, []() { SetMutedContrast(SETTINGS_DEFAULT_MUTE_NO_CHANGE);}}, - new MenuItem{ListItemType::Generic, "Saturation when toggled", "Saturation enhancement (-5 to 5)", + new MenuItem{ListItemType::Generic, "settings.mute.saturation_toggled", "settings.mute.saturation_toggled_desc", {(int)SETTINGS_DEFAULT_MUTE_NO_CHANGE, -5,-4,-3,-2,-1,0,1,2,3,4,5}, - {"Unchanged","-5","-4","-3","-2","-1","0","1","2","3","4","5"}, + {"val.unchanged","-5","-4","-3","-2","-1","0","1","2","3","4","5"}, []() -> std::any{ return GetMutedSaturation(); }, [](const std::any &value) { SetMutedSaturation(std::any_cast(value)); }, []() { SetMutedSaturation(SETTINGS_DEFAULT_MUTE_NO_CHANGE);}}} @@ -595,9 +700,9 @@ int main(int argc, char *argv[]) } if(deviceInfo.hasExposure()) { muteItems.push_back( - new MenuItem{ListItemType::Generic, "Exposure when toggled", "Exposure enhancement (-4 to 5)", + new MenuItem{ListItemType::Generic, "settings.mute.exposure_toggled", "settings.mute.exposure_toggled_desc", {(int)SETTINGS_DEFAULT_MUTE_NO_CHANGE, -4,-3,-2,-1,0,1,2,3,4,5}, - {"Unchanged","-4","-3","-2","-1","0","1","2","3","4","5"}, + {"val.unchanged","-4","-3","-2","-1","0","1","2","3","4","5"}, []() -> std::any { return GetMutedExposure(); }, [](const std::any &value) { SetMutedExposure(std::any_cast(value)); }, []() { SetMutedExposure(SETTINGS_DEFAULT_MUTE_NO_CHANGE);}} @@ -605,35 +710,35 @@ int main(int argc, char *argv[]) } muteItems.insert(muteItems.end(), { - new MenuItem{ListItemType::Generic, "Turbo fire A", "Enable turbo fire A", {0, 1}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.turbo.a", "settings.mute.turbo.a_desc", {0, 1}, on_off, []() -> std::any { return GetMuteTurboA(); }, [](const std::any &value) { SetMuteTurboA(std::any_cast(value));}, []() { SetMuteTurboA(0);}}, - new MenuItem{ListItemType::Generic, "Turbo fire B", "Enable turbo fire B", {0, 1}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.turbo.b", "settings.mute.turbo.b_desc", {0, 1}, on_off, []() -> std::any { return GetMuteTurboB(); }, [](const std::any &value) { SetMuteTurboB(std::any_cast(value));}, []() { SetMuteTurboB(0);}}, - new MenuItem{ListItemType::Generic, "Turbo fire X", "Enable turbo fire X", {0, 1}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.turbo.x", "settings.mute.turbo.x_desc", {0, 1}, on_off, []() -> std::any { return GetMuteTurboX(); }, [](const std::any &value) { SetMuteTurboX(std::any_cast(value));}, []() { SetMuteTurboX(0);}}, - new MenuItem{ListItemType::Generic, "Turbo fire Y", "Enable turbo fire Y", {0, 1}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.turbo.y", "settings.mute.turbo.y_desc", {0, 1}, on_off, []() -> std::any { return GetMuteTurboY(); }, [](const std::any &value) { SetMuteTurboY(std::any_cast(value));}, []() { SetMuteTurboY(0);}}, - new MenuItem{ListItemType::Generic, "Turbo fire L1", "Enable turbo fire L1", {0, 1}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.turbo.l1", "settings.mute.turbo.l1_desc", {0, 1}, on_off, []() -> std::any { return GetMuteTurboL1(); }, [](const std::any &value) { SetMuteTurboL1(std::any_cast(value));}, []() { SetMuteTurboL1(0);}}, - new MenuItem{ListItemType::Generic, "Turbo fire L2", "Enable turbo fire L2", {0, 1}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.turbo.l2", "settings.mute.turbo.l2_desc", {0, 1}, on_off, []() -> std::any { return GetMuteTurboL2(); }, [](const std::any &value) { SetMuteTurboL2(std::any_cast(value));}, []() { SetMuteTurboL2(0);}}, - new MenuItem{ListItemType::Generic, "Turbo fire R1", "Enable turbo fire R1", {0, 1}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.turbo.r1", "settings.mute.turbo.r1_desc", {0, 1}, on_off, []() -> std::any { return GetMuteTurboR1(); }, [](const std::any &value) { SetMuteTurboR1(std::any_cast(value));}, []() { SetMuteTurboR1(0);}}, - new MenuItem{ListItemType::Generic, "Turbo fire R2", "Enable turbo fire R2", {0, 1}, on_off, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.turbo.r2", "settings.mute.turbo.r2_desc", {0, 1}, on_off, []() -> std::any { return GetMuteTurboR2(); }, [](const std::any &value) { SetMuteTurboR2(std::any_cast(value));}, []() { SetMuteTurboR2(0);}} @@ -642,7 +747,7 @@ int main(int argc, char *argv[]) if(deviceInfo.hasMuteToggle() && !deviceInfo.hasAnalogSticks()){ muteItems.push_back( - new MenuItem{ListItemType::Generic, "Dpad mode when toggled", "Dpad: default. Joystick: Dpad exclusively acts as analog stick.\nBoth: Dpad and Joystick inputs at the same time.", {0, 1, 2}, {"Dpad", "Joystick", "Both"}, []() -> std::any + new MenuItem{ListItemType::Generic, "settings.mute.dpad_mode", "settings.mute.dpad_mode_desc", {0, 1, 2}, {"val.dpad", "val.joystick", "val.both"}, []() -> std::any { if(!GetMuteDisablesDpad() && !GetMuteEmulatesJoystick()) return 0; if(GetMuteDisablesDpad() && GetMuteEmulatesJoystick()) return 1; @@ -660,81 +765,81 @@ int main(int argc, char *argv[]) SetMuteEmulatesJoystick(0); }}); } - muteItems.push_back(new MenuItem{ListItemType::Button, "Reset to defaults", "Resets all options in this menu to their default values.", ResetCurrentMenu}); + muteItems.push_back(new MenuItem{ListItemType::Button, "settings.reset_defaults", "settings.reset_defaults_desc", ResetCurrentMenu}); - auto notificationsMenu = new MenuList(MenuItemType::Fixed, "Notifications", + auto notificationsMenu = new MenuList(MenuItemType::Fixed, "settings.notif.title", { - new MenuItem{ListItemType::Generic, "Save states", "Show notification when saving game state", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.notif.save_states", "settings.notif.save_states_desc", {false, true}, on_off, []() -> std::any { return CFG_getNotifyManualSave(); }, [](const std::any &value) { CFG_setNotifyManualSave(std::any_cast(value)); }, []() { CFG_setNotifyManualSave(CFG_DEFAULT_NOTIFY_MANUAL_SAVE);}}, - new MenuItem{ListItemType::Generic, "Load states", "Show notification when loading game state", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.notif.load_states", "settings.notif.load_states_desc", {false, true}, on_off, []() -> std::any { return CFG_getNotifyLoad(); }, [](const std::any &value) { CFG_setNotifyLoad(std::any_cast(value)); }, []() { CFG_setNotifyLoad(CFG_DEFAULT_NOTIFY_LOAD);}}, - new MenuItem{ListItemType::Generic, "Screenshots", "Show notification when taking a screenshot", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.notif.screenshots", "settings.notif.screenshots_desc", {false, true}, on_off, []() -> std::any { return CFG_getNotifyScreenshot(); }, [](const std::any &value) { CFG_setNotifyScreenshot(std::any_cast(value)); }, []() { CFG_setNotifyScreenshot(CFG_DEFAULT_NOTIFY_SCREENSHOT);}}, - new MenuItem{ListItemType::Generic, "Vol / Display Adjustments", "Show overlay for volume, brightness,\nand color temp adjustments", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.notif.vol_display", "settings.notif.vol_display_desc", {false, true}, on_off, []() -> std::any { return CFG_getNotifyAdjustments(); }, [](const std::any &value) { CFG_setNotifyAdjustments(std::any_cast(value)); }, []() { CFG_setNotifyAdjustments(CFG_DEFAULT_NOTIFY_ADJUSTMENTS);}}, - new MenuItem{ListItemType::Generic, "Duration", "How long notifications stay on screen", notify_duration_values, notify_duration_labels, + new MenuItem{ListItemType::Generic, "settings.notif.duration", "settings.notif.duration_desc", notify_duration_values, notify_duration_labels, []() -> std::any { return CFG_getNotifyDuration(); }, [](const std::any &value) { CFG_setNotifyDuration(std::any_cast(value)); }, []() { CFG_setNotifyDuration(CFG_DEFAULT_NOTIFY_DURATION);}}, - new MenuItem{ListItemType::Button, "Reset to defaults", "Resets all options in this menu to their default values.", ResetCurrentMenu}, + new MenuItem{ListItemType::Button, "settings.reset_defaults", "settings.reset_defaults_desc", ResetCurrentMenu}, }); // RetroAchievements keyboard prompts - auto raUsernamePrompt = new KeyboardPrompt("Enter Username", [](AbstractMenuItem &item) -> InputReactionHint { + auto raUsernamePrompt = new KeyboardPrompt("settings.ra.username", [](AbstractMenuItem &item) -> InputReactionHint { CFG_setRAUsername(item.getName().c_str()); return Exit; }); - auto raPasswordPrompt = new KeyboardPrompt("Enter Password", [](AbstractMenuItem &item) -> InputReactionHint { + auto raPasswordPrompt = new KeyboardPrompt("settings.ra.password", [](AbstractMenuItem &item) -> InputReactionHint { CFG_setRAPassword(item.getName().c_str()); return Exit; }); - auto retroAchievementsMenu = new MenuList(MenuItemType::Fixed, "RetroAchievements", + auto retroAchievementsMenu = new MenuList(MenuItemType::Fixed, "settings.ra.title", { - new MenuItem{ListItemType::Generic, "Enable Achievements", "Enable RetroAchievements integration", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.ra.enable", "settings.ra.enable_desc", {false, true}, on_off, []() -> std::any { return CFG_getRAEnable(); }, [](const std::any &value) { CFG_setRAEnable(std::any_cast(value)); }, []() { CFG_setRAEnable(CFG_DEFAULT_RA_ENABLE);}}, - new TextInputMenuItem{"Username", "RetroAchievements username", + new TextInputMenuItem{"settings.ra.username", "settings.ra.username_desc", []() -> std::any { std::string username = CFG_getRAUsername(); - return username.empty() ? std::string("(not set)") : username; + return username.empty() ? std::string("settings.ra.not_set") : username; }, [raUsernamePrompt](AbstractMenuItem &item) -> InputReactionHint { raUsernamePrompt->setInitialText(CFG_getRAUsername()); item.defer(true); return NoOp; }, raUsernamePrompt}, - new TextInputMenuItem{"Password", "RetroAchievements password", + new TextInputMenuItem{"settings.ra.password", "settings.ra.password_desc", []() -> std::any { std::string password = CFG_getRAPassword(); - return password.empty() ? std::string("(not set)") : std::string("********"); + return password.empty() ? std::string("settings.ra.not_set") : std::string("********"); }, [raPasswordPrompt](AbstractMenuItem &item) -> InputReactionHint { raPasswordPrompt->setInitialText(CFG_getRAPassword()); item.defer(true); return NoOp; }, raPasswordPrompt}, - new MenuItem{ListItemType::Button, "Authenticate", "Test credentials and retrieve API token", + new MenuItem{ListItemType::Button, "settings.ra.authenticate", "settings.ra.authenticate_desc", [](AbstractMenuItem &item) -> InputReactionHint { const char* username = CFG_getRAUsername(); const char* password = CFG_getRAPassword(); if (!username || strlen(username) == 0 || !password || strlen(password) == 0) { - item.setDesc("Error: Username and password required"); + item.setDesc("settings.ra.err_credentials"); return NoOp; } - item.setDesc("Authenticating..."); + item.setDesc("settings.ra.authenticating"); RA_AuthResponse response; RA_AuthResult result = RA_authenticateSync(username, password, &response); @@ -742,72 +847,74 @@ int main(int argc, char *argv[]) if (result == RA_AUTH_SUCCESS) { CFG_setRAToken(response.token); CFG_setRAAuthenticated(true); - std::string desc = "Authenticated as " + std::string(response.display_name); - item.setDesc(desc); + char buf[256]; + snprintf(buf, sizeof(buf), "settings.ra.authenticated_as", response.display_name); + item.setDesc(buf); } else { CFG_setRAToken(""); CFG_setRAAuthenticated(false); - std::string desc = "Error: " + std::string(response.error_message); - item.setDesc(desc); + char buf[256]; + snprintf(buf, sizeof(buf), "settings.ra.err_prefix", response.error_message); + item.setDesc(buf); } return NoOp; }}, - new StaticMenuItem{ListItemType::Generic, "Status", "Authentication status", + new StaticMenuItem{ListItemType::Generic, "settings.ra.status", "settings.ra.status_desc", []() -> std::any { if (CFG_getRAAuthenticated() && strlen(CFG_getRAToken()) > 0) { - return std::string("Authenticated"); + return std::string("settings.ra.status_yes"); } - return std::string("Not authenticated"); + return std::string("settings.ra.status_no"); }}, // TODO: Hardcore mode hidden until feature is fully implemented and ready for the emulator approval process done by the RetroAchievements team // new MenuItem{ListItemType::Generic, "Hardcore Mode", "Disable save states and cheats for achievements", {false, true}, on_off, // []() -> std::any { return CFG_getRAHardcoreMode(); }, // [](const std::any &value) { CFG_setRAHardcoreMode(std::any_cast(value)); }, // []() { CFG_setRAHardcoreMode(CFG_DEFAULT_RA_HARDCOREMODE);}}, - new MenuItem{ListItemType::Generic, "Show Notifications", "Show achievement unlock notifications", {false, true}, on_off, + new MenuItem{ListItemType::Generic, "settings.ra.show_notif", "settings.ra.show_notif_desc", {false, true}, on_off, []() -> std::any { return CFG_getRAShowNotifications(); }, [](const std::any &value) { CFG_setRAShowNotifications(std::any_cast(value)); }, []() { CFG_setRAShowNotifications(CFG_DEFAULT_RA_SHOW_NOTIFICATIONS);}}, - new MenuItem{ListItemType::Generic, "Notification Duration", "How long achievement notifications stay on screen", notify_duration_values, notify_duration_labels, + new MenuItem{ListItemType::Generic, "settings.ra.notif_duration", "settings.ra.notif_duration_desc", notify_duration_values, notify_duration_labels, []() -> std::any { return CFG_getRANotificationDuration(); }, [](const std::any &value) { CFG_setRANotificationDuration(std::any_cast(value)); }, []() { CFG_setRANotificationDuration(CFG_DEFAULT_RA_NOTIFICATION_DURATION);}}, - new MenuItem{ListItemType::Generic, "Progress Duration", "Duration for progress updates (top-left). Off to disable.", progress_duration_values, progress_duration_labels, + new MenuItem{ListItemType::Generic, "settings.ra.progress_duration", "settings.ra.progress_duration_desc", progress_duration_values, progress_duration_labels, []() -> std::any { return CFG_getRAProgressNotificationDuration(); }, [](const std::any &value) { CFG_setRAProgressNotificationDuration(std::any_cast(value)); }, []() { CFG_setRAProgressNotificationDuration(CFG_DEFAULT_RA_PROGRESS_NOTIFICATION_DURATION);}}, - new MenuItem{ListItemType::Generic, "Achievement Sort Order", "How achievements are sorted in the in-game menu", ra_sort_values, ra_sort_labels, + new MenuItem{ListItemType::Generic, "settings.ra.sort", "settings.ra.sort_desc", ra_sort_values, ra_sort_labels, []() -> std::any { return CFG_getRAAchievementSortOrder(); }, [](const std::any &value) { CFG_setRAAchievementSortOrder(std::any_cast(value)); }, []() { CFG_setRAAchievementSortOrder(CFG_DEFAULT_RA_ACHIEVEMENT_SORT_ORDER);}}, - new MenuItem{ListItemType::Button, "Sync Offline Unlocks", + new MenuItem{ListItemType::Button, "settings.ra.sync", []() -> std::string { uint32_t count = 0; RA_Sync_hasPendingUnlocks(&count); if (count > 0) { - char buf[64]; - snprintf(buf, sizeof(buf), "%u pending \u2014 send to RA server", count); + char buf[128]; + snprintf(buf, sizeof(buf), "settings.ra.sync_pending_fmt", count); return std::string(buf); } - return std::string("No pending unlocks"); + return std::string("settings.ra.sync_no_pending"); }(), [](AbstractMenuItem &item) -> InputReactionHint { // Check authentication if (!CFG_getRAAuthenticated() || strlen(CFG_getRAToken()) == 0) { - item.setDesc("Not authenticated"); + item.setDesc("settings.ra.sync_not_auth"); return NoOp; } // Check for pending unlocks uint32_t pending = 0; if (!RA_Sync_hasPendingUnlocks(&pending) || pending == 0) { - item.setDesc("No pending unlocks"); + item.setDesc("settings.ra.sync_no_pending"); return NoOp; } // Show initial overlay with cancel hint - char msg[128]; - snprintf(msg, sizeof(msg), "Syncing %u achievement%s...\n\n(0/%u)", + char msg[256]; + snprintf(msg, sizeof(msg), "settings.ra.sync_inprogress_fmt", pending, pending == 1 ? "" : "s", pending); MenuList::showOverlay(msg, OverlayDismissMode::None); @@ -834,8 +941,8 @@ int main(int argc, char *argv[]) sync_result = RA_Sync_syncAll(0, NULL, &cancel, [](uint32_t current, uint32_t total, bool success, void* userdata) { auto* ctx = static_cast(userdata); - char buf[128]; - snprintf(buf, sizeof(buf), "Syncing achievements...\n\n(%u/%u)", + char buf[256]; + snprintf(buf, sizeof(buf), "settings.ra.sync_progress_fmt", current, ctx->total); { std::lock_guard lock(*ctx->mutex); @@ -853,7 +960,7 @@ int main(int argc, char *argv[]) if (PAD_justPressed(BTN_B)) { SDL_AtomicSet(&cancel, 1); - MenuList::showOverlay("Cancelling sync...", OverlayDismissMode::None); + MenuList::showOverlay("settings.ra.sync_cancelling", OverlayDismissMode::None); } // Update overlay if progress changed @@ -876,90 +983,87 @@ int main(int argc, char *argv[]) // Update button description with result if (SDL_AtomicGet(&cancel) && sync_result.synced == 0) { - item.setDesc("Sync cancelled"); + item.setDesc("settings.ra.sync_cancelled"); } else if (SDL_AtomicGet(&cancel) && sync_result.synced > 0) { - snprintf(msg, sizeof(msg), "Cancelled: %u of %u synced", + snprintf(msg, sizeof(msg), "settings.ra.sync_cancelled_partial_fmt", sync_result.synced, sync_result.total); item.setDesc(msg); } else if (sync_result.failed > 0) { - snprintf(msg, sizeof(msg), "Incomplete: %u synced, retry later", + snprintf(msg, sizeof(msg), "settings.ra.sync_incomplete_fmt", sync_result.synced); item.setDesc(msg); } else if (sync_result.synced > 0) { - snprintf(msg, sizeof(msg), "Synced %u achievement%s", + snprintf(msg, sizeof(msg), "settings.ra.sync_synced_fmt", sync_result.synced, sync_result.synced == 1 ? "" : "s"); item.setDesc(msg); } else { - item.setDesc("No pending unlocks"); + item.setDesc("settings.ra.sync_no_pending"); } return NoOp; }}, - new MenuItem{ListItemType::Button, "Reset to defaults", "Resets all options in this menu to their default values.", ResetCurrentMenu}, + new MenuItem{ListItemType::Button, "settings.reset_defaults", "settings.reset_defaults_desc", ResetCurrentMenu}, }); - auto minarchMenu = new MenuList(MenuItemType::List, "In-Game", + auto minarchMenu = new MenuList(MenuItemType::List, "settings.ingame.title", { - new MenuItem{ListItemType::Generic, "Notifications", "Save state notifications", {}, {}, nullptr, nullptr, DeferToSubmenu, notificationsMenu}, - new MenuItem{ListItemType::Generic, "RetroAchievements", "Achievement tracking settings", {}, {}, nullptr, nullptr, DeferToSubmenu, retroAchievementsMenu}, + new MenuItem{ListItemType::Generic, "settings.ingame.notifications", "settings.ingame.notifications_desc", {}, {}, nullptr, nullptr, DeferToSubmenu, notificationsMenu}, + new MenuItem{ListItemType::Generic, "settings.ra.in_game", "settings.ra.in_game_desc", {}, {}, nullptr, nullptr, DeferToSubmenu, retroAchievementsMenu}, }); // We need to alert the user about potential issues if the // stock OS was modified in way that are known to cause issues std::string bbver = extractBusyBoxVersion(execCommand("cat --help")); if (bbver.empty()) - bbver = "BusyBox version not found."; + bbver = "settings.about.busybox_missing"; else if(deviceInfo.getPlatform() == DeviceInfo::tg5040 && bbver.find(BUSYBOX_STOCK_VERSION) == std::string::npos) ctx.menu->showOverlay( - "Stock OS changes detected.\n" - "This may cause instability or issues.\n" - "If you experience problems, please consider\n" - "reverting to clean stock firmware.", + "settings.stock_warning", OverlayDismissMode::DismissOnA); - auto aboutMenu = new MenuList(MenuItemType::Fixed, "About", + auto aboutMenu = new MenuList(MenuItemType::Fixed, "settings.about.title", { - new StaticMenuItem{ListItemType::Generic, "NextUI version", "", + new StaticMenuItem{ListItemType::Generic, "settings.about.version", "", []() -> std::any { std::ifstream t(ROOT_SYSTEM_PATH "/version.txt"); std::stringstream buffer; buffer << t.rdbuf(); return buffer.str(); }}, - new StaticMenuItem{ListItemType::Generic, "Platform", "", + new StaticMenuItem{ListItemType::Generic, "settings.about.platform", "", []() -> std::any { return std::string(PLAT_getModel()); } }, - new StaticMenuItem{ListItemType::Generic, "Stock OS version", "", + new StaticMenuItem{ListItemType::Generic, "settings.about.os_version", "", []() -> std::any { char osver[128]; PLAT_getOsVersionInfo(osver, 128); return std::string(osver); } }, - new StaticMenuItem{ListItemType::Generic, "Busybox version", "", + new StaticMenuItem{ListItemType::Generic, "settings.about.busybox", "", [&]() -> std::any { return bbver; } }, }); std::vector mainItems = { - new MenuItem{ListItemType::Generic, "Appearance", "UI customization", {}, {}, nullptr, nullptr, DeferToSubmenu, appearanceMenu}, - new MenuItem{ListItemType::Generic, "Display", "", {}, {}, nullptr, nullptr, DeferToSubmenu, displayMenu}, - new MenuItem{ListItemType::Generic, "System", "", {}, {}, nullptr, nullptr, DeferToSubmenu, systemMenu}, + new MenuItem{ListItemType::Generic, "settings.main.appearance", "settings.main.appearance_desc", {}, {}, nullptr, nullptr, DeferToSubmenu, appearanceMenu}, + new MenuItem{ListItemType::Generic, "settings.main.display", "", {}, {}, nullptr, nullptr, DeferToSubmenu, displayMenu}, + new MenuItem{ListItemType::Generic, "settings.main.system", "", {}, {}, nullptr, nullptr, DeferToSubmenu, systemMenu}, }; if(deviceInfo.hasMuteToggle()) - mainItems.push_back(new MenuItem{ListItemType::Generic, "FN switch", "FN switch settings", {}, {}, nullptr, nullptr, DeferToSubmenu, - new MenuList(MenuItemType::Fixed, "FN Switch", muteItems)}); + mainItems.push_back(new MenuItem{ListItemType::Generic, "settings.fn.main", "settings.fn.main_desc", {}, {}, nullptr, nullptr, DeferToSubmenu, + new MenuList(MenuItemType::Fixed, "settings.fn.title", muteItems)}); - mainItems.push_back(new MenuItem{ListItemType::Generic, "In-Game", "In-game settings for MinArch", {}, {}, nullptr, nullptr, DeferToSubmenu, minarchMenu}); + mainItems.push_back(new MenuItem{ListItemType::Generic, "settings.ingame.main", "settings.ingame.main_desc", {}, {}, nullptr, nullptr, DeferToSubmenu, minarchMenu}); if(deviceInfo.hasWifi()) - mainItems.push_back(new MenuItem{ListItemType::Generic, "Network", "", {}, {}, nullptr, nullptr, DeferToSubmenu, new Wifi::Menu(appQuit, ctx.dirty)}); + mainItems.push_back(new MenuItem{ListItemType::Generic, "settings.main.network", "", {}, {}, nullptr, nullptr, DeferToSubmenu, new Wifi::Menu(appQuit, ctx.dirty)}); if(deviceInfo.hasBluetooth()) - mainItems.push_back(new MenuItem{ListItemType::Generic, "Bluetooth", "", {}, {}, nullptr, nullptr, DeferToSubmenu, new Bluetooth::Menu(appQuit, ctx.dirty)}); + mainItems.push_back(new MenuItem{ListItemType::Generic, "settings.main.bluetooth", "", {}, {}, nullptr, nullptr, DeferToSubmenu, new Bluetooth::Menu(appQuit, ctx.dirty)}); - mainItems.push_back(new MenuItem{ListItemType::Generic, "About", "", {}, {}, nullptr, nullptr, DeferToSubmenu, aboutMenu}); + mainItems.push_back(new MenuItem{ListItemType::Generic, "settings.about.main", "", {}, {}, nullptr, nullptr, DeferToSubmenu, aboutMenu}); ctx.menu = new MenuList(MenuItemType::List, "Main", mainItems); @@ -1049,10 +1153,10 @@ int main(int argc, char *argv[]) GFX_blitHardwareHints(ctx.screen, ctx.show_setting); else { - char *hints[] = {(char *)("MENU"), (char *)("SLEEP"), NULL}; + char *hints[] = {(char *)T("btn.menu"), (char *)T("btn.sleep"), NULL}; GFX_blitButtonGroup(hints, 0, ctx.screen, 0); } - char *hints[] = {(char *)("B"), (char *)("BACK"), (char *)("A"), (char *)("OKAY"), NULL}; + char *hints[] = {(char *)"B", (char *)T("btn.back"), (char *)"A", (char *)T("btn.okay"), NULL}; GFX_blitButtonGroup(hints, 1, ctx.screen, 1); } diff --git a/workspace/all/settings/wifimenu.cpp b/workspace/all/settings/wifimenu.cpp index 46f476c0f..7b84b6977 100644 --- a/workspace/all/settings/wifimenu.cpp +++ b/workspace/all/settings/wifimenu.cpp @@ -13,13 +13,13 @@ typedef std::shared_lock ReadLock; using namespace Wifi; using namespace std::placeholders; -Menu::Menu(const int &globalQuit, int &globalDirty) : MenuList(MenuItemType::Fixed, "Network", {}), globalQuit(globalQuit), globalDirty(globalDirty) +Menu::Menu(const int &globalQuit, int &globalDirty) : MenuList(MenuItemType::Fixed, "wifi.network", {}), globalQuit(globalQuit), globalDirty(globalDirty) { - toggleItem = new MenuItem(ListItemType::Generic, "WiFi", "Enable/disable WiFi", {false, true}, {"Off", "On"}, + toggleItem = new MenuItem(ListItemType::Generic, "wifi.toggle", "wifi.toggle_desc", {false, true}, {"val.off", "val.on"}, std::bind(&Menu::getWifToggleState, this), std::bind(&Menu::setWifiToggleState, this, std::placeholders::_1), std::bind(&Menu::resetWifiToggleState, this)); - diagItem = new MenuItem(ListItemType::Generic, "WiFi diagnostics", "Enable/disable WiFi logging", {false, true}, {"Off", "On"}, + diagItem = new MenuItem(ListItemType::Generic, "wifi.diagnostics", "wifi.diagnostics_desc", {false, true}, {"val.off", "val.on"}, std::bind(&Menu::getWifDiagnosticsState, this), std::bind(&Menu::setWifiDiagnosticsState, this, std::placeholders::_1), std::bind(&Menu::resetWifiDiagnosticsState, this)); @@ -60,7 +60,7 @@ std::any Menu::getWifToggleState() const void Menu::setWifiToggleState(const std::any &on) { auto state = std::any_cast(on); - ScopedOverlay overlay(state ? "Enabling WiFi..." : "Disabling WiFi..."); + ScopedOverlay overlay(state ? "wifi.enabling" : "wifi.disabling"); WIFI_enable(state); } @@ -153,7 +153,7 @@ void Menu::updater() MenuList *options; if (connected) - options = new MenuList(MenuItemType::List, "Options", + options = new MenuList(MenuItemType::List, "wifi.options", { new MenuItem{ListItemType::Button, "Disconnect", "Disconnect from this network.", [&](AbstractMenuItem &item) -> InputReactionHint @@ -162,9 +162,9 @@ void Menu::updater() }); else if (hasCredentials) - options = new MenuList(MenuItemType::List, "Options", { new ConnectKnownItem(r, selectionDirty), new ForgetItem(r, selectionDirty) }); + options = new MenuList(MenuItemType::List, "wifi.options", { new ConnectKnownItem(r, selectionDirty), new ForgetItem(r, selectionDirty) }); else - options = new MenuList(MenuItemType::List, "Options", { new ConnectNewItem(r, selectionDirty) }); + options = new MenuList(MenuItemType::List, "wifi.options", { new ConnectNewItem(r, selectionDirty) }); auto itm = new NetworkItem{r, connected, options}; if(connected && !std::string(connection.ip).empty()) @@ -205,29 +205,29 @@ void Menu::updater() } ConnectKnownItem::ConnectKnownItem(WIFI_network n, bool& dirty) - : MenuItem(ListItemType::Button, "Connect", "Connect to this network.", [&](AbstractMenuItem &item) -> InputReactionHint{ - ScopedOverlay overlay("Connecting..."); - WIFI_connect(net.ssid, net.security); + : MenuItem(ListItemType::Button, "wifi.connect_known", "wifi.connect_known_desc", [&](AbstractMenuItem &item) -> InputReactionHint{ + ScopedOverlay overlay("wifi.connecting_overlay"); + WIFI_connect(net.ssid, net.security); dirty = true; return Exit; }), net(n) {} ConnectNewItem::ConnectNewItem(WIFI_network n, bool& dirty) - : MenuItem(ListItemType::Button, "Enter WiFi passcode", "Connect to this network.", DeferToSubmenu, new KeyboardPrompt("Enter Wifi passcode", + : MenuItem(ListItemType::Button, "wifi.enter_pass", "wifi.enter_pass_desc", DeferToSubmenu, new KeyboardPrompt("wifi.password_prompt", [&](AbstractMenuItem &item) -> InputReactionHint { - ScopedOverlay overlay("Connecting..."); - WIFI_connectPass(net.ssid, net.security, item.getName().c_str()); + ScopedOverlay overlay("wifi.connecting_overlay"); + WIFI_connectPass(net.ssid, net.security, item.getName().c_str()); dirty = true; - return Exit; + return Exit; })), net(n) {} ForgetItem::ForgetItem(WIFI_network n, bool& dirty) - : MenuItem(ListItemType::Button, "Forget", "Removes credentials for this network.", - [&](AbstractMenuItem &item) -> InputReactionHint { - WIFI_forget(net.ssid, net.security); - dirty = true; + : MenuItem(ListItemType::Button, "wifi.forget", "wifi.forget_desc", + [&](AbstractMenuItem &item) -> InputReactionHint { + WIFI_forget(net.ssid, net.security); + dirty = true; return Exit; }), net(n) {}