import { StorageKey } from "@/enums/pref-keys"; import { NATIVE_FETCH } from "./bx-flags"; import { BxLogger } from "./bx-logger"; import { GhPagesUtils } from "./gh-pages"; export const SUPPORTED_LANGUAGES = { 'en-US': 'English (US)', 'ca-CA': 'Català', 'da-DK': 'dansk', 'de-DE': 'Deutsch', 'en-ID': 'Bahasa Indonesia', 'es-ES': 'español (España)', 'fr-FR': 'français', 'it-IT': 'italiano', 'ja-JP': '日本語', 'ko-KR': '한국어', 'pl-PL': 'polski', 'pt-BR': 'português (Brasil)', 'ru-RU': 'русский', 'th-TH': 'ภาษาไทย', 'tr-TR': 'Türkçe', 'uk-UA': 'українська', 'vi-VN': 'Tiếng Việt', 'zh-CN': '中文(简体)', 'zh-TW': '中文(繁體)', }; const Texts = { "activate": "Activate", "activated": "Activated", "active": "Active", "advanced": "Advanced", "always-off": "Always off", "always-on": "Always on", "amd-fidelity-cas": "AMD FidelityFX CAS", "app-settings": "App settings", "apply": "Apply", "aspect-ratio": "Aspect ratio", "aspect-ratio-note": "Don't use with native touch games", "audio": "Audio", "auto": "Auto", "back-to-home": "Back to home", "back-to-home-confirm": "Do you want to go back to the home page (without disconnecting)?", "battery": "Battery", "battery-saving": "Battery saving", "better-xcloud": "Better xCloud", "bitrate-audio-maximum": "Maximum audio bitrate", "bitrate-video-maximum": "Maximum video bitrate", "bottom": "Bottom", "bottom-half": "Bottom half", "bottom-left": "Bottom-left", "bottom-right": "Bottom-right", "brazil": "Brazil", "brightness": "Brightness", "browser-unsupported-feature": "Your browser doesn't support this feature", "bypass-region-restriction": "Bypass region restriction", "can-stream-xbox-360-games": "Can stream Xbox 360 games", "cancel": "Cancel", "cant-stream-xbox-360-games": "Can't stream Xbox 360 games", "center": "Center", "clarity-boost": "Clarity boost", "clarity-boost-warning": "These settings don't work when the Clarity Boost mode is ON", "clear": "Clear", "clear-data": "Clear data", "clear-data-confirm": "Do you want to clear all Better xCloud settings and data?", "clear-data-success": "Data cleared! Refresh the page to apply the changes.", "clock": "Clock", "close": "Close", "close-app": "Close app", "combine-audio-video-streams": "Combine audio & video streams", "combine-audio-video-streams-summary": "May fix the laggy audio problem", "conditional-formatting": "Conditional formatting text color", "confirm-delete-preset": "Do you want to delete this preset?", "confirm-reload-stream": "Do you want to refresh the stream?", "connected": "Connected", "console-connect": "Connect", "continent-asia": "Asia", "continent-australia": "Australia", "continent-europe": "Europe", "continent-north-america": "North America", "continent-south-america": "South America", "contrast": "Contrast", "controller": "Controller", "controller-friendly-ui": "Controller-friendly UI", "controller-shortcuts": "Controller shortcuts", "controller-shortcuts-connect-note": "Connect a controller to use this feature", "controller-shortcuts-in-game": "In-game controller shortcuts", "controller-shortcuts-xbox-note": "Button to open the Guide menu", "controller-vibration": "Controller vibration", "copy": "Copy", "create-shortcut": "Shortcut", "custom": "Custom", "deadzone-counterweight": "Deadzone counterweight", "decrease": "Decrease", "default": "Default", "default-preset-note": "You can't modify default presets. Create a new one to customize it.", "delete": "Delete", "device": "Device", "device-unsupported-touch": "Your device doesn't have touch support", "device-vibration": "Device vibration", "device-vibration-not-using-gamepad": "On when not using gamepad", "disable": "Disable", "disable-byog-feature": "Disable \"Stream your own game\" feature", "disable-home-context-menu": "Disable context menu in Home page", "disable-post-stream-feedback-dialog": "Disable post-stream feedback dialog", "disable-social-features": "Disable social features", "disable-xcloud-analytics": "Disable xCloud analytics", "disabled": "Disabled", "disconnected": "Disconnected", "download": "Download", "downloaded": "Downloaded", "edit": "Edit", "enable-controller-shortcuts": "Enable controller shortcuts", "enable-local-co-op-support": "Enable local co-op support", "enable-local-co-op-support-note": "Only works with some games", "enable-mic-on-startup": "Enable microphone on game launch", "enable-mkb": "Emulate controller with Mouse & Keyboard", "enable-quick-glance-mode": "Enable \"Quick Glance\" mode", "enable-remote-play-feature": "Enable the \"Remote Play\" feature", "enable-volume-control": "Enable volume control feature", "enabled": "Enabled", "experimental": "Experimental", "export": "Export", "fast": "Fast", "force-native-mkb-games": "Force native Mouse & Keyboard for these games", "fortnite-allow-stw-mode": "Allows playing \"Save the World\" mode on mobile", "fortnite-force-console-version": "Fortnite: force console version", "game-bar": "Game Bar", "getting-consoles-list": "Getting the list of consoles...", "guide": "Guide", "help": "Help", "hide": "Hide", "hide-idle-cursor": "Hide mouse cursor on idle", "hide-scrollbar": "Hide web page's scrollbar", "hide-sections": "Hide sections", "hide-system-menu-icon": "Hide System menu's icon", "hide-touch-controller": "Hide touch controller", "high-performance": "High performance", "highest-quality": "Highest quality", "highest-quality-note": "Your device may not be powerful enough to use these settings", "horizontal-scroll-sensitivity": "Horizontal scroll sensitivity", "horizontal-sensitivity": "Horizontal sensitivity", "how-to-fix": "How to fix", "how-to-improve-app-performance": "How to improve app's performance", "ignore": "Ignore", "import": "Import", "increase": "Increase", "install-android": "Better xCloud app for Android", "japan": "Japan", "jitter": "Jitter", "keyboard-key": "Keyboard key", "keyboard-shortcuts": "Keyboard shortcuts", "keyboard-shortcuts-in-game": "In-game keyboard shortcuts", "korea": "Korea", "language": "Language", "large": "Large", "layout": "Layout", "left-stick": "Left stick", "load-failed-message": "Failed to run Better xCloud", "loading-screen": "Loading screen", "local-co-op": "Local co-op", "lowest-quality": "Lowest quality", "manage": "Manage", "map-mouse-to": "Map mouse to", "max-fps": "Max FPS", "may-not-work-properly": "May not work properly!", "menu": "Menu", "microphone": "Microphone", "mkb-adjust-ingame-settings": "You may also need to adjust the in-game sensitivity & deadzone settings", "mkb-click-to-activate": "Click to activate", "mkb-disclaimer": "This could be viewed as cheating when playing online", "modifiers-note": "To use more than one key, include Ctrl, Alt or Shift in your shortcut. Command key is not allowed.", "mouse-and-keyboard": "Mouse & Keyboard", "mouse-click": "Mouse click", "mouse-wheel": "Mouse wheel", "muted": "Muted", "name": "Name", "native-mkb": "Native Mouse & Keyboard", "new": "New", "new-version-available": [ (e: any) => `Version ${e.version} available`, (e: any) => `Versió ${e.version} disponible`, , (e: any) => `Version ${e.version} verfügbar`, (e: any) => `Versi ${e.version} tersedia`, (e: any) => `Versión ${e.version} disponible`, (e: any) => `Version ${e.version} disponible`, (e: any) => `Disponibile la versione ${e.version}`, (e: any) => `Ver ${e.version} が利用可能です`, (e: any) => `${e.version} 버전 사용가능`, (e: any) => `Dostępna jest nowa wersja ${e.version}`, (e: any) => `Versão ${e.version} disponível`, (e: any) => `Версия ${e.version} доступна`, (e: any) => `เวอร์ชัน ${e.version} พร้อมใช้งานแล้ว`, (e: any) => `${e.version} sayılı yeni sürüm mevcut`, (e: any) => `Доступна версія ${e.version}`, (e: any) => `Đã có phiên bản ${e.version}`, (e: any) => `版本 ${e.version} 可供更新`, (e: any) => `已可更新為 ${e.version} 版`, ], "no-consoles-found": "No consoles found", "no-controllers-connected": "No controllers connected", "normal": "Normal", "off": "Off", "official": "Official", "on": "On", "only-supports-some-games": "Only supports some games", "opacity": "Opacity", "other": "Other", "playing": "Playing", "playtime": "Playtime", "poland": "Poland", "polling-rate": "Polling rate", "position": "Position", "powered-off": "Powered off", "powered-on": "Powered on", "prefer-ipv6-server": "Prefer IPv6 server", "preferred-game-language": "Preferred game's language", "preset": "Preset", "press-esc-to-cancel": "Press Esc to cancel", "press-key-to-toggle-mkb": [ (e: any) => `Press ${e.key} to toggle this feature`, (e: any) => `Premeu ${e.key} per alternar aquesta funció`, (e: any) => `Tryk på ${e.key} for at slå denne funktion til`, (e: any) => `${e.key}: Funktion an-/ausschalten`, (e: any) => `Tekan ${e.key} untuk mengaktifkan fitur ini`, (e: any) => `Pulsa ${e.key} para alternar esta función`, (e: any) => `Appuyez sur ${e.key} pour activer cette fonctionnalité`, (e: any) => `Premi ${e.key} per attivare questa funzionalità`, (e: any) => `${e.key} でこの機能を切替`, (e: any) => `${e.key} 키를 눌러 이 기능을 켜고 끄세요`, (e: any) => `Naciśnij ${e.key} aby przełączyć tę funkcję`, (e: any) => `Pressione ${e.key} para alternar este recurso`, (e: any) => `Нажмите ${e.key} для переключения этой функции`, (e: any) => `กด ${e.key} เพื่อสลับคุณสมบัตินี้`, (e: any) => `Etkinleştirmek için ${e.key} tuşuna basın`, (e: any) => `Натисніть ${e.key} щоб перемкнути цю функцію`, (e: any) => `Nhấn ${e.key} để bật/tắt tính năng này`, (e: any) => `按下 ${e.key} 来切换此功能`, (e: any) => `按下 ${e.key} 來啟用此功能`, ], "press-to-bind": "Press a key or do a mouse click to bind...", "prompt-preset-name": "Preset's name:", "recommended": "Recommended", "recommended-settings-for-device": [ (e: any) => `Recommended settings for ${e.device}`, (e: any) => `Configuració recomanada per a ${e.device}`, , (e: any) => `Empfohlene Einstellungen für ${e.device}`, (e: any) => `Rekomendasi pengaturan untuk ${e.device}`, (e: any) => `Ajustes recomendados para ${e.device}`, (e: any) => `Paramètres recommandés pour ${e.device}`, (e: any) => `Configurazioni consigliate per ${e.device}`, (e: any) => `${e.device} の推奨設定`, (e: any) => `다음 기기에서 권장되는 설정: ${e.device}`, (e: any) => `Zalecane ustawienia dla ${e.device}`, (e: any) => `Configurações recomendadas para ${e.device}`, (e: any) => `Рекомендуемые настройки для ${e.device}`, (e: any) => `การตั้งค่าที่แนะนำสำหรับ ${e.device}`, (e: any) => `${e.device} için önerilen ayarlar`, (e: any) => `Рекомендовані налаштування для ${e.device}`, (e: any) => `Cấu hình được đề xuất cho ${e.device}`, (e: any) => `${e.device} 的推荐设置`, (e: any) => `${e.device} 推薦的設定`, ], "reduce-animations": "Reduce UI animations", "region": "Region", "reload-page": "Reload page", "remote-play": "Remote Play", "rename": "Rename", "renderer": "Renderer", "renderer-configuration": "Renderer configuration", "right-click-to-unbind": "Right-click on a key to unbind it", "right-stick": "Right stick", "rocket-always-hide": "Always hide", "rocket-always-show": "Always show", "rocket-animation": "Rocket animation", "rocket-hide-queue": "Hide when queuing", "saturation": "Saturation", "save": "Save", "screen": "Screen", "screenshot-apply-filters": "Apply video filters to screenshots", "section-all-games": "All games", "section-byog": "Stream your own game", "section-most-popular": "Most popular", "section-native-mkb": "Play with mouse & keyboard", "section-news": "News", "section-play-with-friends": "Play with friends", "section-touch": "Play with touch", "separate-touch-controller": "Separate Touch controller & Controller #1", "separate-touch-controller-note": "Touch controller is Player 1, Controller #1 is Player 2", "server": "Server", "server-locations": "Server locations", "settings": "Settings", "settings-reload": "Reload page to reflect changes", "settings-reload-note": "Settings in this tab only go into effect on the next page load", "settings-reloading": "Reloading...", "sharpness": "Sharpness", "shortcut-keys": "Shortcut keys", "show": "Show", "show-controller-connection-status": "Show controller connection status", "show-game-art": "Show game art", "show-hide": "Show/hide", "show-stats-on-startup": "Show stats when starting the game", "show-touch-controller": "Show touch controller", "show-wait-time": "Show the estimated wait time", "show-wait-time-in-game-card": "Show wait time in game card", "simplify-stream-menu": "Simplify Stream's menu", "skip-splash-video": "Skip Xbox splash video", "slow": "Slow", "small": "Small", "smart-tv": "Smart TV", "sound": "Sound", "standard": "Standard", "standby": "Standby", "stat-bitrate": "Bitrate", "stat-decode-time": "Decode time", "stat-fps": "FPS", "stat-frames-lost": "Frames lost", "stat-packets-lost": "Packets lost", "stat-ping": "Ping", "stats": "Stats", "stick-decay-minimum": "Stick decay minimum", "stick-decay-strength": "Stick decay strength", "stream": "Stream", "stream-settings": "Stream settings", "stream-stats": "Stream stats", "stretch": "Stretch", "suggest-settings": "Suggest settings", "suggest-settings-link": "Suggest recommended settings for this device", "support-better-xcloud": "Support Better xCloud", "swap-buttons": "Swap buttons", "take-screenshot": "Take screenshot", "target-resolution": "Target resolution", "tc-all-games": "All games", "tc-all-white": "All white", "tc-auto-off": "Off when controller found", "tc-availability": "Availability", "tc-custom-layout-style": "Custom layout's button style", "tc-default-opacity": "Default opacity", "tc-muted-colors": "Muted colors", "tc-standard-layout-style": "Standard layout's button style", "text-size": "Text size", "toggle": "Toggle", "top": "Top", "top-center": "Top-center", "top-half": "Top half", "top-left": "Top-left", "top-right": "Top-right", "touch-control-layout": "Touch control layout", "touch-control-layout-by": [ (e: any) => `Touch control layout by ${e.name}`, (e: any) => `Format del control tàctil per ${e.name}`, (e: any) => `Touch-kontrol layout af ${e.name}`, (e: any) => `Touch-Steuerungslayout von ${e.name}`, (e: any) => `Tata letak Sentuhan layar oleh ${e.name}`, (e: any) => `Disposición del control táctil por ${e.nombre}`, (e: any) => `Disposition du contrôleur tactile par ${e.name}`, (e: any) => `Configurazione dei comandi su schermo creata da ${e.name}`, (e: any) => `タッチ操作レイアウト作成者: ${e.name}`, (e: any) => `${e.name} 제작, 터치 컨트롤 레이아웃`, (e: any) => `Układ sterowania dotykowego stworzony przez ${e.name}`, (e: any) => `Disposição de controle por toque feito por ${e.name}`, (e: any) => `Сенсорная раскладка по ${e.name}`, (e: any) => `รูปแบบการควบคุมแบบสัมผัสโดย ${e.name}`, (e: any) => `${e.name} kişisinin dokunmatik kontrolcü tuş şeması`, (e: any) => `Розташування сенсорного керування від ${e.name}`, (e: any) => `Bố cục điều khiển cảm ứng tạo bởi ${e.name}`, (e: any) => `由 ${e.name} 提供的虚拟按键样式`, (e: any) => `觸控遊玩佈局由 ${e.name} 提供`, ], "touch-controller": "Touch controller", "transparent-background": "Transparent background", "true-achievements": "TrueAchievements", "ui": "UI", "unexpected-behavior": "May cause unexpected behavior", "united-states": "United States", "unknown": "Unknown", "unlimited": "Unlimited", "unmuted": "Unmuted", "unofficial": "Unofficial", "unofficial-game-list": "Unofficial game list", "unsharp-masking": "Unsharp masking", "upload": "Upload", "uploaded": "Uploaded", "use-mouse-absolute-position": "Use mouse's absolute position", "use-this-at-your-own-risk": "Use this at your own risk", "user-agent-profile": "User-Agent profile", "vertical-scroll-sensitivity": "Vertical scroll sensitivity", "vertical-sensitivity": "Vertical sensitivity", "vibration-intensity": "Vibration intensity", "vibration-status": "Vibration", "video": "Video", "virtual-controller": "Virtual controller", "virtual-controller-slot": "Virtual controller slot", "visual-quality": "Visual quality", "visual-quality-high": "High", "visual-quality-low": "Low", "visual-quality-normal": "Normal", "volume": "Volume", "wait-time-countdown": "Countdown", "wait-time-estimated": "Estimated finish time", "waiting-for-input": "Waiting for input...", "wallpaper": "Wallpaper", "webgl2": "WebGL2", }; export class Translations { private static readonly EN_US = 'en-US'; private static readonly KEY_LOCALE = StorageKey.LOCALE; private static readonly KEY_TRANSLATIONS = StorageKey.LOCALE_TRANSLATIONS; private static selectedLocaleIndex = -1; private static selectedLocale: keyof typeof SUPPORTED_LANGUAGES = 'en-US'; private static supportedLocales = Object.keys(SUPPORTED_LANGUAGES); private static foreignTranslations: any = {}; private static enUsIndex = Translations.supportedLocales.indexOf(Translations.EN_US); static async init() { Translations.refreshLocale(); await Translations.loadTranslations(); } static refreshLocale(newLocale?: string) { let locale; if (newLocale) { localStorage.setItem(Translations.KEY_LOCALE, newLocale); locale = newLocale; } else { locale = localStorage.getItem(Translations.KEY_LOCALE); } const supportedLocales = Translations.supportedLocales; if (!locale) { // Get browser's locale locale = window.navigator.language || Translations.EN_US; if (supportedLocales.indexOf(locale) === -1) { locale = Translations.EN_US; } localStorage.setItem(Translations.KEY_LOCALE, locale); } Translations.selectedLocale = locale as keyof typeof SUPPORTED_LANGUAGES; Translations.selectedLocaleIndex = supportedLocales.indexOf(locale); } static get(key: keyof typeof Texts, values?: any): T { let text = null; if (Translations.foreignTranslations && Translations.selectedLocale !== Translations.EN_US) { text = Translations.foreignTranslations[key]; } if (!text) { text = Texts[key] || alert(`Missing translation key: ${key}`); } let translation: unknown; if (Array.isArray(text)) { translation = text[Translations.selectedLocaleIndex] || text[Translations.enUsIndex]; return (translation as any)(values); } translation = text; return translation as T; } private static async loadTranslations() { if (Translations.selectedLocale === Translations.EN_US) { return; } try { Translations.foreignTranslations = JSON.parse(window.localStorage.getItem(Translations.KEY_TRANSLATIONS)!); } catch(e) {} if (!Translations.foreignTranslations) { await this.downloadTranslations(Translations.selectedLocale); } } static async updateTranslations(async=false) { // Don't have to download en-US if (Translations.selectedLocale === Translations.EN_US) { localStorage.removeItem(Translations.KEY_TRANSLATIONS); return; } if (async) { Translations.downloadTranslationsAsync(Translations.selectedLocale); } else { await Translations.downloadTranslations(Translations.selectedLocale); } } static async downloadTranslations(locale: string) { try { const resp = await NATIVE_FETCH(GhPagesUtils.getUrl(`translations/${locale}.json`)); const translations = await resp.json(); // Prevent saving incorrect translations let currentLocale = localStorage.getItem(Translations.KEY_LOCALE); if (currentLocale === locale) { window.localStorage.setItem(Translations.KEY_TRANSLATIONS, JSON.stringify(translations)); Translations.foreignTranslations = translations; } return true; } catch (e) { debugger; } return false; } static downloadTranslationsAsync(locale: string) { NATIVE_FETCH(GhPagesUtils.getUrl(`translations/${locale}.json`)) .then(resp => resp.json()) .then(translations => { window.localStorage.setItem(Translations.KEY_TRANSLATIONS, JSON.stringify(translations)); Translations.foreignTranslations = translations; }); } static switchLocale(locale: string) { localStorage.setItem(Translations.KEY_LOCALE, locale); } } export const t = Translations.get; export const ut = (text: string): string => { BxLogger.warning('Untranslated text', text); return text; } Translations.init();