diff --git a/App/PyUI/fonts/BeVietnamPro-Regular.ttf b/App/PyUI/fonts/BeVietnamPro-Regular.ttf new file mode 100644 index 000000000..dfa34c09f Binary files /dev/null and b/App/PyUI/fonts/BeVietnamPro-Regular.ttf differ diff --git a/App/PyUI/fonts/BeVietnamPro-SemiBold.ttf b/App/PyUI/fonts/BeVietnamPro-SemiBold.ttf new file mode 100644 index 000000000..b25bad31f Binary files /dev/null and b/App/PyUI/fonts/BeVietnamPro-SemiBold.ttf differ diff --git a/App/PyUI/fonts/README.md b/App/PyUI/fonts/README.md new file mode 100644 index 000000000..dc28180e5 --- /dev/null +++ b/App/PyUI/fonts/README.md @@ -0,0 +1,16 @@ +# PyUI bundled fonts + +Fonts in this folder can be referenced from language packs via the `fonts` object in `App/PyUI/lang/.json`. + +## Vietnamese (`Vietnamese.json`) + +- **Be Vietnam Pro** — menus, lists, grid, and shadowed text (supports Vietnamese diacritics) + +## Files + +| File | Use | +|------|-----| +| `BeVietnamPro-Regular.ttf` | Body text, menus, lists, grid | +| `BeVietnamPro-SemiBold.ttf` | Shadowed / emphasized text | + +License text: `Licenses/fonts/bevietnampro.txt` diff --git a/App/PyUI/lang/English.json b/App/PyUI/lang/English.json index 40a75a2af..3d1b76e58 100644 --- a/App/PyUI/lang/English.json +++ b/App/PyUI/lang/English.json @@ -1,5 +1,21 @@ { - "Games" : "Games", + "fontPurposeSizes": { + "TOP_BAR_TEXT": "Top Bar Text Size", + "BATTERY_PERCENT": "Battery Percent Size", + "LIST": "List Size", + "MESSAGE": "Message Size", + "DESCRIPTIVE_LIST_TITLE": "Descriptive List Title Size", + "DESCRIPTIVE_LIST_DESCRIPTION": "Descriptive List Description Size", + "GRID_ONE_ROW": "Grid One Row Size", + "GRID_MULTI_ROW": "Grid Multi Row Size", + "LIST_INDEX": "List Index Size", + "LIST_TOTAL": "List Total Size", + "SHADOWED": "Shadowed Size", + "SHADOWED_BACKDROP": "Shadowed Backdrop Size", + "SHADOWED_SMALL": "Shadowed Small Size", + "SHADOWED_BACKDROP_SMALL": "Shadowed Backdrop Small Size" + }, + "Games": "Games", "Recents": "Recents", "Apps": "Apps", "Collections": "Collections", @@ -8,7 +24,7 @@ "noEntriesFound": "No Entries Found", "systemGameSearch": "$system Game Search", "allSystemGameSearch": "All System Game Search", - "systemMenuSubOptions" : "$system Menu Sub Options", + "systemMenuSubOptions": "$system Menu Sub Options", "gameSearch": "Game Search:", "exitPyUi": "Reload UI", "setPyuiAsStartup": "Set PyUI as Startup", @@ -117,5 +133,304 @@ "Fonts": "Fonts", "gridViewThemeOptions": "Grid View Theme Options", "topBarOptions": "Top Bar Options", - "leftSideInitialXOffset": "Left Side Initial X Offset" -} \ No newline at end of file + "leftSideInitialXOffset": "Left Side Initial X Offset", + "showSelBg": "Show Sel BG", + "wrapAround": "Wrap-Around", + "resizeType": "Resize Type", + "percentageMode": "Percentage Mode", + "verticalCarousel": "Vertical Carousel", + "sidesHangOff": "Sides Hang Off", + "height": "Height", + "width": "Width", + "selectedHeight": "Selected Height", + "selectedWidth": "Selected Width", + "imageResizeType": "Image Resize Type", + "internalPadding": "Internal Padding", + "yOffset": "Y-Offset", + "selectedIconPositionOffset": "Selected Icon Position Offset", + "useSelectedImageInAnimation": "Use Selected Image In Animation", + "xOffset": "X-Offset", + "setTopBarText": "Set Top Bar Text", + "setBottomBarText": "Set Bottom Bar Text", + "showBatteryPercent": "Show Battery Percent", + "showBatteryIcon": "Show Battery Icon", + "showVolumeNumbers": "Show Volume Numbers", + "showBottomBarButtons": "Show Bottom Bar Buttons", + "showClock": "Show Clock", + "mainMenuTitle": "Main Menu Title", + "setGridBgOffsetToImageOffset": "Set Grid BG Offset To Image Offset", + "wifiConfiguration": "WiFi Configuration", + "wifiPassword": "WiFi Password", + "scanningForNetworks": "Scanning for networks...", + "bluetoothConfiguration": "Bluetooth Configuration", + "scanning": "Scanning", + "buttonRemapper": "Button Remapper", + "mainMenuSubOptions": "Main Menu Sub Options", + "collectionName": "Collection Name:", + "gameSystemSortModeTitle": "Game System Sort Mode", + "downloadBoxartDesc": "Scan entire library for missing boxart", + "optimizeBoxartDesc": "Resize boxart and convert to QOI for faster loading", + "lockedDownModesDesc": "Simpler modes for new users or kids", + "gameOnlyModeTitle": "Game Selection Only Mode", + "gameOnlyModePrompt1": "Would you like to enter game selection only mode?", + "gameOnlyModePrompt2": "Boot straight into the game selection screen", + "gameOnlyModePrompt3": "To exit enter the Konami Code", + "simpleModeTitle": "Simple Mode", + "simpleModePrompt1": "Would you like to enter simple mode?", + "simpleModePrompt2": "It has restricted access to settings", + "simpleModePrompt3": "To exit enter the Konami Code", + "deleteBoxart": "Delete Box Art", + "deleteRom": "Delete ROM", + "deleteRomPrompt1": "Would you like to permanently delete", + "deleteBoxartPrompt1": "Would you like to permanently delete the boxart for", + "deleteRomDeleted": "{path} deleted.", + "deleteBoxartDeleted": "Boxart for {name} deleted.", + "gameTracking": "Game Tracking", + "systemTracking": "System Tracking", + "activityTracker": "Activity Tracker", + "systemActivityTracker": "System Activity Tracker", + "today": "Today", + "thisWeek": "This Week", + "thisMonth": "This Month", + "thisYear": "This Year", + "allTime": "All Time", + "screenshot": "Screenshot", + "boxart": "Boxart", + "yes": "Yes", + "no": "No", + "recentDesc": "Recent", + "collectionDesc": "Collection", + "favoriteDesc": "Favorite", + "yourGamesDesc": "Your games", + "yourAppsDesc": "Your Apps", + "yourSettingsDesc": "Your settings", + "launchRecents": "Launch Recents", + "launchFavorites": "Launch Favorites", + "launchCollections": "Launch Collections", + "launchApplications": "Launch Applications", + "launchSettings": "Launch Settings", + "applications": "Applications", + "scrapeBoxartDesc": "Scrape game boxart", + "trackAppUsageDesc": "Track app usage", + "size": "Size", + "viewTypes": { + "ICON_AND_DESC": "Icon and Description", + "FULLSCREEN_GRID": "Fullscreen Grid", + "GRID": "Grid", + "TEXT_AND_IMAGE": "Text and Image", + "TEXT_ONLY": "Text Only", + "POPUP": "Popup", + "CAROUSEL": "Carousel" + }, + "resizeTypes": { + "FIT": "Fit", + "ZOOM": "Zoom", + "NONE": "None" + }, + "booleans": { + "true": "Enabled", + "false": "Disabled" + }, + "onOff": { + "on": "On", + "off": "Off" + }, + "gameSystemSortModes": { + "Alphabetical": "Alphabetical", + "SortOrderKey": "Sort Order Key", + "Custom": "Custom" + }, + "screenTypes": { + "Collections": "Collections", + "Favorites": "Favorites", + "GameSelect": "Game Select", + "Recents": "Recents", + "GameSwitcher": "Game Switcher" + }, + "controllerInputs": { + "A": "A", + "B": "B", + "X": "X", + "Y": "Y", + "MENU": "Menu", + "DPAD_UP": "D-Pad Up", + "DPAD_DOWN": "D-Pad Down", + "DPAD_LEFT": "D-Pad Left", + "DPAD_RIGHT": "D-Pad Right", + "L1": "L1", + "L2": "L2", + "L3": "L3", + "R1": "R1", + "R2": "R2", + "R3": "R3", + "START": "Start", + "SELECT": "Select", + "LEFT_STICK_UP": "Left Stick Up", + "LEFT_STICK_DOWN": "Left Stick Down", + "LEFT_STICK_LEFT": "Left Stick Left", + "LEFT_STICK_RIGHT": "Left Stick Right", + "RIGHT_STICK_UP": "Right Stick Up", + "RIGHT_STICK_DOWN": "Right Stick Down", + "RIGHT_STICK_LEFT": "Right Stick Left", + "RIGHT_STICK_RIGHT": "Right Stick Right", + "VOLUME_DOWN": "Volume Down", + "VOLUME_UP": "Volume Up", + "POWER_BUTTON": "Power Button", + "UNKNOWN": "Unknown" + }, + "removeGameSwitcherGame": "Remove from GameSwitcher", + "addGameSwitcherGame": "Add to GameSwitcher", + "removeRecents": "Remove Recents", + "selectBoxart": "Select BoxArt Download", + "animationSettings": "Animation Settings", + "animationsEnabled": "Animations Enabled", + "animationSpeed": "Animation Speed", + "inputRateLimitMs": "Input Rate Limiting (ms)", + "ignoreArticlesWhenSorting": "Ignore articles when sorting", + "powerDownPrompt": "Would you like to power down?", + "powerDownOptionsWithReboot": "A = Power Down, X = Reboot, B = Cancel", + "powerDownOptions": "A = Power Down, B = Cancel", + "turningOnWifiPower": "Turning on WiFi Power", + "startingWifiProcess": "Starting WiFi process", + "startingIpAssignment": "Starting ip address assignment process", + "sdcardReadOnlyWarning": "Warning: /mnt/SDCARD is read-only. Please check your SD card.", + "wifiStatusOff": "Off", + "wifiStatusConnecting": "Connecting", + "wifiStatusError": "Error", + "aboutIpAddress": "IP Address", + "aboutMacAddress": "Mac Address", + "aboutFwVersion": "FW Version", + "aboutUnknown": "Unknown", + "noValidRomsToLaunch": "No valid ROMs available to launch.", + "loadingBoxartList": "Loading boxart list...", + "downloadingBoxartTo": "Downloading {boxart} to {path}", + "toggleView": "Toggle View", + "selectBoxArtTitle": "Select Box Art", + "optimizeBoxartPrompt1": "Would you like to optimize boxart?", + "optimizeBoxartPrompt2": "Originals will be converted, be sure to backup!", + "optimizeBoxartPrompt3": "A = Yes, B = No, X/Y = Never Prompt", + "optimizeBoxartPrompt4": "You can manually do this in:", + "optimizeBoxartPrompt5": "Settings -> Extra Settings -> Optimize BoxArt", + "selectAnOption": "Select a {name}", + "gameConfigurationTitle": "{system} Configuration", + "bluetoothConnectFailed": "Bluetooth device {name} failed to connect at step: {step}. {output}", + "bluetoothConnectSuccess": "Bluetooth device {name} connected successfully", + "stoppingBluetoothScanner": "Stopping Bluetooth scanner...", + "stoppingWifiScanner": "Stopping WiFi scanner...", + "invalidWifiPasswordLength": "Invalid WiFi password length! Must be between 8 and 63", + "updatingWifiConfig": "Updating config file for {ssid} with password {password}", + "confirmText": "Okay", + "backText": "Back", + "cloudflareOfflineAborting": "Cloudflare ping failed; device is offline. Aborting.", + "scrapingBoxartPatient": "Scraping box art. Please be patient, especially with large libraries!", + "scrapingComplete": "Scraping complete!", + "wifiMustBeConnected": "Wifi must be connected", + "libretroUnavailable": "Libretro thumbnail service unavailable; trying fallback.", + "libretroGithubUnavailable": "Libretro thumbnail GitHub repo is also currently unavailable. Please try again later.", + "imageListNotFound": "Image list file not found for {system}.", + "noMissingBoxartForSystem": "No roms are missing boxart for {system}.", + "remoteSystemNotFound": "Remote system name not found - unable to download box art for {system}.", + "scrapingBoxartProgress": "Scraping box art... ({count}/{total})", + "scrapingBoxartProgressFound": "Scraping box art... ({count}/{total}). Found {found} so far.", + "gameCountSingular": "{count} game", + "gameCountPlural": "{count} games", + "systemIndexDescription": "{name} - {count} - System {index} of {total}", + "activityTimeHoursMinutes": "{hours}h {minutes}m", + "activityTimeMinutesOnly": "{minutes}m", + "menuOptionDisplays": { + "CPU Mode": "CPU Mode", + "Retroarch Core": "Retroarch Core", + "Emulator": "Emulator", + "Emulator64": "Emulator64", + "Emulator32": "Emulator32", + "Emulator_32": "Emulator (32-bit)", + "Emulator_64": "Emulator (64-bit)", + "Emulator_Pixel2": "Emulator", + "RA Build": "RA Build", + "RA: C-button Mode": "RA: C-button Mode", + "Stretch to Full Screen": "Stretch to Full Screen", + "Control Profile": "Control Profile", + "Steward Mode (overrides control profile)": "Steward Mode (overrides control profile)", + "Controller Mode": "Controller Mode", + "Menu Hold Time": "Menu Hold Time", + "Vibrate on Menu Hold": "Vibrate on Menu Hold", + "Power indicator LED mode": "Power indicator LED mode", + "Low power warning percent": "Low power warning percent", + "Shutdown after sleeping": "Shutdown after sleeping", + "Shutdown after sleeping if unplugged": "Shutdown after sleeping if unplugged", + "Shutdown while idle in menu": "Shutdown while idle in menu", + "Shutdown while idle in game": "Shutdown while idle in game", + "Turn screen off when charging in menu": "Turn screen off when charging in menu", + "Turn screen off when charging in game": "Turn screen off when charging in game", + "Disable network services in game": "Disable network services in game", + "Disable wifi in game": "Disable wifi in game", + "Check for spruce updates on startup": "Check for spruce updates on startup", + "Launch application on startup": "Launch application on startup", + "Rumble intensity": "Rumble intensity", + "Extended brightness controls": "Extended brightness controls", + "Disable joystick": "Disable joystick", + "Enable Lid Sensor": "Enable Lid Sensor", + "Use ZRAM": "Use ZRAM", + "SD card swapfile": "SD card swapfile", + "Global sceenshot shortcut": "Global sceenshot shortcut", + "Overclock Speed": "Overclock Speed", + "Run TrimUI OSD": "Run TrimUI OSD", + "Thermal Control": "Thermal Control", + "Tap home key action": "Tap home key action", + "Hold home key action": "Hold home key action", + "RA: use in-game menu": "RA: use in-game menu", + "Transfer saves between cores": "Transfer saves between cores", + "RA: hotkey enable button": "RA: hotkey enable button", + "RA: auto save state on close": "RA: auto save state on close", + "RA: auto load state on launch": "RA: auto load state on launch", + "GB[C/A]: use Perfect Overlays": "GB[C/A]: use Perfect Overlays", + "Verbose emulator logging": "Verbose emulator logging", + "Genesis Plus GX: enable widescreen": "Genesis Plus GX: enable widescreen", + "Enable Samba": "Enable Samba", + "Enable SSH": "Enable SSH", + "Enable Syncthing": "Enable Syncthing", + "Enable WiFi File Transfer": "Enable WiFi File Transfer", + "OTA: skip version check": "OTA: skip version check", + "Enable Network Proxy": "Enable Network Proxy", + "Proxy Address": "Proxy Address", + "Proxy Port": "Proxy Port", + "Proxy Protocol": "Proxy Protocol", + "Disable RGB LEDs in all contexts": "Disable RGB LEDs in all contexts", + "RGB LED brightness": "RGB LED brightness", + "Default LED color": "Default LED color", + "Default LED behavior": "Default LED behavior", + "Default LED cycle duration (ms)": "Default LED cycle duration (ms)", + "In-game LED behavior": "In-game LED behavior", + "In-game LED color": "In-game LED color", + "In-game LED cycle duration (ms)": "In-game LED cycle duration (ms)", + "Use separate LED control during emulation": "Use separate LED control during emulation", + "Headphone Audio Multiplier": "Headphone Audio Multiplier", + "Retroachivements mode": "Retroachivements mode", + "Password": "Password", + "Username": "Username" + }, + "governorOptions": { + "Smart": "Smart", + "Performance": "Performance", + "Overclock": "Overclock", + "Powersave": "Powersave" + }, + "launchOptions": { + "Generate M3U playlists for multi-disc games": "Generate M3U playlists for multi-disc games", + "Open ScummVM Menu": "Open ScummVM Menu", + "Scan ScummVM Games": "Scan ScummVM Games", + "Generate namelist": "Generate namelist", + "Import carts from Splore": "Import carts from Splore" + }, + "powerDownChoicesReboot": "A = Power Down, X = Reboot, B = Cancel", + "powerDownChoices": "A = Power Down, B = Cancel", + "poweringOff": "Powering off...", + "rebooting": "Rebooting...", + "wifiPowerOn": "Turning on WiFi Power", + "wifiProcessStart": "Starting WiFi process", + "wifiDhcpStart": "Starting ip address assignment process", + "noValidRoms": "No valid ROMs available to launch.", + "selectOptionPrompt": "Select a {name}", + "patchingThemeAssets": "Patching theme to faster assets", + "noCoreFound": "No core found" +} diff --git a/App/PyUI/lang/Vietnamese.json b/App/PyUI/lang/Vietnamese.json index 965560b03..cfea7e57e 100644 --- a/App/PyUI/lang/Vietnamese.json +++ b/App/PyUI/lang/Vietnamese.json @@ -1,121 +1,616 @@ -{ - "Games": "Trò chơi", - "Recents": "Gần đây", - "Apps": "Ứng dụng", - "Collections": "Bộ sưu tập", - "Favorites": "Yêu thích", - "Settings": "Cài đặt", - "noEntriesFound": "Không tìm thấy mục nào", - "systemGameSearch": "Tìm kiếm trò chơi $system", - "allSystemGameSearch": "Tìm kiếm trò chơi tất cả hệ thống", - "systemMenuSubOptions": "Tùy chọn con của menu $system", - "gameSearch": "Tìm kiếm trò chơi:", - "exitPyUi": "Tải lại giao diện", - "setPyuiAsStartup": "Đặt PyUI làm khởi động", - "romSearch": "Tìm ROM", - "showApp": "Hiển thị ứng dụng", - "hideHiddenApps": "Ẩn ứng dụng ẩn", - "sortFavorites": "Sắp xếp yêu thích", - "toggleSettingsAsGameSpecificOverride": "Bật/tắt thiết lập cho trò chơi cụ thể", - "removeFavorite": "Xóa khỏi yêu thích", - "addFavorite": "Thêm vào yêu thích", - "addRemoveCollection": "Thêm/Xóa khỏi bộ sưu tập", - "launchRandomGame": "Khởi chạy trò chơi ngẫu nhiên", - "exitGame": "Thoát trò chơi", - "saveState": "Lưu trạng thái", - "loadState": "Tải trạng thái", - "toggleFastForward": "Bật/tắt tua nhanh", - "raMenu": "Menu RA", - "createNewCollection": "Tạo bộ sưu tập mới", - "addTo": "Thêm vào", - "addToCollection": "Thêm vào bộ sưu tập", - "removeFrom": "Xóa khỏi", - "powerOff": "Tắt nguồn", - "Backlight": "Đèn nền", - "Volume": "Âm lượng", - "WiFi": "WiFi", - "Bluetooth": "Bluetooth", - "Theme": "Chủ đề", - "themeSettings": "Cài đặt chủ đề", - "soundSettings": "Cài đặt âm thanh", - "additionalSettings": "Cài đặt bổ sung", - "Status": "Trạng thái", - "calibrateAnalogSticks": "Hiệu chỉnh cần điều khiển", - "remapButtons": "Định lại nút", - "Brightness": "Độ sáng", - "Contrast": "Độ tương phản", - "Saturation": "Độ bão hòa", - "Hue": "Màu sắc", - "Red": "Đỏ", - "Blue": "Xanh dương", - "Green": "Xanh lá", - "displaySettings": "Cài đặt màn hình", - "timeSettings": "Cài đặt thời gian", - "gameSystemSelectSettings": "Cài đặt chọn hệ thống trò chơi", - "gameSelectSettings": "Cài đặt chọn trò chơi", - "gameSwitcherSettings": "Cài đặt chuyển đổi trò chơi", - "gameArtDisplaySettings": "Cài đặt hiển thị bìa trò chơi", - "downloadBoxart": "Tải bìa", - "controllerSettings": "Cài đặt bộ điều khiển", - "languageSettings": "Cài đặt ngôn ngữ", - "stockOsMenu": "Menu OS mặc định", - "optimizeBoxart": "Tối ưu hóa bìa", - "lockedDownModes": "Chế độ khóa", - "l2R2SkipByLetterForDaijishoThemes": "Bỏ qua L2/R2 theo chữ cái cho chủ đề Daijisho", - "holdMenuForGameswitcher": "Giữ menu cho GameSwitcher", - "preferSavestateScreenshots": "Ưu tiên ảnh chụp trạng thái lưu", - "gameCount": "Số lượng trò chơi", - "viewType": "Kiểu hiển thị", - "fullScreenResizeType": "Kiểu thay đổi kích thước toàn màn hình", - "trueFullScreen": "Toàn màn hình thực", - "topbarGamename": "Thanh trên = Tên trò chơi", - "useRecentsForGameswitcher": "Sử dụng gần đây cho GameSwitcher", - "showAllSystems": "Hiển thị tất cả hệ thống", - "gameSystemSorting": "Sắp xếp hệ thống trò chơi", - "systemTypePriority": "Ưu tiên loại hệ thống", - "systemBrandPriority": "Ưu tiên thương hiệu hệ thống", - "systemYearPriority": "Ưu tiên năm hệ thống", - "systemNamePriority": "Ưu tiên tên hệ thống", - "enterGameSelectionOnlyMode": "Vào chế độ chỉ chọn trò chơi", - "enterSimpleMode": "Vào chế độ đơn giản", - "Year": "Năm", - "Month": "Tháng", - "Day": "Ngày", - "hour24": "24 giờ", - "Minute": "Phút", - "playButtonPressSound": "Phát âm thanh nút bấm", - "playBgm": "Phát nhạc nền", - "bgmVolume": "Âm lượng nhạc nền", - "setTimeDate": "Đặt giờ và ngày", - "setTimezone": "Đặt múi giờ", - "Clock": "Đồng hồ", - "24HourClock": "Đồng hồ 24 giờ", - "showAmPm": "Hiển thị AM/PM", - "gameSelMenu": "Menu chọn trò chơi", - "imgMode": "Chế độ ảnh", - "Rows": "Hàng", - "Cols": "Cột", - "imgWidth": "Chiều rộng ảnh", - "imgHeight": "Chiều cao ảnh", - "primImgWidth": "Chiều rộng ảnh chính %", - "shrinkFurtherAway": "Thu nhỏ khi xa", - "boxartResizeType": "Kiểu thay đổi kích thước bìa", - "selBgResizePadWidth": "Chiều rộng nền chọn", - "selBgResizePadHeight": "Chiều cao nền chọn", - "skipMainMenu": "Bỏ qua menu chính", - "mainMenu": "Menu chính", - "mainMenuColumns": "Cột menu chính", - "showText": "Hiển thị văn bản", - "showRecents": "Hiển thị gần đây", - "showCollections": "Hiển thị bộ sưu tập", - "showFavorites": "Hiển thị yêu thích", - "showApps": "Hiển thị ứng dụng", - "showSettings": "Hiển thị cài đặt", - "mainMenuThemeOptions": "Tùy chọn chủ đề menu chính", - "systemSelectThemeOptions": "Tùy chọn chủ đề chọn hệ thống", - "gameSelectMenuThemeOptions": "Tùy chọn chủ đề menu chọn trò chơi", - "Fonts": "Phông chữ", - "gridViewThemeOptions": "Tùy chọn chủ đề chế độ lưới", - "topBarOptions": "Tùy chọn thanh trên", - "leftSideInitialXOffset": "Độ lệch X ban đầu bên trái" -} +{ + "displayName": "Tiếng Việt", + "fonts": { + "list": "BeVietnamPro-Regular.ttf", + "grid": "BeVietnamPro-Regular.ttf", + "shadowed": "BeVietnamPro-SemiBold.ttf", + "default": "BeVietnamPro-Regular.ttf" + }, + "fontPurposeSizes": { + "TOP_BAR_TEXT": "Cỡ chữ thanh trên", + "BATTERY_PERCENT": "Cỡ chữ % pin", + "LIST": "Cỡ chữ danh sách", + "MESSAGE": "Cỡ chữ thông báo", + "DESCRIPTIVE_LIST_TITLE": "Cỡ chữ tiêu đề", + "DESCRIPTIVE_LIST_DESCRIPTION": "Cỡ chữ mô tả", + "GRID_ONE_ROW": "Cỡ chữ lưới (1 hàng)", + "GRID_MULTI_ROW": "Cỡ chữ lưới (nhiều hàng)", + "LIST_INDEX": "Cỡ chữ số thứ tự", + "LIST_TOTAL": "Cỡ chữ tổng số", + "SHADOWED": "Cỡ chữ đổ bóng", + "SHADOWED_BACKDROP": "Cỡ chữ nền đổ bóng", + "SHADOWED_SMALL": "Cỡ chữ đổ bóng nhỏ", + "SHADOWED_BACKDROP_SMALL": "Cỡ chữ nền đổ bóng nhỏ" + }, + "Games": "Trò chơi", + "Recents": "Gần đây", + "Apps": "Ứng dụng", + "Collections": "Bộ sưu tập", + "Favorites": "Yêu thích", + "Settings": "Cài đặt", + "noEntriesFound": "Không tìm thấy mục nào", + "systemGameSearch": "Tìm trò chơi $system", + "allSystemGameSearch": "Tìm trò chơi tất cả hệ máy", + "systemMenuSubOptions": "Tùy chọn menu $system", + "gameSearch": "Tìm trò chơi:", + "exitPyUi": "Tải lại giao diện", + "setPyuiAsStartup": "Đặt PyUI làm màn hình khởi động", + "romSearch": "Tìm ROM", + "showApp": "Hiện ứng dụng", + "hideHiddenApps": "Ẩn ứng dụng đã ẩn", + "sortFavorites": "Sắp xếp yêu thích", + "toggleSettingsAsGameSpecificOverride": "Bật/tắt cài đặt riêng cho từng game", + "removeFavorite": "Bỏ yêu thích", + "addFavorite": "Thêm yêu thích", + "removeGameSwitcherGame": "Xóa khỏi Game Switcher", + "addGameSwitcherGame": "Thêm vào Game Switcher", + "addRemoveCollection": "Thêm/Xóa bộ sưu tập", + "removeRecents": "Xóa mục gần đây", + "launchRandomGame": "Chơi game ngẫu nhiên", + "exitGame": "Thoát game", + "saveState": "Lưu trạng thái", + "loadState": "Tải trạng thái", + "toggleFastForward": "Bật/tắt tua nhanh", + "raMenu": "Menu RetroArch", + "createNewCollection": "Tạo bộ sưu tập mới", + "addTo": "Thêm vào", + "addToCollection": "Thêm vào bộ sưu tập", + "removeFrom": "Xóa khỏi", + "powerOff": "Tắt nguồn", + "Backlight": "Đèn nền", + "Volume": "Âm lượng", + "WiFi": "Wi-Fi", + "Bluetooth": "Bluetooth", + "Theme": "Giao diện", + "themeSettings": "Cài đặt giao diện", + "soundSettings": "Cài đặt âm thanh", + "additionalSettings": "Cài đặt bổ sung", + "tasks": "Tác vụ", + "Status": "Trạng thái", + "calibrateAnalogSticks": "Hiệu chỉnh cần analog", + "remapButtons": "Gán lại nút", + "Brightness": "Độ sáng", + "Contrast": "Độ tương phản", + "Saturation": "Độ bão hòa", + "Hue": "Sắc độ", + "Red": "Đỏ", + "Blue": "Xanh dương", + "Green": "Xanh lá", + "displaySettings": "Cài đặt màn hình", + "timeSettings": "Cài đặt thời gian", + "gameSystemSelectSettings": "Cài đặt chọn hệ máy", + "gameSelectSettings": "Cài đặt chọn game", + "gameSwitcherSettings": "Cài đặt Game Switcher", + "gameArtDisplaySettings": "Cài đặt ảnh bìa game", + "downloadBoxart": "Tải ảnh bìa", + "selectBoxart": "Chọn ảnh bìa để tải", + "deleteBoxart": "Xóa ảnh bìa", + "deleteRom": "Xóa ROM", + "deleteRomPrompt1": "Bạn có muốn xóa vĩnh viễn", + "deleteBoxartPrompt1": "Bạn có muốn xóa vĩnh viễn ảnh bìa của", + "deleteRomDeleted": "Đã xóa {path}.", + "deleteBoxartDeleted": "Đã xóa ảnh bìa của {name}.", + "controllerSettings": "Cài đặt tay cầm", + "languageSettings": "Cài đặt ngôn ngữ", + "animationSettings": "Cài đặt hiệu ứng", + "animationsEnabled": "Bật hiệu ứng chuyển động", + "animationSpeed": "Tốc độ hiệu ứng", + "inputRateLimitMs": "Giới hạn tốc độ nhập (ms)", + "stockOsMenu": "Menu hệ điều hành gốc", + "optimizeBoxart": "Tối ưu ảnh bìa", + "lockedDownModes": "Chế độ giới hạn", + "l2R2SkipByLetterForDaijishoThemes": "L2/R2 nhảy theo chữ (theme Daijisho)", + "ignoreArticlesWhenSorting": "Bỏ qua mạo từ khi sắp xếp", + "holdMenuForGameswitcher": "Giữ Menu để mở Game Switcher", + "preferSavestateScreenshots": "Ưu tiên ảnh save state", + "gameCount": "Số lượng game", + "viewType": "Kiểu hiển thị", + "fullScreenResizeType": "Kiểu co giãn toàn màn hình", + "trueFullScreen": "Toàn màn hình thật", + "topbarGamename": "Thanh trên = tên game", + "useRecentsForGameswitcher": "Dùng Gần đây cho Game Switcher", + "showAllSystems": "Hiện tất cả hệ máy", + "gameSystemSorting": "Sắp xếp hệ máy", + "systemTypePriority": "Ưu tiên loại hệ máy", + "systemBrandPriority": "Ưu tiên hãng", + "systemYearPriority": "Ưu tiên năm phát hành", + "systemNamePriority": "Ưu tiên tên hệ máy", + "enterGameSelectionOnlyMode": "Vào chế độ chỉ chọn game", + "enterSimpleMode": "Vào chế độ đơn giản", + "Year": "Năm", + "Month": "Tháng", + "Day": "Ngày", + "hour24": "Giờ 24h", + "Minute": "Phút", + "playButtonPressSound": "Phát âm thanh khi bấm nút", + "playBgm": "Phát nhạc nền", + "bgmVolume": "Âm lượng nhạc nền", + "setTimeDate": "Đặt giờ và ngày", + "setTimezone": "Đặt múi giờ", + "Clock": "Đồng hồ", + "24HourClock": "Đồng hồ 24 giờ", + "showAmPm": "Hiện AM/PM", + "gameSelMenu": "Menu chọn game", + "imgMode": "Chế độ ảnh", + "Rows": "Hàng", + "Cols": "Cột", + "imgWidth": "Chiều rộng ảnh", + "imgHeight": "Chiều cao ảnh", + "primImgWidth": "Chiều rộng ảnh chính %", + "shrinkFurtherAway": "Thu nhỏ khi xa hơn", + "boxartResizeType": "Kiểu co giãn ảnh bìa", + "selBgResizePadWidth": "Đệm nền chọn (rộng)", + "selBgResizePadHeight": "Đệm nền chọn (cao)", + "skipMainMenu": "Bỏ qua menu chính", + "mainMenu": "Menu chính", + "mainMenuColumns": "Số cột menu chính", + "showText": "Hiện chữ", + "showRecents": "Hiện Gần đây", + "showCollections": "Hiện Bộ sưu tập", + "showFavorites": "Hiện Yêu thích", + "showApps": "Hiện Ứng dụng", + "showSettings": "Hiện Cài đặt", + "mainMenuThemeOptions": "Tùy chọn giao diện menu chính", + "systemSelectThemeOptions": "Tùy chọn giao diện chọn hệ máy", + "gameSelectMenuThemeOptions": "Tùy chọn giao diện chọn game", + "Fonts": "Phông chữ", + "gridViewThemeOptions": "Tùy chọn giao diện dạng lưới", + "topBarOptions": "Tùy chọn thanh trên", + "leftSideInitialXOffset": "Lệch X ban đầu bên trái", + "showSelBg": "Hiện nền mục chọn", + "wrapAround": "Cuộn vòng", + "resizeType": "Kiểu co giãn", + "percentageMode": "Chế độ phần trăm", + "verticalCarousel": "Carousel dọc", + "sidesHangOff": "Tràn ra hai bên", + "height": "Chiều cao", + "width": "Chiều rộng", + "selectedHeight": "Chiều cao mục chọn", + "selectedWidth": "Chiều rộng mục chọn", + "imageResizeType": "Kiểu co giãn ảnh", + "internalPadding": "Đệm trong", + "yOffset": "Lệch Y", + "selectedIconPositionOffset": "Lệch vị trí icon được chọn", + "useSelectedImageInAnimation": "Dùng ảnh chọn trong hiệu ứng", + "xOffset": "Lệch X", + "setTopBarText": "Đặt chữ thanh trên", + "setBottomBarText": "Đặt chữ thanh dưới", + "showBatteryPercent": "Hiện % pin", + "showBatteryIcon": "Hiện icon pin", + "showVolumeNumbers": "Hiện số âm lượng", + "showBottomBarButtons": "Hiện nút thanh dưới", + "showClock": "Hiện đồng hồ", + "mainMenuTitle": "Tiêu đề menu chính", + "setGridBgOffsetToImageOffset": "Căn nền lưới theo lệch ảnh", + "wifiConfiguration": "Cấu hình Wi-Fi", + "wifiPassword": "Mật khẩu Wi-Fi", + "scanningForNetworks": "Đang quét mạng...", + "bluetoothConfiguration": "Cấu hình Bluetooth", + "scanning": "Đang quét", + "buttonRemapper": "Gán lại nút", + "mainMenuSubOptions": "Tùy chọn phụ menu chính", + "collectionName": "Tên bộ sưu tập:", + "gameSystemSortModeTitle": "Chế độ sắp xếp hệ máy", + "downloadBoxartDesc": "Quét thư viện để tìm ảnh bìa còn thiếu", + "optimizeBoxartDesc": "Co giãn ảnh bìa và chuyển sang QOI để tải nhanh hơn", + "lockedDownModesDesc": "Chế độ đơn giản cho người mới hoặc trẻ em", + "gameOnlyModeTitle": "Chế độ chỉ chọn game", + "gameOnlyModePrompt1": "Bạn có muốn vào chế độ chỉ chọn game không?", + "gameOnlyModePrompt2": "Khởi động thẳng vào màn hình chọn game", + "gameOnlyModePrompt3": "Để thoát, nhập mã Konami", + "simpleModeTitle": "Chế độ đơn giản", + "simpleModePrompt1": "Bạn có muốn vào chế độ đơn giản không?", + "simpleModePrompt2": "Giới hạn quyền truy cập cài đặt", + "simpleModePrompt3": "Để thoát, nhập mã Konami", + "gameTracking": "Theo dõi game", + "systemTracking": "Theo dõi hệ máy", + "activityTracker": "Theo dõi hoạt động", + "systemActivityTracker": "Theo dõi hoạt động hệ máy", + "today": "Hôm nay", + "thisWeek": "Tuần này", + "thisMonth": "Tháng này", + "thisYear": "Năm nay", + "allTime": "Toàn thời gian", + "screenshot": "Ảnh chụp màn hình", + "boxart": "Ảnh bìa", + "yes": "Có", + "no": "Không", + "recentDesc": "Gần đây", + "collectionDesc": "Bộ sưu tập", + "favoriteDesc": "Yêu thích", + "yourGamesDesc": "Game của bạn", + "yourAppsDesc": "Ứng dụng của bạn", + "yourSettingsDesc": "Cài đặt của bạn", + "launchRecents": "Mở Gần đây", + "launchFavorites": "Mở Yêu thích", + "launchCollections": "Mở Bộ sưu tập", + "launchApplications": "Mở Ứng dụng", + "launchSettings": "Mở Cài đặt", + "applications": "Ứng dụng", + "scrapeBoxartDesc": "Tải ảnh bìa game", + "trackAppUsageDesc": "Theo dõi thời gian dùng app", + "size": "Cỡ chữ", + "viewTypes": { + "ICON_AND_DESC": "Icon và mô tả", + "FULLSCREEN_GRID": "Lưới toàn màn hình", + "GRID": "Lưới", + "TEXT_AND_IMAGE": "Chữ và ảnh", + "TEXT_ONLY": "Chỉ chữ", + "POPUP": "Popup", + "CAROUSEL": "Carousel" + }, + "resizeTypes": { + "FIT": "Vừa khung", + "ZOOM": "Phóng to", + "NONE": "Không co giãn" + }, + "booleans": { + "true": "Bật", + "false": "Tắt" + }, + "onOff": { + "on": "Bật", + "off": "Tắt" + }, + "gameSystemSortModes": { + "Alphabetical": "Theo bảng chữ cái", + "SortOrderKey": "Theo thứ tự tùy chỉnh", + "Custom": "Tùy chỉnh" + }, + "screenTypes": { + "Collections": "Bộ sưu tập", + "Favorites": "Yêu thích", + "GameSelect": "Chọn game", + "Recents": "Gần đây", + "GameSwitcher": "Game Switcher" + }, + "controllerInputs": { + "A": "A", + "B": "B", + "X": "X", + "Y": "Y", + "MENU": "Menu", + "DPAD_UP": "Lên", + "DPAD_DOWN": "Xuống", + "DPAD_LEFT": "Trái", + "DPAD_RIGHT": "Phải", + "L1": "L1", + "L2": "L2", + "L3": "L3", + "R1": "R1", + "R2": "R2", + "R3": "R3", + "START": "Start", + "SELECT": "Select", + "LEFT_STICK_UP": "Cần trái lên", + "LEFT_STICK_DOWN": "Cần trái xuống", + "LEFT_STICK_LEFT": "Cần trái trái", + "LEFT_STICK_RIGHT": "Cần trái phải", + "RIGHT_STICK_UP": "Cần phải lên", + "RIGHT_STICK_DOWN": "Cần phải xuống", + "RIGHT_STICK_LEFT": "Cần phải trái", + "RIGHT_STICK_RIGHT": "Cần phải phải", + "VOLUME_DOWN": "Giảm âm lượng", + "VOLUME_UP": "Tăng âm lượng", + "POWER_BUTTON": "Nút nguồn", + "UNKNOWN": "Không xác định" + }, + "gameSystems": { + "-CUSTOM-SYSTEM-": "Hệ tùy chỉnh", + "A30PORTS": "Cổng A30", + "AMIGA": "Amiga", + "ARCADE": "Arcade", + "ARDUBOY": "Arduboy", + "ATARI": "Atari 2600", + "ATARIST": "Atari ST", + "ATOMISWAVE": "Atomiswave", + "CHAI": "ChaiLove", + "COLECO": "ColecoVis", + "COMMODORE": "Commodore 64", + "CPC": "Amst CPC", + "CPS1": "CPS1", + "CPS2": "CPS2", + "CPS3": "CPS3", + "DC": "Dreamcast", + "DOOM": "DOOM", + "DOS": "MS-DOS", + "EASYRPG": "EasyRPG", + "EIGHTHUNDRED": "Atari 800", + "FAIRCHILD": "ChannelF", + "FAKE08": "Fake08", + "FBNEO": "FBNEO", + "FC": "NES / Famicom", + "FDS": "Famicom Disk", + "FIFTYTWOHUNDRED": "Atari 5200", + "GAMETANK": "Game Tank", + "GB": "GB", + "GBA": "GBA", + "GBC": "GBC", + "GG": "Game Gear", + "GW": "G&W", + "INTELLIVISION": "Intellivis.", + "LYNX": "Atari Lynx", + "MAME2003PLUS": "MAME 03+", + "MD": "Mega Drive / Genesis", + "MEDIA": "Đa phương tiện", + "MEGADUCK": "Mega Duck", + "MKXP-Z": "MKXP", + "MS": "Sega MS", + "MSU1": "MSU1", + "MSUMD": "MSUMD", + "MSX": "MSX", + "N64": "Nintendo 64", + "NAOMI": "Naomi", + "NDS": "Nintendo DS", + "NEOCD": "Neo CD", + "NEOGEO": "NeoGeo", + "NGP": "NGP", + "NGPC": "NGPC", + "ODYSSEY": "Odyssey2", + "OPENBOR": "OpenBOR", + "PC98": "PC 98", + "PCE": "TG-16", + "PCECD": "TG-CD", + "PICO8": "PICO-8", + "POKE": "Pokemini", + "PORTS": "Cổng (Ports)", + "PS": "PlayStation", + "PSP": "PSP", + "QUAKE": "Quake", + "SATELLAVIEW": "Satella", + "SATURN": "Sega Saturn", + "SCUMMVM": "ScummVM", + "SEGACD": "Sega CD", + "SEGASGONE": "SG-1000", + "SEVENTYEIGHTHUNDRED": "Atari 7800", + "SFC": "SNES / Super Famicom", + "SGB": "SGB", + "SGFX": "SuperGfx", + "SUFAMI": "Sufami", + "SUPERVISION": "Supervision", + "THIRTYTWOX": "Sega 32X", + "TIC": "TIC-80", + "VB": "VirtualBoy", + "VECTREX": "Vectrex", + "VIC20": "VIC-20", + "VIDEOPAC": "VideoPac", + "WOLF": "Wolf3D", + "WS": "WndrSwan", + "WSC": "WndrSwnC", + "X68000": "X68000", + "ZXS": "ZXS" + }, + "menuOptionDisplays": { + "CPU Mode": "Chế độ CPU", + "Retroarch Core": "Core RetroArch", + "Emulator": "Trình giả lập", + "Emulator64": "Trình giả lập (64-bit)", + "Emulator32": "Trình giả lập (32-bit)", + "RA Build": "Bản dựng RetroArch", + "RA: C-button Mode": "RetroArch: chế độ nút C", + "Stretch to Full Screen": "Kéo giãn toàn màn hình", + "Control Profile": "Cấu hình điều khiển", + "Steward Mode (overrides control profile)": "Chế độ Steward (ghi đè cấu hình điều khiển)", + "Controller Mode": "Chế độ tay cầm", + "Menu Hold Time": "Thời gian giữ Menu", + "Vibrate on Menu Hold": "Rung khi giữ Menu", + "Power indicator LED mode": "Chế độ LED báo pin", + "Low power warning percent": "Ngưỡng cảnh báo pin yếu (%)", + "Shutdown after sleeping": "Tắt máy sau khi ngủ", + "Shutdown after sleeping if unplugged": "Tắt máy sau khi ngủ nếu rút sạc", + "Shutdown while idle in menu": "Tắt máy khi idle trong menu", + "Shutdown while idle in game": "Tắt máy khi idle trong game", + "Turn screen off when charging in menu": "Tắt màn hình khi sạc trong menu", + "Turn screen off when charging in game": "Tắt màn hình khi sạc trong game", + "Disable network services in game": "Tắt dịch vụ mạng khi chơi game", + "Disable wifi in game": "Tắt WiFi khi chơi game", + "Check for spruce updates on startup": "Kiểm tra cập nhật spruce khi khởi động", + "Launch application on startup": "Chạy ứng dụng khi khởi động", + "Rumble intensity": "Cường độ rung", + "Extended brightness controls": "Điều khiển độ sáng mở rộng", + "Disable joystick": "Tắt joystick", + "Enable Lid Sensor": "Bật cảm biến nắp", + "Use ZRAM": "Dùng ZRAM", + "SD card swapfile": "Swapfile trên thẻ SD", + "Global sceenshot shortcut": "Phím tắt chụp màn hình toàn cục", + "Overclock Speed": "Tốc độ ép xung", + "Run TrimUI OSD": "Chạy TrimUI OSD", + "Thermal Control": "Điều khiển nhiệt", + "Tap home key action": "Hành động khi nhấn phím Home", + "Hold home key action": "Hành động khi giữ phím Home", + "RA: use in-game menu": "RA: dùng menu trong game", + "Transfer saves between cores": "Chuyển save giữa các core", + "RA: hotkey enable button": "RA: nút bật phím tắt", + "RA: auto save state on close": "RA: tự lưu save state khi thoát", + "RA: auto load state on launch": "RA: tự tải save state khi chạy", + "GB[C/A]: use Perfect Overlays": "GB[C/A]: dùng Perfect Overlays", + "Verbose emulator logging": "Ghi log trình giả lập chi tiết", + "Genesis Plus GX: enable widescreen": "Genesis Plus GX: bật widescreen", + "Enable Samba": "Bật Samba", + "Enable SSH": "Bật SSH", + "Enable Syncthing": "Bật Syncthing", + "Enable WiFi File Transfer": "Bật chuyển file qua WiFi", + "OTA: skip version check": "OTA: bỏ qua kiểm tra phiên bản", + "Enable Network Proxy": "Bật proxy mạng", + "Proxy Address": "Địa chỉ proxy", + "Proxy Port": "Cổng proxy", + "Proxy Protocol": "Giao thức proxy", + "Disable RGB LEDs in all contexts": "Tắt LED RGB trong mọi trường hợp", + "RGB LED brightness": "Độ sáng LED RGB", + "Default LED color": "Màu LED mặc định", + "Default LED behavior": "Hành vi LED mặc định", + "Default LED cycle duration (ms)": "Thời gian chu kỳ LED mặc định (ms)", + "In-game LED behavior": "Hành vi LED trong game", + "In-game LED color": "Màu LED trong game", + "In-game LED cycle duration (ms)": "Thời gian chu kỳ LED trong game (ms)", + "Use separate LED control during emulation": "Điều khiển LED riêng khi giả lập", + "Headphone Audio Multiplier": "Hệ số âm lượng tai nghe", + "Retroachivements mode": "Chế độ RetroAchievements", + "Password": "Mật khẩu", + "Username": "Tên người dùng", + "Emulator_64": "Trình giả lập (64-bit)", + "Emulator_32": "Trình giả lập (32-bit)", + "Emulator_Pixel2": "Trình giả lập" + }, + "governorOptions": { + "Smart": "Thông minh", + "Performance": "Hiệu năng", + "Overclock": "Ép xung", + "Powersave": "Tiết kiệm pin" + }, + "launchOptions": { + "Generate M3U playlists for multi-disc games": "Tạo playlist M3U cho game nhiều đĩa", + "Open ScummVM Menu": "Mở menu ScummVM", + "Scan ScummVM Games": "Quét game ScummVM", + "Generate namelist": "Tạo danh sách tên", + "Import carts from Splore": "Nhập cart từ Splore" + }, + "powerDownPrompt": "Bạn có muốn tắt máy?", + "powerDownOptionsWithReboot": "A = Tắt nguồn, X = Khởi động lại, B = Hủy", + "powerDownOptions": "A = Tắt nguồn, B = Hủy", + "turningOnWifiPower": "Đang bật nguồn Wi-Fi", + "startingWifiProcess": "Đang khởi động tiến trình Wi-Fi", + "startingIpAssignment": "Đang khởi động gán địa chỉ IP", + "sdcardReadOnlyWarning": "Cảnh báo: /mnt/SDCARD ở chế độ chỉ đọc. Hãy kiểm tra thẻ SD.", + "wifiStatusOff": "Tắt", + "wifiStatusConnecting": "Đang kết nối", + "wifiStatusError": "Lỗi", + "aboutIpAddress": "Địa chỉ IP", + "aboutMacAddress": "Địa chỉ MAC", + "aboutFwVersion": "Phiên bản FW", + "aboutUnknown": "Không xác định", + "noValidRomsToLaunch": "Không có ROM hợp lệ để khởi chạy.", + "loadingBoxartList": "Đang tải danh sách ảnh bìa...", + "downloadingBoxartTo": "Đang tải {boxart} vào {path}", + "toggleView": "Chuyển kiểu hiển thị", + "selectBoxArtTitle": "Chọn ảnh bìa", + "optimizeBoxartPrompt1": "Bạn có muốn tối ưu ảnh bìa không?", + "optimizeBoxartPrompt2": "Ảnh gốc sẽ được chuyển đổi, hãy sao lưu trước!", + "optimizeBoxartPrompt3": "A = Có, B = Không, X/Y = Không hỏi lại", + "optimizeBoxartPrompt4": "Bạn có thể làm thủ công tại:", + "optimizeBoxartPrompt5": "Cài đặt -> Cài đặt bổ sung -> Tối ưu ảnh bìa", + "selectAnOption": "Chọn {name}", + "gameConfigurationTitle": "Cấu hình {system}", + "bluetoothConnectFailed": "Thiết bị Bluetooth {name} kết nối thất bại ở bước: {step}. {output}", + "bluetoothConnectSuccess": "Thiết bị Bluetooth {name} đã kết nối thành công", + "stoppingBluetoothScanner": "Đang dừng quét Bluetooth...", + "stoppingWifiScanner": "Đang dừng quét WiFi...", + "invalidWifiPasswordLength": "Mật khẩu WiFi không hợp lệ! Phải từ 8 đến 63 ký tự", + "updatingWifiConfig": "Đang cập nhật cấu hình cho {ssid} với mật khẩu {password}", + "confirmText": "Xác nhận", + "backText": "Quay lại", + "cloudflareOfflineAborting": "Không ping được Cloudflare; thiết bị đang ngoại tuyến. Đã hủy.", + "scrapingBoxartPatient": "Đang tải ảnh bìa. Vui lòng kiên nhẫn, đặc biệt với thư viện lớn!", + "scrapingComplete": "Tải ảnh bìa hoàn tất!", + "wifiMustBeConnected": "Cần kết nối WiFi", + "libretroUnavailable": "Dịch vụ thumbnail Libretro không khả dụng; đang thử phương án dự phòng.", + "libretroGithubUnavailable": "Kho GitHub thumbnail Libretro cũng không khả dụng. Vui lòng thử lại sau.", + "imageListNotFound": "Không tìm thấy file danh sách ảnh cho {system}.", + "noMissingBoxartForSystem": "Không có ROM nào thiếu ảnh bìa cho {system}.", + "remoteSystemNotFound": "Không tìm thấy tên hệ từ xa - không thể tải ảnh bìa cho {system}.", + "scrapingBoxartProgress": "Đang tải ảnh bìa... ({count}/{total})", + "scrapingBoxartProgressFound": "Đang tải ảnh bìa... ({count}/{total}). Đã tìm thấy {found}.", + "gameCountSingular": "{count} trò chơi", + "gameCountPlural": "{count} trò chơi", + "systemIndexDescription": "{name} - {count} - Hệ {index}/{total}", + "activityTimeHoursMinutes": "{hours}g {minutes}p", + "activityTimeMinutesOnly": "{minutes}p", + "powerDownChoicesReboot": "A = Tắt máy, X = Khởi động lại, B = Hủy", + "powerDownChoices": "A = Tắt máy, B = Hủy", + "poweringOff": "Đang tắt máy...", + "rebooting": "Đang khởi động lại...", + "wifiPowerOn": "Đang bật WiFi", + "wifiProcessStart": "Đang khởi động WiFi", + "wifiDhcpStart": "Đang cấp địa chỉ IP", + "noValidRoms": "Không có ROM hợp lệ để chạy.", + "selectOptionPrompt": "Chọn {name}", + "patchingThemeAssets": "Đang tối ưu theme sang asset nhanh hơn", + "noCoreFound": "Không tìm thấy core", + "settingsCategories": { + "Game Switcher Settings": "Cài đặt Game Switcher", + "Battery Settings": "Cài đặt pin", + "System Settings": "Cài đặt hệ thống", + "Emulator Settings": "Cài đặt trình giả lập", + "Network Settings": "Cài đặt mạng", + "Proxy Settings": "Cài đặt proxy", + "RGB LED Settings": "Cài đặt LED RGB", + "RetroAchievements Settings": "Cài đặt RetroAchievements", + "Audio Settings": "Cài đặt âm thanh" + }, + "menuOptionDescriptions": { + "Clockspeed to use for overclocking": "Tốc độ xung nhịp khi ép xung", + "Customize fan profiles": "Tùy chỉnh cấu hình quạt", + "Early Alpha": "Alpha sớm", + "Headphone Audio Multiplier": "Hệ số âm lượng tai nghe", + "Route HTTP(S) requests to proxy server": "Định tuyến HTTP(S) qua máy chủ proxy", + "Start + L/R change contrast at minimum brightness": "START + L/R đổi độ tương phản ở độ sáng tối thiểu", + "allows reinstall of same spruce version": "cho phép cài lại cùng phiên bản spruce", + "can help with stick delete mod bugs": "có thể khắc phục lỗi mod nút xóa", + "caution: high values may freeze device": "cẩn thận: giá trị cao có thể treo máy", + "custom allows to set in RA itself": "tùy chỉnh: đặt trong RetroArch", + "during emulation": "trong lúc giả lập", + "flash LED below threshold": "nhấp nháy LED dưới ngưỡng", + "follows defaults if set to False": "dùng mặc định nếu đặt False", + "for non-red status LED": "cho LED trạng thái không phải màu đỏ", + "for system notifications": "cho thông báo hệ thống", + "http://{ip_addr}:8080": "http://{ip_addr}:8080", + "http://{ip_addr}:8384": "http://{ip_addr}:8384", + "if connected to Wi-Fi": "nếu đã kết nối Wi-Fi", + "if enabled; non-linear": "nếu bật; phi tuyến", + "if not autoresuming last game": "nếu không tự tiếp tục game cuối", + "ignore shutdown timeout if charging": "bỏ qua hẹn tắt máy khi đang sạc", + "in RA, PPSSPP, or DraStic (reboot to apply)": "trong RA, PPSSPP hoặc DraStic (khởi động lại để áp dụng)", + "in menu, etc.": "trong menu, v.v.", + "including samba, ssh, etc.": "bao gồm samba, ssh, v.v.", + "log output to SD card for debugging": "ghi log ra thẻ SD để gỡ lỗi", + "off sleeps indefinitely": "Off = ngủ vô thời hạn", + "only applies in main UI (reboot to apply)": "chỉ áp dụng trong UI chính (khởi động lại để áp dụng)", + "provided by 1PlayerInsertCoin and Mugwomp93": "cung cấp bởi 1PlayerInsertCoin và Mugwomp93", + "reboot to apply": "khởi động lại để áp dụng", + "reduces battery consumption": "giảm tiêu thụ pin", + "reload UI to apply": "tải lại UI để áp dụng", + "seconds before GS launch from menu": "giây trước khi mở GS từ menu", + "shut device to enter sleep": "gập máy để vào chế độ ngủ", + "smb://{ip_addr}/SDCARD (user/pass=spruce/happygaming)": "smb://{ip_addr}/SDCARD (user/pass=spruce/happygaming)", + "ssh spruce@{ip_addr} (password=happygaming)": "ssh spruce@{ip_addr} (password=happygaming)", + "use widescreen hacked core when available": "dùng core widescreen hack nếu có", + "when launching GS from menu": "khi mở GS từ menu" + }, + "menuOptionValues": { + "True": "Bật", + "False": "Tắt", + "Off": "Tắt", + "On": "Bật", + "Always off": "Luôn tắt", + "Always on": "Luôn bật", + "On in menu only": "Chỉ trong menu", + "Only when unplugged": "Chỉ khi rút sạc", + "Emulator menu": "Menu trình giả lập", + "Game Switcher": "Game Switcher", + "Exit game": "Thoát game", + "Nothing": "Không làm gì", + "Prompt": "Hỏi", + "Always": "Luôn", + "Never": "Không bao giờ", + "Select": "Select", + "Start": "Start", + "Custom": "Tùy chỉnh", + "Smart": "Thông minh", + "Performance": "Hiệu năng", + "Overclock": "Ép xung", + "Powersave": "Tiết kiệm pin", + "Quiet": "Im lặng", + "Adaptive": "Thích ứng", + "Conservative": "Tiết kiệm", + "Sport": "Thể thao", + "http": "http", + "socks5": "socks5", + "Alphabetical": "Theo bảng chữ cái", + "SortOrderKey": "Theo thứ tự hệ thống", + "X360": "X360", + "Nintendo": "Nintendo", + "L2+R2+Y": "L2+R2+Y", + "L2+R2+X": "L2+R2+X", + "L2+R2+DOWN": "L2+R2+DOWN" + } +} diff --git a/App/PyUI/main-ui/devices/device_common.py b/App/PyUI/main-ui/devices/device_common.py index 0c23a2ae0..83922130e 100644 --- a/App/PyUI/main-ui/devices/device_common.py +++ b/App/PyUI/main-ui/devices/device_common.py @@ -9,6 +9,7 @@ from pathlib import Path from audio.audio_player_none import AudioPlayerNone from controller.controller_inputs import ControllerInput +from menus.language.language import Language from devices.abstract_device import AbstractDevice from devices.miyoo.device_user_config import DeviceUserConfig from devices.utils.process_runner import ProcessRunner @@ -38,11 +39,11 @@ def prompt_power_down(self): while(True): PyUiLogger.get_logger().info("Prompting for shutdown") Display.clear("Power") - Display.render_text_centered(f"Would you like to power down?",self.screen_width()//2, self.screen_height()//2,Theme.text_color(FontPurpose.LIST), purpose=FontPurpose.LIST) + Display.render_text_centered(Language.label("powerDownPrompt", "Would you like to power down?"),self.screen_width()//2, self.screen_height()//2,Theme.text_color(FontPurpose.LIST), purpose=FontPurpose.LIST) if(self.reboot_cmd() is not None): - Display.render_text_centered(f"A = Power Down, X = Reboot, B = Cancel",self.screen_width() //2, self.screen_height()//2+100,Theme.text_color(FontPurpose.LIST), purpose=FontPurpose.LIST) + Display.render_text_centered(Language.label("powerDownOptionsWithReboot", "A = Power Down, X = Reboot, B = Cancel"),self.screen_width() //2, self.screen_height()//2+100,Theme.text_color(FontPurpose.LIST), purpose=FontPurpose.LIST) else: - Display.render_text_centered(f"A = Power Down, B = Cancel",self.screen_width() //2, self.screen_height()//2+100,Theme.text_color(FontPurpose.LIST), purpose=FontPurpose.LIST) + Display.render_text_centered(Language.label("powerDownOptions", "A = Power Down, B = Cancel"),self.screen_width() //2, self.screen_height()//2+100,Theme.text_color(FontPurpose.LIST), purpose=FontPurpose.LIST) Display.present() if(Controller.get_input()): if(Controller.last_input() == ControllerInput.A): @@ -248,7 +249,11 @@ def get_wifi_status(self): if not self.is_wifi_enabled(): return WifiStatus.OFF - if self.get_ip_addr_text() in ["Off", "Error", "Connecting"]: + if self.get_ip_addr_text() in [ + Language.label("wifiStatusOff", "Off"), + Language.label("wifiStatusError", "Error"), + Language.label("wifiStatusConnecting", "Connecting"), + ]: return WifiStatus.OFF info = self.get_wifi_connection_quality_info() @@ -300,15 +305,15 @@ def start_wifi_services(self, foreground_call=False): if not self.connection_seems_up(): PyUiLogger.get_logger().info("Starting WiFi Services") if(foreground_call): - Display.display_message("Turning on WiFi Power") + Display.display_message(Language.label("turningOnWifiPower", "Turning on WiFi Power")) self.set_wifi_power(1) time.sleep(1) if(foreground_call): - Display.display_message("Starting WiFi process") + Display.display_message(Language.label("startingWifiProcess", "Starting WiFi process")) self.start_wpa_supplicant() if(foreground_call): - Display.display_message("Starting ip address assignment process") + Display.display_message(Language.label("startingIpAssignment", "Starting ip address assignment process")) self.start_udhcpc() @@ -317,7 +322,7 @@ def get_ip_addr_text(self): import subprocess if not self.is_wifi_enabled(): - return "Off" + return Language.label("wifiStatusOff", "Off") try: # Query interface address @@ -335,10 +340,10 @@ def get_ip_addr_text(self): if line.startswith("inet "): return line.split()[1].split("/")[0] - return "Connecting" + return Language.label("wifiStatusConnecting", "Connecting") except Exception: - return "Error" + return Language.label("wifiStatusError", "Error") def exit_pyui(self): Display.deinit_display() @@ -464,16 +469,16 @@ def get_mac_address(self,iface="wlan0"): return f.read().strip() except Exception as e: PyUiLogger.get_logger().error(f"Could not read MAC address for interface {iface} : {e}") - return "Unknown" + return Language.label("aboutUnknown", "Unknown") def get_fw_version(self): - return "Unknown" + return Language.label("aboutUnknown", "Unknown") def get_about_info_entries(self): about_info_entries = [] - about_info_entries.append( ("IP Address", self.get_ip_addr_text()) ) - about_info_entries.append( ("Mac Address", self.get_mac_address()) ) - about_info_entries.append( ("FW Version",self.get_fw_version()) ) + about_info_entries.append( (Language.label("aboutIpAddress", "IP Address"), self.get_ip_addr_text()) ) + about_info_entries.append( (Language.label("aboutMacAddress", "Mac Address"), self.get_mac_address()) ) + about_info_entries.append( (Language.label("aboutFwVersion", "FW Version"),self.get_fw_version()) ) about_info_entries.extend(self.get_device_specific_about_info_entries()) return about_info_entries @@ -514,7 +519,7 @@ def is_filesystem_read_only(self,path="/mnt/SDCARD"): def perform_sdcard_ro_check(self): if self.is_filesystem_read_only("/mnt/SDCARD"): - Display.display_message("Warning: /mnt/SDCARD is read-only. Please check your SD card.", duration_ms=10000) + Display.display_message(Language.label("sdcardReadOnlyWarning", "Warning: /mnt/SDCARD is read-only. Please check your SD card."), duration_ms=10000) def sync_hw_clock(self): #Is this different per device? Should be right for the tina linux handhelds at least diff --git a/App/PyUI/main-ui/devices/gkd/connman_wifi_menu.py b/App/PyUI/main-ui/devices/gkd/connman_wifi_menu.py index 8995b73e3..93b52403c 100644 --- a/App/PyUI/main-ui/devices/gkd/connman_wifi_menu.py +++ b/App/PyUI/main-ui/devices/gkd/connman_wifi_menu.py @@ -71,12 +71,17 @@ def connman_connect(self, id_str: str): def switch_network(self, net: WiFiNetwork): PyUiLogger.get_logger().info(f"Selected {net.ssid}!") if(net.requires_password()): - password = self.on_screen_keyboard.get_input("WiFi Password") + password = self.on_screen_keyboard.get_input(Language.label("wifiPassword", "WiFi Password")) if(password is not None and 8 <= len(password) <= 63): self.write_connman_conf(net, password) - Display.display_message(f"Updating config file for {net.ssid} with password {password}", duration_ms=5000) + Display.display_message( + Language.label("updatingWifiConfig", "Updating config file for {ssid} with password {password}") + .replace("{ssid}", net.ssid) + .replace("{password}", password), + duration_ms=5000, + ) else: - Display.display_message("Invalid WiFi password length! Must be between 8 and 63", duration_ms=5000) + Display.display_message(Language.label("invalidWifiPasswordLength", "Invalid WiFi password length! Must be between 8 and 63"), duration_ms=5000) self.connman_connect(net.id_str) @@ -92,7 +97,7 @@ def _build_options( option_list.append( GridOrListEntry( primary_text=Language.status(), - value_text="< " + ("On" if wifi_enabled else "Off") + " >", + value_text="< " + Language.on_off_label(wifi_enabled) + " >", image_path=None, image_path_selected=None, description=None, @@ -219,5 +224,5 @@ def _show_menu(self): time.sleep(0.05) finally: - Display.display_message("Stopping WiFi scanner...") + Display.display_message(Language.label("stoppingWifiScanner", "Stopping WiFi scanner...")) self.wifi_scanner.stop() diff --git a/App/PyUI/main-ui/devices/trimui/trim_ui_smart_pro_s.py b/App/PyUI/main-ui/devices/trimui/trim_ui_smart_pro_s.py index 19b1218a7..44c1d1d45 100644 --- a/App/PyUI/main-ui/devices/trimui/trim_ui_smart_pro_s.py +++ b/App/PyUI/main-ui/devices/trimui/trim_ui_smart_pro_s.py @@ -19,6 +19,7 @@ from devices.utils.file_watcher import FileWatcher from devices.utils.process_runner import ProcessRunner from display.display import Display +from menus.language.language import Language from utils import throttle from utils.config_copier import ConfigCopier @@ -186,7 +187,7 @@ def _prepare_for_power_action(self): time.sleep(1) def power_off(self): - Display.display_message("Powering off...") + Display.display_message(Language.label("poweringOff", "Powering off...")) self._prepare_for_power_action() time.sleep(1) super().power_off() @@ -195,7 +196,7 @@ def power_off(self): def reboot(self): - Display.display_message("Rebooting...") + Display.display_message(Language.label("rebooting", "Rebooting...")) self._prepare_for_power_action() time.sleep(1) super().reboot() diff --git a/App/PyUI/main-ui/mainui.py b/App/PyUI/main-ui/mainui.py index 057b27009..32b6c59ea 100644 --- a/App/PyUI/main-ui/mainui.py +++ b/App/PyUI/main-ui/mainui.py @@ -221,6 +221,7 @@ def main(): PyUiConfig.init(args.pyUiConfig) UserConfig.reload_config() CfwSystemConfig.init(args.cfwConfig) + Language.init() main_ui_mode = True @@ -247,7 +248,6 @@ def main(): #2nd init is just to allow scaling if needed Theme.convert_theme_if_needed(selected_theme, Device.get_device().screen_width(), Device.get_device().screen_height()) Controller.init() - Language.init() Device.get_device().perform_sdcard_ro_check() diff --git a/App/PyUI/main-ui/menus/app/app_menu.py b/App/PyUI/main-ui/menus/app/app_menu.py index ffd46f082..f8da518bb 100644 --- a/App/PyUI/main-ui/menus/app/app_menu.py +++ b/App/PyUI/main-ui/menus/app/app_menu.py @@ -50,7 +50,7 @@ def append_pyui_apps(self, app_list): primary_text=boxart_scraper_config.get_label() + "(Hidden)" if AppsManager.is_hidden(boxart_scraper_config) else boxart_scraper_config.get_label(), image_path=None, image_path_selected=None, - description="Scrape game boxart", + description=Language.label("scrapeBoxartDesc", "Scrape game boxart"), icon=icon, extra_data=boxart_scraper_config, value=BoxArtScraper().scrape_boxart @@ -66,7 +66,7 @@ def append_pyui_apps(self, app_list): primary_text=activity_tracker_config.get_label() + "(Hidden)" if AppsManager.is_hidden(activity_tracker_config) else activity_tracker_config.get_label(), image_path=None, image_path_selected=None, - description="Track app usage", + description=Language.label("trackAppUsageDesc", "Track app usage"), icon=icon, extra_data=activity_tracker_config, value=ActivityTracker().run_activity_tracking_app diff --git a/App/PyUI/main-ui/menus/common/bottom_bar.py b/App/PyUI/main-ui/menus/common/bottom_bar.py index 0d8f341d1..0ad2c2e06 100644 --- a/App/PyUI/main-ui/menus/common/bottom_bar.py +++ b/App/PyUI/main-ui/menus/common/bottom_bar.py @@ -1,6 +1,7 @@ from devices.device import Device from display.font_purpose import FontPurpose from display.render_mode import RenderMode +from menus.language.language import Language from themes.theme import Theme from utils.logger import PyUiLogger @@ -46,7 +47,7 @@ def render_standard_bottom_bar(self): if(Theme.show_bottom_bar_buttons()): confirm_text_y = bottom_icons_y - confirm_icon_h//2 - confirm_text_w, confirm_text_h = Display.render_text(Theme.confirm_text(), + confirm_text_w, confirm_text_h = Display.render_text(Language.label("confirmText", Theme.confirm_text()), x_offset, confirm_text_y, Theme.text_color(FontPurpose.DESCRIPTIVE_LIST_TITLE), @@ -59,7 +60,7 @@ def render_standard_bottom_bar(self): back_icon, x_offset, bottom_icons_y, RenderMode.BOTTOM_LEFT_ALIGNED) x_offset += padding + back_icon_w back_text_y = bottom_icons_y - back_icon_h//2 - back_text_w, back_text_h = Display.render_text(Theme.back_text(), + back_text_w, back_text_h = Display.render_text(Language.label("backText", Theme.back_text()), x_offset, back_text_y, Theme.text_color(FontPurpose.DESCRIPTIVE_LIST_TITLE), diff --git a/App/PyUI/main-ui/menus/games/collections/collections_add_menu.py b/App/PyUI/main-ui/menus/games/collections/collections_add_menu.py index 1e71833e1..ef6970424 100644 --- a/App/PyUI/main-ui/menus/games/collections/collections_add_menu.py +++ b/App/PyUI/main-ui/menus/games/collections/collections_add_menu.py @@ -21,7 +21,7 @@ def add_to_collection(self, collection_name): CollectionsManager.add_game_to_collection(collection_name, self.rom_info) def create_new_collection_and_add_to(self): - collection_name = OnScreenKeyboard().get_input("Collection Name:") + collection_name = OnScreenKeyboard().get_input(Language.label("collectionName", "Collection Name:")) if(collection_name is not None and collection_name != ""): self.add_to_collection(collection_name) @@ -43,7 +43,7 @@ def build_options_list(self): for collection_name in collections_containing: option_list.append( GridOrListEntry( - primary_text="Add to " + collection_name, + primary_text=Language.add_to() + " " + collection_name, value_text="", image_path=None, image_path_selected=None, diff --git a/App/PyUI/main-ui/menus/games/collections/collections_management_menu.py b/App/PyUI/main-ui/menus/games/collections/collections_management_menu.py index 71bdc6adf..3b4fc6a50 100644 --- a/App/PyUI/main-ui/menus/games/collections/collections_management_menu.py +++ b/App/PyUI/main-ui/menus/games/collections/collections_management_menu.py @@ -38,7 +38,7 @@ def build_options_list(self): for collection_name in collections_containing: option_list.append( GridOrListEntry( - primary_text="Remove from " + collection_name, + primary_text=Language.remove_from() + " " + collection_name, value_text="", image_path=None, image_path_selected=None, diff --git a/App/PyUI/main-ui/menus/games/file_based_game_system_config.py b/App/PyUI/main-ui/menus/games/file_based_game_system_config.py index a91965c40..a9d7ca139 100644 --- a/App/PyUI/main-ui/menus/games/file_based_game_system_config.py +++ b/App/PyUI/main-ui/menus/games/file_based_game_system_config.py @@ -1,6 +1,7 @@ import json import os +from menus.language.language import Language from utils.logger import PyUiLogger class FileBasedGameSystemConfig(): @@ -27,7 +28,8 @@ def get_emu_folder(self): return self.emu_folder def get_label(self): - return self._data.get('label') + raw_label = self._data.get('label') + return Language.game_system_label(self.system_name, raw_label) def get_icon(self): return self._data.get('icon') diff --git a/App/PyUI/main-ui/menus/games/game_config_menu.py b/App/PyUI/main-ui/menus/games/game_config_menu.py index d924a9e95..27066beba 100644 --- a/App/PyUI/main-ui/menus/games/game_config_menu.py +++ b/App/PyUI/main-ui/menus/games/game_config_menu.py @@ -85,7 +85,7 @@ def change_indexed_array_option(self, entry_name, input, rom_file_path, contains selected_index = 0 elif(ControllerInput.A == input): #selected_index = ThemeSelectionMenu().get_selected_theme_index(theme_folders) - selected_index = self.get_selected_index(f"Select a {entry_name}", all_options) + selected_index = self.get_selected_index(Language.select_option_prompt(entry_name), all_options) PyUiLogger.get_logger().info(f"{current_value} is updated to index {selected_index}") @@ -137,14 +137,20 @@ def run_launch_option(self, input_value, launch_option): def delete_rom(self, input_value): if(ControllerInput.A == input_value): - if UserPrompt.prompt_yes_no(Language.delete_rom(), [f"Would you like to permanently delete", f"{self.game.display_name}?"]): + if UserPrompt.prompt_yes_no(Language.delete_rom(), [ + Language.label("deleteRomPrompt1", "Would you like to permanently delete"), + f"{self.game.display_name}?", + ]): path = self.game.rom_file_path if os.path.isdir(path): shutil.rmtree(path) else: os.remove(path) self.perform_boxart_deletion() - Display.display_message(f"{path} deleted.",2000) + Display.display_message( + Language.label("deleteRomDeleted", "{path} deleted.").replace("{path}", path), + 2000, + ) FavoritesManager.remove_favorite(self.game) RecentsManager.remove_game(self.game) CollectionsManager.remove_game_from_collections(self.game) @@ -161,9 +167,15 @@ def perform_boxart_deletion(self): def delete_boxart(self, input_value): if(ControllerInput.A == input_value): - if UserPrompt.prompt_yes_no(Language.delete_boxart(), [f"Would you like to permanently delete the boxart for", f"{self.game.display_name}?"]): + if UserPrompt.prompt_yes_no(Language.delete_boxart(), [ + Language.label("deleteBoxartPrompt1", "Would you like to permanently delete the boxart for"), + f"{self.game.display_name}?", + ]): self.perform_boxart_deletion() - Display.display_message(f"Boxart for {self.game.display_name} deleted.",2000) + Display.display_message( + Language.label("deleteBoxartDeleted", "Boxart for {name} deleted.").replace("{name}", self.game.display_name), + 2000, + ) def show_config(self, rom_file_path) : self.game_system.game_system_config.reload_config() @@ -182,7 +194,7 @@ def show_config(self, rom_file_path) : if(supported_device): config_list.append( GridOrListEntry( - primary_text=config_option.get('name'), + primary_text=Language.launch_option_name(config_option.get('name')), image_path=None, image_path_selected=None, description=None, @@ -204,16 +216,17 @@ def show_config(self, rom_file_path) : supported_device = not devices or Device.get_device().get_device_name() in devices if(supported_device): effective_value = self.game_system.game_system_config.get_effective_menu_selection(name,rom_file_path) - display_name = option.get('display') + display_name = Language.menu_option_display(option.get('display')) contains_override = self.game_system.game_system_config.contains_menu_override(name,rom_file_path) if(contains_override): display_name = display_name + "*" overridable_entries.append(name) + localized_value = Language.menu_option_value(effective_value) config_list.append( GridOrListEntry( primary_text=display_name, - value_text="< " + effective_value + " >", + value_text="< " + localized_value + " >", image_path=None, image_path_selected=None, description=None, @@ -261,7 +274,7 @@ def show_config(self, rom_file_path) : if(view is None): view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text=self.game_system.display_name + " Configuration", + top_bar_text=Language.label("gameConfigurationTitle", "{system} Configuration").replace("{system}", self.game_system.display_name), options=config_list, selected_index=selected.get_index()) else: diff --git a/App/PyUI/main-ui/menus/games/game_select_menu_popup.py b/App/PyUI/main-ui/menus/games/game_select_menu_popup.py index 793fd9d64..4b9acc039 100644 --- a/App/PyUI/main-ui/menus/games/game_select_menu_popup.py +++ b/App/PyUI/main-ui/menus/games/game_select_menu_popup.py @@ -48,7 +48,7 @@ def launch_random_game(self, input_value, rom_list): roms_only = [rom for rom in rom_list if not os.path.isdir(rom.get_value().rom_file_path)] if not roms_only: - Display.display_message("No valid ROMs available to launch.", duration_ms=2000) + Display.display_message(Language.label("noValidRomsToLaunch", "No valid ROMs available to launch."), duration_ms=2000) return selected_rom = random.choice(roms_only) @@ -56,7 +56,7 @@ def launch_random_game(self, input_value, rom_list): def execute_game_search(self, game_system, input_value): from menus.games.search_games_for_system_menu import SearchGamesForSystemMenu - search_txt = OnScreenKeyboard().get_input("Game Search:") + search_txt = OnScreenKeyboard().get_input(Language.game_search()) if(search_txt is not None): return SearchGamesForSystemMenu(game_system, search_txt.upper()).run_rom_selection() @@ -112,7 +112,7 @@ def find_index_for_boxart(self, boxart_name, boxart_list): def select_specific_boxart(self, input, rom_info : RomInfo): if (ControllerInput.A == input): - Display.display_message("Loading boxart list...") + Display.display_message(Language.label("loadingBoxartList", "Loading boxart list...")) scraper = BoxArtScraper() if(not scraper.check_wifi()): return @@ -125,7 +125,7 @@ def select_specific_boxart(self, input, rom_info : RomInfo): ) start_index = self.find_index_for_boxart(name_without_ext, image_list) - boxart_download = ListOfOptionsSelectionMenu().get_selected_option_index(image_list,"Select Box Art", start_index) + boxart_download = ListOfOptionsSelectionMenu().get_selected_option_index(image_list, Language.label("selectBoxArtTitle", "Select Box Art"), start_index) if(boxart_download is not None): box_art = image_list[boxart_download] @@ -135,7 +135,11 @@ def select_specific_boxart(self, input, rom_info : RomInfo): os.remove(existing_image) Display.clear_image_cache() PyUiLogger().get_logger().info(f"Downloading {box_art} to {img_path}") - Display.display_message(f"Downloading {box_art} to {img_path}") + Display.display_message( + Language.label("downloadingBoxartTo", "Downloading {boxart} to {path}") + .replace("{boxart}", box_art) + .replace("{path}", img_path) + ) scraper.download_remote_image_for_system(rom_info.game_system.folder_name, box_art,img_path) BoxArtResizer.patch_boxart_list([img_path]) @@ -239,7 +243,7 @@ def get_game_options(self, rom_info : RomInfo, additional_popup_options = [], ro def run_game_select_popup_menu(self, rom_info : RomInfo, additional_popup_options = [], rom_list= []): popup_options = [] popup_options.append(GridOrListEntry( - primary_text=f"{rom_info.game_system.display_name} Game Search", + primary_text=Language.system_game_search().replace("$system", rom_info.game_system.display_name), image_path=Theme.settings(), image_path_selected=Theme.settings_selected(), description=None, @@ -252,7 +256,7 @@ def run_game_select_popup_menu(self, rom_info : RomInfo, additional_popup_option if(not Device.get_device().get_system_config().simple_mode_enabled()): popup_options.append(GridOrListEntry( - primary_text=f"Toggle View", + primary_text=Language.label("toggleView", "Toggle View"), image_path=Theme.settings(), image_path_selected=Theme.settings_selected(), description=None, @@ -263,7 +267,7 @@ def run_game_select_popup_menu(self, rom_info : RomInfo, additional_popup_option popup_view = ViewCreator.create_view( view_type=ViewType.POPUP, options=popup_options, - top_bar_text=f"{rom_info.game_system.display_name} Menu Sub Options", + top_bar_text=Language.system_menu_sub_options().replace("$system", rom_info.game_system.display_name), selected_index=0, cols=Theme.popup_menu_cols(), rows=Theme.popup_menu_rows()) diff --git a/App/PyUI/main-ui/menus/games/game_system_select_menu.py b/App/PyUI/main-ui/menus/games/game_system_select_menu.py index bdeda5003..0607378a2 100644 --- a/App/PyUI/main-ui/menus/games/game_system_select_menu.py +++ b/App/PyUI/main-ui/menus/games/game_system_select_menu.py @@ -195,10 +195,9 @@ def get_images(game_system : GameSystem): def get_rom_count_text(self, game_system): roms = get_rom_select_options_builder().build_rom_list(game_system, subfolder=None) rom_count = len(roms) - if(rom_count > 1): - return f"{len(roms)} games" - else: - return f"{len(roms)} game" + if rom_count > 1: + return Language.label("gameCountPlural", "{count} games").replace("{count}", str(rom_count)) + return Language.label("gameCountSingular", "{count} game").replace("{count}", str(rom_count)) def game_system_selected(self, input_value, game_system : GameSystem): if(ControllerInput.A == input_value): @@ -239,31 +238,31 @@ def add_start_of_list_extras_to_systems_list(self, systems_list): if(Theme.skip_main_menu() and Theme.show_extras_in_system_select_menu()) or Theme.merge_main_menu_and_game_menu(): if(Theme.get_recents_enabled()): systems_list.append(GridOrListEntry( - primary_text="Recents", - primary_text_long="Recents", + primary_text=Language.recents(), + primary_text_long=Language.recents(), image_path=self.get_main_menu_icon("recents",Theme.recent()), image_path_selected=self.get_main_menu_icon_selected("recents",Theme.recent_selected()), - description = "Launch Recents", + description = Language.label("launchRecents", "Launch Recents"), icon=self.get_main_menu_icon_selected("recents",Theme.recent_selected()), value=lambda input_value: self.run_extra(input_value, "Recents", self.recents_menu.run_rom_selection) ) ) if(Theme.get_favorites_enabled()): systems_list.append(GridOrListEntry( - primary_text="Favorites", - primary_text_long="Favorites", + primary_text=Language.favorites(), + primary_text_long=Language.favorites(), image_path=self.get_main_menu_icon("favorites",Theme.favorite()), image_path_selected=self.get_main_menu_icon_selected("favorites",Theme.favorite_selected()), - description = "Launch Favorites", + description = Language.label("launchFavorites", "Launch Favorites"), icon=self.get_main_menu_icon_selected("favorites",Theme.favorite_selected()), value=lambda input_value: self.run_extra(input_value, "Favorites", self.favorites_menu.run_rom_selection) ) ) if(Theme.get_collections_enabled()): systems_list.append(GridOrListEntry( - primary_text="Collections", - primary_text_long="Collections", + primary_text=Language.collections(), + primary_text_long=Language.collections(), image_path=self.get_main_menu_icon("collections",Theme.collection()), image_path_selected=self.get_main_menu_icon_selected("collections",Theme.collection_selected()), - description = "Launch Collections", + description = Language.label("launchCollections", "Launch Collections"), icon=self.get_main_menu_icon_selected("collections",Theme.collection_selected()), value=lambda input_value: self.run_extra(input_value, "Collections", self.collections_menu.run_rom_selection) ) ) @@ -275,21 +274,21 @@ def add_end_of_list_extras_to_systems_list(self, systems_list): if(Theme.get_apps_enabled()): systems_list.append(GridOrListEntry( - primary_text="Apps", - primary_text_long="Applications", + primary_text=Language.apps(), + primary_text_long=Language.label("applications", "Applications"), image_path=self.get_main_menu_icon("apps",Theme.app()), image_path_selected=self.get_main_menu_icon_selected("apps",Theme.app_selected()), - description = "Launch Applications", + description = Language.label("launchApplications", "Launch Applications"), icon=self.get_main_menu_icon_selected("apps",Theme.app_selected()), value=lambda input_value: self.run_extra(input_value, "Apps",self.app_menu.run_app_selection) )) if(Theme.get_settings_enabled() or (Theme.merge_main_menu_and_game_menu() and not Theme.skip_main_menu())): systems_list.append(GridOrListEntry( - primary_text="Settings", - primary_text_long="Settings", + primary_text=Language.settings(), + primary_text_long=Language.settings(), image_path=self.get_main_menu_icon("settings",Theme.settings()), image_path_selected=self.get_main_menu_icon_selected("settings",Theme.settings_selected()), - description = "Launch Settings", + description = Language.label("launchSettings", "Launch Settings"), icon=self.get_main_menu_icon_selected("settings",Theme.settings_selected()), value=lambda input_value: self.settings_menu.show_menu() if ControllerInput.A == input_value else None ) ) @@ -313,7 +312,10 @@ def build_system_list(self): primary_text_long=GameSystemSelectMenu.full_name_mapping.get(game_system.folder_name.lower()), image_path=image_path, image_path_selected=image_path_selected, - description = lambda idx=index, gs=game_system: f"{gs.display_name} - {self.get_rom_count_text(gs)} - System {idx} of {total_count}", + description = lambda idx=index, gs=game_system, total=total_count: Language.label( + "systemIndexDescription", + "{name} - {count} - System {index} of {total}", + ).replace("{name}", gs.display_name).replace("{count}", self.get_rom_count_text(gs)).replace("{index}", str(idx)).replace("{total}", str(total)), icon=icon, value=lambda input_value, game_system=game_system: self.game_system_selected(input_value, game_system) ) diff --git a/App/PyUI/main-ui/menus/games/in_game_menu_popup.py b/App/PyUI/main-ui/menus/games/in_game_menu_popup.py index b52185276..4e659dea2 100644 --- a/App/PyUI/main-ui/menus/games/in_game_menu_popup.py +++ b/App/PyUI/main-ui/menus/games/in_game_menu_popup.py @@ -33,7 +33,7 @@ def run_in_game_menu(self): popup_view = ViewCreator.create_view( view_type=ViewType.TEXT_ONLY, options=popup_options, - top_bar_text="Main Menu Sub Options", + top_bar_text=Language.label("mainMenuSubOptions", "Main Menu Sub Options"), selected_index=0, cols=Theme.popup_menu_cols(), rows=Theme.popup_menu_rows()) diff --git a/App/PyUI/main-ui/menus/games/retroarch_in_game_menu_popup.py b/App/PyUI/main-ui/menus/games/retroarch_in_game_menu_popup.py index 1a0521a9e..944e54220 100644 --- a/App/PyUI/main-ui/menus/games/retroarch_in_game_menu_popup.py +++ b/App/PyUI/main-ui/menus/games/retroarch_in_game_menu_popup.py @@ -98,7 +98,7 @@ def run_in_game_menu(self): popup_view = ViewCreator.create_view( view_type=ViewType.TEXT_ONLY, options=popup_options, - top_bar_text="Main Menu Sub Options", + top_bar_text=Language.label("mainMenuSubOptions", "Main Menu Sub Options"), selected_index=0, cols=Theme.popup_menu_cols(), rows=Theme.popup_menu_rows()) diff --git a/App/PyUI/main-ui/menus/games/utils/rom_select_options_builder.py b/App/PyUI/main-ui/menus/games/utils/rom_select_options_builder.py index b62ff5581..52ba05f6d 100644 --- a/App/PyUI/main-ui/menus/games/utils/rom_select_options_builder.py +++ b/App/PyUI/main-ui/menus/games/utils/rom_select_options_builder.py @@ -17,6 +17,7 @@ from menus.games.utils.rom_file_name_utils import RomFileNameUtils from menus.games.utils.rom_info import RomInfo from themes.theme import Theme +from menus.language.language import Language from utils.cached_exists import CachedExists from utils.logger import PyUiLogger from utils.time_logger import log_timing @@ -152,12 +153,14 @@ def get_image_path(self, rom_info: RomInfo, game_entry = None, prefer_savestate_ if(Device.get_device().get_system_config().never_prompt_boxart_resize()): RomSelectOptionsBuilder._user_doesnt_want_to_resize = True else: - Display.display_message_multiline([f"Would you like to optimize boxart?", - "Originals will be converted, be sure to backup!", - "A = Yes, B = No, X/Y = Never Prompt", - "", - "You can manually do this in:", - "Settings -> Extra Settings -> Optimize BoxArt"], 0) + Display.display_message_multiline([ + Language.label("optimizeBoxartPrompt1", "Would you like to optimize boxart?"), + Language.label("optimizeBoxartPrompt2", "Originals will be converted, be sure to backup!"), + Language.label("optimizeBoxartPrompt3", "A = Yes, B = No, X/Y = Never Prompt"), + "", + Language.label("optimizeBoxartPrompt4", "You can manually do this in:"), + Language.label("optimizeBoxartPrompt5", "Settings -> Extra Settings -> Optimize BoxArt"), + ], 0) input = Controller.wait_for_input([ControllerInput.A,ControllerInput.B,ControllerInput.X,ControllerInput.Y]) if(input == ControllerInput.B): diff --git a/App/PyUI/main-ui/menus/language/language.py b/App/PyUI/main-ui/menus/language/language.py index e19812de0..fc07cedb7 100644 --- a/App/PyUI/main-ui/menus/language/language.py +++ b/App/PyUI/main-ui/menus/language/language.py @@ -3,6 +3,7 @@ import os import sys +from display.font_purpose import FontPurpose from utils.logger import PyUiLogger from utils.py_ui_config import PyUiConfig @@ -33,7 +34,7 @@ def load(cls): def _read_from_file(cls, filepath): if(filepath is not None): try: - with open(filepath, 'r') as f: + with open(filepath, 'r', encoding='utf-8') as f: cls._data = json.load(f) #PyUiLogger.get_logger().info(f"Languages loaded from {filepath}") except FileNotFoundError: @@ -55,7 +56,7 @@ def save(cls): def _write_to_file(cls, filepath): try: os.makedirs(os.path.dirname(filepath), exist_ok=True) - with open(filepath, 'w') as f: + with open(filepath, 'w', encoding='utf-8') as f: json.dump(cls._data, f, indent=4) PyUiLogger.get_logger().info(f"Settings saved to {filepath}") except Exception as e: @@ -89,6 +90,172 @@ def to_dict(cls): def clear(cls): cls._data.clear() + @classmethod + def get_fonts_dir(cls): + base_dir = os.path.abspath(sys.path[0]) + parent_dir = os.path.dirname(base_dir) + return os.path.join(parent_dir, "fonts") + + @classmethod + def get_display_name_for_file(cls, language_file: str) -> str: + base_dir = os.path.abspath(sys.path[0]) + parent_dir = os.path.dirname(base_dir) + lang_path = os.path.join(parent_dir, "lang", f"{language_file}.json") + try: + with open(lang_path, "r", encoding="utf-8") as f: + data = json.load(f) + return data.get("displayName", language_file) + except Exception: + return language_file + + @classmethod + def display_name(cls) -> str: + return cls._data.get("displayName", PyUiConfig.get_language() or "English") + + @classmethod + def label(cls, key: str, default: str) -> str: + return cls._data.get(key, default) + + @classmethod + def enum_label(cls, section: str, name: str, default: str | None = None) -> str: + section_data = cls._data.get(section) + if isinstance(section_data, dict) and name in section_data: + return section_data[name] + if default is not None: + return default + return name.replace("_", " ").title() + + @classmethod + def boolean_label(cls, value) -> str: + if isinstance(value, bool): + key = "true" if value else "false" + else: + key = str(value).lower() + booleans = cls._data.get("booleans", {}) + if key in booleans: + return booleans[key] + return str(value) + + @classmethod + def on_off_label(cls, enabled: bool) -> str: + on_off = cls._data.get("onOff", {}) + key = "on" if enabled else "off" + return on_off.get(key, "On" if enabled else "Off") + + @classmethod + def view_type_label(cls, view_type) -> str: + return cls.enum_label("viewTypes", view_type.name, view_type.name.replace("_", " ").title()) + + @classmethod + def resize_type_label(cls, resize_type) -> str: + return cls.enum_label("resizeTypes", resize_type.name, resize_type.name) + + @classmethod + def controller_input_label(cls, controller_input) -> str: + return cls.enum_label("controllerInputs", controller_input.name, controller_input.name) + + @classmethod + def game_system_sort_mode_label(cls, mode: str) -> str: + return cls.enum_label("gameSystemSortModes", mode, mode) + + @classmethod + def game_system_label(cls, system_key: str, default: str) -> str: + systems = cls._data.get("gameSystems", {}) + if system_key in systems: + return systems[system_key] + return default + + @classmethod + def menu_option_display(cls, display: str) -> str: + displays = cls._data.get("menuOptionDisplays", {}) + return displays.get(display, display) + + @classmethod + def menu_option_description(cls, description: str) -> str: + if not description: + return description + descriptions = cls._data.get("menuOptionDescriptions", {}) + return descriptions.get(description, description) + + @classmethod + def settings_category(cls, category: str) -> str: + categories = cls._data.get("settingsCategories", {}) + return categories.get(category, category) + + @classmethod + def menu_option_value(cls, value) -> str: + if value is None: + return "" + if isinstance(value, bool): + return cls.boolean_label(value) + text = str(value) + if text in ("True", "False"): + return cls.boolean_label(text == "True") + values = cls._data.get("menuOptionValues", {}) + if text in values: + return values[text] + governor = cls._data.get("governorOptions", {}) + if text in governor: + return governor[text] + return text + + @classmethod + def select_option_prompt(cls, name: str) -> str: + template = cls.label("selectOptionPrompt", "Select a {name}") + return template.replace("{name}", name) + + @classmethod + def governor_option_label(cls, value: str) -> str: + options = cls._data.get("governorOptions", {}) + return options.get(value, value) + + @classmethod + def launch_option_name(cls, name: str) -> str: + options = cls._data.get("launchOptions", {}) + return options.get(name, name) + + @classmethod + def screen_type_label(cls, screen_type: str) -> str: + return cls.enum_label("screenTypes", screen_type, screen_type) + + @classmethod + def _font_config_key_for_purpose(cls, font_purpose: FontPurpose) -> str: + match font_purpose: + case FontPurpose.GRID_ONE_ROW | FontPurpose.GRID_MULTI_ROW | FontPurpose.LIST_INDEX | FontPurpose.LIST_TOTAL: + return "grid" + case FontPurpose.SHADOWED | FontPurpose.SHADOWED_BACKDROP | FontPurpose.SHADOWED_SMALL | FontPurpose.SHADOWED_BACKDROP_SMALL: + return "shadowed" + case _: + return "list" + + @classmethod + def font_purpose_size_label(cls, font_purpose: FontPurpose) -> str: + purposes = cls._data.get("fontPurposeSizes", {}) + if font_purpose.name in purposes: + return purposes[font_purpose.name] + size_suffix = cls._data.get("size", "Size") + return f"{font_purpose.name} {size_suffix}" + + @classmethod + def get_font_for_purpose(cls, font_purpose: FontPurpose): + fonts_config = cls._data.get("fonts") + if not fonts_config: + return None + + filename = fonts_config.get(cls._font_config_key_for_purpose(font_purpose)) + if not filename: + filename = fonts_config.get("default") + + if not filename: + return None + + font_path = os.path.join(cls.get_fonts_dir(), filename) + if os.path.exists(font_path): + return font_path + + PyUiLogger.get_logger().warning(f"Language font not found: {font_path}") + return None + @classmethod def games(cls): return cls._data.get("Games","Games") diff --git a/App/PyUI/main-ui/menus/main_menu.py b/App/PyUI/main-ui/menus/main_menu.py index f80f8a3db..86ba5ccdb 100644 --- a/App/PyUI/main-ui/menus/main_menu.py +++ b/App/PyUI/main-ui/menus/main_menu.py @@ -46,7 +46,7 @@ def build_options(self): primary_text=Language.recents(), image_path=Theme.recent(), image_path_selected=Theme.recent_selected(), - description="Recent", + description=Language.label("recentDesc", "Recent"), icon=None, value="Recent" ) @@ -58,7 +58,7 @@ def build_options(self): primary_text=Language.collections(), image_path=Theme.collection(), image_path_selected=Theme.collection_selected(), - description="Collection", + description=Language.label("collectionDesc", "Collection"), icon=None, value="Collection" ) @@ -70,7 +70,7 @@ def build_options(self): primary_text=Language.favorites(), image_path=Theme.favorite(), image_path_selected=Theme.favorite_selected(), - description="Favorite", + description=Language.label("favoriteDesc", "Favorite"), icon=None, value="Favorite" ) @@ -81,7 +81,7 @@ def build_options(self): primary_text=Language.games(), image_path=Theme.game(), image_path_selected=Theme.game_selected(), - description="Your games", + description=Language.label("yourGamesDesc", "Your games"), icon=None, value="Game" ) @@ -94,7 +94,7 @@ def build_options(self): primary_text=Language.apps(), image_path=Theme.app(), image_path_selected=Theme.app_selected(), - description="Your Apps", + description=Language.label("yourAppsDesc", "Your Apps"), icon=None, value="App" ) @@ -106,7 +106,7 @@ def build_options(self): primary_text=Language.settings(), image_path=Theme.settings(), image_path_selected=Theme.settings_selected(), - description="Your Apps", + description=Language.label("yourSettingsDesc", "Your settings"), icon=None, value="Setting" ) diff --git a/App/PyUI/main-ui/menus/main_menu_popup.py b/App/PyUI/main-ui/menus/main_menu_popup.py index 28c3782a2..f172d7659 100644 --- a/App/PyUI/main-ui/menus/main_menu_popup.py +++ b/App/PyUI/main-ui/menus/main_menu_popup.py @@ -19,7 +19,7 @@ def __init__(self): def rom_search(self, input): if (ControllerInput.A == input): - search_txt = OnScreenKeyboard().get_input("Game Search:") + search_txt = OnScreenKeyboard().get_input(Language.game_search()) if (search_txt is not None): return SearchedRomsMenu(search_txt.upper()).run_rom_selection() @@ -99,7 +99,7 @@ def run_popup_menu_selection(self): popup_view = ViewCreator.create_view( view_type=ViewType.POPUP, options=popup_options, - top_bar_text="Main Menu Sub Options", + top_bar_text=Language.label("mainMenuSubOptions", "Main Menu Sub Options"), selected_index=0, cols=Theme.popup_menu_cols(), rows=Theme.popup_menu_rows) diff --git a/App/PyUI/main-ui/menus/settings/basic_settings_menu.py b/App/PyUI/main-ui/menus/settings/basic_settings_menu.py index 2220841b5..844884557 100644 --- a/App/PyUI/main-ui/menus/settings/basic_settings_menu.py +++ b/App/PyUI/main-ui/menus/settings/basic_settings_menu.py @@ -94,7 +94,7 @@ def change_theme(self, input): elif(ControllerInput.X == input and not Device.get_device().get_system_config().simple_mode_enabled()): ThemeSettingsMenu().show_theme_options_menu() elif(ControllerInput.A == input): - selected_index = ThemeSelectionMenu().get_selected_option_index(theme_folders, "Themes") + selected_index = ThemeSelectionMenu().get_selected_option_index(theme_folders, Language.theme()) if(selected_index is not None): @@ -302,7 +302,7 @@ def show_menu(self) : Display.clear_text_cache() list_view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Settings", + top_bar_text=Language.settings(), options=option_list, selected_index=selected.get_index()) diff --git a/App/PyUI/main-ui/menus/settings/bluetooth_menu.py b/App/PyUI/main-ui/menus/settings/bluetooth_menu.py index b8f9bd58e..27c47028d 100644 --- a/App/PyUI/main-ui/menus/settings/bluetooth_menu.py +++ b/App/PyUI/main-ui/menus/settings/bluetooth_menu.py @@ -47,10 +47,19 @@ def connect_device(self, device) -> bool: if not output or success_token.lower() not in output.lower(): log.info(f"{name} FAILED for {device.address}") - Display.display_message(f"Bluetooth device {device.name} failed to connect at step: {name}. {output}", duration_ms=5000) + Display.display_message( + Language.label("bluetoothConnectFailed", "Bluetooth device {name} failed to connect at step: {step}. {output}") + .replace("{name}", device.name) + .replace("{step}", name) + .replace("{output}", output), + duration_ms=5000, + ) return False - Display.display_message(f"Bluetooth device {device.name} connected successfully", duration_ms=5000) + Display.display_message( + Language.label("bluetoothConnectSuccess", "Bluetooth device {name} connected successfully").replace("{name}", device.name), + duration_ms=5000, + ) self.bluetooth_scanner.refresh_devices() return True @@ -70,7 +79,7 @@ def show_bluetooth_menu(self): option_list.append( GridOrListEntry( primary_text=Language.status(), - value_text="< " + ("Scanning" if bluetooth_enabled else "Off") + " >", + value_text="< " + (Language.label("scanning", "Scanning") if bluetooth_enabled else Language.on_off_label(False)) + " >", image_path=None, image_path_selected=None, description=None, @@ -97,7 +106,7 @@ def show_bluetooth_menu(self): list_view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Bluetooth Configuration", + top_bar_text=Language.label("bluetoothConfiguration", "Bluetooth Configuration"), options=option_list, selected_index=selected.get_index()) #always reset to the top in case devices change during a scan @@ -115,5 +124,5 @@ def show_bluetooth_menu(self): selected = None finally: - Display.display_message("Stopping Bluetooth scanner...") + Display.display_message(Language.label("stoppingBluetoothScanner", "Stopping Bluetooth scanner...")) self.bluetooth_scanner.stop() diff --git a/App/PyUI/main-ui/menus/settings/button_remapper.py b/App/PyUI/main-ui/menus/settings/button_remapper.py index c6f0d3027..98d910525 100644 --- a/App/PyUI/main-ui/menus/settings/button_remapper.py +++ b/App/PyUI/main-ui/menus/settings/button_remapper.py @@ -2,6 +2,7 @@ from controller.controller_inputs import ControllerInput from devices.miyoo.device_user_config import DeviceUserConfig +from menus.language.language import Language from utils.logger import PyUiLogger from views.grid_or_list_entry import GridOrListEntry from views.selection import Selection @@ -33,14 +34,12 @@ def build_remap_buttons_options(self): option_list = [] for controller_input in ControllerInput: - current_value = controller_input.name - if(controller_input in self.button_mapping): - current_value = self.button_mapping[controller_input].name + mapped_input = self.button_mapping.get(controller_input, controller_input) option_list.append( GridOrListEntry( - primary_text=controller_input.name, - value_text=current_value, + primary_text=Language.controller_input_label(controller_input), + value_text=Language.controller_input_label(mapped_input), image_path=None, image_path_selected=None, description=None, @@ -55,7 +54,7 @@ def remap_buttons(self): selected = Selection(None,None,0) list_view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text=f"Button Remapper", + top_bar_text=Language.label("buttonRemapper", "Button Remapper"), options=self.build_remap_buttons_options(), selected_index=selected.get_index()) while(selected is not None): @@ -148,7 +147,7 @@ def remap_single_button(self, physical_button): for controller_input in ControllerInput: option_list.append( GridOrListEntry( - primary_text=controller_input.name, + primary_text=Language.controller_input_label(controller_input), image_path=None, image_path_selected=None, description=None, diff --git a/App/PyUI/main-ui/menus/settings/extra_settings_menu.py b/App/PyUI/main-ui/menus/settings/extra_settings_menu.py index e34e46a5e..4273baf1d 100644 --- a/App/PyUI/main-ui/menus/settings/extra_settings_menu.py +++ b/App/PyUI/main-ui/menus/settings/extra_settings_menu.py @@ -1,6 +1,7 @@ from controller.controller_inputs import ControllerInput from devices.device import Device +from display.display import Display from display.on_screen_keyboard import OnScreenKeyboard from games.utils.box_art_resizer import BoxArtResizer from menus.language.language import Language @@ -68,6 +69,7 @@ def change_language_setting(self, input): if (lang is not None): PyUiConfig.set_language(lang) Language.load() + Display.clear_text_cache() def launch_game_art_display_settings(self,input): if(ControllerInput.A == input): @@ -226,7 +228,7 @@ def build_options_list(self): if(contains_entry_for_device and not self.is_excluded_setting(category)): option_list.append( GridOrListEntry( - primary_text=category, + primary_text=Language.settings_category(category), value_text=None, image_path=None, image_path_selected=None, diff --git a/App/PyUI/main-ui/menus/settings/game_art_display_settings_menu.py b/App/PyUI/main-ui/menus/settings/game_art_display_settings_menu.py index f247325a5..911f529db 100644 --- a/App/PyUI/main-ui/menus/settings/game_art_display_settings_menu.py +++ b/App/PyUI/main-ui/menus/settings/game_art_display_settings_menu.py @@ -1,6 +1,7 @@ from controller.controller_inputs import ControllerInput from devices.device import Device +from menus.language.language import Language from menus.settings import settings_menu from utils.consts import COLLECTIONS, FAVORITES, GAME_SELECT, GAME_SWITCHER, RECENTS from views.grid_or_list_entry import GridOrListEntry @@ -21,8 +22,8 @@ def build_options_list(self): for screen_type in [COLLECTIONS,FAVORITES,GAME_SELECT,RECENTS,GAME_SWITCHER]: option_list.append( GridOrListEntry( - primary_text=screen_type, - value_text="< " + ("Screenshot" if Device.get_device().get_system_config().use_savestate_screenshots(screen_type) else "Boxart") + " >", + primary_text=Language.screen_type_label(screen_type), + value_text="< " + (Language.label("screenshot", "Screenshot") if Device.get_device().get_system_config().use_savestate_screenshots(screen_type) else Language.label("boxart", "Boxart")) + " >", image_path=None, image_path_selected=None, description=None, diff --git a/App/PyUI/main-ui/menus/settings/game_switcher_settings_menu.py b/App/PyUI/main-ui/menus/settings/game_switcher_settings_menu.py index eeedd1958..d524ce643 100644 --- a/App/PyUI/main-ui/menus/settings/game_switcher_settings_menu.py +++ b/App/PyUI/main-ui/menus/settings/game_switcher_settings_menu.py @@ -43,7 +43,7 @@ def build_options_list(self): option_list.append( GridOrListEntry( primary_text=Language.hold_menu_for_gameswitcher(), - value_text="< " + str(Device.get_device().get_system_config().game_switcher_enabled()) + " >", + value_text="< " + Language.boolean_label(Device.get_device().get_system_config().game_switcher_enabled()) + " >", image_path=None, image_path_selected=None, description=None, @@ -55,7 +55,7 @@ def build_options_list(self): option_list.append( GridOrListEntry( primary_text=Language.prefer_savestate_screenshots(), - value_text="< " + str(Device.get_device().get_system_config().use_savestate_screenshots(GAME_SWITCHER)) + " >", + value_text="< " + Language.boolean_label(Device.get_device().get_system_config().use_savestate_screenshots(GAME_SWITCHER)) + " >", image_path=None, image_path_selected=None, description=None, @@ -83,7 +83,8 @@ def build_options_list(self): primary_text=Language.view_type(), get_value_func=Theme.get_view_type_for_game_switcher, set_value_func=Theme.set_view_type_for_game_switcher, - get_next_enum_type=get_next_view_type + get_next_enum_type=get_next_view_type, + enum_section="viewTypes" ) ) @@ -93,7 +94,8 @@ def build_options_list(self): primary_text=Language.full_screen_resize_type(), get_value_func=Theme.get_resize_type_for_game_switcher, set_value_func=Theme.set_resize_type_for_game_switcher, - get_next_enum_type=get_next_resize_type + get_next_enum_type=get_next_resize_type, + enum_section="resizeTypes" ) ) @@ -136,7 +138,7 @@ def build_options_list(self): option_list.append( GridOrListEntry( primary_text=Language.use_recents_for_gameswitcher(), - value_text="< " + ("No" if Device.get_device().get_system_config().use_custom_gameswitcher_path() else "Yes") + " >", + value_text="< " + (Language.label("no", "No") if Device.get_device().get_system_config().use_custom_gameswitcher_path() else Language.label("yes", "Yes")) + " >", image_path=None, image_path_selected=None, description=None, diff --git a/App/PyUI/main-ui/menus/settings/game_system_select_settings_menu.py b/App/PyUI/main-ui/menus/settings/game_system_select_settings_menu.py index 56b7cc601..6a200e2f3 100644 --- a/App/PyUI/main-ui/menus/settings/game_system_select_settings_menu.py +++ b/App/PyUI/main-ui/menus/settings/game_system_select_settings_menu.py @@ -20,7 +20,10 @@ def change_game_system_sort_mode(self, input): elif (ControllerInput.DPAD_RIGHT == input): PyUiConfig.set_game_system_sort_mode(self.get_next_entry(PyUiConfig.game_system_sort_mode(),GAME_SYSTEM_SORT_MODE_OPTIONS,+1)) elif (ControllerInput.A): - selected_index = ListOfOptionsSelectionMenu().get_selected_option_index(GAME_SYSTEM_SORT_MODE_OPTIONS, "Game System Sort Mode") + selected_index = ListOfOptionsSelectionMenu().get_selected_option_index( + GAME_SYSTEM_SORT_MODE_OPTIONS, + Language.label("gameSystemSortModeTitle", "Game System Sort Mode"), + label_func=Language.game_system_sort_mode_label) if(selected_index is not None): PyUiConfig.set_game_system_sort_mode(GAME_SYSTEM_SORT_MODE_OPTIONS[selected_index]) @@ -95,8 +98,7 @@ def build_options_list(self): option_list.append( GridOrListEntry( primary_text=Language.show_all_systems(), - value_text="< " + - ("On" if PyUiConfig.show_all_game_systems() else "Off") + " >", + value_text="< " + Language.on_off_label(PyUiConfig.show_all_game_systems()) + " >", image_path=None, image_path_selected=None, description=None, @@ -108,7 +110,7 @@ def build_options_list(self): option_list.append( GridOrListEntry( primary_text=Language.game_system_sorting(), - value_text="< " + PyUiConfig.game_system_sort_mode()+ " >", + value_text="< " + Language.game_system_sort_mode_label(PyUiConfig.game_system_sort_mode()) + " >", image_path=None, image_path_selected=None, description=None, diff --git a/App/PyUI/main-ui/menus/settings/language_menu.py b/App/PyUI/main-ui/menus/settings/language_menu.py index c7723a3bc..6dad355ec 100644 --- a/App/PyUI/main-ui/menus/settings/language_menu.py +++ b/App/PyUI/main-ui/menus/settings/language_menu.py @@ -3,6 +3,7 @@ import os import sys from controller.controller_inputs import ControllerInput +from menus.language.language import Language from utils.logger import PyUiLogger from views.grid_or_list_entry import GridOrListEntry from views.selection import Selection @@ -34,7 +35,7 @@ def ask_user_for_language(self): try: options.append( GridOrListEntry( - primary_text=language, + primary_text=Language.get_display_name_for_file(language), image_path=None, image_path_selected=None, description=None, @@ -48,7 +49,7 @@ def ask_user_for_language(self): view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Language", + top_bar_text=Language.language_settings(), options=options, selected_index=selected.get_index(), ) diff --git a/App/PyUI/main-ui/menus/settings/list_of_options_selection_menu.py b/App/PyUI/main-ui/menus/settings/list_of_options_selection_menu.py index ca4f3591b..efef4cc97 100644 --- a/App/PyUI/main-ui/menus/settings/list_of_options_selection_menu.py +++ b/App/PyUI/main-ui/menus/settings/list_of_options_selection_menu.py @@ -1,5 +1,6 @@ from controller.controller_inputs import ControllerInput +from menus.language.language import Language from views.grid_or_list_entry import GridOrListEntry from views.selection import Selection from views.view_creator import ViewCreator @@ -10,19 +11,17 @@ class ListOfOptionsSelectionMenu: def __init__(self): pass - def get_selected_option_index(self, options, title, default_index=0): + def get_selected_option_index(self, options, title, default_index=0, label_func=None): selected = Selection(None, None, default_index) option_list = [] for index, opt in enumerate(options): option_list.append( GridOrListEntry( - primary_text=opt, + primary_text=label_func(opt) if label_func else opt, value=index ) ) - #convert to text and desc and show the theme desc - #maybe preview too if theyre common view = ViewCreator.create_view( view_type=ViewType.TEXT_ONLY, top_bar_text=title, diff --git a/App/PyUI/main-ui/menus/settings/modes_menu.py b/App/PyUI/main-ui/menus/settings/modes_menu.py index 60a02dbe5..cc977d4bd 100644 --- a/App/PyUI/main-ui/menus/settings/modes_menu.py +++ b/App/PyUI/main-ui/menus/settings/modes_menu.py @@ -15,10 +15,10 @@ def __init__(self): def prompt_games_only_mode(self,input): if(ControllerInput.A == input): - if UserPrompt.prompt_yes_no("Game Selection Only Mode", - ["Would you like to enter game selection only mode?", - "Boot straight into the game selection screen", - "To exit enter the Konami Code", + if UserPrompt.prompt_yes_no(Language.label("gameOnlyModeTitle", "Game Selection Only Mode"), + [Language.label("gameOnlyModePrompt1", "Would you like to enter game selection only mode?"), + Language.label("gameOnlyModePrompt2", "Boot straight into the game selection screen"), + Language.label("gameOnlyModePrompt3", "To exit enter the Konami Code"), "↑↑↓↓←→←→BA,START,SELECT"]): Device.get_device().get_system_config().set_game_selection_only_mode_enabled(True) Device.get_device().exit_pyui() @@ -27,10 +27,10 @@ def prompt_games_only_mode(self,input): def prompt_simple_mode(self,input): if(ControllerInput.A == input): - if UserPrompt.prompt_yes_no("Simple Mode", - ["Would you like to enter simple mode?", - "It has restricted access to settings", - "To exit enter the Konami Code", + if UserPrompt.prompt_yes_no(Language.label("simpleModeTitle", "Simple Mode"), + [Language.label("simpleModePrompt1", "Would you like to enter simple mode?"), + Language.label("simpleModePrompt2", "It has restricted access to settings"), + Language.label("simpleModePrompt3", "To exit enter the Konami Code"), "↑↑↓↓←→←→BA,START,SELECT"]): Device.get_device().get_system_config().set_simple_mode_enabled(True) Device.get_device().exit_pyui() diff --git a/App/PyUI/main-ui/menus/settings/settings_menu.py b/App/PyUI/main-ui/menus/settings/settings_menu.py index 2390387cb..568b973e5 100644 --- a/App/PyUI/main-ui/menus/settings/settings_menu.py +++ b/App/PyUI/main-ui/menus/settings/settings_menu.py @@ -2,6 +2,7 @@ from controller.controller_inputs import ControllerInput from devices.device import Device from display.on_screen_keyboard import OnScreenKeyboard +from menus.language.language import Language from utils.cfw_system_config import CfwSystemConfig from utils.logger import PyUiLogger from views.grid_or_list_entry import GridOrListEntry @@ -39,7 +40,7 @@ def show_menu(self) : if(list_view is None or self.theme_changed): list_view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Settings", + top_bar_text=Language.settings(), options=option_list, selected_index=selected.get_index()) self.theme_changed = False @@ -94,7 +95,7 @@ def change_indexed_array_option_for_menu_options_list(self, category, entry_name if(selected_index == len(all_options)): selected_index = 0 elif(ControllerInput.A == input): - selected_index = self.get_selected_index(f"Select a {entry_name}", all_options) + selected_index = self.get_selected_index(Language.select_option_prompt(entry_name), all_options) PyUiLogger.get_logger().info(f"{current_value} is updated to index {selected_index}") @@ -113,8 +114,10 @@ def build_options_list_from_config_menu_options(self, category): menu_options = CfwSystemConfig.get_menu_options(category=category) for name, option in menu_options.items(): - display_name = option.get('display') - description = self.replace_dynamic_text_in_description(option.get('description')) + display_name = Language.menu_option_display(option.get('display')) + description = self.replace_dynamic_text_in_description( + Language.menu_option_description(option.get('description')) + ) devices = option.get('devices') supported_device = not devices or Device.get_device().get_device_name() in devices if(supported_device): @@ -142,7 +145,7 @@ def build_options_list_from_config_menu_options(self, category): option_list.append( GridOrListEntry( primary_text=display_name, - value_text="< " + selected_value + " >", + value_text="< " + Language.menu_option_value(selected_value) + " >", image_path=None, image_path_selected=None, description=description, @@ -187,7 +190,7 @@ def build_enabled_disabled_entry(self, primary_text, get_value_func, set_value_f return GridOrListEntry( primary_text=primary_text, - value_text="< " + str(get_value_func()) + " >", + value_text="< " + Language.boolean_label(get_value_func()) + " >", image_path=None, image_path_selected=None, description=None, @@ -226,7 +229,7 @@ def build_view_type_entry(self, primary_text, get_value_func, set_value_func) -> return GridOrListEntry( primary_text=primary_text, - value_text="< " + str(get_value_func().name) + " >", + value_text="< " + Language.view_type_label(get_value_func()) + " >", image_path=None, image_path_selected=None, description=None, @@ -247,11 +250,20 @@ def change_view_type(self, input, get_view_type_func, set_view_type_func): set_view_type_func(next_view_type) - def build_enum_entry(self, primary_text, get_value_func, set_value_func, get_next_enum_type) -> GridOrListEntry: + def build_enum_entry(self, primary_text, get_value_func, set_value_func, get_next_enum_type, enum_section=None) -> GridOrListEntry: + current = get_value_func() + if enum_section == "resizeTypes": + display_value = Language.resize_type_label(current) + elif enum_section == "viewTypes": + display_value = Language.view_type_label(current) + elif enum_section: + display_value = Language.enum_label(enum_section, current.name) + else: + display_value = current.name return GridOrListEntry( primary_text=primary_text, - value_text="< " + str(get_value_func().name) + " >", + value_text="< " + display_value + " >", image_path=None, image_path_selected=None, description=None, diff --git a/App/PyUI/main-ui/menus/settings/tasks_menu.py b/App/PyUI/main-ui/menus/settings/tasks_menu.py index 13124783e..bdaa4b999 100644 --- a/App/PyUI/main-ui/menus/settings/tasks_menu.py +++ b/App/PyUI/main-ui/menus/settings/tasks_menu.py @@ -38,7 +38,7 @@ def build_options_list(self): primary_text=Language.download_boxart(), image_path=None, image_path_selected=None, - description="Scan entire library for missing boxart", + description=Language.label("downloadBoxartDesc", "Scan entire library for missing boxart"), icon=None, value=self.scrape_box_art ) @@ -51,7 +51,7 @@ def build_options_list(self): value_text=None, image_path=None, image_path_selected=None, - description="Resize boxart and convert to QOI for faster loading", + description=Language.label("optimizeBoxartDesc", "Resize boxart and convert to QOI for faster loading"), icon=None, value=self.resize_boxart ) @@ -63,7 +63,7 @@ def build_options_list(self): value_text=None, image_path=None, image_path_selected=None, - description="Simpler modes for new users or kids", + description=Language.label("lockedDownModesDesc", "Simpler modes for new users or kids"), icon=None, value=self.launch_modes_menu ) diff --git a/App/PyUI/main-ui/menus/settings/theme/theme_settings_fonts.py b/App/PyUI/main-ui/menus/settings/theme/theme_settings_fonts.py index 447c20dd3..48f222b5a 100644 --- a/App/PyUI/main-ui/menus/settings/theme/theme_settings_fonts.py +++ b/App/PyUI/main-ui/menus/settings/theme/theme_settings_fonts.py @@ -1,6 +1,7 @@ from display.display import Display from display.font_purpose import FontPurpose +from menus.language.language import Language from menus.settings.theme.theme_settings_menu_common import ThemeSettingsMenuCommon from themes.theme import Theme from views.grid_or_list_entry import GridOrListEntry @@ -20,7 +21,7 @@ def build_options_list(self) -> list[GridOrListEntry]: if(FontPurpose.ON_SCREEN_KEYBOARD != purpose): option_list.append( self.build_numeric_entry( - primary_text=purpose.name + " Size", + primary_text=Language.font_purpose_size_label(purpose), get_value_func=lambda font_purpose=purpose : Theme.get_font_size(font_purpose), set_value_func=lambda size, font_purpose=purpose : Theme.set_font_size(font_purpose,size) ) diff --git a/App/PyUI/main-ui/menus/settings/theme/theme_settings_game_select_menu.py b/App/PyUI/main-ui/menus/settings/theme/theme_settings_game_select_menu.py index dca907f70..5f2ec3180 100644 --- a/App/PyUI/main-ui/menus/settings/theme/theme_settings_game_select_menu.py +++ b/App/PyUI/main-ui/menus/settings/theme/theme_settings_game_select_menu.py @@ -36,7 +36,8 @@ def build_grid_specific_options(self): primary_text=Language.img_mode(), get_value_func=Theme.get_grid_game_selected_resize_type, set_value_func=Theme.set_grid_game_selected_resize_type, - get_next_enum_type=get_next_resize_type + get_next_enum_type=get_next_resize_type, + enum_section="resizeTypes" ) ) option_list.append( @@ -68,18 +69,18 @@ def build_grid_specific_options(self): ) ) option_list.append( - self.build_enabled_disabled_entry("Show Text", + self.build_enabled_disabled_entry(Language.show_text(), Theme.get_game_select_show_text_grid_mode, Theme.set_game_select_show_text_grid_mode) ) option_list.append( - self.build_enabled_disabled_entry("Show Sel BG", + self.build_enabled_disabled_entry(Language.label("showSelBg", "Show Sel BG"), Theme.get_game_select_show_sel_bg_grid_mode, Theme.set_game_select_show_sel_bg_grid_mode) ) option_list.append( - self.build_enabled_disabled_entry("TopBar = GameName", + self.build_enabled_disabled_entry(Language.topbar_gamename(), Theme.get_set_top_bar_text_to_game_selection, Theme.set_set_top_bar_text_to_game_selection) ) @@ -110,7 +111,7 @@ def build_carousel_specific_options(self): ) option_list.append( self.build_enabled_disabled_entry( - "TopBar = GameName", + Language.topbar_gamename(), Theme.get_set_top_bar_text_to_game_selection, Theme.set_set_top_bar_text_to_game_selection) ) @@ -126,7 +127,7 @@ def build_carousel_specific_options(self): if (not Theme.get_carousel_game_select_shrink_further_away()): option_list.append( self.build_enabled_disabled_entry( - "Sides Hang Off", + Language.label("sidesHangOff", "Sides Hang Off"), Theme.get_carousel_game_select_sides_hang_off, Theme.set_carousel_game_select_sides_hang_off) ) @@ -136,7 +137,7 @@ def build_full_screen_grid_specific_options(self): option_list = [] option_list.append( self.build_enabled_disabled_entry( - "TopBar = GameName", + Language.topbar_gamename(), Theme.get_set_top_bar_text_to_game_selection, Theme.set_set_top_bar_text_to_game_selection) ) @@ -146,7 +147,8 @@ def build_full_screen_grid_specific_options(self): primary_text=Language.boxart_resize_type(), get_value_func=Theme.get_full_screen_grid_game_menu_resize_type, set_value_func=Theme.set_full_screen_grid_game_menu_resize_type, - get_next_enum_type=get_next_resize_type + get_next_enum_type=get_next_resize_type, + enum_section="resizeTypes" ) ) diff --git a/App/PyUI/main-ui/menus/settings/theme/theme_settings_grid_view.py b/App/PyUI/main-ui/menus/settings/theme/theme_settings_grid_view.py index d8061e322..2d5bb2e55 100644 --- a/App/PyUI/main-ui/menus/settings/theme/theme_settings_grid_view.py +++ b/App/PyUI/main-ui/menus/settings/theme/theme_settings_grid_view.py @@ -55,7 +55,7 @@ def build_options_list(self) -> list[GridOrListEntry]: ) ) option_list.append( - self.build_enabled_disabled_entry("Set Grid BG Offset To Image Offset", + self.build_enabled_disabled_entry(Language.label("setGridBgOffsetToImageOffset", "Set Grid BG Offset To Image Offset"), Theme.grid_bg_offset_to_image_offset, Theme.set_grid_bg_offset_to_image_offset) ) diff --git a/App/PyUI/main-ui/menus/settings/theme/theme_settings_main_menu.py b/App/PyUI/main-ui/menus/settings/theme/theme_settings_main_menu.py index 27ff7ab6c..dedb43632 100644 --- a/App/PyUI/main-ui/menus/settings/theme/theme_settings_main_menu.py +++ b/App/PyUI/main-ui/menus/settings/theme/theme_settings_main_menu.py @@ -17,7 +17,7 @@ def __init__(self): def main_menu_title(self, input_value): if(ControllerInput.A == input_value): new_title = self.on_screen_keyboard.get_input( - "Main Menu Title", + Language.label("mainMenuTitle", "Main Menu Title"), Theme.get_main_menu_title() ) if(new_title is not None): @@ -85,8 +85,8 @@ def build_options_list(self) -> list[GridOrListEntry]: ) ) option_list.append( - self.build_enabled_disabled_entry("Wrap-Around", - Theme.get_main_menu_grid_wrap_around_single_row, + self.build_enabled_disabled_entry(Language.label("wrapAround", "Wrap-Around"), + Theme.get_main_menu_grid_wrap_around_single_row, Theme.set_main_menu_grid_wrap_around_single_row) ) option_list.append( @@ -153,7 +153,7 @@ def build_options_list(self) -> list[GridOrListEntry]: ) option_list.append( GridOrListEntry( - primary_text="Main Menu Title", + primary_text=Language.label("mainMenuTitle", "Main Menu Title"), value_text=Theme.get_main_menu_title(), value=self.main_menu_title ) diff --git a/App/PyUI/main-ui/menus/settings/theme/theme_settings_menu.py b/App/PyUI/main-ui/menus/settings/theme/theme_settings_menu.py index 4065e358a..210accba0 100644 --- a/App/PyUI/main-ui/menus/settings/theme/theme_settings_menu.py +++ b/App/PyUI/main-ui/menus/settings/theme/theme_settings_menu.py @@ -132,7 +132,7 @@ def show_theme_options_menu(self): if (list_view is None or self.theme_changed): list_view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Settings", + top_bar_text=Language.settings(), options=option_list, selected_index=selected.get_index()) self.theme_changed = False diff --git a/App/PyUI/main-ui/menus/settings/theme/theme_settings_menu_common.py b/App/PyUI/main-ui/menus/settings/theme/theme_settings_menu_common.py index 5ddf7b4e9..60d80dd4a 100644 --- a/App/PyUI/main-ui/menus/settings/theme/theme_settings_menu_common.py +++ b/App/PyUI/main-ui/menus/settings/theme/theme_settings_menu_common.py @@ -1,5 +1,6 @@ +from menus.language.language import Language from abc import abstractmethod from controller.controller_inputs import ControllerInput from menus.settings.settings_menu import SettingsMenu @@ -33,7 +34,7 @@ def show_theme_options_menu(self): reload_view = False list_view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Settings", + top_bar_text=Language.settings(), options=option_list, selected_index=selected.get_index()) self.theme_changed = False diff --git a/App/PyUI/main-ui/menus/settings/theme/theme_settings_system_select_menu.py b/App/PyUI/main-ui/menus/settings/theme/theme_settings_system_select_menu.py index 2f3e0c103..ac7b7be00 100644 --- a/App/PyUI/main-ui/menus/settings/theme/theme_settings_system_select_menu.py +++ b/App/PyUI/main-ui/menus/settings/theme/theme_settings_system_select_menu.py @@ -1,216 +1,221 @@ - -from display.resize_type import get_next_resize_type -from menus.language.language import Language -from menus.settings.theme.theme_settings_menu_common import ThemeSettingsMenuCommon -from themes.theme import Theme -from views.grid_or_list_entry import GridOrListEntry -from views.view_type import ViewType - - -class ThemeSettingsSystemSelectMenu(ThemeSettingsMenuCommon): - def __init__(self): - super().__init__() - - def build_options_list(self) -> list[GridOrListEntry]: - option_list = [] - option_list.append( - self.build_view_type_entry("View Type", - Theme.get_view_type_for_system_select_menu, - Theme.set_view_type_for_system_select_menu) - ) - - if(ViewType.TEXT_AND_IMAGE == Theme.get_view_type_for_system_select_menu()): - option_list.append( - self.build_numeric_entry( - primary_text=Language.img_width(), - get_value_func=Theme.get_list_system_select_img_width, - set_value_func=Theme.set_list_system_select_img_width - ) - ) - option_list.append( - self.build_numeric_entry( - primary_text=Language.img_height(), - get_value_func=Theme.get_list_system_select_img_height, - set_value_func=Theme.set_list_system_select_img_height - ) - ) - if(ViewType.CAROUSEL == Theme.get_view_type_for_system_select_menu()): - option_list.append( - self.build_numeric_entry("Columns", - Theme.get_game_system_select_carousel_col_count, - Theme.set_game_system_select_carousel_col_count) - ) - - if(ViewType.GRID == Theme.get_view_type_for_system_select_menu()): - option_list.append( - self.build_numeric_entry("Columns", - Theme.get_game_system_select_col_count, - Theme.set_game_system_select_col_count) - ) - option_list.append( - self.build_numeric_entry("Rows", - Theme.get_game_system_select_row_count, - Theme.set_game_system_select_row_count) - ) - option_list.append( - self.build_enabled_disabled_entry("Show Text", - Theme.get_system_select_show_text_grid_mode, - Theme.set_system_select_show_text_grid_mode) - ) - option_list.append( - self.build_enabled_disabled_entry("Show Sel BG", - Theme.get_system_select_show_sel_bg_grid_mode, - Theme.set_system_select_show_sel_bg_grid_mode) - ) - option_list.append( - self.build_enum_entry( - primary_text=Language.img_mode(), - get_value_func=Theme.get_grid_system_selected_resize_type, - set_value_func=Theme.set_grid_system_selected_resize_type, - get_next_enum_type=get_next_resize_type - ) - ) - option_list.append( - self.build_numeric_entry( - primary_text=Language.img_width(), - get_value_func=Theme.get_grid_system_select_img_width, - set_value_func=Theme.set_grid_system_select_img_width - ) - ) - option_list.append( - self.build_numeric_entry( - primary_text=Language.img_height(), - get_value_func=Theme.get_grid_system_select_img_height, - set_value_func=Theme.set_grid_system_select_img_height - ) - ) - if(Theme.get_game_system_select_row_count() == 1): - option_list.append( - self.build_enabled_disabled_entry("Wrap-Around", - Theme.get_system_select_grid_wrap_around_single_row, - Theme.set_system_select_grid_wrap_around_single_row) - ) - - if(ViewType.FULLSCREEN_GRID == Theme.get_view_type_for_system_select_menu()): - option_list.append( - self.build_enabled_disabled_entry("Show Text", - Theme.get_system_select_render_full_screen_grid_text_overlay, - Theme.set_system_select_render_full_screen_grid_text_overlay) - ) - option_list.append( - self.build_enum_entry( - primary_text="Resize Type", - get_value_func=Theme.get_full_screen_grid_system_select_menu_resize_type, - set_value_func=Theme.set_full_screen_grid_system_select_menu_resize_type, - get_next_enum_type=get_next_resize_type - ) - ) - - if(ViewType.CAROUSEL == Theme.get_view_type_for_system_select_menu()): - option_list.append( - self.build_enabled_disabled_entry( - primary_text="Percentage Mode", - get_value_func=Theme.get_carousel_system_use_percentage_mode, - set_value_func=Theme.set_carousel_system_use_percentage_mode - ) - ) - option_list.append( - self.build_enabled_disabled_entry("Vertical Carousel", - Theme.get_system_select_use_vertical_carousel, - Theme.set_system_select_use_vertical_carousel) - ) - if(Theme.get_carousel_system_use_percentage_mode()): - option_list.append( - self.build_percent_entry( - primary_text=Language.prim_img_width(), - get_value_func=Theme.get_carousel_system_select_primary_img_width, - set_value_func=Theme.set_carousel_system_select_primary_img_width - ) - ) - if (Theme.get_game_system_select_carousel_col_count() > 3): - option_list.append( - self.build_enabled_disabled_entry( - primary_text=Language.shrink_further_away(), - get_value_func=Theme.get_carousel_system_select_shrink_further_away, - set_value_func=Theme.set_carousel_system_select_shrink_further_away - ) - ) - - if (not Theme.get_carousel_system_select_shrink_further_away()): - option_list.append( - self.build_enabled_disabled_entry( - "Sides Hang Off", - Theme.get_carousel_system_select_sides_hang_off, - Theme.set_carousel_system_select_sides_hang_off) - ) - else: - option_list.append( - self.build_numeric_entry("Height" if Theme.get_system_select_use_vertical_carousel() else "Width", - Theme.get_carousel_system_fixed_width, - Theme.set_carousel_system_fixed_width) - ) - option_list.append( - self.build_numeric_entry("Selected Height" if Theme.get_system_select_use_vertical_carousel() else "Selected Width", - Theme.get_carousel_system_fixed_selected_width, - Theme.set_carousel_system_fixed_selected_width) - ) - - # Should restrict NONE if in use percentage mode but oh well - option_list.append( - self.build_enum_entry( - primary_text="Image Resize Type", - get_value_func=Theme.get_carousel_system_resize_type, - set_value_func=Theme.set_carousel_system_resize_type, - get_next_enum_type=get_next_resize_type - ) - ) - - - option_list.append( - self.build_numeric_entry("Internal Padding", - Theme.get_carousel_system_x_pad, - Theme.set_carousel_system_x_pad, - min=0) - ) - option_list.append( - self.build_numeric_entry("Y-Offset", - Theme.get_carousel_system_additional_y_offset, - Theme.set_carousel_system_additional_y_offset, - min=0) - ) - option_list.append( - self.build_numeric_entry("Selected Icon Position Offset", - Theme.get_carousel_system_selected_offset, - Theme.set_carousel_system_selected_offset, - min=0) - ) - option_list.append( - self.build_enabled_disabled_entry("Use Selected Image In Animation", - Theme.get_carousel_use_selected_image_in_animation, - Theme.set_carousel_use_selected_image_in_animation) - ) - - - - option_list.append( - self.build_numeric_entry("X-Offset", - Theme.get_carousel_system_external_x_offset, - Theme.set_carousel_system_external_x_offset, - min=-99999) - ) - - if(ViewType.CAROUSEL == Theme.get_view_type_for_system_select_menu() or ViewType.CAROUSEL == Theme.get_view_type_for_system_select_menu()): - option_list.append( - self.build_enabled_disabled_entry( - "Set Top Bar Text", - Theme.get_system_selection_set_top_bar_text, - Theme.set_system_selection_set_top_bar_text) - ) - option_list.append( - self.build_enabled_disabled_entry( - "Set Bottom Bar Text", - Theme.get_system_selection_set_bottom_bar_text, - Theme.set_system_selection_set_bottom_bar_text) - ) - - return option_list + +from display.resize_type import get_next_resize_type +from menus.language.language import Language +from menus.settings.theme.theme_settings_menu_common import ThemeSettingsMenuCommon +from themes.theme import Theme +from views.grid_or_list_entry import GridOrListEntry +from views.view_type import ViewType + + +class ThemeSettingsSystemSelectMenu(ThemeSettingsMenuCommon): + def __init__(self): + super().__init__() + + def build_options_list(self) -> list[GridOrListEntry]: + option_list = [] + option_list.append( + self.build_view_type_entry(Language.view_type(), + Theme.get_view_type_for_system_select_menu, + Theme.set_view_type_for_system_select_menu) + ) + + if(ViewType.TEXT_AND_IMAGE == Theme.get_view_type_for_system_select_menu()): + option_list.append( + self.build_numeric_entry( + primary_text=Language.img_width(), + get_value_func=Theme.get_list_system_select_img_width, + set_value_func=Theme.set_list_system_select_img_width + ) + ) + option_list.append( + self.build_numeric_entry( + primary_text=Language.img_height(), + get_value_func=Theme.get_list_system_select_img_height, + set_value_func=Theme.set_list_system_select_img_height + ) + ) + if(ViewType.CAROUSEL == Theme.get_view_type_for_system_select_menu()): + option_list.append( + self.build_numeric_entry(Language.cols(), + Theme.get_game_system_select_carousel_col_count, + Theme.set_game_system_select_carousel_col_count) + ) + + if(ViewType.GRID == Theme.get_view_type_for_system_select_menu()): + option_list.append( + self.build_numeric_entry(Language.cols(), + Theme.get_game_system_select_col_count, + Theme.set_game_system_select_col_count) + ) + option_list.append( + self.build_numeric_entry(Language.rows(), + Theme.get_game_system_select_row_count, + Theme.set_game_system_select_row_count) + ) + option_list.append( + self.build_enabled_disabled_entry(Language.show_text(), + Theme.get_system_select_show_text_grid_mode, + Theme.set_system_select_show_text_grid_mode) + ) + option_list.append( + self.build_enabled_disabled_entry(Language.label("showSelBg", "Show Sel BG"), + Theme.get_system_select_show_sel_bg_grid_mode, + Theme.set_system_select_show_sel_bg_grid_mode) + ) + option_list.append( + self.build_enum_entry( + primary_text=Language.img_mode(), + get_value_func=Theme.get_grid_system_selected_resize_type, + set_value_func=Theme.set_grid_system_selected_resize_type, + get_next_enum_type=get_next_resize_type, + enum_section="resizeTypes" + ) + ) + option_list.append( + self.build_numeric_entry( + primary_text=Language.img_width(), + get_value_func=Theme.get_grid_system_select_img_width, + set_value_func=Theme.set_grid_system_select_img_width + ) + ) + option_list.append( + self.build_numeric_entry( + primary_text=Language.img_height(), + get_value_func=Theme.get_grid_system_select_img_height, + set_value_func=Theme.set_grid_system_select_img_height + ) + ) + if(Theme.get_game_system_select_row_count() == 1): + option_list.append( + self.build_enabled_disabled_entry(Language.label("wrapAround", "Wrap-Around"), + Theme.get_system_select_grid_wrap_around_single_row, + Theme.set_system_select_grid_wrap_around_single_row) + ) + + if(ViewType.FULLSCREEN_GRID == Theme.get_view_type_for_system_select_menu()): + option_list.append( + self.build_enabled_disabled_entry(Language.show_text(), + Theme.get_system_select_render_full_screen_grid_text_overlay, + Theme.set_system_select_render_full_screen_grid_text_overlay) + ) + option_list.append( + self.build_enum_entry( + primary_text=Language.label("resizeType", "Resize Type"), + get_value_func=Theme.get_full_screen_grid_system_select_menu_resize_type, + set_value_func=Theme.set_full_screen_grid_system_select_menu_resize_type, + get_next_enum_type=get_next_resize_type, + enum_section="resizeTypes" + ) + ) + + if(ViewType.CAROUSEL == Theme.get_view_type_for_system_select_menu()): + option_list.append( + self.build_enabled_disabled_entry( + primary_text=Language.label("percentageMode", "Percentage Mode"), + get_value_func=Theme.get_carousel_system_use_percentage_mode, + set_value_func=Theme.set_carousel_system_use_percentage_mode + ) + ) + option_list.append( + self.build_enabled_disabled_entry(Language.label("verticalCarousel", "Vertical Carousel"), + Theme.get_system_select_use_vertical_carousel, + Theme.set_system_select_use_vertical_carousel) + ) + if(Theme.get_carousel_system_use_percentage_mode()): + option_list.append( + self.build_percent_entry( + primary_text=Language.prim_img_width(), + get_value_func=Theme.get_carousel_system_select_primary_img_width, + set_value_func=Theme.set_carousel_system_select_primary_img_width + ) + ) + if (Theme.get_game_system_select_carousel_col_count() > 3): + option_list.append( + self.build_enabled_disabled_entry( + primary_text=Language.shrink_further_away(), + get_value_func=Theme.get_carousel_system_select_shrink_further_away, + set_value_func=Theme.set_carousel_system_select_shrink_further_away + ) + ) + + if (not Theme.get_carousel_system_select_shrink_further_away()): + option_list.append( + self.build_enabled_disabled_entry( + Language.label("sidesHangOff", "Sides Hang Off"), + Theme.get_carousel_system_select_sides_hang_off, + Theme.set_carousel_system_select_sides_hang_off) + ) + else: + option_list.append( + self.build_numeric_entry( + Language.label("height", "Height") if Theme.get_system_select_use_vertical_carousel() else Language.label("width", "Width"), + Theme.get_carousel_system_fixed_width, + Theme.set_carousel_system_fixed_width) + ) + option_list.append( + self.build_numeric_entry( + Language.label("selectedHeight", "Selected Height") if Theme.get_system_select_use_vertical_carousel() else Language.label("selectedWidth", "Selected Width"), + Theme.get_carousel_system_fixed_selected_width, + Theme.set_carousel_system_fixed_selected_width) + ) + + # Should restrict NONE if in use percentage mode but oh well + option_list.append( + self.build_enum_entry( + primary_text=Language.label("imageResizeType", "Image Resize Type"), + get_value_func=Theme.get_carousel_system_resize_type, + set_value_func=Theme.set_carousel_system_resize_type, + get_next_enum_type=get_next_resize_type, + enum_section="resizeTypes" + ) + ) + + + option_list.append( + self.build_numeric_entry(Language.label("internalPadding", "Internal Padding"), + Theme.get_carousel_system_x_pad, + Theme.set_carousel_system_x_pad, + min=0) + ) + option_list.append( + self.build_numeric_entry(Language.label("yOffset", "Y-Offset"), + Theme.get_carousel_system_additional_y_offset, + Theme.set_carousel_system_additional_y_offset, + min=0) + ) + option_list.append( + self.build_numeric_entry(Language.label("selectedIconPositionOffset", "Selected Icon Position Offset"), + Theme.get_carousel_system_selected_offset, + Theme.set_carousel_system_selected_offset, + min=0) + ) + option_list.append( + self.build_enabled_disabled_entry(Language.label("useSelectedImageInAnimation", "Use Selected Image In Animation"), + Theme.get_carousel_use_selected_image_in_animation, + Theme.set_carousel_use_selected_image_in_animation) + ) + + + + option_list.append( + self.build_numeric_entry(Language.label("xOffset", "X-Offset"), + Theme.get_carousel_system_external_x_offset, + Theme.set_carousel_system_external_x_offset, + min=-99999) + ) + + if(ViewType.CAROUSEL == Theme.get_view_type_for_system_select_menu() or ViewType.CAROUSEL == Theme.get_view_type_for_system_select_menu()): + option_list.append( + self.build_enabled_disabled_entry( + Language.label("setTopBarText", "Set Top Bar Text"), + Theme.get_system_selection_set_top_bar_text, + Theme.set_system_selection_set_top_bar_text) + ) + option_list.append( + self.build_enabled_disabled_entry( + Language.label("setBottomBarText", "Set Bottom Bar Text"), + Theme.get_system_selection_set_bottom_bar_text, + Theme.set_system_selection_set_bottom_bar_text) + ) + + return option_list diff --git a/App/PyUI/main-ui/menus/settings/theme/theme_settings_top_bar.py b/App/PyUI/main-ui/menus/settings/theme/theme_settings_top_bar.py index e73e46509..738395ba6 100644 --- a/App/PyUI/main-ui/menus/settings/theme/theme_settings_top_bar.py +++ b/App/PyUI/main-ui/menus/settings/theme/theme_settings_top_bar.py @@ -26,31 +26,31 @@ def build_options_list(self) -> list[GridOrListEntry]: ) option_list.append( - self.build_enabled_disabled_entry("Show Battery Percent", + self.build_enabled_disabled_entry(Language.label("showBatteryPercent", "Show Battery Percent"), Theme.display_battery_percent, Theme.set_display_battery_percent) ) option_list.append( - self.build_enabled_disabled_entry("Show Battery Icon", + self.build_enabled_disabled_entry(Language.label("showBatteryIcon", "Show Battery Icon"), Theme.display_battery_icon, Theme.set_display_battery_icon) ) option_list.append( - self.build_enabled_disabled_entry("Show Volume Numbers", + self.build_enabled_disabled_entry(Language.label("showVolumeNumbers", "Show Volume Numbers"), Theme.display_volume_numbers, Theme.set_display_volume_numbers) ) option_list.append( - self.build_enabled_disabled_entry("Show Bottom Bar Buttons", + self.build_enabled_disabled_entry(Language.label("showBottomBarButtons", "Show Bottom Bar Buttons"), Theme.show_bottom_bar_buttons, Theme.set_show_bottom_bar_buttons) ) option_list.append( - self.build_enabled_disabled_entry("Show Clock", + self.build_enabled_disabled_entry(Language.label("showClock", "Show Clock"), Theme.show_clock, Theme.set_show_clock) ) diff --git a/App/PyUI/main-ui/menus/settings/timezone_menu.py b/App/PyUI/main-ui/menus/settings/timezone_menu.py index 4e77c044c..2178c7cfc 100644 --- a/App/PyUI/main-ui/menus/settings/timezone_menu.py +++ b/App/PyUI/main-ui/menus/settings/timezone_menu.py @@ -5,6 +5,7 @@ from zoneinfo import ZoneInfo from controller.controller_inputs import ControllerInput from utils.logger import PyUiLogger +from menus.language.language import Language from views.grid_or_list_entry import GridOrListEntry from views.selection import Selection from views.view_creator import ViewCreator @@ -74,7 +75,7 @@ def ask_user_for_timezone(self,timezone_entries): view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Timezone", + top_bar_text=Language.set_timezone(), options=options, selected_index=selected.get_index(), ) diff --git a/App/PyUI/main-ui/menus/settings/wifi_menu.py b/App/PyUI/main-ui/menus/settings/wifi_menu.py index c432c7f60..10645b765 100644 --- a/App/PyUI/main-ui/menus/settings/wifi_menu.py +++ b/App/PyUI/main-ui/menus/settings/wifi_menu.py @@ -116,12 +116,17 @@ def reload_wpa_supplicant_config(self): def switch_network(self, net: WiFiNetwork): PyUiLogger.get_logger().info(f"Selected {net.ssid}!") if(net.requires_password()): - password = self.on_screen_keyboard.get_input("WiFi Password") + password = self.on_screen_keyboard.get_input(Language.label("wifiPassword", "WiFi Password")) if(password is not None and 8 <= len(password) <= 63): self.write_wpa_supplicant_conf(net.ssid, "psk=\""+password+"\"") - Display.display_message(f"Updating config file for {net.ssid} with password {password}", duration_ms=5000) + Display.display_message( + Language.label("updatingWifiConfig", "Updating config file for {ssid} with password {password}") + .replace("{ssid}", net.ssid) + .replace("{password}", password), + duration_ms=5000, + ) else: - Display.display_message("Invalid WiFi password length! Must be between 8 and 63", duration_ms=5000) + Display.display_message(Language.label("invalidWifiPasswordLength", "Invalid WiFi password length! Must be between 8 and 63"), duration_ms=5000) else: self.write_wpa_supplicant_conf(net.ssid, "key_mgmt=NONE") @@ -140,7 +145,7 @@ def _build_options( option_list.append( GridOrListEntry( primary_text=Language.status(), - value_text="< " + ("On" if wifi_enabled else "Off") + " >", + value_text="< " + Language.on_off_label(wifi_enabled) + " >", image_path=None, image_path_selected=None, description=None, @@ -154,7 +159,7 @@ def _build_options( if not networks: option_list.append( GridOrListEntry( - primary_text="Scanning for networks...", + primary_text=Language.label("scanningForNetworks", "Scanning for networks..."), value_text=None, image_path=None, image_path_selected=None, @@ -241,7 +246,7 @@ def show_wifi_menu(self): # Render view list_view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="WiFi Configuration", + top_bar_text=Language.label("wifiConfiguration", "WiFi Configuration"), options=option_list, selected_index=selected.get_index(), ) @@ -261,5 +266,5 @@ def show_wifi_menu(self): time.sleep(0.05) finally: - Display.display_message("Stopping WiFi scanner...") + Display.display_message(Language.label("stoppingWifiScanner", "Stopping WiFi scanner...")) self.wifi_scanner.stop() diff --git a/App/PyUI/main-ui/themes/theme.py b/App/PyUI/main-ui/themes/theme.py index 882aee798..baae864e6 100644 --- a/App/PyUI/main-ui/themes/theme.py +++ b/App/PyUI/main-ui/themes/theme.py @@ -10,6 +10,7 @@ from display.font_purpose import FontPurpose from display.resize_type import ResizeType from menus.games.utils.daijisho_theme_index import DaijishoThemeIndex +from menus.language.language import Language from themes.theme_patcher import ThemePatcher from utils.logger import PyUiLogger from utils.py_ui_config import PyUiConfig @@ -469,6 +470,10 @@ def get_system_icon_selected(cls, system): @classmethod def get_font(cls, font_purpose : FontPurpose): + language_font = Language.get_font_for_purpose(font_purpose) + if language_font: + return language_font + try: match font_purpose: case FontPurpose.TOP_BAR_TEXT: @@ -510,6 +515,9 @@ def get_font(cls, font_purpose : FontPurpose): @classmethod def get_fallback_font(cls): + language_font = Language.get_font_for_purpose(FontPurpose.LIST) + if language_font: + return language_font base_dir = os.path.abspath(sys.path[0]) return os.path.join(base_dir, "themes", "font.ttf") diff --git a/App/PyUI/main-ui/themes/theme_patcher.py b/App/PyUI/main-ui/themes/theme_patcher.py index 16c806ce2..bbc3c309e 100644 --- a/App/PyUI/main-ui/themes/theme_patcher.py +++ b/App/PyUI/main-ui/themes/theme_patcher.py @@ -35,7 +35,8 @@ def convert_to_qoi(cls, path): return False Display.clear("Patching Theme") - Display.display_message("Patching theme to faster assets") + from menus.language.language import Language + Display.display_message(Language.label("patchingThemeAssets", "Patching theme to faster assets")) for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: if filename.lower().endswith(".png"): diff --git a/App/PyUI/main-ui/utils/activity/activity_tracker.py b/App/PyUI/main-ui/utils/activity/activity_tracker.py index 274e67ad4..70a73ee72 100644 --- a/App/PyUI/main-ui/utils/activity/activity_tracker.py +++ b/App/PyUI/main-ui/utils/activity/activity_tracker.py @@ -4,6 +4,7 @@ from typing import Dict from controller.controller_inputs import ControllerInput from devices.device import Device +from menus.language.language import Language from utils.activity.activity_log import ActivityLog from utils.py_ui_config import PyUiConfig from views.grid_or_list_entry import GridOrListEntry @@ -21,14 +22,14 @@ def run_activity_tracking_app(self): option_list = [] option_list.append( GridOrListEntry( - primary_text="Game Tracking", + primary_text=Language.label("gameTracking", "Game Tracking"), value=lambda input_value,activity_list=activity_log, : self.all_apps_list(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="System Tracking", + primary_text=Language.label("systemTracking", "System Tracking"), value=lambda input_value,activity_list=activity_log, : self.by_system_list(activity_list) ) @@ -38,7 +39,7 @@ def run_activity_tracking_app(self): view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Activity Tracker", + top_bar_text=Language.label("activityTracker", "Activity Tracker"), options=option_list, selected_index=0) @@ -57,42 +58,42 @@ def all_apps_list(self, activity_log : ActivityLog): option_list = [] option_list.append( GridOrListEntry( - primary_text="Today", + primary_text=Language.label("today", "Today"), value=lambda input_value,activity_list=activity_log.all_apps_today(), : self.display_activity_details(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="This Week", + primary_text=Language.label("thisWeek", "This Week"), value=lambda input_value,activity_list=activity_log.all_apps_this_week(), : self.display_activity_details(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="This Month", + primary_text=Language.label("thisMonth", "This Month"), value=lambda input_value,activity_list=activity_log.all_apps_this_month(), : self.display_activity_details(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="This Year", + primary_text=Language.label("thisYear", "This Year"), value=lambda input_value,activity_list=activity_log.all_apps_this_year(), : self.display_activity_details(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="All Time", + primary_text=Language.label("allTime", "All Time"), value=lambda input_value,activity_list=activity_log.all_apps_all_time(), : self.display_activity_details(activity_list) ) ) view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Activity Tracker", + top_bar_text=Language.label("activityTracker", "Activity Tracker"), options=option_list, selected_index=0) @@ -110,35 +111,35 @@ def by_system_list(self, activity_log : ActivityLog): option_list = [] option_list.append( GridOrListEntry( - primary_text="Today", + primary_text=Language.label("today", "Today"), value=lambda input_value,activity_list=activity_log.systems_today(), : self.display_activity_details(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="This Week", + primary_text=Language.label("thisWeek", "This Week"), value=lambda input_value,activity_list=activity_log.systems_this_week(), : self.display_activity_details(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="This Month", + primary_text=Language.label("thisMonth", "This Month"), value=lambda input_value,activity_list=activity_log.systems_this_month(), : self.display_activity_details(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="This Year", + primary_text=Language.label("thisYear", "This Year"), value=lambda input_value,activity_list=activity_log.systems_this_year(), : self.display_activity_details(activity_list) ) ) option_list.append( GridOrListEntry( - primary_text="All Time", + primary_text=Language.label("allTime", "All Time"), value=lambda input_value,activity_list=activity_log.systems_all_time(), : self.display_activity_details(activity_list) ) @@ -146,7 +147,7 @@ def by_system_list(self, activity_log : ActivityLog): view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="System Activity Tracker", + top_bar_text=Language.label("systemActivityTracker", "System Activity Tracker"), options=option_list, selected_index=0) @@ -168,7 +169,10 @@ def display_activity_details(self, activity_list: Dict[str, int]): img = Device.get_device().get_image_for_activity(app) hours = total_seconds // 3600 minutes = (total_seconds % 3600) // 60 - time_str = f"{hours}h {minutes}m" if hours > 0 else f"{minutes}m" + if hours > 0: + time_str = Language.label("activityTimeHoursMinutes", "{hours}h {minutes}m").replace("{hours}", str(hours)).replace("{minutes}", str(minutes)) + else: + time_str = Language.label("activityTimeMinutesOnly", "{minutes}m").replace("{minutes}", str(minutes)) if app.endswith("launch.sh"): primary = app.rsplit("/", 2)[-2] # directory before launch.sh else: @@ -183,7 +187,7 @@ def display_activity_details(self, activity_list: Dict[str, int]): view = ViewCreator.create_view( view_type=ViewType.ICON_AND_DESC, - top_bar_text="Activity Tracker", + top_bar_text=Language.label("activityTracker", "Activity Tracker"), options=option_list, selected_index=0) diff --git a/App/PyUI/main-ui/utils/boxart/box_art_scraper.py b/App/PyUI/main-ui/utils/boxart/box_art_scraper.py index 14d6ec07c..3479bb5f2 100644 --- a/App/PyUI/main-ui/utils/boxart/box_art_scraper.py +++ b/App/PyUI/main-ui/utils/boxart/box_art_scraper.py @@ -13,6 +13,7 @@ from devices.device import Device from display.display import Display from games.utils.box_art_resizer import BoxArtResizer +from menus.language.language import Language from utils.cached_exists import CachedExists from utils.logger import PyUiLogger import re @@ -109,7 +110,7 @@ def is_wifi_connected(self) -> bool: self.log_message("Cloudflare ping successful; device is online.") return True else: - self.log_and_display_message("Cloudflare ping failed; device is offline. Aborting.") + self.log_and_display_message(Language.label("cloudflareOfflineAborting", "Cloudflare ping failed; device is offline. Aborting.")) return False def get_ra_alias(self, system: str) -> str: @@ -305,7 +306,9 @@ def get_image_list_for_system(self, sys_name: str) -> List[str]: """Match ROM to image name based on db/_games.txt.""" image_list_file = self._find_game_list_file(sys_name) if not image_list_file: - self.log_and_display_message(f"Image list file not found for {sys_name}.") + self.log_and_display_message( + Language.label("imageListNotFound", "Image list file not found for {system}.").replace("{system}", sys_name) + ) time.sleep(2) return None @@ -600,15 +603,19 @@ def download_boxart_batch( roms_and_paths: list of (rom_file_name, image_path) """ - self.log_and_display_message("Scraping box art. Please be patient, especially with large libraries!") + self.log_and_display_message(Language.label("scrapingBoxartPatient", "Scraping box art. Please be patient, especially with large libraries!")) if not roms_and_paths: - self.log_and_display_message(f"No roms are missing boxart for {sys_name}.") + self.log_and_display_message( + Language.label("noMissingBoxartForSystem", "No roms are missing boxart for {system}.").replace("{system}", sys_name) + ) time.sleep(2) return ra_name = self.get_ra_alias(sys_name) if not ra_name: - self.log_and_display_message(f"Remote system name not found - unable to download box art for {sys_name}.") + self.log_and_display_message( + Language.label("remoteSystemNotFound", "Remote system name not found - unable to download box art for {system}.").replace("{system}", sys_name) + ) time.sleep(2) return @@ -622,7 +629,11 @@ def download_boxart_batch( for future in as_completed(futures): count = count +1 - self.log_and_display_message(f"Scraping box art... ({count}/{len(roms_and_paths)})") + self.log_and_display_message( + Language.label("scrapingBoxartProgress", "Scraping box art... ({count}/{total})") + .replace("{count}", str(count)) + .replace("{total}", str(len(roms_and_paths))) + ) try: future.result() # triggers exception if any occurred except Exception as e: @@ -640,7 +651,12 @@ def run_scraper_tasks(self, max_workers, tasks): for future in as_completed(futures): count = count +1 - self.log_and_display_message(f"Scraping box art... ({count}/{len(tasks)}). Found {success_count} so far.") + self.log_and_display_message( + Language.label("scrapingBoxartProgressFound", "Scraping box art... ({count}/{total}). Found {found} so far.") + .replace("{count}", str(count)) + .replace("{total}", str(len(tasks))) + .replace("{found}", str(success_count)) + ) try: result = future.result() if result: @@ -649,7 +665,7 @@ def run_scraper_tasks(self, max_workers, tasks): except Exception as e: self.log_message(f"BoxartScraper: Error processing a ROM - {e}") - self.log_and_display_message("Scraping complete!") + self.log_and_display_message(Language.label("scrapingComplete", "Scraping complete!")) time.sleep(2) BoxArtResizer.patch_boxart_list(downloaded_files) @@ -698,14 +714,14 @@ def get_scrape_tasks_for_system(self, sys_dir: str) -> List[tuple]: def check_wifi(self): if not Device.get_device().is_wifi_enabled(): - Display.display_message("Wifi must be connected", 2000) + Display.display_message(Language.label("wifiMustBeConnected", "Wifi must be connected"), 2000) return False if not self._ping("thumbnails.libretro.com"): - self.log_and_display_message("Libretro thumbnail service unavailable; trying fallback.") + self.log_and_display_message(Language.label("libretroUnavailable", "Libretro thumbnail service unavailable; trying fallback.")) if not self._ping("github.com"): self.log_and_display_message( - "Libretro thumbnail GitHub repo is also currently unavailable. Please try again later." + Language.label("libretroGithubUnavailable", "Libretro thumbnail GitHub repo is also currently unavailable. Please try again later.") ) time.sleep(3) return False @@ -713,7 +729,7 @@ def check_wifi(self): def scrape_boxart(self, max_workers=8): self.log_and_display_message( - "Scraping box art. Please be patient, especially with large libraries!" + Language.label("scrapingBoxartPatient", "Scraping box art. Please be patient, especially with large libraries!") ) if(not self.check_wifi()): diff --git a/Licenses/fonts/bevietnampro.txt b/Licenses/fonts/bevietnampro.txt new file mode 100644 index 000000000..6de4a2875 --- /dev/null +++ b/Licenses/fonts/bevietnampro.txt @@ -0,0 +1,5 @@ +Be Vietnam Pro +Copyright 2018 The Be Vietnam Pro Project Authors (https://github.com/bettergui/Be-Vietnam-Pro) + +Licensed under the SIL Open Font License, Version 1.1. +https://openfontlicense.org