mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 12:11:58 +00:00
[Feature] Implement internazionalization for Decky Loader (#361)
* First iteration for internationalization of the loader * First iteration for internationalization of the loader * Cleanup node mess * Cleanup node mess pt2 * Additional touches * Latest decky changed merged into i18n and updated translation. * Styling fixes * Initial backend hosting implementation * Added correct url path of the loopback server. * Added correct url path of the loopback server. * Some better namespaced text. * Added whitelist for locales path. * Refactor languages and fix hooks logic bugs. * Small typo in language translation structure. * Working backend, automatically swtich languages with steam and language fixes. * Fix to languages * Key fixes * Additional language fixes. * Additional json changes * Final text revision and added a vscode tasks to automatically extract text from code. * Typo in the middleware * Remove unused imports * Cleanup whitespaces. * Import changes * Revert "Import changes" This reverts commit8e8231950f. * Update index.d.ts * Clean up unused imports * Delete pnpm-lock.yaml * Update rollup.config.js * Update PluginInstallModal.tsx * Update index.tsx * Update plugin-loader.tsx * Update plugin-loader.tsx * Revert "Delete pnpm-lock.yaml" This reverts commit3a39f36f21. * Additional strings reworks. * Fixes for issues coming from github merge. * Fixes for master * Styling fixes * Styling pt2 * Missed a few strings in master, * Styling fixes * Additional master merge fixes. * Final cleanup and adaptation to master. * Final empty language cleanup and few string added * Small changes to italian translation * Disabled translation on a few components inside plugin-loader for missing react hooks. * Fixed passing tag to translation. * Disable debug output for reducing console spam. * Return correct content type * Small italian language change * Added support for country code * Fixed missing translation for uninstall popup. * Fix class name shenanigans for toast notification * Update dependencies * Fixed github workflow to include the new locales folder * Update dependencies to latest version (unless it's React) and fixed the new small errors that cropped up * Missed a file name change * Updated dev dependencies to latest version * Missed a few dev dependencies * Revert "Update dependencies to latest version (unless it's React) and fixed the new small errors that cropped up" Messed up merge with a different main branch * Messed up deletion of rollup config. * Fix broken pnpm lock file * Missed a localized string during the merge * Fixed a parameter mistake in the uninstall text parameter * Fix pnpm random issues * Small italian language tweaks * Fix wrong parameter passed to the uninstall function call * Another fix on a wrong function parameter * Additional translation text on the store and branch selection channels * Changed the default type passed to map to being able to index the two arrays. * Reverted and reworked the last changes * Distinguish events in UI for installing vs reinstalling plugins * Additional fixes for reinstall prompt * Revert the use of intevalPlural since the parser doesn't seem to support that. * Missed a routing path in the backend * Small bugfixes * Small fixes * Correctly adding the parameter to the request headers. * Refactoring of the UI popup modal * Fix pnpm shenanigans * Final fixes for the install UI localization * Clean up unnedeed backend code * Small rework on text selection. * Cleaned up parser configuration * Removed extracttext dependency to pnpmsetup * Merged translation and cleaned up parser * Fixed JSON structure after manual merge. * Added translation to the file picker * Revert changes to PluginInstallModal * Reworked the text modal for the final time * Missed the proper linted text * Missed the backend change * Final branch cleanup * Fixed small translation bleeding Caused from the manual merge of _old.json files. * fix extra space in browser.py * fix extra newline in plugin-loader.tsx * Cleanup i18next-parser.config.mjs * Update plugin-loader.tsx * Cleanup language files * Better labeling of text * Fixed language typos in BranchSelect * Fixed language typos in StoreSelect * Cleanup plugin-loader.tsx from unused imports * Removed the path bypass since I'm using authentication from the frontend. * Reimplemented this component as a functional component. * Updated dependencies and lockfile * Removed static route from main.py Already handled in loader.py * Small italian coherency fixes * Fix small typography fixes on plugin name uninstall * Fixed italian typo on removal popup * Reenabled manual escaping value in i18next * Set to fallback to the default language if the string in the JSON file is empty. * Fixed pnpm wankery * Added a missed italian text translation string --------- Co-authored-by: AAGaming <aa@mail.catvibers.me>
This commit is contained in:
@@ -69,7 +69,7 @@ jobs:
|
|||||||
run: pnpm run build
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Build Python Backend 🛠️
|
- name: Build Python Backend 🛠️
|
||||||
run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/legacy:/legacy --add-data ./plugin:/plugin ./backend/*.py
|
run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/locales:/locales --add-data ./backend/legacy:/legacy --add-data ./plugin:/plugin ./backend/*.py
|
||||||
|
|
||||||
- name: Upload package artifact ⬆️
|
- name: Upload package artifact ⬆️
|
||||||
if: ${{ !env.ACT }}
|
if: ${{ !env.ACT }}
|
||||||
|
|||||||
Vendored
+8
@@ -41,6 +41,14 @@
|
|||||||
"command": "rsync -azp --rsh='ssh -p ${config:deckport} ${config:deckkey}' requirements.txt deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader/requirements.txt && ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip && python -m pip install --upgrade pip && python -m pip install --upgrade setuptools && python -m pip install -r ${config:deckdir}/homebrew/dev/pluginloader/requirements.txt'",
|
"command": "rsync -azp --rsh='ssh -p ${config:deckport} ${config:deckkey}' requirements.txt deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader/requirements.txt && ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'python -m ensurepip && python -m pip install --upgrade pip && python -m pip install --upgrade setuptools && python -m pip install -r ${config:deckdir}/homebrew/dev/pluginloader/requirements.txt'",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "extracttext",
|
||||||
|
"type": "shell",
|
||||||
|
"group": "none",
|
||||||
|
"detail": "Check for new strings in the frontend source code and extract it into the corresponding json language files",
|
||||||
|
"command": "cd frontend && ./node_modules/.bin/i18next --config ./i18next-parser.config.mjs",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
// BUILD
|
// BUILD
|
||||||
{
|
{
|
||||||
"label": "pnpmsetup",
|
"label": "pnpmsetup",
|
||||||
|
|||||||
+2
-2
@@ -209,12 +209,12 @@ class PluginBrowser:
|
|||||||
if self.loader.watcher:
|
if self.loader.watcher:
|
||||||
self.loader.watcher.disabled = False
|
self.loader.watcher.disabled = False
|
||||||
|
|
||||||
async def request_plugin_install(self, artifact, name, version, hash):
|
async def request_plugin_install(self, artifact, name, version, hash, install_type):
|
||||||
request_id = str(time())
|
request_id = str(time())
|
||||||
self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash)
|
self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash)
|
||||||
tab = await get_gamepadui_tab()
|
tab = await get_gamepadui_tab()
|
||||||
await tab.open_websocket()
|
await tab.open_websocket()
|
||||||
await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}')")
|
await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}', {install_type})")
|
||||||
|
|
||||||
async def confirm_plugin_install(self, request_id):
|
async def confirm_plugin_install(self, request_id):
|
||||||
request = self.install_requests.pop(request_id)
|
request = self.install_requests.pop(request_id)
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ class Loader:
|
|||||||
|
|
||||||
server_instance.add_routes([
|
server_instance.add_routes([
|
||||||
web.get("/frontend/{path:.*}", self.handle_frontend_assets),
|
web.get("/frontend/{path:.*}", self.handle_frontend_assets),
|
||||||
|
web.get("/locales/{path:.*}", self.handle_frontend_locales),
|
||||||
web.get("/plugins", self.get_plugins),
|
web.get("/plugins", self.get_plugins),
|
||||||
web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle),
|
web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle),
|
||||||
web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call),
|
web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call),
|
||||||
@@ -99,6 +100,11 @@ class Loader:
|
|||||||
|
|
||||||
return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
|
return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
|
||||||
|
|
||||||
|
async def handle_frontend_locales(self, request):
|
||||||
|
file = path.join(path.dirname(__file__), "locales", request.match_info["path"])
|
||||||
|
|
||||||
|
return web.FileResponse(file, headers={"Cache-Control": "no-cache", "Content-Type": "application/json"})
|
||||||
|
|
||||||
async def get_plugins(self, request):
|
async def get_plugins(self, request):
|
||||||
plugins = list(self.plugins.values())
|
plugins = list(self.plugins.values())
|
||||||
return web.json_response([{"name": str(i) if not i.legacy else "$LEGACY_"+str(i), "version": i.version} for i in plugins])
|
return web.json_response([{"name": str(i) if not i.legacy else "$LEGACY_"+str(i), "version": i.version} for i in plugins])
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
{
|
||||||
|
"BranchSelect": {
|
||||||
|
"update_channel": {
|
||||||
|
"label": "Update Channel",
|
||||||
|
"prerelease": "Prerelease",
|
||||||
|
"stable": "Stable",
|
||||||
|
"testing": "Testing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Developer": {
|
||||||
|
"5secreload": "Reloading in 5 seconds",
|
||||||
|
"disabling": "Disabling",
|
||||||
|
"enabling": "Enabling"
|
||||||
|
},
|
||||||
|
"FilePickerIndex": {
|
||||||
|
"folder": {
|
||||||
|
"select": "Use this folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PluginCard": {
|
||||||
|
"plugin_full_access": "This plugin has full access to your Steam Deck.",
|
||||||
|
"plugin_install": "Install",
|
||||||
|
"plugin_no_desc": "No description provided.",
|
||||||
|
"plugin_version_label": "Plugin Version"
|
||||||
|
},
|
||||||
|
"PluginInstallModal": {
|
||||||
|
"install": {
|
||||||
|
"button_idle": "Install",
|
||||||
|
"button_processing": "Installing",
|
||||||
|
"desc": "Are you sure you want to install {{artifact}} {{version}}?",
|
||||||
|
"title": "Install {{artifact}}"
|
||||||
|
},
|
||||||
|
"no_hash": "This plugin does not have a hash, you are installing it at your own risk.",
|
||||||
|
"reinstall": {
|
||||||
|
"button_idle": "Reinstall",
|
||||||
|
"button_processing": "Reinstalling",
|
||||||
|
"desc": "Are you sure you want to reinstall {{artifact}} {{version}}?",
|
||||||
|
"title": "Reinstall {{artifact}}"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"button_idle": "Update",
|
||||||
|
"button_processing": "Updating",
|
||||||
|
"desc": "Are you sure you want to update {{artifact}} {{version}}?",
|
||||||
|
"title": "Update {{name}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PluginListIndex": {
|
||||||
|
"no_plugin": "No plugins installed!",
|
||||||
|
"plugin_actions": "Plugin Actions",
|
||||||
|
"reinstall": "Reinstall",
|
||||||
|
"reload": "Reload",
|
||||||
|
"uninstall": "Uninstall",
|
||||||
|
"update_to": "Update to {{name}}"
|
||||||
|
},
|
||||||
|
"PluginLoader": {
|
||||||
|
"decky_title": "Decky",
|
||||||
|
"decky_update_available": "Update to {{tag_name}} available!",
|
||||||
|
"error": "Error",
|
||||||
|
"plugin_error_uninstall": "Please go to {{-icon}} in the Decky menu if you need to uninstall this plugin.",
|
||||||
|
"plugin_load_error": {
|
||||||
|
"message": "Error loading plugin {{name}}",
|
||||||
|
"toast": "Error loading {{name}}"
|
||||||
|
},
|
||||||
|
"plugin_uninstall": {
|
||||||
|
"button": "Uninstall",
|
||||||
|
"desc": "Are you sure you want to uninstall {{name}}?",
|
||||||
|
"title": "Uninstall {{name}}"
|
||||||
|
},
|
||||||
|
"plugin_update_one": "Updates available for 1 plugin!",
|
||||||
|
"plugin_update_other": "Updates available for {{count}} plugins!"
|
||||||
|
},
|
||||||
|
"RemoteDebugging": {
|
||||||
|
"remote_cef": {
|
||||||
|
"desc": "Allow unauthenticated access to the CEF debugger to anyone in your network",
|
||||||
|
"label": "Allow Remote CEF Debugging"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SettingsDeveloperIndex": {
|
||||||
|
"header_other": "Other",
|
||||||
|
"react_devtools": {
|
||||||
|
"desc": "Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set the IP address before enabling.",
|
||||||
|
"ip_label": "IP",
|
||||||
|
"label": "Enable React DevTools"
|
||||||
|
},
|
||||||
|
"third_party_plugins": {
|
||||||
|
"button_install": "Install",
|
||||||
|
"button_zip": "Browse",
|
||||||
|
"header": "Third-Party Plugins",
|
||||||
|
"label_desc": "URL",
|
||||||
|
"label_url": "Install Plugin from URL",
|
||||||
|
"label_zip": "Install Plugin from ZIP File"
|
||||||
|
},
|
||||||
|
"toast_zip": {
|
||||||
|
"body": "Installation failed! Only ZIP files are supported.",
|
||||||
|
"title": "Decky"
|
||||||
|
},
|
||||||
|
"valve_internal": {
|
||||||
|
"desc1": "Enables the Valve internal developer menu.",
|
||||||
|
"desc2": "Do not touch anything in this menu unless you know what it does.",
|
||||||
|
"label": "Enable Valve Internal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SettingsGeneralIndex": {
|
||||||
|
"about": {
|
||||||
|
"decky_version": "Decky Version",
|
||||||
|
"header": "About"
|
||||||
|
},
|
||||||
|
"beta": {
|
||||||
|
"header": "Beta participation"
|
||||||
|
},
|
||||||
|
"developer_mode": {
|
||||||
|
"desc": "Enables Decky's developer settings.",
|
||||||
|
"label": "Developer mode"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"header": "Other"
|
||||||
|
},
|
||||||
|
"updates": {
|
||||||
|
"header": "Updates"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SettingsIndex": {
|
||||||
|
"developer_title": "Developer",
|
||||||
|
"general_title": "General",
|
||||||
|
"navbar_settings": "Decky Settings",
|
||||||
|
"plugins_title": "Plugins"
|
||||||
|
},
|
||||||
|
"Store": {
|
||||||
|
"store_contrib": {
|
||||||
|
"desc": "If you would like to contribute to the Decky Plugin Store, check the SteamDeckHomebrew/decky-plugin-template repository on GitHub. Information on development and distribution is available in the README.",
|
||||||
|
"label": "Contributing"
|
||||||
|
},
|
||||||
|
"store_filter": {
|
||||||
|
"label": "Filter",
|
||||||
|
"label_def": "All"
|
||||||
|
},
|
||||||
|
"store_search": {
|
||||||
|
"label": "Search"
|
||||||
|
},
|
||||||
|
"store_sort": {
|
||||||
|
"label": "Sort",
|
||||||
|
"label_def": "Last Updated (Newest)"
|
||||||
|
},
|
||||||
|
"store_source": {
|
||||||
|
"desc": "All plugin source code is available on SteamDeckHomebrew/decky-plugin-database repository on GitHub.",
|
||||||
|
"label": "Source Code"
|
||||||
|
},
|
||||||
|
"store_tabs": {
|
||||||
|
"about": "About",
|
||||||
|
"alph_asce": "Alphabetical (Z to A)",
|
||||||
|
"alph_desc": "Alphabetical (A to Z)",
|
||||||
|
"title": "Browse"
|
||||||
|
},
|
||||||
|
"store_testing_cta": "Please consider testing new plugins to help the Decky Loader team!"
|
||||||
|
},
|
||||||
|
"StoreSelect": {
|
||||||
|
"custom_store": {
|
||||||
|
"label": "Custom Store",
|
||||||
|
"url_label": "URL"
|
||||||
|
},
|
||||||
|
"store_channel": {
|
||||||
|
"custom": "Custom",
|
||||||
|
"default": "Default",
|
||||||
|
"label": "Store Channel",
|
||||||
|
"testing": "Testing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Updater": {
|
||||||
|
"decky_updates": "Decky Updates",
|
||||||
|
"no_patch_notes_desc": "no patch notes for this version",
|
||||||
|
"patch_notes_desc": "Patch Notes",
|
||||||
|
"updates": {
|
||||||
|
"check_button": "Check For Updates",
|
||||||
|
"checking": "'Checking",
|
||||||
|
"cur_version": "Current version: {{ver}}",
|
||||||
|
"install_button": "Install Update",
|
||||||
|
"label": "Updates",
|
||||||
|
"lat_version": "Up to date: running {{ver}}",
|
||||||
|
"reloading": "Reloading",
|
||||||
|
"updating": "Updating"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
{
|
||||||
|
"BranchSelect": {
|
||||||
|
"update_channel": {
|
||||||
|
"label": "Canale di aggiornamento",
|
||||||
|
"prerelease": "Prerilascio",
|
||||||
|
"stable": "Stabile",
|
||||||
|
"testing": "In prova"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Developer": {
|
||||||
|
"5secreload": "Ricaricando in 5 secondi",
|
||||||
|
"disabling": "Disabilitando",
|
||||||
|
"enabling": "Abilitando"
|
||||||
|
},
|
||||||
|
"FilePickerIndex": {
|
||||||
|
"folder": {
|
||||||
|
"select": "Usa questa cartella"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PluginCard": {
|
||||||
|
"plugin_full_access": "Questo plugin ha accesso completo al tuo Steam Deck.",
|
||||||
|
"plugin_install": "Installa",
|
||||||
|
"plugin_no_desc": "Nessuna descrizione fornita.",
|
||||||
|
"plugin_version_label": "Versione Plugin"
|
||||||
|
},
|
||||||
|
"PluginInstallModal": {
|
||||||
|
"install": {
|
||||||
|
"button_idle": "Installa",
|
||||||
|
"button_processing": "Installando",
|
||||||
|
"desc": "Sei sicuro di voler installare {{artifact}} {{version}}?",
|
||||||
|
"title": "Installa {{artifact}}"
|
||||||
|
},
|
||||||
|
"no_hash": "Questo plugin non ha un hash associato, lo stai installando a tuo rischio e pericolo.",
|
||||||
|
"reinstall": {
|
||||||
|
"button_idle": "Reinstalla",
|
||||||
|
"button_processing": "Reinstallando",
|
||||||
|
"desc": "Sei sicuro di voler reinstallare {{artifact}} {{version}}?",
|
||||||
|
"title": "Reinstalla {{artifact}}"
|
||||||
|
},
|
||||||
|
"update": {
|
||||||
|
"button_idle": "Aggiorna",
|
||||||
|
"button_processing": "Aggiornando",
|
||||||
|
"desc": "Sei sicuro di voler aggiornare {{artifact}} {{version}}?",
|
||||||
|
"title": "Aggiorna {{artifact}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"PluginListIndex": {
|
||||||
|
"no_plugin": "Nessun plugin installato!",
|
||||||
|
"plugin_actions": "Operazioni sui plugins",
|
||||||
|
"reinstall": "Reinstalla",
|
||||||
|
"reload": "Ricarica",
|
||||||
|
"uninstall": "Rimuovi",
|
||||||
|
"update_to": "Aggiorna a {{name}}"
|
||||||
|
},
|
||||||
|
"PluginLoader": {
|
||||||
|
"decky_title": "Decky",
|
||||||
|
"decky_update_available": "Disponibile aggiornamento a {{tag_name}}!",
|
||||||
|
"error": "Errore",
|
||||||
|
"plugin_error_uninstall": "Per rimuovere questo plugin vai su {{-icon}} nel menu di Decky.",
|
||||||
|
"plugin_load_error": {
|
||||||
|
"message": "Errore caricando il plugin {{name}}",
|
||||||
|
"toast": "Errore caricando {{name}}"
|
||||||
|
},
|
||||||
|
"plugin_uninstall": {
|
||||||
|
"button": "Rimuovi",
|
||||||
|
"desc": "Sei sicuro di voler rimuovere {{name}}?",
|
||||||
|
"title": "Rimuovi {{name}}"
|
||||||
|
},
|
||||||
|
"plugin_update_one": "Aggiornamento disponibile per un plugin!",
|
||||||
|
"plugin_update_many": "Aggiornamento disponibile per {{count}} plugins!",
|
||||||
|
"plugin_update_other": "Aggiornamento disponibile per {{count}} plugins!"
|
||||||
|
},
|
||||||
|
"RemoteDebugging": {
|
||||||
|
"remote_cef": {
|
||||||
|
"desc": "Permetti l'accesso non autenticato al debugger di CEF da tutti gli indirizzi sulla tua rete locale.",
|
||||||
|
"label": "Permetti il debug remoto di CEF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SettingsDeveloperIndex": {
|
||||||
|
"header_other": "Altro",
|
||||||
|
"react_devtools": {
|
||||||
|
"desc": "Abilita la connessione ad un computer che esegue i DevTools di React. Cambiando questa impostazione ricaricherà Steam. Imposta l'indirizzo IP prima di abilitarlo.",
|
||||||
|
"ip_label": "IP",
|
||||||
|
"label": "Abilita i DevTools di React"
|
||||||
|
},
|
||||||
|
"third_party_plugins": {
|
||||||
|
"button_install": "Installa",
|
||||||
|
"button_zip": "Seleziona",
|
||||||
|
"header": "Plugin di terze parti",
|
||||||
|
"label_desc": "URL",
|
||||||
|
"label_url": "Installa plugin da un'indirizzo web",
|
||||||
|
"label_zip": "Installa plugin da un file ZIP"
|
||||||
|
},
|
||||||
|
"toast_zip": {
|
||||||
|
"body": "Installazione non riuscita, solo i file ZIP sono supportati!",
|
||||||
|
"title": "Decky"
|
||||||
|
},
|
||||||
|
"valve_internal": {
|
||||||
|
"desc1": "Abilita il menu di sviluppo interno di Valve.",
|
||||||
|
"desc2": "Non toccare nulla in questo menu se non sai quello che fa.",
|
||||||
|
"label": "Abilita Menu Sviluppatore"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SettingsGeneralIndex": {
|
||||||
|
"about": {
|
||||||
|
"decky_version": "Versione di Decky",
|
||||||
|
"header": "Riguardo a"
|
||||||
|
},
|
||||||
|
"beta": {
|
||||||
|
"header": "Partecipazione alla beta"
|
||||||
|
},
|
||||||
|
"developer_mode": {
|
||||||
|
"desc": "Abilità le impostazioni di sviluppo di Decky.",
|
||||||
|
"label": "Modalità sviluppatore"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"header": "Altro"
|
||||||
|
},
|
||||||
|
"updates": {
|
||||||
|
"header": "Aggiornamenti"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SettingsIndex": {
|
||||||
|
"developer_title": "Sviluppatore",
|
||||||
|
"general_title": "Generali",
|
||||||
|
"navbar_settings": "Impostazioni Decky",
|
||||||
|
"plugins_title": "Plugins"
|
||||||
|
},
|
||||||
|
"Store": {
|
||||||
|
"store_contrib": {
|
||||||
|
"desc": "Se desideri contribuire allo store di Decky, puoi trovare un template caricato su GitHub all'indirizzo SteamDeckHomebrew/decky-plugin-template. Informazioni riguardo sviluppo e distribuzione sono disponibili nel README.",
|
||||||
|
"label": "Contribuisci"
|
||||||
|
},
|
||||||
|
"store_filter": {
|
||||||
|
"label": "Filtra",
|
||||||
|
"label_def": "Tutto"
|
||||||
|
},
|
||||||
|
"store_search": {
|
||||||
|
"label": "Cerca"
|
||||||
|
},
|
||||||
|
"store_sort": {
|
||||||
|
"label": "Ordina",
|
||||||
|
"label_def": "Ultimo aggiornato (Più recente)"
|
||||||
|
},
|
||||||
|
"store_source": {
|
||||||
|
"desc": "Tutto il codice sorgente dei plugin è disponibile su GitHub all'indirizzo SteamDeckHomebrew/decky-plugin-database",
|
||||||
|
"label": "Codice Sorgente"
|
||||||
|
},
|
||||||
|
"store_tabs": {
|
||||||
|
"about": "Riguardo a",
|
||||||
|
"alph_asce": "Alfabetico (Z a A)",
|
||||||
|
"alph_desc": "Alfabetico (A a Z)",
|
||||||
|
"title": "Sfoglia"
|
||||||
|
},
|
||||||
|
"store_testing_cta": "Valuta la possibilità di testare nuovi plugin per aiutare il team di Decky Loader!"
|
||||||
|
},
|
||||||
|
"StoreSelect": {
|
||||||
|
"custom_store": {
|
||||||
|
"label": "Negozio custom",
|
||||||
|
"url_label": "URL"
|
||||||
|
},
|
||||||
|
"store_channel": {
|
||||||
|
"custom": "Personalizzato",
|
||||||
|
"default": "Default",
|
||||||
|
"label": "Canale del negozio",
|
||||||
|
"testing": "In prova"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Updater": {
|
||||||
|
"decky_updates": "Aggiornamento di Decky",
|
||||||
|
"no_patch_notes_desc": "nessuna patch notes per questa versione",
|
||||||
|
"patch_notes_desc": "Cambiamenti",
|
||||||
|
"updates": {
|
||||||
|
"check_button": "Cerca aggiornamenti",
|
||||||
|
"checking": "Controllando",
|
||||||
|
"cur_version": "Versione attuale: {{ver}}",
|
||||||
|
"install_button": "Installa aggiornamento",
|
||||||
|
"label": "Aggiornamenti",
|
||||||
|
"lat_version": "Aggiornato. Eseguendo {{ver}}",
|
||||||
|
"reloading": "Ricaricando",
|
||||||
|
"updating": "Aggiornando"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,12 +61,13 @@ class Utilities:
|
|||||||
res["success"] = False
|
res["success"] = False
|
||||||
return web.json_response(res)
|
return web.json_response(res)
|
||||||
|
|
||||||
async def install_plugin(self, artifact="", name="No name", version="dev", hash=False):
|
async def install_plugin(self, artifact="", name="No name", version="dev", hash=False, install_type=0):
|
||||||
return await self.context.plugin_browser.request_plugin_install(
|
return await self.context.plugin_browser.request_plugin_install(
|
||||||
artifact=artifact,
|
artifact=artifact,
|
||||||
name=name,
|
name=name,
|
||||||
version=version,
|
version=version,
|
||||||
hash=hash
|
hash=hash,
|
||||||
|
install_type=install_type
|
||||||
)
|
)
|
||||||
|
|
||||||
async def confirm_plugin_install(self, request_id):
|
async def confirm_plugin_install(self, request_id):
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
export default {
|
||||||
|
contextSeparator: '_',
|
||||||
|
// Key separator used in your translation keys
|
||||||
|
|
||||||
|
createOldCatalogs: false,
|
||||||
|
// Save the \_old files
|
||||||
|
|
||||||
|
defaultNamespace: 'translation',
|
||||||
|
// Default namespace used in your i18next config
|
||||||
|
|
||||||
|
defaultValue: '',
|
||||||
|
// Default value to give to keys with no value
|
||||||
|
// You may also specify a function accepting the locale, namespace, key, and value as arguments
|
||||||
|
|
||||||
|
indentation: 2,
|
||||||
|
// Indentation of the catalog files
|
||||||
|
|
||||||
|
keepRemoved: true,
|
||||||
|
// Keep keys from the catalog that are no longer in code
|
||||||
|
|
||||||
|
keySeparator: '.',
|
||||||
|
// Key separator used in your translation keys
|
||||||
|
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
||||||
|
|
||||||
|
// see below for more details
|
||||||
|
lexers: {
|
||||||
|
mjs: ['JavascriptLexer'],
|
||||||
|
js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
|
||||||
|
ts: ['JavascriptLexer'],
|
||||||
|
jsx: ['JsxLexer'],
|
||||||
|
tsx: ['JsxLexer'],
|
||||||
|
|
||||||
|
default: ['JavascriptLexer'],
|
||||||
|
},
|
||||||
|
|
||||||
|
lineEnding: 'auto',
|
||||||
|
// Control the line ending. See options at https://github.com/ryanve/eol
|
||||||
|
|
||||||
|
locales: ['en-US', 'it-IT'],
|
||||||
|
// An array of the locales in your applications
|
||||||
|
|
||||||
|
namespaceSeparator: false,
|
||||||
|
// Namespace separator used in your translation keys
|
||||||
|
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
||||||
|
|
||||||
|
output: '../backend/locales/$LOCALE.json',
|
||||||
|
// Supports $LOCALE and $NAMESPACE injection
|
||||||
|
// Supports JSON (.json) and YAML (.yml) file formats
|
||||||
|
// Where to write the locale files relative to process.cwd()
|
||||||
|
|
||||||
|
pluralSeparator: '_',
|
||||||
|
// Plural separator used in your translation keys
|
||||||
|
// If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
|
||||||
|
|
||||||
|
input: './src/**/*.{ts,tsx}',
|
||||||
|
// An array of globs that describe where to look for source files
|
||||||
|
// relative to the location of the configuration file
|
||||||
|
|
||||||
|
sort: true,
|
||||||
|
// Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters)
|
||||||
|
|
||||||
|
verbose: false,
|
||||||
|
// Display info about the parsing including some stats
|
||||||
|
|
||||||
|
failOnWarnings: false,
|
||||||
|
// Exit with an exit code of 1 on warnings
|
||||||
|
|
||||||
|
failOnUpdate: false,
|
||||||
|
// Exit with an exit code of 1 when translations are updated (for CI purpose)
|
||||||
|
|
||||||
|
customValueTemplate: null,
|
||||||
|
// If you wish to customize the value output the value as an object, you can set your own format.
|
||||||
|
// ${defaultValue} is the default value you set in your translation function.
|
||||||
|
// Any other custom property will be automatically extracted.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// {
|
||||||
|
// message: "${defaultValue}",
|
||||||
|
// description: "${maxLength}", // t('my-key', {maxLength: 150})
|
||||||
|
// }
|
||||||
|
|
||||||
|
resetDefaultValueLocale: null,
|
||||||
|
// The locale to compare with default values to determine whether a default value has been changed.
|
||||||
|
// If this is set and a default value differs from a translation in the specified locale, all entries
|
||||||
|
// for that key across locales are reset to the default value, and existing translations are moved to
|
||||||
|
// the `_old` file.
|
||||||
|
|
||||||
|
i18nextOptions: null,
|
||||||
|
// If you wish to customize options in internally used i18next instance, you can define an object with any
|
||||||
|
// configuration property supported by i18next (https://www.i18next.com/overview/configuration-options).
|
||||||
|
// { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals.
|
||||||
|
|
||||||
|
yamlOptions: null,
|
||||||
|
// If you wish to customize options for yaml output, you can define an object here.
|
||||||
|
// Configuration options are here (https://github.com/nodeca/js-yaml#dump-object---options-).
|
||||||
|
// Example:
|
||||||
|
// {
|
||||||
|
// lineWidth: -1,
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -22,9 +22,10 @@
|
|||||||
"@types/react-router": "5.1.18",
|
"@types/react-router": "5.1.18",
|
||||||
"@types/webpack": "^5.28.1",
|
"@types/webpack": "^5.28.1",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
|
"i18next-parser": "^7.7.0",
|
||||||
"import-sort-style-module": "^6.0.0",
|
"import-sort-style-module": "^6.0.0",
|
||||||
"inquirer": "^8.2.5",
|
"inquirer": "^8.2.5",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-import-sort": "^0.0.7",
|
"prettier-plugin-import-sort": "^0.0.7",
|
||||||
"react": "16.14.0",
|
"react": "16.14.0",
|
||||||
"react-dom": "16.14.0",
|
"react-dom": "16.14.0",
|
||||||
@@ -43,9 +44,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"decky-frontend-lib": "3.20.6",
|
"decky-frontend-lib": "3.20.6",
|
||||||
|
"i18next": "^22.4.15",
|
||||||
|
"i18next-browser-languagedetector": "^7.0.1",
|
||||||
|
"i18next-http-backend": "^2.2.0",
|
||||||
"react-file-icon": "^1.3.0",
|
"react-file-icon": "^1.3.0",
|
||||||
|
"react-i18next": "^12.2.2",
|
||||||
"react-icons": "^4.8.0",
|
"react-icons": "^4.8.0",
|
||||||
"react-markdown": "^8.0.6",
|
"react-markdown": "^8.0.7",
|
||||||
"remark-gfm": "^3.0.1"
|
"remark-gfm": "^3.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1428
-223
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,10 @@ export default defineConfig({
|
|||||||
input: 'src/index.tsx',
|
input: 'src/index.tsx',
|
||||||
plugins: [
|
plugins: [
|
||||||
del({ targets: '../backend/static/*', force: true }),
|
del({ targets: '../backend/static/*', force: true }),
|
||||||
commonjs(),
|
commonjs({include: '../frontend/node_modules/**'}),
|
||||||
nodeResolve(),
|
nodeResolve({
|
||||||
|
browser: true
|
||||||
|
}),
|
||||||
externalGlobals({
|
externalGlobals({
|
||||||
react: 'SP_REACT',
|
react: 'SP_REACT',
|
||||||
'react-dom': 'SP_REACTDOM',
|
'react-dom': 'SP_REACTDOM',
|
||||||
@@ -44,4 +46,4 @@ export default defineConfig({
|
|||||||
if (hiddenWarnings.some((warning) => message.code === warning)) return;
|
if (hiddenWarnings.some((warning) => message.code === warning)) return;
|
||||||
handleWarning(message);
|
handleWarning(message);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1,18 +1,31 @@
|
|||||||
import { ConfirmModal, Navigation, QuickAccessTab } from 'decky-frontend-lib';
|
import { ConfirmModal, Navigation, QuickAccessTab } from 'decky-frontend-lib';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import TPluginInstallModal, { TranslatedPart } from './TPluginInstallModal';
|
||||||
|
|
||||||
interface PluginInstallModalProps {
|
interface PluginInstallModalProps {
|
||||||
artifact: string;
|
artifact: string;
|
||||||
version: string;
|
version: string;
|
||||||
hash: string;
|
hash: string;
|
||||||
// reinstall: boolean;
|
installType: number;
|
||||||
onOK(): void;
|
onOK(): void;
|
||||||
onCancel(): void;
|
onCancel(): void;
|
||||||
closeModal?(): void;
|
closeModal?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, hash, onOK, onCancel, closeModal }) => {
|
const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
||||||
|
artifact,
|
||||||
|
version,
|
||||||
|
hash,
|
||||||
|
installType,
|
||||||
|
onOK,
|
||||||
|
onCancel,
|
||||||
|
closeModal,
|
||||||
|
}) => {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
bOKDisabled={loading}
|
bOKDisabled={loading}
|
||||||
@@ -26,14 +39,22 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
|
|||||||
onCancel={async () => {
|
onCancel={async () => {
|
||||||
await onCancel();
|
await onCancel();
|
||||||
}}
|
}}
|
||||||
strTitle={`Install ${artifact}`}
|
strTitle={<TPluginInstallModal trans_part={TranslatedPart.TITLE} trans_type={installType} artifact={artifact} />}
|
||||||
strOKButtonText={loading ? 'Installing' : 'Install'}
|
strOKButtonText={
|
||||||
|
loading ? (
|
||||||
|
<TPluginInstallModal trans_part={TranslatedPart.BUTTON_PROC} trans_type={installType} />
|
||||||
|
) : (
|
||||||
|
<TPluginInstallModal trans_part={TranslatedPart.BUTTON_IDLE} trans_type={installType} />
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Are you sure you want to install {artifact}
|
<TPluginInstallModal
|
||||||
{version ? ` ${version}` : ''}?
|
trans_part={TranslatedPart.DESC}
|
||||||
{hash == 'False' && (
|
trans_type={installType}
|
||||||
<span style={{ color: 'red' }}> This plugin does not have a hash, you are installing it at your own risk.</span>
|
artifact={artifact}
|
||||||
)}
|
version={version ? version : ''}
|
||||||
|
/>
|
||||||
|
{hash == 'False' && <span style={{ color: 'red' }}>{t('PluginInstallModal.no_hash')}</span>}
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Translation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { InstallType } from '../../plugin';
|
||||||
|
|
||||||
|
export enum TranslatedPart {
|
||||||
|
TITLE,
|
||||||
|
DESC,
|
||||||
|
BUTTON_IDLE,
|
||||||
|
BUTTON_PROC,
|
||||||
|
}
|
||||||
|
interface TPluginInstallModalProps {
|
||||||
|
trans_part: TranslatedPart;
|
||||||
|
trans_type: number;
|
||||||
|
artifact?: string;
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TPluginInstallModal: FC<TPluginInstallModalProps> = ({ trans_part, trans_type, artifact, version }) => {
|
||||||
|
return (
|
||||||
|
<Translation>
|
||||||
|
{(t, {}) => {
|
||||||
|
switch (trans_part) {
|
||||||
|
case TranslatedPart.TITLE:
|
||||||
|
switch (trans_type) {
|
||||||
|
case InstallType.INSTALL:
|
||||||
|
return <div>{t('PluginInstallModal.install.title', { artifact: artifact })}</div>;
|
||||||
|
case InstallType.REINSTALL:
|
||||||
|
return <div>{t('PluginInstallModal.reinstall.title', { artifact: artifact })}</div>;
|
||||||
|
case InstallType.UPDATE:
|
||||||
|
return <div>{t('PluginInstallModal.update.title', { artifact: artifact })}</div>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case TranslatedPart.DESC:
|
||||||
|
switch (trans_type) {
|
||||||
|
case InstallType.INSTALL:
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('PluginInstallModal.install.desc', {
|
||||||
|
artifact: artifact,
|
||||||
|
version: version,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case InstallType.REINSTALL:
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('PluginInstallModal.reinstall.desc', {
|
||||||
|
artifact: artifact,
|
||||||
|
version: version,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case InstallType.UPDATE:
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('PluginInstallModal.update.desc', {
|
||||||
|
artifact: artifact,
|
||||||
|
version: version,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case TranslatedPart.BUTTON_IDLE:
|
||||||
|
switch (trans_type) {
|
||||||
|
case InstallType.INSTALL:
|
||||||
|
return <div>{t('PluginInstallModal.install.button_idle')}</div>;
|
||||||
|
case InstallType.REINSTALL:
|
||||||
|
return <div>{t('PluginInstallModal.reinstall.button_idle')}</div>;
|
||||||
|
case InstallType.UPDATE:
|
||||||
|
return <div>{t('PluginInstallModal.update.button_idle')}</div>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case TranslatedPart.BUTTON_PROC:
|
||||||
|
switch (trans_type) {
|
||||||
|
case InstallType.INSTALL:
|
||||||
|
return <div>{t('PluginInstallModal.install.button_processing')}</div>;
|
||||||
|
case InstallType.REINSTALL:
|
||||||
|
return <div>{t('PluginInstallModal.reinstall.button_processing')}</div>;
|
||||||
|
case InstallType.UPDATE:
|
||||||
|
return <div>{t('PluginInstallModal.update.button_processing')}</div>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</Translation>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TPluginInstallModal;
|
||||||
@@ -2,6 +2,7 @@ import { DialogButton, Focusable, SteamSpinner, TextField } from 'decky-frontend
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { FunctionComponent, useState } from 'react';
|
import { FunctionComponent, useState } from 'react';
|
||||||
import { FileIcon, defaultStyles } from 'react-file-icon';
|
import { FileIcon, defaultStyles } from 'react-file-icon';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaArrowUp, FaFolder } from 'react-icons/fa';
|
import { FaArrowUp, FaFolder } from 'react-icons/fa';
|
||||||
|
|
||||||
import Logger from '../../../logger';
|
import Logger from '../../../logger';
|
||||||
@@ -47,6 +48,7 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
closeModal,
|
closeModal,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path
|
if (startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path
|
||||||
const [path, setPath] = useState<string>(startPath);
|
const [path, setPath] = useState<string>(startPath);
|
||||||
const [listing, setListing] = useState<FileListing>({ files: [], realpath: path });
|
const [listing, setListing] = useState<FileListing>({ files: [], realpath: path });
|
||||||
@@ -158,7 +160,7 @@ const FilePicker: FunctionComponent<FilePickerProps> = ({
|
|||||||
closeModal?.();
|
closeModal?.();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Use this folder
|
{t('FilePickerIndex.folder.select')}
|
||||||
</DialogButton>
|
</DialogButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SidebarNavigation } from 'decky-frontend-lib';
|
import { SidebarNavigation } from 'decky-frontend-lib';
|
||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaCode, FaPlug } from 'react-icons/fa';
|
import { FaCode, FaPlug } from 'react-icons/fa';
|
||||||
|
|
||||||
import { useSetting } from '../../utils/hooks/useSetting';
|
import { useSetting } from '../../utils/hooks/useSetting';
|
||||||
@@ -12,22 +13,23 @@ const DeveloperSettings = lazy(() => import('./pages/developer'));
|
|||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const [isDeveloper, setIsDeveloper] = useSetting<boolean>('developer.enabled', false);
|
const [isDeveloper, setIsDeveloper] = useSetting<boolean>('developer.enabled', false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const pages = [
|
const pages = [
|
||||||
{
|
{
|
||||||
title: 'Decky',
|
title: t('SettingsIndex.general_title'),
|
||||||
content: <GeneralSettings isDeveloper={isDeveloper} setIsDeveloper={setIsDeveloper} />,
|
content: <GeneralSettings isDeveloper={isDeveloper} setIsDeveloper={setIsDeveloper} />,
|
||||||
route: '/decky/settings/general',
|
route: '/decky/settings/general',
|
||||||
icon: <DeckyIcon />,
|
icon: <DeckyIcon />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Plugins',
|
title: t('SettingsIndex.plugins_title'),
|
||||||
content: <PluginList />,
|
content: <PluginList />,
|
||||||
route: '/decky/settings/plugins',
|
route: '/decky/settings/plugins',
|
||||||
icon: <FaPlug />,
|
icon: <FaPlug />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Developer',
|
title: t('SettingsIndex.developer_title'),
|
||||||
content: (
|
content: (
|
||||||
<WithSuspense>
|
<WithSuspense>
|
||||||
<DeveloperSettings />
|
<DeveloperSettings />
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Toggle,
|
Toggle,
|
||||||
} from 'decky-frontend-lib';
|
} from 'decky-frontend-lib';
|
||||||
import { useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaFileArchive, FaLink, FaReact, FaSteamSymbol } from 'react-icons/fa';
|
import { FaFileArchive, FaLink, FaReact, FaSteamSymbol } from 'react-icons/fa';
|
||||||
|
|
||||||
import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer';
|
import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer';
|
||||||
@@ -24,8 +25,10 @@ const installFromZip = () => {
|
|||||||
installFromURL(url);
|
installFromURL(url);
|
||||||
} else {
|
} else {
|
||||||
window.DeckyPluginLoader.toaster.toast({
|
window.DeckyPluginLoader.toaster.toast({
|
||||||
|
//title: t('SettingsDeveloperIndex.toast_zip.title'),
|
||||||
title: 'Decky',
|
title: 'Decky',
|
||||||
body: `Installation failed! Only ZIP files are supported.`,
|
//body: t('SettingsDeveloperIndex.toast_zip.body'),
|
||||||
|
body: 'Installation failed! Only ZIP files are supported.',
|
||||||
onClick: installFromZip,
|
onClick: installFromZip,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -38,33 +41,47 @@ export default function DeveloperSettings() {
|
|||||||
const [reactDevtoolsIP, setReactDevtoolsIP] = useSetting<string>('developer.rdt.ip', '');
|
const [reactDevtoolsIP, setReactDevtoolsIP] = useSetting<string>('developer.rdt.ip', '');
|
||||||
const [pluginURL, setPluginURL] = useState('');
|
const [pluginURL, setPluginURL] = useState('');
|
||||||
const textRef = useRef<HTMLDivElement>(null);
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogBody>
|
<DialogBody>
|
||||||
<DialogControlsSection>
|
<DialogControlsSection>
|
||||||
<DialogControlsSectionHeader>Third-Party Plugins</DialogControlsSectionHeader>
|
<DialogControlsSectionHeader>
|
||||||
<Field label="Install Plugin from ZIP File" icon={<FaFileArchive style={{ display: 'block' }} />}>
|
{t('SettingsDeveloperIndex.third_party_plugins.header')}
|
||||||
<DialogButton onClick={installFromZip}>Browse</DialogButton>
|
</DialogControlsSectionHeader>
|
||||||
|
<Field
|
||||||
|
label={t('SettingsDeveloperIndex.third_party_plugins.label_zip')}
|
||||||
|
icon={<FaFileArchive style={{ display: 'block' }} />}
|
||||||
|
>
|
||||||
|
<DialogButton onClick={installFromZip}>
|
||||||
|
{t('SettingsDeveloperIndex.third_party_plugins.button_zip')}
|
||||||
|
</DialogButton>
|
||||||
</Field>
|
</Field>
|
||||||
<Field
|
<Field
|
||||||
label="Install Plugin from URL"
|
label={t('SettingsDeveloperIndex.third_party_plugins.label_url')}
|
||||||
description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />}
|
description={
|
||||||
|
<TextField
|
||||||
|
label={t('SettingsDeveloperIndex.third_party_plugins.label_desc')}
|
||||||
|
value={pluginURL}
|
||||||
|
onChange={(e) => setPluginURL(e?.target.value)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
icon={<FaLink style={{ display: 'block' }} />}
|
icon={<FaLink style={{ display: 'block' }} />}
|
||||||
>
|
>
|
||||||
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
|
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
|
||||||
Install
|
{t('SettingsDeveloperIndex.third_party_plugins.button_install')}
|
||||||
</DialogButton>
|
</DialogButton>
|
||||||
</Field>
|
</Field>
|
||||||
</DialogControlsSection>
|
</DialogControlsSection>
|
||||||
<DialogControlsSection>
|
<DialogControlsSection>
|
||||||
<DialogControlsSectionHeader>Other</DialogControlsSectionHeader>
|
<DialogControlsSectionHeader>{t('SettingsDeveloperIndex.header_other')}</DialogControlsSectionHeader>
|
||||||
<RemoteDebuggingSettings />
|
<RemoteDebuggingSettings />
|
||||||
<Field
|
<Field
|
||||||
label="Enable Valve Internal"
|
label={t('SettingsDeveloperIndex.valve_internal.label')}
|
||||||
description={
|
description={
|
||||||
<span style={{ whiteSpace: 'pre-line' }}>
|
<span style={{ whiteSpace: 'pre-line' }}>
|
||||||
Enables the Valve internal developer menu.{' '}
|
{t('SettingsDeveloperIndex.valve_internal.desc1')}{' '}
|
||||||
<span style={{ color: 'red' }}>Do not touch anything in this menu unless you know what it does.</span>
|
<span style={{ color: 'red' }}>{t('SettingsDeveloperIndex.valve_internal.desc2')}</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
icon={<FaSteamSymbol style={{ display: 'block' }} />}
|
icon={<FaSteamSymbol style={{ display: 'block' }} />}
|
||||||
@@ -78,17 +95,18 @@ export default function DeveloperSettings() {
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field
|
<Field
|
||||||
label="Enable React DevTools"
|
label={t('SettingsDeveloperIndex.react_devtools.label')}
|
||||||
description={
|
description={
|
||||||
<>
|
<>
|
||||||
<span style={{ whiteSpace: 'pre-line' }}>
|
<span style={{ whiteSpace: 'pre-line' }}>{t('SettingsDeveloperIndex.react_devtools.desc')}</span>
|
||||||
Enables connection to a computer running React DevTools. Changing this setting will reload Steam. Set
|
|
||||||
the IP address before enabling.
|
|
||||||
</span>
|
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<div ref={textRef}>
|
<div ref={textRef}>
|
||||||
<TextField label={'IP'} value={reactDevtoolsIP} onChange={(e) => setReactDevtoolsIP(e?.target.value)} />
|
<TextField
|
||||||
|
label={t('SettingsDeveloperIndex.react_devtools.ip_label')}
|
||||||
|
value={reactDevtoolsIP}
|
||||||
|
onChange={(e) => setReactDevtoolsIP(e?.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Dropdown, Field } from 'decky-frontend-lib';
|
import { Dropdown, Field } from 'decky-frontend-lib';
|
||||||
import { FunctionComponent } from 'react';
|
import { FunctionComponent } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Logger from '../../../../logger';
|
import Logger from '../../../../logger';
|
||||||
import { callUpdaterMethod } from '../../../../updater';
|
import { callUpdaterMethod } from '../../../../updater';
|
||||||
@@ -14,17 +15,23 @@ enum UpdateBranch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BranchSelect: FunctionComponent<{}> = () => {
|
const BranchSelect: FunctionComponent<{}> = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const tBranches = [
|
||||||
|
t('BranchSelect.update_channel.stable'),
|
||||||
|
t('BranchSelect.update_channel.prerelease'),
|
||||||
|
t('BranchSelect.update_channel.testing'),
|
||||||
|
];
|
||||||
const [selectedBranch, setSelectedBranch] = useSetting<UpdateBranch>('branch', UpdateBranch.Prerelease);
|
const [selectedBranch, setSelectedBranch] = useSetting<UpdateBranch>('branch', UpdateBranch.Prerelease);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Returns numerical values from 0 to 2 (with current branch setup as of 8/28/22)
|
// Returns numerical values from 0 to 2 (with current branch setup as of 8/28/22)
|
||||||
// 0 being stable, 1 being pre-release and 2 being nightly
|
// 0 being stable, 1 being pre-release and 2 being nightly
|
||||||
<Field label="Decky Update Channel" childrenContainerWidth={'fixed'}>
|
<Field label={t('BranchSelect.update_channel.label')} childrenContainerWidth={'fixed'}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
rgOptions={Object.values(UpdateBranch)
|
rgOptions={Object.values(UpdateBranch)
|
||||||
.filter((branch) => typeof branch == 'string')
|
.filter((branch) => typeof branch == 'string')
|
||||||
.map((branch) => ({
|
.map((branch) => ({
|
||||||
label: branch,
|
label: tBranches[UpdateBranch[branch]],
|
||||||
data: UpdateBranch[branch],
|
data: UpdateBranch[branch],
|
||||||
}))}
|
}))}
|
||||||
selectedOption={selectedBranch}
|
selectedOption={selectedBranch}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { Field, Toggle } from 'decky-frontend-lib';
|
import { Field, Toggle } from 'decky-frontend-lib';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaChrome } from 'react-icons/fa';
|
import { FaChrome } from 'react-icons/fa';
|
||||||
|
|
||||||
import { useSetting } from '../../../../utils/hooks/useSetting';
|
import { useSetting } from '../../../../utils/hooks/useSetting';
|
||||||
|
|
||||||
export default function RemoteDebuggingSettings() {
|
export default function RemoteDebuggingSettings() {
|
||||||
const [allowRemoteDebugging, setAllowRemoteDebugging] = useSetting<boolean>('cef_forward', false);
|
const [allowRemoteDebugging, setAllowRemoteDebugging] = useSetting<boolean>('cef_forward', false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
label="Allow Remote CEF Debugging"
|
label={t('RemoteDebugging.remote_cef.label')}
|
||||||
description={
|
description={<span style={{ whiteSpace: 'pre-line' }}>{t('RemoteDebugging.remote_cef.desc')}</span>}
|
||||||
<span style={{ whiteSpace: 'pre-line' }}>
|
|
||||||
Allows unauthenticated access to the CEF debugger to anyone in your network.
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
icon={<FaChrome style={{ display: 'block' }} />}
|
icon={<FaChrome style={{ display: 'block' }} />}
|
||||||
>
|
>
|
||||||
<Toggle
|
<Toggle
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Dropdown, Field, TextField } from 'decky-frontend-lib';
|
import { Dropdown, Field, TextField } from 'decky-frontend-lib';
|
||||||
import { FunctionComponent } from 'react';
|
import { FunctionComponent } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaShapes } from 'react-icons/fa';
|
import { FaShapes } from 'react-icons/fa';
|
||||||
|
|
||||||
import Logger from '../../../../logger';
|
import Logger from '../../../../logger';
|
||||||
@@ -11,17 +12,23 @@ const logger = new Logger('StoreSelect');
|
|||||||
const StoreSelect: FunctionComponent<{}> = () => {
|
const StoreSelect: FunctionComponent<{}> = () => {
|
||||||
const [selectedStore, setSelectedStore] = useSetting<Store>('store', Store.Default);
|
const [selectedStore, setSelectedStore] = useSetting<Store>('store', Store.Default);
|
||||||
const [selectedStoreURL, setSelectedStoreURL] = useSetting<string | null>('store-url', null);
|
const [selectedStoreURL, setSelectedStoreURL] = useSetting<string | null>('store-url', null);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const tStores = [
|
||||||
|
t('StoreSelect.store_channel.default'),
|
||||||
|
t('StoreSelect.store_channel.testing'),
|
||||||
|
t('StoreSelect.store_channel.custom'),
|
||||||
|
];
|
||||||
|
|
||||||
// Returns numerical values from 0 to 2 (with current branch setup as of 8/28/22)
|
// Returns numerical values from 0 to 2 (with current branch setup as of 8/28/22)
|
||||||
// 0 being Default, 1 being Testing and 2 being Custom
|
// 0 being Default, 1 being Testing and 2 being Custom
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Field label="Plugin Store Channel" childrenContainerWidth={'fixed'}>
|
<Field label={t('StoreSelect.store_channel.label')} childrenContainerWidth={'fixed'}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
rgOptions={Object.values(Store)
|
rgOptions={Object.values(Store)
|
||||||
.filter((store) => typeof store == 'string')
|
.filter((store) => typeof store == 'string')
|
||||||
.map((store) => ({
|
.map((store) => ({
|
||||||
label: store,
|
label: tStores[Store[store]],
|
||||||
data: Store[store],
|
data: Store[store],
|
||||||
}))}
|
}))}
|
||||||
selectedOption={selectedStore}
|
selectedOption={selectedStore}
|
||||||
@@ -33,11 +40,11 @@ const StoreSelect: FunctionComponent<{}> = () => {
|
|||||||
</Field>
|
</Field>
|
||||||
{selectedStore == Store.Custom && (
|
{selectedStore == Store.Custom && (
|
||||||
<Field
|
<Field
|
||||||
label="Custom Store"
|
label={t('StoreSelect.custom_store.label')}
|
||||||
indentLevel={1}
|
indentLevel={1}
|
||||||
description={
|
description={
|
||||||
<TextField
|
<TextField
|
||||||
label={'URL'}
|
label={t('StoreSelect.custom_store.url_label')}
|
||||||
value={selectedStoreURL || undefined}
|
value={selectedStoreURL || undefined}
|
||||||
onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
|
onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Suspense, lazy } from 'react';
|
import { Suspense, lazy } from 'react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaExclamation } from 'react-icons/fa';
|
import { FaExclamation } from 'react-icons/fa';
|
||||||
|
|
||||||
import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater';
|
import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater';
|
||||||
@@ -23,6 +24,7 @@ const MarkdownRenderer = lazy(() => import('../../../Markdown'));
|
|||||||
|
|
||||||
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
|
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
|
||||||
const SP = findSP();
|
const SP = findSP();
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Focusable onCancelButton={closeModal}>
|
<Focusable onCancelButton={closeModal}>
|
||||||
<FocusRing>
|
<FocusRing>
|
||||||
@@ -45,7 +47,7 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
|
|||||||
<MarkdownRenderer onDismiss={closeModal}>{versionInfo.all[id].body}</MarkdownRenderer>
|
<MarkdownRenderer onDismiss={closeModal}>{versionInfo.all[id].body}</MarkdownRenderer>
|
||||||
</WithSuspense>
|
</WithSuspense>
|
||||||
) : (
|
) : (
|
||||||
'no patch notes for this version'
|
t('Updater.no_patch_notes_desc')
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Focusable>
|
</Focusable>
|
||||||
@@ -58,7 +60,7 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
|
|||||||
initialColumn={0}
|
initialColumn={0}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
fnGetColumnWidth={() => SP.innerWidth}
|
fnGetColumnWidth={() => SP.innerWidth}
|
||||||
name="Decky Updates"
|
name={t('Updater.decky_updates') as string}
|
||||||
/>
|
/>
|
||||||
</FocusRing>
|
</FocusRing>
|
||||||
</Focusable>
|
</Focusable>
|
||||||
@@ -72,6 +74,8 @@ export default function UpdaterSettings() {
|
|||||||
const [updateProgress, setUpdateProgress] = useState<number>(-1);
|
const [updateProgress, setUpdateProgress] = useState<number>(-1);
|
||||||
const [reloading, setReloading] = useState<boolean>(false);
|
const [reloading, setReloading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.DeckyUpdater = {
|
window.DeckyUpdater = {
|
||||||
updateProgress: (i) => {
|
updateProgress: (i) => {
|
||||||
@@ -93,14 +97,14 @@ export default function UpdaterSettings() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Field
|
<Field
|
||||||
onOptionsActionDescription={versionInfo?.all ? 'Patch Notes' : undefined}
|
onOptionsActionDescription={versionInfo?.all ? t('Updater.patch_notes_desc') : undefined}
|
||||||
onOptionsButton={versionInfo?.all ? showPatchNotes : undefined}
|
onOptionsButton={versionInfo?.all ? showPatchNotes : undefined}
|
||||||
label="Decky Updates"
|
label={t('Updater.updates.label')}
|
||||||
description={
|
description={
|
||||||
checkingForUpdates || versionInfo?.remote?.tag_name != versionInfo?.current || !versionInfo?.remote ? (
|
checkingForUpdates || versionInfo?.remote?.tag_name != versionInfo?.current || !versionInfo?.remote ? (
|
||||||
''
|
''
|
||||||
) : (
|
) : (
|
||||||
<span>Up to date: running {versionInfo?.current}</span>
|
<span>{t('Updater.updates.lat_version', { ver: versionInfo?.current })} </span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
@@ -129,10 +133,10 @@ export default function UpdaterSettings() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{checkingForUpdates
|
{checkingForUpdates
|
||||||
? 'Checking'
|
? t('Updater.updates.checking')
|
||||||
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||||
? 'Check For Updates'
|
? t('Updater.updates.check_button')
|
||||||
: 'Install Update'}
|
: t('Updater.updates.install_button')}
|
||||||
</DialogButton>
|
</DialogButton>
|
||||||
) : (
|
) : (
|
||||||
<ProgressBarWithInfo
|
<ProgressBarWithInfo
|
||||||
@@ -140,7 +144,7 @@ export default function UpdaterSettings() {
|
|||||||
bottomSeparator="none"
|
bottomSeparator="none"
|
||||||
nProgress={updateProgress}
|
nProgress={updateProgress}
|
||||||
indeterminate={reloading}
|
indeterminate={reloading}
|
||||||
sOperationText={reloading ? 'Reloading' : 'Updating'}
|
sOperationText={reloading ? t('Updater.updates.reloading') : t('Updater.updates.updating')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DialogBody, DialogControlsSection, DialogControlsSectionHeader, Field, Toggle } from 'decky-frontend-lib';
|
import { DialogBody, DialogControlsSection, DialogControlsSectionHeader, Field, Toggle } from 'decky-frontend-lib';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { useDeckyState } from '../../../DeckyState';
|
import { useDeckyState } from '../../../DeckyState';
|
||||||
import BranchSelect from './BranchSelect';
|
import BranchSelect from './BranchSelect';
|
||||||
@@ -13,21 +14,22 @@ export default function GeneralSettings({
|
|||||||
setIsDeveloper: (val: boolean) => void;
|
setIsDeveloper: (val: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
const { versionInfo } = useDeckyState();
|
const { versionInfo } = useDeckyState();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogBody>
|
<DialogBody>
|
||||||
<DialogControlsSection>
|
<DialogControlsSection>
|
||||||
<DialogControlsSectionHeader>Updates</DialogControlsSectionHeader>
|
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.updates.header')}</DialogControlsSectionHeader>
|
||||||
<UpdaterSettings />
|
<UpdaterSettings />
|
||||||
</DialogControlsSection>
|
</DialogControlsSection>
|
||||||
<DialogControlsSection>
|
<DialogControlsSection>
|
||||||
<DialogControlsSectionHeader>Beta Participation</DialogControlsSectionHeader>
|
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.beta.header')}</DialogControlsSectionHeader>
|
||||||
<BranchSelect />
|
<BranchSelect />
|
||||||
<StoreSelect />
|
<StoreSelect />
|
||||||
</DialogControlsSection>
|
</DialogControlsSection>
|
||||||
<DialogControlsSection>
|
<DialogControlsSection>
|
||||||
<DialogControlsSectionHeader>Other</DialogControlsSectionHeader>
|
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.other.header')}</DialogControlsSectionHeader>
|
||||||
<Field label="Enable Developer Mode">
|
<Field label={t('SettingsGeneralIndex.developer_mode.label')}>
|
||||||
<Toggle
|
<Toggle
|
||||||
value={isDeveloper}
|
value={isDeveloper}
|
||||||
onChange={(toggleValue) => {
|
onChange={(toggleValue) => {
|
||||||
@@ -37,8 +39,8 @@ export default function GeneralSettings({
|
|||||||
</Field>
|
</Field>
|
||||||
</DialogControlsSection>
|
</DialogControlsSection>
|
||||||
<DialogControlsSection>
|
<DialogControlsSection>
|
||||||
<DialogControlsSectionHeader>About</DialogControlsSectionHeader>
|
<DialogControlsSectionHeader>{t('SettingsGeneralIndex.about.header')}</DialogControlsSectionHeader>
|
||||||
<Field label="Decky Version" focusable={true}>
|
<Field label={t('SettingsGeneralIndex.about.decky_version')} focusable={true}>
|
||||||
<div style={{ color: 'var(--gpSystemLighterGrey)' }}>{versionInfo?.current}</div>
|
<div style={{ color: 'var(--gpSystemLighterGrey)' }}>{versionInfo?.current}</div>
|
||||||
</Field>
|
</Field>
|
||||||
</DialogControlsSection>
|
</DialogControlsSection>
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import {
|
|||||||
showContextMenu,
|
showContextMenu,
|
||||||
} from 'decky-frontend-lib';
|
} from 'decky-frontend-lib';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaDownload, FaEllipsisH, FaRecycle } from 'react-icons/fa';
|
import { FaDownload, FaEllipsisH, FaRecycle } from 'react-icons/fa';
|
||||||
|
|
||||||
|
import { InstallType } from '../../../../plugin';
|
||||||
import { StorePluginVersion, getPluginList, requestPluginInstall } from '../../../../store';
|
import { StorePluginVersion, getPluginList, requestPluginInstall } from '../../../../store';
|
||||||
import { useSetting } from '../../../../utils/hooks/useSetting';
|
import { useSetting } from '../../../../utils/hooks/useSetting';
|
||||||
import { useDeckyState } from '../../../DeckyState';
|
import { useDeckyState } from '../../../DeckyState';
|
||||||
@@ -25,19 +27,33 @@ async function reinstallPlugin(pluginName: string, currentVersion?: string) {
|
|||||||
const remotePlugin = serverData?.find((x) => x.name == pluginName);
|
const remotePlugin = serverData?.find((x) => x.name == pluginName);
|
||||||
if (remotePlugin && remotePlugin.versions?.length > 0) {
|
if (remotePlugin && remotePlugin.versions?.length > 0) {
|
||||||
const currentVersionData = remotePlugin.versions.find((version) => version.name == currentVersion);
|
const currentVersionData = remotePlugin.versions.find((version) => version.name == currentVersion);
|
||||||
if (currentVersionData) requestPluginInstall(pluginName, currentVersionData);
|
if (currentVersionData) requestPluginInstall(pluginName, currentVersionData, InstallType.REINSTALL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) {
|
function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) {
|
||||||
const data = props.entry.data;
|
const data = props.entry.data;
|
||||||
|
const { t } = useTranslation();
|
||||||
let pluginName = labelToName(props.entry.label, data?.version);
|
let pluginName = labelToName(props.entry.label, data?.version);
|
||||||
|
|
||||||
const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
|
const showCtxMenu = (e: MouseEvent | GamepadEvent) => {
|
||||||
showContextMenu(
|
showContextMenu(
|
||||||
<Menu label="Plugin Actions">
|
<Menu label={t('PluginListIndex.plugin_actions')}>
|
||||||
<MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(pluginName, data?.version)}>Reload</MenuItem>
|
<MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(pluginName, data?.version)}>
|
||||||
<MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin(pluginName)}>Uninstall</MenuItem>
|
{t('PluginListIndex.reload')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onSelected={() =>
|
||||||
|
window.DeckyPluginLoader.uninstallPlugin(
|
||||||
|
pluginName,
|
||||||
|
t('PluginLoader.plugin_uninstall.title', { name: pluginName }),
|
||||||
|
t('PluginLoader.plugin_uninstall.button'),
|
||||||
|
t('PluginLoader.plugin_uninstall.desc', { name: pluginName }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('PluginListIndex.uninstall')}
|
||||||
|
</MenuItem>
|
||||||
</Menu>,
|
</Menu>,
|
||||||
e.currentTarget ?? window,
|
e.currentTarget ?? window,
|
||||||
);
|
);
|
||||||
@@ -48,11 +64,11 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) {
|
|||||||
{data?.update ? (
|
{data?.update ? (
|
||||||
<DialogButton
|
<DialogButton
|
||||||
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
|
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
|
||||||
onClick={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion)}
|
onClick={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion, InstallType.UPDATE)}
|
||||||
onOKButton={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion)}
|
onOKButton={() => requestPluginInstall(pluginName, data?.update as StorePluginVersion, InstallType.UPDATE)}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
Update to {data?.update?.name}
|
{t('PluginListIndex.update_to', { name: data?.update?.name })}
|
||||||
<FaDownload style={{ paddingLeft: '2rem' }} />
|
<FaDownload style={{ paddingLeft: '2rem' }} />
|
||||||
</div>
|
</div>
|
||||||
</DialogButton>
|
</DialogButton>
|
||||||
@@ -63,7 +79,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginData> }) {
|
|||||||
onOKButton={() => reinstallPlugin(pluginName, data?.version)}
|
onOKButton={() => reinstallPlugin(pluginName, data?.version)}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
Reinstall
|
{t('PluginListIndex.reinstall')}
|
||||||
<FaRecycle style={{ paddingLeft: '5.3rem' }} />
|
<FaRecycle style={{ paddingLeft: '5.3rem' }} />
|
||||||
</div>
|
</div>
|
||||||
</DialogButton>
|
</DialogButton>
|
||||||
@@ -90,6 +106,7 @@ export default function PluginList() {
|
|||||||
'pluginOrder',
|
'pluginOrder',
|
||||||
plugins.map((plugin) => plugin.name),
|
plugins.map((plugin) => plugin.name),
|
||||||
);
|
);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.DeckyPluginLoader.checkPluginUpdates();
|
window.DeckyPluginLoader.checkPluginUpdates();
|
||||||
@@ -115,7 +132,7 @@ export default function PluginList() {
|
|||||||
if (plugins.length === 0) {
|
if (plugins.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>No plugins installed</p>
|
<p>{t('PluginListIndex.no_plugin')}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import {
|
|||||||
SuspensefulImage,
|
SuspensefulImage,
|
||||||
} from 'decky-frontend-lib';
|
} from 'decky-frontend-lib';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { InstallType } from '../../plugin';
|
||||||
import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store';
|
import { StorePlugin, StorePluginVersion, requestPluginInstall } from '../../store';
|
||||||
|
|
||||||
interface PluginCardProps {
|
interface PluginCardProps {
|
||||||
@@ -18,6 +20,8 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
|||||||
const [selectedOption, setSelectedOption] = useState<number>(0);
|
const [selectedOption, setSelectedOption] = useState<number>(0);
|
||||||
const root: boolean = plugin.tags.some((tag) => tag === 'root');
|
const root: boolean = plugin.tags.some((tag) => tag === 'root');
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="deckyStoreCard"
|
className="deckyStoreCard"
|
||||||
@@ -97,7 +101,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
|||||||
plugin.description
|
plugin.description
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
<i style={{ color: '#666' }}>No description provided.</i>
|
<i style={{ color: '#666' }}>{t('PluginCard.plugin_no_desc')}</i>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
@@ -109,7 +113,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
|||||||
color: '#fee75c',
|
color: '#fee75c',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<i>This plugin has full access to your Steam Deck.</i>{' '}
|
<i>{t('PluginCard.plugin_full_access')}</i>{' '}
|
||||||
<a
|
<a
|
||||||
className="deckyStoreCardDescriptionRootLink"
|
className="deckyStoreCardDescriptionRootLink"
|
||||||
href="https://deckbrew.xyz/root"
|
href="https://deckbrew.xyz/root"
|
||||||
@@ -144,9 +148,11 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
|||||||
<ButtonItem
|
<ButtonItem
|
||||||
bottomSeparator="none"
|
bottomSeparator="none"
|
||||||
layout="below"
|
layout="below"
|
||||||
onClick={() => requestPluginInstall(plugin.name, plugin.versions[selectedOption])}
|
onClick={() =>
|
||||||
|
requestPluginInstall(plugin.name, plugin.versions[selectedOption], InstallType.INSTALL)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span className="deckyStoreCardInstallText">Install</span>
|
<span className="deckyStoreCardInstallText">{t('PluginCard.plugin_install')}</span>
|
||||||
</ButtonItem>
|
</ButtonItem>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -163,7 +169,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
|||||||
label: version.name,
|
label: version.name,
|
||||||
})) as SingleDropdownOption[]
|
})) as SingleDropdownOption[]
|
||||||
}
|
}
|
||||||
menuLabel="Plugin Version"
|
menuLabel={t('PluginCard.plugin_version_label') as string}
|
||||||
selectedOption={selectedOption}
|
selectedOption={selectedOption}
|
||||||
onChange={({ data }) => setSelectedOption(data)}
|
onChange={({ data }) => setSelectedOption(data)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
findModule,
|
findModule,
|
||||||
} from 'decky-frontend-lib';
|
} from 'decky-frontend-lib';
|
||||||
import { FC, useEffect, useMemo, useState } from 'react';
|
import { FC, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import logo from '../../../assets/plugin_store.png';
|
import logo from '../../../assets/plugin_store.png';
|
||||||
import Logger from '../../logger';
|
import Logger from '../../logger';
|
||||||
@@ -25,6 +26,8 @@ const StorePage: FC<{}> = () => {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const res = await getPluginList();
|
const res = await getPluginList();
|
||||||
@@ -54,13 +57,13 @@ const StorePage: FC<{}> = () => {
|
|||||||
}}
|
}}
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
title: 'Browse',
|
title: t('Store.store_tabs.title'),
|
||||||
content: <BrowseTab children={{ data: data }} />,
|
content: <BrowseTab children={{ data: data }} />,
|
||||||
id: 'browse',
|
id: 'browse',
|
||||||
renderTabAddon: () => <span className={TabCount}>{data.length}</span>,
|
renderTabAddon: () => <span className={TabCount}>{data.length}</span>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'About',
|
title: t('Store.store_tabs.about'),
|
||||||
content: <AboutTab />,
|
content: <AboutTab />,
|
||||||
id: 'about',
|
id: 'about',
|
||||||
},
|
},
|
||||||
@@ -73,10 +76,12 @@ const StorePage: FC<{}> = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
|
const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const sortOptions = useMemo(
|
const sortOptions = useMemo(
|
||||||
(): DropdownOption[] => [
|
(): DropdownOption[] => [
|
||||||
{ data: 1, label: 'Alphabetical (A to Z)' },
|
{ data: 1, label: t('Store.store_tabs.alph_desc') },
|
||||||
{ data: 2, label: 'Alphabetical (Z to A)' },
|
{ data: 2, label: t('Store.store_tabs.alph_asce') },
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -105,11 +110,11 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
|
|||||||
width: '47.5%',
|
width: '47.5%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="DialogLabel">Sort</span>
|
<span className="DialogLabel">{t("Store.store_sort.label")}</span>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menuLabel="Sort"
|
menuLabel={t("Store.store_sort.label") as string}
|
||||||
rgOptions={sortOptions}
|
rgOptions={sortOptions}
|
||||||
strDefaultLabel="Last Updated (Newest)"
|
strDefaultLabel={t("Store.store_sort.label_def") as string}
|
||||||
selectedOption={selectedSort}
|
selectedOption={selectedSort}
|
||||||
onChange={(e) => setSort(e.data)}
|
onChange={(e) => setSort(e.data)}
|
||||||
/>
|
/>
|
||||||
@@ -122,11 +127,11 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
|
|||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="DialogLabel">Filter</span>
|
<span className="DialogLabel">{t("Store.store_filter.label")}</span>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menuLabel="Filter"
|
menuLabel={t("Store.store_filter.label")}
|
||||||
rgOptions={filterOptions}
|
rgOptions={filterOptions}
|
||||||
strDefaultLabel="All"
|
strDefaultLabel={t("Store.store_filter.label_def")}
|
||||||
selectedOption={selectedFilter}
|
selectedOption={selectedFilter}
|
||||||
onChange={(e) => setFilter(e.data)}
|
onChange={(e) => setFilter(e.data)}
|
||||||
/>
|
/>
|
||||||
@@ -136,7 +141,7 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
|
|||||||
<div style={{ justifyContent: 'center', display: 'flex' }}>
|
<div style={{ justifyContent: 'center', display: 'flex' }}>
|
||||||
<Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
|
<Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<TextField label="Search" value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
|
<TextField label={t("Store.store_search.label")} value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
</Focusable>
|
</Focusable>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,11 +156,11 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
|
|||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="DialogLabel">Sort</span>
|
<span className="DialogLabel">{t('Store.store_sort.label')}</span>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menuLabel="Sort"
|
menuLabel={t('Store.store_sort.label') as string}
|
||||||
rgOptions={sortOptions}
|
rgOptions={sortOptions}
|
||||||
strDefaultLabel="Last Updated (Newest)"
|
strDefaultLabel={t('Store.store_sort.label_def') as string}
|
||||||
selectedOption={selectedSort}
|
selectedOption={selectedSort}
|
||||||
onChange={(e) => setSort(e.data)}
|
onChange={(e) => setSort(e.data)}
|
||||||
/>
|
/>
|
||||||
@@ -165,7 +170,11 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
|
|||||||
<div style={{ justifyContent: 'center', display: 'flex' }}>
|
<div style={{ justifyContent: 'center', display: 'flex' }}>
|
||||||
<Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
|
<Focusable style={{ display: 'flex', alignItems: 'center', width: '96%' }}>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<TextField label="Search" value={searchFieldValue} onChange={(e) => setSearchValue(e.target.value)} />
|
<TextField
|
||||||
|
label={t('Store.store_search.label')}
|
||||||
|
value={searchFieldValue}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Focusable>
|
</Focusable>
|
||||||
</div>
|
</div>
|
||||||
@@ -192,6 +201,8 @@ const BrowseTab: FC<{ children: { data: StorePlugin[] } }> = (data) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AboutTab: FC<{}> = () => {
|
const AboutTab: FC<{}> = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -216,7 +227,7 @@ const AboutTab: FC<{}> = () => {
|
|||||||
/>
|
/>
|
||||||
<span className="deckyStoreAboutHeader">Testing</span>
|
<span className="deckyStoreAboutHeader">Testing</span>
|
||||||
<span>
|
<span>
|
||||||
Please consider testing new plugins to help the Decky Loader team!{' '}
|
{t('Store.store_testing_cta')}{' '}
|
||||||
<a
|
<a
|
||||||
href="https://deckbrew.xyz/testing"
|
href="https://deckbrew.xyz/testing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -227,13 +238,10 @@ const AboutTab: FC<{}> = () => {
|
|||||||
deckbrew.xyz/testing
|
deckbrew.xyz/testing
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span className="deckyStoreAboutHeader">Contributing</span>
|
<span className="deckyStoreAboutHeader">{t('Store.store_contrib.label')}</span>
|
||||||
<span>
|
<span>{t('Store.store_contrib.desc')}</span>
|
||||||
If you would like to contribute to the Decky Plugin Store, check the SteamDeckHomebrew/decky-plugin-template
|
<span className="deckyStoreAboutHeader">{t('Store.store_source.label')}</span>
|
||||||
repository on GitHub. Information on development and distribution is available in the README.
|
<span>{t('Store.store_source.desc')}</span>
|
||||||
</span>
|
|
||||||
<span className="deckyStoreAboutHeader">Source Code</span>
|
|
||||||
<span>All plugin source code is available on SteamDeckHomebrew/decky-plugin-database repository on GitHub.</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
staticClasses,
|
staticClasses,
|
||||||
updaterFieldClasses,
|
updaterFieldClasses,
|
||||||
} from 'decky-frontend-lib';
|
} from 'decky-frontend-lib';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaReact } from 'react-icons/fa';
|
import { FaReact } from 'react-icons/fa';
|
||||||
|
|
||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
@@ -58,9 +59,11 @@ export async function setShowValveInternal(show: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function setShouldConnectToReactDevTools(enable: boolean) {
|
export async function setShouldConnectToReactDevTools(enable: boolean) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
window.DeckyPluginLoader.toaster.toast({
|
window.DeckyPluginLoader.toaster.toast({
|
||||||
title: (enable ? 'Enabling' : 'Disabling') + ' React DevTools',
|
title: (enable ? t('Developer.enabling') : t('Developer.disabling')) + ' React DevTools',
|
||||||
body: 'Reloading in 5 seconds',
|
body: t('Developer.5secreload'),
|
||||||
icon: <FaReact />,
|
icon: <FaReact />,
|
||||||
});
|
});
|
||||||
await sleep(5000);
|
await sleep(5000);
|
||||||
|
|||||||
+33
-1
@@ -1,4 +1,8 @@
|
|||||||
import { Navigation, Router, sleep } from 'decky-frontend-lib';
|
import { Navigation, Router, sleep } from 'decky-frontend-lib';
|
||||||
|
import i18n from 'i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import Backend from 'i18next-http-backend';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
import PluginLoader from './plugin-loader';
|
import PluginLoader from './plugin-loader';
|
||||||
import { DeckyUpdater } from './updater';
|
import { DeckyUpdater } from './updater';
|
||||||
@@ -36,9 +40,35 @@ declare global {
|
|||||||
(async () => {
|
(async () => {
|
||||||
window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text());
|
window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text());
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(Backend)
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
load: 'currentOnly',
|
||||||
|
detection: {
|
||||||
|
order: ['querystring', 'navigator'],
|
||||||
|
lookupQuerystring: 'lng',
|
||||||
|
},
|
||||||
|
//debug: true,
|
||||||
|
fallbackLng: 'en-US',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: true,
|
||||||
|
},
|
||||||
|
returnEmptyString: false,
|
||||||
|
backend: {
|
||||||
|
loadPath: 'http://127.0.0.1:1337/locales/{{lng}}.json',
|
||||||
|
customHeaders: {
|
||||||
|
Authentication: window.deckyAuthToken,
|
||||||
|
},
|
||||||
|
requestOptions: {
|
||||||
|
credentials: 'include',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
window.DeckyPluginLoader?.dismountAll();
|
window.DeckyPluginLoader?.dismountAll();
|
||||||
window.DeckyPluginLoader?.deinit();
|
window.DeckyPluginLoader?.deinit();
|
||||||
|
|
||||||
window.DeckyPluginLoader = new PluginLoader();
|
window.DeckyPluginLoader = new PluginLoader();
|
||||||
window.DeckyPluginLoader.init();
|
window.DeckyPluginLoader.init();
|
||||||
window.importDeckyPlugin = function (name: string, version: string) {
|
window.importDeckyPlugin = function (name: string, version: string) {
|
||||||
@@ -62,3 +92,5 @@ declare global {
|
|||||||
|
|
||||||
setTimeout(() => window.syncDeckyPlugins(), 5000);
|
setTimeout(() => window.syncDeckyPlugins(), 5000);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
|
|||||||
@@ -98,7 +98,9 @@ class PluginLoader extends Logger {
|
|||||||
const versionInfo = await this.updateVersion();
|
const versionInfo = await this.updateVersion();
|
||||||
if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) {
|
if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) {
|
||||||
this.toaster.toast({
|
this.toaster.toast({
|
||||||
|
//title: t('PluginLoader.decky_title'),
|
||||||
title: 'Decky',
|
title: 'Decky',
|
||||||
|
//body: t('PluginLoader.decky_update_available', { tag_name: versionInfo?.remote?.tag_name }),
|
||||||
body: `Update to ${versionInfo?.remote?.tag_name} available!`,
|
body: `Update to ${versionInfo?.remote?.tag_name} available!`,
|
||||||
onClick: () => Router.Navigate('/decky/settings'),
|
onClick: () => Router.Navigate('/decky/settings'),
|
||||||
});
|
});
|
||||||
@@ -118,26 +120,35 @@ class PluginLoader extends Logger {
|
|||||||
const updates = await this.checkPluginUpdates();
|
const updates = await this.checkPluginUpdates();
|
||||||
if (updates?.size > 0) {
|
if (updates?.size > 0) {
|
||||||
this.toaster.toast({
|
this.toaster.toast({
|
||||||
|
//title: t('PluginLoader.decky_title'),
|
||||||
title: 'Decky',
|
title: 'Decky',
|
||||||
|
//body: t('PluginLoader.plugin_update', { count: updates.size }),
|
||||||
body: `Updates available for ${updates.size} plugin${updates.size > 1 ? 's' : ''}!`,
|
body: `Updates available for ${updates.size} plugin${updates.size > 1 ? 's' : ''}!`,
|
||||||
onClick: () => Router.Navigate('/decky/settings/plugins'),
|
onClick: () => Router.Navigate('/decky/settings/plugins'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addPluginInstallPrompt(artifact: string, version: string, request_id: string, hash: string) {
|
public addPluginInstallPrompt(
|
||||||
|
artifact: string,
|
||||||
|
version: string,
|
||||||
|
request_id: string,
|
||||||
|
hash: string,
|
||||||
|
install_type: number,
|
||||||
|
) {
|
||||||
showModal(
|
showModal(
|
||||||
<PluginInstallModal
|
<PluginInstallModal
|
||||||
artifact={artifact}
|
artifact={artifact}
|
||||||
version={version}
|
version={version}
|
||||||
hash={hash}
|
hash={hash}
|
||||||
|
installType={install_type}
|
||||||
onOK={() => this.callServerMethod('confirm_plugin_install', { request_id })}
|
onOK={() => this.callServerMethod('confirm_plugin_install', { request_id })}
|
||||||
onCancel={() => this.callServerMethod('cancel_plugin_install', { request_id })}
|
onCancel={() => this.callServerMethod('cancel_plugin_install', { request_id })}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public uninstallPlugin(name: string) {
|
public uninstallPlugin(name: string, title: string, button_text: string, description: string) {
|
||||||
showModal(
|
showModal(
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
onOK={async () => {
|
onOK={async () => {
|
||||||
@@ -146,10 +157,10 @@ class PluginLoader extends Logger {
|
|||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
// do nothing
|
// do nothing
|
||||||
}}
|
}}
|
||||||
strTitle={`Uninstall ${name}`}
|
strTitle={title}
|
||||||
strOKButtonText={'Uninstall'}
|
strOKButtonText={button_text}
|
||||||
>
|
>
|
||||||
Are you sure you want to uninstall {name}?
|
{description}
|
||||||
</ConfirmModal>,
|
</ConfirmModal>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -242,7 +253,17 @@ class PluginLoader extends Logger {
|
|||||||
version: version,
|
version: version,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
//this.error(t('PluginLoader.plugin_load_error.message', { name: name }), e);
|
||||||
this.error('Error loading plugin ' + name, e);
|
this.error('Error loading plugin ' + name, e);
|
||||||
|
/*const TheError: FC<{}> = () => (
|
||||||
|
<>
|
||||||
|
{t('PluginLoader.error')}:{' '}
|
||||||
|
<pre>
|
||||||
|
<code>{e instanceof Error ? e.stack : JSON.stringify(e)}</code>
|
||||||
|
</pre>
|
||||||
|
<>{t('PluginLoader.plugin_error_uninstall', { icon: "<FaCog style={{ display: 'inline' }} />" })}</>
|
||||||
|
</>
|
||||||
|
);*/
|
||||||
const TheError: FC<{}> = () => (
|
const TheError: FC<{}> = () => (
|
||||||
<>
|
<>
|
||||||
Error:{' '}
|
Error:{' '}
|
||||||
@@ -261,7 +282,12 @@ class PluginLoader extends Logger {
|
|||||||
content: <TheError />,
|
content: <TheError />,
|
||||||
icon: <FaExclamationCircle />,
|
icon: <FaExclamationCircle />,
|
||||||
});
|
});
|
||||||
this.toaster.toast({ title: 'Error loading ' + name, body: '' + e, icon: <FaExclamationCircle /> });
|
this.toaster.toast({
|
||||||
|
//title: t('PluginLoader.plugin_load_error.toast', { name: name }),
|
||||||
|
title: 'Error loading ' + name,
|
||||||
|
body: '' + e,
|
||||||
|
icon: <FaExclamationCircle />,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else throw new Error(`${name} frontend_bundle not OK`);
|
} else throw new Error(`${name} frontend_bundle not OK`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,3 +6,9 @@ export interface Plugin {
|
|||||||
onDismount?(): void;
|
onDismount?(): void;
|
||||||
alwaysRender?: boolean;
|
alwaysRender?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum InstallType {
|
||||||
|
INSTALL,
|
||||||
|
REINSTALL,
|
||||||
|
UPDATE,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Plugin } from './plugin';
|
import { InstallType, Plugin } from './plugin';
|
||||||
import { getSetting, setSetting } from './utils/settings';
|
import { getSetting, setSetting } from './utils/settings';
|
||||||
|
|
||||||
export enum Store {
|
export enum Store {
|
||||||
@@ -73,7 +73,7 @@ export async function installFromURL(url: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion) {
|
export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion, installType: InstallType) {
|
||||||
const artifactUrl =
|
const artifactUrl =
|
||||||
selectedVer.artifact ?? `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`;
|
selectedVer.artifact ?? `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`;
|
||||||
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
||||||
@@ -81,6 +81,7 @@ export async function requestPluginInstall(plugin: string, selectedVer: StorePlu
|
|||||||
artifact: artifactUrl,
|
artifact: artifactUrl,
|
||||||
version: selectedVer.name,
|
version: selectedVer.name,
|
||||||
hash: selectedVer.hash,
|
hash: selectedVer.hash,
|
||||||
|
install_type: installType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"suppressImplicitAnyIndexErrors": true,
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": ["src", "index.d.ts"],
|
"include": ["src", "index.d.ts"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|||||||
Reference in New Issue
Block a user