From 1e1e82ed71524ad5cb879e80fc4f7615d59fdba2 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 5 Aug 2023 17:10:54 -0400 Subject: remove useless main.py imports --- backend/main.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'backend') diff --git a/backend/main.py b/backend/main.py index b2e3e74a..ae11b066 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3,21 +3,19 @@ import sys from localplatform import (chmod, chown, service_stop, service_start, ON_WINDOWS, get_log_level, get_live_reload, get_server_port, get_server_host, get_chown_plugin_path, - get_unprivileged_user, get_unprivileged_path, get_privileged_path) if hasattr(sys, '_MEIPASS'): - chmod(sys._MEIPASS, 755) + chmod(sys._MEIPASS, 755) # type: ignore # Full imports from asyncio import new_event_loop, set_event_loop, sleep -from json import dumps, loads -from logging import DEBUG, INFO, basicConfig, getLogger -from os import getenv, path +from logging import basicConfig, getLogger +from os import path from traceback import format_exc import multiprocessing import aiohttp_cors # Partial imports -from aiohttp import client_exceptions, WSMsgType +from aiohttp import client_exceptions from aiohttp.web import Application, Response, get, run_app, static from aiohttp_jinja2 import setup as jinja_setup @@ -26,7 +24,7 @@ from browser import PluginBrowser from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, mkdir_as_user, get_system_pythonpaths, get_effective_user_id) -from injector import get_gamepadui_tab, Tab, get_tabs, close_old_tabs +from injector import get_gamepadui_tab, Tab, close_old_tabs from loader import Loader from settings import SettingsManager from updater import Updater -- cgit v1.2.3 From e2d708a6af0ec75c557b11d3a442af57240302b4 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 26 Aug 2023 22:06:01 -0400 Subject: begin adding static types to backend code --- backend/browser.py | 62 +++++++++++++++++++++----------- backend/helpers.py | 55 ++++++++++++---------------- backend/injector.py | 84 +++++++++++++++++++++++++------------------ backend/loader.py | 69 +++++++++++++++++++---------------- backend/localplatformlinux.py | 16 ++++----- backend/main.py | 10 +++--- backend/plugin.py | 20 ++++++----- backend/settings.py | 11 +++--- backend/utilities.py | 15 ++++---- 9 files changed, 189 insertions(+), 153 deletions(-) (limited to 'backend') diff --git a/backend/browser.py b/backend/browser.py index ce9b3dd7..358c05f9 100644 --- a/backend/browser.py +++ b/backend/browser.py @@ -4,53 +4,70 @@ import json # from pprint import pformat # Partial imports -from aiohttp import ClientSession, web -from asyncio import get_event_loop, sleep -from concurrent.futures import ProcessPoolExecutor +from aiohttp import ClientSession +from asyncio import sleep from hashlib import sha256 from io import BytesIO from logging import getLogger -from os import R_OK, W_OK, path, rename, listdir, access, mkdir +from os import R_OK, W_OK, path, listdir, access, mkdir from shutil import rmtree from time import time from zipfile import ZipFile from localplatform import chown, chmod +from enum import IntEnum +from typing import Dict, List, TypedDict # Local modules -from helpers import get_ssl_context, download_remote_binary_to_path -from injector import get_gamepadui_tab +from .loader import Loader, Plugins +from .helpers import get_ssl_context, download_remote_binary_to_path +from .settings import SettingsManager +from .injector import get_gamepadui_tab logger = getLogger("Browser") +class PluginInstallType(IntEnum): + INSTALL = 0 + REINSTALL = 1 + UPDATE = 2 + +class PluginInstallRequest(TypedDict): + name: str + artifact: str + version: str + hash: str + install_type: PluginInstallType + class PluginInstallContext: - def __init__(self, artifact, name, version, hash) -> None: + def __init__(self, artifact: str, name: str, version: str, hash: str) -> None: self.artifact = artifact self.name = name self.version = version self.hash = hash class PluginBrowser: - def __init__(self, plugin_path, plugins, loader, settings) -> None: + def __init__(self, plugin_path: str, plugins: Plugins, loader: Loader, settings: SettingsManager) -> None: self.plugin_path = plugin_path self.plugins = plugins self.loader = loader self.settings = settings - self.install_requests = {} + self.install_requests: Dict[str, PluginInstallContext | List[PluginInstallContext]] = {} - def _unzip_to_plugin_dir(self, zip, name, hash): + def _unzip_to_plugin_dir(self, zip: BytesIO, name: str, hash: str): zip_hash = sha256(zip.getbuffer()).hexdigest() if hash and (zip_hash != hash): return False zip_file = ZipFile(zip) zip_file.extractall(self.plugin_path) - plugin_dir = path.join(self.plugin_path, self.find_plugin_folder(name)) + plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None + plugin_dir = path.join(self.plugin_path, plugin_folder) if not chown(plugin_dir) or not chmod(plugin_dir, 555): logger.error(f"chown/chmod exited with a non-zero exit code") return False return True - async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath): + async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: str): rv = False try: packageJsonPath = path.join(pluginBasePath, 'package.json') @@ -91,7 +108,7 @@ class PluginBrowser: return rv """Return the filename (only) for the specified plugin""" - def find_plugin_folder(self, name): + def find_plugin_folder(self, name: str) -> str | None: for folder in listdir(self.plugin_path): try: with open(path.join(self.plugin_path, folder, 'plugin.json'), "r", encoding="utf-8") as f: @@ -102,11 +119,13 @@ class PluginBrowser: except: logger.debug(f"skipping {folder}") - async def uninstall_plugin(self, name): + async def uninstall_plugin(self, name: str): if self.loader.watcher: self.loader.watcher.disabled = True tab = await get_gamepadui_tab() - plugin_dir = path.join(self.plugin_path, self.find_plugin_folder(name)) + plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None + plugin_dir = path.join(self.plugin_path, ) try: logger.info("uninstalling " + name) logger.info(" at dir " + plugin_dir) @@ -133,7 +152,7 @@ class PluginBrowser: if self.loader.watcher: self.loader.watcher.disabled = False - async def _install(self, artifact, name, version, hash): + async def _install(self, artifact: str, name: str, version: str, hash: str): # Will be set later in code res_zip = None @@ -185,6 +204,7 @@ class PluginBrowser: ret = self._unzip_to_plugin_dir(res_zip, name, hash) if ret: plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None plugin_dir = path.join(self.plugin_path, plugin_folder) ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir) if ret: @@ -206,14 +226,14 @@ class PluginBrowser: if self.loader.watcher: self.loader.watcher.disabled = False - async def request_plugin_install(self, artifact, name, version, hash, install_type): + async def request_plugin_install(self, artifact: str, name: str, version: str, hash: str, install_type: PluginInstallType): request_id = str(time()) self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash) tab = await get_gamepadui_tab() await tab.open_websocket() await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}', {install_type})") - async def request_multiple_plugin_installs(self, requests): + async def request_multiple_plugin_installs(self, requests: List[PluginInstallRequest]): request_id = str(time()) self.install_requests[request_id] = [PluginInstallContext(req['artifact'], req['name'], req['version'], req['hash']) for req in requests] js_requests_parameter = ','.join([ @@ -224,17 +244,17 @@ class PluginBrowser: await tab.open_websocket() await tab.evaluate_js(f"DeckyPluginLoader.addMultiplePluginsInstallPrompt('{request_id}', [{js_requests_parameter}])") - async def confirm_plugin_install(self, request_id): + async def confirm_plugin_install(self, request_id: str): requestOrRequests = self.install_requests.pop(request_id) if isinstance(requestOrRequests, list): [await self._install(req.artifact, req.name, req.version, req.hash) for req in requestOrRequests] else: await self._install(requestOrRequests.artifact, requestOrRequests.name, requestOrRequests.version, requestOrRequests.hash) - def cancel_plugin_install(self, request_id): + def cancel_plugin_install(self, request_id: str): self.install_requests.pop(request_id) - def cleanup_plugin_settings(self, name): + def cleanup_plugin_settings(self, name: str): """Removes any settings related to a plugin. Propably called when a plugin is uninstalled. Args: diff --git a/backend/helpers.py b/backend/helpers.py index a1877fb8..4036db85 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -2,13 +2,13 @@ import re import ssl import uuid import os -import sys import subprocess from hashlib import sha256 from io import BytesIO import certifi -from aiohttp.web import Response, middleware +from aiohttp.web import Request, Response, middleware +from aiohttp.typedefs import Handler from aiohttp import ClientSession import localplatform from customtypes import UserType @@ -31,17 +31,17 @@ def get_csrf_token(): return csrf_token @middleware -async def csrf_middleware(request, handler): +async def csrf_middleware(request: Request, handler: Handler): if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): return await handler(request) - return Response(text='Forbidden', status='403') + return Response(text='Forbidden', status=403) # Get the default homebrew path unless a home_path is specified. home_path argument is deprecated -def get_homebrew_path(home_path = None) -> str: +def get_homebrew_path() -> str: return localplatform.get_unprivileged_path() # Recursively create path and chown as user -def mkdir_as_user(path): +def mkdir_as_user(path: str): path = os.path.realpath(path) os.makedirs(path, exist_ok=True) localplatform.chown(path) @@ -57,23 +57,18 @@ def get_loader_version() -> str: # returns the appropriate system python paths def get_system_pythonpaths() -> list[str]: - extra_args = {} - - if localplatform.ON_LINUX: - # run as normal normal user to also include user python paths - extra_args["user"] = localplatform.localplatform._get_user_id() - extra_args["env"] = {} - try: + # run as normal normal user if on linux to also include user python paths proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"], - capture_output=True, **extra_args) + # TODO make this less insane + capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # type: ignore return [x.strip() for x in proc.stdout.decode().strip().split("\n")] except Exception as e: logger.warn(f"Failed to execute get_system_pythonpaths(): {str(e)}") return [] # Download Remote Binaries to local Plugin -async def download_remote_binary_to_path(url, binHash, path) -> bool: +async def download_remote_binary_to_path(url: str, binHash: str, path: str) -> bool: rv = False try: if os.access(os.path.dirname(path), os.W_OK): @@ -110,46 +105,42 @@ def set_user_group() -> str: # Get the user id hosting the plugin loader def get_user_id() -> int: - return localplatform.localplatform._get_user_id() + return localplatform.localplatform._get_user_id() # pyright: ignore [reportPrivateUsage] # Get the user hosting the plugin loader def get_user() -> str: - return localplatform.localplatform._get_user() + return localplatform.localplatform._get_user() # pyright: ignore [reportPrivateUsage] # Get the effective user id of the running process def get_effective_user_id() -> int: - return localplatform.localplatform._get_effective_user_id() + return localplatform.localplatform._get_effective_user_id() # pyright: ignore [reportPrivateUsage] # Get the effective user of the running process def get_effective_user() -> str: - return localplatform.localplatform._get_effective_user() + return localplatform.localplatform._get_effective_user() # pyright: ignore [reportPrivateUsage] # Get the effective user group id of the running process def get_effective_user_group_id() -> int: - return localplatform.localplatform._get_effective_user_group_id() + return localplatform.localplatform._get_effective_user_group_id() # pyright: ignore [reportPrivateUsage] # Get the effective user group of the running process def get_effective_user_group() -> str: - return localplatform.localplatform._get_effective_user_group() + return localplatform.localplatform._get_effective_user_group() # pyright: ignore [reportPrivateUsage] # Get the user owner of the given file path. -def get_user_owner(file_path) -> str: - return localplatform.localplatform._get_user_owner(file_path) +def get_user_owner(file_path: str) -> str: + return localplatform.localplatform._get_user_owner(file_path) # pyright: ignore [reportPrivateUsage] -# Get the user group of the given file path. -def get_user_group(file_path) -> str: - return localplatform.localplatform._get_user_group(file_path) +# Get the user group of the given file path, or the user group hosting the plugin loader +def get_user_group(file_path: str | None = None) -> str: + return localplatform.localplatform._get_user_group(file_path) # pyright: ignore [reportPrivateUsage] # Get the group id of the user hosting the plugin loader def get_user_group_id() -> int: - return localplatform.localplatform._get_user_group_id() - -# Get the group of the user hosting the plugin loader -def get_user_group() -> str: - return localplatform.localplatform._get_user_group() + return localplatform.localplatform._get_user_group_id() # pyright: ignore [reportPrivateUsage] # Get the default home path unless a user is specified -def get_home_path(username = None) -> str: +def get_home_path(username: str | None = None) -> str: return localplatform.get_home_path(UserType.ROOT if username == "root" else UserType.HOST_USER) async def is_systemd_unit_active(unit_name: str) -> bool: diff --git a/backend/injector.py b/backend/injector.py index e3414fee..a217f689 100644 --- a/backend/injector.py +++ b/backend/injector.py @@ -2,10 +2,9 @@ from asyncio import sleep from logging import getLogger -from traceback import format_exc -from typing import List +from typing import Any, Callable, List, TypedDict, Dict -from aiohttp import ClientSession, WSMsgType +from aiohttp import ClientSession from aiohttp.client_exceptions import ClientConnectorError, ClientOSError from asyncio.exceptions import TimeoutError import uuid @@ -14,35 +13,43 @@ BASE_ADDRESS = "http://localhost:8080" logger = getLogger("Injector") +class _TabResponse(TypedDict): + title: str + id: str + url: str + webSocketDebuggerUrl: str class Tab: cmd_id = 0 - def __init__(self, res) -> None: - self.title = res["title"] - self.id = res["id"] - self.url = res["url"] - self.ws_url = res["webSocketDebuggerUrl"] + def __init__(self, res: _TabResponse) -> None: + self.title: str = res["title"] + self.id: str = res["id"] + self.url: str = res["url"] + self.ws_url: str = res["webSocketDebuggerUrl"] self.websocket = None self.client = None async def open_websocket(self): self.client = ClientSession() - self.websocket = await self.client.ws_connect(self.ws_url) + self.websocket = await self.client.ws_connect(self.ws_url) # type: ignore async def close_websocket(self): - await self.websocket.close() - await self.client.close() + if self.websocket: + await self.websocket.close() + if self.client: + await self.client.close() async def listen_for_message(self): - async for message in self.websocket: - data = message.json() - yield data - logger.warn(f"The Tab {self.title} socket has been disconnected while listening for messages.") - await self.close_websocket() + if self.websocket: + async for message in self.websocket: + data = message.json() + yield data + logger.warn(f"The Tab {self.title} socket has been disconnected while listening for messages.") + await self.close_websocket() - async def _send_devtools_cmd(self, dc, receive=True): + async def _send_devtools_cmd(self, dc: Dict[str, Any], receive: bool = True): if self.websocket: self.cmd_id += 1 dc["id"] = self.cmd_id @@ -54,7 +61,7 @@ class Tab: return None raise RuntimeError("Websocket not opened") - async def evaluate_js(self, js, run_async=False, manage_socket=True, get_result=True): + async def evaluate_js(self, js: str, run_async: bool | None = False, manage_socket: bool | None = True, get_result: bool = True): try: if manage_socket: await self.open_websocket() @@ -73,15 +80,16 @@ class Tab: await self.close_websocket() return res - async def has_global_var(self, var_name, manage_socket=True): + async def has_global_var(self, var_name: str, manage_socket: bool = True): res = await self.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False, manage_socket) + assert res is not None if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: return False return res["result"]["result"]["value"] - async def close(self, manage_socket=True): + async def close(self, manage_socket: bool = True): try: if manage_socket: await self.open_websocket() @@ -111,7 +119,7 @@ class Tab: "method": "Page.disable", }, False) - async def refresh(self, manage_socket=True): + async def refresh(self, manage_socket: bool = True): try: if manage_socket: await self.open_websocket() @@ -125,7 +133,7 @@ class Tab: await self.close_websocket() return - async def reload_and_evaluate(self, js, manage_socket=True): + async def reload_and_evaluate(self, js: str, manage_socket: bool = True): """ Reloads the current tab, with JS to run on load via debugger """ @@ -153,11 +161,13 @@ class Tab: } }, True) + assert breakpoint_res is not None + logger.info(breakpoint_res) # Page finishes loading when breakpoint hits - for x in range(20): + for _ in range(20): # this works around 1/5 of the time, so just send it 8 times. # the js accounts for being injected multiple times allowing only one instance to run at a time anyway await self._send_devtools_cmd({ @@ -176,7 +186,7 @@ class Tab: } }, False) - for x in range(4): + for _ in range(4): await self._send_devtools_cmd({ "method": "Debugger.resume" }, False) @@ -190,7 +200,7 @@ class Tab: await self.close_websocket() return - async def add_script_to_evaluate_on_new_document(self, js, add_dom_wrapper=True, manage_socket=True, get_result=True): + async def add_script_to_evaluate_on_new_document(self, js: str, add_dom_wrapper: bool = True, manage_socket: bool = True, get_result: bool = True): """ How the underlying call functions is not particularly clear from the devtools docs, so stealing puppeteer's description: @@ -253,7 +263,7 @@ class Tab: await self.close_websocket() return res - async def remove_script_to_evaluate_on_new_document(self, script_id, manage_socket=True): + async def remove_script_to_evaluate_on_new_document(self, script_id: str, manage_socket: bool = True): """ Removes a script from a page that was added with `add_script_to_evaluate_on_new_document` @@ -267,7 +277,7 @@ class Tab: if manage_socket: await self.open_websocket() - res = await self._send_devtools_cmd({ + await self._send_devtools_cmd({ "method": "Page.removeScriptToEvaluateOnNewDocument", "params": { "identifier": script_id @@ -278,15 +288,16 @@ class Tab: if manage_socket: await self.close_websocket() - async def has_element(self, element_name, manage_socket=True): + async def has_element(self, element_name: str, manage_socket: bool = True): res = await self.evaluate_js(f"document.getElementById('{element_name}') != null", False, manage_socket) + assert res is not None if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: return False return res["result"]["result"]["value"] - async def inject_css(self, style, manage_socket=True): + async def inject_css(self, style: str, manage_socket: bool = True): try: css_id = str(uuid.uuid4()) @@ -300,6 +311,8 @@ class Tab: }})() """, False, manage_socket) + assert result is not None + if "exceptionDetails" in result["result"]: return { "success": False, @@ -316,7 +329,7 @@ class Tab: "result": e } - async def remove_css(self, css_id, manage_socket=True): + async def remove_css(self, css_id: str, manage_socket: bool = True): try: result = await self.evaluate_js( f""" @@ -328,6 +341,8 @@ class Tab: }})() """, False, manage_socket) + assert result is not None + if "exceptionDetails" in result["result"]: return { "success": False, @@ -343,8 +358,9 @@ class Tab: "result": e } - async def get_steam_resource(self, url): + async def get_steam_resource(self, url: str): res = await self.evaluate_js(f'(async function test() {{ return await (await fetch("{url}")).text() }})()', True) + assert res is not None return res["result"]["result"]["value"] def __repr__(self): @@ -380,14 +396,14 @@ async def get_tabs() -> List[Tab]: raise Exception(f"/json did not return 200. {await res.text()}") -async def get_tab(tab_name) -> Tab: +async def get_tab(tab_name: str) -> Tab: tabs = await get_tabs() tab = next((i for i in tabs if i.title == tab_name), None) if not tab: raise ValueError(f"Tab {tab_name} not found") return tab -async def get_tab_lambda(test) -> Tab: +async def get_tab_lambda(test: Callable[[Tab], bool]) -> Tab: tabs = await get_tabs() tab = next((i for i in tabs if test(i)), None) if not tab: @@ -408,7 +424,7 @@ async def get_gamepadui_tab() -> Tab: raise ValueError(f"GamepadUI Tab not found") return tab -async def inject_to_tab(tab_name, js, run_async=False): +async def inject_to_tab(tab_name: str, js: str, run_async: bool = False): tab = await get_tab(tab_name) return await tab.evaluate_js(js, run_async) diff --git a/backend/loader.py b/backend/loader.py index d07b1c08..89bb0457 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -1,34 +1,40 @@ -from asyncio import Queue, sleep +from asyncio import AbstractEventLoop, Queue, sleep from json.decoder import JSONDecodeError from logging import getLogger from os import listdir, path from pathlib import Path from traceback import print_exc +from typing import Any, Tuple from aiohttp import web from os.path import exists -from watchdog.events import RegexMatchingEventHandler -from watchdog.observers import Observer +from watchdog.events import RegexMatchingEventHandler, DirCreatedEvent, DirModifiedEvent, FileCreatedEvent, FileModifiedEvent # type: ignore +from watchdog.observers import Observer # type: ignore -from injector import get_tab, get_gamepadui_tab -from plugin import PluginWrapper +from backend.main import PluginManager # type: ignore + +from .injector import get_tab, get_gamepadui_tab +from .plugin import PluginWrapper + +Plugins = dict[str, PluginWrapper] +ReloadQueue = Queue[Tuple[str, str, bool | None] | Tuple[str, str]] class FileChangeHandler(RegexMatchingEventHandler): - def __init__(self, queue, plugin_path) -> None: - super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) + def __init__(self, queue: ReloadQueue, plugin_path: str) -> None: + super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) # type: ignore self.logger = getLogger("file-watcher") self.plugin_path = plugin_path self.queue = queue self.disabled = True - def maybe_reload(self, src_path): + def maybe_reload(self, src_path: str): if self.disabled: return plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0] if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")): self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True)) - def on_created(self, event): + def on_created(self, event: DirCreatedEvent | FileCreatedEvent): src_path = event.src_path if "__pycache__" in src_path: return @@ -42,7 +48,7 @@ class FileChangeHandler(RegexMatchingEventHandler): self.logger.debug(f"file created: {src_path}") self.maybe_reload(src_path) - def on_modified(self, event): + def on_modified(self, event: DirModifiedEvent | FileModifiedEvent): src_path = event.src_path if "__pycache__" in src_path: return @@ -57,25 +63,25 @@ class FileChangeHandler(RegexMatchingEventHandler): self.maybe_reload(src_path) class Loader: - def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> None: + def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool =False) -> None: self.loop = loop self.logger = getLogger("Loader") self.plugin_path = plugin_path self.logger.info(f"plugin_path: {self.plugin_path}") - self.plugins : dict[str, PluginWrapper] = {} + self.plugins: Plugins = {} self.watcher = None self.live_reload = live_reload - self.reload_queue = Queue() + self.reload_queue: ReloadQueue = Queue() self.loop.create_task(self.handle_reloads()) if live_reload: self.observer = Observer() self.watcher = FileChangeHandler(self.reload_queue, plugin_path) - self.observer.schedule(self.watcher, self.plugin_path, recursive=True) + self.observer.schedule(self.watcher, self.plugin_path, recursive=True) # type: ignore self.observer.start() self.loop.create_task(self.enable_reload_wait()) - server_instance.add_routes([ + server_instance.web_app.add_routes([ web.get("/frontend/{path:.*}", self.handle_frontend_assets), web.get("/locales/{path:.*}", self.handle_frontend_locales), web.get("/plugins", self.get_plugins), @@ -93,15 +99,16 @@ class Loader: async def enable_reload_wait(self): if self.live_reload: await sleep(10) - self.logger.info("Hot reload enabled") - self.watcher.disabled = False + if self.watcher: + self.logger.info("Hot reload enabled") + self.watcher.disabled = False - async def handle_frontend_assets(self, request): + async def handle_frontend_assets(self, request: web.Request): file = path.join(path.dirname(__file__), "static", request.match_info["path"]) return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) - async def handle_frontend_locales(self, request): + async def handle_frontend_locales(self, request: web.Request): req_lang = request.match_info["path"] file = path.join(path.dirname(__file__), "locales", req_lang) if exists(file): @@ -110,23 +117,23 @@ class Loader: self.logger.info(f"Language {req_lang} not available, returning an empty dictionary") return web.json_response(data={}, headers={"Cache-Control": "no-cache"}) - async def get_plugins(self, request): + async def get_plugins(self, request: web.Request): 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]) - def handle_plugin_frontend_assets(self, request): + async def handle_plugin_frontend_assets(self, request: web.Request): plugin = self.plugins[request.match_info["plugin_name"]] file = path.join(self.plugin_path, plugin.plugin_directory, "dist/assets", request.match_info["path"]) return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) - def handle_frontend_bundle(self, request): + async def handle_frontend_bundle(self, request: web.Request): plugin = self.plugins[request.match_info["plugin_name"]] with open(path.join(self.plugin_path, plugin.plugin_directory, "dist/index.js"), "r", encoding="utf-8") as bundle: return web.Response(text=bundle.read(), content_type="application/javascript") - def import_plugin(self, file, plugin_directory, refresh=False, batch=False): + def import_plugin(self, file: str, plugin_directory: str, refresh: bool | None = False, batch: bool | None = False): try: plugin = PluginWrapper(file, plugin_directory, self.plugin_path) if plugin.name in self.plugins: @@ -146,7 +153,7 @@ class Loader: self.logger.error(f"Could not load {file}. {e}") print_exc() - async def dispatch_plugin(self, name, version): + async def dispatch_plugin(self, name: str, version: str | None): gpui_tab = await get_gamepadui_tab() await gpui_tab.evaluate_js(f"window.importDeckyPlugin('{name}', '{version}')") @@ -161,15 +168,15 @@ class Loader: async def handle_reloads(self): while True: args = await self.reload_queue.get() - self.import_plugin(*args) + self.import_plugin(*args) # type: ignore - async def handle_plugin_method_call(self, request): + async def handle_plugin_method_call(self, request: web.Request): res = {} plugin = self.plugins[request.match_info["plugin_name"]] method_name = request.match_info["method_name"] try: method_info = await request.json() - args = method_info["args"] + args: Any = method_info["args"] except JSONDecodeError: args = {} try: @@ -189,7 +196,7 @@ class Loader: can introduce it more smoothly and give people the chance to sample the new features even without plugin support. They will be removed once legacy plugins are no longer relevant. """ - async def load_plugin_main_view(self, request): + async def load_plugin_main_view(self, request: web.Request): plugin = self.plugins[request.match_info["name"]] with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), "r", encoding="utf-8") as template: template_data = template.read() @@ -201,7 +208,7 @@ class Loader: """ return web.Response(text=ret, content_type="text/html") - async def handle_sub_route(self, request): + async def handle_sub_route(self, request: web.Request): plugin = self.plugins[request.match_info["name"]] route_path = request.match_info["path"] self.logger.info(path) @@ -212,14 +219,14 @@ class Loader: return web.Response(text=ret) - async def get_steam_resource(self, request): + async def get_steam_resource(self, request: web.Request): tab = await get_tab("SP") try: return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html") except Exception as e: return web.Response(text=str(e), status=400) - async def handle_backend_reload_request(self, request): + async def handle_backend_reload_request(self, request: web.Request): plugin_name : str = request.match_info["plugin_name"] plugin = self.plugins[plugin_name] diff --git a/backend/localplatformlinux.py b/backend/localplatformlinux.py index 811db8a6..58b9dbc2 100644 --- a/backend/localplatformlinux.py +++ b/backend/localplatformlinux.py @@ -29,21 +29,17 @@ def _get_effective_user_group() -> str: return grp.getgrgid(_get_effective_user_group_id()).gr_name # Get the user owner of the given file path. -def _get_user_owner(file_path) -> str: +def _get_user_owner(file_path: str) -> str: return pwd.getpwuid(os.stat(file_path).st_uid).pw_name -# Get the user group of the given file path. -def _get_user_group(file_path) -> str: - return grp.getgrgid(os.stat(file_path).st_gid).gr_name +# Get the user group of the given file path, or the user group hosting the plugin loader +def _get_user_group(file_path: str | None = None) -> str: + return grp.getgrgid(os.stat(file_path).st_gid if file_path is not None else _get_user_group_id()).gr_name # Get the group id of the user hosting the plugin loader def _get_user_group_id() -> int: return pwd.getpwuid(_get_user_id()).pw_gid -# Get the group of the user hosting the plugin loader -def _get_user_group() -> str: - return grp.getgrgid(_get_user_group_id()).gr_name - def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: user_str = "" @@ -146,7 +142,7 @@ def get_privileged_path() -> str: return path -def _parent_dir(path : str) -> str: +def _parent_dir(path : str | None) -> str | None: if path == None: return None @@ -166,7 +162,7 @@ def get_unprivileged_path() -> str: # Expected path of loader binary is /home/deck/homebrew/service/PluginLoader path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0]))) - if not os.path.exists(path): + if path != None and not os.path.exists(path): path = None if path == None: diff --git a/backend/main.py b/backend/main.py index ae11b066..433b202f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,16 +7,16 @@ from localplatform import (chmod, chown, service_stop, service_start, if hasattr(sys, '_MEIPASS'): chmod(sys._MEIPASS, 755) # type: ignore # Full imports -from asyncio import new_event_loop, set_event_loop, sleep +from asyncio import AbstractEventLoop, new_event_loop, set_event_loop, sleep from logging import basicConfig, getLogger from os import path from traceback import format_exc import multiprocessing -import aiohttp_cors +import aiohttp_cors # type: ignore # Partial imports from aiohttp import client_exceptions -from aiohttp.web import Application, Response, get, run_app, static +from aiohttp.web import Application, Response, get, run_app, static # type: ignore from aiohttp_jinja2 import setup as jinja_setup # local modules @@ -51,7 +51,7 @@ if get_chown_plugin_path() == True: chown_plugin_dir() class PluginManager: - def __init__(self, loop) -> None: + def __init__(self, loop: AbstractEventLoop) -> None: self.loop = loop self.web_app = Application() self.web_app.middlewares.append(csrf_middleware) @@ -62,7 +62,7 @@ class PluginManager: allow_credentials=True ) }) - self.plugin_loader = Loader(self.web_app, plugin_path, self.loop, get_live_reload()) + self.plugin_loader = Loader(self, plugin_path, self.loop, get_live_reload()) self.settings = SettingsManager("loader", path.join(get_privileged_path(), "settings")) self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings) self.utilities = Utilities(self) diff --git a/backend/plugin.py b/backend/plugin.py index 026a6b09..781d9f7b 100644 --- a/backend/plugin.py +++ b/backend/plugin.py @@ -1,7 +1,6 @@ import multiprocessing from asyncio import (Lock, get_event_loop, new_event_loop, set_event_loop, sleep) -from concurrent.futures import ProcessPoolExecutor from importlib.util import module_from_spec, spec_from_file_location from json import dumps, load, loads from logging import getLogger @@ -9,14 +8,14 @@ from traceback import format_exc from os import path, environ from signal import SIGINT, signal from sys import exit, path as syspath -from time import time +from typing import Any, Dict from localsocket import LocalSocket from localplatform import setgid, setuid, get_username, get_home_path from customtypes import UserType import helpers class PluginWrapper: - def __init__(self, file, plugin_directory, plugin_path) -> None: + def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: self.file = file self.plugin_path = plugin_path self.plugin_directory = plugin_directory @@ -73,14 +72,17 @@ class PluginWrapper: helpers.mkdir_as_user(environ["DECKY_PLUGIN_LOG_DIR"]) environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory) environ["DECKY_PLUGIN_NAME"] = self.name - environ["DECKY_PLUGIN_VERSION"] = self.version + if self.version: + environ["DECKY_PLUGIN_VERSION"] = self.version environ["DECKY_PLUGIN_AUTHOR"] = self.author # append the plugin's `py_modules` to the recognized python paths syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules")) spec = spec_from_file_location("_", self.file) + assert spec is not None module = module_from_spec(spec) + assert spec.loader is not None spec.loader.exec_module(module) self.Plugin = module.Plugin @@ -118,7 +120,8 @@ class PluginWrapper: get_event_loop().close() raise Exception("Closing message listener") - d = {"res": None, "success": True} + # TODO there is definitely a better way to type this + d: Dict[str, Any] = {"res": None, "success": True} try: d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"]) except Exception as e: @@ -137,17 +140,18 @@ class PluginWrapper: if self.passive: return - async def _(self): + async def _(self: PluginWrapper): await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) await self.socket.close_socket_connection() get_event_loop().create_task(_(self)) - async def execute_method(self, method_name, kwargs): + async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]): if self.passive: raise RuntimeError("This plugin is passive (aka does not implement main.py)") async with self.method_call_lock: - reader, writer = await self.socket.get_socket_connection() + # reader, writer = + await self.socket.get_socket_connection() await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False)) diff --git a/backend/settings.py b/backend/settings.py index c00e6a82..26dfc97f 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -1,5 +1,6 @@ from json import dump, load from os import mkdir, path, listdir, rename +from typing import Any, Dict from localplatform import chown, folder_owner, get_chown_plugin_path from customtypes import UserType @@ -7,7 +8,7 @@ from helpers import get_homebrew_path class SettingsManager: - def __init__(self, name, settings_directory = None) -> None: + def __init__(self, name: str, settings_directory: str | None = None) -> None: wrong_dir = get_homebrew_path() if settings_directory == None: settings_directory = path.join(wrong_dir, "settings") @@ -31,11 +32,11 @@ class SettingsManager: if folder_owner(settings_directory) != expected_user: chown(settings_directory, expected_user, False) - self.settings = {} + self.settings: Dict[str, Any] = {} try: open(self.path, "x", encoding="utf-8") - except FileExistsError as e: + except FileExistsError as _: self.read() pass @@ -51,9 +52,9 @@ class SettingsManager: with open(self.path, "w+", encoding="utf-8") as file: dump(self.settings, file, indent=4, ensure_ascii=False) - def getSetting(self, key, default=None): + def getSetting(self, key: str, default: Any = None) -> Any: return self.settings.get(key, default) - def setSetting(self, key, value): + def setSetting(self, key: str, value: Any) -> Any: self.settings[key] = value self.commit() diff --git a/backend/utilities.py b/backend/utilities.py index bcb35578..72b6f008 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -1,26 +1,27 @@ import uuid -import os from json.decoder import JSONDecodeError from os.path import splitext import re from traceback import format_exc -from stat import FILE_ATTRIBUTE_HIDDEN +from stat import FILE_ATTRIBUTE_HIDDEN # type: ignore -from asyncio import sleep, start_server, gather, open_connection +from asyncio import start_server, gather, open_connection from aiohttp import ClientSession, web +from typing import Dict from logging import getLogger +from backend.browser import PluginInstallType +from backend.main import PluginManager from injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab from pathlib import Path from localplatform import ON_WINDOWS import helpers -import subprocess from localplatform import service_stop, service_start, get_home_path, get_username class Utilities: - def __init__(self, context) -> None: + def __init__(self, context: PluginManager) -> None: self.context = context - self.util_methods = { + self.util_methods: Dict[] = { "ping": self.ping, "http_request": self.http_request, "install_plugin": self.install_plugin, @@ -69,7 +70,7 @@ class Utilities: res["success"] = False return web.json_response(res) - async def install_plugin(self, artifact="", name="No name", version="dev", hash=False, install_type=0): + async def install_plugin(self, artifact="", name="No name", version="dev", hash=False, install_type=PluginInstallType.INSTALL): return await self.context.plugin_browser.request_plugin_install( artifact=artifact, name=name, -- cgit v1.2.3 From a7c358844c96b7fb52f4a7e8d16a5cd928a1ca12 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Mon, 18 Sep 2023 00:31:54 +0300 Subject: type hints on main,plugin,updater,utilites.localsocket --- backend/localsocket.py | 37 +++++++++++++++---------- backend/main.py | 15 +++++----- backend/plugin.py | 2 +- backend/updater.py | 27 ++++++++++++------ backend/utilities.py | 74 +++++++++++++++++++++++++++++--------------------- 5 files changed, 92 insertions(+), 63 deletions(-) (limited to 'backend') diff --git a/backend/localsocket.py b/backend/localsocket.py index ef0e3933..3659da03 100644 --- a/backend/localsocket.py +++ b/backend/localsocket.py @@ -1,10 +1,13 @@ -import asyncio, time, random +import asyncio, time +from typing import Awaitable, Callable +import random + from localplatform import ON_WINDOWS BUFFER_LIMIT = 2 ** 20 # 1 MiB class UnixSocket: - def __init__(self, on_new_message): + def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): ''' on_new_message takes 1 string argument. It's return value gets used, if not None, to write data to the socket. @@ -46,28 +49,32 @@ class UnixSocket: self.reader = None async def read_single_line(self) -> str|None: - reader, writer = await self.get_socket_connection() + reader, _ = await self.get_socket_connection() - if self.reader == None: - return None + try: + assert reader + except AssertionError: + return return await self._read_single_line(reader) async def write_single_line(self, message : str): - reader, writer = await self.get_socket_connection() + _, writer = await self.get_socket_connection() - if self.writer == None: - return; + try: + assert writer + except AssertionError: + return await self._write_single_line(writer, message) - async def _read_single_line(self, reader) -> str: + async def _read_single_line(self, reader: asyncio.StreamReader) -> str: line = bytearray() while True: try: line.extend(await reader.readuntil()) except asyncio.LimitOverrunError: - line.extend(await reader.read(reader._limit)) + line.extend(await reader.read(reader._limit)) # type: ignore continue except asyncio.IncompleteReadError as err: line.extend(err.partial) @@ -77,27 +84,27 @@ class UnixSocket: return line.decode("utf-8") - async def _write_single_line(self, writer, message : str): + async def _write_single_line(self, writer: asyncio.StreamWriter, message : str): if not message.endswith("\n"): message += "\n" writer.write(message.encode("utf-8")) await writer.drain() - async def _listen_for_method_call(self, reader, writer): + async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): while True: line = await self._read_single_line(reader) try: res = await self.on_new_message(line) - except Exception as e: + except Exception: return if res != None: await self._write_single_line(writer, res) class PortSocket (UnixSocket): - def __init__(self, on_new_message): + def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): ''' on_new_message takes 1 string argument. It's return value gets used, if not None, to write data to the socket. @@ -125,7 +132,7 @@ class PortSocket (UnixSocket): return True if ON_WINDOWS: - class LocalSocket (PortSocket): + class LocalSocket (PortSocket): # type: ignore pass else: class LocalSocket (UnixSocket): diff --git a/backend/main.py b/backend/main.py index 433b202f..8857fb22 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,6 @@ # Change PyInstaller files permissions import sys +from typing import Dict from localplatform import (chmod, chown, service_stop, service_start, ON_WINDOWS, get_log_level, get_live_reload, get_server_port, get_server_host, get_chown_plugin_path, @@ -16,7 +17,7 @@ import multiprocessing import aiohttp_cors # type: ignore # Partial imports from aiohttp import client_exceptions -from aiohttp.web import Application, Response, get, run_app, static # type: ignore +from aiohttp.web import Application, Response, Request, get, run_app, static # type: ignore from aiohttp_jinja2 import setup as jinja_setup # local modules @@ -70,7 +71,7 @@ class PluginManager: jinja_setup(self.web_app) - async def startup(_): + async def startup(_: Application): if self.settings.getSetting("cef_forward", False): self.loop.create_task(service_start(REMOTE_DEBUGGER_UNIT)) else: @@ -84,16 +85,16 @@ class PluginManager: self.web_app.add_routes([get("/auth/token", self.get_auth_token)]) for route in list(self.web_app.router.routes()): - self.cors.add(route) + self.cors.add(route) # type: ignore self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))]) self.web_app.add_routes([static("/legacy", path.join(path.dirname(__file__), 'legacy'))]) - def exception_handler(self, loop, context): + def exception_handler(self, loop: AbstractEventLoop, context: Dict[str, str]): if context["message"] == "Unclosed connection": return loop.default_exception_handler(context) - async def get_auth_token(self, request): + async def get_auth_token(self, request: Request): return Response(text=get_csrf_token()) async def load_plugins(self): @@ -144,7 +145,7 @@ class PluginManager: # This is because of https://github.com/aio-libs/aiohttp/blob/3ee7091b40a1bc58a8d7846e7878a77640e96996/aiohttp/client_ws.py#L321 logger.info("CEF has disconnected...") # At this point the loop starts again and we connect to the freshly started Steam client once it is ready. - except Exception as e: + except Exception: logger.error("Exception while reading page events " + format_exc()) await tab.close_websocket() pass @@ -154,7 +155,7 @@ class PluginManager: # logger.info("Plugin loader isn't present in Steam anymore, reinjecting...") # await self.inject_javascript(tab) - async def inject_javascript(self, tab: Tab, first=False, request=None): + async def inject_javascript(self, tab: Tab, first: bool=False, request: Request|None=None): logger.info("Loading Decky frontend!") try: if first: diff --git a/backend/plugin.py b/backend/plugin.py index 781d9f7b..5c1e099f 100644 --- a/backend/plugin.py +++ b/backend/plugin.py @@ -20,7 +20,7 @@ class PluginWrapper: self.plugin_path = plugin_path self.plugin_directory = plugin_directory self.method_call_lock = Lock() - self.socket = LocalSocket(self._on_new_message) + self.socket: LocalSocket = LocalSocket(self._on_new_message) self.version = None diff --git a/backend/updater.py b/backend/updater.py index 6b38dd25..d7a3d712 100644 --- a/backend/updater.py +++ b/backend/updater.py @@ -1,23 +1,31 @@ import os import shutil -import uuid from asyncio import sleep -from ensurepip import version from json.decoder import JSONDecodeError from logging import getLogger from os import getcwd, path, remove +from typing import List, TypedDict +from backend.main import PluginManager from localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux from aiohttp import ClientSession, web import helpers -from injector import get_gamepadui_tab, inject_to_tab +from injector import get_gamepadui_tab from settings import SettingsManager logger = getLogger("Updater") +class RemoteVerAsset(TypedDict): + name: str + browser_download_url: str +class RemoteVer(TypedDict): + tag_name: str + prerelease: bool + assets: List[RemoteVerAsset] + class Updater: - def __init__(self, context) -> None: + def __init__(self, context: PluginManager) -> None: self.context = context self.settings = self.context.settings # Exposes updater methods to frontend @@ -28,8 +36,8 @@ class Updater: "do_restart": self.do_restart, "check_for_updates": self.check_for_updates } - self.remoteVer = None - self.allRemoteVers = None + self.remoteVer: RemoteVer | None = None + self.allRemoteVers: List[RemoteVer] = [] self.localVer = helpers.get_loader_version() try: @@ -44,7 +52,7 @@ class Updater: ]) context.loop.create_task(self.version_reloader()) - async def _handle_server_method_call(self, request): + async def _handle_server_method_call(self, request: web.Request): method_name = request.match_info["method_name"] try: args = await request.json() @@ -52,7 +60,7 @@ class Updater: args = {} res = {} try: - r = await self.updater_methods[method_name](**args) + r = await self.updater_methods[method_name](**args) # type: ignore res["result"] = r res["success"] = True except Exception as e: @@ -105,7 +113,7 @@ class Updater: selectedBranch = self.get_branch(self.context.settings) async with ClientSession() as web: async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", ssl=helpers.get_ssl_context()) as res: - remoteVersions = await res.json() + remoteVersions: List[RemoteVer] = await res.json() if selectedBranch == 0: logger.debug("release type: release") remoteVersions = list(filter(lambda ver: ver["tag_name"].startswith("v") and not ver["prerelease"] and not ver["tag_name"].find("-pre") > 0 and ver["tag_name"], remoteVersions)) @@ -142,6 +150,7 @@ class Updater: async def do_update(self): logger.debug("Starting update.") + assert self.remoteVer version = self.remoteVer["tag_name"] download_url = None download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe" diff --git a/backend/utilities.py b/backend/utilities.py index 72b6f008..d45bec9b 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -1,3 +1,4 @@ +from os import stat_result import uuid from json.decoder import JSONDecodeError from os.path import splitext @@ -5,12 +6,12 @@ import re from traceback import format_exc from stat import FILE_ATTRIBUTE_HIDDEN # type: ignore -from asyncio import start_server, gather, open_connection +from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection from aiohttp import ClientSession, web -from typing import Dict +from typing import Callable, Coroutine, Dict, Any, List, TypedDict from logging import getLogger -from backend.browser import PluginInstallType +from backend.browser import PluginInstallRequest, PluginInstallType from backend.main import PluginManager from injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab from pathlib import Path @@ -18,10 +19,15 @@ from localplatform import ON_WINDOWS import helpers from localplatform import service_stop, service_start, get_home_path, get_username +class FilePickerObj(TypedDict): + file: Path + filest: stat_result + is_dir: bool + class Utilities: def __init__(self, context: PluginManager) -> None: self.context = context - self.util_methods: Dict[] = { + self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = { "ping": self.ping, "http_request": self.http_request, "install_plugin": self.install_plugin, @@ -54,7 +60,7 @@ class Utilities: web.post("/methods/{method_name}", self._handle_server_method_call) ]) - async def _handle_server_method_call(self, request): + async def _handle_server_method_call(self, request: web.Request): method_name = request.match_info["method_name"] try: args = await request.json() @@ -70,7 +76,7 @@ class Utilities: res["success"] = False return web.json_response(res) - async def install_plugin(self, artifact="", name="No name", version="dev", hash=False, install_type=PluginInstallType.INSTALL): + async def install_plugin(self, artifact: str="", name: str="No name", version: str="dev", hash: str="", install_type: PluginInstallType=PluginInstallType.INSTALL): return await self.context.plugin_browser.request_plugin_install( artifact=artifact, name=name, @@ -79,21 +85,21 @@ class Utilities: install_type=install_type ) - async def install_plugins(self, requests): + async def install_plugins(self, requests: List[PluginInstallRequest]): return await self.context.plugin_browser.request_multiple_plugin_installs( requests=requests ) - async def confirm_plugin_install(self, request_id): + async def confirm_plugin_install(self, request_id: str): return await self.context.plugin_browser.confirm_plugin_install(request_id) - def cancel_plugin_install(self, request_id): + async def cancel_plugin_install(self, request_id: str): return self.context.plugin_browser.cancel_plugin_install(request_id) - async def uninstall_plugin(self, name): + async def uninstall_plugin(self, name: str): return await self.context.plugin_browser.uninstall_plugin(name) - async def http_request(self, method="", url="", **kwargs): + async def http_request(self, method: str="", url: str="", **kwargs: Any): async with ClientSession() as web: res = await web.request(method, url, ssl=helpers.get_ssl_context(), **kwargs) text = await res.text() @@ -103,12 +109,13 @@ class Utilities: "body": text } - async def ping(self, **kwargs): + async def ping(self, **kwargs: Any): return "pong" - async def execute_in_tab(self, tab, run_async, code): + async def execute_in_tab(self, tab: str, run_async: bool, code: str): try: result = await inject_to_tab(tab, code, run_async) + assert result if "exceptionDetails" in result["result"]: return { "success": False, @@ -125,7 +132,7 @@ class Utilities: "result": e } - async def inject_css_into_tab(self, tab, style): + async def inject_css_into_tab(self, tab: str, style: str): try: css_id = str(uuid.uuid4()) @@ -139,7 +146,7 @@ class Utilities: }})() """, False) - if "exceptionDetails" in result["result"]: + if result and "exceptionDetails" in result["result"]: return { "success": False, "result": result["result"] @@ -155,7 +162,7 @@ class Utilities: "result": e } - async def remove_css_from_tab(self, tab, css_id): + async def remove_css_from_tab(self, tab: str, css_id: str): try: result = await inject_to_tab(tab, f""" @@ -167,7 +174,7 @@ class Utilities: }})() """, False) - if "exceptionDetails" in result["result"]: + if result and "exceptionDetails" in result["result"]: return { "success": False, "result": result @@ -182,10 +189,10 @@ class Utilities: "result": e } - async def get_setting(self, key, default): + async def get_setting(self, key: str, default: Any): return self.context.settings.getSetting(key, default) - async def set_setting(self, key, value): + async def set_setting(self, key: str, value: Any): return self.context.settings.setSetting(key, value) async def allow_remote_debugging(self): @@ -210,17 +217,18 @@ class Utilities: if path == None: path = get_home_path() - path = Path(path).resolve() + path_obj = Path(path).resolve() - files, folders = [], [] + files: List[FilePickerObj] = [] + folders: List[FilePickerObj] = [] #Resolving all files/folders in the requested directory - for file in path.iterdir(): + for file in path_obj.iterdir(): if file.exists(): filest = file.stat() is_hidden = file.name.startswith('.') if ON_WINDOWS and not is_hidden: - is_hidden = bool(filest.st_file_attributes & FILE_ATTRIBUTE_HIDDEN) + is_hidden = bool(filest.st_file_attributes & FILE_ATTRIBUTE_HIDDEN) # type: ignore if include_folders and file.is_dir(): if (is_hidden and include_hidden) or not is_hidden: folders.append({"file": file, "filest": filest, "is_dir": True}) @@ -234,9 +242,9 @@ class Utilities: if filter_for is not None: try: if re.compile(filter_for): - files = filter(lambda file: re.search(filter_for, file.name) != None, files) + files = list(filter(lambda file: re.search(filter_for, file["file"].name) != None, files)) except re.error: - files = filter(lambda file: file.name.find(filter_for) != -1, files) + files = list(filter(lambda file: file["file"].name.find(filter_for) != -1, files)) # Ordering logic ord_arg = order_by.split("_") @@ -256,6 +264,9 @@ class Utilities: files.sort(key=lambda x: x['filest'].st_size, reverse = not rev) # Folders has no file size, order by name instead folders.sort(key=lambda x: x['file'].name.casefold()) + case _: + files.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) + folders.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) #Constructing the final file list, folders first all = [{ @@ -275,14 +286,14 @@ class Utilities: # Based on https://stackoverflow.com/a/46422554/13174603 - def start_rdt_proxy(self, ip, port): - async def pipe(reader, writer): + def start_rdt_proxy(self, ip: str, port: int): + async def pipe(reader: StreamReader, writer: StreamWriter): try: while not reader.at_eof(): writer.write(await reader.read(2048)) finally: writer.close() - async def handle_client(local_reader, local_writer): + async def handle_client(local_reader: StreamReader, local_writer: StreamWriter): try: remote_reader, remote_writer = await open_connection( ip, port) @@ -298,7 +309,8 @@ class Utilities: def stop_rdt_proxy(self): if self.rdt_proxy_server: self.rdt_proxy_server.close() - self.rdt_proxy_task.cancel() + if self.rdt_proxy_task: + self.rdt_proxy_task.cancel() async def _enable_rdt(self): # TODO un-hardcode port @@ -348,11 +360,11 @@ class Utilities: await tab.evaluate_js("location.reload();", False, True, False) self.logger.info("React DevTools disabled") - async def get_user_info(self) -> dict: + async def get_user_info(self) -> Dict[str, str]: return { "username": get_username(), "path": get_home_path() } - async def get_tab_id(self, name): + async def get_tab_id(self, name: str): return (await get_tab(name)).id -- cgit v1.2.3 From bf83eabe6b19e560bc3e5fd5ae961edf7aea6839 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:06:46 -0400 Subject: move to module imports --- backend/browser.py | 2 +- backend/helpers.py | 4 ++-- backend/loader.py | 6 ++++-- backend/localplatform.py | 8 ++++---- backend/localplatformlinux.py | 2 +- backend/localplatformwin.py | 2 +- backend/localsocket.py | 2 +- backend/main.py | 20 ++++++++++---------- backend/plugin.py | 8 ++++---- backend/settings.py | 6 +++--- backend/updater.py | 22 ++++++++++++++-------- backend/utilities.py | 20 +++++++++++--------- 12 files changed, 56 insertions(+), 46 deletions(-) (limited to 'backend') diff --git a/backend/browser.py b/backend/browser.py index 358c05f9..08560749 100644 --- a/backend/browser.py +++ b/backend/browser.py @@ -13,11 +13,11 @@ from os import R_OK, W_OK, path, listdir, access, mkdir from shutil import rmtree from time import time from zipfile import ZipFile -from localplatform import chown, chmod from enum import IntEnum from typing import Dict, List, TypedDict # Local modules +from .localplatform import chown, chmod from .loader import Loader, Plugins from .helpers import get_ssl_context, download_remote_binary_to_path from .settings import SettingsManager diff --git a/backend/helpers.py b/backend/helpers.py index 4036db85..f8796bd8 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -10,8 +10,8 @@ import certifi from aiohttp.web import Request, Response, middleware from aiohttp.typedefs import Handler from aiohttp import ClientSession -import localplatform -from customtypes import UserType +from . import localplatform +from .customtypes import UserType from logging import getLogger REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service" diff --git a/backend/loader.py b/backend/loader.py index 89bb0457..dc7a5633 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -11,7 +11,9 @@ from os.path import exists from watchdog.events import RegexMatchingEventHandler, DirCreatedEvent, DirModifiedEvent, FileCreatedEvent, FileModifiedEvent # type: ignore from watchdog.observers import Observer # type: ignore -from backend.main import PluginManager # type: ignore +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .main import PluginManager from .injector import get_tab, get_gamepadui_tab from .plugin import PluginWrapper @@ -63,7 +65,7 @@ class FileChangeHandler(RegexMatchingEventHandler): self.maybe_reload(src_path) class Loader: - def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool =False) -> None: + def __init__(self, server_instance: 'PluginManager', plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None: self.loop = loop self.logger = getLogger("Loader") self.plugin_path = plugin_path diff --git a/backend/localplatform.py b/backend/localplatform.py index 43043ad0..028eff8f 100644 --- a/backend/localplatform.py +++ b/backend/localplatform.py @@ -4,11 +4,11 @@ ON_WINDOWS = platform.system() == "Windows" ON_LINUX = not ON_WINDOWS if ON_WINDOWS: - from localplatformwin import * - import localplatformwin as localplatform + from .localplatformwin import * + from . import localplatformwin as localplatform else: - from localplatformlinux import * - import localplatformlinux as localplatform + from .localplatformlinux import * + from . import localplatformlinux as localplatform def get_privileged_path() -> str: '''Get path accessible by elevated user. Holds plugins, decky loader and decky loader configs''' diff --git a/backend/localplatformlinux.py b/backend/localplatformlinux.py index 58b9dbc2..bde2caac 100644 --- a/backend/localplatformlinux.py +++ b/backend/localplatformlinux.py @@ -1,6 +1,6 @@ import os, pwd, grp, sys, logging from subprocess import call, run, DEVNULL, PIPE, STDOUT -from customtypes import UserType +from .customtypes import UserType logger = logging.getLogger("localplatform") diff --git a/backend/localplatformwin.py b/backend/localplatformwin.py index b6bee330..4c4e9439 100644 --- a/backend/localplatformwin.py +++ b/backend/localplatformwin.py @@ -1,4 +1,4 @@ -from customtypes import UserType +from .customtypes import UserType import os, sys def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: diff --git a/backend/localsocket.py b/backend/localsocket.py index 3659da03..f38fe5e7 100644 --- a/backend/localsocket.py +++ b/backend/localsocket.py @@ -2,7 +2,7 @@ import asyncio, time from typing import Awaitable, Callable import random -from localplatform import ON_WINDOWS +from .localplatform import ON_WINDOWS BUFFER_LIMIT = 2 ** 20 # 1 MiB diff --git a/backend/main.py b/backend/main.py index 8857fb22..793d000c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,7 +1,7 @@ # Change PyInstaller files permissions import sys from typing import Dict -from localplatform import (chmod, chown, service_stop, service_start, +from .localplatform import (chmod, chown, service_stop, service_start, ON_WINDOWS, get_log_level, get_live_reload, get_server_port, get_server_host, get_chown_plugin_path, get_privileged_path) @@ -21,16 +21,16 @@ from aiohttp.web import Application, Response, Request, get, run_app, static # t from aiohttp_jinja2 import setup as jinja_setup # local modules -from browser import PluginBrowser -from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, +from .browser import PluginBrowser +from .helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, mkdir_as_user, get_system_pythonpaths, get_effective_user_id) -from injector import get_gamepadui_tab, Tab, close_old_tabs -from loader import Loader -from settings import SettingsManager -from updater import Updater -from utilities import Utilities -from customtypes import UserType +from .injector import get_gamepadui_tab, Tab, close_old_tabs +from .loader import Loader +from .settings import SettingsManager +from .updater import Updater +from .utilities import Utilities +from .customtypes import UserType basicConfig( @@ -169,7 +169,7 @@ class PluginManager: def run(self): return run_app(self.web_app, host=get_server_host(), port=get_server_port(), loop=self.loop, access_log=None) -if __name__ == "__main__": +def main(): if ON_WINDOWS: # Fix windows/flask not recognising that .js means 'application/javascript' import mimetypes diff --git a/backend/plugin.py b/backend/plugin.py index 5c1e099f..163bb9b6 100644 --- a/backend/plugin.py +++ b/backend/plugin.py @@ -9,10 +9,10 @@ from os import path, environ from signal import SIGINT, signal from sys import exit, path as syspath from typing import Any, Dict -from localsocket import LocalSocket -from localplatform import setgid, setuid, get_username, get_home_path -from customtypes import UserType -import helpers +from .localsocket import LocalSocket +from .localplatform import setgid, setuid, get_username, get_home_path +from .customtypes import UserType +from . import helpers class PluginWrapper: def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: diff --git a/backend/settings.py b/backend/settings.py index 26dfc97f..a9ab3daa 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -1,10 +1,10 @@ from json import dump, load from os import mkdir, path, listdir, rename from typing import Any, Dict -from localplatform import chown, folder_owner, get_chown_plugin_path -from customtypes import UserType +from .localplatform import chown, folder_owner, get_chown_plugin_path +from .customtypes import UserType -from helpers import get_homebrew_path +from .helpers import get_homebrew_path class SettingsManager: diff --git a/backend/updater.py b/backend/updater.py index d7a3d712..0bd7218a 100644 --- a/backend/updater.py +++ b/backend/updater.py @@ -4,15 +4,16 @@ from asyncio import sleep from json.decoder import JSONDecodeError from logging import getLogger from os import getcwd, path, remove -from typing import List, TypedDict -from backend.main import PluginManager -from localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux +from typing import TYPE_CHECKING, List, TypedDict +if TYPE_CHECKING: + from .main import PluginManager +from .localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux from aiohttp import ClientSession, web -import helpers -from injector import get_gamepadui_tab -from settings import SettingsManager +from .import helpers +from .injector import get_gamepadui_tab +from .settings import SettingsManager logger = getLogger("Updater") @@ -25,7 +26,7 @@ class RemoteVer(TypedDict): assets: List[RemoteVerAsset] class Updater: - def __init__(self, context: PluginManager) -> None: + def __init__(self, context: 'PluginManager') -> None: self.context = context self.settings = self.context.settings # Exposes updater methods to frontend @@ -150,7 +151,12 @@ class Updater: async def do_update(self): logger.debug("Starting update.") - assert self.remoteVer + try: + assert self.remoteVer + except AssertionError: + logger.error("Unable to update as remoteVer is missing") + return + version = self.remoteVer["tag_name"] download_url = None download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe" diff --git a/backend/utilities.py b/backend/utilities.py index d45bec9b..1e4110cf 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -8,16 +8,18 @@ from stat import FILE_ATTRIBUTE_HIDDEN # type: ignore from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection from aiohttp import ClientSession, web -from typing import Callable, Coroutine, Dict, Any, List, TypedDict +from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Any, List, TypedDict from logging import getLogger -from backend.browser import PluginInstallRequest, PluginInstallType -from backend.main import PluginManager -from injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab from pathlib import Path -from localplatform import ON_WINDOWS -import helpers -from localplatform import service_stop, service_start, get_home_path, get_username + +from .browser import PluginInstallRequest, PluginInstallType +if TYPE_CHECKING: + from .main import PluginManager +from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab +from .localplatform import ON_WINDOWS +from .import helpers +from .localplatform import service_stop, service_start, get_home_path, get_username class FilePickerObj(TypedDict): file: Path @@ -25,7 +27,7 @@ class FilePickerObj(TypedDict): is_dir: bool class Utilities: - def __init__(self, context: PluginManager) -> None: + def __init__(self, context: 'PluginManager') -> None: self.context = context self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = { "ping": self.ping, @@ -307,7 +309,7 @@ class Utilities: self.rdt_proxy_task = self.context.loop.create_task(self.rdt_proxy_server) def stop_rdt_proxy(self): - if self.rdt_proxy_server: + if self.rdt_proxy_server != None: self.rdt_proxy_server.close() if self.rdt_proxy_task: self.rdt_proxy_task.cancel() -- cgit v1.2.3 From ca1332334d3e05d36419b3a65b6f5dbee35409ca Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:28:15 -0400 Subject: remove quotes on some types --- backend/loader.py | 3 ++- backend/updater.py | 2 +- backend/utilities.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'backend') diff --git a/backend/loader.py b/backend/loader.py index dc7a5633..684570f7 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -1,3 +1,4 @@ +from __future__ import annotations from asyncio import AbstractEventLoop, Queue, sleep from json.decoder import JSONDecodeError from logging import getLogger @@ -65,7 +66,7 @@ class FileChangeHandler(RegexMatchingEventHandler): self.maybe_reload(src_path) class Loader: - def __init__(self, server_instance: 'PluginManager', plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None: + def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None: self.loop = loop self.logger = getLogger("Loader") self.plugin_path = plugin_path diff --git a/backend/updater.py b/backend/updater.py index 0bd7218a..ac7c78d8 100644 --- a/backend/updater.py +++ b/backend/updater.py @@ -26,7 +26,7 @@ class RemoteVer(TypedDict): assets: List[RemoteVerAsset] class Updater: - def __init__(self, context: 'PluginManager') -> None: + def __init__(self, context: PluginManager) -> None: self.context = context self.settings = self.context.settings # Exposes updater methods to frontend diff --git a/backend/utilities.py b/backend/utilities.py index 1e4110cf..3c7c8c2e 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -1,3 +1,4 @@ +from __future__ import annotations from os import stat_result import uuid from json.decoder import JSONDecodeError @@ -27,7 +28,7 @@ class FilePickerObj(TypedDict): is_dir: bool class Utilities: - def __init__(self, context: 'PluginManager') -> None: + def __init__(self, context: PluginManager) -> None: self.context = context self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = { "ping": self.ping, -- cgit v1.2.3 From 59379710142c12e18a5fa1ece81c2ca41bb665a2 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 26 Sep 2023 14:54:52 +0200 Subject: Moved backend entirely into the backend folder --- backend/browser.py | 275 ------------------------ backend/customtypes.py | 6 - backend/helpers.py | 153 ------------- backend/injector.py | 438 -------------------------------------- backend/legacy/library.js | 84 -------- backend/loader.py | 238 --------------------- backend/locales/bg-BG.json | 252 ---------------------- backend/locales/cs-CZ.json | 267 ----------------------- backend/locales/de-DE.json | 195 ----------------- backend/locales/el-GR.json | 260 ---------------------- backend/locales/en-US.json | 260 ---------------------- backend/locales/es-ES.json | 217 ------------------- backend/locales/fi-FI.json | 260 ---------------------- backend/locales/fr-FR.json | 201 ----------------- backend/locales/it-IT.json | 267 ----------------------- backend/locales/ko-KR.json | 253 ---------------------- backend/locales/nl-NL.json | 243 --------------------- backend/locales/pl-PL.json | 267 ----------------------- backend/locales/pt-BR.json | 259 ---------------------- backend/locales/pt-PT.json | 222 ------------------- backend/locales/ru-RU.json | 267 ----------------------- backend/locales/sq-AL.json | 131 ------------ backend/locales/uk-UA.json | 222 ------------------- backend/locales/zh-CN.json | 253 ---------------------- backend/locales/zh-TW.json | 245 --------------------- backend/localplatform.py | 52 ----- backend/localplatformlinux.py | 192 ----------------- backend/localplatformwin.py | 53 ----- backend/localsocket.py | 139 ------------ backend/main.py | 192 ----------------- backend/plugin.py | 163 -------------- backend/settings.py | 60 ------ backend/src/browser.py | 275 ++++++++++++++++++++++++ backend/src/customtypes.py | 6 + backend/src/helpers.py | 153 +++++++++++++ backend/src/injector.py | 438 ++++++++++++++++++++++++++++++++++++++ backend/src/legacy/library.js | 84 ++++++++ backend/src/loader.py | 238 +++++++++++++++++++++ backend/src/locales/bg-BG.json | 252 ++++++++++++++++++++++ backend/src/locales/cs-CZ.json | 267 +++++++++++++++++++++++ backend/src/locales/de-DE.json | 195 +++++++++++++++++ backend/src/locales/el-GR.json | 260 ++++++++++++++++++++++ backend/src/locales/en-US.json | 260 ++++++++++++++++++++++ backend/src/locales/es-ES.json | 217 +++++++++++++++++++ backend/src/locales/fi-FI.json | 260 ++++++++++++++++++++++ backend/src/locales/fr-FR.json | 201 +++++++++++++++++ backend/src/locales/it-IT.json | 267 +++++++++++++++++++++++ backend/src/locales/ko-KR.json | 253 ++++++++++++++++++++++ backend/src/locales/nl-NL.json | 243 +++++++++++++++++++++ backend/src/locales/pl-PL.json | 267 +++++++++++++++++++++++ backend/src/locales/pt-BR.json | 259 ++++++++++++++++++++++ backend/src/locales/pt-PT.json | 222 +++++++++++++++++++ backend/src/locales/ru-RU.json | 267 +++++++++++++++++++++++ backend/src/locales/sq-AL.json | 131 ++++++++++++ backend/src/locales/uk-UA.json | 222 +++++++++++++++++++ backend/src/locales/zh-CN.json | 253 ++++++++++++++++++++++ backend/src/locales/zh-TW.json | 245 +++++++++++++++++++++ backend/src/localplatform.py | 52 +++++ backend/src/localplatformlinux.py | 192 +++++++++++++++++ backend/src/localplatformwin.py | 53 +++++ backend/src/localsocket.py | 139 ++++++++++++ backend/src/main.py | 192 +++++++++++++++++ backend/src/plugin.py | 163 ++++++++++++++ backend/src/settings.py | 60 ++++++ backend/src/updater.py | 237 +++++++++++++++++++++ backend/src/utilities.py | 373 ++++++++++++++++++++++++++++++++ backend/updater.py | 237 --------------------- backend/utilities.py | 373 -------------------------------- 68 files changed, 7196 insertions(+), 7196 deletions(-) delete mode 100644 backend/browser.py delete mode 100644 backend/customtypes.py delete mode 100644 backend/helpers.py delete mode 100644 backend/injector.py delete mode 100644 backend/legacy/library.js delete mode 100644 backend/loader.py delete mode 100644 backend/locales/bg-BG.json delete mode 100644 backend/locales/cs-CZ.json delete mode 100644 backend/locales/de-DE.json delete mode 100644 backend/locales/el-GR.json delete mode 100644 backend/locales/en-US.json delete mode 100644 backend/locales/es-ES.json delete mode 100644 backend/locales/fi-FI.json delete mode 100644 backend/locales/fr-FR.json delete mode 100644 backend/locales/it-IT.json delete mode 100644 backend/locales/ko-KR.json delete mode 100644 backend/locales/nl-NL.json delete mode 100644 backend/locales/pl-PL.json delete mode 100644 backend/locales/pt-BR.json delete mode 100644 backend/locales/pt-PT.json delete mode 100644 backend/locales/ru-RU.json delete mode 100644 backend/locales/sq-AL.json delete mode 100644 backend/locales/uk-UA.json delete mode 100644 backend/locales/zh-CN.json delete mode 100644 backend/locales/zh-TW.json delete mode 100644 backend/localplatform.py delete mode 100644 backend/localplatformlinux.py delete mode 100644 backend/localplatformwin.py delete mode 100644 backend/localsocket.py delete mode 100644 backend/main.py delete mode 100644 backend/plugin.py delete mode 100644 backend/settings.py create mode 100644 backend/src/browser.py create mode 100644 backend/src/customtypes.py create mode 100644 backend/src/helpers.py create mode 100644 backend/src/injector.py create mode 100644 backend/src/legacy/library.js create mode 100644 backend/src/loader.py create mode 100644 backend/src/locales/bg-BG.json create mode 100644 backend/src/locales/cs-CZ.json create mode 100644 backend/src/locales/de-DE.json create mode 100644 backend/src/locales/el-GR.json create mode 100644 backend/src/locales/en-US.json create mode 100644 backend/src/locales/es-ES.json create mode 100644 backend/src/locales/fi-FI.json create mode 100644 backend/src/locales/fr-FR.json create mode 100644 backend/src/locales/it-IT.json create mode 100644 backend/src/locales/ko-KR.json create mode 100644 backend/src/locales/nl-NL.json create mode 100644 backend/src/locales/pl-PL.json create mode 100644 backend/src/locales/pt-BR.json create mode 100644 backend/src/locales/pt-PT.json create mode 100644 backend/src/locales/ru-RU.json create mode 100644 backend/src/locales/sq-AL.json create mode 100644 backend/src/locales/uk-UA.json create mode 100644 backend/src/locales/zh-CN.json create mode 100644 backend/src/locales/zh-TW.json create mode 100644 backend/src/localplatform.py create mode 100644 backend/src/localplatformlinux.py create mode 100644 backend/src/localplatformwin.py create mode 100644 backend/src/localsocket.py create mode 100644 backend/src/main.py create mode 100644 backend/src/plugin.py create mode 100644 backend/src/settings.py create mode 100644 backend/src/updater.py create mode 100644 backend/src/utilities.py delete mode 100644 backend/updater.py delete mode 100644 backend/utilities.py (limited to 'backend') diff --git a/backend/browser.py b/backend/browser.py deleted file mode 100644 index 08560749..00000000 --- a/backend/browser.py +++ /dev/null @@ -1,275 +0,0 @@ -# Full imports -import json -# import pprint -# from pprint import pformat - -# Partial imports -from aiohttp import ClientSession -from asyncio import sleep -from hashlib import sha256 -from io import BytesIO -from logging import getLogger -from os import R_OK, W_OK, path, listdir, access, mkdir -from shutil import rmtree -from time import time -from zipfile import ZipFile -from enum import IntEnum -from typing import Dict, List, TypedDict - -# Local modules -from .localplatform import chown, chmod -from .loader import Loader, Plugins -from .helpers import get_ssl_context, download_remote_binary_to_path -from .settings import SettingsManager -from .injector import get_gamepadui_tab - -logger = getLogger("Browser") - -class PluginInstallType(IntEnum): - INSTALL = 0 - REINSTALL = 1 - UPDATE = 2 - -class PluginInstallRequest(TypedDict): - name: str - artifact: str - version: str - hash: str - install_type: PluginInstallType - -class PluginInstallContext: - def __init__(self, artifact: str, name: str, version: str, hash: str) -> None: - self.artifact = artifact - self.name = name - self.version = version - self.hash = hash - -class PluginBrowser: - def __init__(self, plugin_path: str, plugins: Plugins, loader: Loader, settings: SettingsManager) -> None: - self.plugin_path = plugin_path - self.plugins = plugins - self.loader = loader - self.settings = settings - self.install_requests: Dict[str, PluginInstallContext | List[PluginInstallContext]] = {} - - def _unzip_to_plugin_dir(self, zip: BytesIO, name: str, hash: str): - zip_hash = sha256(zip.getbuffer()).hexdigest() - if hash and (zip_hash != hash): - return False - zip_file = ZipFile(zip) - zip_file.extractall(self.plugin_path) - plugin_folder = self.find_plugin_folder(name) - assert plugin_folder is not None - plugin_dir = path.join(self.plugin_path, plugin_folder) - - if not chown(plugin_dir) or not chmod(plugin_dir, 555): - logger.error(f"chown/chmod exited with a non-zero exit code") - return False - return True - - async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: str): - rv = False - try: - packageJsonPath = path.join(pluginBasePath, 'package.json') - pluginBinPath = path.join(pluginBasePath, 'bin') - - if access(packageJsonPath, R_OK): - with open(packageJsonPath, "r", encoding="utf-8") as f: - packageJson = json.load(f) - if "remote_binary" in packageJson and len(packageJson["remote_binary"]) > 0: - # create bin directory if needed. - chmod(pluginBasePath, 777) - if access(pluginBasePath, W_OK): - if not path.exists(pluginBinPath): - mkdir(pluginBinPath) - if not access(pluginBinPath, W_OK): - chmod(pluginBinPath, 777) - - rv = True - for remoteBinary in packageJson["remote_binary"]: - # Required Fields. If any Remote Binary is missing these fail the install. - binName = remoteBinary["name"] - binURL = remoteBinary["url"] - binHash = remoteBinary["sha256hash"] - if not await download_remote_binary_to_path(binURL, binHash, path.join(pluginBinPath, binName)): - rv = False - raise Exception(f"Error Downloading Remote Binary {binName}@{binURL} with hash {binHash} to {path.join(pluginBinPath, binName)}") - - chown(self.plugin_path) - chmod(pluginBasePath, 555) - else: - rv = True - logger.debug(f"No Remote Binaries to Download") - - except Exception as e: - rv = False - logger.debug(str(e)) - - return rv - - """Return the filename (only) for the specified plugin""" - def find_plugin_folder(self, name: str) -> str | None: - for folder in listdir(self.plugin_path): - try: - with open(path.join(self.plugin_path, folder, 'plugin.json'), "r", encoding="utf-8") as f: - plugin = json.load(f) - - if plugin['name'] == name: - return folder - except: - logger.debug(f"skipping {folder}") - - async def uninstall_plugin(self, name: str): - if self.loader.watcher: - self.loader.watcher.disabled = True - tab = await get_gamepadui_tab() - plugin_folder = self.find_plugin_folder(name) - assert plugin_folder is not None - plugin_dir = path.join(self.plugin_path, ) - try: - logger.info("uninstalling " + name) - logger.info(" at dir " + plugin_dir) - logger.debug("calling frontend unload for %s" % str(name)) - res = await tab.evaluate_js(f"DeckyPluginLoader.unloadPlugin('{name}')") - logger.debug("result of unload from UI: %s", res) - # plugins_snapshot = self.plugins.copy() - # snapshot_string = pformat(plugins_snapshot) - # logger.debug("current plugins: %s", snapshot_string) - if name in self.plugins: - logger.debug("Plugin %s was found", name) - self.plugins[name].stop() - logger.debug("Plugin %s was stopped", name) - del self.plugins[name] - logger.debug("Plugin %s was removed from the dictionary", name) - self.cleanup_plugin_settings(name) - logger.debug("removing files %s" % str(name)) - rmtree(plugin_dir) - except FileNotFoundError: - logger.warning(f"Plugin {name} not installed, skipping uninstallation") - except Exception as e: - logger.error(f"Plugin {name} in {plugin_dir} was not uninstalled") - logger.error(f"Error at {str(e)}", exc_info=e) - if self.loader.watcher: - self.loader.watcher.disabled = False - - async def _install(self, artifact: str, name: str, version: str, hash: str): - # Will be set later in code - res_zip = None - - # Check if plugin is installed - isInstalled = False - # Preserve plugin order before removing plugin (uninstall alters the order and removes the plugin from the list) - current_plugin_order = self.settings.getSetting("pluginOrder")[:] - if self.loader.watcher: - self.loader.watcher.disabled = True - try: - pluginFolderPath = self.find_plugin_folder(name) - if pluginFolderPath: - isInstalled = True - except: - logger.error(f"Failed to determine if {name} is already installed, continuing anyway.") - - # Check if the file is a local file or a URL - if artifact.startswith("file://"): - logger.info(f"Installing {name} from local ZIP file (Version: {version})") - res_zip = BytesIO(open(artifact[7:], "rb").read()) - else: - logger.info(f"Installing {name} from URL (Version: {version})") - async with ClientSession() as client: - logger.debug(f"Fetching {artifact}") - res = await client.get(artifact, ssl=get_ssl_context()) - if res.status == 200: - logger.debug("Got 200. Reading...") - data = await res.read() - logger.debug(f"Read {len(data)} bytes") - res_zip = BytesIO(data) - else: - logger.fatal(f"Could not fetch from URL. {await res.text()}") - - # Check to make sure we got the file - if res_zip is None: - logger.fatal(f"Could not fetch {artifact}") - return - - # If plugin is installed, uninstall it - if isInstalled: - try: - logger.debug("Uninstalling existing plugin...") - await self.uninstall_plugin(name) - except: - logger.error(f"Plugin {name} could not be uninstalled.") - - # Install the plugin - logger.debug("Unzipping...") - ret = self._unzip_to_plugin_dir(res_zip, name, hash) - if ret: - plugin_folder = self.find_plugin_folder(name) - assert plugin_folder is not None - plugin_dir = path.join(self.plugin_path, plugin_folder) - ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir) - if ret: - logger.info(f"Installed {name} (Version: {version})") - if name in self.loader.plugins: - self.loader.plugins[name].stop() - self.loader.plugins.pop(name, None) - await sleep(1) - if not isInstalled: - current_plugin_order = self.settings.getSetting("pluginOrder") - current_plugin_order.append(name) - self.settings.setSetting("pluginOrder", current_plugin_order) - logger.debug("Plugin %s was added to the pluginOrder setting", name) - self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder) - else: - logger.fatal(f"Failed Downloading Remote Binaries") - else: - logger.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})") - if self.loader.watcher: - self.loader.watcher.disabled = False - - async def request_plugin_install(self, artifact: str, name: str, version: str, hash: str, install_type: PluginInstallType): - request_id = str(time()) - self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash) - tab = await get_gamepadui_tab() - await tab.open_websocket() - await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}', {install_type})") - - async def request_multiple_plugin_installs(self, requests: List[PluginInstallRequest]): - request_id = str(time()) - self.install_requests[request_id] = [PluginInstallContext(req['artifact'], req['name'], req['version'], req['hash']) for req in requests] - js_requests_parameter = ','.join([ - f"{{ name: '{req['name']}', version: '{req['version']}', hash: '{req['hash']}', install_type: {req['install_type']}}}" for req in requests - ]) - - tab = await get_gamepadui_tab() - await tab.open_websocket() - await tab.evaluate_js(f"DeckyPluginLoader.addMultiplePluginsInstallPrompt('{request_id}', [{js_requests_parameter}])") - - async def confirm_plugin_install(self, request_id: str): - requestOrRequests = self.install_requests.pop(request_id) - if isinstance(requestOrRequests, list): - [await self._install(req.artifact, req.name, req.version, req.hash) for req in requestOrRequests] - else: - await self._install(requestOrRequests.artifact, requestOrRequests.name, requestOrRequests.version, requestOrRequests.hash) - - def cancel_plugin_install(self, request_id: str): - self.install_requests.pop(request_id) - - def cleanup_plugin_settings(self, name: str): - """Removes any settings related to a plugin. Propably called when a plugin is uninstalled. - - Args: - name (string): The name of the plugin - """ - hidden_plugins = self.settings.getSetting("hiddenPlugins", []) - if name in hidden_plugins: - hidden_plugins.remove(name) - self.settings.setSetting("hiddenPlugins", hidden_plugins) - - - plugin_order = self.settings.getSetting("pluginOrder", []) - - if name in plugin_order: - plugin_order.remove(name) - self.settings.setSetting("pluginOrder", plugin_order) - - logger.debug("Removed any settings for plugin %s", name) diff --git a/backend/customtypes.py b/backend/customtypes.py deleted file mode 100644 index 84ebc235..00000000 --- a/backend/customtypes.py +++ /dev/null @@ -1,6 +0,0 @@ -from enum import Enum - -class UserType(Enum): - HOST_USER = 1 - EFFECTIVE_USER = 2 - ROOT = 3 \ No newline at end of file diff --git a/backend/helpers.py b/backend/helpers.py deleted file mode 100644 index f8796bd8..00000000 --- a/backend/helpers.py +++ /dev/null @@ -1,153 +0,0 @@ -import re -import ssl -import uuid -import os -import subprocess -from hashlib import sha256 -from io import BytesIO - -import certifi -from aiohttp.web import Request, Response, middleware -from aiohttp.typedefs import Handler -from aiohttp import ClientSession -from . import localplatform -from .customtypes import UserType -from logging import getLogger - -REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service" - -# global vars -csrf_token = str(uuid.uuid4()) -ssl_ctx = ssl.create_default_context(cafile=certifi.where()) - -assets_regex = re.compile("^/plugins/.*/assets/.*") -frontend_regex = re.compile("^/frontend/.*") -logger = getLogger("Main") - -def get_ssl_context(): - return ssl_ctx - -def get_csrf_token(): - return csrf_token - -@middleware -async def csrf_middleware(request: Request, handler: Handler): - if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): - return await handler(request) - return Response(text='Forbidden', status=403) - -# Get the default homebrew path unless a home_path is specified. home_path argument is deprecated -def get_homebrew_path() -> str: - return localplatform.get_unprivileged_path() - -# Recursively create path and chown as user -def mkdir_as_user(path: str): - path = os.path.realpath(path) - os.makedirs(path, exist_ok=True) - localplatform.chown(path) - -# Fetches the version of loader -def get_loader_version() -> str: - try: - with open(os.path.join(os.getcwd(), ".loader.version"), "r", encoding="utf-8") as version_file: - return version_file.readline().strip() - except Exception as e: - logger.warn(f"Failed to execute get_loader_version(): {str(e)}") - return "unknown" - -# returns the appropriate system python paths -def get_system_pythonpaths() -> list[str]: - try: - # run as normal normal user if on linux to also include user python paths - proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"], - # TODO make this less insane - capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # type: ignore - return [x.strip() for x in proc.stdout.decode().strip().split("\n")] - except Exception as e: - logger.warn(f"Failed to execute get_system_pythonpaths(): {str(e)}") - return [] - -# Download Remote Binaries to local Plugin -async def download_remote_binary_to_path(url: str, binHash: str, path: str) -> bool: - rv = False - try: - if os.access(os.path.dirname(path), os.W_OK): - async with ClientSession() as client: - res = await client.get(url, ssl=get_ssl_context()) - if res.status == 200: - data = BytesIO(await res.read()) - remoteHash = sha256(data.getbuffer()).hexdigest() - if binHash == remoteHash: - data.seek(0) - with open(path, 'wb') as f: - f.write(data.getbuffer()) - rv = True - else: - raise Exception(f"Fatal Error: Hash Mismatch for remote binary {path}@{url}") - else: - rv = False - except: - rv = False - - return rv - -# Deprecated -def set_user(): - pass - -# Deprecated -def set_user_group() -> str: - return get_user_group() - -######### -# Below is legacy code, provided for backwards compatibility. This will break on windows -######### - -# Get the user id hosting the plugin loader -def get_user_id() -> int: - return localplatform.localplatform._get_user_id() # pyright: ignore [reportPrivateUsage] - -# Get the user hosting the plugin loader -def get_user() -> str: - return localplatform.localplatform._get_user() # pyright: ignore [reportPrivateUsage] - -# Get the effective user id of the running process -def get_effective_user_id() -> int: - return localplatform.localplatform._get_effective_user_id() # pyright: ignore [reportPrivateUsage] - -# Get the effective user of the running process -def get_effective_user() -> str: - return localplatform.localplatform._get_effective_user() # pyright: ignore [reportPrivateUsage] - -# Get the effective user group id of the running process -def get_effective_user_group_id() -> int: - return localplatform.localplatform._get_effective_user_group_id() # pyright: ignore [reportPrivateUsage] - -# Get the effective user group of the running process -def get_effective_user_group() -> str: - return localplatform.localplatform._get_effective_user_group() # pyright: ignore [reportPrivateUsage] - -# Get the user owner of the given file path. -def get_user_owner(file_path: str) -> str: - return localplatform.localplatform._get_user_owner(file_path) # pyright: ignore [reportPrivateUsage] - -# Get the user group of the given file path, or the user group hosting the plugin loader -def get_user_group(file_path: str | None = None) -> str: - return localplatform.localplatform._get_user_group(file_path) # pyright: ignore [reportPrivateUsage] - -# Get the group id of the user hosting the plugin loader -def get_user_group_id() -> int: - return localplatform.localplatform._get_user_group_id() # pyright: ignore [reportPrivateUsage] - -# Get the default home path unless a user is specified -def get_home_path(username: str | None = None) -> str: - return localplatform.get_home_path(UserType.ROOT if username == "root" else UserType.HOST_USER) - -async def is_systemd_unit_active(unit_name: str) -> bool: - return await localplatform.service_active(unit_name) - -async def stop_systemd_unit(unit_name: str) -> bool: - return await localplatform.service_stop(unit_name) - -async def start_systemd_unit(unit_name: str) -> bool: - return await localplatform.service_start(unit_name) diff --git a/backend/injector.py b/backend/injector.py deleted file mode 100644 index a217f689..00000000 --- a/backend/injector.py +++ /dev/null @@ -1,438 +0,0 @@ -# Injector code from https://github.com/SteamDeckHomebrew/steamdeck-ui-inject. More info on how it works there. - -from asyncio import sleep -from logging import getLogger -from typing import Any, Callable, List, TypedDict, Dict - -from aiohttp import ClientSession -from aiohttp.client_exceptions import ClientConnectorError, ClientOSError -from asyncio.exceptions import TimeoutError -import uuid - -BASE_ADDRESS = "http://localhost:8080" - -logger = getLogger("Injector") - -class _TabResponse(TypedDict): - title: str - id: str - url: str - webSocketDebuggerUrl: str - -class Tab: - cmd_id = 0 - - def __init__(self, res: _TabResponse) -> None: - self.title: str = res["title"] - self.id: str = res["id"] - self.url: str = res["url"] - self.ws_url: str = res["webSocketDebuggerUrl"] - - self.websocket = None - self.client = None - - async def open_websocket(self): - self.client = ClientSession() - self.websocket = await self.client.ws_connect(self.ws_url) # type: ignore - - async def close_websocket(self): - if self.websocket: - await self.websocket.close() - if self.client: - await self.client.close() - - async def listen_for_message(self): - if self.websocket: - async for message in self.websocket: - data = message.json() - yield data - logger.warn(f"The Tab {self.title} socket has been disconnected while listening for messages.") - await self.close_websocket() - - async def _send_devtools_cmd(self, dc: Dict[str, Any], receive: bool = True): - if self.websocket: - self.cmd_id += 1 - dc["id"] = self.cmd_id - await self.websocket.send_json(dc) - if receive: - async for msg in self.listen_for_message(): - if "id" in msg and msg["id"] == dc["id"]: - return msg - return None - raise RuntimeError("Websocket not opened") - - async def evaluate_js(self, js: str, run_async: bool | None = False, manage_socket: bool | None = True, get_result: bool = True): - try: - if manage_socket: - await self.open_websocket() - - res = await self._send_devtools_cmd({ - "method": "Runtime.evaluate", - "params": { - "expression": js, - "userGesture": True, - "awaitPromise": run_async - } - }, get_result) - - finally: - if manage_socket: - await self.close_websocket() - return res - - async def has_global_var(self, var_name: str, manage_socket: bool = True): - res = await self.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False, manage_socket) - assert res is not None - - if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: - return False - - return res["result"]["result"]["value"] - - async def close(self, manage_socket: bool = True): - try: - if manage_socket: - await self.open_websocket() - - res = await self._send_devtools_cmd({ - "method": "Page.close", - }, False) - - finally: - if manage_socket: - await self.close_websocket() - return res - - async def enable(self): - """ - Enables page domain notifications. - """ - await self._send_devtools_cmd({ - "method": "Page.enable", - }, False) - - async def disable(self): - """ - Disables page domain notifications. - """ - await self._send_devtools_cmd({ - "method": "Page.disable", - }, False) - - async def refresh(self, manage_socket: bool = True): - try: - if manage_socket: - await self.open_websocket() - - await self._send_devtools_cmd({ - "method": "Page.reload", - }, False) - - finally: - if manage_socket: - await self.close_websocket() - - return - async def reload_and_evaluate(self, js: str, manage_socket: bool = True): - """ - Reloads the current tab, with JS to run on load via debugger - """ - try: - if manage_socket: - await self.open_websocket() - - await self._send_devtools_cmd({ - "method": "Debugger.enable" - }, True) - - await self._send_devtools_cmd({ - "method": "Runtime.evaluate", - "params": { - "expression": "location.reload();", - "userGesture": True, - "awaitPromise": False - } - }, False) - - breakpoint_res = await self._send_devtools_cmd({ - "method": "Debugger.setInstrumentationBreakpoint", - "params": { - "instrumentation": "beforeScriptExecution" - } - }, True) - - assert breakpoint_res is not None - - logger.info(breakpoint_res) - - # Page finishes loading when breakpoint hits - - for _ in range(20): - # this works around 1/5 of the time, so just send it 8 times. - # the js accounts for being injected multiple times allowing only one instance to run at a time anyway - await self._send_devtools_cmd({ - "method": "Runtime.evaluate", - "params": { - "expression": js, - "userGesture": True, - "awaitPromise": False - } - }, False) - - await self._send_devtools_cmd({ - "method": "Debugger.removeBreakpoint", - "params": { - "breakpointId": breakpoint_res["result"]["breakpointId"] - } - }, False) - - for _ in range(4): - await self._send_devtools_cmd({ - "method": "Debugger.resume" - }, False) - - await self._send_devtools_cmd({ - "method": "Debugger.disable" - }, True) - - finally: - if manage_socket: - await self.close_websocket() - return - - async def add_script_to_evaluate_on_new_document(self, js: str, add_dom_wrapper: bool = True, manage_socket: bool = True, get_result: bool = True): - """ - How the underlying call functions is not particularly clear from the devtools docs, so stealing puppeteer's description: - - Adds a function which would be invoked in one of the following scenarios: - * whenever the page is navigated - * whenever the child frame is attached or navigated. In this case, the - function is invoked in the context of the newly attached frame. - - The function is invoked after the document was created but before any of - its scripts were run. This is useful to amend the JavaScript environment, - e.g. to seed `Math.random`. - - Parameters - ---------- - js : str - The script to evaluate on new document - add_dom_wrapper : bool - True to wrap the script in a wait for the 'DOMContentLoaded' event. - DOM will usually not exist when this execution happens, - so it is necessary to delay til DOM is loaded if you are modifying it - manage_socket : bool - True to have this function handle opening/closing the websocket for this tab - get_result : bool - True to wait for the result of this call - - Returns - ------- - int or None - The identifier of the script added, used to remove it later. - (see remove_script_to_evaluate_on_new_document below) - None is returned if `get_result` is False - """ - try: - - wrappedjs = """ - function scriptFunc() { - {js} - } - if (document.readyState === 'loading') { - addEventListener('DOMContentLoaded', () => { - scriptFunc(); - }); - } else { - scriptFunc(); - } - """.format(js=js) if add_dom_wrapper else js - - if manage_socket: - await self.open_websocket() - - res = await self._send_devtools_cmd({ - "method": "Page.addScriptToEvaluateOnNewDocument", - "params": { - "source": wrappedjs - } - }, get_result) - - finally: - if manage_socket: - await self.close_websocket() - return res - - async def remove_script_to_evaluate_on_new_document(self, script_id: str, manage_socket: bool = True): - """ - Removes a script from a page that was added with `add_script_to_evaluate_on_new_document` - - Parameters - ---------- - script_id : int - The identifier of the script to remove (returned from `add_script_to_evaluate_on_new_document`) - """ - - try: - if manage_socket: - await self.open_websocket() - - await self._send_devtools_cmd({ - "method": "Page.removeScriptToEvaluateOnNewDocument", - "params": { - "identifier": script_id - } - }, False) - - finally: - if manage_socket: - await self.close_websocket() - - async def has_element(self, element_name: str, manage_socket: bool = True): - res = await self.evaluate_js(f"document.getElementById('{element_name}') != null", False, manage_socket) - assert res is not None - - if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: - return False - - return res["result"]["result"]["value"] - - async def inject_css(self, style: str, manage_socket: bool = True): - try: - css_id = str(uuid.uuid4()) - - result = await self.evaluate_js( - f""" - (function() {{ - const style = document.createElement('style'); - style.id = "{css_id}"; - document.head.append(style); - style.textContent = `{style}`; - }})() - """, False, manage_socket) - - assert result is not None - - if "exceptionDetails" in result["result"]: - return { - "success": False, - "result": result["result"] - } - - return { - "success": True, - "result": css_id - } - except Exception as e: - return { - "success": False, - "result": e - } - - async def remove_css(self, css_id: str, manage_socket: bool = True): - try: - result = await self.evaluate_js( - f""" - (function() {{ - let style = document.getElementById("{css_id}"); - - if (style.nodeName.toLowerCase() == 'style') - style.parentNode.removeChild(style); - }})() - """, False, manage_socket) - - assert result is not None - - if "exceptionDetails" in result["result"]: - return { - "success": False, - "result": result - } - - return { - "success": True - } - except Exception as e: - return { - "success": False, - "result": e - } - - async def get_steam_resource(self, url: str): - res = await self.evaluate_js(f'(async function test() {{ return await (await fetch("{url}")).text() }})()', True) - assert res is not None - return res["result"]["result"]["value"] - - def __repr__(self): - return self.title - - -async def get_tabs() -> List[Tab]: - res = {} - - na = False - while True: - try: - async with ClientSession() as web: - res = await web.get(f"{BASE_ADDRESS}/json", timeout=3) - except ClientConnectorError: - if not na: - logger.debug("Steam isn't available yet. Wait for a moment...") - na = True - await sleep(5) - except ClientOSError: - logger.warn(f"The request to {BASE_ADDRESS}/json was reset") - await sleep(1) - except TimeoutError: - logger.warn(f"The request to {BASE_ADDRESS}/json timed out") - await sleep(1) - else: - break - - if res.status == 200: - r = await res.json() - return [Tab(i) for i in r] - else: - raise Exception(f"/json did not return 200. {await res.text()}") - - -async def get_tab(tab_name: str) -> Tab: - tabs = await get_tabs() - tab = next((i for i in tabs if i.title == tab_name), None) - if not tab: - raise ValueError(f"Tab {tab_name} not found") - return tab - -async def get_tab_lambda(test: Callable[[Tab], bool]) -> Tab: - tabs = await get_tabs() - tab = next((i for i in tabs if test(i)), None) - if not tab: - raise ValueError(f"Tab not found by lambda") - return tab - -SHARED_CTX_NAMES = ["SharedJSContext", "Steam Shared Context presented by Valve™", "Steam", "SP"] -CLOSEABLE_URLS = ["about:blank", "data:text/html,%3Cbody%3E%3C%2Fbody%3E"] # Closing anything other than these *really* likes to crash Steam -DO_NOT_CLOSE_URL = "Valve Steam Gamepad/default" # Steam Big Picture Mode tab - -def tab_is_gamepadui(t: Tab) -> bool: - return "https://steamloopback.host/routes/" in t.url and t.title in SHARED_CTX_NAMES - -async def get_gamepadui_tab() -> Tab: - tabs = await get_tabs() - tab = next((i for i in tabs if tab_is_gamepadui(i)), None) - if not tab: - raise ValueError(f"GamepadUI Tab not found") - return tab - -async def inject_to_tab(tab_name: str, js: str, run_async: bool = False): - tab = await get_tab(tab_name) - - return await tab.evaluate_js(js, run_async) - -async def close_old_tabs(): - tabs = await get_tabs() - for t in tabs: - if not t.title or (t.title not in SHARED_CTX_NAMES and any(url in t.url for url in CLOSEABLE_URLS) and DO_NOT_CLOSE_URL not in t.url): - logger.debug("Closing tab: " + getattr(t, "title", "Untitled")) - await t.close() - await sleep(0.5) diff --git a/backend/legacy/library.js b/backend/legacy/library.js deleted file mode 100644 index 17f4e46f..00000000 --- a/backend/legacy/library.js +++ /dev/null @@ -1,84 +0,0 @@ -class PluginEventTarget extends EventTarget { } -method_call_ev_target = new PluginEventTarget(); - -window.addEventListener("message", function(evt) { - let ev = new Event(evt.data.call_id); - ev.data = evt.data.result; - method_call_ev_target.dispatchEvent(ev); -}, false); - -async function call_server_method(method_name, arg_object={}) { - const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text()); - const response = await fetch(`http://127.0.0.1:1337/methods/${method_name}`, { - method: 'POST', - credentials: "include", - headers: { - 'Content-Type': 'application/json', - Authentication: token - }, - body: JSON.stringify(arg_object), - }); - - const dta = await response.json(); - if (!dta.success) throw dta.result; - return dta.result; -} - -// Source: https://stackoverflow.com/a/2117523 Thanks! -function uuidv4() { - return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => - (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) - ); -} - -async function fetch_nocors(url, request={}) { - let args = { method: "POST", headers: {}, body: "" }; - request = {...args, ...request}; - request.url = url; - request.data = request.body; - delete request.body; //maintain api-compatibility with fetch - return await call_server_method("http_request", request); -} - -async function call_plugin_method(method_name, arg_object={}) { - if (plugin_name == undefined) - throw new Error("Plugin methods can only be called from inside plugins (duh)"); - const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text()); - const response = await fetch(`http://127.0.0.1:1337/plugins/${plugin_name}/methods/${method_name}`, { - method: 'POST', - credentials: "include", - headers: { - 'Content-Type': 'application/json', - Authentication: token - }, - body: JSON.stringify({ - args: arg_object, - }), - }); - - const dta = await response.json(); - if (!dta.success) throw dta.result; - return dta.result; -} - -async function execute_in_tab(tab, run_async, code) { - return await call_server_method("execute_in_tab", { - 'tab': tab, - 'run_async': run_async, - 'code': code - }); -} - -async function inject_css_into_tab(tab, style) { - return await call_server_method("inject_css_into_tab", { - 'tab': tab, - 'style': style - }); -} - -async function remove_css_from_tab(tab, css_id) { - return await call_server_method("remove_css_from_tab", { - 'tab': tab, - 'css_id': css_id - }); -} \ No newline at end of file diff --git a/backend/loader.py b/backend/loader.py deleted file mode 100644 index 684570f7..00000000 --- a/backend/loader.py +++ /dev/null @@ -1,238 +0,0 @@ -from __future__ import annotations -from asyncio import AbstractEventLoop, Queue, sleep -from json.decoder import JSONDecodeError -from logging import getLogger -from os import listdir, path -from pathlib import Path -from traceback import print_exc -from typing import Any, Tuple - -from aiohttp import web -from os.path import exists -from watchdog.events import RegexMatchingEventHandler, DirCreatedEvent, DirModifiedEvent, FileCreatedEvent, FileModifiedEvent # type: ignore -from watchdog.observers import Observer # type: ignore - -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from .main import PluginManager - -from .injector import get_tab, get_gamepadui_tab -from .plugin import PluginWrapper - -Plugins = dict[str, PluginWrapper] -ReloadQueue = Queue[Tuple[str, str, bool | None] | Tuple[str, str]] - -class FileChangeHandler(RegexMatchingEventHandler): - def __init__(self, queue: ReloadQueue, plugin_path: str) -> None: - super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) # type: ignore - self.logger = getLogger("file-watcher") - self.plugin_path = plugin_path - self.queue = queue - self.disabled = True - - def maybe_reload(self, src_path: str): - if self.disabled: - return - plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0] - if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")): - self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True)) - - def on_created(self, event: DirCreatedEvent | FileCreatedEvent): - src_path = event.src_path - if "__pycache__" in src_path: - return - - # check to make sure this isn't a directory - if path.isdir(src_path): - return - - # get the directory name of the plugin so that we can find its "main.py" and reload it; the - # file that changed is not necessarily the one that needs to be reloaded - self.logger.debug(f"file created: {src_path}") - self.maybe_reload(src_path) - - def on_modified(self, event: DirModifiedEvent | FileModifiedEvent): - src_path = event.src_path - if "__pycache__" in src_path: - return - - # check to make sure this isn't a directory - if path.isdir(src_path): - return - - # get the directory name of the plugin so that we can find its "main.py" and reload it; the - # file that changed is not necessarily the one that needs to be reloaded - self.logger.debug(f"file modified: {src_path}") - self.maybe_reload(src_path) - -class Loader: - def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None: - self.loop = loop - self.logger = getLogger("Loader") - self.plugin_path = plugin_path - self.logger.info(f"plugin_path: {self.plugin_path}") - self.plugins: Plugins = {} - self.watcher = None - self.live_reload = live_reload - self.reload_queue: ReloadQueue = Queue() - self.loop.create_task(self.handle_reloads()) - - if live_reload: - self.observer = Observer() - self.watcher = FileChangeHandler(self.reload_queue, plugin_path) - self.observer.schedule(self.watcher, self.plugin_path, recursive=True) # type: ignore - self.observer.start() - self.loop.create_task(self.enable_reload_wait()) - - server_instance.web_app.add_routes([ - 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/{plugin_name}/frontend_bundle", self.handle_frontend_bundle), - web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call), - web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets), - web.post("/plugins/{plugin_name}/reload", self.handle_backend_reload_request), - - # The following is legacy plugin code. - web.get("/plugins/load_main/{name}", self.load_plugin_main_view), - web.get("/plugins/plugin_resource/{name}/{path:.+}", self.handle_sub_route), - web.get("/steam_resource/{path:.+}", self.get_steam_resource) - ]) - - async def enable_reload_wait(self): - if self.live_reload: - await sleep(10) - if self.watcher: - self.logger.info("Hot reload enabled") - self.watcher.disabled = False - - async def handle_frontend_assets(self, request: web.Request): - file = path.join(path.dirname(__file__), "static", request.match_info["path"]) - - return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) - - async def handle_frontend_locales(self, request: web.Request): - req_lang = request.match_info["path"] - file = path.join(path.dirname(__file__), "locales", req_lang) - if exists(file): - return web.FileResponse(file, headers={"Cache-Control": "no-cache", "Content-Type": "application/json"}) - else: - self.logger.info(f"Language {req_lang} not available, returning an empty dictionary") - return web.json_response(data={}, headers={"Cache-Control": "no-cache"}) - - async def get_plugins(self, request: web.Request): - 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]) - - async def handle_plugin_frontend_assets(self, request: web.Request): - plugin = self.plugins[request.match_info["plugin_name"]] - file = path.join(self.plugin_path, plugin.plugin_directory, "dist/assets", request.match_info["path"]) - - return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) - - async def handle_frontend_bundle(self, request: web.Request): - plugin = self.plugins[request.match_info["plugin_name"]] - - with open(path.join(self.plugin_path, plugin.plugin_directory, "dist/index.js"), "r", encoding="utf-8") as bundle: - return web.Response(text=bundle.read(), content_type="application/javascript") - - def import_plugin(self, file: str, plugin_directory: str, refresh: bool | None = False, batch: bool | None = False): - try: - plugin = PluginWrapper(file, plugin_directory, self.plugin_path) - if plugin.name in self.plugins: - if not "debug" in plugin.flags and refresh: - self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded") - return - else: - self.plugins[plugin.name].stop() - self.plugins.pop(plugin.name, None) - if plugin.passive: - self.logger.info(f"Plugin {plugin.name} is passive") - self.plugins[plugin.name] = plugin.start() - self.logger.info(f"Loaded {plugin.name}") - if not batch: - self.loop.create_task(self.dispatch_plugin(plugin.name if not plugin.legacy else "$LEGACY_" + plugin.name, plugin.version)) - except Exception as e: - self.logger.error(f"Could not load {file}. {e}") - print_exc() - - async def dispatch_plugin(self, name: str, version: str | None): - gpui_tab = await get_gamepadui_tab() - await gpui_tab.evaluate_js(f"window.importDeckyPlugin('{name}', '{version}')") - - def import_plugins(self): - self.logger.info(f"import plugins from {self.plugin_path}") - - directories = [i for i in listdir(self.plugin_path) if path.isdir(path.join(self.plugin_path, i)) and path.isfile(path.join(self.plugin_path, i, "plugin.json"))] - for directory in directories: - self.logger.info(f"found plugin: {directory}") - self.import_plugin(path.join(self.plugin_path, directory, "main.py"), directory, False, True) - - async def handle_reloads(self): - while True: - args = await self.reload_queue.get() - self.import_plugin(*args) # type: ignore - - async def handle_plugin_method_call(self, request: web.Request): - res = {} - plugin = self.plugins[request.match_info["plugin_name"]] - method_name = request.match_info["method_name"] - try: - method_info = await request.json() - args: Any = method_info["args"] - except JSONDecodeError: - args = {} - try: - if method_name.startswith("_"): - raise RuntimeError("Tried to call private method") - res["result"] = await plugin.execute_method(method_name, args) - res["success"] = True - except Exception as e: - res["result"] = str(e) - res["success"] = False - return web.json_response(res) - - """ - The following methods are used to load legacy plugins, which are considered deprecated. - I made the choice to re-add them so that the first iteration/version of the react loader - can work as a drop-in replacement for the stable branch of the PluginLoader, so that we - can introduce it more smoothly and give people the chance to sample the new features even - without plugin support. They will be removed once legacy plugins are no longer relevant. - """ - async def load_plugin_main_view(self, request: web.Request): - plugin = self.plugins[request.match_info["name"]] - with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), "r", encoding="utf-8") as template: - template_data = template.read() - ret = f""" - - - - {template_data} - """ - return web.Response(text=ret, content_type="text/html") - - async def handle_sub_route(self, request: web.Request): - plugin = self.plugins[request.match_info["name"]] - route_path = request.match_info["path"] - self.logger.info(path) - ret = "" - file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path) - with open(file_path, "r", encoding="utf-8") as resource_data: - ret = resource_data.read() - - return web.Response(text=ret) - - async def get_steam_resource(self, request: web.Request): - tab = await get_tab("SP") - try: - return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html") - except Exception as e: - return web.Response(text=str(e), status=400) - - async def handle_backend_reload_request(self, request: web.Request): - plugin_name : str = request.match_info["plugin_name"] - plugin = self.plugins[plugin_name] - - await self.reload_queue.put((plugin.file, plugin.plugin_directory)) - - return web.Response(status=200) \ No newline at end of file diff --git a/backend/locales/bg-BG.json b/backend/locales/bg-BG.json deleted file mode 100644 index b9c4d803..00000000 --- a/backend/locales/bg-BG.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "stable": "Стабилен", - "testing": "Тестване", - "label": "Канал за обновления", - "prerelease": "Предварителни издания" - } - }, - "Developer": { - "5secreload": "Презареждане след 5 секунди", - "disabling": "Изключване на React DevTools", - "enabling": "Включване на React DevTools" - }, - "DropdownMultiselect": { - "button": { - "back": "Назад" - } - }, - "FilePickerError": { - "errors": { - "unknown": "Възникна неизвестна грешка. Грешката в суров вид е: {{raw_error}}", - "file_not_found": "Посоченият път е неправилен. Проверете го и го въведете правилно.", - "perm_denied": "Нямате достъп до посочената папка. Проверете дали потребителят (deck на Steam Deck) има съответните правомощия за достъп до посочената папка/файл." - } - }, - "FilePickerIndex": { - "file": { - "select": "Избиране на този файл" - }, - "files": { - "all_files": "Всички файлове", - "file_type": "Файлов тип", - "show_hidden": "Показване на скритите файлове" - }, - "filter": { - "created_asce": "Дата на създаване (първо най-старите)", - "created_desc": "Дата на създаване (първо най-новите)", - "modified_asce": "Дата на промяна (първо най-старите)", - "modified_desc": "Дата на промяна (първо най-новите)", - "name_asce": "Я-А", - "name_desc": "А-Я", - "size_asce": "Размер (първо най-малките)", - "size_desc": "Размер (първо най-големите)" - }, - "folder": { - "label": "Папка", - "show_more": "Показване на още файлове", - "select": "Използване на тази папка" - } - }, - "MultiplePluginsInstallModal": { - "description": { - "install": "Инсталиране на {{name}} {{version}}", - "reinstall": "Преинсталиране на {{name}} {{version}}", - "update": "Обновяване на {{name}} до {{version}}" - }, - "ok_button": { - "idle": "Потвърждаване", - "loading": "В процес на работа" - }, - "title": { - "mixed_one": "Промяна на {{count}} добавка", - "mixed_other": "Промяна на {{count}} добавки", - "update_one": "Обновяване на 1 добавка", - "update_other": "Обновяване на {{count}} добавки", - "install_one": "Инсталиране на 1 добавка", - "install_other": "Инсталиране на {{count}} добавки", - "reinstall_one": "Преинсталиране на 1 добавка", - "reinstall_other": "Преинсталиране на {{count}} добавки" - }, - "confirm": "Наистина ли искате да направите следните промени?" - }, - "PluginCard": { - "plugin_full_access": "Тази добавка има пълен достъп до Вашия Steam Deck.", - "plugin_install": "Инсталиране", - "plugin_no_desc": "Няма описание.", - "plugin_version_label": "Версия на добавката" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Инсталиране", - "desc": "Наистина ли искате да инсталирате {{artifact}} {{version}}?", - "title": "Инсталиране на {{artifact}}", - "button_processing": "Инсталиране" - }, - "reinstall": { - "button_idle": "Преинсталиране", - "button_processing": "Преинсталиране", - "desc": "Наистина ли искате да преинсталирате {{artifact}} {{version}}?", - "title": "Преинсталиране на {{artifact}}" - }, - "update": { - "button_idle": "Обновяване", - "title": "Обновяване на {{artifact}}", - "button_processing": "Обновяване", - "desc": "Наистина ли искате да обновите {{artifact}} {{version}}?" - }, - "no_hash": "Тази добавка няма хеш. Инсталирате я на свой собствен риск." - }, - "PluginListIndex": { - "hide": "Бърз достъп: Скриване", - "no_plugin": "Няма инсталирани добавки!", - "plugin_actions": "Действия с добавката", - "reinstall": "Преинсталиране", - "uninstall": "Деинсталиране", - "update_to": "Обновяване до {{name}}", - "reload": "Презареждане", - "show": "Бърз достъп: Показване", - "update_all_one": "Обновяване на 1 добавка", - "update_all_other": "Обновяване на {{count}} добавки" - }, - "PluginListLabel": { - "hidden": "Скрито от менюто за бърз достъп" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Грешка", - "plugin_load_error": { - "message": "Грешка при зареждането на добавката {{name}}", - "toast": "Грешка при зареждането на {{name}}" - }, - "plugin_uninstall": { - "button": "Деинсталиране", - "desc": "Наистина ли искате да деинсталирате {{name}}?", - "title": "Деинсталиране на {{name}}" - }, - "plugin_update_one": "Има налично обновление за 1 добавка!", - "plugin_update_other": "Има налични обновления за {{count}} добавки!", - "decky_update_available": "Има налично обновление до {{tag_name}}!", - "plugin_error_uninstall": "Зареждането на {{name}} предизвика грешка, както се вижда по-горе. Това обикновено означава, че добавката изисква обновяване на новата версия на SteamUI. Проверете дали има обновление или изберете да я премахнете в настройките на Decky, в раздела с добавките." - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Разрешаване на достъп без удостоверяване до дебъгера на CEF на всеки от Вашата мрежа", - "label": "Разрешаване на отдалеченото дебъгване на CEF" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Отваряне на конзолата", - "label": "Конзола на CEF", - "desc": "Отваря конзолата на CEF. Това има смисъл единствено за дебъгване. Нещата тук може да са опасни и трябва да бъдат използвани само ако Вие сте разработчик на добавка, или получавате насоки от такъв." - }, - "header": "Други", - "react_devtools": { - "ip_label": "IP", - "label": "Включване на React DevTools", - "desc": "Включва свързването към компютър, на който работи React DevTools. Промяната на тази настройка ще презареди Steam. Задайте IP адреса преди да включите това." - }, - "third_party_plugins": { - "button_install": "Инсталиране", - "button_zip": "Разглеждане", - "header": "Добавки от външен източник", - "label_desc": "Адрес", - "label_zip": "Инсталиране на добавка от файл ZIP", - "label_url": "Инсталиране на добавка от адрес в Интернет" - }, - "valve_internal": { - "desc2": "Не пипайте нищо в това меню, освен ако не знаете какво правите.", - "label": "Включване на вътрешното меню на Valve", - "desc1": "Включва вътрешното меню за разработчици на Valve." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Версия на Decky", - "header": "Относно" - }, - "developer_mode": { - "label": "Режим за разработчици" - }, - "notifications": { - "decky_updates_label": "Има налично обновление на Decky", - "header": "Известия", - "plugin_updates_label": "Има налични обновления на добавките" - }, - "other": { - "header": "Други" - }, - "updates": { - "header": "Обновления" - }, - "beta": { - "header": "Участие в бета-версии" - } - }, - "SettingsIndex": { - "developer_title": "Разработчик", - "general_title": "Общи", - "plugins_title": "Добавки" - }, - "Store": { - "store_contrib": { - "label": "Допринасяне", - "desc": "Ако искате да допринесете към магазина за добавки на Decky, разгледайте хранилището SteamDeckHomebrew/decky-plugin-template в GitHub. Може да намерите информация относно разработката и разпространението във файла README." - }, - "store_filter": { - "label": "Филтър", - "label_def": "Всички" - }, - "store_search": { - "label": "Търсене" - }, - "store_sort": { - "label": "Подредба", - "label_def": "Последно обновление (първо най-новите)" - }, - "store_source": { - "label": "Изходен код", - "desc": "Целият изходен код е наличен в хранилището SteamDeckHomebrew/decky-plugin-database в GitHub." - }, - "store_tabs": { - "about": "Относно", - "alph_asce": "По азбучен ред (Я -> А)", - "alph_desc": "По азбучен ред (А -> Я)", - "title": "Разглеждане" - }, - "store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!" - }, - "StoreSelect": { - "custom_store": { - "label": "Персонализиран магазин", - "url_label": "Адрес" - }, - "store_channel": { - "custom": "Персонализиран", - "default": "По подразбиране", - "label": "Канал за магазина", - "testing": "Тестване" - } - }, - "Updater": { - "decky_updates": "Обновления на Decky", - "patch_notes_desc": "Бележки за промените", - "updates": { - "check_button": "Проверка за обновления", - "checking": "Проверяване", - "cur_version": "Текуща версия: {{ver}}", - "label": "Обновления", - "lat_version": "Използвате най-новата версия: {{ver}}", - "reloading": "Презареждане", - "updating": "Обновяване", - "install_button": "Инсталиране на обновлението" - }, - "no_patch_notes_desc": "няма бележки за промените в тази версия" - }, - "PluginView": { - "hidden_one": "1 добавка е скрита от този списък", - "hidden_other": "{{count}} добавки са скрити от този списък" - } -} diff --git a/backend/locales/cs-CZ.json b/backend/locales/cs-CZ.json deleted file mode 100644 index 74b7230c..00000000 --- a/backend/locales/cs-CZ.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "Aktualizační kanál", - "prerelease": "Předběžná vydání", - "stable": "Stabilní", - "testing": "Testování" - } - }, - "Developer": { - "disabling": "Vypínám React DevTools", - "enabling": "Zapínám React DevTools", - "5secreload": "Znovu načtení za 5 vteřin" - }, - "FilePickerIndex": { - "folder": { - "select": "Použít tuto složku", - "label": "Složka", - "show_more": "Zobrazit více souborů" - }, - "filter": { - "created_asce": "Vytvořeno (Nejstarší)", - "created_desc": "Vytvořeno (Nejnovější)", - "modified_asce": "Upraveno (Nejstarší)", - "modified_desc": "Upraveno (Nejnovější)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Velikost (Nejmenší)", - "size_desc": "Velikost (Největší)" - }, - "files": { - "show_hidden": "Zobrazit skryté soubory", - "all_files": "Všechny soubory", - "file_type": "Typ souboru" - }, - "file": { - "select": "Vybrat tento soubor" - } - }, - "PluginView": { - "hidden_one": "1 plugin je v tomto seznamu skrytý", - "hidden_few": "{{count}} pluginů je v tomto seznamu skryto", - "hidden_other": "{{count}} pluginů je v tomto seznamu skryto" - }, - "PluginListLabel": { - "hidden": "Skryto z nabídky rychlého přístupu" - }, - "PluginCard": { - "plugin_full_access": "Tento plugin má plný přístup k vašemu Steam Decku.", - "plugin_install": "Instalovat", - "plugin_no_desc": "Nebyl uveden žádný popis.", - "plugin_version_label": "Verze pluginu" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalovat", - "button_processing": "Instalování", - "title": "Instalovat {{artifact}}", - "desc": "Jste si jisti, že chcete nainstalovat {{artifact}} {{version}}?" - }, - "no_hash": "Tento plugin nemá hash, instalujete jej na vlastní nebezpečí.", - "reinstall": { - "button_idle": "Přeinstalovat", - "button_processing": "Přeinstalování", - "title": "Přeinstalovat {{artifact}}", - "desc": "Jste si jisti, že chcete přeinstalovat {{artifact}} {{version}}?" - }, - "update": { - "button_idle": "Aktualizovat", - "button_processing": "Aktualizování", - "desc": "Jste si jisti, že chcete aktualizovat {{artifact}} {{version}}?", - "title": "Aktualizovat {{artifact}}" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Upravit {{count}} plugin", - "mixed_few": "Upravit {{count}} pluginů", - "mixed_other": "Upravit {{count}} pluginů", - "reinstall_one": "Přeinstalovat 1 plugin", - "reinstall_few": "Přeinstalovat {{count}} pluginů", - "reinstall_other": "Přeinstalovat {{count}} pluginů", - "install_one": "Instalovat 1 plugin", - "install_few": "Instalovat {{count}} pluginů", - "install_other": "Instalovat {{count}} pluginů", - "update_one": "Aktualizovat 1 plugin", - "update_few": "Aktualizovat {{count}} pluginů", - "update_other": "Aktualizovat {{count}} pluginů" - }, - "ok_button": { - "idle": "Potvrdit", - "loading": "Probíhá" - }, - "description": { - "install": "Instalovat {{name}} {{version}}", - "update": "Aktualizovat {{name}} na {{version}}", - "reinstall": "Přeinstalovat {{name}} {{version}}" - }, - "confirm": "Jste si jisti, že chcete udělat následující úpravy?" - }, - "PluginListIndex": { - "no_plugin": "Nejsou nainstalovány žádné pluginy!", - "plugin_actions": "Akce pluginu", - "reinstall": "Přeinstalovat", - "reload": "Znovu načíst", - "uninstall": "Odinstalovat", - "update_to": "Aktualizovat na {{name}}", - "show": "Rychlý přístup: Zobrazit", - "hide": "Rychlý přístup: Skrýt", - "update_all_one": "Aktualizovat 1 plugin", - "update_all_few": "Aktualizovat {{count}} pluginů", - "update_all_other": "Aktualizovat {{count}} pluginů" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Aktualizace na {{tag_name}} dostupná!", - "error": "Chyba", - "plugin_load_error": { - "message": "Chyba při načítání pluginu {{name}}", - "toast": "Chyba při načítání {{name}}" - }, - "plugin_uninstall": { - "button": "Odinstalovat", - "desc": "Opravdu chcete odinstalovat {{name}}?", - "title": "Odinstalovat {{name}}" - }, - "plugin_update_one": "Je dostupná aktualizace pro 1 plugin!", - "plugin_update_few": "Jsou dostupné aktualizace pro {{count}} pluginů!", - "plugin_update_other": "Jsou dostupné aktualizace pro {{count}} pluginů!", - "plugin_error_uninstall": "Načítání {{name}} způsobilo chybu uvedenou výše. To obvykle znamená, že plugin vyžaduje aktualizaci SteamUI. Zkontrolujte, zda je aktualizace k dispozici, nebo zvažte odstranění pluginu v nastavení Decky v sekci Pluginy." - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Otevřít konzoli", - "label": "CEF konzole", - "desc": "Otevře CEF konzoli. Užitečné pouze pro účely ladění. Věci zde jsou potenciálně nebezpečné a měly by být používány pouze v případě, že jste vývojář pluginů, nebo vás sem nějaký nasměroval." - }, - "header": "Ostatní", - "react_devtools": { - "desc": "Umožňuje připojení k počítači, na kterém běží React DevTools. Změnou tohoto nastavení se znovu načte Steam. Před povolením nastavte IP adresu.", - "ip_label": "IP adresa", - "label": "Zapnout React DevTools" - }, - "third_party_plugins": { - "button_install": "Instalovat", - "button_zip": "Procházet", - "header": "Pluginy třetí strany", - "label_desc": "URL", - "label_url": "Instalovat plugin z URL", - "label_zip": "Instalovat plugin ze ZIP souboru" - }, - "valve_internal": { - "desc1": "Zapíná interní vývojářské menu Valve.", - "desc2": "Nedotýkejte se ničeho v této nabídce, pokud nevíte, co děláte.", - "label": "Zapnout Valve Internal" - } - }, - "RemoteDebugging": { - "remote_cef": { - "label": "Povolit vzdálené CEF ladění", - "desc": "Umožní neověřený přístup k CEF ladění komukoli ve vaší síti" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky verze", - "header": "O Decky" - }, - "beta": { - "header": "Účast v betě" - }, - "developer_mode": { - "label": "Vývojářský režim" - }, - "other": { - "header": "Ostatní" - }, - "updates": { - "header": "Aktualizace" - }, - "notifications": { - "decky_updates_label": "Dostupná aktualizace Decky", - "header": "Notifikace", - "plugin_updates_label": "Dostupná aktualizace pluginu" - } - }, - "SettingsIndex": { - "developer_title": "Vývojář", - "general_title": "Obecné", - "plugins_title": "Pluginy" - }, - "Store": { - "store_contrib": { - "label": "Přispívání", - "desc": "Pokud byste chtěli přispět do obchodu Decky Plugin Store, podívejte se na repozitář SteamDeckHomebrew/decky-plugin-template na GitHubu. Informace o vývoji a distribuci jsou k dispozici v README." - }, - "store_filter": { - "label": "Filtr", - "label_def": "Vše" - }, - "store_search": { - "label": "Hledat" - }, - "store_sort": { - "label": "Seřadit", - "label_def": "Naposledy aktualizováno (Nejnovější)" - }, - "store_source": { - "desc": "Veškerý zdrojový kód pluginu je dostupný v repozitáři SteamDeckHomebrew/decky-plugin-database na GitHubu.", - "label": "Zdrojový kód" - }, - "store_tabs": { - "about": "O Decky Plugin Store", - "alph_asce": "Abecedně (Z do A)", - "alph_desc": "Abecedně (A do Z)", - "title": "Procházet" - }, - "store_testing_cta": "Zvažte prosím testování nových pluginů, pomůžete tím týmu Decky Loader!", - "store_testing_warning": { - "desc": "Tento kanál obchodu můžete použít k testování nejnovějších verzí pluginů. Nezapomeňte zanechat zpětnou vazbu na GitHubu, aby bylo možné plugin aktualizovat pro všechny uživatele.", - "label": "Vítejte na testovacím kanálu obchodu" - } - }, - "StoreSelect": { - "custom_store": { - "label": "Vlastní obchod", - "url_label": "URL" - }, - "store_channel": { - "custom": "Vlastní", - "default": "Výchozí", - "label": "Kanál obchodu", - "testing": "Testování" - } - }, - "Updater": { - "updates": { - "lat_version": "Aktuální: běží na verzi {{ver}}", - "reloading": "Znovu načítání", - "updating": "Aktualizování", - "check_button": "Zkontrolovat aktualizace", - "checking": "Kontrolování", - "cur_version": "Aktuální verze: {{ver}}", - "install_button": "Instalovat aktualizaci", - "label": "Aktualizace" - }, - "decky_updates": "Aktualizace Decky", - "patch_notes_desc": "Poznámky k verzi", - "no_patch_notes_desc": "žádné poznámky pro tuto verzi" - }, - "DropdownMultiselect": { - "button": { - "back": "Zpět" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "Zadaná cesta není platná. Zkontrolujte ji a zadejte znovu správně.", - "unknown": "Nastala neznámá chyba. Nezpracovaná chyba je: {{raw_error}}", - "perm_denied": "Nemáte přístup k zadanému adresáři. Zkontrolujte, zda jako uživatel (deck na Steam Decku) máte odpovídající oprávnění pro přístup k dané složce/souboru." - } - }, - "TitleView": { - "settings_desc": "Otevřít nastavení Decky", - "decky_store_desc": "Otevřít obchod Decky" - } -} diff --git a/backend/locales/de-DE.json b/backend/locales/de-DE.json deleted file mode 100644 index 4ded8703..00000000 --- a/backend/locales/de-DE.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "Updatekanal", - "prerelease": "Vorabveröffentlichung", - "stable": "Standard", - "testing": "Test" - } - }, - "Developer": { - "disabling": "Deaktiviere", - "enabling": "Aktiviere", - "5secreload": "Neu laden in 5 Sekunden" - }, - "FilePickerIndex": { - "folder": { - "select": "Diesen Ordner verwenden" - } - }, - "PluginCard": { - "plugin_install": "Installieren", - "plugin_no_desc": "Keine Beschreibung angegeben.", - "plugin_version_label": "Erweiterungs Version", - "plugin_full_access": "Diese Erweiterung hat uneingeschränkten Zugriff auf dein Steam Deck." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Installieren", - "button_processing": "Wird installiert", - "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} installieren willst?", - "title": "Installiere {{artifact}}" - }, - "reinstall": { - "button_idle": "Neu installieren", - "button_processing": "Wird neu installiert", - "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} neu installieren willst?", - "title": "Neu installation {{artifact}}" - }, - "update": { - "button_idle": "Aktualisieren", - "button_processing": "Wird aktualisiert", - "title": "Aktualisiere {{artifact}}", - "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} aktualisieren willst?" - }, - "no_hash": "Diese Erweiterung besitzt keine Prüfsumme, Installation auf eigene Gefahr." - }, - "PluginListIndex": { - "no_plugin": "Keine Erweiterungen installiert!", - "plugin_actions": "Erweiterungs Aktionen", - "reinstall": "Neu installieren", - "reload": "Neu laden", - "uninstall": "Deinstallieren", - "update_to": "Aktualisieren zu {{name}}", - "update_all_one": "", - "update_all_other": "" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Eine neue Version ({{tag_name}}) ist verfügbar!", - "error": "Fehler", - "plugin_load_error": { - "toast": "Fehler beim Laden von {{name}}", - "message": "Fehler beim Laden von {{name}}" - }, - "plugin_uninstall": { - "button": "Deinstallieren", - "desc": "Bist du dir sicher, dass du {{name}} deinstallieren willst?", - "title": "Deinstalliere {{name}}" - }, - "plugin_error_uninstall": "Das Laden von {{name}} hat einen Fehler verursacht. Dies bedeutet normalerweise, dass die Erweiterung ein Update für die neue Version von SteamUI benötigt. Prüfe in den Decky-Einstellungen im Bereich Erweiterungen, ob ein Update vorhanden ist.", - "plugin_update_one": "1 Erweiterung kann aktualisiert werden!", - "plugin_update_other": "{{count}} Erweiterungen können aktualisiert werden!" - }, - "RemoteDebugging": { - "remote_cef": { - "label": "Remote CEF Debugging Zugriff", - "desc": "Erlaubt jedem aus dem Neztwerk unautorisierten Zugriff auf den CEF Debugger" - } - }, - "SettingsDeveloperIndex": { - "header": "Sonstiges", - "react_devtools": { - "ip_label": "IP", - "label": "Aktiviere React DevTools", - "desc": "Erlaubt die Verbindung mit einem anderen Rechner, auf welchem React DevTools läuft. Eine Änderung startet Steam neu. Die IP Adresse muss vor Aktivierung ausgefüllt sein." - }, - "third_party_plugins": { - "button_zip": "Durchsuchen", - "header": "Erweiterungen von Drittanbietern", - "label_desc": "URL", - "label_zip": "Installiere Erweiterung via ZIP Datei", - "button_install": "Installieren", - "label_url": "Installiere Erweiterung via URL" - }, - "valve_internal": { - "desc2": "Fasse in diesem Menü nichts an, es sei denn, du weißt was du tust.", - "label": "Aktiviere Valve-internes Menü", - "desc1": "Aktiviert das Valve-interne Entwickler Menü." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky Version", - "header": "Über" - }, - "beta": { - "header": "Beta Teilnahme" - }, - "developer_mode": { - "label": "Entwickleroptionen" - }, - "other": { - "header": "Sonstiges" - }, - "updates": { - "header": "Aktualisierungen" - } - }, - "SettingsIndex": { - "developer_title": "Entwickler", - "general_title": "Allgemein", - "plugins_title": "Erweiterungen" - }, - "Store": { - "store_contrib": { - "label": "Mitwirken", - "desc": "Wenn du Erweiterungen im Decky Store veröffentlichen willst, besuche die SteamDeckHomebrew/decky-plugin-template Repository auf GitHub. Informationen rund um Entwicklung und Veröffentlichung findest du in der README." - }, - "store_filter": { - "label": "Filter", - "label_def": "Alle" - }, - "store_search": { - "label": "Suche" - }, - "store_sort": { - "label": "Sortierung", - "label_def": "Zuletzt aktualisiert" - }, - "store_source": { - "desc": "Jeder Erweiterungs Quellcode ist in der SteamDeckHomebrew/decky-plugin-database Repository auf GitHub verfügbar.", - "label": "Quellcode" - }, - "store_tabs": { - "about": "Über", - "alph_asce": "Alphabetisch (Z zu A)", - "alph_desc": "Alphabetisch (A zu Z)", - "title": "Durchstöbern" - }, - "store_testing_cta": "Unterstütze das Decky Loader Team mit dem Testen von neuen Erweiterungen!" - }, - "StoreSelect": { - "custom_store": { - "label": "Benutzerdefinierter Marktplatz", - "url_label": "URL" - }, - "store_channel": { - "custom": "Benutzerdefiniert", - "default": "Standard", - "label": "Marktplatz Kanal", - "testing": "Test" - } - }, - "Updater": { - "decky_updates": "Decky Aktualisierungen", - "patch_notes_desc": "Patchnotizen", - "updates": { - "check_button": "Auf Aktualisierungen prüfen", - "checking": "Wird überprüft", - "cur_version": "Aktualle Version: {{ver}}", - "install_button": "Aktualisierung installieren", - "label": "Aktualisierungen", - "lat_version": "{{ver}} ist die aktuellste", - "reloading": "Lade neu", - "updating": "Aktualisiere" - }, - "no_patch_notes_desc": "Für diese Version gibt es keine Patchnotizen" - }, - "PluginView": { - "hidden_one": "", - "hidden_other": "" - }, - "MultiplePluginsInstallModal": { - "title": { - "install_one": "", - "install_other": "", - "mixed_one": "", - "mixed_other": "", - "update_one": "", - "update_other": "", - "reinstall_one": "", - "reinstall_other": "" - } - } -} diff --git a/backend/locales/el-GR.json b/backend/locales/el-GR.json deleted file mode 100644 index 62562935..00000000 --- a/backend/locales/el-GR.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "SettingsDeveloperIndex": { - "react_devtools": { - "desc": "Επιτρέπει την σύνδεση με υπολογιστή που τρέχει React DevTools. Η αλλαγή αυτής της ρύθμισης θα προκαλέσει επαναφόρτωση του Steam. Ωρίστε την διεύθυνση IP πριν την ενεργοποιήσετε.", - "ip_label": "IP", - "label": "Ενεργοποίηση React DevTools" - }, - "third_party_plugins": { - "button_install": "Εγκατάσταση", - "button_zip": "Περιήγηση", - "header": "Επεκτάσεις τρίτων", - "label_desc": "URL", - "label_url": "Εγκατάσταση επέκτασης απο URL", - "label_zip": "Εγκατάσταση επέκτασης από αρχείο ZIP" - }, - "valve_internal": { - "desc1": "Ενεργοποιεί το μενού προγραμματιστή της Valve.", - "desc2": "Μην αγγίξετε τίποτα σε αυτό το μενού εκτός και αν ξέρετε τι κάνει.", - "label": "Ενεργοποιήση εσωτερικού μενού Valve" - }, - "cef_console": { - "button": "Άνοιγμα Κονσόλας", - "desc": "Ανοίγει την Κονσόλα CEF. Χρήσιμο μόνο για εντοπισμό σφαλμάτων. Τα πράγματα εδώ είναι δυνητικά επικίνδυνα και θα πρέπει να χρησιμοποιηθεί μόνο εάν είστε προγραμματιστής επεκτάσεων, ή κατευθυνθήκατε εδώ από έναν προγραμματιστή.", - "label": "Κονσόλα CEF" - }, - "header": "Άλλα" - }, - "BranchSelect": { - "update_channel": { - "prerelease": "Προ-κυκλοφορία", - "stable": "Σταθερό", - "label": "Κανάλι ενημερώσεων", - "testing": "Δοκιμαστικό" - } - }, - "Developer": { - "5secreload": "Γίνεται επαναφόρτωση σε 5 δευτερόλεπτα", - "disabling": "Γίνεται απενεργοποίηση των React DevTools", - "enabling": "Γίνεται ενεργοποίηση των React DevTools" - }, - "PluginCard": { - "plugin_no_desc": "Δεν υπάρχει περιγραφή.", - "plugin_full_access": "Αυτή η επέκταση έχει πλήρη πρόσβαση στο Steam Deck σας.", - "plugin_install": "Εγκατάσταση", - "plugin_version_label": "Έκδοση επέκτασης" - }, - "PluginInstallModal": { - "install": { - "desc": "Σίγουρα θέλετε να εγκαταστήσετε το {{artifact}}{{version}};", - "button_idle": "Εγκατάσταση", - "button_processing": "Γίνεται εγκατάσταση", - "title": "Εγκατάσταση {{artifact}}" - }, - "no_hash": "Αυτή η επέκταση δεν έχει υπογραφή, την εγκαθηστάτε με δικό σας ρίσκο.", - "reinstall": { - "button_idle": "Επανεγκατάσταση", - "button_processing": "Γίνεται επανεγκατάσταση", - "desc": "Σίγουρα θέλετε να επανεγκαταστήσετε το {{artifact}}{{version}};", - "title": "Επανεγκατάσταση {{artifact}}" - }, - "update": { - "button_idle": "Ενημέρωση", - "desc": "Σίγουρα θέλετε να ενημερώσετε το {{artifact}} {{version}};", - "title": "Ενημέρωση {{artifact}}", - "button_processing": "Γίνεται ενημέρωση" - } - }, - "PluginListIndex": { - "no_plugin": "Δεν υπάρχουν εγκατεστημένες επεκτάσεις!", - "plugin_actions": "Ενέργειες επεκτάσεων", - "reinstall": "Επανεγκατάσταση", - "reload": "Επαναφόρτωση", - "uninstall": "Απεγκατάσταση", - "update_to": "Ενημέρωση σε {{name}}", - "update_all_one": "Ενημέρωση 1 επέκτασης", - "update_all_other": "Ενημέρωση {{count}} επεκτάσεων", - "show": "Γρήγορη πρόσβαση: Εμφάνιση", - "hide": "Γρήγορη πρόσβαση: Απόκρυψη" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Ενημέρωση σε {{tag_name}} διαθέσιμη!", - "error": "Σφάλμα", - "plugin_error_uninstall": "Η φόρτωση του {{name}} προκάλεσε το παραπάνω σφάλμα. Αυτό συνήθως σημαίνει ότι η επέκταση απαιτεί ενημέρωση για τη νέα έκδοση του SteamUI. Ελέγξτε εάν υπάρχει ενημέρωση ή αξιολογήστε την απεγκαταστήσετε της επέκτασης στις ρυθμίσεις του Decky, στην ενότητα Επεκτάσεις.", - "plugin_load_error": { - "message": "Σφάλμα στη φόρτωση της επέκτασης {{name}}", - "toast": "Σφάλμα φόρτωσης {{name}}" - }, - "plugin_uninstall": { - "button": "Απεγκατάσταση", - "desc": "Σίγουρα θέλετε να απεγκαταστήσετε το {{name}};", - "title": "Απεγκατάσταση {{name}}" - }, - "plugin_update_one": "Διαθέσιμη ενημέρωση για 1 επέκταση!", - "plugin_update_other": "Διαθέσιμες ενημερώσεις για {{count}} επεκτάσεις!" - }, - "RemoteDebugging": { - "remote_cef": { - "label": "Να επιτρέπεται η απομακρυσμένη πρόσβαση στον CEF debugger", - "desc": "Να επιτρέπεται η ανεξέλεγκτη πρόσβαση στον CEF debugger σε οποιονδήποτε στο τοπικό δίκτυο" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Έκδοση Decky", - "header": "Σχετικά" - }, - "developer_mode": { - "label": "Λειτουργία προγραμματιστή" - }, - "other": { - "header": "Άλλα" - }, - "updates": { - "header": "Ενημερώσεις" - }, - "beta": { - "header": "Συμμετοχή στη Beta" - }, - "notifications": { - "decky_updates_label": "Διαθέσιμη ενημέρωση του Decky", - "header": "Ειδοποιήσεις", - "plugin_updates_label": "Διαθέσιμες ενημερώσεις επεκτάσεων" - } - }, - "SettingsIndex": { - "plugins_title": "Επεκτάσεις", - "developer_title": "Προγραμματιστής", - "general_title": "Γενικά" - }, - "Store": { - "store_contrib": { - "label": "Συνεισφέροντας", - "desc": "Αν θέλετε να συνεισφέρετε στο κατάστημα επεκτάσεων του Decky, τσεκάρετε το SteamDeckHomebrew/decky-plugin-template repository στο GitHub. Πληροφορίες σχετικά με τη δημιουργία και τη διανομή επεκτάσεων είναι διαθέσιμες στο README." - }, - "store_filter": { - "label": "Φίλτρο", - "label_def": "Όλα" - }, - "store_search": { - "label": "Αναζήτηση" - }, - "store_sort": { - "label": "Ταξινόμηση", - "label_def": "Τελευταία ενημέρωση (Νεότερα)" - }, - "store_source": { - "desc": "Ο πηγαίος κώδικας όλων των επεκτάσεων είναι διαθέσιμος στο SteamDeckHomebrew/decky-plugin-database repository στο GitHub.", - "label": "Πηγαίος κώδικας" - }, - "store_tabs": { - "about": "Σχετικά", - "alph_asce": "Αλφαβητικά (Ζ σε Α)", - "alph_desc": "Αλφαβητικά (Α σε Ζ)", - "title": "Περιήγηση" - }, - "store_testing_cta": "Παρακαλώ σκεφτείτε να τεστάρετε νέες επεκτάσεις για να βοηθήσετε την ομάδα του Decky Loader!", - "store_testing_warning": { - "desc": "Μπορείτε να χρησιμοποιήσετε αυτό το κανάλι του καταστήματος για να δοκιμάσετε τις νεότερες εκδόσεις των επεκτάσεων. Φροντίστε να αφήσετε σχόλια στο GitHub, ώστε να βοηθήσετε στην ενημέρωση της εκάστοτε επέκταση για όλους τους χρήστες.", - "label": "Καλώς ήρθατε στο Δοκιμαστικό Κανάλι τους Καταστήματος" - } - }, - "StoreSelect": { - "custom_store": { - "label": "Προσαρμοσμένο κατάστημα", - "url_label": "URL" - }, - "store_channel": { - "custom": "Προσαρμοσμένο", - "default": "Προεπιλεγμένο", - "label": "Κανάλι καταστήματος", - "testing": "Δοκιμαστικό" - } - }, - "Updater": { - "no_patch_notes_desc": "Κανένα ενημερωτικό σημείωμα για αυτή την έκδοση", - "patch_notes_desc": "Σημειώσεις ενημέρωσης", - "updates": { - "check_button": "Έλεγχος για ενημερώσεις", - "checking": "Γίνεται έλεγχος", - "cur_version": "Τρέχουσα έκδοση: {{ver}}", - "install_button": "Εγκατάσταση ενημέρωσης", - "label": "Ενημερώσεις", - "updating": "Γίνεται ενημέρωση", - "lat_version": "Ενημερωμένο: τρέχουσα έκδοση {{ver}}", - "reloading": "Γίνεται επαναφόρτωση" - }, - "decky_updates": "Ενημερώσεις Decky" - }, - "FilePickerIndex": { - "folder": { - "select": "Χρησιμοποιήστε αυτό το φάκελο", - "label": "Φάκελος", - "show_more": "Εμφάνιση περισσότερων αρχείων" - }, - "filter": { - "modified_asce": "Τροποποιήθηκε (Παλαιότερο)", - "modified_desc": "Τροποποιήθηκε (Νεότερο)", - "created_desc": "Δημιουργήθηκε (Νεότερο)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "created_asce": "Δημιουργήθηκε (Παλαιότερο)", - "size_asce": "Μέγεθος (Μικρότερο)", - "size_desc": "Μέγεθος (Μεγαλύτερο)" - }, - "file": { - "select": "Επιλογή αυτού του αρχείου" - }, - "files": { - "show_hidden": "Εμφάνιση Κρυφών Αρχείων", - "all_files": "Όλα Τα Αρχεία", - "file_type": "Τύπος Αρχείου" - } - }, - "PluginView": { - "hidden_one": "1 επέκταση είναι κρυμμένη σε αυτήν τη λίστα", - "hidden_other": "{{count}} επεκτάσεις είναι κρυμμένες σε αυτήν τη λίστα" - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Τροποποίηση 1 επέκτασης", - "mixed_other": "Τροποποίηση {{count}} επεκτάσεων", - "update_one": "Ενημέρωση 1 επέκτασης", - "update_other": "Ενημέρωση {{count}} επεκτάσεων", - "reinstall_one": "Επανεγκατάσταση 1 επέκτασης", - "reinstall_other": "Επανεγκατάσταση {{count}} επεκτάσεων", - "install_one": "Εγκατάσταση 1 επέκτασης", - "install_other": "Εγκατάσταση {{count}} επεκτάσεων" - }, - "confirm": "Είστε βέβαιοι ότι θέλετε να κάνετε τις ακόλουθες τροποποιήσεις;", - "description": { - "reinstall": "Επανεγκατάσταση {{name}} {{version}}", - "update": "Ενημέρωση {{name}} to {{version}}", - "install": "Εγκατάσταση {{name}} {{version}}" - }, - "ok_button": { - "idle": "Επιβεβαίωση", - "loading": "Φόρτωση" - } - }, - "PluginListLabel": { - "hidden": "Κρυφό στο μενού γρήγορης πρόσβασης" - }, - "TitleView": { - "settings_desc": "Άνοιγμα Ρυθμίσεων Decky", - "decky_store_desc": "Άνοιγμα Καταστήματος Decky" - }, - "DropdownMultiselect": { - "button": { - "back": "Πίσω" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "Η καθορισμένη διαδρομή δεν είναι έγκυρη. Παρακαλούμε ελέγξτε τη και εισάγετέ τη ξανά σωστά.", - "perm_denied": "Δεν έχετε πρόσβαση στην καθορισμένη διαδρομή. Ελέγξτε εάν ο χρήστης σας (deck στο Steam Deck) έχει τα αντίστοιχα δικαιώματα πρόσβασης στον καθορισμένο φάκελο/αρχείο.", - "unknown": "Παρουσιάστηκε άγνωστο σφάλμα. Το σφάλμα είναι: {{raw_error}}" - } - } -} diff --git a/backend/locales/en-US.json b/backend/locales/en-US.json deleted file mode 100644 index 7845ae4f..00000000 --- a/backend/locales/en-US.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "Update Channel", - "prerelease": "Prerelease", - "stable": "Stable", - "testing": "Testing" - } - }, - "Developer": { - "5secreload": "Reloading in 5 seconds", - "disabling": "Disabling React DevTools", - "enabling": "Enabling React DevTools" - }, - "DropdownMultiselect": { - "button": { - "back": "Back" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "The path specified is not valid. Please check it and reenter it correctly.", - "perm_denied": "You do not have access to the specified directory. Please check if your user (deck on Steam Deck) has the corresponding permission to access the specified folder/file.", - "unknown": "An unknown error occurred. The raw error is: {{raw_error}}" - } - }, - "FilePickerIndex": { - "file": { - "select": "Select this file" - }, - "files": { - "all_files": "All Files", - "file_type": "File Type", - "show_hidden": "Show Hidden Files" - }, - "filter": { - "created_asce": "Created (Oldest)", - "created_desc": "Created (Newest)", - "modified_asce": "Modified (Oldest)", - "modified_desc": "Modified (Newest)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Size (Smallest)", - "size_desc": "Size (Largest)" - }, - "folder": { - "label": "Folder", - "select": "Use this folder", - "show_more": "Show more files" - } - }, - "MultiplePluginsInstallModal": { - "confirm": "Are you sure you want to make the following modifications?", - "description": { - "install": "Install {{name}} {{version}}", - "reinstall": "Reinstall {{name}} {{version}}", - "update": "Update {{name}} to {{version}}" - }, - "ok_button": { - "idle": "Confirm", - "loading": "Working" - }, - "title": { - "install_one": "Install 1 plugin", - "install_other": "Install {{count}} plugins", - "mixed_one": "Modify {{count}} plugin", - "mixed_other": "Modify {{count}} plugins", - "reinstall_one": "Reinstall 1 plugin", - "reinstall_other": "Reinstall {{count}} plugins", - "update_one": "Update 1 plugin", - "update_other": "Update {{count}} plugins" - } - }, - "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 {{artifact}}" - } - }, - "PluginListIndex": { - "hide": "Quick access: Hide", - "no_plugin": "No plugins installed!", - "plugin_actions": "Plugin Actions", - "reinstall": "Reinstall", - "reload": "Reload", - "show": "Quick access: Show", - "uninstall": "Uninstall", - "update_all_one": "Update 1 plugin", - "update_all_other": "Update {{count}} plugins", - "update_to": "Update to {{name}}" - }, - "PluginListLabel": { - "hidden": "Hidden from the quick access menu" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Update to {{tag_name}} available!", - "error": "Error", - "plugin_error_uninstall": "Loading {{name}} caused an exception as shown above. This usually means that the plugin requires an update for the new version of SteamUI. Check if an update is present or evaluate its removal in the Decky settings, in the Plugins section.", - "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!" - }, - "PluginView": { - "hidden_one": "1 plugin is hidden from this list", - "hidden_other": "{{count}} plugins are hidden from this list" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Allow unauthenticated access to the CEF debugger to anyone in your network", - "label": "Allow Remote CEF Debugging" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Open Console", - "desc": "Opens the CEF Console. Only useful for debugging purposes. Stuff here is potentially dangerous and should only be used if you are a plugin dev, or are directed here by one.", - "label": "CEF Console" - }, - "header": "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" - }, - "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": { - "label": "Developer mode" - }, - "notifications": { - "decky_updates_label": "Decky update available", - "header": "Notifications", - "plugin_updates_label": "Plugin updates available" - }, - "other": { - "header": "Other" - }, - "updates": { - "header": "Updates" - } - }, - "SettingsIndex": { - "developer_title": "Developer", - "general_title": "General", - "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!", - "store_testing_warning": { - "desc": "You can use this store channel to test bleeding-edge plugin versions. Be sure to leave feedback on GitHub so the plugin can be updated for all users.", - "label": "Welcome to the Testing Store Channel" - } - }, - "StoreSelect": { - "custom_store": { - "label": "Custom Store", - "url_label": "URL" - }, - "store_channel": { - "custom": "Custom", - "default": "Default", - "label": "Store Channel", - "testing": "Testing" - } - }, - "TitleView": { - "decky_store_desc": "Open Decky Store", - "settings_desc": "Open Decky Settings" - }, - "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" - } - } -} diff --git a/backend/locales/es-ES.json b/backend/locales/es-ES.json deleted file mode 100644 index 6c47eb06..00000000 --- a/backend/locales/es-ES.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "SettingsDeveloperIndex": { - "third_party_plugins": { - "button_install": "Instalar", - "button_zip": "Navegar", - "label_desc": "URL", - "label_url": "Instalar plugin desde URL", - "label_zip": "Instalar plugin desde archivo ZIP", - "header": "Plugins de terceros" - }, - "valve_internal": { - "desc2": "No toques nada en este menú a menos que sepas lo que haces.", - "label": "Activar menú interno de Valve", - "desc1": "Activa el menú interno de desarrollo de Valve." - }, - "cef_console": { - "button": "Abrir consola", - "label": "Consola CEF", - "desc": "Abre la consola del CEF. Solamente es útil para propósitos de depuración. Las cosas que hagas aquí pueden ser potencialmente peligrosas y solo se debería usar si eres un desarrollador de plugins, o uno te ha dirigido aquí." - }, - "react_devtools": { - "ip_label": "IP", - "label": "Activar DevTools de React", - "desc": "Permite la conexión a un ordenador ejecutando las DevTools de React. Cambiar este ajuste recargará Steam. Configura la dirección IP antes de activarlo." - }, - "header": "Otros" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalar", - "button_processing": "Instalando", - "title": "Instalar {{artifact}}", - "desc": "¿Estás seguro de que quieres instalar {{artifact}} {{version}}?" - }, - "reinstall": { - "button_idle": "Reinstalar", - "button_processing": "Reinstalando", - "desc": "¿Estás seguro de que quieres reinstalar {{artifact}} {{version}}?", - "title": "Reinstalar {{artifact}}" - }, - "update": { - "button_processing": "Actualizando", - "button_idle": "Actualizar", - "desc": "¿Estás seguro de que quieres actualizar {{artifact}} {{version}}?", - "title": "Actualizar {{artifact}}" - }, - "no_hash": "Este plugin no tiene un hash, lo estás instalando bajo tu propia responsabilidad." - }, - "Developer": { - "disabling": "Desactivando DevTools de React", - "enabling": "Activando DevTools de React", - "5secreload": "Recargando en 5 segundos" - }, - "BranchSelect": { - "update_channel": { - "prerelease": "Prelanzamiento", - "stable": "Estable", - "label": "Canal de actualización", - "testing": "Pruebas" - } - }, - "PluginCard": { - "plugin_full_access": "Este plugin tiene acceso completo a su Steam Deck.", - "plugin_install": "Instalar", - "plugin_version_label": "Versión de Plugin", - "plugin_no_desc": "No se proporcionó una descripción." - }, - "FilePickerIndex": { - "folder": { - "select": "Usar esta carpeta" - } - }, - "PluginListIndex": { - "uninstall": "Desinstalar", - "reinstall": "Reinstalar", - "reload": "Recargar", - "plugin_actions": "Acciones de plugin", - "no_plugin": "¡No hay plugins instalados!", - "update_all_one": "Actualizar 1 plugin", - "update_all_many": "Actualizar {{count}} plugins", - "update_all_other": "Actualizar {{count}} plugins", - "update_to": "Actualizar a {{name}}" - }, - "PluginLoader": { - "error": "Error", - "plugin_uninstall": { - "button": "Desinstalar", - "desc": "¿Estás seguro de que quieres desinstalar {{name}}?", - "title": "Desinstalar {{name}}" - }, - "decky_title": "Decky", - "plugin_update_one": "¡Actualización disponible para 1 plugin!", - "plugin_update_many": "¡Actualizaciones disponibles para {{count}} plugins!", - "plugin_update_other": "¡Actualizaciones disponibles para {{count}} plugins!", - "decky_update_available": "¡Actualización {{tag_name}} disponible!", - "plugin_load_error": { - "message": "Se ha producido un error al cargar el plugin {{name}}", - "toast": "Se ha producido un error al cargar {{name}}" - }, - "plugin_error_uninstall": "Al cargar {{name}} se ha producido una excepción como se muestra arriba. Esto suele significar que el plugin requiere una actualización para la nueva versión de SteamUI. Comprueba si hay una actualización disponible o valora eliminarlo en los ajustes de Decky, en la sección Plugins." - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Permitir acceso no autenticado al CEF debugger a cualquier persona en su red", - "label": "Permitir depuración remota del CEF" - } - }, - "SettingsGeneralIndex": { - "updates": { - "header": "Actualizaciones" - }, - "about": { - "header": "Acerca de", - "decky_version": "Versión de Decky" - }, - "developer_mode": { - "label": "Modo desarrollador" - }, - "beta": { - "header": "Participación en la beta" - }, - "other": { - "header": "Otros" - } - }, - "SettingsIndex": { - "developer_title": "Desarrollador", - "general_title": "General", - "plugins_title": "Plugins" - }, - "Store": { - "store_search": { - "label": "Buscar" - }, - "store_sort": { - "label": "Ordenar", - "label_def": "Actualizado por última vez (Nuevos)" - }, - "store_contrib": { - "desc": "Si desea contribuir a la tienda de plugins de Decky, mira el repositorio SteamDeckHomebrew/decky-plugin-template en GitHub. Hay información acerca del desarrollo y distribución en el archivo README.", - "label": "Contribuyendo" - }, - "store_tabs": { - "about": "Información", - "title": "Navegar", - "alph_asce": "Alfabéticamente (Z-A)", - "alph_desc": "Alfabéticamente (A-Z)" - }, - "store_testing_cta": "¡Por favor considera probar plugins nuevos para ayudar al equipo de Decky Loader!", - "store_source": { - "desc": "El código fuente de los plugins está disponible en el repositiorio SteamDeckHomebrew/decky-plugin-database en GitHub.", - "label": "Código fuente" - }, - "store_filter": { - "label_def": "Todos", - "label": "Filtrar" - } - }, - "Updater": { - "updates": { - "reloading": "Recargando", - "updating": "Actualizando", - "checking": "Buscando", - "check_button": "Buscar actualizaciones", - "install_button": "Instalar actualización", - "label": "Actualizaciones", - "lat_version": "Actualizado: ejecutando {{ver}}", - "cur_version": "Versión actual: {{ver}}" - }, - "decky_updates": "Actualizaciones de Decky", - "no_patch_notes_desc": "No hay notas de parche para esta versión", - "patch_notes_desc": "Notas de parche" - }, - "MultiplePluginsInstallModal": { - "title": { - "reinstall_one": "Reinstalar 1 plugin", - "reinstall_many": "Reinstalar {{count}} plugins", - "reinstall_other": "Reinstalar {{count}} plugins", - "update_one": "Actualizar 1 plugin", - "update_many": "Actualizar {{count}} plugins", - "update_other": "Actualizar {{count}} plugins", - "mixed_one": "Modificar 1 plugin", - "mixed_many": "Modificar {{count}} plugins", - "mixed_other": "Modificar {{count}} plugins", - "install_one": "Instalar 1 plugin", - "install_many": "Instalar {{count}} plugins", - "install_other": "Instalar {{count}} plugins" - }, - "ok_button": { - "idle": "Confirmar", - "loading": "Trabajando" - }, - "confirm": "¿Estás seguro de que quieres hacer las siguientes modificaciones?", - "description": { - "install": "Instalar {{name}} {{version}}", - "update": "Actualizar {{name}} a {{version}}", - "reinstall": "Reinstalar {{name}} {{version}}" - } - }, - "StoreSelect": { - "custom_store": { - "url_label": "URL", - "label": "Tienda personalizada" - }, - "store_channel": { - "custom": "Personalizada", - "default": "Por defecto", - "label": "Canál de la tienda", - "testing": "Pruebas" - } - }, - "PluginView": { - "hidden_one": "", - "hidden_many": "", - "hidden_other": "" - } -} diff --git a/backend/locales/fi-FI.json b/backend/locales/fi-FI.json deleted file mode 100644 index b0ff1309..00000000 --- a/backend/locales/fi-FI.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "Esijulkaisu", - "testing": "Testiversio", - "stable": "Vakaa versio", - "label": "Päivityskanava" - } - }, - "Developer": { - "5secreload": "Uudelleenladataan 5 sekunin kuluttua", - "disabling": "Poistetaan React DevTools käytöstä", - "enabling": "Otetaan React DevTools käyttöön" - }, - "FilePickerError": { - "errors": { - "perm_denied": "Sinulla ei ole käyttöoikeutta määritettyyn hakemistoon. Tarkista, onko käyttäjälläsi (käyttäjä 'deck' Steam Deckillä) vastaavat oikeudet käyttää määritettyä kansiota/tiedostoa.", - "unknown": "Tapahtui tuntematon virhe. Raaka virhe on: {{raw_error}}", - "file_not_found": "Määritetty polku ei kelpaa. Tarkista se ja kirjoita se uudelleen oikein." - } - }, - "FilePickerIndex": { - "file": { - "select": "Valitse tämä tiedosto" - }, - "files": { - "all_files": "Kaikki tiedostot", - "file_type": "Tiedostotyyppi", - "show_hidden": "Näytä piilotetut tiedostot" - }, - "filter": { - "created_desc": "Luotu (uusin ensin)", - "modified_asce": "Muokattu (vanhin)", - "modified_desc": "Muokattu (uusin)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Koko (pienin ensin)", - "size_desc": "Koko (suurin ensin)", - "created_asce": "Luotu (vanhin ensin)" - }, - "folder": { - "label": "Kansio", - "select": "Käytä tätä kansiota", - "show_more": "Näytä lisää tiedostoja" - } - }, - "MultiplePluginsInstallModal": { - "confirm": "Haluatko varmasti tehdä seuraavat muutokset?", - "description": { - "reinstall": "Uudelleenasenna {{name}} {{version}}", - "update": "Päivitä {{name}} versioon {{version}}", - "install": "Asenna {{name}} {{version}}" - }, - "ok_button": { - "idle": "Vahvista", - "loading": "Ladataan" - }, - "title": { - "install_one": "Asenna yksi laajennus", - "install_other": "Asenna {{count}} laajennusta", - "update_one": "Päivitä yksi laajennus", - "update_other": "Päivitä {{count}} laajennusta", - "mixed_one": "Muuta yhtä laajennusta", - "mixed_other": "Muuta {{count}} laajennusta", - "reinstall_one": "Uudelleenasenna yksi laajennus", - "reinstall_other": "Uudelleenasenna {{count}} laajennusta" - } - }, - "PluginCard": { - "plugin_install": "Asenna", - "plugin_no_desc": "Ei kuvausta.", - "plugin_version_label": "Laajennuksen versio", - "plugin_full_access": "Tällä laajennuksella on täysi pääsy Steam Deckkiisi." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Asenna", - "button_processing": "Asennetaan", - "desc": "Haluatko varmasti asentaa {{artifact}} {{version}}?", - "title": "Asenna {{artifact}}" - }, - "no_hash": "Tällä laajennuksella ei ole hashia, asennat sen omalla vastuullasi.", - "reinstall": { - "button_idle": "Uudelleenasenna", - "button_processing": "Uudelleenasennetaan", - "desc": "Haluatko varmasti uudelleenasentaa {{artifact}} {{version}}?", - "title": "Uudelleenasenna {{artifact}}" - }, - "update": { - "button_idle": "Päivitä", - "button_processing": "Päivitetään", - "desc": "Haluatko varmasti päivittää {{artifact}} {{version}}?", - "title": "Päivitä {{artifact}}" - } - }, - "DropdownMultiselect": { - "button": { - "back": "Takaisin" - } - }, - "PluginListIndex": { - "no_plugin": "Ei asennettuja laajennuksia!", - "plugin_actions": "Laajennustoiminnot", - "reinstall": "Uudelleenasenna", - "reload": "Lataa uudelleen", - "uninstall": "Poista asennus", - "update_all_one": "Päivitä yksi laajennus", - "update_all_other": "Päivitä {{count}} laajennusta", - "update_to": "Päivitä versioon {{name}}", - "hide": "Pikavalikko: Piilota", - "show": "Pikavalikko: Näytä" - }, - "PluginListLabel": { - "hidden": "Piilotettu pikavalikosta" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Päivitys versioon {{tag_name}} on saatavilla!", - "error": "Virhe", - "plugin_load_error": { - "message": "Virhe ladattaessa {{name}}-laajennusta", - "toast": "Virhe ladattaessa {{name}}" - }, - "plugin_uninstall": { - "button": "Poista asennus", - "desc": "Haluatko varmasti poistaa {{name}} asennuksen?", - "title": "Poista {{name}}" - }, - "plugin_update_one": "Päivityksiä saatavilla yhdelle laajennukselle!", - "plugin_update_other": "Päivityksiä saatavilla {{count}} laajennukselle!", - "plugin_error_uninstall": "{{name}} lataaminen aiheutti yllä olevan poikkeuksen. Tämä tarkoittaa yleensä sitä, että laajennus vaatii päivityksen uudelle SteamUI-versiolle. Tarkista, onko päivitystä saatavilla, tai harkitse laajennuksen poistoa Decky-asetuksista, laajennukset-osiosta." - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Salli todentamaton pääsy CEF-debuggeriin kenelle tahansa verkossasi", - "label": "Salli CEF-etädebugaus" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Avaa konsoli", - "desc": "Avaa CEF-konsolin. Hyödyllinen vain debugaustarkoituksiin. Täällä olevat jutut ovat mahdollisesti vaarallisia, ja niitä tulisi käyttää vain, jos olet laajennuksen kehittäjä tai jos kehittäjä on ohjannut sinut tänne.", - "label": "CEF-konsoli" - }, - "header": "Muu", - "react_devtools": { - "desc": "Mahdollistaa yhteyden tietokoneeseen, jossa on käytössä React DevTools. Tämän asetuksen muuttaminen lataa Steamin uudelleen. Aseta IP-osoite ennen käyttöönottoa.", - "ip_label": "IP-osoite", - "label": "Ota React DevTools käyttöön" - }, - "third_party_plugins": { - "button_install": "Asenna", - "button_zip": "Selaa", - "header": "Kolmannen osapuolen laajennukset", - "label_desc": "URL-osoite", - "label_zip": "Asenna laajennus ZIP-tiedostosta", - "label_url": "Asenna laajennus URL-osoitteesta" - }, - "valve_internal": { - "desc2": "Älä koske mihinkään tässä valikossa, ellet tiedä mitä se tekee.", - "label": "Ota Valve Internal käyttöön", - "desc1": "Ottaa käyttöön Valven sisäisen kehittäjävalikon." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky-versio", - "header": "Tietoja" - }, - "beta": { - "header": "Beta-osallistuminen" - }, - "developer_mode": { - "label": "Kehittäjätila" - }, - "notifications": { - "decky_updates_label": "Decky-päivitys saatavilla", - "header": "Ilmoitukset", - "plugin_updates_label": "Laajennuspäivityksiä saatavilla" - }, - "other": { - "header": "Muu" - }, - "updates": { - "header": "Päivitykset" - } - }, - "SettingsIndex": { - "developer_title": "Kehittäjä", - "general_title": "Yleinen", - "plugins_title": "Laajennukset" - }, - "Store": { - "store_contrib": { - "label": "Osallistuminen", - "desc": "Mikäli haluat julkaista Decky Plugin Storeen, tarkista GitHubin SteamDeckHomebrew/decky-plugin-template -esimerkkitietovarasto. Tietoa kehityksestä ja jakelusta löytyy README:stä." - }, - "store_filter": { - "label": "Suodin", - "label_def": "Kaikki" - }, - "store_search": { - "label": "Hae" - }, - "store_sort": { - "label": "Järjestä", - "label_def": "Viimeksi päivitetty (uusin ensin)" - }, - "store_source": { - "desc": "Kaikken laajennusten lähdekoodit ovat saatavilla SteamDeckHomebrew/decky-plugin-database -arkistosta GitHubissa.", - "label": "Lähdekoodi" - }, - "store_tabs": { - "about": "Tietoja", - "alph_asce": "Aakkosjärjestyksessä (Z–A)", - "alph_desc": "Aakkosjärjestyksessä (A–Z)", - "title": "Selaa" - }, - "store_testing_cta": "Harkitse uusien lisäosien testaamista auttaaksesi Decky Loader -tiimiä!", - "store_testing_warning": { - "label": "Tervetuloa testausmyymälä-kanavalle", - "desc": "Voit käyttää tätä myymäläkanavaa testataksesi uusimpia laajennusversioita. Muista jättää palautetta GitHubissa, jotta laajennus voidaan päivittää kaikille käyttäjille." - } - }, - "StoreSelect": { - "custom_store": { - "label": "Mukautettu myymälä", - "url_label": "URL-osoite" - }, - "store_channel": { - "custom": "Mukautettu", - "default": "Oletus", - "label": "Myymäläkanava", - "testing": "Testaus" - } - }, - "TitleView": { - "decky_store_desc": "Avaa Decky-myymälä", - "settings_desc": "Avaa Decky-asetukset" - }, - "Updater": { - "decky_updates": "Decky-päivitykset", - "no_patch_notes_desc": "tälle versiolle ei ole korjausmerkintöjä", - "patch_notes_desc": "Korjausmerkinnät", - "updates": { - "check_button": "Tarkista päivitykset", - "checking": "Tarkistetaan", - "cur_version": "Nykyinen versio: {{ver}}", - "install_button": "Asenna päivitys", - "label": "Päivitykset", - "lat_version": "Ajan tasalla: versio {{ver}}", - "reloading": "Uudelleenladataan", - "updating": "Päivitetään" - } - }, - "PluginView": { - "hidden_one": "Yksi laajennus on piilotettu tästä luettelosta", - "hidden_other": "{{count}} laajennusta on piilotettu tästä luettelosta" - } -} diff --git a/backend/locales/fr-FR.json b/backend/locales/fr-FR.json deleted file mode 100644 index f1c305f9..00000000 --- a/backend/locales/fr-FR.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "SettingsDeveloperIndex": { - "react_devtools": { - "desc": "Permet la connexion à un ordinateur exécutant React DevTools. Changer ce paramètre rechargera Steam. Définissez l'adresse IP avant l'activation.", - "ip_label": "IP", - "label": "Activer React DevTools" - }, - "third_party_plugins": { - "button_install": "Installer", - "button_zip": "Parcourir", - "header": "Plugins tiers", - "label_desc": "URL", - "label_url": "Installer le plugin à partir d'un URL", - "label_zip": "Installer le plugin à partir d'un fichier ZIP" - }, - "valve_internal": { - "desc1": "Active le menu développeur interne de Valve.", - "desc2": "Ne touchez à rien dans ce menu à moins que vous ne sachiez ce qu'il fait.", - "label": "Activer Valve Internal" - } - }, - "BranchSelect": { - "update_channel": { - "prerelease": "Avant-première", - "label": "Canal de mise à jour", - "stable": "Stable", - "testing": "Test" - } - }, - "StoreSelect": { - "store_channel": { - "label": "Canal du Plugin Store", - "testing": "Test", - "custom": "Personnalisé", - "default": "Par défaut" - }, - "custom_store": { - "label": "Plugin Store personnalisé", - "url_label": "URL" - } - }, - "Updater": { - "decky_updates": "Mises à jour de Decky", - "no_patch_notes_desc": "pas de notes de mise à jour pour cette version", - "patch_notes_desc": "Notes de mise à jour", - "updates": { - "check_button": "Chercher les mises à jour", - "checking": "Recherche", - "cur_version": "Version actuelle: {{ver}}", - "install_button": "Installer la mise à jour", - "label": "Mises à jour", - "lat_version": "À jour: version {{ver}}", - "reloading": "Rechargement", - "updating": "Mise à jour en cours" - } - }, - "Developer": { - "5secreload": "Rechargement dans 5 secondes", - "disabling": "Désactivation", - "enabling": "Activation" - }, - "FilePickerIndex": { - "folder": { - "select": "Utiliser ce dossier" - } - }, - "PluginCard": { - "plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.", - "plugin_install": "Installer", - "plugin_no_desc": "Aucune description fournie.", - "plugin_version_label": "Version du plugin" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Installer", - "button_processing": "Installation en cours", - "title": "Installer {{artifact}}", - "desc": "Êtes-vous sûr de vouloir installer {{artifact}} {{version}} ?" - }, - "no_hash": "Ce plugin n'a pas de somme de contrôle, vous l'installez à vos risques et périls.", - "reinstall": { - "button_idle": "Réinstaller", - "button_processing": "Réinstallation en cours", - "desc": "Êtes-vous sûr de vouloir réinstaller {{artifact}} {{version}} ?", - "title": "Réinstaller {{artifact}}" - }, - "update": { - "button_idle": "Mettre à jour", - "button_processing": "Mise à jour", - "title": "Mettre à jour {{artifact}}", - "desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} {{version}} ?" - } - }, - "PluginListIndex": { - "plugin_actions": "Plugin Actions", - "reinstall": "Réinstaller", - "reload": "Recharger", - "uninstall": "Désinstaller", - "update_to": "Mettre à jour vers {{name}}", - "no_plugin": "Aucun plugin installé !", - "update_all_one": "", - "update_all_many": "", - "update_all_other": "" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Erreur", - "plugin_error_uninstall": "Allez sur {{name}} dans le menu de Decky si vous voulez désinstaller ce plugin.", - "plugin_load_error": { - "message": "Erreur lors du chargement du plugin {{name}}", - "toast": "Erreur lors du chargement de {{name}}" - }, - "decky_update_available": "Mise à jour vers {{tag_name}} disponible !", - "plugin_uninstall": { - "button": "Désinstaller", - "title": "Désinstaller {{name}}", - "desc": "Êtes-vous sûr.e de vouloir désinstaller {{name}} ?" - }, - "plugin_update_one": "", - "plugin_update_many": "", - "plugin_update_other": "" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Autoriser l'accès non authentifié au débogueur CEF à toute personne de votre réseau", - "label": "Autoriser le débogage CEF à distance" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Version de Decky", - "header": "À propos" - }, - "beta": { - "header": "Participation à la Bêta" - }, - "developer_mode": { - "label": "Mode développeur" - }, - "other": { - "header": "Autre" - }, - "updates": { - "header": "Mises à jour" - } - }, - "SettingsIndex": { - "developer_title": "Développeur", - "general_title": "Général", - "plugins_title": "Plugins" - }, - "Store": { - "store_contrib": { - "desc": "Si vous souhaitez contribuer au Decky Plugin Store, consultez le dépôt SteamDeckHomebrew/decky-plugin-template sur GitHub. Des informations sur le développement et la distribution sont disponibles dans le fichier README.", - "label": "Contributions" - }, - "store_filter": { - "label": "Filtrer", - "label_def": "Tous" - }, - "store_search": { - "label": "Rechercher" - }, - "store_sort": { - "label": "Trier", - "label_def": "Mises à jour (Plus récentes)" - }, - "store_source": { - "desc": "Tout le code source des plugins est disponible sur le dépôt SteamDeckHomebrew/decky-plugin-database sur GitHub.", - "label": "Code Source" - }, - "store_tabs": { - "about": "À propos", - "alph_asce": "Alphabétique (Z à A)", - "alph_desc": "Alphabétique (A à Z)", - "title": "Explorer" - }, - "store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !" - }, - "PluginView": { - "hidden_one": "", - "hidden_many": "", - "hidden_other": "" - }, - "MultiplePluginsInstallModal": { - "title": { - "reinstall_one": "", - "reinstall_many": "", - "reinstall_other": "", - "install_one": "", - "install_many": "", - "install_other": "", - "mixed_one": "", - "mixed_many": "", - "mixed_other": "", - "update_one": "", - "update_many": "", - "update_other": "" - } - } -} diff --git a/backend/locales/it-IT.json b/backend/locales/it-IT.json deleted file mode 100644 index 237bcdf4..00000000 --- a/backend/locales/it-IT.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "Canale di aggiornamento", - "prerelease": "Prerilascio", - "stable": "Stabile", - "testing": "In prova" - } - }, - "Developer": { - "5secreload": "Ricarico tra 5 secondi", - "disabling": "Disabilito i tools di React", - "enabling": "Abilito i tools di React" - }, - "DropdownMultiselect": { - "button": { - "back": "Indietro" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "Il percorso specificato non è valido. Controllalo e prova a reinserirlo di nuovo.", - "unknown": "È avvenuto un'errore sconosciuto. L'errore segnalato è {{raw_error}}", - "perm_denied": "Il tuo utente non ha accesso alla directory specificata. Verifica se l'utente corrente (è deck su Steam Deck di default) ha i permessi corrispondenti per accedere alla cartella/file desiderato." - } - }, - "FilePickerIndex": { - "file": { - "select": "Seleziona questo file" - }, - "files": { - "all_files": "Tutti i file", - "file_type": "Tipo di file", - "show_hidden": "Mostra nascosti" - }, - "filter": { - "created_asce": "Creazione (meno recente)", - "created_desc": "Creazione (più recente)", - "modified_asce": "Modifica (meno recente)", - "modified_desc": "Modifica (più recente)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Dimensione (più piccolo)", - "size_desc": "Dimensione (più grande)" - }, - "folder": { - "label": "Cartella", - "select": "Usa questa cartella", - "show_more": "Mostra più file" - } - }, - "MultiplePluginsInstallModal": { - "confirm": "Sei sicuro di voler effettuare le modifiche seguenti?", - "description": { - "install": "Installa {{name}} {{version}}", - "reinstall": "Reinstalla {{name}} {{version}}", - "update": "Aggiorna {{name}} alla versione {{version}}" - }, - "ok_button": { - "idle": "Conferma", - "loading": "Elaboro" - }, - "title": { - "install_one": "Installa un plugin", - "install_many": "Installa {{count}} plugins", - "install_other": "Installa {{count}} plugins", - "mixed_one": "Modifica un plugin", - "mixed_many": "Modifica {{count}} plugins", - "mixed_other": "Modifica {{count}} plugins", - "reinstall_one": "Reinstalla un plugin", - "reinstall_many": "Reinstalla {{count}} plugins", - "reinstall_other": "Reinstalla {{count}} plugins", - "update_one": "Aggiorna un plugin", - "update_many": "Aggiorna {{count}} plugins", - "update_other": "Aggiorna {{count}} plugins" - } - }, - "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": { - "hide": "Accesso rapido: Nascondi", - "no_plugin": "Nessun plugin installato!", - "plugin_actions": "Operazioni sui plugins", - "reinstall": "Reinstalla", - "reload": "Ricarica", - "show": "Accesso rapido: Mostra", - "uninstall": "Rimuovi", - "update_all_one": "Aggiorna un plugin", - "update_all_many": "Aggiorna {{count}} plugins", - "update_all_other": "Aggiorna {{count}} plugins", - "update_to": "Aggiorna a {{name}}" - }, - "PluginListLabel": { - "hidden": "Nascosto dal menu di accesso rapido" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Disponibile aggiornamento a {{tag_name}}!", - "error": "Errore", - "plugin_error_uninstall": "Il plugin {{name}} ha causato un'eccezione che è descritta sopra. Questo tipicamente significa che il plugin deve essere aggiornato per funzionare sulla nuova versione di SteamUI. Controlla se è disponibile un aggiornamento o valutane la rimozione andando nelle impostazioni di Decky nella sezione Plugins.", - "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 1 plugin!", - "plugin_update_many": "Aggiornamenti disponibili per {{count}} plugins!", - "plugin_update_other": "Aggiornamenti disponibili per {{count}} plugins!" - }, - "PluginView": { - "hidden_one": "Un plugin è nascosto dalla lista", - "hidden_many": "Sono nascosti {{count}} plugin dalla lista", - "hidden_other": "Sono nascosti {{count}} plugin dalla lista" - }, - "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": { - "cef_console": { - "button": "Apri la console", - "desc": "Apri la console di CEF. Utile solamente per ragioni di debug. Questa opzione deve essere usata solo se sei uno sviluppatore di plugin o se uno di questi ti ha chiesto di farlo, visto che questa feature potrebbe essere potenzialmente pericolosa.", - "label": "Console CEF" - }, - "header": "Altro", - "react_devtools": { - "desc": "Abilita la connessione ad un computer che esegue i DevTools di React. Steam verrà ricaricato se lo stato cambia. Imposta il tuo 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" - }, - "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": { - "label": "Modalità sviluppatore" - }, - "other": { - "header": "Altro" - }, - "updates": { - "header": "Aggiornamenti" - }, - "notifications": { - "header": "Notifiche", - "decky_updates_label": "Aggiornamenti di Decky", - "plugin_updates_label": "Aggiornamenti dei plugins" - } - }, - "SettingsIndex": { - "developer_title": "Sviluppatore", - "general_title": "Generali", - "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!", - "store_testing_warning": { - "label": "Benvenuto nel Negozio di Test dei Plugins", - "desc": "Puoi usare questo canale del negozio per testare versioni di plugin sperimentali. Assicurati di lasciare un feedback su Github dopo averlo testato in modo che il plugin possa essere promosso a stabile per tutti gli altri utenti o per permettere allo sviluppatore di plugin di correggere eventuali errori." - } - }, - "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" - } - }, - "TitleView": { - "settings_desc": "Apri le impostazioni di Decky", - "decky_store_desc": "Apri lo store di Decky" - } -} diff --git a/backend/locales/ko-KR.json b/backend/locales/ko-KR.json deleted file mode 100644 index 48698c5c..00000000 --- a/backend/locales/ko-KR.json +++ /dev/null @@ -1,253 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "업데이트 배포 채널", - "stable": "안정", - "testing": "테스트", - "prerelease": "사전 출시" - } - }, - "Developer": { - "disabling": "React DevTools 비활성화", - "enabling": "React DevTools 활성화", - "5secreload": "5초 내로 다시 로드 됩니다" - }, - "FilePickerIndex": { - "folder": { - "select": "이 폴더 사용", - "label": "폴더", - "show_more": "더 많은 파일 표시" - }, - "filter": { - "created_asce": "만든 날짜 (오름차순)", - "modified_asce": "수정한 날짜 (오름차순)", - "created_desc": "만든 날짜 (내림차 순)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "크기 (오름차순)", - "modified_desc": "수정한 날짜 (내림차순)", - "size_desc": "크기 (내림차순)" - }, - "files": { - "all_files": "모든 파일", - "show_hidden": "숨김 파일 표시", - "file_type": "파일 형식" - }, - "file": { - "select": "이 파일 선택" - } - }, - "PluginView": { - "hidden_other": "플러그인 {{count}}개 숨김" - }, - "PluginListLabel": { - "hidden": "빠른 액세스 메뉴에서 숨김" - }, - "PluginCard": { - "plugin_install": "설치", - "plugin_no_desc": "플러그인 설명이 제공되지 않았습니다.", - "plugin_version_label": "플러그인 버전", - "plugin_full_access": "이 플러그인은 Steam Deck의 모든 접근 권한을 가집니다." - }, - "PluginInstallModal": { - "install": { - "button_idle": "설치", - "button_processing": "설치 중", - "desc": "{{artifact}} {{version}}을(를) 설치하겠습니까?", - "title": "{{artifact}} 설치" - }, - "reinstall": { - "button_idle": "재설치", - "button_processing": "재설치 중", - "desc": "{{artifact}} {{version}}을(를) 재설치하겠습니까?", - "title": "{{artifact}} 재설치" - }, - "update": { - "button_idle": "업데이트", - "button_processing": "업데이트 중", - "title": "{{artifact}} 업데이트", - "desc": "{{artifact}} {{version}} 업데이트를 설치하겠습니까?" - }, - "no_hash": "이 플러그인은 해시 확인을 하지 않습니다, 설치에 따른 위험은 사용자가 감수해야 합니다." - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_other": "플러그인 {{count}}개 수정", - "update_other": "플러그인 {{count}}개 업데이트", - "reinstall_other": "플러그인 {{count}}개 재설치", - "install_other": "플러그인 {{count}}개 설치" - }, - "ok_button": { - "idle": "확인", - "loading": "작업 중" - }, - "confirm": "해당 수정을 적용하겠습니까?", - "description": { - "install": "{{name}} {{version}} 플러그인 설치", - "update": "{{name}}의 {{version}} 업데이트 설치", - "reinstall": "{{name}} {{version}} 재설치" - } - }, - "PluginListIndex": { - "plugin_actions": "플러그인 동작", - "reinstall": "재설치", - "reload": "다시 로드", - "uninstall": "설치 제거", - "show": "빠른 액세스 메뉴: 표시", - "hide": "빠른 액세스 메뉴: 숨김", - "update_all_other": "플러그인 {{count}}개 업데이트", - "no_plugin": "설치된 플러그인이 없습니다!", - "update_to": "{{name}}(으)로 업데이트" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "{{tag_name}} 업데이트를 설치할 수 있습니다!", - "error": "오류", - "plugin_load_error": { - "message": "{{name}} 플러그인 불러오기 오류", - "toast": "{{name}} 불러오기 오류" - }, - "plugin_uninstall": { - "button": "설치 제거", - "desc": "{{name}}을(를) 설치 제거하겠습니까?", - "title": "{{name}} 설치 제거" - }, - "plugin_update_other": "플러그인 {{count}}개를 업데이트 할 수 있습니다!", - "plugin_error_uninstall": "{{name}} 플러그인을 불러올 때 위와 같은 예외가 발생했습니다. 이는 보통 SteamUI 최신 버전에 맞는 플러그인 업데이트가 필요할 때 발생합니다. Decky 설정의 플러그인 섹션에서 업데이트가 있는지 확인하거나 설치 제거를 시도 해 보세요." - }, - "RemoteDebugging": { - "remote_cef": { - "label": "리모트 CEF 디버그 허용", - "desc": "네트워크의 모든 사용자에게 CEF 디버거에 대한 인증되지 않은 액세스 허용" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "콘솔 열기", - "label": "CEF 콘솔", - "desc": "CEF 콘솔을 엽니다. 디버그 전용입니다. 이 항목들은 위험 가능성이 있으므로 플러그인 개발자이거나 개발자의 가이드를 따를 경우에만 사용해야 합니다." - }, - "header": "기타", - "react_devtools": { - "ip_label": "IP", - "label": "React DevTools 활성화", - "desc": "React DevTools를 실행하고 있는 컴퓨터에 연결을 활성화합니다. 이 설정을 변경하면 Steam이 다시 로드됩니다. 활성화하기 전에 IP 주소를 설정하세요." - }, - "third_party_plugins": { - "button_install": "설치", - "button_zip": "검색", - "header": "서드파티 플러그인", - "label_desc": "URL", - "label_url": "URL에서 플러그인 설치", - "label_zip": "ZIP 파일에서 플러그인 설치" - }, - "valve_internal": { - "desc1": "Valve 내부 개발자 메뉴를 활성화합니다.", - "label": "Valve 내부 개발자 메뉴 활성화", - "desc2": "이 메뉴의 기능을 모른다면 어떤 것도 건드리지 마세요." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky 버전", - "header": "정보" - }, - "beta": { - "header": "베타 참가" - }, - "developer_mode": { - "label": "개발자 모드" - }, - "other": { - "header": "기타" - }, - "updates": { - "header": "업데이트" - }, - "notifications": { - "header": "알림", - "plugin_updates_label": "플러그인 업데이트 가능", - "decky_updates_label": "Decky 업데이트 가능" - } - }, - "SettingsIndex": { - "developer_title": "개발자", - "general_title": "일반", - "plugins_title": "플러그인" - }, - "Store": { - "store_contrib": { - "desc": "Decky 플러그인 스토어에 기여하고 싶다면 SteamDeckHomebrew/decky-plugin-template Github 저장소를 확인하세요. 개발 및 배포에 대한 정보는 README에서 확인할 수 있습니다.", - "label": "기여하기" - }, - "store_filter": { - "label": "필터", - "label_def": "모두" - }, - "store_search": { - "label": "검색" - }, - "store_sort": { - "label": "정렬", - "label_def": "최근 업데이트 순" - }, - "store_source": { - "desc": "모든 플러그인 소스 코드는 SteamDeckHomebrew/decky-plugin-database Github 저장소에서 확인할 수 있습니다.", - "label": "소스 코드" - }, - "store_tabs": { - "about": "정보", - "alph_asce": "알파벳순 (Z-A)", - "alph_desc": "알파벳순 (A-Z)", - "title": "검색" - }, - "store_testing_cta": "새로운 플러그인을 테스트하여 Decky Loader 팀을 도와주세요!", - "store_testing_warning": { - "desc": "이 스토어 채널을 사용하여 가장 최신 버전의 플러그인을 테스트할 수 있습니다. GitHub에 피드백을 남겨서 모든 사용자가 업데이트 할 수 있게 해주세요.", - "label": "테스트 스토어 채널에 오신 것을 환영합니다" - } - }, - "StoreSelect": { - "custom_store": { - "label": "사용자 지정 스토어", - "url_label": "URL" - }, - "store_channel": { - "custom": "사용자 지정", - "label": "스토어 배포 채널", - "default": "기본", - "testing": "테스트" - } - }, - "Updater": { - "decky_updates": "Decky 업데이트", - "no_patch_notes_desc": "이 버전에는 패치 노트가 없습니다", - "patch_notes_desc": "패치 노트", - "updates": { - "check_button": "업데이트 확인", - "checking": "확인 중", - "cur_version": "현재 버전: {{ver}}", - "install_button": "업데이트 설치", - "label": "업데이트", - "lat_version": "최신 상태: {{ver}} 실행 중", - "reloading": "다시 로드 중", - "updating": "업데이트 중" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "지정된 경로가 잘못되었습니다. 확인 후에 다시 입력해 주세요.", - "unknown": "알 수 없는 오류가 발생했습니다. Raw 오류: {{raw_error}}", - "perm_denied": "선택한 경로에 접근 할 수 없습니다. 선택한 폴더/파일 접근 권한이 유저(Steam Deck의 deck 유저)에 맞게 올바르게 설정 되었는지 확인하세요." - } - }, - "DropdownMultiselect": { - "button": { - "back": "뒤로" - } - }, - "TitleView": { - "settings_desc": "Decky 설정 열기", - "decky_store_desc": "Decky 스토어 열기" - } -} diff --git a/backend/locales/nl-NL.json b/backend/locales/nl-NL.json deleted file mode 100644 index 1adde308..00000000 --- a/backend/locales/nl-NL.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "Vooruitgave", - "stable": "Stabiel", - "label": "Update Kanaal", - "testing": "Test" - } - }, - "Developer": { - "5secreload": "Herlaad in 5 seconden", - "disabling": "Uitschakelen React DevTools", - "enabling": "Inschakelen React DevTools" - }, - "DropdownMultiselect": { - "button": { - "back": "Terug" - } - }, - "FilePickerError": { - "errors": { - "unknown": "Een onbekende fout is opgetreden. De ruwe fout is: {{raw_error}}", - "file_not_found": "Het opgegeven pad is niet geldig. Controleer het en voer het opnieuw correct in." - } - }, - "FilePickerIndex": { - "files": { - "all_files": "Alle bestanden", - "file_type": "Bestandstype", - "show_hidden": "Toon verborgen bestanden" - }, - "filter": { - "created_desc": "Gecreëerd ( Nieuwste)", - "modified_asce": "Veranderd (Oudste)", - "modified_desc": "Veranderd (Nieuwste)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Grootte (Kleinste)", - "size_desc": "Grootte (Grootste)", - "created_asce": "Gecreëerd (Oudste)" - }, - "folder": { - "label": "Map", - "select": "Gebruik deze map", - "show_more": "Toon meer bestanden" - } - }, - "PluginView": { - "hidden_one": "1 plug-in is verborgen in deze lijst", - "hidden_other": "{{count}} plug-ins zijn verborgen in deze lijst" - }, - "PluginListLabel": { - "hidden": "Verborgen in het snelmenu" - }, - "PluginCard": { - "plugin_install": "Installeren", - "plugin_no_desc": "Geen beschrijving gegeven.", - "plugin_version_label": "Plugin Versie", - "plugin_full_access": "Deze plug-in heeft volledige toegang tot je Steam Deck." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Installeren", - "button_processing": "Bezig met installeren", - "title": "Installeer {{artifact}}", - "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt installeren?" - }, - "no_hash": "Deze plug-in heeft geen hash, u installeert deze op eigen risico.", - "reinstall": { - "button_idle": "Herinstalleren", - "button_processing": "Bezig te herinstalleren", - "desc": "Weet je zeker dat je {{artifact}} {{version}} opnieuw wilt installeren?", - "title": "Installeer {{artifact}} opnieuw" - }, - "update": { - "button_idle": "Update", - "button_processing": "Bezig met updaten", - "title": "{{artifact}} bijwerken", - "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt updaten?" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Wijzig {{count}} plug-in", - "mixed_other": "Pas {{count}} plug-ins aan", - "update_one": "1 plugin bijwerken", - "update_other": "{{count}} plug-ins bijwerken", - "install_one": "Installeer 1 plugin", - "install_other": "Installeer {{count}} plugins", - "reinstall_one": "1 plugin opnieuw installeren", - "reinstall_other": "{{count}} plugins opnieuw installeren" - }, - "ok_button": { - "idle": "Bevestigen", - "loading": "Werkend" - }, - "confirm": "Weet u zeker dat u de volgende wijzigingen wilt aanbrengen?", - "description": { - "install": "Installeer {{name}} {{version}}", - "update": "Update {{name}} naar {{version}}", - "reinstall": "Installeer opnieuw {{name}} {{version}}" - } - }, - "PluginListIndex": { - "no_plugin": "Geen plugins geïnstalleerd!", - "plugin_actions": "Plugin Acties", - "reload": "Herladen", - "uninstall": "Verwijderen", - "update_to": "Update naar {{name}}", - "hide": "Snelle toegang: Verberg", - "update_all_one": "Update 1 plugin", - "update_all_other": "Update {{count}} plugins", - "reinstall": "Opnieuw installeren", - "show": "Snelle toegang: Toon" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Fout", - "plugin_load_error": { - "message": "Fout bij het laden van plugin {{name}}", - "toast": "Fout bij het laden van {{name}}" - }, - "plugin_uninstall": { - "button": "Verwijderen", - "desc": "Weet je zeker dat je {{name}} wilt verwijderen?", - "title": "Verwijder {{name}}" - }, - "plugin_update_one": "Updates beschikbaar voor 1 plugin!", - "plugin_update_other": "Updates beschikbaar voor {{count}} plugins!", - "decky_update_available": "Update naar {{tag_name}} beschikbaar!", - "plugin_error_uninstall": "Het laden van {{name}} veroorzaakte een uitzondering zoals hierboven weergegeven. Dit betekent meestal dat de plug-in een update vereist voor de nieuwe versie van SteamUI. Controleer of er een update aanwezig is of evalueer de verwijdering ervan in de Decky-instellingen, in het gedeelte Plug-ins." - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Sta ongeauthenticeerde toegang tot de CEF-foutopsporing toe aan iedereen in uw netwerk", - "label": "Externe CEF-foutopsporing toestaan" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Console openen", - "label": "CEF Bedieningsscherm", - "desc": "Opent de CEF-console. Alleen nuttig voor foutopsporingsdoeleinden. Dingen hier zijn potentieel gevaarlijk en mogen alleen worden gebruikt als u een ontwikkelaar van plug-ins bent, of hier door een ontwikkelaar naartoe wordt geleid." - }, - "header": "Andere", - "react_devtools": { - "ip_label": "IP", - "label": "Aanzetten React DevTools", - "desc": "Maakt verbinding met een computer met React DevTools mogelijk. Als je deze instelling wijzigt, wordt Steam opnieuw geladen. Stel het IP-adres in voordat u het inschakelt." - }, - "third_party_plugins": { - "header": "Plug-ins van derden", - "label_desc": "URL", - "label_url": "Installeer Plugin van URL", - "label_zip": "Installeer Plugin van Zip bestand", - "button_install": "Installeren", - "button_zip": "Bladeren" - }, - "valve_internal": { - "desc1": "Schakelt het interne ontwikkelaarsmenu van Valve in.", - "desc2": "Raak niets in dit menu aan tenzij u weet wat het doet.", - "label": "Valve Internal inschakelen" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky versie", - "header": "Over" - }, - "beta": { - "header": "Beta deelname" - }, - "developer_mode": { - "label": "Ontwikkelaars modus" - }, - "other": { - "header": "Overige" - }, - "updates": { - "header": "Nieuwe Versies" - } - }, - "SettingsIndex": { - "developer_title": "Ontwikkelaar", - "general_title": "Algemeen", - "plugins_title": "Plugins" - }, - "Store": { - "store_filter": { - "label": "Filter", - "label_def": "Alles" - }, - "store_search": { - "label": "Zoek" - }, - "store_sort": { - "label": "Sorteren", - "label_def": "Laatste Geupdate (Nieuwste)" - }, - "store_source": { - "label": "Bron Code", - "desc": "Alle broncode van de plug-in is beschikbaar in de SteamDeckHomebrew/decky-plugin-database-repository op GitHub." - }, - "store_tabs": { - "about": "Over", - "alph_asce": "Alfabetisch (Z naar A)", - "alph_desc": "Alfabetisch (A naar Z)", - "title": "Bladeren" - }, - "store_testing_cta": "Overweeg alsjeblieft om nieuwe plug-ins te testen om het Decky Loader-team te helpen!", - "store_contrib": { - "desc": "Als je wilt bijdragen aan de Decky Plugin winkel, kijk dan in de SteamDeckHomebrew/decky-plugin-template repository op GitHub. Informatie over ontwikkeling en distributie is beschikbaar in de README.", - "label": "Bijdragende" - } - }, - "StoreSelect": { - "custom_store": { - "label": "Aangepassingen winkel", - "url_label": "URL" - }, - "store_channel": { - "custom": "Aanpassingen", - "default": "Standaard", - "label": "Winkel Kanaal", - "testing": "Testen" - } - }, - "Updater": { - "patch_notes_desc": "Correctie opmerkingen", - "updates": { - "check_button": "Controleer op updates", - "checking": "Controleren", - "cur_version": "Huidige versie: {{ver}}", - "install_button": "Installeer Update", - "label": "Update", - "lat_version": "Up-to-date: loopt {{ver}}", - "reloading": "Herstarten", - "updating": "Aan het updaten" - }, - "decky_updates": "Decky Nieuwe Versies", - "no_patch_notes_desc": "geen correctie-opmerkingen voor deze versie" - } -} diff --git a/backend/locales/pl-PL.json b/backend/locales/pl-PL.json deleted file mode 100644 index 5231fa37..00000000 --- a/backend/locales/pl-PL.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "testing": "Testowy", - "label": "Kanał aktualizacji", - "stable": "Stabilny", - "prerelease": "Przedpremierowy" - } - }, - "Developer": { - "enabling": "Włączanie React DevTools", - "5secreload": "Ponowne załadowanie za 5 sekund", - "disabling": "Wyłączanie React DevTools" - }, - "DropdownMultiselect": { - "button": { - "back": "Powrót" - } - }, - "FilePickerError": { - "errors": { - "perm_denied": "Nie masz dostępu do podanego katalogu. Sprawdź, czy twój użytkownik (deck na Steam Deck) ma odpowiednie uprawnienia dostępu do określonego katalogu/pliku.", - "unknown": "Wystąpił nieznany błąd. Surowy błąd to {{raw_error}}", - "file_not_found": "Podana ścieżka jest nieprawidłowa. Sprawdź ją i wprowadź ponownie poprawnie." - } - }, - "FilePickerIndex": { - "file": { - "select": "Wybierz ten plik" - }, - "files": { - "all_files": "Wszystkie pliki", - "file_type": "Typ pliku", - "show_hidden": "Pokaż ukryte pliki" - }, - "filter": { - "created_asce": "Utworzono (najstarszy)", - "created_desc": "Utworzono (najnowszy)", - "modified_asce": "Zmodyfikowany (najstarszy)", - "modified_desc": "Zmodyfikowany (najnowszy)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Rozmiar (najmniejszy)", - "size_desc": "Rozmiar (największy)" - }, - "folder": { - "label": "Katalog", - "select": "Użyj tego katalogu", - "show_more": "Pokaż więcej plików" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Zmodyfikuj {{count}} plugin", - "mixed_few": "Zmodyfikuj {{count}} pluginy", - "mixed_many": "Zmodyfikuj {{count}} pluginów", - "reinstall_one": "Reinstaluj 1 plugin", - "reinstall_few": "Reinstaluj {{count}} pluginy", - "reinstall_many": "Reinstaluj {{count}} pluginów", - "install_one": "Zainstaluj 1 plugin", - "install_few": "Zainstaluj {{count}} pluginy", - "install_many": "Zainstaluj {{count}} pluginów", - "update_one": "Zaktualizuj 1 plugin", - "update_few": "Zaktualizuj {{count}} pluginy", - "update_many": "Zaktualizuj {{count}} pluginów" - }, - "confirm": "Czy na pewno chcesz wprowadzić następujące modyfikacje?", - "description": { - "install": "Zainstaluj {{name}} {{version}}", - "reinstall": "Reinstaluj {{name}} {{version}}", - "update": "Zaktualizuj {{name}} do {{version}}" - }, - "ok_button": { - "idle": "Potwierdź", - "loading": "W toku" - } - }, - "PluginCard": { - "plugin_install": "Zainstaluj", - "plugin_no_desc": "Brak opisu.", - "plugin_version_label": "Wersja pluginu", - "plugin_full_access": "Ten plugin ma pełny dostęp do twojego Steam Decka." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Zainstaluj", - "button_processing": "Instalowanie", - "desc": "Czy na pewno chcesz zainstalować {{artifact}} {{version}}?", - "title": "Zainstaluj {{artifact}}" - }, - "reinstall": { - "button_idle": "Reinstaluj", - "button_processing": "Reinstalowanie", - "desc": "Czy na pewno chcesz ponownie zainstalować {{artifact}} {{version}}?", - "title": "Reinstaluj {{artifact}}" - }, - "update": { - "button_idle": "Aktualizacja", - "button_processing": "Aktualizowanie", - "desc": "Czy na pewno chcesz zaktualizować {{artifact}} {{version}}?", - "title": "Zaktualizuj {{artifact}}" - }, - "no_hash": "Ten plugin nie ma hasha, instalujesz go na własne ryzyko." - }, - "PluginListIndex": { - "hide": "Szybki dostęp: Ukryj", - "no_plugin": "Brak zainstalowanych pluginów!", - "reload": "Załaduj ponownie", - "update_all_one": "Zaktualizuj 1 plugin", - "update_all_few": "Zaktualizuj {{count}} pluginy", - "update_all_many": "Zaktualizuj {{count}} pluginów", - "plugin_actions": "Akcje pluginów", - "reinstall": "Reinstalacja", - "show": "Szybki dostęp: Pokaż", - "uninstall": "Odinstaluj", - "update_to": "Zaktualizuj do {{name}}" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Dostępna aktualizacja do {{tag_name}}!", - "error": "Błąd", - "plugin_error_uninstall": "Ładowanie {{name}} spowodowało wyjątek, jak pokazano powyżej. Zwykle oznacza to, że plugin wymaga aktualizacji do nowej wersji SteamUI. Sprawdź, czy aktualizacja jest obecna lub rozważ usunięcie go w ustawieniach Decky, w sekcji Pluginy.", - "plugin_load_error": { - "message": "Błąd ładowania plugin {{name}}", - "toast": "Błąd ładowania {{name}}" - }, - "plugin_uninstall": { - "button": "Odinstaluj", - "title": "Odinstaluj {{name}}", - "desc": "Czy na pewno chcesz odinstalować {{name}}?" - }, - "plugin_update_one": "Aktualizacje dostępne dla 1 pluginu!", - "plugin_update_few": "Aktualizacje dostępne dla {{count}} pluginów!", - "plugin_update_many": "Aktualizacje dostępne dla {{count}} pluginów!" - }, - "PluginListLabel": { - "hidden": "Ukryty w menu szybkiego dostępu" - }, - "PluginView": { - "hidden_one": "1 plugin jest ukryty na tej liście", - "hidden_few": "{{count}} pluginy jest ukryty na tej liście", - "hidden_many": "{{count}} pluginów jest ukryty na tej liście" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Zezwalaj na nieuwierzytelniony dostęp do debugera CEF wszystkim osobom w Twojej sieci", - "label": "Zezwól na zdalne debugowanie CEF" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Otwórz konsolę", - "desc": "Otwiera konsolę CEF. Przydatne tylko do celów debugowania. Rzeczy tutaj są potencjalnie niebezpieczne i powinny być używane tylko wtedy, gdy jesteś twórcą wtyczek lub zostałeś tu przez kogoś skierowany.", - "label": "Konsola CEF" - }, - "header": "Inne", - "react_devtools": { - "desc": "Umożliwia połączenie z komputerem z uruchomionym React DevTools. Zmiana tego ustawienia spowoduje ponowne załadowanie Steam. Ustaw adres IP przed włączeniem.", - "ip_label": "IP", - "label": "Włącz React DevTools" - }, - "third_party_plugins": { - "button_install": "Zainstaluj", - "button_zip": "Przeglądaj", - "header": "Pluginy zewnętrzne", - "label_desc": "URL", - "label_url": "Zainstaluj plugin z adresu URL", - "label_zip": "Zainstaluj plugin z pliku ZIP" - }, - "valve_internal": { - "desc1": "Włącza wewnętrzne menu programisty Valve.", - "desc2": "Nie dotykaj niczego w tym menu, chyba że wiesz, co robi.", - "label": "Włącz Valve Internal" - } - }, - "SettingsGeneralIndex": { - "notifications": { - "decky_updates_label": "Dostępna aktualizacja Decky", - "header": "Powiadomienia", - "plugin_updates_label": "Dostępne aktualizacje pluginów" - }, - "other": { - "header": "Inne" - }, - "updates": { - "header": "Aktualizacje" - }, - "about": { - "header": "Informacje", - "decky_version": "Wersja Decky" - }, - "beta": { - "header": "Udział w becie" - }, - "developer_mode": { - "label": "Tryb dewelopera" - } - }, - "SettingsIndex": { - "developer_title": "Deweloper", - "general_title": "Ogólne", - "plugins_title": "Pluginy" - }, - "Store": { - "store_contrib": { - "desc": "Jeśli chcesz przyczynić się do rozwoju Decky Plugin Store, sprawdź repozytorium SteamDeckHomebrew/decky-plugin-template na GitHub. Informacje na temat rozwoju i dystrybucji są dostępne w pliku README.", - "label": "Współtworzenie" - }, - "store_filter": { - "label": "Filtr", - "label_def": "Wszystko" - }, - "store_search": { - "label": "Szukaj" - }, - "store_sort": { - "label": "Sortowanie", - "label_def": "Ostatnia aktualizacja (najnowsza)" - }, - "store_source": { - "desc": "Cały kod źródłowy pluginów jest dostępny w repozytorium SteamDeckHomebrew/decky-plugin-database na GitHub.", - "label": "Kod źródłowy" - }, - "store_tabs": { - "alph_asce": "Alfabetycznie (od Z do A)", - "alph_desc": "Alfabetycznie (od A do Z)", - "title": "Przeglądaj", - "about": "Informacje" - }, - "store_testing_cta": "Rozważ przetestowanie nowych pluginów, aby pomóc zespołowi Decky Loader!", - "store_testing_warning": { - "label": "Witamy w Testowym Kanale Sklepu", - "desc": "Możesz użyć tego kanału sklepu do testowania najnowszych wersji pluginów. Pamiętaj, aby zostawić opinię na GitHub, aby plugin mogła zostać zaktualizowana dla wszystkich użytkowników." - } - }, - "StoreSelect": { - "custom_store": { - "label": "Niestandardowy sklep", - "url_label": "URL" - }, - "store_channel": { - "custom": "Niestandardowy", - "default": "Domyślny", - "label": "Kanał sklepu", - "testing": "Testowy" - } - }, - "Updater": { - "decky_updates": "Aktualizacje Decky", - "no_patch_notes_desc": "Brak informacji o poprawkach dla tej wersji", - "patch_notes_desc": "Opis zmian", - "updates": { - "check_button": "Sprawdź aktualizacje", - "checking": "Sprawdzanie", - "cur_version": "Aktualna wersja: {{ver}}", - "install_button": "Zainstaluj aktualizację", - "label": "Aktualizacje", - "lat_version": "Aktualizacje zainstalowane. Aktualna wersja: {{ver}}", - "reloading": "Ponowne ładowanie", - "updating": "Aktualizowanie" - } - }, - "TitleView": { - "settings_desc": "Otwórz ustawienia Decky", - "decky_store_desc": "Otwórz sklep Decky" - } -} diff --git a/backend/locales/pt-BR.json b/backend/locales/pt-BR.json deleted file mode 100644 index 2a7c173b..00000000 --- a/backend/locales/pt-BR.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "Pré-lançamento", - "stable": "Estável", - "testing": "Em Teste", - "label": "Canal de Atualização" - } - }, - "Developer": { - "5secreload": "Recarregando em 5 segundos", - "enabling": "Habilitando React DevTools", - "disabling": "Desabilitando React DevTools" - }, - "FilePickerIndex": { - "folder": { - "select": "Use esta pasta", - "label": "Pasta", - "show_more": "Mostrar mais arquivos" - }, - "files": { - "show_hidden": "Mostrar Arquivos Ocultos", - "all_files": "Todos os arquivos", - "file_type": "Formato de arquivo" - }, - "filter": { - "created_asce": "Criado (Mais antigo)", - "created_desc": "Criado (Mais recente)", - "modified_asce": "Alterado (Mais antigo)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Tamanho (Menor)", - "size_desc": "Tamanho (Maior)", - "modified_desc": "Alterado (Mais recente)" - }, - "file": { - "select": "Selecione este arquivo" - } - }, - "PluginListLabel": { - "hidden": "Oculto no menu de acesso rápido" - }, - "PluginCard": { - "plugin_full_access": "Este plugin tem acesso total ao seu Steam Deck.", - "plugin_install": "Instalar", - "plugin_no_desc": "Nenhuma descrição fornecida.", - "plugin_version_label": "Versão do plugin" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalar", - "button_processing": "Instalando", - "desc": "Você tem certeza que deseja instalar {{artifact}} {{version}}?", - "title": "Instalar {{artifact}}" - }, - "reinstall": { - "button_idle": "Reinstalar", - "button_processing": "Reinstalando", - "desc": "Tem certeza que voce deseja reinstalar {{artifact}} {{version}}?", - "title": "Reinstalar {{artifact}}" - }, - "update": { - "button_idle": "Atualizar", - "button_processing": "Atualizando", - "desc": "Tem certeza que voce deseja atualizar {{artifact}} {{version}}?", - "title": "Atualizar {{artifact}}" - }, - "no_hash": "Este plugin não tem um hash, você o está instalando por sua conta em risco." - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Modificar {{count}} plugin", - "mixed_many": "Modificar {{count}} plugins", - "mixed_other": "Modificar {{count}} plugins", - "update_one": "Atualizar 1 plugin", - "update_many": "Atualizar {{count}} plugins", - "update_other": "Atualizar {{count}} plugins", - "install_one": "Instalar 1 plugin", - "install_many": "Instalar {{count}} plugins", - "install_other": "Instalar {{count}} plugins", - "reinstall_one": "Reinstalar 1 plugin", - "reinstall_many": "Reinstalar {{count}} plugins", - "reinstall_other": "Reinstalar {{count}} plugins" - }, - "ok_button": { - "idle": "Confirmar", - "loading": "Carregando" - }, - "description": { - "install": "Instalar {{name}} {{version}}", - "update": "Atualizar {{name}} para {{version}}", - "reinstall": "Reinstalar {{name}} {{version}}" - }, - "confirm": "Tem certeza que deseja fazer as seguintes modificações?" - }, - "PluginListIndex": { - "no_plugin": "Nenhum plugin instalado!", - "plugin_actions": "Ações do plugin", - "reinstall": "Reinstalar", - "reload": "Recarregar", - "uninstall": "Desinstalar", - "update_to": "Atualizar para {{name}}", - "show": "Acesso Rápido: Mostrar", - "update_all_one": "Atualizar 1 plugin", - "update_all_many": "Atualizar {{count}} plugins", - "update_all_other": "Atualizar {{count}} plugins", - "hide": "Acesso Rápido: Ocultar" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Erro", - "plugin_load_error": { - "message": "Erro ao carregar o plugin {{name}}", - "toast": "Erro ao carregar {{name}}" - }, - "plugin_uninstall": { - "button": "Desinstalar", - "desc": "Você tem certeza que deseja desinstalar {{name}}?", - "title": "Desinstalar {{name}}" - }, - "decky_update_available": "Atualização para {{tag_name}} disponível!", - "plugin_error_uninstall": "Um erro aconteceu ao carregar {{name}}, como mostrado acima. Isso normalmente significa que o plugin precisa de uma atualização para a nova versão do SteamUI. Confira se existe uma atualização ou avalie a remoção do plugin nas configurações do Decky, na sessão de plugins.", - "plugin_update_one": "Atualização disponível para 1 plugin!", - "plugin_update_many": "Atualizações disponíveis para {{count}} plugins!", - "plugin_update_other": "Atualizações disponíveis para {{count}} plugins!" - }, - "RemoteDebugging": { - "remote_cef": { - "label": "Permitir Depuração CEF Demota", - "desc": "Permitir acesso não autenticato ao depurador CEF para qualquer um na sua rede" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Abrir o Console", - "label": "Console CEF", - "desc": "Abre o Console CEF. Somente útil para fins de depuração. O material aqui é potencialmente perigoso e só deve ser usado se você for um desenvolvedor de plugin, ou direcionado até aqui por um." - }, - "header": "Outros", - "react_devtools": { - "desc": "Habilita a conexão a um computador executando React DevTools. Alterar essa configuração irá recarregar a Steam. Defina o endereço IP antes de habilitar.", - "ip_label": "IP", - "label": "Habilitar React DevTools" - }, - "third_party_plugins": { - "button_install": "Instalar", - "button_zip": "Navegar", - "header": "Plugins de terceiros", - "label_url": "Instalar Plugin a partir da URL", - "label_zip": "Instalar Plugin a partir de um arquivo ZIP", - "label_desc": "URL" - }, - "valve_internal": { - "desc1": "Habilita o menu interno de desenvolvedor da Valve.", - "desc2": "Não toque em nada neste menu, a não ser que você saiba o que está fazendo.", - "label": "Habilitar Menu Interno da Valve" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Versão do Decky", - "header": "Sobre" - }, - "developer_mode": { - "label": "Modo Deselvolvedor" - }, - "other": { - "header": "Outros" - }, - "updates": { - "header": "Atualizações" - }, - "beta": { - "header": "Participação no Beta" - }, - "notifications": { - "decky_updates_label": "Atualização do Decky disponível", - "header": "Noificações", - "plugin_updates_label": "Atualizações de Plugin disponíveis" - } - }, - "SettingsIndex": { - "developer_title": "Desenvolvedor", - "general_title": "Geral", - "plugins_title": "Plugins" - }, - "Store": { - "store_contrib": { - "label": "Contribuindo", - "desc": "Se você deseja contribuir para a Loja de Plugins para o Decky, confira o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Informações sobre o desenvolvimento e distribuição estão disponíveis no README." - }, - "store_filter": { - "label": "Filtros", - "label_def": "Todos" - }, - "store_search": { - "label": "Buscar" - }, - "store_sort": { - "label": "Ordenar", - "label_def": "Último atualizado (Mais recente)" - }, - "store_source": { - "desc": "Todos os códigos fonte dos plugins estão disponíveis no repositório SteamDeckHomebrew/decky-plugin-database no GitHub.", - "label": "Código Fonte" - }, - "store_tabs": { - "about": "Sobre", - "alph_desc": "Alfabética (A - Z)", - "title": "Navegar", - "alph_asce": "Alfabética (Z - A)" - }, - "store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!" - }, - "StoreSelect": { - "custom_store": { - "label": "Loja Personalizada", - "url_label": "URL" - }, - "store_channel": { - "custom": "Personalizada", - "default": "Padrão", - "label": "Canal da Loja", - "testing": "Em Teste" - } - }, - "Updater": { - "no_patch_notes_desc": "nenhuma nota de alteração para esta versão", - "patch_notes_desc": "Notas de alteração", - "updates": { - "check_button": "Buscar Atualizações", - "checking": "Buscando", - "cur_version": "Versão atual: {{ver}}", - "install_button": "Instalar Atualização", - "label": "Atualizações", - "lat_version": "Atualizado: rodando {{ver}}", - "reloading": "Recarregando", - "updating": "Atualizando" - }, - "decky_updates": "Atualizações do Decky" - }, - "PluginView": { - "hidden_one": "1 plugin está oculto nesta lista", - "hidden_many": "{{count}} plugins estão ocultos nesta lista", - "hidden_other": "{{count}} plugins estão ocultos nesta lista" - }, - "DropdownMultiselect": { - "button": { - "back": "Voltar" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "O caminho especificado não é válido. Por favor, confira e reinsira corretamente.", - "unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}", - "perm_denied": "Você não tem acesso à este diretório. Por favor, verifiquei se seu usuário (deck no Steam Deck) tem as permissões necessárias para acessar este arquivo/pasta." - } - } -} diff --git a/backend/locales/pt-PT.json b/backend/locales/pt-PT.json deleted file mode 100644 index 9b273569..00000000 --- a/backend/locales/pt-PT.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "FilePickerIndex": { - "folder": { - "select": "Usar esta pasta" - } - }, - "PluginView": { - "hidden_one": "1 plugin está oculto desta lista", - "hidden_many": "{{count}} plugins estão ocultos desta lista", - "hidden_other": "{{count}} plugins estão ocultos desta lista" - }, - "PluginCard": { - "plugin_full_access": "Este plugin tem acesso total à tua Steam Deck.", - "plugin_install": "Instalar", - "plugin_version_label": "Versão do plugin", - "plugin_no_desc": "Não tem descrição." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalar", - "button_processing": "Instalação em curso", - "title": "Instalar {{artifact}}", - "desc": "De certeza que queres instalar {{artifact}} {{version}}?" - }, - "reinstall": { - "button_idle": "Reinstalar", - "button_processing": "Reinstalação em curso", - "title": "Reinstalar {{artifact}}", - "desc": "De certeza que queres reinstalar {{artifact}} {{version}}?" - }, - "update": { - "button_idle": "Actualizar", - "button_processing": "Actualização em curso", - "title": "Actualizar {{artifact}}", - "desc": "De certeza que queres actualizar {{artifact}} {{version}}?" - }, - "no_hash": "Este plugin não tem um hash, estás a instalá-lo por tua conta e risco." - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Alterar 1 plugin", - "mixed_many": "Alterar {{count}} plugins", - "mixed_other": "Alterar {{count}} plugins", - "update_one": "Actualizar 1 plugin", - "update_many": "Actualizar {{count}} plugins", - "update_other": "Actualizar {{count}} plugins", - "reinstall_one": "Reinstalar 1 plugin", - "reinstall_many": "Reinstalar {{count}} plugins", - "reinstall_other": "Reinstalar {{count}} plugins", - "install_one": "Instalar 1 plugin", - "install_many": "Instalar {{count}} plugins", - "install_other": "Instalar {{count}} plugins" - }, - "ok_button": { - "idle": "Confirmar", - "loading": "Em curso" - }, - "description": { - "install": "Instalar {{name}} {{version}}", - "update": "Actualizar {{name}} para {{version}}", - "reinstall": "Reinstalar {{name}} {{version}}" - }, - "confirm": "De certeza que queres fazer as seguintes alterações?" - }, - "PluginListIndex": { - "no_plugin": "Nenhum plugin instalado!", - "reinstall": "Reinstalar", - "uninstall": "Desinstalar", - "update_to": "Actualizar para {{name}}", - "update_all_one": "Actualizar 1 plugin", - "update_all_many": "Actualizar {{count}} plugins", - "update_all_other": "Actualizar {{count}} plugins", - "plugin_actions": "Operações de plugin", - "reload": "Recarregar", - "show": "Acesso rápido: Mostrar", - "hide": "Acesso rápido: Ocultar" - }, - "BranchSelect": { - "update_channel": { - "stable": "Estável", - "testing": "Em teste", - "label": "Canal de actualização", - "prerelease": "Pré-lançamento" - } - }, - "Developer": { - "5secreload": "Vai recarregar em 5 segundos", - "disabling": "Desactivando React DevTools", - "enabling": "Activando React DevTools" - }, - "PluginListLabel": { - "hidden": "Oculto do menu de acesso rápido" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Erro", - "plugin_load_error": { - "message": "Erro ao carregar o plugin {{name}}", - "toast": "Erro ao carregar {{name}}" - }, - "plugin_uninstall": { - "button": "Desinstalar", - "title": "Desinstalar {{name}}", - "desc": "De certeza que queres desinstalar {{name}}?" - }, - "decky_update_available": "Está disponível uma nova versão de {{tag_name}} !", - "plugin_update_one": "1 plugin tem actualizações disponíveis!", - "plugin_update_many": "{{count}} plugins têm actualizações disponíveis!", - "plugin_update_other": "{{count}} plugins têm actualizações disponíveis!", - "plugin_error_uninstall": "Houve uma excepção ao carregar {{name}}, como mostrado em cima. Pode ter sido porque o plugin requere a última versão do SteamUI. Verifica se há uma actualização disponível ou desinstala o plugin nas definições do Decky." - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Abrir consola", - "label": "Consola CEF", - "desc": "Abre a consola do CEF. Só é útil para efeitos de debugging. Pode ser perigosa e só deve ser usada se és um desenvolvedor de plugins, ou se foste aqui indicado por um desenvolvedor." - }, - "header": "Outros", - "react_devtools": { - "desc": "Permite a conecção a um computador que está a correr React DevTools. Mudar esta definição vai recarregar o Steam. Define o endereço de IP antes de activar.", - "ip_label": "IP", - "label": "Activar React DevTools" - }, - "third_party_plugins": { - "button_install": "Instalar", - "button_zip": "Navegar", - "header": "Plugins de terceiros", - "label_desc": "URl", - "label_url": "Instalar plugin a partir dum URL", - "label_zip": "Instalar plugin a partir dum ficheiro ZIP" - }, - "valve_internal": { - "label": "Activar menu interno da Valve", - "desc1": "Activa o menu interno de programador da Valve.", - "desc2": "Não toques em nada deste menu se não souberes a sua função." - } - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Permitir acesso não autenticado ao debugger do CEF a qualquer pessoa na tua rede", - "label": "Permitir debugging remoto do CEF" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Versão do Decky", - "header": "Sobre" - }, - "beta": { - "header": "Participação na versão Beta" - }, - "developer_mode": { - "label": "Modo de programador" - }, - "other": { - "header": "Outros" - }, - "updates": { - "header": "Actualizações" - } - }, - "SettingsIndex": { - "developer_title": "Programador", - "general_title": "Geral", - "plugins_title": "Plugins" - }, - "Store": { - "store_contrib": { - "label": "Contribuir", - "desc": "Se queres contribuir com um novo plugin, vai ao repositório SteamDeckHomebrew/decky-plugin-template no GitHub. No README, podes encontrar mais informação sobre desenvolvimento e distribuição." - }, - "store_filter": { - "label": "Filtro", - "label_def": "Todos" - }, - "store_search": { - "label": "Procurar" - }, - "store_sort": { - "label": "Ordenar", - "label_def": "Última actualização (mais recente)" - }, - "store_source": { - "label": "Código fonte", - "desc": "O código fonte de cada plugin está disponível no repositório SteamDeckHomebrew/decky-plugin-database no GitHub." - }, - "store_tabs": { - "about": "Sobre", - "alph_asce": "Alfabeticamente (Z-A)", - "alph_desc": "Alfabeticamente (A-Z)", - "title": "Navegar" - }, - "store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!" - }, - "StoreSelect": { - "custom_store": { - "url_label": "URL", - "label": "Loja personalizada" - }, - "store_channel": { - "custom": "Personalizada", - "default": "Standard", - "testing": "Em teste", - "label": "Canal de loja" - } - }, - "Updater": { - "decky_updates": "Actualizações do Decky", - "no_patch_notes_desc": "sem registo de alterações desta versão", - "patch_notes_desc": "Registo de alterações", - "updates": { - "check_button": "Procurar actualizações", - "checking": "Busca de actualizações em curso", - "cur_version": "Versão actual: {{ver}}", - "label": "Actualizações", - "lat_version": "Actualizado: a correr {{ver}}", - "updating": "Actualização em curso", - "reloading": "Recarregar", - "install_button": "Instalar actualização" - } - } -} diff --git a/backend/locales/ru-RU.json b/backend/locales/ru-RU.json deleted file mode 100644 index 776ffa12..00000000 --- a/backend/locales/ru-RU.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "MultiplePluginsInstallModal": { - "title": { - "update_one": "Переустановить {{count}} плагин", - "update_few": "Переустановить {{count}} плагинов", - "update_many": "Переустановить {{count}} плагинов", - "reinstall_one": "Переустановить {{count}} плагин", - "reinstall_few": "Переустановить {{count}} плагинов", - "reinstall_many": "Переустановить {{count}} плагинов", - "install_one": "Установить {{count}} плагин", - "install_few": "Установить {{count}} плагинов", - "install_many": "Установить {{count}} плагинов", - "mixed_one": "Изменить {{count}} плагин", - "mixed_few": "Изменить {{count}} плагинов", - "mixed_many": "Изменить {{count}} плагинов" - }, - "description": { - "install": "Установить {{name}} {{version}}", - "reinstall": "Переустановить {{name}} {{version}}", - "update": "Обновить с {{name}} на {{version}}" - }, - "confirm": "Вы уверены, что хотите внести следующие изменения?", - "ok_button": { - "idle": "Подтвердить", - "loading": "В процессе" - } - }, - "PluginListIndex": { - "update_all_one": "Обновить {{count}} плагин", - "update_all_few": "Обновить {{count}} плагинов", - "update_all_many": "Обновить {{count}} плагинов", - "hide": "Быстрый доступ: Скрыть", - "reload": "Перезагрузить", - "uninstall": "Удалить", - "update_to": "Обновить на {{name}}", - "show": "Быстрый доступ: Показать", - "plugin_actions": "Действия с плагинами", - "no_plugin": "Не установлено ни одного плагина!", - "reinstall": "Переустановить" - }, - "PluginLoader": { - "plugin_update_one": "Обновления доступны для {{count}} плагина!", - "plugin_update_few": "Обновления доступны для {{count}} плагинов!", - "plugin_update_many": "Обновления доступны для {{count}} плагинов!", - "plugin_error_uninstall": "Загрузка {{name}} вызвала исключение, указанное выше. Обычно это означает, что плагин требует обновления для новой версии SteamUI. Проверьте наличие обновления или попробуйте его удалить в настройках Decky, в разделе Плагины.", - "plugin_load_error": { - "message": "Ошибка загрузки плагина {{name}}", - "toast": "Ошибка загрузки {{name}}" - }, - "plugin_uninstall": { - "button": "Удалить", - "desc": "Вы уверены, что хотите удалить {{name}}?", - "title": "Удалить {{name}}" - }, - "decky_title": "Decky", - "decky_update_available": "Доступно обновление на {{tag_name}}!", - "error": "Ошибка" - }, - "PluginView": { - "hidden_one": "{{count}} плагин скрыт из списка", - "hidden_few": "{{count}} плагинов скрыт из списка", - "hidden_many": "{{count}} плагинов скрыт из списка" - }, - "FilePickerIndex": { - "files": { - "show_hidden": "Показать скрытые файлы", - "all_files": "Все файлы", - "file_type": "Тип файла" - }, - "filter": { - "created_asce": "Создан (самый старый)", - "modified_asce": "Модифицирован (самый новый)", - "modified_desc": "Модифицирован (самый старый)", - "size_asce": "Размер (самый малый)", - "size_desc": "Размер (самый большой)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "created_desc": "Создан (самый новый)" - }, - "folder": { - "label": "Папка", - "show_more": "Показать больше файлов", - "select": "Использовать этот каталог" - }, - "file": { - "select": "Выберите этот файл" - } - }, - "PluginCard": { - "plugin_install": "Установить", - "plugin_no_desc": "Нет описания.", - "plugin_version_label": "Версия плагина", - "plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck." - }, - "PluginInstallModal": { - "install": { - "button_processing": "Установка", - "title": "Установить {{artifact}}", - "button_idle": "Установить", - "desc": "Вы уверены, что хотите установить {{artifact}} {{version}}?" - }, - "no_hash": "У данного плагина отсутствует хэш, устанавливайте на свой страх и риск.", - "reinstall": { - "title": "Переустановить {{artifact}}", - "desc": "Вы уверены, что хотите переустановить {{artifact}} {{version}}?", - "button_idle": "Переустановить", - "button_processing": "Переустановка" - }, - "update": { - "button_idle": "Обновить", - "button_processing": "Обновление", - "desc": "Вы уверены, что хотите обновить {{artifact}} {{version}}?", - "title": "Обновить {{artifact}}" - } - }, - "PluginListLabel": { - "hidden": "Скрыто из меню быстрого доступа" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Разрешить неаутентифицированный доступ к отладчику CEF всем в вашей сети", - "label": "Разрешить удаленную отладку CEF" - } - }, - "SettingsDeveloperIndex": { - "header": "Другое", - "third_party_plugins": { - "button_install": "Установить", - "label_zip": "Установить плагин из ZIP файла", - "label_url": "Установить плагин из URL", - "button_zip": "Обзор", - "header": "Сторонние плагины", - "label_desc": "Ссылка" - }, - "react_devtools": { - "ip_label": "IP", - "desc": "Позволяет подключиться к компьютеру, на котором работает React DevTools. Изменение этого параметра приведет к перезагрузке Steam. Установите IP-адрес перед включением.", - "label": "Включить React DevTools" - }, - "cef_console": { - "button": "Открыть консоль", - "desc": "Открывает консоль CEF. Полезно только для целей отладки. Настройки здесь потенциально опасны и должны использоваться только в том случае, если вы являетесь разработчиком плагинов или направленны сюда одним из них.", - "label": "CEF Консоль" - }, - "valve_internal": { - "desc1": "Включает внутреннее меню разработчика Valve.", - "label": "Включить Valve Internal", - "desc2": "Ничего не трогайте в этом меню, если не знаете, что оно делает." - } - }, - "SettingsGeneralIndex": { - "beta": { - "header": "Бета программа" - }, - "developer_mode": { - "label": "Режим разработчика" - }, - "other": { - "header": "Другое" - }, - "about": { - "decky_version": "Версия Decky", - "header": "Информация" - }, - "updates": { - "header": "Обновления" - }, - "notifications": { - "decky_updates_label": "Обновление Decky доступно", - "header": "Уведомления", - "plugin_updates_label": "Доступны обновления плагинов" - } - }, - "Store": { - "store_sort": { - "label": "Сортировка", - "label_def": "Последнее обновление(самые новые)" - }, - "store_source": { - "label": "Исходный код", - "desc": "Весь исходный код плагина доступен в репозитории SteamDeckHomebrew/decky-plugin-database на GitHub." - }, - "store_tabs": { - "about": "Информация", - "alph_desc": "По алфавиту (A - Z)", - "title": "Обзор", - "alph_asce": "По алфавиту (Z - A)" - }, - "store_testing_cta": "Пожалуйста, рассмотрите возможность тестирования новых плагинов, чтобы помочь команде Decky Loader!", - "store_contrib": { - "desc": "Если вы хотите внести свой вклад в магазин плагинов Decky, проверьте репозиторий SteamDeckHomebrew/decky-plugin-template на GitHub. Информация о разработке и распространении доступна в README.", - "label": "Помощь проекту" - }, - "store_filter": { - "label": "Фильтр", - "label_def": "Все" - }, - "store_search": { - "label": "Поиск" - }, - "store_testing_warning": { - "label": "Добро пожаловать в тестовый канал магазина", - "desc": "Вы можете использовать этот канал магазина для тестирования новейших версий плагинов. Не забудьте оставить отзыв на GitHub, чтобы плагин можно было обновить для всех пользователей." - } - }, - "StoreSelect": { - "custom_store": { - "label": "Сторонний магазин", - "url_label": "URL" - }, - "store_channel": { - "custom": "Сторонний", - "default": "По-умолчанию", - "label": "Канал магазина", - "testing": "Тестовый" - } - }, - "Updater": { - "decky_updates": "Обновления Decky", - "no_patch_notes_desc": "нет примечаний к патчу для этой версии", - "updates": { - "check_button": "Проверить обновления", - "checking": "Проверка", - "cur_version": "Текущая версия: {{ver}}", - "updating": "Обновление", - "install_button": "Установить обновление", - "label": "Обновления", - "lat_version": "Обновлено: версия {{ver}}", - "reloading": "Перезагрузка" - }, - "patch_notes_desc": "Примечания к патчу" - }, - "FilePickerError": { - "errors": { - "perm_denied": "У вас нет доступа к указанному каталогу.. Пожалуйста, проверьте имеет ли пользователь (deck на Steam Deck) соответствующие права доступа к указанной папке/файлу.", - "file_not_found": "Указан недействительный путь. Пожалуйста, проверьте его и повторите ввод.", - "unknown": "Произошла неизвестная ошибка. Текст ошибки: {{raw_error}}" - } - }, - "DropdownMultiselect": { - "button": { - "back": "Назад" - } - }, - "BranchSelect": { - "update_channel": { - "prerelease": "Предрелиз", - "stable": "Стабильный", - "testing": "Тестовый", - "label": "Канал обновлений" - } - }, - "Developer": { - "5secreload": "Перезагрузка через 5 секунд", - "disabling": "Выключение React DevTools", - "enabling": "Включение React DevTools" - }, - "SettingsIndex": { - "developer_title": "Разработчик", - "general_title": "Общее", - "plugins_title": "Плагины" - }, - "TitleView": { - "decky_store_desc": "Открыть магазин Decky", - "settings_desc": "Открыть настройки Decky" - } -} diff --git a/backend/locales/sq-AL.json b/backend/locales/sq-AL.json deleted file mode 100644 index fe9d7eec..00000000 --- a/backend/locales/sq-AL.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "SettingsDeveloperIndex": { - "react_devtools": { - "ip_label": "IP", - "label": "Aktivizo React DevTools" - }, - "third_party_plugins": { - "button_zip": "Kërko", - "header": "Shtesa të Huaj", - "button_install": "Instalo", - "label_desc": "URL", - "label_url": "Instalo Shtes Nga URL", - "label_zip": "Instalo Shtes Nga ZIP" - } - }, - "BranchSelect": { - "update_channel": { - "stable": "Fiksuar", - "label": "Kanali Përditësimet" - } - }, - "FilePickerIndex": { - "folder": { - "select": "Përdore këtë folder" - } - }, - "PluginCard": { - "plugin_install": "Instalo", - "plugin_version_label": "Versioni Shteses" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalo", - "button_processing": "Instalohet", - "desc": "Je i sigurt që don ta instalojsh {{artifact}} {{version}}?", - "title": "Instalo {{artifact}}" - }, - "no_hash": "Ky shtesë nuk ka hash, ti e instalon me rrezikun tuaj.", - "reinstall": { - "button_idle": "Riinstalo", - "button_processing": "Riinstalohet", - "desc": "Je i sigurt a don ta riinstalojsh {{artifact}} {{version}}?", - "title": "Riinstalo {{artifact}}" - }, - "update": { - "button_processing": "Përditësohet", - "desc": "Je i sigurt a don ta përditësojsh {{artifact}} {{version}}?", - "title": "Përditëso {{artifact}}" - } - }, - "PluginLoader": { - "decky_title": "Decky", - "plugin_uninstall": { - "title": "Çinstalo {{name}}", - "button": "Çinstalo", - "desc": "Je i sigurt që don ta çinstalojsh {{name}}?" - }, - "error": "Gabim", - "plugin_error_uninstall": "Ju lutem shko nga {{name}} në Decky menu nëse don ta çinstalojsh këtë shtese.", - "plugin_update_one": "", - "plugin_update_other": "" - }, - "PluginListIndex": { - "no_plugin": "Nuk ka shtesa të instaluar!", - "uninstall": "Çinstalo", - "update_all_one": "", - "update_all_other": "" - }, - "SettingsGeneralIndex": { - "other": { - "header": "Të Tjera" - }, - "about": { - "decky_version": "Versioni Decky" - }, - "updates": { - "header": "Përmirësimet" - } - }, - "SettingsIndex": { - "developer_title": "Zhvillues", - "general_title": "Gjeneral" - }, - "Store": { - "store_sort": { - "label": "Rendit" - }, - "store_tabs": { - "title": "Kërko" - }, - "store_contrib": { - "label": "Kontributi" - }, - "store_filter": { - "label": "Filtro", - "label_def": "Të Gjitha" - }, - "store_search": { - "label": "Kërko" - }, - "store_source": { - "label": "Kodin Burimor" - } - }, - "StoreSelect": { - "store_channel": { - "label": "Kanali Dyqanit" - } - }, - "Updater": { - "updates": { - "cur_version": "Versioni e tanishëme: {{ver}}" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "", - "mixed_other": "", - "update_one": "", - "update_other": "", - "reinstall_one": "", - "reinstall_other": "", - "install_one": "", - "install_other": "" - } - }, - "PluginView": { - "hidden_one": "", - "hidden_other": "" - } -} diff --git a/backend/locales/uk-UA.json b/backend/locales/uk-UA.json deleted file mode 100644 index 09fbca1b..00000000 --- a/backend/locales/uk-UA.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "Передреліз", - "testing": "Тестовий", - "label": "Канал оновлень", - "stable": "Стабільний" - } - }, - "Developer": { - "5secreload": "Перезавантаження за 5 секунд", - "enabling": "Увімкнення React DevTools", - "disabling": "Вимкнення React DevTools" - }, - "FilePickerIndex": { - "folder": { - "select": "Використовувати цю папку" - } - }, - "PluginListLabel": { - "hidden": "Приховано з меню швидкого доступу" - }, - "PluginCard": { - "plugin_full_access": "Цей плагін має повний доступ до вашого Steam Deck.", - "plugin_install": "Встановити", - "plugin_no_desc": "Опис не надано.", - "plugin_version_label": "Версія плагіна" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Встановити", - "button_processing": "Встановлення", - "title": "Встановити {{artifact}}", - "desc": "Ви впевнені, що хочете встановити {{artifact}} {{version}}?" - }, - "reinstall": { - "button_idle": "Перевстановити", - "desc": "Ви впевнені, що хочете перевстановити {{artifact}} {{version}}?", - "title": "Перевстановити {{artifact}}", - "button_processing": "Перевстановлення" - }, - "update": { - "button_idle": "Оновити", - "button_processing": "Оновлення", - "title": "Оновити {{artifact}}", - "desc": "Ви впевнені, що хочете оновити {{artifact}} {{version}}?" - }, - "no_hash": "Цей плагін не має хешу, ви встановлюєте його на власний ризик." - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Модифікувати 1 плагін", - "mixed_few": "Модифікувати {{count}} плагінів", - "mixed_many": "", - "reinstall_one": "Перевстановити 1 плагін", - "reinstall_few": "Перевстановити {{count}} плагінів", - "reinstall_many": "Перевстановити {{count}} плагінів", - "update_one": "Оновити 1 плагін", - "update_few": "Оновити {{count}} плагінів", - "update_many": "Оновити {{count}} плагінів", - "install_one": "Встановити 1 плагін", - "install_few": "Встановити {{count}} плагінів", - "install_many": "Встановити {{count}} плагінів" - }, - "ok_button": { - "idle": "Підтвердити", - "loading": "Опрацювання" - }, - "description": { - "install": "Встановити {{name}} {{version}}", - "update": "Оновити {{name}} до {{version}}", - "reinstall": "Перевстановити {{name}} {{version}}" - }, - "confirm": "Ви впевнені, що хочете застосувати такі модифікації?" - }, - "PluginListIndex": { - "no_plugin": "Плагінів не встановлено!", - "plugin_actions": "Дії плагінів", - "reinstall": "Перевстановити", - "reload": "Перезавантажити", - "update_to": "Оновити {{name}}", - "show": "Швидкий доступ: Показати", - "hide": "Швидкий доступ: Приховати", - "uninstall": "Видалити", - "update_all_one": "Оновити 1 плагін", - "update_all_few": "Оновити {{count}} плагінів", - "update_all_many": "Оновити {{count}} плагінів" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Доступне оновлення до {{tag_name}}!", - "error": "Помилка", - "plugin_load_error": { - "message": "Помилка завантаження плагіна {{name}}", - "toast": "Помилка завантаження {{name}}" - }, - "plugin_uninstall": { - "desc": "Ви впевнені, що хочете видалити {{name}}?", - "title": "Видалити {{name}}", - "button": "Видалення" - }, - "plugin_error_uninstall": "Завантаження {{name}} спровокувало помилку показану вище. Зазвичай це означає, що плагін вимагає оновлення до нової версії SteamUI. Перевірте чи таке оновлення доступне або виконайте його видалення у налаштуваннях Decky, у секції Плагіни.", - "plugin_update_one": "Доступне оновлення для 1 плагіна!", - "plugin_update_few": "Доступне оновлення для {{count}} плагінів!", - "plugin_update_many": "Доступне оновлення для {{count}} плагінів!" - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Відкрити консоль", - "label": "CEF-консоль", - "desc": "Відкрити CEF-консоль. Корисно тільки для дебагу. Ця штука потенційно небезпечна і повинна використовувати виключно якщо ви розробник плагіна, або якщо розробник спрямував вас сюди." - }, - "header": "Інше", - "react_devtools": { - "desc": "Вмикає доступ до компʼютера із запущеним React DevTools. Зміна цього налаштування перезавантажить Steam. Вкажіть IP перед увімкненням.", - "label": "Увімкнути React DevTools", - "ip_label": "IP" - }, - "third_party_plugins": { - "button_install": "Встановити", - "header": "Сторонні плагіни", - "label_desc": "URL", - "label_url": "Встановити плагін з URL", - "label_zip": "Встановити плагін з ZIP-файлу", - "button_zip": "Огляд" - }, - "valve_internal": { - "desc1": "Вмикає внутрішнє розробницьке меню Valve.", - "label": "Увімкнути Valve Internal", - "desc2": "Нічого не торкайтесь у цьому меню, якщо не розумієте, що ви робите." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Версія Decky", - "header": "Про нас" - }, - "beta": { - "header": "Участь у Beta" - }, - "developer_mode": { - "label": "Розробницький режим" - }, - "other": { - "header": "Інше" - }, - "updates": { - "header": "Оновлення" - } - }, - "SettingsIndex": { - "developer_title": "Розробник", - "general_title": "Загальне", - "plugins_title": "Плагіни" - }, - "Store": { - "store_contrib": { - "label": "Зробити внесок", - "desc": "Якщо ви бажаєте додати щось у Decky Plugin Store, завітайте у репозиторій SteamDeckHomebrew/decky-plugin-template на GitHub. Інформація про розробку та поширення доступна у README." - }, - "store_filter": { - "label": "Фільтр", - "label_def": "Усе" - }, - "store_search": { - "label": "Пошук" - }, - "store_sort": { - "label": "Сортування", - "label_def": "Востаннє оновлені (Найновіші)" - }, - "store_source": { - "label": "Вихідний код", - "desc": "Код усіх плагінів доступний у репозиторії SteamDeckHomebrew/decky-plugin-database на GitHub." - }, - "store_tabs": { - "about": "Інформація", - "alph_asce": "За алфавітом (Z до A)", - "alph_desc": "За алфавітом (A до Z)", - "title": "Огляд" - }, - "store_testing_cta": "Розгляньте можливість тестування нових плагінів, щоб допомогти команді Decky Loader!" - }, - "StoreSelect": { - "custom_store": { - "label": "Власний магазин", - "url_label": "URL" - }, - "store_channel": { - "custom": "Власний", - "default": "За замовчуванням", - "testing": "Тестування", - "label": "Канал магазину" - } - }, - "Updater": { - "decky_updates": "Оновлення Decky", - "no_patch_notes_desc": "Немає нотаток до цієї версії", - "patch_notes_desc": "Перелік змін", - "updates": { - "checking": "Перевірка", - "cur_version": "Поточна версія: {{ver}}", - "install_button": "Встановити оновлення", - "label": "Оновлення", - "reloading": "Перезавантаження", - "updating": "Оновлення", - "check_button": "Перевірити оновлення", - "lat_version": "Оновлено: використовується {{ver}}" - } - }, - "PluginView": { - "hidden_one": "{{count}} плагін приховано з цього списку", - "hidden_few": "{{count}} плагінів приховано з цього списку", - "hidden_many": "{{count}} плагінів приховано з цього списку" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Дозволити доступ до CEF-дебагера без аутентифікації для будь-кого у вашій мережі", - "label": "Дозволити віддалений CEF-дебагінг" - } - } -} diff --git a/backend/locales/zh-CN.json b/backend/locales/zh-CN.json deleted file mode 100644 index d9d12aa0..00000000 --- a/backend/locales/zh-CN.json +++ /dev/null @@ -1,253 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "发布候选", - "stable": "稳定", - "testing": "测试", - "label": "更新通道" - } - }, - "Developer": { - "5secreload": "5 秒钟后重新加载", - "disabling": "正在禁用 React DevTools", - "enabling": "正在启用 React DevTools" - }, - "FilePickerIndex": { - "folder": { - "select": "使用这个文件夹", - "label": "文件夹", - "show_more": "显示更多文件" - }, - "filter": { - "created_asce": "创建日期(最旧)", - "created_desc": "创建日期(最新)", - "modified_asce": "修改日期(最旧)", - "modified_desc": "修改日期(最新)", - "name_asce": "字母降序", - "name_desc": "字母升序", - "size_asce": "大小(最小)", - "size_desc": "大小(最大)" - }, - "files": { - "all_files": "全部文件", - "file_type": "文件类型", - "show_hidden": "显示隐藏文件" - }, - "file": { - "select": "选择此文件" - } - }, - "PluginCard": { - "plugin_install": "安装", - "plugin_no_desc": "无描述提供。", - "plugin_version_label": "插件版本", - "plugin_full_access": "此插件可以完全访问你的 Steam Deck。" - }, - "PluginInstallModal": { - "install": { - "button_idle": "安装", - "button_processing": "安装中", - "desc": "你确定要安装 {{artifact}} {{version}} 吗?", - "title": "安装 {{artifact}}" - }, - "reinstall": { - "button_idle": "重新安装", - "button_processing": "正在重新安装", - "desc": "你确定要重新安装 {{artifact}} {{version}} 吗?", - "title": "重新安装 {{artifact}}" - }, - "update": { - "button_idle": "更新", - "button_processing": "正在更新", - "desc": "你确定要更新 {{artifact}} {{version}} 吗?", - "title": "更新 {{artifact}}" - }, - "no_hash": "此插件没有哈希校验值,你需要自行承担安装风险。" - }, - "PluginListIndex": { - "no_plugin": "没有安装插件!", - "plugin_actions": "插件操作", - "reinstall": "重新安装", - "reload": "重新加载", - "uninstall": "卸载", - "update_to": "更新 {{name}}", - "update_all_other": "更新 {{count}} 个插件", - "show": "在快速访问菜单中显示", - "hide": "在快速访问菜单中隐藏" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "错误", - "plugin_error_uninstall": "加载 {{name}} 时引起了上述异常。这通常意味着插件需要更新以适应 SteamUI 的新版本。请检查插件是否有更新,或在 Decky 设置中的插件部分将其移除。", - "plugin_load_error": { - "message": "加载插件 {{name}} 错误", - "toast": "加载插件 {{name}} 发生了错误" - }, - "plugin_uninstall": { - "button": "卸载", - "title": "卸载 {{name}}", - "desc": "你确定要卸载 {{name}} 吗?" - }, - "decky_update_available": "新版本 {{tag_name}} 可用!", - "plugin_update_other": "{{count}} 个插件有更新!" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "允许你网络中的任何人无需身份验证即可访问CEF调试器", - "label": "允许远程访问CEF调试" - } - }, - "SettingsDeveloperIndex": { - "react_devtools": { - "ip_label": "IP", - "label": "启用 React DevTools", - "desc": "允许连接到运行着 React DevTools 的计算机,更改此设置将重新加载Steam,请在启用前设置IP地址。" - }, - "third_party_plugins": { - "button_install": "安装", - "button_zip": "浏览文件", - "header": "第三方插件", - "label_desc": "URL", - "label_url": "从 URL 安装插件", - "label_zip": "从 ZIP 压缩文件安装插件" - }, - "valve_internal": { - "desc1": "启用 Valve 内部开发者菜单。", - "desc2": "除非你知道你在干什么,否则请不要修改此菜单中的任何内容。", - "label": "启用 Valve 内部开发者" - }, - "cef_console": { - "button": "打开控制台", - "label": "CEF 控制台", - "desc": "打开 CEF 控制台。仅在调试目的下使用。这列选项均有风险,请仅在您是插件开发者或是在插件开发者指导时访问使用。" - }, - "header": "其他" - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky 版本", - "header": "关于" - }, - "beta": { - "header": "参与测试" - }, - "developer_mode": { - "label": "开发者模式" - }, - "other": { - "header": "其他" - }, - "updates": { - "header": "更新" - }, - "notifications": { - "header": "通知", - "decky_updates_label": "Decky 更新可用", - "plugin_updates_label": "插件更新可用" - } - }, - "SettingsIndex": { - "developer_title": "开发者", - "general_title": "通用", - "plugins_title": "插件" - }, - "Store": { - "store_contrib": { - "label": "贡献", - "desc": "如果你想要提交你的插件到 Decky 插件商店,请访问 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 存储库,关于开发和分发的相关信息,请查看 README 文件。" - }, - "store_filter": { - "label": "过滤器", - "label_def": "全部" - }, - "store_search": { - "label": "搜索" - }, - "store_sort": { - "label": "排序", - "label_def": "最后更新 (最新)" - }, - "store_source": { - "label": "源代码", - "desc": "所有插件的源代码都可以在 GitHub 上的 SteamDeckHomebrew/decky-plugin-database 存储库中获得。" - }, - "store_tabs": { - "about": "关于", - "alph_asce": "字母排序 (Z 到 A)", - "alph_desc": "字母排序 (A 到 Z)", - "title": "浏览" - }, - "store_testing_cta": "请考虑测试新插件以帮助 Decky Loader 团队!", - "store_testing_warning": { - "desc": "你可以使用该商店频道以体验最新版本的插件。 请在插件 Github 页面留言以使插件可以正式面向所有用户。", - "label": "欢迎来到商店测试频道" - } - }, - "StoreSelect": { - "store_channel": { - "default": "默认", - "label": "商店通道", - "testing": "测试", - "custom": "自定义" - }, - "custom_store": { - "label": "自定义商店", - "url_label": "URL" - } - }, - "Updater": { - "decky_updates": "Decky 更新", - "no_patch_notes_desc": "此版本没有补丁说明", - "patch_notes_desc": "补丁说明", - "updates": { - "check_button": "检查更新", - "checking": "检查中", - "cur_version": "当前版本: {{ver}}", - "install_button": "安装更新", - "label": "更新", - "lat_version": "已是最新版本: {{ver}} 运行中", - "reloading": "重新加载中", - "updating": "更新中" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_other": "更改 {{count}} 个插件", - "update_other": "更新 {{count}} 个插件", - "reinstall_other": "重装 {{count}} 个插件", - "install_other": "安装 {{count}} 个插件" - }, - "ok_button": { - "idle": "确认", - "loading": "工作中" - }, - "confirm": "确定要进行以下修改吗?", - "description": { - "install": "安装 {{name}} {{version}}", - "update": "更新 {{name}} to {{version}}", - "reinstall": "重装 {{name}} {{version}}" - } - }, - "PluginListLabel": { - "hidden": "在快速访问菜单中已隐藏" - }, - "PluginView": { - "hidden_other": "此列表隐藏了 {{count}} 个插件" - }, - "DropdownMultiselect": { - "button": { - "back": "返回" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "指定路径无效。请检查并输入正确的路径。", - "unknown": "发生了一个未知错误。原始错误为:{{raw_error}}", - "perm_denied": "你没有访问特定目录的权限。请检查你的用户(Steam Deck 中的 deck 账户)有着相对应的权限以访问特定的文件夹或文件。" - } - }, - "TitleView": { - "decky_store_desc": "打开 Decky 商店", - "settings_desc": "打开 Decky 设置" - } -} diff --git a/backend/locales/zh-TW.json b/backend/locales/zh-TW.json deleted file mode 100644 index 2891aa9c..00000000 --- a/backend/locales/zh-TW.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "testing": "測試版", - "label": "更新頻道", - "prerelease": "預發佈", - "stable": "穩定版" - } - }, - "Developer": { - "5secreload": "5 秒後重新載入", - "disabling": "正在停用 React DevTools", - "enabling": "正在啟用 React DevTools" - }, - "FilePickerIndex": { - "folder": { - "select": "使用此資料夾", - "show_more": "顯示更多檔案", - "label": "資料夾" - }, - "filter": { - "modified_asce": "修改日期(舊到新)", - "created_desc": "建立日期(新到舊)", - "modified_desc": "修改日期(新到舊)", - "name_desc": "子母排序(A到Z)", - "name_asce": "子母排序(Z到A)", - "size_asce": "檔案大小(小到大)", - "size_desc": "檔案大小(大到小)", - "created_asce": "建立日期(舊到新)" - }, - "file": { - "select": "選擇此檔案" - }, - "files": { - "all_files": "所有檔案", - "file_type": "檔案類型", - "show_hidden": "顯示隱藏檔" - } - }, - "PluginCard": { - "plugin_install": "安裝", - "plugin_no_desc": "未提示描述。", - "plugin_version_label": "外掛程式版本", - "plugin_full_access": "此外掛程式擁有您的 Steam Deck 的完整存取權。" - }, - "PluginInstallModal": { - "install": { - "button_idle": "安裝", - "button_processing": "正在安裝", - "title": "安裝 {{artifact}}", - "desc": "您確定要安裝 {{artifact}} {{version}} 嗎?" - }, - "reinstall": { - "button_idle": "重新安裝", - "button_processing": "正在重新安裝", - "desc": "您確定要重新安裝 {{artifact}} {{version}} 嗎?", - "title": "重新安裝 {{artifact}}" - }, - "update": { - "button_idle": "更新", - "button_processing": "正在更新", - "desc": "您確定要更新 {{artifact}} {{version}} 嗎?", - "title": "更新 {{artifact}}" - }, - "no_hash": "此外掛程式沒有提供 hash 驗證,安裝可能有風險。" - }, - "PluginListIndex": { - "no_plugin": "未安裝外掛程式!", - "plugin_actions": "外掛程式操作", - "uninstall": "解除安裝", - "update_to": "更新到 {{name}}", - "reinstall": "重新安裝", - "reload": "重新載入", - "show": "快速存取:顯示", - "hide": "快速存取:隱藏", - "update_all_other": "更新 {{count}} 個外掛程式" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "錯誤", - "plugin_error_uninstall": "載入 {{name}} 導致上述異常。這通常意味著該外掛程式需要針對新版本的 SteamUI 進行更新。在 Decky 設定中檢查是否存在更新,或評估刪除此外掛程式。", - "plugin_load_error": { - "message": "載入外掛程式 {{name}} 發生錯誤", - "toast": "{{name}} 載入出錯" - }, - "plugin_uninstall": { - "button": "解除安裝", - "title": "解除安裝 {{name}}", - "desc": "您確定要解除安裝 {{name}} 嗎?" - }, - "decky_update_available": "可更新至版本 {{tag_name}}!", - "plugin_update_other": "可更新 {{count}} 個外掛程式!" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "允許您的網路中的任何人未經認證地存取 CEF 偵錯器", - "label": "允許 CEF 遠端偵錯" - } - }, - "SettingsDeveloperIndex": { - "third_party_plugins": { - "button_zip": "開啟", - "label_desc": "網址", - "label_url": "從網址安裝外掛程式", - "label_zip": "從 ZIP 檔案安裝外掛程式", - "button_install": "安裝", - "header": "第三方外掛程式" - }, - "valve_internal": { - "desc2": "除非您知道它的作用,否則不要碰這個選單中的任何東西。", - "desc1": "啟用 Valve 內建開發人員選單。", - "label": "啟用 Valve 內建" - }, - "react_devtools": { - "desc": "啟用與執行 React DevTools 的電腦的連接。改變這個設定將重新載入 Steam。啟用前必須設定 IP 位址。", - "ip_label": "IP", - "label": "啟用 React DevTools" - }, - "header": "其他", - "cef_console": { - "button": "開啟控制台", - "label": "CEF 控制台", - "desc": "開啟 CEF 控制台。僅用於偵錯。這裡的東西有潛在的風險,只有當您是一個外掛程式開發者或者被外掛程式開發者引導到這裡時,才應該使用。" - } - }, - "SettingsGeneralIndex": { - "about": { - "header": "關於", - "decky_version": "Decky 版本" - }, - "beta": { - "header": "參與測試" - }, - "developer_mode": { - "label": "開發人員模式" - }, - "other": { - "header": "其他" - }, - "updates": { - "header": "更新" - }, - "notifications": { - "decky_updates_label": "Decky 可更新", - "header": "通知", - "plugin_updates_label": "外掛程式有更新" - } - }, - "SettingsIndex": { - "developer_title": "開發人員", - "general_title": "一般", - "plugins_title": "外掛程式" - }, - "Store": { - "store_contrib": { - "label": "貢獻", - "desc": "如果您想為 Decky 外掛程式商店做貢獻,請查看 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 儲存庫。README 中提供了有關開發和發佈的資訊。" - }, - "store_filter": { - "label": "過濾", - "label_def": "全部" - }, - "store_search": { - "label": "搜尋" - }, - "store_sort": { - "label": "排序", - "label_def": "最後更新 (最新)" - }, - "store_source": { - "label": "原始碼", - "desc": "所有外掛程式原始碼可以在 GitHub 的 SteamDeckHomebrew/decky-plugin-database 儲存庫查看。" - }, - "store_tabs": { - "about": "關於", - "alph_asce": "依字母排序 (Z 到 A)", - "alph_desc": "依字母排序 (A 到 Z)", - "title": "瀏覽" - }, - "store_testing_cta": "請考慮測試新的外掛程式來幫助 Decky Loader 團隊!" - }, - "StoreSelect": { - "custom_store": { - "label": "自訂商店", - "url_label": "網址" - }, - "store_channel": { - "custom": "自訂", - "default": "預設", - "label": "商店頻道", - "testing": "測試" - } - }, - "Updater": { - "decky_updates": "Decky 更新", - "no_patch_notes_desc": "這個版本沒有更新日誌", - "patch_notes_desc": "更新日誌", - "updates": { - "checking": "正在檢查", - "install_button": "安裝更新", - "label": "更新", - "lat_version": "已是最新:執行 {{ver}}", - "reloading": "正在重新載入", - "check_button": "檢查更新", - "cur_version": "目前版本:{{ver}}", - "updating": "正在更新" - } - }, - "PluginView": { - "hidden_other": "{{count}} 個外掛程式已隱藏" - }, - "PluginListLabel": { - "hidden": "已從快速存取選單中移除" - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_other": "修改 {{count}} 個外掛程式", - "update_other": "更新 {{count}} 個外掛程式", - "reinstall_other": "重新安裝 {{count}} 個外掛程式", - "install_other": "安裝 {{count}} 個外掛程式" - }, - "ok_button": { - "idle": "確定", - "loading": "執行中" - }, - "confirm": "您確定要進行以下的修改嗎?", - "description": { - "install": "安裝 {{name}} {{version}}", - "update": "更新 {{name}} 到 {{version}}", - "reinstall": "重新安裝 {{name}} {{version}}" - } - }, - "FilePickerError": { - "errors": { - "perm_denied": "您沒有瀏覽此目錄的權限。請檢查您的使用者(Steam Deck 中的 deck 帳號)有權限瀏覽特定的資料夾或檔案。", - "unknown": "發生未知錯誤。錯誤詳細資料:{{raw_error}}", - "file_not_found": "指定路徑無效。請檢查並輸入正確路徑。" - } - }, - "DropdownMultiselect": { - "button": { - "back": "返回" - } - } -} diff --git a/backend/localplatform.py b/backend/localplatform.py deleted file mode 100644 index 028eff8f..00000000 --- a/backend/localplatform.py +++ /dev/null @@ -1,52 +0,0 @@ -import platform, os - -ON_WINDOWS = platform.system() == "Windows" -ON_LINUX = not ON_WINDOWS - -if ON_WINDOWS: - from .localplatformwin import * - from . import localplatformwin as localplatform -else: - from .localplatformlinux import * - from . import localplatformlinux as localplatform - -def get_privileged_path() -> str: - '''Get path accessible by elevated user. Holds plugins, decky loader and decky loader configs''' - return localplatform.get_privileged_path() - -def get_unprivileged_path() -> str: - '''Get path accessible by non-elevated user. Holds plugin configuration, plugin data and plugin logs. Externally referred to as the 'Homebrew' directory''' - return localplatform.get_unprivileged_path() - -def get_unprivileged_user() -> str: - '''Get user that should own files made in unprivileged path''' - return localplatform.get_unprivileged_user() - -def get_chown_plugin_path() -> bool: - return os.getenv("CHOWN_PLUGIN_PATH", "1") == "1" - -def get_server_host() -> str: - return os.getenv("SERVER_HOST", "127.0.0.1") - -def get_server_port() -> int: - return int(os.getenv("SERVER_PORT", "1337")) - -def get_live_reload() -> bool: - return os.getenv("LIVE_RELOAD", "1") == "1" - -def get_keep_systemd_service() -> bool: - return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1" - -def get_log_level() -> int: - return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[ - os.getenv("LOG_LEVEL", "INFO") - ] - -def get_selinux() -> bool: - if ON_LINUX: - from subprocess import check_output - try: - if (check_output("getenforce").decode("ascii").strip("\n") == "Enforcing"): return True - except FileNotFoundError: - pass - return False diff --git a/backend/localplatformlinux.py b/backend/localplatformlinux.py deleted file mode 100644 index bde2caac..00000000 --- a/backend/localplatformlinux.py +++ /dev/null @@ -1,192 +0,0 @@ -import os, pwd, grp, sys, logging -from subprocess import call, run, DEVNULL, PIPE, STDOUT -from .customtypes import UserType - -logger = logging.getLogger("localplatform") - -# Get the user id hosting the plugin loader -def _get_user_id() -> int: - return pwd.getpwnam(_get_user()).pw_uid - -# Get the user hosting the plugin loader -def _get_user() -> str: - return get_unprivileged_user() - -# Get the effective user id of the running process -def _get_effective_user_id() -> int: - return os.geteuid() - -# Get the effective user of the running process -def _get_effective_user() -> str: - return pwd.getpwuid(_get_effective_user_id()).pw_name - -# Get the effective user group id of the running process -def _get_effective_user_group_id() -> int: - return os.getegid() - -# Get the effective user group of the running process -def _get_effective_user_group() -> str: - return grp.getgrgid(_get_effective_user_group_id()).gr_name - -# Get the user owner of the given file path. -def _get_user_owner(file_path: str) -> str: - return pwd.getpwuid(os.stat(file_path).st_uid).pw_name - -# Get the user group of the given file path, or the user group hosting the plugin loader -def _get_user_group(file_path: str | None = None) -> str: - return grp.getgrgid(os.stat(file_path).st_gid if file_path is not None else _get_user_group_id()).gr_name - -# Get the group id of the user hosting the plugin loader -def _get_user_group_id() -> int: - return pwd.getpwuid(_get_user_id()).pw_gid - -def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: - user_str = "" - - if user == UserType.HOST_USER: - user_str = _get_user()+":"+_get_user_group() - elif user == UserType.EFFECTIVE_USER: - user_str = _get_effective_user()+":"+_get_effective_user_group() - elif user == UserType.ROOT: - user_str = "root:root" - else: - raise Exception("Unknown User Type") - - result = call(["chown", "-R", user_str, path] if recursive else ["chown", user_str, path]) - return result == 0 - -def chmod(path : str, permissions : int, recursive : bool = True) -> bool: - if _get_effective_user_id() != 0: - return True - result = call(["chmod", "-R", str(permissions), path] if recursive else ["chmod", str(permissions), path]) - return result == 0 - -def folder_owner(path : str) -> UserType|None: - user_owner = _get_user_owner(path) - - if (user_owner == _get_user()): - return UserType.HOST_USER - - elif (user_owner == _get_effective_user()): - return UserType.EFFECTIVE_USER - - else: - return None - -def get_home_path(user : UserType = UserType.HOST_USER) -> str: - user_name = "root" - - if user == UserType.HOST_USER: - user_name = _get_user() - elif user == UserType.EFFECTIVE_USER: - user_name = _get_effective_user() - elif user == UserType.ROOT: - pass - else: - raise Exception("Unknown User Type") - - return pwd.getpwnam(user_name).pw_dir - -def get_username() -> str: - return _get_user() - -def setgid(user : UserType = UserType.HOST_USER): - user_id = 0 - - if user == UserType.HOST_USER: - user_id = _get_user_group_id() - elif user == UserType.ROOT: - pass - else: - raise Exception("Unknown user type") - - os.setgid(user_id) - -def setuid(user : UserType = UserType.HOST_USER): - user_id = 0 - - if user == UserType.HOST_USER: - user_id = _get_user_id() - elif user == UserType.ROOT: - pass - else: - raise Exception("Unknown user type") - - os.setuid(user_id) - -async def service_active(service_name : str) -> bool: - res = run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL) - return res.returncode == 0 - -async def service_restart(service_name : str) -> bool: - call(["systemctl", "daemon-reload"]) - cmd = ["systemctl", "restart", service_name] - res = run(cmd, stdout=PIPE, stderr=STDOUT) - return res.returncode == 0 - -async def service_stop(service_name : str) -> bool: - cmd = ["systemctl", "stop", service_name] - res = run(cmd, stdout=PIPE, stderr=STDOUT) - return res.returncode == 0 - -async def service_start(service_name : str) -> bool: - cmd = ["systemctl", "start", service_name] - res = run(cmd, stdout=PIPE, stderr=STDOUT) - return res.returncode == 0 - -def get_privileged_path() -> str: - path = os.getenv("PRIVILEGED_PATH") - - if path == None: - path = get_unprivileged_path() - - return path - -def _parent_dir(path : str | None) -> str | None: - if path == None: - return None - - if path.endswith('/'): - path = path[:-1] - - return os.path.dirname(path) - -def get_unprivileged_path() -> str: - path = os.getenv("UNPRIVILEGED_PATH") - - if path == None: - path = _parent_dir(os.getenv("PLUGIN_PATH")) - - if path == None: - logger.debug("Unprivileged path is not properly configured. Making something up!") - # Expected path of loader binary is /home/deck/homebrew/service/PluginLoader - path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0]))) - - if path != None and not os.path.exists(path): - path = None - - if path == None: - logger.warn("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew") - path = "/home/deck/homebrew" # We give up - - return path - - -def get_unprivileged_user() -> str: - user = os.getenv("UNPRIVILEGED_USER") - - if user == None: - # Lets hope we can extract it from the unprivileged dir - dir = os.path.realpath(get_unprivileged_path()) - - pws = sorted(pwd.getpwall(), reverse=True, key=lambda pw: len(pw.pw_dir)) - for pw in pws: - if dir.startswith(os.path.realpath(pw.pw_dir)): - user = pw.pw_name - break - - if user == None: - logger.warn("Unprivileged user is not properly configured. Defaulting to 'deck'") - user = 'deck' - - return user diff --git a/backend/localplatformwin.py b/backend/localplatformwin.py deleted file mode 100644 index 4c4e9439..00000000 --- a/backend/localplatformwin.py +++ /dev/null @@ -1,53 +0,0 @@ -from .customtypes import UserType -import os, sys - -def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: - return True # Stubbed - -def chmod(path : str, permissions : int, recursive : bool = True) -> bool: - return True # Stubbed - -def folder_owner(path : str) -> UserType|None: - return UserType.HOST_USER # Stubbed - -def get_home_path(user : UserType = UserType.HOST_USER) -> str: - return os.path.expanduser("~") # Mostly stubbed - -def setgid(user : UserType = UserType.HOST_USER): - pass # Stubbed - -def setuid(user : UserType = UserType.HOST_USER): - pass # Stubbed - -async def service_active(service_name : str) -> bool: - return True # Stubbed - -async def service_stop(service_name : str) -> bool: - return True # Stubbed - -async def service_start(service_name : str) -> bool: - return True # Stubbed - -async def service_restart(service_name : str) -> bool: - if service_name == "plugin_loader": - sys.exit(42) - - return True # Stubbed - -def get_username() -> str: - return os.getlogin() - -def get_privileged_path() -> str: - '''On windows, privileged_path is equal to unprivileged_path''' - return get_unprivileged_path() - -def get_unprivileged_path() -> str: - path = os.getenv("UNPRIVILEGED_PATH") - - if path == None: - path = os.getenv("PRIVILEGED_PATH", os.path.join(os.path.expanduser("~"), "homebrew")) - - return path - -def get_unprivileged_user() -> str: - return os.getenv("UNPRIVILEGED_USER", os.getlogin()) diff --git a/backend/localsocket.py b/backend/localsocket.py deleted file mode 100644 index f38fe5e7..00000000 --- a/backend/localsocket.py +++ /dev/null @@ -1,139 +0,0 @@ -import asyncio, time -from typing import Awaitable, Callable -import random - -from .localplatform import ON_WINDOWS - -BUFFER_LIMIT = 2 ** 20 # 1 MiB - -class UnixSocket: - def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): - ''' - on_new_message takes 1 string argument. - It's return value gets used, if not None, to write data to the socket. - Method should be async - ''' - self.socket_addr = f"/tmp/plugin_socket_{time.time()}" - self.on_new_message = on_new_message - self.socket = None - self.reader = None - self.writer = None - - async def setup_server(self): - self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT) - - async def _open_socket_if_not_exists(self): - if not self.reader: - retries = 0 - while retries < 10: - try: - self.reader, self.writer = await asyncio.open_unix_connection(self.socket_addr, limit=BUFFER_LIMIT) - return True - except: - await asyncio.sleep(2) - retries += 1 - return False - else: - return True - - async def get_socket_connection(self): - if not await self._open_socket_if_not_exists(): - return None, None - - return self.reader, self.writer - - async def close_socket_connection(self): - if self.writer != None: - self.writer.close() - - self.reader = None - - async def read_single_line(self) -> str|None: - reader, _ = await self.get_socket_connection() - - try: - assert reader - except AssertionError: - return - - return await self._read_single_line(reader) - - async def write_single_line(self, message : str): - _, writer = await self.get_socket_connection() - - try: - assert writer - except AssertionError: - return - - await self._write_single_line(writer, message) - - async def _read_single_line(self, reader: asyncio.StreamReader) -> str: - line = bytearray() - while True: - try: - line.extend(await reader.readuntil()) - except asyncio.LimitOverrunError: - line.extend(await reader.read(reader._limit)) # type: ignore - continue - except asyncio.IncompleteReadError as err: - line.extend(err.partial) - break - else: - break - - return line.decode("utf-8") - - async def _write_single_line(self, writer: asyncio.StreamWriter, message : str): - if not message.endswith("\n"): - message += "\n" - - writer.write(message.encode("utf-8")) - await writer.drain() - - async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): - while True: - line = await self._read_single_line(reader) - - try: - res = await self.on_new_message(line) - except Exception: - return - - if res != None: - await self._write_single_line(writer, res) - -class PortSocket (UnixSocket): - def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): - ''' - on_new_message takes 1 string argument. - It's return value gets used, if not None, to write data to the socket. - Method should be async - ''' - super().__init__(on_new_message) - self.host = "127.0.0.1" - self.port = random.sample(range(40000, 60000), 1)[0] - - async def setup_server(self): - self.socket = await asyncio.start_server(self._listen_for_method_call, host=self.host, port=self.port, limit=BUFFER_LIMIT) - - async def _open_socket_if_not_exists(self): - if not self.reader: - retries = 0 - while retries < 10: - try: - self.reader, self.writer = await asyncio.open_connection(host=self.host, port=self.port, limit=BUFFER_LIMIT) - return True - except: - await asyncio.sleep(2) - retries += 1 - return False - else: - return True - -if ON_WINDOWS: - class LocalSocket (PortSocket): # type: ignore - pass -else: - class LocalSocket (UnixSocket): - pass \ No newline at end of file diff --git a/backend/main.py b/backend/main.py deleted file mode 100644 index 793d000c..00000000 --- a/backend/main.py +++ /dev/null @@ -1,192 +0,0 @@ -# Change PyInstaller files permissions -import sys -from typing import Dict -from .localplatform import (chmod, chown, service_stop, service_start, - ON_WINDOWS, get_log_level, get_live_reload, - get_server_port, get_server_host, get_chown_plugin_path, - get_privileged_path) -if hasattr(sys, '_MEIPASS'): - chmod(sys._MEIPASS, 755) # type: ignore -# Full imports -from asyncio import AbstractEventLoop, new_event_loop, set_event_loop, sleep -from logging import basicConfig, getLogger -from os import path -from traceback import format_exc -import multiprocessing - -import aiohttp_cors # type: ignore -# Partial imports -from aiohttp import client_exceptions -from aiohttp.web import Application, Response, Request, get, run_app, static # type: ignore -from aiohttp_jinja2 import setup as jinja_setup - -# local modules -from .browser import PluginBrowser -from .helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, - mkdir_as_user, get_system_pythonpaths, get_effective_user_id) - -from .injector import get_gamepadui_tab, Tab, close_old_tabs -from .loader import Loader -from .settings import SettingsManager -from .updater import Updater -from .utilities import Utilities -from .customtypes import UserType - - -basicConfig( - level=get_log_level(), - format="[%(module)s][%(levelname)s]: %(message)s" -) - -logger = getLogger("Main") -plugin_path = path.join(get_privileged_path(), "plugins") - -def chown_plugin_dir(): - if not path.exists(plugin_path): # For safety, create the folder before attempting to do anything with it - mkdir_as_user(plugin_path) - - if not chown(plugin_path, UserType.HOST_USER) or not chmod(plugin_path, 555): - logger.error(f"chown/chmod exited with a non-zero exit code") - -if get_chown_plugin_path() == True: - chown_plugin_dir() - -class PluginManager: - def __init__(self, loop: AbstractEventLoop) -> None: - self.loop = loop - self.web_app = Application() - self.web_app.middlewares.append(csrf_middleware) - self.cors = aiohttp_cors.setup(self.web_app, defaults={ - "https://steamloopback.host": aiohttp_cors.ResourceOptions( - expose_headers="*", - allow_headers="*", - allow_credentials=True - ) - }) - self.plugin_loader = Loader(self, plugin_path, self.loop, get_live_reload()) - self.settings = SettingsManager("loader", path.join(get_privileged_path(), "settings")) - self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings) - self.utilities = Utilities(self) - self.updater = Updater(self) - - jinja_setup(self.web_app) - - async def startup(_: Application): - if self.settings.getSetting("cef_forward", False): - self.loop.create_task(service_start(REMOTE_DEBUGGER_UNIT)) - else: - self.loop.create_task(service_stop(REMOTE_DEBUGGER_UNIT)) - self.loop.create_task(self.loader_reinjector()) - self.loop.create_task(self.load_plugins()) - - self.web_app.on_startup.append(startup) - - self.loop.set_exception_handler(self.exception_handler) - self.web_app.add_routes([get("/auth/token", self.get_auth_token)]) - - for route in list(self.web_app.router.routes()): - self.cors.add(route) # type: ignore - self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))]) - self.web_app.add_routes([static("/legacy", path.join(path.dirname(__file__), 'legacy'))]) - - def exception_handler(self, loop: AbstractEventLoop, context: Dict[str, str]): - if context["message"] == "Unclosed connection": - return - loop.default_exception_handler(context) - - async def get_auth_token(self, request: Request): - return Response(text=get_csrf_token()) - - async def load_plugins(self): - # await self.wait_for_server() - logger.debug("Loading plugins") - self.plugin_loader.import_plugins() - # await inject_to_tab("SP", "window.syncDeckyPlugins();") - if self.settings.getSetting("pluginOrder", None) == None: - self.settings.setSetting("pluginOrder", list(self.plugin_loader.plugins.keys())) - logger.debug("Did not find pluginOrder setting, set it to default") - - async def loader_reinjector(self): - while True: - tab = None - nf = False - dc = False - while not tab: - try: - tab = await get_gamepadui_tab() - except (client_exceptions.ClientConnectorError, client_exceptions.ServerDisconnectedError): - if not dc: - logger.debug("Couldn't connect to debugger, waiting...") - dc = True - pass - except ValueError: - if not nf: - logger.debug("Couldn't find GamepadUI tab, waiting...") - nf = True - pass - if not tab: - await sleep(5) - await tab.open_websocket() - await tab.enable() - await self.inject_javascript(tab, True) - try: - async for msg in tab.listen_for_message(): - # this gets spammed a lot - if msg.get("method", None) != "Page.navigatedWithinDocument": - logger.debug("Page event: " + str(msg.get("method", None))) - if msg.get("method", None) == "Page.domContentEventFired": - if not await tab.has_global_var("deckyHasLoaded", False): - await self.inject_javascript(tab) - if msg.get("method", None) == "Inspector.detached": - logger.info("CEF has requested that we detach.") - await tab.close_websocket() - break - # If this is a forceful disconnect the loop will just stop without any failure message. In this case, injector.py will handle this for us so we don't need to close the socket. - # This is because of https://github.com/aio-libs/aiohttp/blob/3ee7091b40a1bc58a8d7846e7878a77640e96996/aiohttp/client_ws.py#L321 - logger.info("CEF has disconnected...") - # At this point the loop starts again and we connect to the freshly started Steam client once it is ready. - except Exception: - logger.error("Exception while reading page events " + format_exc()) - await tab.close_websocket() - pass - # while True: - # await sleep(5) - # if not await tab.has_global_var("deckyHasLoaded", False): - # logger.info("Plugin loader isn't present in Steam anymore, reinjecting...") - # await self.inject_javascript(tab) - - async def inject_javascript(self, tab: Tab, first: bool=False, request: Request|None=None): - logger.info("Loading Decky frontend!") - try: - if first: - if await tab.has_global_var("deckyHasLoaded", False): - await close_old_tabs() - await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => location.reload(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}", False, False, False) - except: - logger.info("Failed to inject JavaScript into tab\n" + format_exc()) - pass - - def run(self): - return run_app(self.web_app, host=get_server_host(), port=get_server_port(), loop=self.loop, access_log=None) - -def main(): - if ON_WINDOWS: - # Fix windows/flask not recognising that .js means 'application/javascript' - import mimetypes - mimetypes.add_type('application/javascript', '.js') - - # Required for multiprocessing support in frozen files - multiprocessing.freeze_support() - else: - if get_effective_user_id() != 0: - logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues") - - # Append the loader's plugin path to the recognized python paths - sys.path.append(path.join(path.dirname(__file__), "plugin")) - - # Append the system and user python paths - sys.path.extend(get_system_pythonpaths()) - - loop = new_event_loop() - set_event_loop(loop) - PluginManager(loop).run() diff --git a/backend/plugin.py b/backend/plugin.py deleted file mode 100644 index 163bb9b6..00000000 --- a/backend/plugin.py +++ /dev/null @@ -1,163 +0,0 @@ -import multiprocessing -from asyncio import (Lock, get_event_loop, new_event_loop, - set_event_loop, sleep) -from importlib.util import module_from_spec, spec_from_file_location -from json import dumps, load, loads -from logging import getLogger -from traceback import format_exc -from os import path, environ -from signal import SIGINT, signal -from sys import exit, path as syspath -from typing import Any, Dict -from .localsocket import LocalSocket -from .localplatform import setgid, setuid, get_username, get_home_path -from .customtypes import UserType -from . import helpers - -class PluginWrapper: - def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: - self.file = file - self.plugin_path = plugin_path - self.plugin_directory = plugin_directory - self.method_call_lock = Lock() - self.socket: LocalSocket = LocalSocket(self._on_new_message) - - self.version = None - - json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8")) - if path.isfile(path.join(plugin_path, plugin_directory, "package.json")): - package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8")) - self.version = package_json["version"] - - self.legacy = False - self.main_view_html = json["main_view_html"] if "main_view_html" in json else "" - self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else "" - self.legacy = self.main_view_html or self.tile_view_html - - self.name = json["name"] - self.author = json["author"] - self.flags = json["flags"] - - self.log = getLogger("plugin") - - self.passive = not path.isfile(self.file) - - def __str__(self) -> str: - return self.name - - def _init(self): - try: - signal(SIGINT, lambda s, f: exit(0)) - - set_event_loop(new_event_loop()) - if self.passive: - return - setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) - setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) - # export a bunch of environment variables to help plugin developers - environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) - environ["USER"] = "root" if "root" in self.flags else get_username() - environ["DECKY_VERSION"] = helpers.get_loader_version() - environ["DECKY_USER"] = get_username() - environ["DECKY_USER_HOME"] = helpers.get_home_path() - environ["DECKY_HOME"] = helpers.get_homebrew_path() - environ["DECKY_PLUGIN_SETTINGS_DIR"] = path.join(environ["DECKY_HOME"], "settings", self.plugin_directory) - helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "settings")) - helpers.mkdir_as_user(environ["DECKY_PLUGIN_SETTINGS_DIR"]) - environ["DECKY_PLUGIN_RUNTIME_DIR"] = path.join(environ["DECKY_HOME"], "data", self.plugin_directory) - helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "data")) - helpers.mkdir_as_user(environ["DECKY_PLUGIN_RUNTIME_DIR"]) - environ["DECKY_PLUGIN_LOG_DIR"] = path.join(environ["DECKY_HOME"], "logs", self.plugin_directory) - helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "logs")) - helpers.mkdir_as_user(environ["DECKY_PLUGIN_LOG_DIR"]) - environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory) - environ["DECKY_PLUGIN_NAME"] = self.name - if self.version: - environ["DECKY_PLUGIN_VERSION"] = self.version - environ["DECKY_PLUGIN_AUTHOR"] = self.author - - # append the plugin's `py_modules` to the recognized python paths - syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules")) - - spec = spec_from_file_location("_", self.file) - assert spec is not None - module = module_from_spec(spec) - assert spec.loader is not None - spec.loader.exec_module(module) - self.Plugin = module.Plugin - - if hasattr(self.Plugin, "_migration"): - get_event_loop().run_until_complete(self.Plugin._migration(self.Plugin)) - if hasattr(self.Plugin, "_main"): - get_event_loop().create_task(self.Plugin._main(self.Plugin)) - get_event_loop().create_task(self.socket.setup_server()) - get_event_loop().run_forever() - except: - self.log.error("Failed to start " + self.name + "!\n" + format_exc()) - exit(0) - - async def _unload(self): - try: - self.log.info("Attempting to unload with plugin " + self.name + "'s \"_unload\" function.\n") - if hasattr(self.Plugin, "_unload"): - await self.Plugin._unload(self.Plugin) - self.log.info("Unloaded " + self.name + "\n") - else: - self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n") - except: - self.log.error("Failed to unload " + self.name + "!\n" + format_exc()) - exit(0) - - async def _on_new_message(self, message : str) -> str|None: - data = loads(message) - - if "stop" in data: - self.log.info("Calling Loader unload function.") - await self._unload() - get_event_loop().stop() - while get_event_loop().is_running(): - await sleep(0) - get_event_loop().close() - raise Exception("Closing message listener") - - # TODO there is definitely a better way to type this - d: Dict[str, Any] = {"res": None, "success": True} - try: - d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"]) - except Exception as e: - d["res"] = str(e) - d["success"] = False - finally: - return dumps(d, ensure_ascii=False) - - def start(self): - if self.passive: - return self - multiprocessing.Process(target=self._init).start() - return self - - def stop(self): - if self.passive: - return - - async def _(self: PluginWrapper): - await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) - await self.socket.close_socket_connection() - - get_event_loop().create_task(_(self)) - - async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]): - if self.passive: - raise RuntimeError("This plugin is passive (aka does not implement main.py)") - async with self.method_call_lock: - # reader, writer = - await self.socket.get_socket_connection() - - await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False)) - - line = await self.socket.read_single_line() - if line != None: - res = loads(line) - if not res["success"]: - raise Exception(res["res"]) - return res["res"] \ No newline at end of file diff --git a/backend/settings.py b/backend/settings.py deleted file mode 100644 index a9ab3daa..00000000 --- a/backend/settings.py +++ /dev/null @@ -1,60 +0,0 @@ -from json import dump, load -from os import mkdir, path, listdir, rename -from typing import Any, Dict -from .localplatform import chown, folder_owner, get_chown_plugin_path -from .customtypes import UserType - -from .helpers import get_homebrew_path - - -class SettingsManager: - def __init__(self, name: str, settings_directory: str | None = None) -> None: - wrong_dir = get_homebrew_path() - if settings_directory == None: - settings_directory = path.join(wrong_dir, "settings") - - self.path = path.join(settings_directory, name + ".json") - - #Create the folder with the correct permission - if not path.exists(settings_directory): - mkdir(settings_directory) - - #Copy all old settings file in the root directory to the correct folder - for file in listdir(wrong_dir): - if file.endswith(".json"): - rename(path.join(wrong_dir,file), - path.join(settings_directory, file)) - self.path = path.join(settings_directory, name + ".json") - - - #If the owner of the settings directory is not the user, then set it as the user: - expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.ROOT - if folder_owner(settings_directory) != expected_user: - chown(settings_directory, expected_user, False) - - self.settings: Dict[str, Any] = {} - - try: - open(self.path, "x", encoding="utf-8") - except FileExistsError as _: - self.read() - pass - - def read(self): - try: - with open(self.path, "r", encoding="utf-8") as file: - self.settings = load(file) - except Exception as e: - print(e) - pass - - def commit(self): - with open(self.path, "w+", encoding="utf-8") as file: - dump(self.settings, file, indent=4, ensure_ascii=False) - - def getSetting(self, key: str, default: Any = None) -> Any: - return self.settings.get(key, default) - - def setSetting(self, key: str, value: Any) -> Any: - self.settings[key] = value - self.commit() diff --git a/backend/src/browser.py b/backend/src/browser.py new file mode 100644 index 00000000..08560749 --- /dev/null +++ b/backend/src/browser.py @@ -0,0 +1,275 @@ +# Full imports +import json +# import pprint +# from pprint import pformat + +# Partial imports +from aiohttp import ClientSession +from asyncio import sleep +from hashlib import sha256 +from io import BytesIO +from logging import getLogger +from os import R_OK, W_OK, path, listdir, access, mkdir +from shutil import rmtree +from time import time +from zipfile import ZipFile +from enum import IntEnum +from typing import Dict, List, TypedDict + +# Local modules +from .localplatform import chown, chmod +from .loader import Loader, Plugins +from .helpers import get_ssl_context, download_remote_binary_to_path +from .settings import SettingsManager +from .injector import get_gamepadui_tab + +logger = getLogger("Browser") + +class PluginInstallType(IntEnum): + INSTALL = 0 + REINSTALL = 1 + UPDATE = 2 + +class PluginInstallRequest(TypedDict): + name: str + artifact: str + version: str + hash: str + install_type: PluginInstallType + +class PluginInstallContext: + def __init__(self, artifact: str, name: str, version: str, hash: str) -> None: + self.artifact = artifact + self.name = name + self.version = version + self.hash = hash + +class PluginBrowser: + def __init__(self, plugin_path: str, plugins: Plugins, loader: Loader, settings: SettingsManager) -> None: + self.plugin_path = plugin_path + self.plugins = plugins + self.loader = loader + self.settings = settings + self.install_requests: Dict[str, PluginInstallContext | List[PluginInstallContext]] = {} + + def _unzip_to_plugin_dir(self, zip: BytesIO, name: str, hash: str): + zip_hash = sha256(zip.getbuffer()).hexdigest() + if hash and (zip_hash != hash): + return False + zip_file = ZipFile(zip) + zip_file.extractall(self.plugin_path) + plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None + plugin_dir = path.join(self.plugin_path, plugin_folder) + + if not chown(plugin_dir) or not chmod(plugin_dir, 555): + logger.error(f"chown/chmod exited with a non-zero exit code") + return False + return True + + async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: str): + rv = False + try: + packageJsonPath = path.join(pluginBasePath, 'package.json') + pluginBinPath = path.join(pluginBasePath, 'bin') + + if access(packageJsonPath, R_OK): + with open(packageJsonPath, "r", encoding="utf-8") as f: + packageJson = json.load(f) + if "remote_binary" in packageJson and len(packageJson["remote_binary"]) > 0: + # create bin directory if needed. + chmod(pluginBasePath, 777) + if access(pluginBasePath, W_OK): + if not path.exists(pluginBinPath): + mkdir(pluginBinPath) + if not access(pluginBinPath, W_OK): + chmod(pluginBinPath, 777) + + rv = True + for remoteBinary in packageJson["remote_binary"]: + # Required Fields. If any Remote Binary is missing these fail the install. + binName = remoteBinary["name"] + binURL = remoteBinary["url"] + binHash = remoteBinary["sha256hash"] + if not await download_remote_binary_to_path(binURL, binHash, path.join(pluginBinPath, binName)): + rv = False + raise Exception(f"Error Downloading Remote Binary {binName}@{binURL} with hash {binHash} to {path.join(pluginBinPath, binName)}") + + chown(self.plugin_path) + chmod(pluginBasePath, 555) + else: + rv = True + logger.debug(f"No Remote Binaries to Download") + + except Exception as e: + rv = False + logger.debug(str(e)) + + return rv + + """Return the filename (only) for the specified plugin""" + def find_plugin_folder(self, name: str) -> str | None: + for folder in listdir(self.plugin_path): + try: + with open(path.join(self.plugin_path, folder, 'plugin.json'), "r", encoding="utf-8") as f: + plugin = json.load(f) + + if plugin['name'] == name: + return folder + except: + logger.debug(f"skipping {folder}") + + async def uninstall_plugin(self, name: str): + if self.loader.watcher: + self.loader.watcher.disabled = True + tab = await get_gamepadui_tab() + plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None + plugin_dir = path.join(self.plugin_path, ) + try: + logger.info("uninstalling " + name) + logger.info(" at dir " + plugin_dir) + logger.debug("calling frontend unload for %s" % str(name)) + res = await tab.evaluate_js(f"DeckyPluginLoader.unloadPlugin('{name}')") + logger.debug("result of unload from UI: %s", res) + # plugins_snapshot = self.plugins.copy() + # snapshot_string = pformat(plugins_snapshot) + # logger.debug("current plugins: %s", snapshot_string) + if name in self.plugins: + logger.debug("Plugin %s was found", name) + self.plugins[name].stop() + logger.debug("Plugin %s was stopped", name) + del self.plugins[name] + logger.debug("Plugin %s was removed from the dictionary", name) + self.cleanup_plugin_settings(name) + logger.debug("removing files %s" % str(name)) + rmtree(plugin_dir) + except FileNotFoundError: + logger.warning(f"Plugin {name} not installed, skipping uninstallation") + except Exception as e: + logger.error(f"Plugin {name} in {plugin_dir} was not uninstalled") + logger.error(f"Error at {str(e)}", exc_info=e) + if self.loader.watcher: + self.loader.watcher.disabled = False + + async def _install(self, artifact: str, name: str, version: str, hash: str): + # Will be set later in code + res_zip = None + + # Check if plugin is installed + isInstalled = False + # Preserve plugin order before removing plugin (uninstall alters the order and removes the plugin from the list) + current_plugin_order = self.settings.getSetting("pluginOrder")[:] + if self.loader.watcher: + self.loader.watcher.disabled = True + try: + pluginFolderPath = self.find_plugin_folder(name) + if pluginFolderPath: + isInstalled = True + except: + logger.error(f"Failed to determine if {name} is already installed, continuing anyway.") + + # Check if the file is a local file or a URL + if artifact.startswith("file://"): + logger.info(f"Installing {name} from local ZIP file (Version: {version})") + res_zip = BytesIO(open(artifact[7:], "rb").read()) + else: + logger.info(f"Installing {name} from URL (Version: {version})") + async with ClientSession() as client: + logger.debug(f"Fetching {artifact}") + res = await client.get(artifact, ssl=get_ssl_context()) + if res.status == 200: + logger.debug("Got 200. Reading...") + data = await res.read() + logger.debug(f"Read {len(data)} bytes") + res_zip = BytesIO(data) + else: + logger.fatal(f"Could not fetch from URL. {await res.text()}") + + # Check to make sure we got the file + if res_zip is None: + logger.fatal(f"Could not fetch {artifact}") + return + + # If plugin is installed, uninstall it + if isInstalled: + try: + logger.debug("Uninstalling existing plugin...") + await self.uninstall_plugin(name) + except: + logger.error(f"Plugin {name} could not be uninstalled.") + + # Install the plugin + logger.debug("Unzipping...") + ret = self._unzip_to_plugin_dir(res_zip, name, hash) + if ret: + plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None + plugin_dir = path.join(self.plugin_path, plugin_folder) + ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir) + if ret: + logger.info(f"Installed {name} (Version: {version})") + if name in self.loader.plugins: + self.loader.plugins[name].stop() + self.loader.plugins.pop(name, None) + await sleep(1) + if not isInstalled: + current_plugin_order = self.settings.getSetting("pluginOrder") + current_plugin_order.append(name) + self.settings.setSetting("pluginOrder", current_plugin_order) + logger.debug("Plugin %s was added to the pluginOrder setting", name) + self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder) + else: + logger.fatal(f"Failed Downloading Remote Binaries") + else: + logger.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})") + if self.loader.watcher: + self.loader.watcher.disabled = False + + async def request_plugin_install(self, artifact: str, name: str, version: str, hash: str, install_type: PluginInstallType): + request_id = str(time()) + self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash) + tab = await get_gamepadui_tab() + await tab.open_websocket() + await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}', {install_type})") + + async def request_multiple_plugin_installs(self, requests: List[PluginInstallRequest]): + request_id = str(time()) + self.install_requests[request_id] = [PluginInstallContext(req['artifact'], req['name'], req['version'], req['hash']) for req in requests] + js_requests_parameter = ','.join([ + f"{{ name: '{req['name']}', version: '{req['version']}', hash: '{req['hash']}', install_type: {req['install_type']}}}" for req in requests + ]) + + tab = await get_gamepadui_tab() + await tab.open_websocket() + await tab.evaluate_js(f"DeckyPluginLoader.addMultiplePluginsInstallPrompt('{request_id}', [{js_requests_parameter}])") + + async def confirm_plugin_install(self, request_id: str): + requestOrRequests = self.install_requests.pop(request_id) + if isinstance(requestOrRequests, list): + [await self._install(req.artifact, req.name, req.version, req.hash) for req in requestOrRequests] + else: + await self._install(requestOrRequests.artifact, requestOrRequests.name, requestOrRequests.version, requestOrRequests.hash) + + def cancel_plugin_install(self, request_id: str): + self.install_requests.pop(request_id) + + def cleanup_plugin_settings(self, name: str): + """Removes any settings related to a plugin. Propably called when a plugin is uninstalled. + + Args: + name (string): The name of the plugin + """ + hidden_plugins = self.settings.getSetting("hiddenPlugins", []) + if name in hidden_plugins: + hidden_plugins.remove(name) + self.settings.setSetting("hiddenPlugins", hidden_plugins) + + + plugin_order = self.settings.getSetting("pluginOrder", []) + + if name in plugin_order: + plugin_order.remove(name) + self.settings.setSetting("pluginOrder", plugin_order) + + logger.debug("Removed any settings for plugin %s", name) diff --git a/backend/src/customtypes.py b/backend/src/customtypes.py new file mode 100644 index 00000000..84ebc235 --- /dev/null +++ b/backend/src/customtypes.py @@ -0,0 +1,6 @@ +from enum import Enum + +class UserType(Enum): + HOST_USER = 1 + EFFECTIVE_USER = 2 + ROOT = 3 \ No newline at end of file diff --git a/backend/src/helpers.py b/backend/src/helpers.py new file mode 100644 index 00000000..f8796bd8 --- /dev/null +++ b/backend/src/helpers.py @@ -0,0 +1,153 @@ +import re +import ssl +import uuid +import os +import subprocess +from hashlib import sha256 +from io import BytesIO + +import certifi +from aiohttp.web import Request, Response, middleware +from aiohttp.typedefs import Handler +from aiohttp import ClientSession +from . import localplatform +from .customtypes import UserType +from logging import getLogger + +REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service" + +# global vars +csrf_token = str(uuid.uuid4()) +ssl_ctx = ssl.create_default_context(cafile=certifi.where()) + +assets_regex = re.compile("^/plugins/.*/assets/.*") +frontend_regex = re.compile("^/frontend/.*") +logger = getLogger("Main") + +def get_ssl_context(): + return ssl_ctx + +def get_csrf_token(): + return csrf_token + +@middleware +async def csrf_middleware(request: Request, handler: Handler): + if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): + return await handler(request) + return Response(text='Forbidden', status=403) + +# Get the default homebrew path unless a home_path is specified. home_path argument is deprecated +def get_homebrew_path() -> str: + return localplatform.get_unprivileged_path() + +# Recursively create path and chown as user +def mkdir_as_user(path: str): + path = os.path.realpath(path) + os.makedirs(path, exist_ok=True) + localplatform.chown(path) + +# Fetches the version of loader +def get_loader_version() -> str: + try: + with open(os.path.join(os.getcwd(), ".loader.version"), "r", encoding="utf-8") as version_file: + return version_file.readline().strip() + except Exception as e: + logger.warn(f"Failed to execute get_loader_version(): {str(e)}") + return "unknown" + +# returns the appropriate system python paths +def get_system_pythonpaths() -> list[str]: + try: + # run as normal normal user if on linux to also include user python paths + proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"], + # TODO make this less insane + capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # type: ignore + return [x.strip() for x in proc.stdout.decode().strip().split("\n")] + except Exception as e: + logger.warn(f"Failed to execute get_system_pythonpaths(): {str(e)}") + return [] + +# Download Remote Binaries to local Plugin +async def download_remote_binary_to_path(url: str, binHash: str, path: str) -> bool: + rv = False + try: + if os.access(os.path.dirname(path), os.W_OK): + async with ClientSession() as client: + res = await client.get(url, ssl=get_ssl_context()) + if res.status == 200: + data = BytesIO(await res.read()) + remoteHash = sha256(data.getbuffer()).hexdigest() + if binHash == remoteHash: + data.seek(0) + with open(path, 'wb') as f: + f.write(data.getbuffer()) + rv = True + else: + raise Exception(f"Fatal Error: Hash Mismatch for remote binary {path}@{url}") + else: + rv = False + except: + rv = False + + return rv + +# Deprecated +def set_user(): + pass + +# Deprecated +def set_user_group() -> str: + return get_user_group() + +######### +# Below is legacy code, provided for backwards compatibility. This will break on windows +######### + +# Get the user id hosting the plugin loader +def get_user_id() -> int: + return localplatform.localplatform._get_user_id() # pyright: ignore [reportPrivateUsage] + +# Get the user hosting the plugin loader +def get_user() -> str: + return localplatform.localplatform._get_user() # pyright: ignore [reportPrivateUsage] + +# Get the effective user id of the running process +def get_effective_user_id() -> int: + return localplatform.localplatform._get_effective_user_id() # pyright: ignore [reportPrivateUsage] + +# Get the effective user of the running process +def get_effective_user() -> str: + return localplatform.localplatform._get_effective_user() # pyright: ignore [reportPrivateUsage] + +# Get the effective user group id of the running process +def get_effective_user_group_id() -> int: + return localplatform.localplatform._get_effective_user_group_id() # pyright: ignore [reportPrivateUsage] + +# Get the effective user group of the running process +def get_effective_user_group() -> str: + return localplatform.localplatform._get_effective_user_group() # pyright: ignore [reportPrivateUsage] + +# Get the user owner of the given file path. +def get_user_owner(file_path: str) -> str: + return localplatform.localplatform._get_user_owner(file_path) # pyright: ignore [reportPrivateUsage] + +# Get the user group of the given file path, or the user group hosting the plugin loader +def get_user_group(file_path: str | None = None) -> str: + return localplatform.localplatform._get_user_group(file_path) # pyright: ignore [reportPrivateUsage] + +# Get the group id of the user hosting the plugin loader +def get_user_group_id() -> int: + return localplatform.localplatform._get_user_group_id() # pyright: ignore [reportPrivateUsage] + +# Get the default home path unless a user is specified +def get_home_path(username: str | None = None) -> str: + return localplatform.get_home_path(UserType.ROOT if username == "root" else UserType.HOST_USER) + +async def is_systemd_unit_active(unit_name: str) -> bool: + return await localplatform.service_active(unit_name) + +async def stop_systemd_unit(unit_name: str) -> bool: + return await localplatform.service_stop(unit_name) + +async def start_systemd_unit(unit_name: str) -> bool: + return await localplatform.service_start(unit_name) diff --git a/backend/src/injector.py b/backend/src/injector.py new file mode 100644 index 00000000..a217f689 --- /dev/null +++ b/backend/src/injector.py @@ -0,0 +1,438 @@ +# Injector code from https://github.com/SteamDeckHomebrew/steamdeck-ui-inject. More info on how it works there. + +from asyncio import sleep +from logging import getLogger +from typing import Any, Callable, List, TypedDict, Dict + +from aiohttp import ClientSession +from aiohttp.client_exceptions import ClientConnectorError, ClientOSError +from asyncio.exceptions import TimeoutError +import uuid + +BASE_ADDRESS = "http://localhost:8080" + +logger = getLogger("Injector") + +class _TabResponse(TypedDict): + title: str + id: str + url: str + webSocketDebuggerUrl: str + +class Tab: + cmd_id = 0 + + def __init__(self, res: _TabResponse) -> None: + self.title: str = res["title"] + self.id: str = res["id"] + self.url: str = res["url"] + self.ws_url: str = res["webSocketDebuggerUrl"] + + self.websocket = None + self.client = None + + async def open_websocket(self): + self.client = ClientSession() + self.websocket = await self.client.ws_connect(self.ws_url) # type: ignore + + async def close_websocket(self): + if self.websocket: + await self.websocket.close() + if self.client: + await self.client.close() + + async def listen_for_message(self): + if self.websocket: + async for message in self.websocket: + data = message.json() + yield data + logger.warn(f"The Tab {self.title} socket has been disconnected while listening for messages.") + await self.close_websocket() + + async def _send_devtools_cmd(self, dc: Dict[str, Any], receive: bool = True): + if self.websocket: + self.cmd_id += 1 + dc["id"] = self.cmd_id + await self.websocket.send_json(dc) + if receive: + async for msg in self.listen_for_message(): + if "id" in msg and msg["id"] == dc["id"]: + return msg + return None + raise RuntimeError("Websocket not opened") + + async def evaluate_js(self, js: str, run_async: bool | None = False, manage_socket: bool | None = True, get_result: bool = True): + try: + if manage_socket: + await self.open_websocket() + + res = await self._send_devtools_cmd({ + "method": "Runtime.evaluate", + "params": { + "expression": js, + "userGesture": True, + "awaitPromise": run_async + } + }, get_result) + + finally: + if manage_socket: + await self.close_websocket() + return res + + async def has_global_var(self, var_name: str, manage_socket: bool = True): + res = await self.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False, manage_socket) + assert res is not None + + if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: + return False + + return res["result"]["result"]["value"] + + async def close(self, manage_socket: bool = True): + try: + if manage_socket: + await self.open_websocket() + + res = await self._send_devtools_cmd({ + "method": "Page.close", + }, False) + + finally: + if manage_socket: + await self.close_websocket() + return res + + async def enable(self): + """ + Enables page domain notifications. + """ + await self._send_devtools_cmd({ + "method": "Page.enable", + }, False) + + async def disable(self): + """ + Disables page domain notifications. + """ + await self._send_devtools_cmd({ + "method": "Page.disable", + }, False) + + async def refresh(self, manage_socket: bool = True): + try: + if manage_socket: + await self.open_websocket() + + await self._send_devtools_cmd({ + "method": "Page.reload", + }, False) + + finally: + if manage_socket: + await self.close_websocket() + + return + async def reload_and_evaluate(self, js: str, manage_socket: bool = True): + """ + Reloads the current tab, with JS to run on load via debugger + """ + try: + if manage_socket: + await self.open_websocket() + + await self._send_devtools_cmd({ + "method": "Debugger.enable" + }, True) + + await self._send_devtools_cmd({ + "method": "Runtime.evaluate", + "params": { + "expression": "location.reload();", + "userGesture": True, + "awaitPromise": False + } + }, False) + + breakpoint_res = await self._send_devtools_cmd({ + "method": "Debugger.setInstrumentationBreakpoint", + "params": { + "instrumentation": "beforeScriptExecution" + } + }, True) + + assert breakpoint_res is not None + + logger.info(breakpoint_res) + + # Page finishes loading when breakpoint hits + + for _ in range(20): + # this works around 1/5 of the time, so just send it 8 times. + # the js accounts for being injected multiple times allowing only one instance to run at a time anyway + await self._send_devtools_cmd({ + "method": "Runtime.evaluate", + "params": { + "expression": js, + "userGesture": True, + "awaitPromise": False + } + }, False) + + await self._send_devtools_cmd({ + "method": "Debugger.removeBreakpoint", + "params": { + "breakpointId": breakpoint_res["result"]["breakpointId"] + } + }, False) + + for _ in range(4): + await self._send_devtools_cmd({ + "method": "Debugger.resume" + }, False) + + await self._send_devtools_cmd({ + "method": "Debugger.disable" + }, True) + + finally: + if manage_socket: + await self.close_websocket() + return + + async def add_script_to_evaluate_on_new_document(self, js: str, add_dom_wrapper: bool = True, manage_socket: bool = True, get_result: bool = True): + """ + How the underlying call functions is not particularly clear from the devtools docs, so stealing puppeteer's description: + + Adds a function which would be invoked in one of the following scenarios: + * whenever the page is navigated + * whenever the child frame is attached or navigated. In this case, the + function is invoked in the context of the newly attached frame. + + The function is invoked after the document was created but before any of + its scripts were run. This is useful to amend the JavaScript environment, + e.g. to seed `Math.random`. + + Parameters + ---------- + js : str + The script to evaluate on new document + add_dom_wrapper : bool + True to wrap the script in a wait for the 'DOMContentLoaded' event. + DOM will usually not exist when this execution happens, + so it is necessary to delay til DOM is loaded if you are modifying it + manage_socket : bool + True to have this function handle opening/closing the websocket for this tab + get_result : bool + True to wait for the result of this call + + Returns + ------- + int or None + The identifier of the script added, used to remove it later. + (see remove_script_to_evaluate_on_new_document below) + None is returned if `get_result` is False + """ + try: + + wrappedjs = """ + function scriptFunc() { + {js} + } + if (document.readyState === 'loading') { + addEventListener('DOMContentLoaded', () => { + scriptFunc(); + }); + } else { + scriptFunc(); + } + """.format(js=js) if add_dom_wrapper else js + + if manage_socket: + await self.open_websocket() + + res = await self._send_devtools_cmd({ + "method": "Page.addScriptToEvaluateOnNewDocument", + "params": { + "source": wrappedjs + } + }, get_result) + + finally: + if manage_socket: + await self.close_websocket() + return res + + async def remove_script_to_evaluate_on_new_document(self, script_id: str, manage_socket: bool = True): + """ + Removes a script from a page that was added with `add_script_to_evaluate_on_new_document` + + Parameters + ---------- + script_id : int + The identifier of the script to remove (returned from `add_script_to_evaluate_on_new_document`) + """ + + try: + if manage_socket: + await self.open_websocket() + + await self._send_devtools_cmd({ + "method": "Page.removeScriptToEvaluateOnNewDocument", + "params": { + "identifier": script_id + } + }, False) + + finally: + if manage_socket: + await self.close_websocket() + + async def has_element(self, element_name: str, manage_socket: bool = True): + res = await self.evaluate_js(f"document.getElementById('{element_name}') != null", False, manage_socket) + assert res is not None + + if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: + return False + + return res["result"]["result"]["value"] + + async def inject_css(self, style: str, manage_socket: bool = True): + try: + css_id = str(uuid.uuid4()) + + result = await self.evaluate_js( + f""" + (function() {{ + const style = document.createElement('style'); + style.id = "{css_id}"; + document.head.append(style); + style.textContent = `{style}`; + }})() + """, False, manage_socket) + + assert result is not None + + if "exceptionDetails" in result["result"]: + return { + "success": False, + "result": result["result"] + } + + return { + "success": True, + "result": css_id + } + except Exception as e: + return { + "success": False, + "result": e + } + + async def remove_css(self, css_id: str, manage_socket: bool = True): + try: + result = await self.evaluate_js( + f""" + (function() {{ + let style = document.getElementById("{css_id}"); + + if (style.nodeName.toLowerCase() == 'style') + style.parentNode.removeChild(style); + }})() + """, False, manage_socket) + + assert result is not None + + if "exceptionDetails" in result["result"]: + return { + "success": False, + "result": result + } + + return { + "success": True + } + except Exception as e: + return { + "success": False, + "result": e + } + + async def get_steam_resource(self, url: str): + res = await self.evaluate_js(f'(async function test() {{ return await (await fetch("{url}")).text() }})()', True) + assert res is not None + return res["result"]["result"]["value"] + + def __repr__(self): + return self.title + + +async def get_tabs() -> List[Tab]: + res = {} + + na = False + while True: + try: + async with ClientSession() as web: + res = await web.get(f"{BASE_ADDRESS}/json", timeout=3) + except ClientConnectorError: + if not na: + logger.debug("Steam isn't available yet. Wait for a moment...") + na = True + await sleep(5) + except ClientOSError: + logger.warn(f"The request to {BASE_ADDRESS}/json was reset") + await sleep(1) + except TimeoutError: + logger.warn(f"The request to {BASE_ADDRESS}/json timed out") + await sleep(1) + else: + break + + if res.status == 200: + r = await res.json() + return [Tab(i) for i in r] + else: + raise Exception(f"/json did not return 200. {await res.text()}") + + +async def get_tab(tab_name: str) -> Tab: + tabs = await get_tabs() + tab = next((i for i in tabs if i.title == tab_name), None) + if not tab: + raise ValueError(f"Tab {tab_name} not found") + return tab + +async def get_tab_lambda(test: Callable[[Tab], bool]) -> Tab: + tabs = await get_tabs() + tab = next((i for i in tabs if test(i)), None) + if not tab: + raise ValueError(f"Tab not found by lambda") + return tab + +SHARED_CTX_NAMES = ["SharedJSContext", "Steam Shared Context presented by Valve™", "Steam", "SP"] +CLOSEABLE_URLS = ["about:blank", "data:text/html,%3Cbody%3E%3C%2Fbody%3E"] # Closing anything other than these *really* likes to crash Steam +DO_NOT_CLOSE_URL = "Valve Steam Gamepad/default" # Steam Big Picture Mode tab + +def tab_is_gamepadui(t: Tab) -> bool: + return "https://steamloopback.host/routes/" in t.url and t.title in SHARED_CTX_NAMES + +async def get_gamepadui_tab() -> Tab: + tabs = await get_tabs() + tab = next((i for i in tabs if tab_is_gamepadui(i)), None) + if not tab: + raise ValueError(f"GamepadUI Tab not found") + return tab + +async def inject_to_tab(tab_name: str, js: str, run_async: bool = False): + tab = await get_tab(tab_name) + + return await tab.evaluate_js(js, run_async) + +async def close_old_tabs(): + tabs = await get_tabs() + for t in tabs: + if not t.title or (t.title not in SHARED_CTX_NAMES and any(url in t.url for url in CLOSEABLE_URLS) and DO_NOT_CLOSE_URL not in t.url): + logger.debug("Closing tab: " + getattr(t, "title", "Untitled")) + await t.close() + await sleep(0.5) diff --git a/backend/src/legacy/library.js b/backend/src/legacy/library.js new file mode 100644 index 00000000..17f4e46f --- /dev/null +++ b/backend/src/legacy/library.js @@ -0,0 +1,84 @@ +class PluginEventTarget extends EventTarget { } +method_call_ev_target = new PluginEventTarget(); + +window.addEventListener("message", function(evt) { + let ev = new Event(evt.data.call_id); + ev.data = evt.data.result; + method_call_ev_target.dispatchEvent(ev); +}, false); + +async function call_server_method(method_name, arg_object={}) { + const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text()); + const response = await fetch(`http://127.0.0.1:1337/methods/${method_name}`, { + method: 'POST', + credentials: "include", + headers: { + 'Content-Type': 'application/json', + Authentication: token + }, + body: JSON.stringify(arg_object), + }); + + const dta = await response.json(); + if (!dta.success) throw dta.result; + return dta.result; +} + +// Source: https://stackoverflow.com/a/2117523 Thanks! +function uuidv4() { + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); +} + +async function fetch_nocors(url, request={}) { + let args = { method: "POST", headers: {}, body: "" }; + request = {...args, ...request}; + request.url = url; + request.data = request.body; + delete request.body; //maintain api-compatibility with fetch + return await call_server_method("http_request", request); +} + +async function call_plugin_method(method_name, arg_object={}) { + if (plugin_name == undefined) + throw new Error("Plugin methods can only be called from inside plugins (duh)"); + const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text()); + const response = await fetch(`http://127.0.0.1:1337/plugins/${plugin_name}/methods/${method_name}`, { + method: 'POST', + credentials: "include", + headers: { + 'Content-Type': 'application/json', + Authentication: token + }, + body: JSON.stringify({ + args: arg_object, + }), + }); + + const dta = await response.json(); + if (!dta.success) throw dta.result; + return dta.result; +} + +async function execute_in_tab(tab, run_async, code) { + return await call_server_method("execute_in_tab", { + 'tab': tab, + 'run_async': run_async, + 'code': code + }); +} + +async function inject_css_into_tab(tab, style) { + return await call_server_method("inject_css_into_tab", { + 'tab': tab, + 'style': style + }); +} + +async function remove_css_from_tab(tab, css_id) { + return await call_server_method("remove_css_from_tab", { + 'tab': tab, + 'css_id': css_id + }); +} \ No newline at end of file diff --git a/backend/src/loader.py b/backend/src/loader.py new file mode 100644 index 00000000..684570f7 --- /dev/null +++ b/backend/src/loader.py @@ -0,0 +1,238 @@ +from __future__ import annotations +from asyncio import AbstractEventLoop, Queue, sleep +from json.decoder import JSONDecodeError +from logging import getLogger +from os import listdir, path +from pathlib import Path +from traceback import print_exc +from typing import Any, Tuple + +from aiohttp import web +from os.path import exists +from watchdog.events import RegexMatchingEventHandler, DirCreatedEvent, DirModifiedEvent, FileCreatedEvent, FileModifiedEvent # type: ignore +from watchdog.observers import Observer # type: ignore + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .main import PluginManager + +from .injector import get_tab, get_gamepadui_tab +from .plugin import PluginWrapper + +Plugins = dict[str, PluginWrapper] +ReloadQueue = Queue[Tuple[str, str, bool | None] | Tuple[str, str]] + +class FileChangeHandler(RegexMatchingEventHandler): + def __init__(self, queue: ReloadQueue, plugin_path: str) -> None: + super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) # type: ignore + self.logger = getLogger("file-watcher") + self.plugin_path = plugin_path + self.queue = queue + self.disabled = True + + def maybe_reload(self, src_path: str): + if self.disabled: + return + plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0] + if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")): + self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True)) + + def on_created(self, event: DirCreatedEvent | FileCreatedEvent): + src_path = event.src_path + if "__pycache__" in src_path: + return + + # check to make sure this isn't a directory + if path.isdir(src_path): + return + + # get the directory name of the plugin so that we can find its "main.py" and reload it; the + # file that changed is not necessarily the one that needs to be reloaded + self.logger.debug(f"file created: {src_path}") + self.maybe_reload(src_path) + + def on_modified(self, event: DirModifiedEvent | FileModifiedEvent): + src_path = event.src_path + if "__pycache__" in src_path: + return + + # check to make sure this isn't a directory + if path.isdir(src_path): + return + + # get the directory name of the plugin so that we can find its "main.py" and reload it; the + # file that changed is not necessarily the one that needs to be reloaded + self.logger.debug(f"file modified: {src_path}") + self.maybe_reload(src_path) + +class Loader: + def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None: + self.loop = loop + self.logger = getLogger("Loader") + self.plugin_path = plugin_path + self.logger.info(f"plugin_path: {self.plugin_path}") + self.plugins: Plugins = {} + self.watcher = None + self.live_reload = live_reload + self.reload_queue: ReloadQueue = Queue() + self.loop.create_task(self.handle_reloads()) + + if live_reload: + self.observer = Observer() + self.watcher = FileChangeHandler(self.reload_queue, plugin_path) + self.observer.schedule(self.watcher, self.plugin_path, recursive=True) # type: ignore + self.observer.start() + self.loop.create_task(self.enable_reload_wait()) + + server_instance.web_app.add_routes([ + 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/{plugin_name}/frontend_bundle", self.handle_frontend_bundle), + web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call), + web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets), + web.post("/plugins/{plugin_name}/reload", self.handle_backend_reload_request), + + # The following is legacy plugin code. + web.get("/plugins/load_main/{name}", self.load_plugin_main_view), + web.get("/plugins/plugin_resource/{name}/{path:.+}", self.handle_sub_route), + web.get("/steam_resource/{path:.+}", self.get_steam_resource) + ]) + + async def enable_reload_wait(self): + if self.live_reload: + await sleep(10) + if self.watcher: + self.logger.info("Hot reload enabled") + self.watcher.disabled = False + + async def handle_frontend_assets(self, request: web.Request): + file = path.join(path.dirname(__file__), "static", request.match_info["path"]) + + return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) + + async def handle_frontend_locales(self, request: web.Request): + req_lang = request.match_info["path"] + file = path.join(path.dirname(__file__), "locales", req_lang) + if exists(file): + return web.FileResponse(file, headers={"Cache-Control": "no-cache", "Content-Type": "application/json"}) + else: + self.logger.info(f"Language {req_lang} not available, returning an empty dictionary") + return web.json_response(data={}, headers={"Cache-Control": "no-cache"}) + + async def get_plugins(self, request: web.Request): + 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]) + + async def handle_plugin_frontend_assets(self, request: web.Request): + plugin = self.plugins[request.match_info["plugin_name"]] + file = path.join(self.plugin_path, plugin.plugin_directory, "dist/assets", request.match_info["path"]) + + return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) + + async def handle_frontend_bundle(self, request: web.Request): + plugin = self.plugins[request.match_info["plugin_name"]] + + with open(path.join(self.plugin_path, plugin.plugin_directory, "dist/index.js"), "r", encoding="utf-8") as bundle: + return web.Response(text=bundle.read(), content_type="application/javascript") + + def import_plugin(self, file: str, plugin_directory: str, refresh: bool | None = False, batch: bool | None = False): + try: + plugin = PluginWrapper(file, plugin_directory, self.plugin_path) + if plugin.name in self.plugins: + if not "debug" in plugin.flags and refresh: + self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded") + return + else: + self.plugins[plugin.name].stop() + self.plugins.pop(plugin.name, None) + if plugin.passive: + self.logger.info(f"Plugin {plugin.name} is passive") + self.plugins[plugin.name] = plugin.start() + self.logger.info(f"Loaded {plugin.name}") + if not batch: + self.loop.create_task(self.dispatch_plugin(plugin.name if not plugin.legacy else "$LEGACY_" + plugin.name, plugin.version)) + except Exception as e: + self.logger.error(f"Could not load {file}. {e}") + print_exc() + + async def dispatch_plugin(self, name: str, version: str | None): + gpui_tab = await get_gamepadui_tab() + await gpui_tab.evaluate_js(f"window.importDeckyPlugin('{name}', '{version}')") + + def import_plugins(self): + self.logger.info(f"import plugins from {self.plugin_path}") + + directories = [i for i in listdir(self.plugin_path) if path.isdir(path.join(self.plugin_path, i)) and path.isfile(path.join(self.plugin_path, i, "plugin.json"))] + for directory in directories: + self.logger.info(f"found plugin: {directory}") + self.import_plugin(path.join(self.plugin_path, directory, "main.py"), directory, False, True) + + async def handle_reloads(self): + while True: + args = await self.reload_queue.get() + self.import_plugin(*args) # type: ignore + + async def handle_plugin_method_call(self, request: web.Request): + res = {} + plugin = self.plugins[request.match_info["plugin_name"]] + method_name = request.match_info["method_name"] + try: + method_info = await request.json() + args: Any = method_info["args"] + except JSONDecodeError: + args = {} + try: + if method_name.startswith("_"): + raise RuntimeError("Tried to call private method") + res["result"] = await plugin.execute_method(method_name, args) + res["success"] = True + except Exception as e: + res["result"] = str(e) + res["success"] = False + return web.json_response(res) + + """ + The following methods are used to load legacy plugins, which are considered deprecated. + I made the choice to re-add them so that the first iteration/version of the react loader + can work as a drop-in replacement for the stable branch of the PluginLoader, so that we + can introduce it more smoothly and give people the chance to sample the new features even + without plugin support. They will be removed once legacy plugins are no longer relevant. + """ + async def load_plugin_main_view(self, request: web.Request): + plugin = self.plugins[request.match_info["name"]] + with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), "r", encoding="utf-8") as template: + template_data = template.read() + ret = f""" + + + + {template_data} + """ + return web.Response(text=ret, content_type="text/html") + + async def handle_sub_route(self, request: web.Request): + plugin = self.plugins[request.match_info["name"]] + route_path = request.match_info["path"] + self.logger.info(path) + ret = "" + file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path) + with open(file_path, "r", encoding="utf-8") as resource_data: + ret = resource_data.read() + + return web.Response(text=ret) + + async def get_steam_resource(self, request: web.Request): + tab = await get_tab("SP") + try: + return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html") + except Exception as e: + return web.Response(text=str(e), status=400) + + async def handle_backend_reload_request(self, request: web.Request): + plugin_name : str = request.match_info["plugin_name"] + plugin = self.plugins[plugin_name] + + await self.reload_queue.put((plugin.file, plugin.plugin_directory)) + + return web.Response(status=200) \ No newline at end of file diff --git a/backend/src/locales/bg-BG.json b/backend/src/locales/bg-BG.json new file mode 100644 index 00000000..b9c4d803 --- /dev/null +++ b/backend/src/locales/bg-BG.json @@ -0,0 +1,252 @@ +{ + "BranchSelect": { + "update_channel": { + "stable": "Стабилен", + "testing": "Тестване", + "label": "Канал за обновления", + "prerelease": "Предварителни издания" + } + }, + "Developer": { + "5secreload": "Презареждане след 5 секунди", + "disabling": "Изключване на React DevTools", + "enabling": "Включване на React DevTools" + }, + "DropdownMultiselect": { + "button": { + "back": "Назад" + } + }, + "FilePickerError": { + "errors": { + "unknown": "Възникна неизвестна грешка. Грешката в суров вид е: {{raw_error}}", + "file_not_found": "Посоченият път е неправилен. Проверете го и го въведете правилно.", + "perm_denied": "Нямате достъп до посочената папка. Проверете дали потребителят (deck на Steam Deck) има съответните правомощия за достъп до посочената папка/файл." + } + }, + "FilePickerIndex": { + "file": { + "select": "Избиране на този файл" + }, + "files": { + "all_files": "Всички файлове", + "file_type": "Файлов тип", + "show_hidden": "Показване на скритите файлове" + }, + "filter": { + "created_asce": "Дата на създаване (първо най-старите)", + "created_desc": "Дата на създаване (първо най-новите)", + "modified_asce": "Дата на промяна (първо най-старите)", + "modified_desc": "Дата на промяна (първо най-новите)", + "name_asce": "Я-А", + "name_desc": "А-Я", + "size_asce": "Размер (първо най-малките)", + "size_desc": "Размер (първо най-големите)" + }, + "folder": { + "label": "Папка", + "show_more": "Показване на още файлове", + "select": "Използване на тази папка" + } + }, + "MultiplePluginsInstallModal": { + "description": { + "install": "Инсталиране на {{name}} {{version}}", + "reinstall": "Преинсталиране на {{name}} {{version}}", + "update": "Обновяване на {{name}} до {{version}}" + }, + "ok_button": { + "idle": "Потвърждаване", + "loading": "В процес на работа" + }, + "title": { + "mixed_one": "Промяна на {{count}} добавка", + "mixed_other": "Промяна на {{count}} добавки", + "update_one": "Обновяване на 1 добавка", + "update_other": "Обновяване на {{count}} добавки", + "install_one": "Инсталиране на 1 добавка", + "install_other": "Инсталиране на {{count}} добавки", + "reinstall_one": "Преинсталиране на 1 добавка", + "reinstall_other": "Преинсталиране на {{count}} добавки" + }, + "confirm": "Наистина ли искате да направите следните промени?" + }, + "PluginCard": { + "plugin_full_access": "Тази добавка има пълен достъп до Вашия Steam Deck.", + "plugin_install": "Инсталиране", + "plugin_no_desc": "Няма описание.", + "plugin_version_label": "Версия на добавката" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Инсталиране", + "desc": "Наистина ли искате да инсталирате {{artifact}} {{version}}?", + "title": "Инсталиране на {{artifact}}", + "button_processing": "Инсталиране" + }, + "reinstall": { + "button_idle": "Преинсталиране", + "button_processing": "Преинсталиране", + "desc": "Наистина ли искате да преинсталирате {{artifact}} {{version}}?", + "title": "Преинсталиране на {{artifact}}" + }, + "update": { + "button_idle": "Обновяване", + "title": "Обновяване на {{artifact}}", + "button_processing": "Обновяване", + "desc": "Наистина ли искате да обновите {{artifact}} {{version}}?" + }, + "no_hash": "Тази добавка няма хеш. Инсталирате я на свой собствен риск." + }, + "PluginListIndex": { + "hide": "Бърз достъп: Скриване", + "no_plugin": "Няма инсталирани добавки!", + "plugin_actions": "Действия с добавката", + "reinstall": "Преинсталиране", + "uninstall": "Деинсталиране", + "update_to": "Обновяване до {{name}}", + "reload": "Презареждане", + "show": "Бърз достъп: Показване", + "update_all_one": "Обновяване на 1 добавка", + "update_all_other": "Обновяване на {{count}} добавки" + }, + "PluginListLabel": { + "hidden": "Скрито от менюто за бърз достъп" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Грешка", + "plugin_load_error": { + "message": "Грешка при зареждането на добавката {{name}}", + "toast": "Грешка при зареждането на {{name}}" + }, + "plugin_uninstall": { + "button": "Деинсталиране", + "desc": "Наистина ли искате да деинсталирате {{name}}?", + "title": "Деинсталиране на {{name}}" + }, + "plugin_update_one": "Има налично обновление за 1 добавка!", + "plugin_update_other": "Има налични обновления за {{count}} добавки!", + "decky_update_available": "Има налично обновление до {{tag_name}}!", + "plugin_error_uninstall": "Зареждането на {{name}} предизвика грешка, както се вижда по-горе. Това обикновено означава, че добавката изисква обновяване на новата версия на SteamUI. Проверете дали има обновление или изберете да я премахнете в настройките на Decky, в раздела с добавките." + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Разрешаване на достъп без удостоверяване до дебъгера на CEF на всеки от Вашата мрежа", + "label": "Разрешаване на отдалеченото дебъгване на CEF" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Отваряне на конзолата", + "label": "Конзола на CEF", + "desc": "Отваря конзолата на CEF. Това има смисъл единствено за дебъгване. Нещата тук може да са опасни и трябва да бъдат използвани само ако Вие сте разработчик на добавка, или получавате насоки от такъв." + }, + "header": "Други", + "react_devtools": { + "ip_label": "IP", + "label": "Включване на React DevTools", + "desc": "Включва свързването към компютър, на който работи React DevTools. Промяната на тази настройка ще презареди Steam. Задайте IP адреса преди да включите това." + }, + "third_party_plugins": { + "button_install": "Инсталиране", + "button_zip": "Разглеждане", + "header": "Добавки от външен източник", + "label_desc": "Адрес", + "label_zip": "Инсталиране на добавка от файл ZIP", + "label_url": "Инсталиране на добавка от адрес в Интернет" + }, + "valve_internal": { + "desc2": "Не пипайте нищо в това меню, освен ако не знаете какво правите.", + "label": "Включване на вътрешното меню на Valve", + "desc1": "Включва вътрешното меню за разработчици на Valve." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Версия на Decky", + "header": "Относно" + }, + "developer_mode": { + "label": "Режим за разработчици" + }, + "notifications": { + "decky_updates_label": "Има налично обновление на Decky", + "header": "Известия", + "plugin_updates_label": "Има налични обновления на добавките" + }, + "other": { + "header": "Други" + }, + "updates": { + "header": "Обновления" + }, + "beta": { + "header": "Участие в бета-версии" + } + }, + "SettingsIndex": { + "developer_title": "Разработчик", + "general_title": "Общи", + "plugins_title": "Добавки" + }, + "Store": { + "store_contrib": { + "label": "Допринасяне", + "desc": "Ако искате да допринесете към магазина за добавки на Decky, разгледайте хранилището SteamDeckHomebrew/decky-plugin-template в GitHub. Може да намерите информация относно разработката и разпространението във файла README." + }, + "store_filter": { + "label": "Филтър", + "label_def": "Всички" + }, + "store_search": { + "label": "Търсене" + }, + "store_sort": { + "label": "Подредба", + "label_def": "Последно обновление (първо най-новите)" + }, + "store_source": { + "label": "Изходен код", + "desc": "Целият изходен код е наличен в хранилището SteamDeckHomebrew/decky-plugin-database в GitHub." + }, + "store_tabs": { + "about": "Относно", + "alph_asce": "По азбучен ред (Я -> А)", + "alph_desc": "По азбучен ред (А -> Я)", + "title": "Разглеждане" + }, + "store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!" + }, + "StoreSelect": { + "custom_store": { + "label": "Персонализиран магазин", + "url_label": "Адрес" + }, + "store_channel": { + "custom": "Персонализиран", + "default": "По подразбиране", + "label": "Канал за магазина", + "testing": "Тестване" + } + }, + "Updater": { + "decky_updates": "Обновления на Decky", + "patch_notes_desc": "Бележки за промените", + "updates": { + "check_button": "Проверка за обновления", + "checking": "Проверяване", + "cur_version": "Текуща версия: {{ver}}", + "label": "Обновления", + "lat_version": "Използвате най-новата версия: {{ver}}", + "reloading": "Презареждане", + "updating": "Обновяване", + "install_button": "Инсталиране на обновлението" + }, + "no_patch_notes_desc": "няма бележки за промените в тази версия" + }, + "PluginView": { + "hidden_one": "1 добавка е скрита от този списък", + "hidden_other": "{{count}} добавки са скрити от този списък" + } +} diff --git a/backend/src/locales/cs-CZ.json b/backend/src/locales/cs-CZ.json new file mode 100644 index 00000000..74b7230c --- /dev/null +++ b/backend/src/locales/cs-CZ.json @@ -0,0 +1,267 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "Aktualizační kanál", + "prerelease": "Předběžná vydání", + "stable": "Stabilní", + "testing": "Testování" + } + }, + "Developer": { + "disabling": "Vypínám React DevTools", + "enabling": "Zapínám React DevTools", + "5secreload": "Znovu načtení za 5 vteřin" + }, + "FilePickerIndex": { + "folder": { + "select": "Použít tuto složku", + "label": "Složka", + "show_more": "Zobrazit více souborů" + }, + "filter": { + "created_asce": "Vytvořeno (Nejstarší)", + "created_desc": "Vytvořeno (Nejnovější)", + "modified_asce": "Upraveno (Nejstarší)", + "modified_desc": "Upraveno (Nejnovější)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Velikost (Nejmenší)", + "size_desc": "Velikost (Největší)" + }, + "files": { + "show_hidden": "Zobrazit skryté soubory", + "all_files": "Všechny soubory", + "file_type": "Typ souboru" + }, + "file": { + "select": "Vybrat tento soubor" + } + }, + "PluginView": { + "hidden_one": "1 plugin je v tomto seznamu skrytý", + "hidden_few": "{{count}} pluginů je v tomto seznamu skryto", + "hidden_other": "{{count}} pluginů je v tomto seznamu skryto" + }, + "PluginListLabel": { + "hidden": "Skryto z nabídky rychlého přístupu" + }, + "PluginCard": { + "plugin_full_access": "Tento plugin má plný přístup k vašemu Steam Decku.", + "plugin_install": "Instalovat", + "plugin_no_desc": "Nebyl uveden žádný popis.", + "plugin_version_label": "Verze pluginu" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalovat", + "button_processing": "Instalování", + "title": "Instalovat {{artifact}}", + "desc": "Jste si jisti, že chcete nainstalovat {{artifact}} {{version}}?" + }, + "no_hash": "Tento plugin nemá hash, instalujete jej na vlastní nebezpečí.", + "reinstall": { + "button_idle": "Přeinstalovat", + "button_processing": "Přeinstalování", + "title": "Přeinstalovat {{artifact}}", + "desc": "Jste si jisti, že chcete přeinstalovat {{artifact}} {{version}}?" + }, + "update": { + "button_idle": "Aktualizovat", + "button_processing": "Aktualizování", + "desc": "Jste si jisti, že chcete aktualizovat {{artifact}} {{version}}?", + "title": "Aktualizovat {{artifact}}" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Upravit {{count}} plugin", + "mixed_few": "Upravit {{count}} pluginů", + "mixed_other": "Upravit {{count}} pluginů", + "reinstall_one": "Přeinstalovat 1 plugin", + "reinstall_few": "Přeinstalovat {{count}} pluginů", + "reinstall_other": "Přeinstalovat {{count}} pluginů", + "install_one": "Instalovat 1 plugin", + "install_few": "Instalovat {{count}} pluginů", + "install_other": "Instalovat {{count}} pluginů", + "update_one": "Aktualizovat 1 plugin", + "update_few": "Aktualizovat {{count}} pluginů", + "update_other": "Aktualizovat {{count}} pluginů" + }, + "ok_button": { + "idle": "Potvrdit", + "loading": "Probíhá" + }, + "description": { + "install": "Instalovat {{name}} {{version}}", + "update": "Aktualizovat {{name}} na {{version}}", + "reinstall": "Přeinstalovat {{name}} {{version}}" + }, + "confirm": "Jste si jisti, že chcete udělat následující úpravy?" + }, + "PluginListIndex": { + "no_plugin": "Nejsou nainstalovány žádné pluginy!", + "plugin_actions": "Akce pluginu", + "reinstall": "Přeinstalovat", + "reload": "Znovu načíst", + "uninstall": "Odinstalovat", + "update_to": "Aktualizovat na {{name}}", + "show": "Rychlý přístup: Zobrazit", + "hide": "Rychlý přístup: Skrýt", + "update_all_one": "Aktualizovat 1 plugin", + "update_all_few": "Aktualizovat {{count}} pluginů", + "update_all_other": "Aktualizovat {{count}} pluginů" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Aktualizace na {{tag_name}} dostupná!", + "error": "Chyba", + "plugin_load_error": { + "message": "Chyba při načítání pluginu {{name}}", + "toast": "Chyba při načítání {{name}}" + }, + "plugin_uninstall": { + "button": "Odinstalovat", + "desc": "Opravdu chcete odinstalovat {{name}}?", + "title": "Odinstalovat {{name}}" + }, + "plugin_update_one": "Je dostupná aktualizace pro 1 plugin!", + "plugin_update_few": "Jsou dostupné aktualizace pro {{count}} pluginů!", + "plugin_update_other": "Jsou dostupné aktualizace pro {{count}} pluginů!", + "plugin_error_uninstall": "Načítání {{name}} způsobilo chybu uvedenou výše. To obvykle znamená, že plugin vyžaduje aktualizaci SteamUI. Zkontrolujte, zda je aktualizace k dispozici, nebo zvažte odstranění pluginu v nastavení Decky v sekci Pluginy." + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Otevřít konzoli", + "label": "CEF konzole", + "desc": "Otevře CEF konzoli. Užitečné pouze pro účely ladění. Věci zde jsou potenciálně nebezpečné a měly by být používány pouze v případě, že jste vývojář pluginů, nebo vás sem nějaký nasměroval." + }, + "header": "Ostatní", + "react_devtools": { + "desc": "Umožňuje připojení k počítači, na kterém běží React DevTools. Změnou tohoto nastavení se znovu načte Steam. Před povolením nastavte IP adresu.", + "ip_label": "IP adresa", + "label": "Zapnout React DevTools" + }, + "third_party_plugins": { + "button_install": "Instalovat", + "button_zip": "Procházet", + "header": "Pluginy třetí strany", + "label_desc": "URL", + "label_url": "Instalovat plugin z URL", + "label_zip": "Instalovat plugin ze ZIP souboru" + }, + "valve_internal": { + "desc1": "Zapíná interní vývojářské menu Valve.", + "desc2": "Nedotýkejte se ničeho v této nabídce, pokud nevíte, co děláte.", + "label": "Zapnout Valve Internal" + } + }, + "RemoteDebugging": { + "remote_cef": { + "label": "Povolit vzdálené CEF ladění", + "desc": "Umožní neověřený přístup k CEF ladění komukoli ve vaší síti" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky verze", + "header": "O Decky" + }, + "beta": { + "header": "Účast v betě" + }, + "developer_mode": { + "label": "Vývojářský režim" + }, + "other": { + "header": "Ostatní" + }, + "updates": { + "header": "Aktualizace" + }, + "notifications": { + "decky_updates_label": "Dostupná aktualizace Decky", + "header": "Notifikace", + "plugin_updates_label": "Dostupná aktualizace pluginu" + } + }, + "SettingsIndex": { + "developer_title": "Vývojář", + "general_title": "Obecné", + "plugins_title": "Pluginy" + }, + "Store": { + "store_contrib": { + "label": "Přispívání", + "desc": "Pokud byste chtěli přispět do obchodu Decky Plugin Store, podívejte se na repozitář SteamDeckHomebrew/decky-plugin-template na GitHubu. Informace o vývoji a distribuci jsou k dispozici v README." + }, + "store_filter": { + "label": "Filtr", + "label_def": "Vše" + }, + "store_search": { + "label": "Hledat" + }, + "store_sort": { + "label": "Seřadit", + "label_def": "Naposledy aktualizováno (Nejnovější)" + }, + "store_source": { + "desc": "Veškerý zdrojový kód pluginu je dostupný v repozitáři SteamDeckHomebrew/decky-plugin-database na GitHubu.", + "label": "Zdrojový kód" + }, + "store_tabs": { + "about": "O Decky Plugin Store", + "alph_asce": "Abecedně (Z do A)", + "alph_desc": "Abecedně (A do Z)", + "title": "Procházet" + }, + "store_testing_cta": "Zvažte prosím testování nových pluginů, pomůžete tím týmu Decky Loader!", + "store_testing_warning": { + "desc": "Tento kanál obchodu můžete použít k testování nejnovějších verzí pluginů. Nezapomeňte zanechat zpětnou vazbu na GitHubu, aby bylo možné plugin aktualizovat pro všechny uživatele.", + "label": "Vítejte na testovacím kanálu obchodu" + } + }, + "StoreSelect": { + "custom_store": { + "label": "Vlastní obchod", + "url_label": "URL" + }, + "store_channel": { + "custom": "Vlastní", + "default": "Výchozí", + "label": "Kanál obchodu", + "testing": "Testování" + } + }, + "Updater": { + "updates": { + "lat_version": "Aktuální: běží na verzi {{ver}}", + "reloading": "Znovu načítání", + "updating": "Aktualizování", + "check_button": "Zkontrolovat aktualizace", + "checking": "Kontrolování", + "cur_version": "Aktuální verze: {{ver}}", + "install_button": "Instalovat aktualizaci", + "label": "Aktualizace" + }, + "decky_updates": "Aktualizace Decky", + "patch_notes_desc": "Poznámky k verzi", + "no_patch_notes_desc": "žádné poznámky pro tuto verzi" + }, + "DropdownMultiselect": { + "button": { + "back": "Zpět" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "Zadaná cesta není platná. Zkontrolujte ji a zadejte znovu správně.", + "unknown": "Nastala neznámá chyba. Nezpracovaná chyba je: {{raw_error}}", + "perm_denied": "Nemáte přístup k zadanému adresáři. Zkontrolujte, zda jako uživatel (deck na Steam Decku) máte odpovídající oprávnění pro přístup k dané složce/souboru." + } + }, + "TitleView": { + "settings_desc": "Otevřít nastavení Decky", + "decky_store_desc": "Otevřít obchod Decky" + } +} diff --git a/backend/src/locales/de-DE.json b/backend/src/locales/de-DE.json new file mode 100644 index 00000000..4ded8703 --- /dev/null +++ b/backend/src/locales/de-DE.json @@ -0,0 +1,195 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "Updatekanal", + "prerelease": "Vorabveröffentlichung", + "stable": "Standard", + "testing": "Test" + } + }, + "Developer": { + "disabling": "Deaktiviere", + "enabling": "Aktiviere", + "5secreload": "Neu laden in 5 Sekunden" + }, + "FilePickerIndex": { + "folder": { + "select": "Diesen Ordner verwenden" + } + }, + "PluginCard": { + "plugin_install": "Installieren", + "plugin_no_desc": "Keine Beschreibung angegeben.", + "plugin_version_label": "Erweiterungs Version", + "plugin_full_access": "Diese Erweiterung hat uneingeschränkten Zugriff auf dein Steam Deck." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Installieren", + "button_processing": "Wird installiert", + "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} installieren willst?", + "title": "Installiere {{artifact}}" + }, + "reinstall": { + "button_idle": "Neu installieren", + "button_processing": "Wird neu installiert", + "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} neu installieren willst?", + "title": "Neu installation {{artifact}}" + }, + "update": { + "button_idle": "Aktualisieren", + "button_processing": "Wird aktualisiert", + "title": "Aktualisiere {{artifact}}", + "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} aktualisieren willst?" + }, + "no_hash": "Diese Erweiterung besitzt keine Prüfsumme, Installation auf eigene Gefahr." + }, + "PluginListIndex": { + "no_plugin": "Keine Erweiterungen installiert!", + "plugin_actions": "Erweiterungs Aktionen", + "reinstall": "Neu installieren", + "reload": "Neu laden", + "uninstall": "Deinstallieren", + "update_to": "Aktualisieren zu {{name}}", + "update_all_one": "", + "update_all_other": "" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Eine neue Version ({{tag_name}}) ist verfügbar!", + "error": "Fehler", + "plugin_load_error": { + "toast": "Fehler beim Laden von {{name}}", + "message": "Fehler beim Laden von {{name}}" + }, + "plugin_uninstall": { + "button": "Deinstallieren", + "desc": "Bist du dir sicher, dass du {{name}} deinstallieren willst?", + "title": "Deinstalliere {{name}}" + }, + "plugin_error_uninstall": "Das Laden von {{name}} hat einen Fehler verursacht. Dies bedeutet normalerweise, dass die Erweiterung ein Update für die neue Version von SteamUI benötigt. Prüfe in den Decky-Einstellungen im Bereich Erweiterungen, ob ein Update vorhanden ist.", + "plugin_update_one": "1 Erweiterung kann aktualisiert werden!", + "plugin_update_other": "{{count}} Erweiterungen können aktualisiert werden!" + }, + "RemoteDebugging": { + "remote_cef": { + "label": "Remote CEF Debugging Zugriff", + "desc": "Erlaubt jedem aus dem Neztwerk unautorisierten Zugriff auf den CEF Debugger" + } + }, + "SettingsDeveloperIndex": { + "header": "Sonstiges", + "react_devtools": { + "ip_label": "IP", + "label": "Aktiviere React DevTools", + "desc": "Erlaubt die Verbindung mit einem anderen Rechner, auf welchem React DevTools läuft. Eine Änderung startet Steam neu. Die IP Adresse muss vor Aktivierung ausgefüllt sein." + }, + "third_party_plugins": { + "button_zip": "Durchsuchen", + "header": "Erweiterungen von Drittanbietern", + "label_desc": "URL", + "label_zip": "Installiere Erweiterung via ZIP Datei", + "button_install": "Installieren", + "label_url": "Installiere Erweiterung via URL" + }, + "valve_internal": { + "desc2": "Fasse in diesem Menü nichts an, es sei denn, du weißt was du tust.", + "label": "Aktiviere Valve-internes Menü", + "desc1": "Aktiviert das Valve-interne Entwickler Menü." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky Version", + "header": "Über" + }, + "beta": { + "header": "Beta Teilnahme" + }, + "developer_mode": { + "label": "Entwickleroptionen" + }, + "other": { + "header": "Sonstiges" + }, + "updates": { + "header": "Aktualisierungen" + } + }, + "SettingsIndex": { + "developer_title": "Entwickler", + "general_title": "Allgemein", + "plugins_title": "Erweiterungen" + }, + "Store": { + "store_contrib": { + "label": "Mitwirken", + "desc": "Wenn du Erweiterungen im Decky Store veröffentlichen willst, besuche die SteamDeckHomebrew/decky-plugin-template Repository auf GitHub. Informationen rund um Entwicklung und Veröffentlichung findest du in der README." + }, + "store_filter": { + "label": "Filter", + "label_def": "Alle" + }, + "store_search": { + "label": "Suche" + }, + "store_sort": { + "label": "Sortierung", + "label_def": "Zuletzt aktualisiert" + }, + "store_source": { + "desc": "Jeder Erweiterungs Quellcode ist in der SteamDeckHomebrew/decky-plugin-database Repository auf GitHub verfügbar.", + "label": "Quellcode" + }, + "store_tabs": { + "about": "Über", + "alph_asce": "Alphabetisch (Z zu A)", + "alph_desc": "Alphabetisch (A zu Z)", + "title": "Durchstöbern" + }, + "store_testing_cta": "Unterstütze das Decky Loader Team mit dem Testen von neuen Erweiterungen!" + }, + "StoreSelect": { + "custom_store": { + "label": "Benutzerdefinierter Marktplatz", + "url_label": "URL" + }, + "store_channel": { + "custom": "Benutzerdefiniert", + "default": "Standard", + "label": "Marktplatz Kanal", + "testing": "Test" + } + }, + "Updater": { + "decky_updates": "Decky Aktualisierungen", + "patch_notes_desc": "Patchnotizen", + "updates": { + "check_button": "Auf Aktualisierungen prüfen", + "checking": "Wird überprüft", + "cur_version": "Aktualle Version: {{ver}}", + "install_button": "Aktualisierung installieren", + "label": "Aktualisierungen", + "lat_version": "{{ver}} ist die aktuellste", + "reloading": "Lade neu", + "updating": "Aktualisiere" + }, + "no_patch_notes_desc": "Für diese Version gibt es keine Patchnotizen" + }, + "PluginView": { + "hidden_one": "", + "hidden_other": "" + }, + "MultiplePluginsInstallModal": { + "title": { + "install_one": "", + "install_other": "", + "mixed_one": "", + "mixed_other": "", + "update_one": "", + "update_other": "", + "reinstall_one": "", + "reinstall_other": "" + } + } +} diff --git a/backend/src/locales/el-GR.json b/backend/src/locales/el-GR.json new file mode 100644 index 00000000..62562935 --- /dev/null +++ b/backend/src/locales/el-GR.json @@ -0,0 +1,260 @@ +{ + "SettingsDeveloperIndex": { + "react_devtools": { + "desc": "Επιτρέπει την σύνδεση με υπολογιστή που τρέχει React DevTools. Η αλλαγή αυτής της ρύθμισης θα προκαλέσει επαναφόρτωση του Steam. Ωρίστε την διεύθυνση IP πριν την ενεργοποιήσετε.", + "ip_label": "IP", + "label": "Ενεργοποίηση React DevTools" + }, + "third_party_plugins": { + "button_install": "Εγκατάσταση", + "button_zip": "Περιήγηση", + "header": "Επεκτάσεις τρίτων", + "label_desc": "URL", + "label_url": "Εγκατάσταση επέκτασης απο URL", + "label_zip": "Εγκατάσταση επέκτασης από αρχείο ZIP" + }, + "valve_internal": { + "desc1": "Ενεργοποιεί το μενού προγραμματιστή της Valve.", + "desc2": "Μην αγγίξετε τίποτα σε αυτό το μενού εκτός και αν ξέρετε τι κάνει.", + "label": "Ενεργοποιήση εσωτερικού μενού Valve" + }, + "cef_console": { + "button": "Άνοιγμα Κονσόλας", + "desc": "Ανοίγει την Κονσόλα CEF. Χρήσιμο μόνο για εντοπισμό σφαλμάτων. Τα πράγματα εδώ είναι δυνητικά επικίνδυνα και θα πρέπει να χρησιμοποιηθεί μόνο εάν είστε προγραμματιστής επεκτάσεων, ή κατευθυνθήκατε εδώ από έναν προγραμματιστή.", + "label": "Κονσόλα CEF" + }, + "header": "Άλλα" + }, + "BranchSelect": { + "update_channel": { + "prerelease": "Προ-κυκλοφορία", + "stable": "Σταθερό", + "label": "Κανάλι ενημερώσεων", + "testing": "Δοκιμαστικό" + } + }, + "Developer": { + "5secreload": "Γίνεται επαναφόρτωση σε 5 δευτερόλεπτα", + "disabling": "Γίνεται απενεργοποίηση των React DevTools", + "enabling": "Γίνεται ενεργοποίηση των React DevTools" + }, + "PluginCard": { + "plugin_no_desc": "Δεν υπάρχει περιγραφή.", + "plugin_full_access": "Αυτή η επέκταση έχει πλήρη πρόσβαση στο Steam Deck σας.", + "plugin_install": "Εγκατάσταση", + "plugin_version_label": "Έκδοση επέκτασης" + }, + "PluginInstallModal": { + "install": { + "desc": "Σίγουρα θέλετε να εγκαταστήσετε το {{artifact}}{{version}};", + "button_idle": "Εγκατάσταση", + "button_processing": "Γίνεται εγκατάσταση", + "title": "Εγκατάσταση {{artifact}}" + }, + "no_hash": "Αυτή η επέκταση δεν έχει υπογραφή, την εγκαθηστάτε με δικό σας ρίσκο.", + "reinstall": { + "button_idle": "Επανεγκατάσταση", + "button_processing": "Γίνεται επανεγκατάσταση", + "desc": "Σίγουρα θέλετε να επανεγκαταστήσετε το {{artifact}}{{version}};", + "title": "Επανεγκατάσταση {{artifact}}" + }, + "update": { + "button_idle": "Ενημέρωση", + "desc": "Σίγουρα θέλετε να ενημερώσετε το {{artifact}} {{version}};", + "title": "Ενημέρωση {{artifact}}", + "button_processing": "Γίνεται ενημέρωση" + } + }, + "PluginListIndex": { + "no_plugin": "Δεν υπάρχουν εγκατεστημένες επεκτάσεις!", + "plugin_actions": "Ενέργειες επεκτάσεων", + "reinstall": "Επανεγκατάσταση", + "reload": "Επαναφόρτωση", + "uninstall": "Απεγκατάσταση", + "update_to": "Ενημέρωση σε {{name}}", + "update_all_one": "Ενημέρωση 1 επέκτασης", + "update_all_other": "Ενημέρωση {{count}} επεκτάσεων", + "show": "Γρήγορη πρόσβαση: Εμφάνιση", + "hide": "Γρήγορη πρόσβαση: Απόκρυψη" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Ενημέρωση σε {{tag_name}} διαθέσιμη!", + "error": "Σφάλμα", + "plugin_error_uninstall": "Η φόρτωση του {{name}} προκάλεσε το παραπάνω σφάλμα. Αυτό συνήθως σημαίνει ότι η επέκταση απαιτεί ενημέρωση για τη νέα έκδοση του SteamUI. Ελέγξτε εάν υπάρχει ενημέρωση ή αξιολογήστε την απεγκαταστήσετε της επέκτασης στις ρυθμίσεις του Decky, στην ενότητα Επεκτάσεις.", + "plugin_load_error": { + "message": "Σφάλμα στη φόρτωση της επέκτασης {{name}}", + "toast": "Σφάλμα φόρτωσης {{name}}" + }, + "plugin_uninstall": { + "button": "Απεγκατάσταση", + "desc": "Σίγουρα θέλετε να απεγκαταστήσετε το {{name}};", + "title": "Απεγκατάσταση {{name}}" + }, + "plugin_update_one": "Διαθέσιμη ενημέρωση για 1 επέκταση!", + "plugin_update_other": "Διαθέσιμες ενημερώσεις για {{count}} επεκτάσεις!" + }, + "RemoteDebugging": { + "remote_cef": { + "label": "Να επιτρέπεται η απομακρυσμένη πρόσβαση στον CEF debugger", + "desc": "Να επιτρέπεται η ανεξέλεγκτη πρόσβαση στον CEF debugger σε οποιονδήποτε στο τοπικό δίκτυο" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Έκδοση Decky", + "header": "Σχετικά" + }, + "developer_mode": { + "label": "Λειτουργία προγραμματιστή" + }, + "other": { + "header": "Άλλα" + }, + "updates": { + "header": "Ενημερώσεις" + }, + "beta": { + "header": "Συμμετοχή στη Beta" + }, + "notifications": { + "decky_updates_label": "Διαθέσιμη ενημέρωση του Decky", + "header": "Ειδοποιήσεις", + "plugin_updates_label": "Διαθέσιμες ενημερώσεις επεκτάσεων" + } + }, + "SettingsIndex": { + "plugins_title": "Επεκτάσεις", + "developer_title": "Προγραμματιστής", + "general_title": "Γενικά" + }, + "Store": { + "store_contrib": { + "label": "Συνεισφέροντας", + "desc": "Αν θέλετε να συνεισφέρετε στο κατάστημα επεκτάσεων του Decky, τσεκάρετε το SteamDeckHomebrew/decky-plugin-template repository στο GitHub. Πληροφορίες σχετικά με τη δημιουργία και τη διανομή επεκτάσεων είναι διαθέσιμες στο README." + }, + "store_filter": { + "label": "Φίλτρο", + "label_def": "Όλα" + }, + "store_search": { + "label": "Αναζήτηση" + }, + "store_sort": { + "label": "Ταξινόμηση", + "label_def": "Τελευταία ενημέρωση (Νεότερα)" + }, + "store_source": { + "desc": "Ο πηγαίος κώδικας όλων των επεκτάσεων είναι διαθέσιμος στο SteamDeckHomebrew/decky-plugin-database repository στο GitHub.", + "label": "Πηγαίος κώδικας" + }, + "store_tabs": { + "about": "Σχετικά", + "alph_asce": "Αλφαβητικά (Ζ σε Α)", + "alph_desc": "Αλφαβητικά (Α σε Ζ)", + "title": "Περιήγηση" + }, + "store_testing_cta": "Παρακαλώ σκεφτείτε να τεστάρετε νέες επεκτάσεις για να βοηθήσετε την ομάδα του Decky Loader!", + "store_testing_warning": { + "desc": "Μπορείτε να χρησιμοποιήσετε αυτό το κανάλι του καταστήματος για να δοκιμάσετε τις νεότερες εκδόσεις των επεκτάσεων. Φροντίστε να αφήσετε σχόλια στο GitHub, ώστε να βοηθήσετε στην ενημέρωση της εκάστοτε επέκταση για όλους τους χρήστες.", + "label": "Καλώς ήρθατε στο Δοκιμαστικό Κανάλι τους Καταστήματος" + } + }, + "StoreSelect": { + "custom_store": { + "label": "Προσαρμοσμένο κατάστημα", + "url_label": "URL" + }, + "store_channel": { + "custom": "Προσαρμοσμένο", + "default": "Προεπιλεγμένο", + "label": "Κανάλι καταστήματος", + "testing": "Δοκιμαστικό" + } + }, + "Updater": { + "no_patch_notes_desc": "Κανένα ενημερωτικό σημείωμα για αυτή την έκδοση", + "patch_notes_desc": "Σημειώσεις ενημέρωσης", + "updates": { + "check_button": "Έλεγχος για ενημερώσεις", + "checking": "Γίνεται έλεγχος", + "cur_version": "Τρέχουσα έκδοση: {{ver}}", + "install_button": "Εγκατάσταση ενημέρωσης", + "label": "Ενημερώσεις", + "updating": "Γίνεται ενημέρωση", + "lat_version": "Ενημερωμένο: τρέχουσα έκδοση {{ver}}", + "reloading": "Γίνεται επαναφόρτωση" + }, + "decky_updates": "Ενημερώσεις Decky" + }, + "FilePickerIndex": { + "folder": { + "select": "Χρησιμοποιήστε αυτό το φάκελο", + "label": "Φάκελος", + "show_more": "Εμφάνιση περισσότερων αρχείων" + }, + "filter": { + "modified_asce": "Τροποποιήθηκε (Παλαιότερο)", + "modified_desc": "Τροποποιήθηκε (Νεότερο)", + "created_desc": "Δημιουργήθηκε (Νεότερο)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "created_asce": "Δημιουργήθηκε (Παλαιότερο)", + "size_asce": "Μέγεθος (Μικρότερο)", + "size_desc": "Μέγεθος (Μεγαλύτερο)" + }, + "file": { + "select": "Επιλογή αυτού του αρχείου" + }, + "files": { + "show_hidden": "Εμφάνιση Κρυφών Αρχείων", + "all_files": "Όλα Τα Αρχεία", + "file_type": "Τύπος Αρχείου" + } + }, + "PluginView": { + "hidden_one": "1 επέκταση είναι κρυμμένη σε αυτήν τη λίστα", + "hidden_other": "{{count}} επεκτάσεις είναι κρυμμένες σε αυτήν τη λίστα" + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Τροποποίηση 1 επέκτασης", + "mixed_other": "Τροποποίηση {{count}} επεκτάσεων", + "update_one": "Ενημέρωση 1 επέκτασης", + "update_other": "Ενημέρωση {{count}} επεκτάσεων", + "reinstall_one": "Επανεγκατάσταση 1 επέκτασης", + "reinstall_other": "Επανεγκατάσταση {{count}} επεκτάσεων", + "install_one": "Εγκατάσταση 1 επέκτασης", + "install_other": "Εγκατάσταση {{count}} επεκτάσεων" + }, + "confirm": "Είστε βέβαιοι ότι θέλετε να κάνετε τις ακόλουθες τροποποιήσεις;", + "description": { + "reinstall": "Επανεγκατάσταση {{name}} {{version}}", + "update": "Ενημέρωση {{name}} to {{version}}", + "install": "Εγκατάσταση {{name}} {{version}}" + }, + "ok_button": { + "idle": "Επιβεβαίωση", + "loading": "Φόρτωση" + } + }, + "PluginListLabel": { + "hidden": "Κρυφό στο μενού γρήγορης πρόσβασης" + }, + "TitleView": { + "settings_desc": "Άνοιγμα Ρυθμίσεων Decky", + "decky_store_desc": "Άνοιγμα Καταστήματος Decky" + }, + "DropdownMultiselect": { + "button": { + "back": "Πίσω" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "Η καθορισμένη διαδρομή δεν είναι έγκυρη. Παρακαλούμε ελέγξτε τη και εισάγετέ τη ξανά σωστά.", + "perm_denied": "Δεν έχετε πρόσβαση στην καθορισμένη διαδρομή. Ελέγξτε εάν ο χρήστης σας (deck στο Steam Deck) έχει τα αντίστοιχα δικαιώματα πρόσβασης στον καθορισμένο φάκελο/αρχείο.", + "unknown": "Παρουσιάστηκε άγνωστο σφάλμα. Το σφάλμα είναι: {{raw_error}}" + } + } +} diff --git a/backend/src/locales/en-US.json b/backend/src/locales/en-US.json new file mode 100644 index 00000000..7845ae4f --- /dev/null +++ b/backend/src/locales/en-US.json @@ -0,0 +1,260 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "Update Channel", + "prerelease": "Prerelease", + "stable": "Stable", + "testing": "Testing" + } + }, + "Developer": { + "5secreload": "Reloading in 5 seconds", + "disabling": "Disabling React DevTools", + "enabling": "Enabling React DevTools" + }, + "DropdownMultiselect": { + "button": { + "back": "Back" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "The path specified is not valid. Please check it and reenter it correctly.", + "perm_denied": "You do not have access to the specified directory. Please check if your user (deck on Steam Deck) has the corresponding permission to access the specified folder/file.", + "unknown": "An unknown error occurred. The raw error is: {{raw_error}}" + } + }, + "FilePickerIndex": { + "file": { + "select": "Select this file" + }, + "files": { + "all_files": "All Files", + "file_type": "File Type", + "show_hidden": "Show Hidden Files" + }, + "filter": { + "created_asce": "Created (Oldest)", + "created_desc": "Created (Newest)", + "modified_asce": "Modified (Oldest)", + "modified_desc": "Modified (Newest)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Size (Smallest)", + "size_desc": "Size (Largest)" + }, + "folder": { + "label": "Folder", + "select": "Use this folder", + "show_more": "Show more files" + } + }, + "MultiplePluginsInstallModal": { + "confirm": "Are you sure you want to make the following modifications?", + "description": { + "install": "Install {{name}} {{version}}", + "reinstall": "Reinstall {{name}} {{version}}", + "update": "Update {{name}} to {{version}}" + }, + "ok_button": { + "idle": "Confirm", + "loading": "Working" + }, + "title": { + "install_one": "Install 1 plugin", + "install_other": "Install {{count}} plugins", + "mixed_one": "Modify {{count}} plugin", + "mixed_other": "Modify {{count}} plugins", + "reinstall_one": "Reinstall 1 plugin", + "reinstall_other": "Reinstall {{count}} plugins", + "update_one": "Update 1 plugin", + "update_other": "Update {{count}} plugins" + } + }, + "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 {{artifact}}" + } + }, + "PluginListIndex": { + "hide": "Quick access: Hide", + "no_plugin": "No plugins installed!", + "plugin_actions": "Plugin Actions", + "reinstall": "Reinstall", + "reload": "Reload", + "show": "Quick access: Show", + "uninstall": "Uninstall", + "update_all_one": "Update 1 plugin", + "update_all_other": "Update {{count}} plugins", + "update_to": "Update to {{name}}" + }, + "PluginListLabel": { + "hidden": "Hidden from the quick access menu" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Update to {{tag_name}} available!", + "error": "Error", + "plugin_error_uninstall": "Loading {{name}} caused an exception as shown above. This usually means that the plugin requires an update for the new version of SteamUI. Check if an update is present or evaluate its removal in the Decky settings, in the Plugins section.", + "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!" + }, + "PluginView": { + "hidden_one": "1 plugin is hidden from this list", + "hidden_other": "{{count}} plugins are hidden from this list" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Allow unauthenticated access to the CEF debugger to anyone in your network", + "label": "Allow Remote CEF Debugging" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Open Console", + "desc": "Opens the CEF Console. Only useful for debugging purposes. Stuff here is potentially dangerous and should only be used if you are a plugin dev, or are directed here by one.", + "label": "CEF Console" + }, + "header": "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" + }, + "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": { + "label": "Developer mode" + }, + "notifications": { + "decky_updates_label": "Decky update available", + "header": "Notifications", + "plugin_updates_label": "Plugin updates available" + }, + "other": { + "header": "Other" + }, + "updates": { + "header": "Updates" + } + }, + "SettingsIndex": { + "developer_title": "Developer", + "general_title": "General", + "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!", + "store_testing_warning": { + "desc": "You can use this store channel to test bleeding-edge plugin versions. Be sure to leave feedback on GitHub so the plugin can be updated for all users.", + "label": "Welcome to the Testing Store Channel" + } + }, + "StoreSelect": { + "custom_store": { + "label": "Custom Store", + "url_label": "URL" + }, + "store_channel": { + "custom": "Custom", + "default": "Default", + "label": "Store Channel", + "testing": "Testing" + } + }, + "TitleView": { + "decky_store_desc": "Open Decky Store", + "settings_desc": "Open Decky Settings" + }, + "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" + } + } +} diff --git a/backend/src/locales/es-ES.json b/backend/src/locales/es-ES.json new file mode 100644 index 00000000..6c47eb06 --- /dev/null +++ b/backend/src/locales/es-ES.json @@ -0,0 +1,217 @@ +{ + "SettingsDeveloperIndex": { + "third_party_plugins": { + "button_install": "Instalar", + "button_zip": "Navegar", + "label_desc": "URL", + "label_url": "Instalar plugin desde URL", + "label_zip": "Instalar plugin desde archivo ZIP", + "header": "Plugins de terceros" + }, + "valve_internal": { + "desc2": "No toques nada en este menú a menos que sepas lo que haces.", + "label": "Activar menú interno de Valve", + "desc1": "Activa el menú interno de desarrollo de Valve." + }, + "cef_console": { + "button": "Abrir consola", + "label": "Consola CEF", + "desc": "Abre la consola del CEF. Solamente es útil para propósitos de depuración. Las cosas que hagas aquí pueden ser potencialmente peligrosas y solo se debería usar si eres un desarrollador de plugins, o uno te ha dirigido aquí." + }, + "react_devtools": { + "ip_label": "IP", + "label": "Activar DevTools de React", + "desc": "Permite la conexión a un ordenador ejecutando las DevTools de React. Cambiar este ajuste recargará Steam. Configura la dirección IP antes de activarlo." + }, + "header": "Otros" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalar", + "button_processing": "Instalando", + "title": "Instalar {{artifact}}", + "desc": "¿Estás seguro de que quieres instalar {{artifact}} {{version}}?" + }, + "reinstall": { + "button_idle": "Reinstalar", + "button_processing": "Reinstalando", + "desc": "¿Estás seguro de que quieres reinstalar {{artifact}} {{version}}?", + "title": "Reinstalar {{artifact}}" + }, + "update": { + "button_processing": "Actualizando", + "button_idle": "Actualizar", + "desc": "¿Estás seguro de que quieres actualizar {{artifact}} {{version}}?", + "title": "Actualizar {{artifact}}" + }, + "no_hash": "Este plugin no tiene un hash, lo estás instalando bajo tu propia responsabilidad." + }, + "Developer": { + "disabling": "Desactivando DevTools de React", + "enabling": "Activando DevTools de React", + "5secreload": "Recargando en 5 segundos" + }, + "BranchSelect": { + "update_channel": { + "prerelease": "Prelanzamiento", + "stable": "Estable", + "label": "Canal de actualización", + "testing": "Pruebas" + } + }, + "PluginCard": { + "plugin_full_access": "Este plugin tiene acceso completo a su Steam Deck.", + "plugin_install": "Instalar", + "plugin_version_label": "Versión de Plugin", + "plugin_no_desc": "No se proporcionó una descripción." + }, + "FilePickerIndex": { + "folder": { + "select": "Usar esta carpeta" + } + }, + "PluginListIndex": { + "uninstall": "Desinstalar", + "reinstall": "Reinstalar", + "reload": "Recargar", + "plugin_actions": "Acciones de plugin", + "no_plugin": "¡No hay plugins instalados!", + "update_all_one": "Actualizar 1 plugin", + "update_all_many": "Actualizar {{count}} plugins", + "update_all_other": "Actualizar {{count}} plugins", + "update_to": "Actualizar a {{name}}" + }, + "PluginLoader": { + "error": "Error", + "plugin_uninstall": { + "button": "Desinstalar", + "desc": "¿Estás seguro de que quieres desinstalar {{name}}?", + "title": "Desinstalar {{name}}" + }, + "decky_title": "Decky", + "plugin_update_one": "¡Actualización disponible para 1 plugin!", + "plugin_update_many": "¡Actualizaciones disponibles para {{count}} plugins!", + "plugin_update_other": "¡Actualizaciones disponibles para {{count}} plugins!", + "decky_update_available": "¡Actualización {{tag_name}} disponible!", + "plugin_load_error": { + "message": "Se ha producido un error al cargar el plugin {{name}}", + "toast": "Se ha producido un error al cargar {{name}}" + }, + "plugin_error_uninstall": "Al cargar {{name}} se ha producido una excepción como se muestra arriba. Esto suele significar que el plugin requiere una actualización para la nueva versión de SteamUI. Comprueba si hay una actualización disponible o valora eliminarlo en los ajustes de Decky, en la sección Plugins." + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Permitir acceso no autenticado al CEF debugger a cualquier persona en su red", + "label": "Permitir depuración remota del CEF" + } + }, + "SettingsGeneralIndex": { + "updates": { + "header": "Actualizaciones" + }, + "about": { + "header": "Acerca de", + "decky_version": "Versión de Decky" + }, + "developer_mode": { + "label": "Modo desarrollador" + }, + "beta": { + "header": "Participación en la beta" + }, + "other": { + "header": "Otros" + } + }, + "SettingsIndex": { + "developer_title": "Desarrollador", + "general_title": "General", + "plugins_title": "Plugins" + }, + "Store": { + "store_search": { + "label": "Buscar" + }, + "store_sort": { + "label": "Ordenar", + "label_def": "Actualizado por última vez (Nuevos)" + }, + "store_contrib": { + "desc": "Si desea contribuir a la tienda de plugins de Decky, mira el repositorio SteamDeckHomebrew/decky-plugin-template en GitHub. Hay información acerca del desarrollo y distribución en el archivo README.", + "label": "Contribuyendo" + }, + "store_tabs": { + "about": "Información", + "title": "Navegar", + "alph_asce": "Alfabéticamente (Z-A)", + "alph_desc": "Alfabéticamente (A-Z)" + }, + "store_testing_cta": "¡Por favor considera probar plugins nuevos para ayudar al equipo de Decky Loader!", + "store_source": { + "desc": "El código fuente de los plugins está disponible en el repositiorio SteamDeckHomebrew/decky-plugin-database en GitHub.", + "label": "Código fuente" + }, + "store_filter": { + "label_def": "Todos", + "label": "Filtrar" + } + }, + "Updater": { + "updates": { + "reloading": "Recargando", + "updating": "Actualizando", + "checking": "Buscando", + "check_button": "Buscar actualizaciones", + "install_button": "Instalar actualización", + "label": "Actualizaciones", + "lat_version": "Actualizado: ejecutando {{ver}}", + "cur_version": "Versión actual: {{ver}}" + }, + "decky_updates": "Actualizaciones de Decky", + "no_patch_notes_desc": "No hay notas de parche para esta versión", + "patch_notes_desc": "Notas de parche" + }, + "MultiplePluginsInstallModal": { + "title": { + "reinstall_one": "Reinstalar 1 plugin", + "reinstall_many": "Reinstalar {{count}} plugins", + "reinstall_other": "Reinstalar {{count}} plugins", + "update_one": "Actualizar 1 plugin", + "update_many": "Actualizar {{count}} plugins", + "update_other": "Actualizar {{count}} plugins", + "mixed_one": "Modificar 1 plugin", + "mixed_many": "Modificar {{count}} plugins", + "mixed_other": "Modificar {{count}} plugins", + "install_one": "Instalar 1 plugin", + "install_many": "Instalar {{count}} plugins", + "install_other": "Instalar {{count}} plugins" + }, + "ok_button": { + "idle": "Confirmar", + "loading": "Trabajando" + }, + "confirm": "¿Estás seguro de que quieres hacer las siguientes modificaciones?", + "description": { + "install": "Instalar {{name}} {{version}}", + "update": "Actualizar {{name}} a {{version}}", + "reinstall": "Reinstalar {{name}} {{version}}" + } + }, + "StoreSelect": { + "custom_store": { + "url_label": "URL", + "label": "Tienda personalizada" + }, + "store_channel": { + "custom": "Personalizada", + "default": "Por defecto", + "label": "Canál de la tienda", + "testing": "Pruebas" + } + }, + "PluginView": { + "hidden_one": "", + "hidden_many": "", + "hidden_other": "" + } +} diff --git a/backend/src/locales/fi-FI.json b/backend/src/locales/fi-FI.json new file mode 100644 index 00000000..b0ff1309 --- /dev/null +++ b/backend/src/locales/fi-FI.json @@ -0,0 +1,260 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "Esijulkaisu", + "testing": "Testiversio", + "stable": "Vakaa versio", + "label": "Päivityskanava" + } + }, + "Developer": { + "5secreload": "Uudelleenladataan 5 sekunin kuluttua", + "disabling": "Poistetaan React DevTools käytöstä", + "enabling": "Otetaan React DevTools käyttöön" + }, + "FilePickerError": { + "errors": { + "perm_denied": "Sinulla ei ole käyttöoikeutta määritettyyn hakemistoon. Tarkista, onko käyttäjälläsi (käyttäjä 'deck' Steam Deckillä) vastaavat oikeudet käyttää määritettyä kansiota/tiedostoa.", + "unknown": "Tapahtui tuntematon virhe. Raaka virhe on: {{raw_error}}", + "file_not_found": "Määritetty polku ei kelpaa. Tarkista se ja kirjoita se uudelleen oikein." + } + }, + "FilePickerIndex": { + "file": { + "select": "Valitse tämä tiedosto" + }, + "files": { + "all_files": "Kaikki tiedostot", + "file_type": "Tiedostotyyppi", + "show_hidden": "Näytä piilotetut tiedostot" + }, + "filter": { + "created_desc": "Luotu (uusin ensin)", + "modified_asce": "Muokattu (vanhin)", + "modified_desc": "Muokattu (uusin)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Koko (pienin ensin)", + "size_desc": "Koko (suurin ensin)", + "created_asce": "Luotu (vanhin ensin)" + }, + "folder": { + "label": "Kansio", + "select": "Käytä tätä kansiota", + "show_more": "Näytä lisää tiedostoja" + } + }, + "MultiplePluginsInstallModal": { + "confirm": "Haluatko varmasti tehdä seuraavat muutokset?", + "description": { + "reinstall": "Uudelleenasenna {{name}} {{version}}", + "update": "Päivitä {{name}} versioon {{version}}", + "install": "Asenna {{name}} {{version}}" + }, + "ok_button": { + "idle": "Vahvista", + "loading": "Ladataan" + }, + "title": { + "install_one": "Asenna yksi laajennus", + "install_other": "Asenna {{count}} laajennusta", + "update_one": "Päivitä yksi laajennus", + "update_other": "Päivitä {{count}} laajennusta", + "mixed_one": "Muuta yhtä laajennusta", + "mixed_other": "Muuta {{count}} laajennusta", + "reinstall_one": "Uudelleenasenna yksi laajennus", + "reinstall_other": "Uudelleenasenna {{count}} laajennusta" + } + }, + "PluginCard": { + "plugin_install": "Asenna", + "plugin_no_desc": "Ei kuvausta.", + "plugin_version_label": "Laajennuksen versio", + "plugin_full_access": "Tällä laajennuksella on täysi pääsy Steam Deckkiisi." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Asenna", + "button_processing": "Asennetaan", + "desc": "Haluatko varmasti asentaa {{artifact}} {{version}}?", + "title": "Asenna {{artifact}}" + }, + "no_hash": "Tällä laajennuksella ei ole hashia, asennat sen omalla vastuullasi.", + "reinstall": { + "button_idle": "Uudelleenasenna", + "button_processing": "Uudelleenasennetaan", + "desc": "Haluatko varmasti uudelleenasentaa {{artifact}} {{version}}?", + "title": "Uudelleenasenna {{artifact}}" + }, + "update": { + "button_idle": "Päivitä", + "button_processing": "Päivitetään", + "desc": "Haluatko varmasti päivittää {{artifact}} {{version}}?", + "title": "Päivitä {{artifact}}" + } + }, + "DropdownMultiselect": { + "button": { + "back": "Takaisin" + } + }, + "PluginListIndex": { + "no_plugin": "Ei asennettuja laajennuksia!", + "plugin_actions": "Laajennustoiminnot", + "reinstall": "Uudelleenasenna", + "reload": "Lataa uudelleen", + "uninstall": "Poista asennus", + "update_all_one": "Päivitä yksi laajennus", + "update_all_other": "Päivitä {{count}} laajennusta", + "update_to": "Päivitä versioon {{name}}", + "hide": "Pikavalikko: Piilota", + "show": "Pikavalikko: Näytä" + }, + "PluginListLabel": { + "hidden": "Piilotettu pikavalikosta" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Päivitys versioon {{tag_name}} on saatavilla!", + "error": "Virhe", + "plugin_load_error": { + "message": "Virhe ladattaessa {{name}}-laajennusta", + "toast": "Virhe ladattaessa {{name}}" + }, + "plugin_uninstall": { + "button": "Poista asennus", + "desc": "Haluatko varmasti poistaa {{name}} asennuksen?", + "title": "Poista {{name}}" + }, + "plugin_update_one": "Päivityksiä saatavilla yhdelle laajennukselle!", + "plugin_update_other": "Päivityksiä saatavilla {{count}} laajennukselle!", + "plugin_error_uninstall": "{{name}} lataaminen aiheutti yllä olevan poikkeuksen. Tämä tarkoittaa yleensä sitä, että laajennus vaatii päivityksen uudelle SteamUI-versiolle. Tarkista, onko päivitystä saatavilla, tai harkitse laajennuksen poistoa Decky-asetuksista, laajennukset-osiosta." + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Salli todentamaton pääsy CEF-debuggeriin kenelle tahansa verkossasi", + "label": "Salli CEF-etädebugaus" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Avaa konsoli", + "desc": "Avaa CEF-konsolin. Hyödyllinen vain debugaustarkoituksiin. Täällä olevat jutut ovat mahdollisesti vaarallisia, ja niitä tulisi käyttää vain, jos olet laajennuksen kehittäjä tai jos kehittäjä on ohjannut sinut tänne.", + "label": "CEF-konsoli" + }, + "header": "Muu", + "react_devtools": { + "desc": "Mahdollistaa yhteyden tietokoneeseen, jossa on käytössä React DevTools. Tämän asetuksen muuttaminen lataa Steamin uudelleen. Aseta IP-osoite ennen käyttöönottoa.", + "ip_label": "IP-osoite", + "label": "Ota React DevTools käyttöön" + }, + "third_party_plugins": { + "button_install": "Asenna", + "button_zip": "Selaa", + "header": "Kolmannen osapuolen laajennukset", + "label_desc": "URL-osoite", + "label_zip": "Asenna laajennus ZIP-tiedostosta", + "label_url": "Asenna laajennus URL-osoitteesta" + }, + "valve_internal": { + "desc2": "Älä koske mihinkään tässä valikossa, ellet tiedä mitä se tekee.", + "label": "Ota Valve Internal käyttöön", + "desc1": "Ottaa käyttöön Valven sisäisen kehittäjävalikon." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky-versio", + "header": "Tietoja" + }, + "beta": { + "header": "Beta-osallistuminen" + }, + "developer_mode": { + "label": "Kehittäjätila" + }, + "notifications": { + "decky_updates_label": "Decky-päivitys saatavilla", + "header": "Ilmoitukset", + "plugin_updates_label": "Laajennuspäivityksiä saatavilla" + }, + "other": { + "header": "Muu" + }, + "updates": { + "header": "Päivitykset" + } + }, + "SettingsIndex": { + "developer_title": "Kehittäjä", + "general_title": "Yleinen", + "plugins_title": "Laajennukset" + }, + "Store": { + "store_contrib": { + "label": "Osallistuminen", + "desc": "Mikäli haluat julkaista Decky Plugin Storeen, tarkista GitHubin SteamDeckHomebrew/decky-plugin-template -esimerkkitietovarasto. Tietoa kehityksestä ja jakelusta löytyy README:stä." + }, + "store_filter": { + "label": "Suodin", + "label_def": "Kaikki" + }, + "store_search": { + "label": "Hae" + }, + "store_sort": { + "label": "Järjestä", + "label_def": "Viimeksi päivitetty (uusin ensin)" + }, + "store_source": { + "desc": "Kaikken laajennusten lähdekoodit ovat saatavilla SteamDeckHomebrew/decky-plugin-database -arkistosta GitHubissa.", + "label": "Lähdekoodi" + }, + "store_tabs": { + "about": "Tietoja", + "alph_asce": "Aakkosjärjestyksessä (Z–A)", + "alph_desc": "Aakkosjärjestyksessä (A–Z)", + "title": "Selaa" + }, + "store_testing_cta": "Harkitse uusien lisäosien testaamista auttaaksesi Decky Loader -tiimiä!", + "store_testing_warning": { + "label": "Tervetuloa testausmyymälä-kanavalle", + "desc": "Voit käyttää tätä myymäläkanavaa testataksesi uusimpia laajennusversioita. Muista jättää palautetta GitHubissa, jotta laajennus voidaan päivittää kaikille käyttäjille." + } + }, + "StoreSelect": { + "custom_store": { + "label": "Mukautettu myymälä", + "url_label": "URL-osoite" + }, + "store_channel": { + "custom": "Mukautettu", + "default": "Oletus", + "label": "Myymäläkanava", + "testing": "Testaus" + } + }, + "TitleView": { + "decky_store_desc": "Avaa Decky-myymälä", + "settings_desc": "Avaa Decky-asetukset" + }, + "Updater": { + "decky_updates": "Decky-päivitykset", + "no_patch_notes_desc": "tälle versiolle ei ole korjausmerkintöjä", + "patch_notes_desc": "Korjausmerkinnät", + "updates": { + "check_button": "Tarkista päivitykset", + "checking": "Tarkistetaan", + "cur_version": "Nykyinen versio: {{ver}}", + "install_button": "Asenna päivitys", + "label": "Päivitykset", + "lat_version": "Ajan tasalla: versio {{ver}}", + "reloading": "Uudelleenladataan", + "updating": "Päivitetään" + } + }, + "PluginView": { + "hidden_one": "Yksi laajennus on piilotettu tästä luettelosta", + "hidden_other": "{{count}} laajennusta on piilotettu tästä luettelosta" + } +} diff --git a/backend/src/locales/fr-FR.json b/backend/src/locales/fr-FR.json new file mode 100644 index 00000000..f1c305f9 --- /dev/null +++ b/backend/src/locales/fr-FR.json @@ -0,0 +1,201 @@ +{ + "SettingsDeveloperIndex": { + "react_devtools": { + "desc": "Permet la connexion à un ordinateur exécutant React DevTools. Changer ce paramètre rechargera Steam. Définissez l'adresse IP avant l'activation.", + "ip_label": "IP", + "label": "Activer React DevTools" + }, + "third_party_plugins": { + "button_install": "Installer", + "button_zip": "Parcourir", + "header": "Plugins tiers", + "label_desc": "URL", + "label_url": "Installer le plugin à partir d'un URL", + "label_zip": "Installer le plugin à partir d'un fichier ZIP" + }, + "valve_internal": { + "desc1": "Active le menu développeur interne de Valve.", + "desc2": "Ne touchez à rien dans ce menu à moins que vous ne sachiez ce qu'il fait.", + "label": "Activer Valve Internal" + } + }, + "BranchSelect": { + "update_channel": { + "prerelease": "Avant-première", + "label": "Canal de mise à jour", + "stable": "Stable", + "testing": "Test" + } + }, + "StoreSelect": { + "store_channel": { + "label": "Canal du Plugin Store", + "testing": "Test", + "custom": "Personnalisé", + "default": "Par défaut" + }, + "custom_store": { + "label": "Plugin Store personnalisé", + "url_label": "URL" + } + }, + "Updater": { + "decky_updates": "Mises à jour de Decky", + "no_patch_notes_desc": "pas de notes de mise à jour pour cette version", + "patch_notes_desc": "Notes de mise à jour", + "updates": { + "check_button": "Chercher les mises à jour", + "checking": "Recherche", + "cur_version": "Version actuelle: {{ver}}", + "install_button": "Installer la mise à jour", + "label": "Mises à jour", + "lat_version": "À jour: version {{ver}}", + "reloading": "Rechargement", + "updating": "Mise à jour en cours" + } + }, + "Developer": { + "5secreload": "Rechargement dans 5 secondes", + "disabling": "Désactivation", + "enabling": "Activation" + }, + "FilePickerIndex": { + "folder": { + "select": "Utiliser ce dossier" + } + }, + "PluginCard": { + "plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.", + "plugin_install": "Installer", + "plugin_no_desc": "Aucune description fournie.", + "plugin_version_label": "Version du plugin" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Installer", + "button_processing": "Installation en cours", + "title": "Installer {{artifact}}", + "desc": "Êtes-vous sûr de vouloir installer {{artifact}} {{version}} ?" + }, + "no_hash": "Ce plugin n'a pas de somme de contrôle, vous l'installez à vos risques et périls.", + "reinstall": { + "button_idle": "Réinstaller", + "button_processing": "Réinstallation en cours", + "desc": "Êtes-vous sûr de vouloir réinstaller {{artifact}} {{version}} ?", + "title": "Réinstaller {{artifact}}" + }, + "update": { + "button_idle": "Mettre à jour", + "button_processing": "Mise à jour", + "title": "Mettre à jour {{artifact}}", + "desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} {{version}} ?" + } + }, + "PluginListIndex": { + "plugin_actions": "Plugin Actions", + "reinstall": "Réinstaller", + "reload": "Recharger", + "uninstall": "Désinstaller", + "update_to": "Mettre à jour vers {{name}}", + "no_plugin": "Aucun plugin installé !", + "update_all_one": "", + "update_all_many": "", + "update_all_other": "" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Erreur", + "plugin_error_uninstall": "Allez sur {{name}} dans le menu de Decky si vous voulez désinstaller ce plugin.", + "plugin_load_error": { + "message": "Erreur lors du chargement du plugin {{name}}", + "toast": "Erreur lors du chargement de {{name}}" + }, + "decky_update_available": "Mise à jour vers {{tag_name}} disponible !", + "plugin_uninstall": { + "button": "Désinstaller", + "title": "Désinstaller {{name}}", + "desc": "Êtes-vous sûr.e de vouloir désinstaller {{name}} ?" + }, + "plugin_update_one": "", + "plugin_update_many": "", + "plugin_update_other": "" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Autoriser l'accès non authentifié au débogueur CEF à toute personne de votre réseau", + "label": "Autoriser le débogage CEF à distance" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Version de Decky", + "header": "À propos" + }, + "beta": { + "header": "Participation à la Bêta" + }, + "developer_mode": { + "label": "Mode développeur" + }, + "other": { + "header": "Autre" + }, + "updates": { + "header": "Mises à jour" + } + }, + "SettingsIndex": { + "developer_title": "Développeur", + "general_title": "Général", + "plugins_title": "Plugins" + }, + "Store": { + "store_contrib": { + "desc": "Si vous souhaitez contribuer au Decky Plugin Store, consultez le dépôt SteamDeckHomebrew/decky-plugin-template sur GitHub. Des informations sur le développement et la distribution sont disponibles dans le fichier README.", + "label": "Contributions" + }, + "store_filter": { + "label": "Filtrer", + "label_def": "Tous" + }, + "store_search": { + "label": "Rechercher" + }, + "store_sort": { + "label": "Trier", + "label_def": "Mises à jour (Plus récentes)" + }, + "store_source": { + "desc": "Tout le code source des plugins est disponible sur le dépôt SteamDeckHomebrew/decky-plugin-database sur GitHub.", + "label": "Code Source" + }, + "store_tabs": { + "about": "À propos", + "alph_asce": "Alphabétique (Z à A)", + "alph_desc": "Alphabétique (A à Z)", + "title": "Explorer" + }, + "store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !" + }, + "PluginView": { + "hidden_one": "", + "hidden_many": "", + "hidden_other": "" + }, + "MultiplePluginsInstallModal": { + "title": { + "reinstall_one": "", + "reinstall_many": "", + "reinstall_other": "", + "install_one": "", + "install_many": "", + "install_other": "", + "mixed_one": "", + "mixed_many": "", + "mixed_other": "", + "update_one": "", + "update_many": "", + "update_other": "" + } + } +} diff --git a/backend/src/locales/it-IT.json b/backend/src/locales/it-IT.json new file mode 100644 index 00000000..237bcdf4 --- /dev/null +++ b/backend/src/locales/it-IT.json @@ -0,0 +1,267 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "Canale di aggiornamento", + "prerelease": "Prerilascio", + "stable": "Stabile", + "testing": "In prova" + } + }, + "Developer": { + "5secreload": "Ricarico tra 5 secondi", + "disabling": "Disabilito i tools di React", + "enabling": "Abilito i tools di React" + }, + "DropdownMultiselect": { + "button": { + "back": "Indietro" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "Il percorso specificato non è valido. Controllalo e prova a reinserirlo di nuovo.", + "unknown": "È avvenuto un'errore sconosciuto. L'errore segnalato è {{raw_error}}", + "perm_denied": "Il tuo utente non ha accesso alla directory specificata. Verifica se l'utente corrente (è deck su Steam Deck di default) ha i permessi corrispondenti per accedere alla cartella/file desiderato." + } + }, + "FilePickerIndex": { + "file": { + "select": "Seleziona questo file" + }, + "files": { + "all_files": "Tutti i file", + "file_type": "Tipo di file", + "show_hidden": "Mostra nascosti" + }, + "filter": { + "created_asce": "Creazione (meno recente)", + "created_desc": "Creazione (più recente)", + "modified_asce": "Modifica (meno recente)", + "modified_desc": "Modifica (più recente)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Dimensione (più piccolo)", + "size_desc": "Dimensione (più grande)" + }, + "folder": { + "label": "Cartella", + "select": "Usa questa cartella", + "show_more": "Mostra più file" + } + }, + "MultiplePluginsInstallModal": { + "confirm": "Sei sicuro di voler effettuare le modifiche seguenti?", + "description": { + "install": "Installa {{name}} {{version}}", + "reinstall": "Reinstalla {{name}} {{version}}", + "update": "Aggiorna {{name}} alla versione {{version}}" + }, + "ok_button": { + "idle": "Conferma", + "loading": "Elaboro" + }, + "title": { + "install_one": "Installa un plugin", + "install_many": "Installa {{count}} plugins", + "install_other": "Installa {{count}} plugins", + "mixed_one": "Modifica un plugin", + "mixed_many": "Modifica {{count}} plugins", + "mixed_other": "Modifica {{count}} plugins", + "reinstall_one": "Reinstalla un plugin", + "reinstall_many": "Reinstalla {{count}} plugins", + "reinstall_other": "Reinstalla {{count}} plugins", + "update_one": "Aggiorna un plugin", + "update_many": "Aggiorna {{count}} plugins", + "update_other": "Aggiorna {{count}} plugins" + } + }, + "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": { + "hide": "Accesso rapido: Nascondi", + "no_plugin": "Nessun plugin installato!", + "plugin_actions": "Operazioni sui plugins", + "reinstall": "Reinstalla", + "reload": "Ricarica", + "show": "Accesso rapido: Mostra", + "uninstall": "Rimuovi", + "update_all_one": "Aggiorna un plugin", + "update_all_many": "Aggiorna {{count}} plugins", + "update_all_other": "Aggiorna {{count}} plugins", + "update_to": "Aggiorna a {{name}}" + }, + "PluginListLabel": { + "hidden": "Nascosto dal menu di accesso rapido" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Disponibile aggiornamento a {{tag_name}}!", + "error": "Errore", + "plugin_error_uninstall": "Il plugin {{name}} ha causato un'eccezione che è descritta sopra. Questo tipicamente significa che il plugin deve essere aggiornato per funzionare sulla nuova versione di SteamUI. Controlla se è disponibile un aggiornamento o valutane la rimozione andando nelle impostazioni di Decky nella sezione Plugins.", + "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 1 plugin!", + "plugin_update_many": "Aggiornamenti disponibili per {{count}} plugins!", + "plugin_update_other": "Aggiornamenti disponibili per {{count}} plugins!" + }, + "PluginView": { + "hidden_one": "Un plugin è nascosto dalla lista", + "hidden_many": "Sono nascosti {{count}} plugin dalla lista", + "hidden_other": "Sono nascosti {{count}} plugin dalla lista" + }, + "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": { + "cef_console": { + "button": "Apri la console", + "desc": "Apri la console di CEF. Utile solamente per ragioni di debug. Questa opzione deve essere usata solo se sei uno sviluppatore di plugin o se uno di questi ti ha chiesto di farlo, visto che questa feature potrebbe essere potenzialmente pericolosa.", + "label": "Console CEF" + }, + "header": "Altro", + "react_devtools": { + "desc": "Abilita la connessione ad un computer che esegue i DevTools di React. Steam verrà ricaricato se lo stato cambia. Imposta il tuo 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" + }, + "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": { + "label": "Modalità sviluppatore" + }, + "other": { + "header": "Altro" + }, + "updates": { + "header": "Aggiornamenti" + }, + "notifications": { + "header": "Notifiche", + "decky_updates_label": "Aggiornamenti di Decky", + "plugin_updates_label": "Aggiornamenti dei plugins" + } + }, + "SettingsIndex": { + "developer_title": "Sviluppatore", + "general_title": "Generali", + "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!", + "store_testing_warning": { + "label": "Benvenuto nel Negozio di Test dei Plugins", + "desc": "Puoi usare questo canale del negozio per testare versioni di plugin sperimentali. Assicurati di lasciare un feedback su Github dopo averlo testato in modo che il plugin possa essere promosso a stabile per tutti gli altri utenti o per permettere allo sviluppatore di plugin di correggere eventuali errori." + } + }, + "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" + } + }, + "TitleView": { + "settings_desc": "Apri le impostazioni di Decky", + "decky_store_desc": "Apri lo store di Decky" + } +} diff --git a/backend/src/locales/ko-KR.json b/backend/src/locales/ko-KR.json new file mode 100644 index 00000000..48698c5c --- /dev/null +++ b/backend/src/locales/ko-KR.json @@ -0,0 +1,253 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "업데이트 배포 채널", + "stable": "안정", + "testing": "테스트", + "prerelease": "사전 출시" + } + }, + "Developer": { + "disabling": "React DevTools 비활성화", + "enabling": "React DevTools 활성화", + "5secreload": "5초 내로 다시 로드 됩니다" + }, + "FilePickerIndex": { + "folder": { + "select": "이 폴더 사용", + "label": "폴더", + "show_more": "더 많은 파일 표시" + }, + "filter": { + "created_asce": "만든 날짜 (오름차순)", + "modified_asce": "수정한 날짜 (오름차순)", + "created_desc": "만든 날짜 (내림차 순)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "크기 (오름차순)", + "modified_desc": "수정한 날짜 (내림차순)", + "size_desc": "크기 (내림차순)" + }, + "files": { + "all_files": "모든 파일", + "show_hidden": "숨김 파일 표시", + "file_type": "파일 형식" + }, + "file": { + "select": "이 파일 선택" + } + }, + "PluginView": { + "hidden_other": "플러그인 {{count}}개 숨김" + }, + "PluginListLabel": { + "hidden": "빠른 액세스 메뉴에서 숨김" + }, + "PluginCard": { + "plugin_install": "설치", + "plugin_no_desc": "플러그인 설명이 제공되지 않았습니다.", + "plugin_version_label": "플러그인 버전", + "plugin_full_access": "이 플러그인은 Steam Deck의 모든 접근 권한을 가집니다." + }, + "PluginInstallModal": { + "install": { + "button_idle": "설치", + "button_processing": "설치 중", + "desc": "{{artifact}} {{version}}을(를) 설치하겠습니까?", + "title": "{{artifact}} 설치" + }, + "reinstall": { + "button_idle": "재설치", + "button_processing": "재설치 중", + "desc": "{{artifact}} {{version}}을(를) 재설치하겠습니까?", + "title": "{{artifact}} 재설치" + }, + "update": { + "button_idle": "업데이트", + "button_processing": "업데이트 중", + "title": "{{artifact}} 업데이트", + "desc": "{{artifact}} {{version}} 업데이트를 설치하겠습니까?" + }, + "no_hash": "이 플러그인은 해시 확인을 하지 않습니다, 설치에 따른 위험은 사용자가 감수해야 합니다." + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_other": "플러그인 {{count}}개 수정", + "update_other": "플러그인 {{count}}개 업데이트", + "reinstall_other": "플러그인 {{count}}개 재설치", + "install_other": "플러그인 {{count}}개 설치" + }, + "ok_button": { + "idle": "확인", + "loading": "작업 중" + }, + "confirm": "해당 수정을 적용하겠습니까?", + "description": { + "install": "{{name}} {{version}} 플러그인 설치", + "update": "{{name}}의 {{version}} 업데이트 설치", + "reinstall": "{{name}} {{version}} 재설치" + } + }, + "PluginListIndex": { + "plugin_actions": "플러그인 동작", + "reinstall": "재설치", + "reload": "다시 로드", + "uninstall": "설치 제거", + "show": "빠른 액세스 메뉴: 표시", + "hide": "빠른 액세스 메뉴: 숨김", + "update_all_other": "플러그인 {{count}}개 업데이트", + "no_plugin": "설치된 플러그인이 없습니다!", + "update_to": "{{name}}(으)로 업데이트" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "{{tag_name}} 업데이트를 설치할 수 있습니다!", + "error": "오류", + "plugin_load_error": { + "message": "{{name}} 플러그인 불러오기 오류", + "toast": "{{name}} 불러오기 오류" + }, + "plugin_uninstall": { + "button": "설치 제거", + "desc": "{{name}}을(를) 설치 제거하겠습니까?", + "title": "{{name}} 설치 제거" + }, + "plugin_update_other": "플러그인 {{count}}개를 업데이트 할 수 있습니다!", + "plugin_error_uninstall": "{{name}} 플러그인을 불러올 때 위와 같은 예외가 발생했습니다. 이는 보통 SteamUI 최신 버전에 맞는 플러그인 업데이트가 필요할 때 발생합니다. Decky 설정의 플러그인 섹션에서 업데이트가 있는지 확인하거나 설치 제거를 시도 해 보세요." + }, + "RemoteDebugging": { + "remote_cef": { + "label": "리모트 CEF 디버그 허용", + "desc": "네트워크의 모든 사용자에게 CEF 디버거에 대한 인증되지 않은 액세스 허용" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "콘솔 열기", + "label": "CEF 콘솔", + "desc": "CEF 콘솔을 엽니다. 디버그 전용입니다. 이 항목들은 위험 가능성이 있으므로 플러그인 개발자이거나 개발자의 가이드를 따를 경우에만 사용해야 합니다." + }, + "header": "기타", + "react_devtools": { + "ip_label": "IP", + "label": "React DevTools 활성화", + "desc": "React DevTools를 실행하고 있는 컴퓨터에 연결을 활성화합니다. 이 설정을 변경하면 Steam이 다시 로드됩니다. 활성화하기 전에 IP 주소를 설정하세요." + }, + "third_party_plugins": { + "button_install": "설치", + "button_zip": "검색", + "header": "서드파티 플러그인", + "label_desc": "URL", + "label_url": "URL에서 플러그인 설치", + "label_zip": "ZIP 파일에서 플러그인 설치" + }, + "valve_internal": { + "desc1": "Valve 내부 개발자 메뉴를 활성화합니다.", + "label": "Valve 내부 개발자 메뉴 활성화", + "desc2": "이 메뉴의 기능을 모른다면 어떤 것도 건드리지 마세요." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky 버전", + "header": "정보" + }, + "beta": { + "header": "베타 참가" + }, + "developer_mode": { + "label": "개발자 모드" + }, + "other": { + "header": "기타" + }, + "updates": { + "header": "업데이트" + }, + "notifications": { + "header": "알림", + "plugin_updates_label": "플러그인 업데이트 가능", + "decky_updates_label": "Decky 업데이트 가능" + } + }, + "SettingsIndex": { + "developer_title": "개발자", + "general_title": "일반", + "plugins_title": "플러그인" + }, + "Store": { + "store_contrib": { + "desc": "Decky 플러그인 스토어에 기여하고 싶다면 SteamDeckHomebrew/decky-plugin-template Github 저장소를 확인하세요. 개발 및 배포에 대한 정보는 README에서 확인할 수 있습니다.", + "label": "기여하기" + }, + "store_filter": { + "label": "필터", + "label_def": "모두" + }, + "store_search": { + "label": "검색" + }, + "store_sort": { + "label": "정렬", + "label_def": "최근 업데이트 순" + }, + "store_source": { + "desc": "모든 플러그인 소스 코드는 SteamDeckHomebrew/decky-plugin-database Github 저장소에서 확인할 수 있습니다.", + "label": "소스 코드" + }, + "store_tabs": { + "about": "정보", + "alph_asce": "알파벳순 (Z-A)", + "alph_desc": "알파벳순 (A-Z)", + "title": "검색" + }, + "store_testing_cta": "새로운 플러그인을 테스트하여 Decky Loader 팀을 도와주세요!", + "store_testing_warning": { + "desc": "이 스토어 채널을 사용하여 가장 최신 버전의 플러그인을 테스트할 수 있습니다. GitHub에 피드백을 남겨서 모든 사용자가 업데이트 할 수 있게 해주세요.", + "label": "테스트 스토어 채널에 오신 것을 환영합니다" + } + }, + "StoreSelect": { + "custom_store": { + "label": "사용자 지정 스토어", + "url_label": "URL" + }, + "store_channel": { + "custom": "사용자 지정", + "label": "스토어 배포 채널", + "default": "기본", + "testing": "테스트" + } + }, + "Updater": { + "decky_updates": "Decky 업데이트", + "no_patch_notes_desc": "이 버전에는 패치 노트가 없습니다", + "patch_notes_desc": "패치 노트", + "updates": { + "check_button": "업데이트 확인", + "checking": "확인 중", + "cur_version": "현재 버전: {{ver}}", + "install_button": "업데이트 설치", + "label": "업데이트", + "lat_version": "최신 상태: {{ver}} 실행 중", + "reloading": "다시 로드 중", + "updating": "업데이트 중" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "지정된 경로가 잘못되었습니다. 확인 후에 다시 입력해 주세요.", + "unknown": "알 수 없는 오류가 발생했습니다. Raw 오류: {{raw_error}}", + "perm_denied": "선택한 경로에 접근 할 수 없습니다. 선택한 폴더/파일 접근 권한이 유저(Steam Deck의 deck 유저)에 맞게 올바르게 설정 되었는지 확인하세요." + } + }, + "DropdownMultiselect": { + "button": { + "back": "뒤로" + } + }, + "TitleView": { + "settings_desc": "Decky 설정 열기", + "decky_store_desc": "Decky 스토어 열기" + } +} diff --git a/backend/src/locales/nl-NL.json b/backend/src/locales/nl-NL.json new file mode 100644 index 00000000..1adde308 --- /dev/null +++ b/backend/src/locales/nl-NL.json @@ -0,0 +1,243 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "Vooruitgave", + "stable": "Stabiel", + "label": "Update Kanaal", + "testing": "Test" + } + }, + "Developer": { + "5secreload": "Herlaad in 5 seconden", + "disabling": "Uitschakelen React DevTools", + "enabling": "Inschakelen React DevTools" + }, + "DropdownMultiselect": { + "button": { + "back": "Terug" + } + }, + "FilePickerError": { + "errors": { + "unknown": "Een onbekende fout is opgetreden. De ruwe fout is: {{raw_error}}", + "file_not_found": "Het opgegeven pad is niet geldig. Controleer het en voer het opnieuw correct in." + } + }, + "FilePickerIndex": { + "files": { + "all_files": "Alle bestanden", + "file_type": "Bestandstype", + "show_hidden": "Toon verborgen bestanden" + }, + "filter": { + "created_desc": "Gecreëerd ( Nieuwste)", + "modified_asce": "Veranderd (Oudste)", + "modified_desc": "Veranderd (Nieuwste)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Grootte (Kleinste)", + "size_desc": "Grootte (Grootste)", + "created_asce": "Gecreëerd (Oudste)" + }, + "folder": { + "label": "Map", + "select": "Gebruik deze map", + "show_more": "Toon meer bestanden" + } + }, + "PluginView": { + "hidden_one": "1 plug-in is verborgen in deze lijst", + "hidden_other": "{{count}} plug-ins zijn verborgen in deze lijst" + }, + "PluginListLabel": { + "hidden": "Verborgen in het snelmenu" + }, + "PluginCard": { + "plugin_install": "Installeren", + "plugin_no_desc": "Geen beschrijving gegeven.", + "plugin_version_label": "Plugin Versie", + "plugin_full_access": "Deze plug-in heeft volledige toegang tot je Steam Deck." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Installeren", + "button_processing": "Bezig met installeren", + "title": "Installeer {{artifact}}", + "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt installeren?" + }, + "no_hash": "Deze plug-in heeft geen hash, u installeert deze op eigen risico.", + "reinstall": { + "button_idle": "Herinstalleren", + "button_processing": "Bezig te herinstalleren", + "desc": "Weet je zeker dat je {{artifact}} {{version}} opnieuw wilt installeren?", + "title": "Installeer {{artifact}} opnieuw" + }, + "update": { + "button_idle": "Update", + "button_processing": "Bezig met updaten", + "title": "{{artifact}} bijwerken", + "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt updaten?" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Wijzig {{count}} plug-in", + "mixed_other": "Pas {{count}} plug-ins aan", + "update_one": "1 plugin bijwerken", + "update_other": "{{count}} plug-ins bijwerken", + "install_one": "Installeer 1 plugin", + "install_other": "Installeer {{count}} plugins", + "reinstall_one": "1 plugin opnieuw installeren", + "reinstall_other": "{{count}} plugins opnieuw installeren" + }, + "ok_button": { + "idle": "Bevestigen", + "loading": "Werkend" + }, + "confirm": "Weet u zeker dat u de volgende wijzigingen wilt aanbrengen?", + "description": { + "install": "Installeer {{name}} {{version}}", + "update": "Update {{name}} naar {{version}}", + "reinstall": "Installeer opnieuw {{name}} {{version}}" + } + }, + "PluginListIndex": { + "no_plugin": "Geen plugins geïnstalleerd!", + "plugin_actions": "Plugin Acties", + "reload": "Herladen", + "uninstall": "Verwijderen", + "update_to": "Update naar {{name}}", + "hide": "Snelle toegang: Verberg", + "update_all_one": "Update 1 plugin", + "update_all_other": "Update {{count}} plugins", + "reinstall": "Opnieuw installeren", + "show": "Snelle toegang: Toon" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Fout", + "plugin_load_error": { + "message": "Fout bij het laden van plugin {{name}}", + "toast": "Fout bij het laden van {{name}}" + }, + "plugin_uninstall": { + "button": "Verwijderen", + "desc": "Weet je zeker dat je {{name}} wilt verwijderen?", + "title": "Verwijder {{name}}" + }, + "plugin_update_one": "Updates beschikbaar voor 1 plugin!", + "plugin_update_other": "Updates beschikbaar voor {{count}} plugins!", + "decky_update_available": "Update naar {{tag_name}} beschikbaar!", + "plugin_error_uninstall": "Het laden van {{name}} veroorzaakte een uitzondering zoals hierboven weergegeven. Dit betekent meestal dat de plug-in een update vereist voor de nieuwe versie van SteamUI. Controleer of er een update aanwezig is of evalueer de verwijdering ervan in de Decky-instellingen, in het gedeelte Plug-ins." + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Sta ongeauthenticeerde toegang tot de CEF-foutopsporing toe aan iedereen in uw netwerk", + "label": "Externe CEF-foutopsporing toestaan" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Console openen", + "label": "CEF Bedieningsscherm", + "desc": "Opent de CEF-console. Alleen nuttig voor foutopsporingsdoeleinden. Dingen hier zijn potentieel gevaarlijk en mogen alleen worden gebruikt als u een ontwikkelaar van plug-ins bent, of hier door een ontwikkelaar naartoe wordt geleid." + }, + "header": "Andere", + "react_devtools": { + "ip_label": "IP", + "label": "Aanzetten React DevTools", + "desc": "Maakt verbinding met een computer met React DevTools mogelijk. Als je deze instelling wijzigt, wordt Steam opnieuw geladen. Stel het IP-adres in voordat u het inschakelt." + }, + "third_party_plugins": { + "header": "Plug-ins van derden", + "label_desc": "URL", + "label_url": "Installeer Plugin van URL", + "label_zip": "Installeer Plugin van Zip bestand", + "button_install": "Installeren", + "button_zip": "Bladeren" + }, + "valve_internal": { + "desc1": "Schakelt het interne ontwikkelaarsmenu van Valve in.", + "desc2": "Raak niets in dit menu aan tenzij u weet wat het doet.", + "label": "Valve Internal inschakelen" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky versie", + "header": "Over" + }, + "beta": { + "header": "Beta deelname" + }, + "developer_mode": { + "label": "Ontwikkelaars modus" + }, + "other": { + "header": "Overige" + }, + "updates": { + "header": "Nieuwe Versies" + } + }, + "SettingsIndex": { + "developer_title": "Ontwikkelaar", + "general_title": "Algemeen", + "plugins_title": "Plugins" + }, + "Store": { + "store_filter": { + "label": "Filter", + "label_def": "Alles" + }, + "store_search": { + "label": "Zoek" + }, + "store_sort": { + "label": "Sorteren", + "label_def": "Laatste Geupdate (Nieuwste)" + }, + "store_source": { + "label": "Bron Code", + "desc": "Alle broncode van de plug-in is beschikbaar in de SteamDeckHomebrew/decky-plugin-database-repository op GitHub." + }, + "store_tabs": { + "about": "Over", + "alph_asce": "Alfabetisch (Z naar A)", + "alph_desc": "Alfabetisch (A naar Z)", + "title": "Bladeren" + }, + "store_testing_cta": "Overweeg alsjeblieft om nieuwe plug-ins te testen om het Decky Loader-team te helpen!", + "store_contrib": { + "desc": "Als je wilt bijdragen aan de Decky Plugin winkel, kijk dan in de SteamDeckHomebrew/decky-plugin-template repository op GitHub. Informatie over ontwikkeling en distributie is beschikbaar in de README.", + "label": "Bijdragende" + } + }, + "StoreSelect": { + "custom_store": { + "label": "Aangepassingen winkel", + "url_label": "URL" + }, + "store_channel": { + "custom": "Aanpassingen", + "default": "Standaard", + "label": "Winkel Kanaal", + "testing": "Testen" + } + }, + "Updater": { + "patch_notes_desc": "Correctie opmerkingen", + "updates": { + "check_button": "Controleer op updates", + "checking": "Controleren", + "cur_version": "Huidige versie: {{ver}}", + "install_button": "Installeer Update", + "label": "Update", + "lat_version": "Up-to-date: loopt {{ver}}", + "reloading": "Herstarten", + "updating": "Aan het updaten" + }, + "decky_updates": "Decky Nieuwe Versies", + "no_patch_notes_desc": "geen correctie-opmerkingen voor deze versie" + } +} diff --git a/backend/src/locales/pl-PL.json b/backend/src/locales/pl-PL.json new file mode 100644 index 00000000..5231fa37 --- /dev/null +++ b/backend/src/locales/pl-PL.json @@ -0,0 +1,267 @@ +{ + "BranchSelect": { + "update_channel": { + "testing": "Testowy", + "label": "Kanał aktualizacji", + "stable": "Stabilny", + "prerelease": "Przedpremierowy" + } + }, + "Developer": { + "enabling": "Włączanie React DevTools", + "5secreload": "Ponowne załadowanie za 5 sekund", + "disabling": "Wyłączanie React DevTools" + }, + "DropdownMultiselect": { + "button": { + "back": "Powrót" + } + }, + "FilePickerError": { + "errors": { + "perm_denied": "Nie masz dostępu do podanego katalogu. Sprawdź, czy twój użytkownik (deck na Steam Deck) ma odpowiednie uprawnienia dostępu do określonego katalogu/pliku.", + "unknown": "Wystąpił nieznany błąd. Surowy błąd to {{raw_error}}", + "file_not_found": "Podana ścieżka jest nieprawidłowa. Sprawdź ją i wprowadź ponownie poprawnie." + } + }, + "FilePickerIndex": { + "file": { + "select": "Wybierz ten plik" + }, + "files": { + "all_files": "Wszystkie pliki", + "file_type": "Typ pliku", + "show_hidden": "Pokaż ukryte pliki" + }, + "filter": { + "created_asce": "Utworzono (najstarszy)", + "created_desc": "Utworzono (najnowszy)", + "modified_asce": "Zmodyfikowany (najstarszy)", + "modified_desc": "Zmodyfikowany (najnowszy)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Rozmiar (najmniejszy)", + "size_desc": "Rozmiar (największy)" + }, + "folder": { + "label": "Katalog", + "select": "Użyj tego katalogu", + "show_more": "Pokaż więcej plików" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Zmodyfikuj {{count}} plugin", + "mixed_few": "Zmodyfikuj {{count}} pluginy", + "mixed_many": "Zmodyfikuj {{count}} pluginów", + "reinstall_one": "Reinstaluj 1 plugin", + "reinstall_few": "Reinstaluj {{count}} pluginy", + "reinstall_many": "Reinstaluj {{count}} pluginów", + "install_one": "Zainstaluj 1 plugin", + "install_few": "Zainstaluj {{count}} pluginy", + "install_many": "Zainstaluj {{count}} pluginów", + "update_one": "Zaktualizuj 1 plugin", + "update_few": "Zaktualizuj {{count}} pluginy", + "update_many": "Zaktualizuj {{count}} pluginów" + }, + "confirm": "Czy na pewno chcesz wprowadzić następujące modyfikacje?", + "description": { + "install": "Zainstaluj {{name}} {{version}}", + "reinstall": "Reinstaluj {{name}} {{version}}", + "update": "Zaktualizuj {{name}} do {{version}}" + }, + "ok_button": { + "idle": "Potwierdź", + "loading": "W toku" + } + }, + "PluginCard": { + "plugin_install": "Zainstaluj", + "plugin_no_desc": "Brak opisu.", + "plugin_version_label": "Wersja pluginu", + "plugin_full_access": "Ten plugin ma pełny dostęp do twojego Steam Decka." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Zainstaluj", + "button_processing": "Instalowanie", + "desc": "Czy na pewno chcesz zainstalować {{artifact}} {{version}}?", + "title": "Zainstaluj {{artifact}}" + }, + "reinstall": { + "button_idle": "Reinstaluj", + "button_processing": "Reinstalowanie", + "desc": "Czy na pewno chcesz ponownie zainstalować {{artifact}} {{version}}?", + "title": "Reinstaluj {{artifact}}" + }, + "update": { + "button_idle": "Aktualizacja", + "button_processing": "Aktualizowanie", + "desc": "Czy na pewno chcesz zaktualizować {{artifact}} {{version}}?", + "title": "Zaktualizuj {{artifact}}" + }, + "no_hash": "Ten plugin nie ma hasha, instalujesz go na własne ryzyko." + }, + "PluginListIndex": { + "hide": "Szybki dostęp: Ukryj", + "no_plugin": "Brak zainstalowanych pluginów!", + "reload": "Załaduj ponownie", + "update_all_one": "Zaktualizuj 1 plugin", + "update_all_few": "Zaktualizuj {{count}} pluginy", + "update_all_many": "Zaktualizuj {{count}} pluginów", + "plugin_actions": "Akcje pluginów", + "reinstall": "Reinstalacja", + "show": "Szybki dostęp: Pokaż", + "uninstall": "Odinstaluj", + "update_to": "Zaktualizuj do {{name}}" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Dostępna aktualizacja do {{tag_name}}!", + "error": "Błąd", + "plugin_error_uninstall": "Ładowanie {{name}} spowodowało wyjątek, jak pokazano powyżej. Zwykle oznacza to, że plugin wymaga aktualizacji do nowej wersji SteamUI. Sprawdź, czy aktualizacja jest obecna lub rozważ usunięcie go w ustawieniach Decky, w sekcji Pluginy.", + "plugin_load_error": { + "message": "Błąd ładowania plugin {{name}}", + "toast": "Błąd ładowania {{name}}" + }, + "plugin_uninstall": { + "button": "Odinstaluj", + "title": "Odinstaluj {{name}}", + "desc": "Czy na pewno chcesz odinstalować {{name}}?" + }, + "plugin_update_one": "Aktualizacje dostępne dla 1 pluginu!", + "plugin_update_few": "Aktualizacje dostępne dla {{count}} pluginów!", + "plugin_update_many": "Aktualizacje dostępne dla {{count}} pluginów!" + }, + "PluginListLabel": { + "hidden": "Ukryty w menu szybkiego dostępu" + }, + "PluginView": { + "hidden_one": "1 plugin jest ukryty na tej liście", + "hidden_few": "{{count}} pluginy jest ukryty na tej liście", + "hidden_many": "{{count}} pluginów jest ukryty na tej liście" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Zezwalaj na nieuwierzytelniony dostęp do debugera CEF wszystkim osobom w Twojej sieci", + "label": "Zezwól na zdalne debugowanie CEF" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Otwórz konsolę", + "desc": "Otwiera konsolę CEF. Przydatne tylko do celów debugowania. Rzeczy tutaj są potencjalnie niebezpieczne i powinny być używane tylko wtedy, gdy jesteś twórcą wtyczek lub zostałeś tu przez kogoś skierowany.", + "label": "Konsola CEF" + }, + "header": "Inne", + "react_devtools": { + "desc": "Umożliwia połączenie z komputerem z uruchomionym React DevTools. Zmiana tego ustawienia spowoduje ponowne załadowanie Steam. Ustaw adres IP przed włączeniem.", + "ip_label": "IP", + "label": "Włącz React DevTools" + }, + "third_party_plugins": { + "button_install": "Zainstaluj", + "button_zip": "Przeglądaj", + "header": "Pluginy zewnętrzne", + "label_desc": "URL", + "label_url": "Zainstaluj plugin z adresu URL", + "label_zip": "Zainstaluj plugin z pliku ZIP" + }, + "valve_internal": { + "desc1": "Włącza wewnętrzne menu programisty Valve.", + "desc2": "Nie dotykaj niczego w tym menu, chyba że wiesz, co robi.", + "label": "Włącz Valve Internal" + } + }, + "SettingsGeneralIndex": { + "notifications": { + "decky_updates_label": "Dostępna aktualizacja Decky", + "header": "Powiadomienia", + "plugin_updates_label": "Dostępne aktualizacje pluginów" + }, + "other": { + "header": "Inne" + }, + "updates": { + "header": "Aktualizacje" + }, + "about": { + "header": "Informacje", + "decky_version": "Wersja Decky" + }, + "beta": { + "header": "Udział w becie" + }, + "developer_mode": { + "label": "Tryb dewelopera" + } + }, + "SettingsIndex": { + "developer_title": "Deweloper", + "general_title": "Ogólne", + "plugins_title": "Pluginy" + }, + "Store": { + "store_contrib": { + "desc": "Jeśli chcesz przyczynić się do rozwoju Decky Plugin Store, sprawdź repozytorium SteamDeckHomebrew/decky-plugin-template na GitHub. Informacje na temat rozwoju i dystrybucji są dostępne w pliku README.", + "label": "Współtworzenie" + }, + "store_filter": { + "label": "Filtr", + "label_def": "Wszystko" + }, + "store_search": { + "label": "Szukaj" + }, + "store_sort": { + "label": "Sortowanie", + "label_def": "Ostatnia aktualizacja (najnowsza)" + }, + "store_source": { + "desc": "Cały kod źródłowy pluginów jest dostępny w repozytorium SteamDeckHomebrew/decky-plugin-database na GitHub.", + "label": "Kod źródłowy" + }, + "store_tabs": { + "alph_asce": "Alfabetycznie (od Z do A)", + "alph_desc": "Alfabetycznie (od A do Z)", + "title": "Przeglądaj", + "about": "Informacje" + }, + "store_testing_cta": "Rozważ przetestowanie nowych pluginów, aby pomóc zespołowi Decky Loader!", + "store_testing_warning": { + "label": "Witamy w Testowym Kanale Sklepu", + "desc": "Możesz użyć tego kanału sklepu do testowania najnowszych wersji pluginów. Pamiętaj, aby zostawić opinię na GitHub, aby plugin mogła zostać zaktualizowana dla wszystkich użytkowników." + } + }, + "StoreSelect": { + "custom_store": { + "label": "Niestandardowy sklep", + "url_label": "URL" + }, + "store_channel": { + "custom": "Niestandardowy", + "default": "Domyślny", + "label": "Kanał sklepu", + "testing": "Testowy" + } + }, + "Updater": { + "decky_updates": "Aktualizacje Decky", + "no_patch_notes_desc": "Brak informacji o poprawkach dla tej wersji", + "patch_notes_desc": "Opis zmian", + "updates": { + "check_button": "Sprawdź aktualizacje", + "checking": "Sprawdzanie", + "cur_version": "Aktualna wersja: {{ver}}", + "install_button": "Zainstaluj aktualizację", + "label": "Aktualizacje", + "lat_version": "Aktualizacje zainstalowane. Aktualna wersja: {{ver}}", + "reloading": "Ponowne ładowanie", + "updating": "Aktualizowanie" + } + }, + "TitleView": { + "settings_desc": "Otwórz ustawienia Decky", + "decky_store_desc": "Otwórz sklep Decky" + } +} diff --git a/backend/src/locales/pt-BR.json b/backend/src/locales/pt-BR.json new file mode 100644 index 00000000..2a7c173b --- /dev/null +++ b/backend/src/locales/pt-BR.json @@ -0,0 +1,259 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "Pré-lançamento", + "stable": "Estável", + "testing": "Em Teste", + "label": "Canal de Atualização" + } + }, + "Developer": { + "5secreload": "Recarregando em 5 segundos", + "enabling": "Habilitando React DevTools", + "disabling": "Desabilitando React DevTools" + }, + "FilePickerIndex": { + "folder": { + "select": "Use esta pasta", + "label": "Pasta", + "show_more": "Mostrar mais arquivos" + }, + "files": { + "show_hidden": "Mostrar Arquivos Ocultos", + "all_files": "Todos os arquivos", + "file_type": "Formato de arquivo" + }, + "filter": { + "created_asce": "Criado (Mais antigo)", + "created_desc": "Criado (Mais recente)", + "modified_asce": "Alterado (Mais antigo)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Tamanho (Menor)", + "size_desc": "Tamanho (Maior)", + "modified_desc": "Alterado (Mais recente)" + }, + "file": { + "select": "Selecione este arquivo" + } + }, + "PluginListLabel": { + "hidden": "Oculto no menu de acesso rápido" + }, + "PluginCard": { + "plugin_full_access": "Este plugin tem acesso total ao seu Steam Deck.", + "plugin_install": "Instalar", + "plugin_no_desc": "Nenhuma descrição fornecida.", + "plugin_version_label": "Versão do plugin" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalar", + "button_processing": "Instalando", + "desc": "Você tem certeza que deseja instalar {{artifact}} {{version}}?", + "title": "Instalar {{artifact}}" + }, + "reinstall": { + "button_idle": "Reinstalar", + "button_processing": "Reinstalando", + "desc": "Tem certeza que voce deseja reinstalar {{artifact}} {{version}}?", + "title": "Reinstalar {{artifact}}" + }, + "update": { + "button_idle": "Atualizar", + "button_processing": "Atualizando", + "desc": "Tem certeza que voce deseja atualizar {{artifact}} {{version}}?", + "title": "Atualizar {{artifact}}" + }, + "no_hash": "Este plugin não tem um hash, você o está instalando por sua conta em risco." + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Modificar {{count}} plugin", + "mixed_many": "Modificar {{count}} plugins", + "mixed_other": "Modificar {{count}} plugins", + "update_one": "Atualizar 1 plugin", + "update_many": "Atualizar {{count}} plugins", + "update_other": "Atualizar {{count}} plugins", + "install_one": "Instalar 1 plugin", + "install_many": "Instalar {{count}} plugins", + "install_other": "Instalar {{count}} plugins", + "reinstall_one": "Reinstalar 1 plugin", + "reinstall_many": "Reinstalar {{count}} plugins", + "reinstall_other": "Reinstalar {{count}} plugins" + }, + "ok_button": { + "idle": "Confirmar", + "loading": "Carregando" + }, + "description": { + "install": "Instalar {{name}} {{version}}", + "update": "Atualizar {{name}} para {{version}}", + "reinstall": "Reinstalar {{name}} {{version}}" + }, + "confirm": "Tem certeza que deseja fazer as seguintes modificações?" + }, + "PluginListIndex": { + "no_plugin": "Nenhum plugin instalado!", + "plugin_actions": "Ações do plugin", + "reinstall": "Reinstalar", + "reload": "Recarregar", + "uninstall": "Desinstalar", + "update_to": "Atualizar para {{name}}", + "show": "Acesso Rápido: Mostrar", + "update_all_one": "Atualizar 1 plugin", + "update_all_many": "Atualizar {{count}} plugins", + "update_all_other": "Atualizar {{count}} plugins", + "hide": "Acesso Rápido: Ocultar" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Erro", + "plugin_load_error": { + "message": "Erro ao carregar o plugin {{name}}", + "toast": "Erro ao carregar {{name}}" + }, + "plugin_uninstall": { + "button": "Desinstalar", + "desc": "Você tem certeza que deseja desinstalar {{name}}?", + "title": "Desinstalar {{name}}" + }, + "decky_update_available": "Atualização para {{tag_name}} disponível!", + "plugin_error_uninstall": "Um erro aconteceu ao carregar {{name}}, como mostrado acima. Isso normalmente significa que o plugin precisa de uma atualização para a nova versão do SteamUI. Confira se existe uma atualização ou avalie a remoção do plugin nas configurações do Decky, na sessão de plugins.", + "plugin_update_one": "Atualização disponível para 1 plugin!", + "plugin_update_many": "Atualizações disponíveis para {{count}} plugins!", + "plugin_update_other": "Atualizações disponíveis para {{count}} plugins!" + }, + "RemoteDebugging": { + "remote_cef": { + "label": "Permitir Depuração CEF Demota", + "desc": "Permitir acesso não autenticato ao depurador CEF para qualquer um na sua rede" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Abrir o Console", + "label": "Console CEF", + "desc": "Abre o Console CEF. Somente útil para fins de depuração. O material aqui é potencialmente perigoso e só deve ser usado se você for um desenvolvedor de plugin, ou direcionado até aqui por um." + }, + "header": "Outros", + "react_devtools": { + "desc": "Habilita a conexão a um computador executando React DevTools. Alterar essa configuração irá recarregar a Steam. Defina o endereço IP antes de habilitar.", + "ip_label": "IP", + "label": "Habilitar React DevTools" + }, + "third_party_plugins": { + "button_install": "Instalar", + "button_zip": "Navegar", + "header": "Plugins de terceiros", + "label_url": "Instalar Plugin a partir da URL", + "label_zip": "Instalar Plugin a partir de um arquivo ZIP", + "label_desc": "URL" + }, + "valve_internal": { + "desc1": "Habilita o menu interno de desenvolvedor da Valve.", + "desc2": "Não toque em nada neste menu, a não ser que você saiba o que está fazendo.", + "label": "Habilitar Menu Interno da Valve" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Versão do Decky", + "header": "Sobre" + }, + "developer_mode": { + "label": "Modo Deselvolvedor" + }, + "other": { + "header": "Outros" + }, + "updates": { + "header": "Atualizações" + }, + "beta": { + "header": "Participação no Beta" + }, + "notifications": { + "decky_updates_label": "Atualização do Decky disponível", + "header": "Noificações", + "plugin_updates_label": "Atualizações de Plugin disponíveis" + } + }, + "SettingsIndex": { + "developer_title": "Desenvolvedor", + "general_title": "Geral", + "plugins_title": "Plugins" + }, + "Store": { + "store_contrib": { + "label": "Contribuindo", + "desc": "Se você deseja contribuir para a Loja de Plugins para o Decky, confira o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Informações sobre o desenvolvimento e distribuição estão disponíveis no README." + }, + "store_filter": { + "label": "Filtros", + "label_def": "Todos" + }, + "store_search": { + "label": "Buscar" + }, + "store_sort": { + "label": "Ordenar", + "label_def": "Último atualizado (Mais recente)" + }, + "store_source": { + "desc": "Todos os códigos fonte dos plugins estão disponíveis no repositório SteamDeckHomebrew/decky-plugin-database no GitHub.", + "label": "Código Fonte" + }, + "store_tabs": { + "about": "Sobre", + "alph_desc": "Alfabética (A - Z)", + "title": "Navegar", + "alph_asce": "Alfabética (Z - A)" + }, + "store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!" + }, + "StoreSelect": { + "custom_store": { + "label": "Loja Personalizada", + "url_label": "URL" + }, + "store_channel": { + "custom": "Personalizada", + "default": "Padrão", + "label": "Canal da Loja", + "testing": "Em Teste" + } + }, + "Updater": { + "no_patch_notes_desc": "nenhuma nota de alteração para esta versão", + "patch_notes_desc": "Notas de alteração", + "updates": { + "check_button": "Buscar Atualizações", + "checking": "Buscando", + "cur_version": "Versão atual: {{ver}}", + "install_button": "Instalar Atualização", + "label": "Atualizações", + "lat_version": "Atualizado: rodando {{ver}}", + "reloading": "Recarregando", + "updating": "Atualizando" + }, + "decky_updates": "Atualizações do Decky" + }, + "PluginView": { + "hidden_one": "1 plugin está oculto nesta lista", + "hidden_many": "{{count}} plugins estão ocultos nesta lista", + "hidden_other": "{{count}} plugins estão ocultos nesta lista" + }, + "DropdownMultiselect": { + "button": { + "back": "Voltar" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "O caminho especificado não é válido. Por favor, confira e reinsira corretamente.", + "unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}", + "perm_denied": "Você não tem acesso à este diretório. Por favor, verifiquei se seu usuário (deck no Steam Deck) tem as permissões necessárias para acessar este arquivo/pasta." + } + } +} diff --git a/backend/src/locales/pt-PT.json b/backend/src/locales/pt-PT.json new file mode 100644 index 00000000..9b273569 --- /dev/null +++ b/backend/src/locales/pt-PT.json @@ -0,0 +1,222 @@ +{ + "FilePickerIndex": { + "folder": { + "select": "Usar esta pasta" + } + }, + "PluginView": { + "hidden_one": "1 plugin está oculto desta lista", + "hidden_many": "{{count}} plugins estão ocultos desta lista", + "hidden_other": "{{count}} plugins estão ocultos desta lista" + }, + "PluginCard": { + "plugin_full_access": "Este plugin tem acesso total à tua Steam Deck.", + "plugin_install": "Instalar", + "plugin_version_label": "Versão do plugin", + "plugin_no_desc": "Não tem descrição." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalar", + "button_processing": "Instalação em curso", + "title": "Instalar {{artifact}}", + "desc": "De certeza que queres instalar {{artifact}} {{version}}?" + }, + "reinstall": { + "button_idle": "Reinstalar", + "button_processing": "Reinstalação em curso", + "title": "Reinstalar {{artifact}}", + "desc": "De certeza que queres reinstalar {{artifact}} {{version}}?" + }, + "update": { + "button_idle": "Actualizar", + "button_processing": "Actualização em curso", + "title": "Actualizar {{artifact}}", + "desc": "De certeza que queres actualizar {{artifact}} {{version}}?" + }, + "no_hash": "Este plugin não tem um hash, estás a instalá-lo por tua conta e risco." + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Alterar 1 plugin", + "mixed_many": "Alterar {{count}} plugins", + "mixed_other": "Alterar {{count}} plugins", + "update_one": "Actualizar 1 plugin", + "update_many": "Actualizar {{count}} plugins", + "update_other": "Actualizar {{count}} plugins", + "reinstall_one": "Reinstalar 1 plugin", + "reinstall_many": "Reinstalar {{count}} plugins", + "reinstall_other": "Reinstalar {{count}} plugins", + "install_one": "Instalar 1 plugin", + "install_many": "Instalar {{count}} plugins", + "install_other": "Instalar {{count}} plugins" + }, + "ok_button": { + "idle": "Confirmar", + "loading": "Em curso" + }, + "description": { + "install": "Instalar {{name}} {{version}}", + "update": "Actualizar {{name}} para {{version}}", + "reinstall": "Reinstalar {{name}} {{version}}" + }, + "confirm": "De certeza que queres fazer as seguintes alterações?" + }, + "PluginListIndex": { + "no_plugin": "Nenhum plugin instalado!", + "reinstall": "Reinstalar", + "uninstall": "Desinstalar", + "update_to": "Actualizar para {{name}}", + "update_all_one": "Actualizar 1 plugin", + "update_all_many": "Actualizar {{count}} plugins", + "update_all_other": "Actualizar {{count}} plugins", + "plugin_actions": "Operações de plugin", + "reload": "Recarregar", + "show": "Acesso rápido: Mostrar", + "hide": "Acesso rápido: Ocultar" + }, + "BranchSelect": { + "update_channel": { + "stable": "Estável", + "testing": "Em teste", + "label": "Canal de actualização", + "prerelease": "Pré-lançamento" + } + }, + "Developer": { + "5secreload": "Vai recarregar em 5 segundos", + "disabling": "Desactivando React DevTools", + "enabling": "Activando React DevTools" + }, + "PluginListLabel": { + "hidden": "Oculto do menu de acesso rápido" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Erro", + "plugin_load_error": { + "message": "Erro ao carregar o plugin {{name}}", + "toast": "Erro ao carregar {{name}}" + }, + "plugin_uninstall": { + "button": "Desinstalar", + "title": "Desinstalar {{name}}", + "desc": "De certeza que queres desinstalar {{name}}?" + }, + "decky_update_available": "Está disponível uma nova versão de {{tag_name}} !", + "plugin_update_one": "1 plugin tem actualizações disponíveis!", + "plugin_update_many": "{{count}} plugins têm actualizações disponíveis!", + "plugin_update_other": "{{count}} plugins têm actualizações disponíveis!", + "plugin_error_uninstall": "Houve uma excepção ao carregar {{name}}, como mostrado em cima. Pode ter sido porque o plugin requere a última versão do SteamUI. Verifica se há uma actualização disponível ou desinstala o plugin nas definições do Decky." + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Abrir consola", + "label": "Consola CEF", + "desc": "Abre a consola do CEF. Só é útil para efeitos de debugging. Pode ser perigosa e só deve ser usada se és um desenvolvedor de plugins, ou se foste aqui indicado por um desenvolvedor." + }, + "header": "Outros", + "react_devtools": { + "desc": "Permite a conecção a um computador que está a correr React DevTools. Mudar esta definição vai recarregar o Steam. Define o endereço de IP antes de activar.", + "ip_label": "IP", + "label": "Activar React DevTools" + }, + "third_party_plugins": { + "button_install": "Instalar", + "button_zip": "Navegar", + "header": "Plugins de terceiros", + "label_desc": "URl", + "label_url": "Instalar plugin a partir dum URL", + "label_zip": "Instalar plugin a partir dum ficheiro ZIP" + }, + "valve_internal": { + "label": "Activar menu interno da Valve", + "desc1": "Activa o menu interno de programador da Valve.", + "desc2": "Não toques em nada deste menu se não souberes a sua função." + } + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Permitir acesso não autenticado ao debugger do CEF a qualquer pessoa na tua rede", + "label": "Permitir debugging remoto do CEF" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Versão do Decky", + "header": "Sobre" + }, + "beta": { + "header": "Participação na versão Beta" + }, + "developer_mode": { + "label": "Modo de programador" + }, + "other": { + "header": "Outros" + }, + "updates": { + "header": "Actualizações" + } + }, + "SettingsIndex": { + "developer_title": "Programador", + "general_title": "Geral", + "plugins_title": "Plugins" + }, + "Store": { + "store_contrib": { + "label": "Contribuir", + "desc": "Se queres contribuir com um novo plugin, vai ao repositório SteamDeckHomebrew/decky-plugin-template no GitHub. No README, podes encontrar mais informação sobre desenvolvimento e distribuição." + }, + "store_filter": { + "label": "Filtro", + "label_def": "Todos" + }, + "store_search": { + "label": "Procurar" + }, + "store_sort": { + "label": "Ordenar", + "label_def": "Última actualização (mais recente)" + }, + "store_source": { + "label": "Código fonte", + "desc": "O código fonte de cada plugin está disponível no repositório SteamDeckHomebrew/decky-plugin-database no GitHub." + }, + "store_tabs": { + "about": "Sobre", + "alph_asce": "Alfabeticamente (Z-A)", + "alph_desc": "Alfabeticamente (A-Z)", + "title": "Navegar" + }, + "store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!" + }, + "StoreSelect": { + "custom_store": { + "url_label": "URL", + "label": "Loja personalizada" + }, + "store_channel": { + "custom": "Personalizada", + "default": "Standard", + "testing": "Em teste", + "label": "Canal de loja" + } + }, + "Updater": { + "decky_updates": "Actualizações do Decky", + "no_patch_notes_desc": "sem registo de alterações desta versão", + "patch_notes_desc": "Registo de alterações", + "updates": { + "check_button": "Procurar actualizações", + "checking": "Busca de actualizações em curso", + "cur_version": "Versão actual: {{ver}}", + "label": "Actualizações", + "lat_version": "Actualizado: a correr {{ver}}", + "updating": "Actualização em curso", + "reloading": "Recarregar", + "install_button": "Instalar actualização" + } + } +} diff --git a/backend/src/locales/ru-RU.json b/backend/src/locales/ru-RU.json new file mode 100644 index 00000000..776ffa12 --- /dev/null +++ b/backend/src/locales/ru-RU.json @@ -0,0 +1,267 @@ +{ + "MultiplePluginsInstallModal": { + "title": { + "update_one": "Переустановить {{count}} плагин", + "update_few": "Переустановить {{count}} плагинов", + "update_many": "Переустановить {{count}} плагинов", + "reinstall_one": "Переустановить {{count}} плагин", + "reinstall_few": "Переустановить {{count}} плагинов", + "reinstall_many": "Переустановить {{count}} плагинов", + "install_one": "Установить {{count}} плагин", + "install_few": "Установить {{count}} плагинов", + "install_many": "Установить {{count}} плагинов", + "mixed_one": "Изменить {{count}} плагин", + "mixed_few": "Изменить {{count}} плагинов", + "mixed_many": "Изменить {{count}} плагинов" + }, + "description": { + "install": "Установить {{name}} {{version}}", + "reinstall": "Переустановить {{name}} {{version}}", + "update": "Обновить с {{name}} на {{version}}" + }, + "confirm": "Вы уверены, что хотите внести следующие изменения?", + "ok_button": { + "idle": "Подтвердить", + "loading": "В процессе" + } + }, + "PluginListIndex": { + "update_all_one": "Обновить {{count}} плагин", + "update_all_few": "Обновить {{count}} плагинов", + "update_all_many": "Обновить {{count}} плагинов", + "hide": "Быстрый доступ: Скрыть", + "reload": "Перезагрузить", + "uninstall": "Удалить", + "update_to": "Обновить на {{name}}", + "show": "Быстрый доступ: Показать", + "plugin_actions": "Действия с плагинами", + "no_plugin": "Не установлено ни одного плагина!", + "reinstall": "Переустановить" + }, + "PluginLoader": { + "plugin_update_one": "Обновления доступны для {{count}} плагина!", + "plugin_update_few": "Обновления доступны для {{count}} плагинов!", + "plugin_update_many": "Обновления доступны для {{count}} плагинов!", + "plugin_error_uninstall": "Загрузка {{name}} вызвала исключение, указанное выше. Обычно это означает, что плагин требует обновления для новой версии SteamUI. Проверьте наличие обновления или попробуйте его удалить в настройках Decky, в разделе Плагины.", + "plugin_load_error": { + "message": "Ошибка загрузки плагина {{name}}", + "toast": "Ошибка загрузки {{name}}" + }, + "plugin_uninstall": { + "button": "Удалить", + "desc": "Вы уверены, что хотите удалить {{name}}?", + "title": "Удалить {{name}}" + }, + "decky_title": "Decky", + "decky_update_available": "Доступно обновление на {{tag_name}}!", + "error": "Ошибка" + }, + "PluginView": { + "hidden_one": "{{count}} плагин скрыт из списка", + "hidden_few": "{{count}} плагинов скрыт из списка", + "hidden_many": "{{count}} плагинов скрыт из списка" + }, + "FilePickerIndex": { + "files": { + "show_hidden": "Показать скрытые файлы", + "all_files": "Все файлы", + "file_type": "Тип файла" + }, + "filter": { + "created_asce": "Создан (самый старый)", + "modified_asce": "Модифицирован (самый новый)", + "modified_desc": "Модифицирован (самый старый)", + "size_asce": "Размер (самый малый)", + "size_desc": "Размер (самый большой)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "created_desc": "Создан (самый новый)" + }, + "folder": { + "label": "Папка", + "show_more": "Показать больше файлов", + "select": "Использовать этот каталог" + }, + "file": { + "select": "Выберите этот файл" + } + }, + "PluginCard": { + "plugin_install": "Установить", + "plugin_no_desc": "Нет описания.", + "plugin_version_label": "Версия плагина", + "plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck." + }, + "PluginInstallModal": { + "install": { + "button_processing": "Установка", + "title": "Установить {{artifact}}", + "button_idle": "Установить", + "desc": "Вы уверены, что хотите установить {{artifact}} {{version}}?" + }, + "no_hash": "У данного плагина отсутствует хэш, устанавливайте на свой страх и риск.", + "reinstall": { + "title": "Переустановить {{artifact}}", + "desc": "Вы уверены, что хотите переустановить {{artifact}} {{version}}?", + "button_idle": "Переустановить", + "button_processing": "Переустановка" + }, + "update": { + "button_idle": "Обновить", + "button_processing": "Обновление", + "desc": "Вы уверены, что хотите обновить {{artifact}} {{version}}?", + "title": "Обновить {{artifact}}" + } + }, + "PluginListLabel": { + "hidden": "Скрыто из меню быстрого доступа" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Разрешить неаутентифицированный доступ к отладчику CEF всем в вашей сети", + "label": "Разрешить удаленную отладку CEF" + } + }, + "SettingsDeveloperIndex": { + "header": "Другое", + "third_party_plugins": { + "button_install": "Установить", + "label_zip": "Установить плагин из ZIP файла", + "label_url": "Установить плагин из URL", + "button_zip": "Обзор", + "header": "Сторонние плагины", + "label_desc": "Ссылка" + }, + "react_devtools": { + "ip_label": "IP", + "desc": "Позволяет подключиться к компьютеру, на котором работает React DevTools. Изменение этого параметра приведет к перезагрузке Steam. Установите IP-адрес перед включением.", + "label": "Включить React DevTools" + }, + "cef_console": { + "button": "Открыть консоль", + "desc": "Открывает консоль CEF. Полезно только для целей отладки. Настройки здесь потенциально опасны и должны использоваться только в том случае, если вы являетесь разработчиком плагинов или направленны сюда одним из них.", + "label": "CEF Консоль" + }, + "valve_internal": { + "desc1": "Включает внутреннее меню разработчика Valve.", + "label": "Включить Valve Internal", + "desc2": "Ничего не трогайте в этом меню, если не знаете, что оно делает." + } + }, + "SettingsGeneralIndex": { + "beta": { + "header": "Бета программа" + }, + "developer_mode": { + "label": "Режим разработчика" + }, + "other": { + "header": "Другое" + }, + "about": { + "decky_version": "Версия Decky", + "header": "Информация" + }, + "updates": { + "header": "Обновления" + }, + "notifications": { + "decky_updates_label": "Обновление Decky доступно", + "header": "Уведомления", + "plugin_updates_label": "Доступны обновления плагинов" + } + }, + "Store": { + "store_sort": { + "label": "Сортировка", + "label_def": "Последнее обновление(самые новые)" + }, + "store_source": { + "label": "Исходный код", + "desc": "Весь исходный код плагина доступен в репозитории SteamDeckHomebrew/decky-plugin-database на GitHub." + }, + "store_tabs": { + "about": "Информация", + "alph_desc": "По алфавиту (A - Z)", + "title": "Обзор", + "alph_asce": "По алфавиту (Z - A)" + }, + "store_testing_cta": "Пожалуйста, рассмотрите возможность тестирования новых плагинов, чтобы помочь команде Decky Loader!", + "store_contrib": { + "desc": "Если вы хотите внести свой вклад в магазин плагинов Decky, проверьте репозиторий SteamDeckHomebrew/decky-plugin-template на GitHub. Информация о разработке и распространении доступна в README.", + "label": "Помощь проекту" + }, + "store_filter": { + "label": "Фильтр", + "label_def": "Все" + }, + "store_search": { + "label": "Поиск" + }, + "store_testing_warning": { + "label": "Добро пожаловать в тестовый канал магазина", + "desc": "Вы можете использовать этот канал магазина для тестирования новейших версий плагинов. Не забудьте оставить отзыв на GitHub, чтобы плагин можно было обновить для всех пользователей." + } + }, + "StoreSelect": { + "custom_store": { + "label": "Сторонний магазин", + "url_label": "URL" + }, + "store_channel": { + "custom": "Сторонний", + "default": "По-умолчанию", + "label": "Канал магазина", + "testing": "Тестовый" + } + }, + "Updater": { + "decky_updates": "Обновления Decky", + "no_patch_notes_desc": "нет примечаний к патчу для этой версии", + "updates": { + "check_button": "Проверить обновления", + "checking": "Проверка", + "cur_version": "Текущая версия: {{ver}}", + "updating": "Обновление", + "install_button": "Установить обновление", + "label": "Обновления", + "lat_version": "Обновлено: версия {{ver}}", + "reloading": "Перезагрузка" + }, + "patch_notes_desc": "Примечания к патчу" + }, + "FilePickerError": { + "errors": { + "perm_denied": "У вас нет доступа к указанному каталогу.. Пожалуйста, проверьте имеет ли пользователь (deck на Steam Deck) соответствующие права доступа к указанной папке/файлу.", + "file_not_found": "Указан недействительный путь. Пожалуйста, проверьте его и повторите ввод.", + "unknown": "Произошла неизвестная ошибка. Текст ошибки: {{raw_error}}" + } + }, + "DropdownMultiselect": { + "button": { + "back": "Назад" + } + }, + "BranchSelect": { + "update_channel": { + "prerelease": "Предрелиз", + "stable": "Стабильный", + "testing": "Тестовый", + "label": "Канал обновлений" + } + }, + "Developer": { + "5secreload": "Перезагрузка через 5 секунд", + "disabling": "Выключение React DevTools", + "enabling": "Включение React DevTools" + }, + "SettingsIndex": { + "developer_title": "Разработчик", + "general_title": "Общее", + "plugins_title": "Плагины" + }, + "TitleView": { + "decky_store_desc": "Открыть магазин Decky", + "settings_desc": "Открыть настройки Decky" + } +} diff --git a/backend/src/locales/sq-AL.json b/backend/src/locales/sq-AL.json new file mode 100644 index 00000000..fe9d7eec --- /dev/null +++ b/backend/src/locales/sq-AL.json @@ -0,0 +1,131 @@ +{ + "SettingsDeveloperIndex": { + "react_devtools": { + "ip_label": "IP", + "label": "Aktivizo React DevTools" + }, + "third_party_plugins": { + "button_zip": "Kërko", + "header": "Shtesa të Huaj", + "button_install": "Instalo", + "label_desc": "URL", + "label_url": "Instalo Shtes Nga URL", + "label_zip": "Instalo Shtes Nga ZIP" + } + }, + "BranchSelect": { + "update_channel": { + "stable": "Fiksuar", + "label": "Kanali Përditësimet" + } + }, + "FilePickerIndex": { + "folder": { + "select": "Përdore këtë folder" + } + }, + "PluginCard": { + "plugin_install": "Instalo", + "plugin_version_label": "Versioni Shteses" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalo", + "button_processing": "Instalohet", + "desc": "Je i sigurt që don ta instalojsh {{artifact}} {{version}}?", + "title": "Instalo {{artifact}}" + }, + "no_hash": "Ky shtesë nuk ka hash, ti e instalon me rrezikun tuaj.", + "reinstall": { + "button_idle": "Riinstalo", + "button_processing": "Riinstalohet", + "desc": "Je i sigurt a don ta riinstalojsh {{artifact}} {{version}}?", + "title": "Riinstalo {{artifact}}" + }, + "update": { + "button_processing": "Përditësohet", + "desc": "Je i sigurt a don ta përditësojsh {{artifact}} {{version}}?", + "title": "Përditëso {{artifact}}" + } + }, + "PluginLoader": { + "decky_title": "Decky", + "plugin_uninstall": { + "title": "Çinstalo {{name}}", + "button": "Çinstalo", + "desc": "Je i sigurt që don ta çinstalojsh {{name}}?" + }, + "error": "Gabim", + "plugin_error_uninstall": "Ju lutem shko nga {{name}} në Decky menu nëse don ta çinstalojsh këtë shtese.", + "plugin_update_one": "", + "plugin_update_other": "" + }, + "PluginListIndex": { + "no_plugin": "Nuk ka shtesa të instaluar!", + "uninstall": "Çinstalo", + "update_all_one": "", + "update_all_other": "" + }, + "SettingsGeneralIndex": { + "other": { + "header": "Të Tjera" + }, + "about": { + "decky_version": "Versioni Decky" + }, + "updates": { + "header": "Përmirësimet" + } + }, + "SettingsIndex": { + "developer_title": "Zhvillues", + "general_title": "Gjeneral" + }, + "Store": { + "store_sort": { + "label": "Rendit" + }, + "store_tabs": { + "title": "Kërko" + }, + "store_contrib": { + "label": "Kontributi" + }, + "store_filter": { + "label": "Filtro", + "label_def": "Të Gjitha" + }, + "store_search": { + "label": "Kërko" + }, + "store_source": { + "label": "Kodin Burimor" + } + }, + "StoreSelect": { + "store_channel": { + "label": "Kanali Dyqanit" + } + }, + "Updater": { + "updates": { + "cur_version": "Versioni e tanishëme: {{ver}}" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "", + "mixed_other": "", + "update_one": "", + "update_other": "", + "reinstall_one": "", + "reinstall_other": "", + "install_one": "", + "install_other": "" + } + }, + "PluginView": { + "hidden_one": "", + "hidden_other": "" + } +} diff --git a/backend/src/locales/uk-UA.json b/backend/src/locales/uk-UA.json new file mode 100644 index 00000000..09fbca1b --- /dev/null +++ b/backend/src/locales/uk-UA.json @@ -0,0 +1,222 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "Передреліз", + "testing": "Тестовий", + "label": "Канал оновлень", + "stable": "Стабільний" + } + }, + "Developer": { + "5secreload": "Перезавантаження за 5 секунд", + "enabling": "Увімкнення React DevTools", + "disabling": "Вимкнення React DevTools" + }, + "FilePickerIndex": { + "folder": { + "select": "Використовувати цю папку" + } + }, + "PluginListLabel": { + "hidden": "Приховано з меню швидкого доступу" + }, + "PluginCard": { + "plugin_full_access": "Цей плагін має повний доступ до вашого Steam Deck.", + "plugin_install": "Встановити", + "plugin_no_desc": "Опис не надано.", + "plugin_version_label": "Версія плагіна" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Встановити", + "button_processing": "Встановлення", + "title": "Встановити {{artifact}}", + "desc": "Ви впевнені, що хочете встановити {{artifact}} {{version}}?" + }, + "reinstall": { + "button_idle": "Перевстановити", + "desc": "Ви впевнені, що хочете перевстановити {{artifact}} {{version}}?", + "title": "Перевстановити {{artifact}}", + "button_processing": "Перевстановлення" + }, + "update": { + "button_idle": "Оновити", + "button_processing": "Оновлення", + "title": "Оновити {{artifact}}", + "desc": "Ви впевнені, що хочете оновити {{artifact}} {{version}}?" + }, + "no_hash": "Цей плагін не має хешу, ви встановлюєте його на власний ризик." + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Модифікувати 1 плагін", + "mixed_few": "Модифікувати {{count}} плагінів", + "mixed_many": "", + "reinstall_one": "Перевстановити 1 плагін", + "reinstall_few": "Перевстановити {{count}} плагінів", + "reinstall_many": "Перевстановити {{count}} плагінів", + "update_one": "Оновити 1 плагін", + "update_few": "Оновити {{count}} плагінів", + "update_many": "Оновити {{count}} плагінів", + "install_one": "Встановити 1 плагін", + "install_few": "Встановити {{count}} плагінів", + "install_many": "Встановити {{count}} плагінів" + }, + "ok_button": { + "idle": "Підтвердити", + "loading": "Опрацювання" + }, + "description": { + "install": "Встановити {{name}} {{version}}", + "update": "Оновити {{name}} до {{version}}", + "reinstall": "Перевстановити {{name}} {{version}}" + }, + "confirm": "Ви впевнені, що хочете застосувати такі модифікації?" + }, + "PluginListIndex": { + "no_plugin": "Плагінів не встановлено!", + "plugin_actions": "Дії плагінів", + "reinstall": "Перевстановити", + "reload": "Перезавантажити", + "update_to": "Оновити {{name}}", + "show": "Швидкий доступ: Показати", + "hide": "Швидкий доступ: Приховати", + "uninstall": "Видалити", + "update_all_one": "Оновити 1 плагін", + "update_all_few": "Оновити {{count}} плагінів", + "update_all_many": "Оновити {{count}} плагінів" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Доступне оновлення до {{tag_name}}!", + "error": "Помилка", + "plugin_load_error": { + "message": "Помилка завантаження плагіна {{name}}", + "toast": "Помилка завантаження {{name}}" + }, + "plugin_uninstall": { + "desc": "Ви впевнені, що хочете видалити {{name}}?", + "title": "Видалити {{name}}", + "button": "Видалення" + }, + "plugin_error_uninstall": "Завантаження {{name}} спровокувало помилку показану вище. Зазвичай це означає, що плагін вимагає оновлення до нової версії SteamUI. Перевірте чи таке оновлення доступне або виконайте його видалення у налаштуваннях Decky, у секції Плагіни.", + "plugin_update_one": "Доступне оновлення для 1 плагіна!", + "plugin_update_few": "Доступне оновлення для {{count}} плагінів!", + "plugin_update_many": "Доступне оновлення для {{count}} плагінів!" + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Відкрити консоль", + "label": "CEF-консоль", + "desc": "Відкрити CEF-консоль. Корисно тільки для дебагу. Ця штука потенційно небезпечна і повинна використовувати виключно якщо ви розробник плагіна, або якщо розробник спрямував вас сюди." + }, + "header": "Інше", + "react_devtools": { + "desc": "Вмикає доступ до компʼютера із запущеним React DevTools. Зміна цього налаштування перезавантажить Steam. Вкажіть IP перед увімкненням.", + "label": "Увімкнути React DevTools", + "ip_label": "IP" + }, + "third_party_plugins": { + "button_install": "Встановити", + "header": "Сторонні плагіни", + "label_desc": "URL", + "label_url": "Встановити плагін з URL", + "label_zip": "Встановити плагін з ZIP-файлу", + "button_zip": "Огляд" + }, + "valve_internal": { + "desc1": "Вмикає внутрішнє розробницьке меню Valve.", + "label": "Увімкнути Valve Internal", + "desc2": "Нічого не торкайтесь у цьому меню, якщо не розумієте, що ви робите." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Версія Decky", + "header": "Про нас" + }, + "beta": { + "header": "Участь у Beta" + }, + "developer_mode": { + "label": "Розробницький режим" + }, + "other": { + "header": "Інше" + }, + "updates": { + "header": "Оновлення" + } + }, + "SettingsIndex": { + "developer_title": "Розробник", + "general_title": "Загальне", + "plugins_title": "Плагіни" + }, + "Store": { + "store_contrib": { + "label": "Зробити внесок", + "desc": "Якщо ви бажаєте додати щось у Decky Plugin Store, завітайте у репозиторій SteamDeckHomebrew/decky-plugin-template на GitHub. Інформація про розробку та поширення доступна у README." + }, + "store_filter": { + "label": "Фільтр", + "label_def": "Усе" + }, + "store_search": { + "label": "Пошук" + }, + "store_sort": { + "label": "Сортування", + "label_def": "Востаннє оновлені (Найновіші)" + }, + "store_source": { + "label": "Вихідний код", + "desc": "Код усіх плагінів доступний у репозиторії SteamDeckHomebrew/decky-plugin-database на GitHub." + }, + "store_tabs": { + "about": "Інформація", + "alph_asce": "За алфавітом (Z до A)", + "alph_desc": "За алфавітом (A до Z)", + "title": "Огляд" + }, + "store_testing_cta": "Розгляньте можливість тестування нових плагінів, щоб допомогти команді Decky Loader!" + }, + "StoreSelect": { + "custom_store": { + "label": "Власний магазин", + "url_label": "URL" + }, + "store_channel": { + "custom": "Власний", + "default": "За замовчуванням", + "testing": "Тестування", + "label": "Канал магазину" + } + }, + "Updater": { + "decky_updates": "Оновлення Decky", + "no_patch_notes_desc": "Немає нотаток до цієї версії", + "patch_notes_desc": "Перелік змін", + "updates": { + "checking": "Перевірка", + "cur_version": "Поточна версія: {{ver}}", + "install_button": "Встановити оновлення", + "label": "Оновлення", + "reloading": "Перезавантаження", + "updating": "Оновлення", + "check_button": "Перевірити оновлення", + "lat_version": "Оновлено: використовується {{ver}}" + } + }, + "PluginView": { + "hidden_one": "{{count}} плагін приховано з цього списку", + "hidden_few": "{{count}} плагінів приховано з цього списку", + "hidden_many": "{{count}} плагінів приховано з цього списку" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Дозволити доступ до CEF-дебагера без аутентифікації для будь-кого у вашій мережі", + "label": "Дозволити віддалений CEF-дебагінг" + } + } +} diff --git a/backend/src/locales/zh-CN.json b/backend/src/locales/zh-CN.json new file mode 100644 index 00000000..d9d12aa0 --- /dev/null +++ b/backend/src/locales/zh-CN.json @@ -0,0 +1,253 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "发布候选", + "stable": "稳定", + "testing": "测试", + "label": "更新通道" + } + }, + "Developer": { + "5secreload": "5 秒钟后重新加载", + "disabling": "正在禁用 React DevTools", + "enabling": "正在启用 React DevTools" + }, + "FilePickerIndex": { + "folder": { + "select": "使用这个文件夹", + "label": "文件夹", + "show_more": "显示更多文件" + }, + "filter": { + "created_asce": "创建日期(最旧)", + "created_desc": "创建日期(最新)", + "modified_asce": "修改日期(最旧)", + "modified_desc": "修改日期(最新)", + "name_asce": "字母降序", + "name_desc": "字母升序", + "size_asce": "大小(最小)", + "size_desc": "大小(最大)" + }, + "files": { + "all_files": "全部文件", + "file_type": "文件类型", + "show_hidden": "显示隐藏文件" + }, + "file": { + "select": "选择此文件" + } + }, + "PluginCard": { + "plugin_install": "安装", + "plugin_no_desc": "无描述提供。", + "plugin_version_label": "插件版本", + "plugin_full_access": "此插件可以完全访问你的 Steam Deck。" + }, + "PluginInstallModal": { + "install": { + "button_idle": "安装", + "button_processing": "安装中", + "desc": "你确定要安装 {{artifact}} {{version}} 吗?", + "title": "安装 {{artifact}}" + }, + "reinstall": { + "button_idle": "重新安装", + "button_processing": "正在重新安装", + "desc": "你确定要重新安装 {{artifact}} {{version}} 吗?", + "title": "重新安装 {{artifact}}" + }, + "update": { + "button_idle": "更新", + "button_processing": "正在更新", + "desc": "你确定要更新 {{artifact}} {{version}} 吗?", + "title": "更新 {{artifact}}" + }, + "no_hash": "此插件没有哈希校验值,你需要自行承担安装风险。" + }, + "PluginListIndex": { + "no_plugin": "没有安装插件!", + "plugin_actions": "插件操作", + "reinstall": "重新安装", + "reload": "重新加载", + "uninstall": "卸载", + "update_to": "更新 {{name}}", + "update_all_other": "更新 {{count}} 个插件", + "show": "在快速访问菜单中显示", + "hide": "在快速访问菜单中隐藏" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "错误", + "plugin_error_uninstall": "加载 {{name}} 时引起了上述异常。这通常意味着插件需要更新以适应 SteamUI 的新版本。请检查插件是否有更新,或在 Decky 设置中的插件部分将其移除。", + "plugin_load_error": { + "message": "加载插件 {{name}} 错误", + "toast": "加载插件 {{name}} 发生了错误" + }, + "plugin_uninstall": { + "button": "卸载", + "title": "卸载 {{name}}", + "desc": "你确定要卸载 {{name}} 吗?" + }, + "decky_update_available": "新版本 {{tag_name}} 可用!", + "plugin_update_other": "{{count}} 个插件有更新!" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "允许你网络中的任何人无需身份验证即可访问CEF调试器", + "label": "允许远程访问CEF调试" + } + }, + "SettingsDeveloperIndex": { + "react_devtools": { + "ip_label": "IP", + "label": "启用 React DevTools", + "desc": "允许连接到运行着 React DevTools 的计算机,更改此设置将重新加载Steam,请在启用前设置IP地址。" + }, + "third_party_plugins": { + "button_install": "安装", + "button_zip": "浏览文件", + "header": "第三方插件", + "label_desc": "URL", + "label_url": "从 URL 安装插件", + "label_zip": "从 ZIP 压缩文件安装插件" + }, + "valve_internal": { + "desc1": "启用 Valve 内部开发者菜单。", + "desc2": "除非你知道你在干什么,否则请不要修改此菜单中的任何内容。", + "label": "启用 Valve 内部开发者" + }, + "cef_console": { + "button": "打开控制台", + "label": "CEF 控制台", + "desc": "打开 CEF 控制台。仅在调试目的下使用。这列选项均有风险,请仅在您是插件开发者或是在插件开发者指导时访问使用。" + }, + "header": "其他" + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky 版本", + "header": "关于" + }, + "beta": { + "header": "参与测试" + }, + "developer_mode": { + "label": "开发者模式" + }, + "other": { + "header": "其他" + }, + "updates": { + "header": "更新" + }, + "notifications": { + "header": "通知", + "decky_updates_label": "Decky 更新可用", + "plugin_updates_label": "插件更新可用" + } + }, + "SettingsIndex": { + "developer_title": "开发者", + "general_title": "通用", + "plugins_title": "插件" + }, + "Store": { + "store_contrib": { + "label": "贡献", + "desc": "如果你想要提交你的插件到 Decky 插件商店,请访问 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 存储库,关于开发和分发的相关信息,请查看 README 文件。" + }, + "store_filter": { + "label": "过滤器", + "label_def": "全部" + }, + "store_search": { + "label": "搜索" + }, + "store_sort": { + "label": "排序", + "label_def": "最后更新 (最新)" + }, + "store_source": { + "label": "源代码", + "desc": "所有插件的源代码都可以在 GitHub 上的 SteamDeckHomebrew/decky-plugin-database 存储库中获得。" + }, + "store_tabs": { + "about": "关于", + "alph_asce": "字母排序 (Z 到 A)", + "alph_desc": "字母排序 (A 到 Z)", + "title": "浏览" + }, + "store_testing_cta": "请考虑测试新插件以帮助 Decky Loader 团队!", + "store_testing_warning": { + "desc": "你可以使用该商店频道以体验最新版本的插件。 请在插件 Github 页面留言以使插件可以正式面向所有用户。", + "label": "欢迎来到商店测试频道" + } + }, + "StoreSelect": { + "store_channel": { + "default": "默认", + "label": "商店通道", + "testing": "测试", + "custom": "自定义" + }, + "custom_store": { + "label": "自定义商店", + "url_label": "URL" + } + }, + "Updater": { + "decky_updates": "Decky 更新", + "no_patch_notes_desc": "此版本没有补丁说明", + "patch_notes_desc": "补丁说明", + "updates": { + "check_button": "检查更新", + "checking": "检查中", + "cur_version": "当前版本: {{ver}}", + "install_button": "安装更新", + "label": "更新", + "lat_version": "已是最新版本: {{ver}} 运行中", + "reloading": "重新加载中", + "updating": "更新中" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_other": "更改 {{count}} 个插件", + "update_other": "更新 {{count}} 个插件", + "reinstall_other": "重装 {{count}} 个插件", + "install_other": "安装 {{count}} 个插件" + }, + "ok_button": { + "idle": "确认", + "loading": "工作中" + }, + "confirm": "确定要进行以下修改吗?", + "description": { + "install": "安装 {{name}} {{version}}", + "update": "更新 {{name}} to {{version}}", + "reinstall": "重装 {{name}} {{version}}" + } + }, + "PluginListLabel": { + "hidden": "在快速访问菜单中已隐藏" + }, + "PluginView": { + "hidden_other": "此列表隐藏了 {{count}} 个插件" + }, + "DropdownMultiselect": { + "button": { + "back": "返回" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "指定路径无效。请检查并输入正确的路径。", + "unknown": "发生了一个未知错误。原始错误为:{{raw_error}}", + "perm_denied": "你没有访问特定目录的权限。请检查你的用户(Steam Deck 中的 deck 账户)有着相对应的权限以访问特定的文件夹或文件。" + } + }, + "TitleView": { + "decky_store_desc": "打开 Decky 商店", + "settings_desc": "打开 Decky 设置" + } +} diff --git a/backend/src/locales/zh-TW.json b/backend/src/locales/zh-TW.json new file mode 100644 index 00000000..2891aa9c --- /dev/null +++ b/backend/src/locales/zh-TW.json @@ -0,0 +1,245 @@ +{ + "BranchSelect": { + "update_channel": { + "testing": "測試版", + "label": "更新頻道", + "prerelease": "預發佈", + "stable": "穩定版" + } + }, + "Developer": { + "5secreload": "5 秒後重新載入", + "disabling": "正在停用 React DevTools", + "enabling": "正在啟用 React DevTools" + }, + "FilePickerIndex": { + "folder": { + "select": "使用此資料夾", + "show_more": "顯示更多檔案", + "label": "資料夾" + }, + "filter": { + "modified_asce": "修改日期(舊到新)", + "created_desc": "建立日期(新到舊)", + "modified_desc": "修改日期(新到舊)", + "name_desc": "子母排序(A到Z)", + "name_asce": "子母排序(Z到A)", + "size_asce": "檔案大小(小到大)", + "size_desc": "檔案大小(大到小)", + "created_asce": "建立日期(舊到新)" + }, + "file": { + "select": "選擇此檔案" + }, + "files": { + "all_files": "所有檔案", + "file_type": "檔案類型", + "show_hidden": "顯示隱藏檔" + } + }, + "PluginCard": { + "plugin_install": "安裝", + "plugin_no_desc": "未提示描述。", + "plugin_version_label": "外掛程式版本", + "plugin_full_access": "此外掛程式擁有您的 Steam Deck 的完整存取權。" + }, + "PluginInstallModal": { + "install": { + "button_idle": "安裝", + "button_processing": "正在安裝", + "title": "安裝 {{artifact}}", + "desc": "您確定要安裝 {{artifact}} {{version}} 嗎?" + }, + "reinstall": { + "button_idle": "重新安裝", + "button_processing": "正在重新安裝", + "desc": "您確定要重新安裝 {{artifact}} {{version}} 嗎?", + "title": "重新安裝 {{artifact}}" + }, + "update": { + "button_idle": "更新", + "button_processing": "正在更新", + "desc": "您確定要更新 {{artifact}} {{version}} 嗎?", + "title": "更新 {{artifact}}" + }, + "no_hash": "此外掛程式沒有提供 hash 驗證,安裝可能有風險。" + }, + "PluginListIndex": { + "no_plugin": "未安裝外掛程式!", + "plugin_actions": "外掛程式操作", + "uninstall": "解除安裝", + "update_to": "更新到 {{name}}", + "reinstall": "重新安裝", + "reload": "重新載入", + "show": "快速存取:顯示", + "hide": "快速存取:隱藏", + "update_all_other": "更新 {{count}} 個外掛程式" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "錯誤", + "plugin_error_uninstall": "載入 {{name}} 導致上述異常。這通常意味著該外掛程式需要針對新版本的 SteamUI 進行更新。在 Decky 設定中檢查是否存在更新,或評估刪除此外掛程式。", + "plugin_load_error": { + "message": "載入外掛程式 {{name}} 發生錯誤", + "toast": "{{name}} 載入出錯" + }, + "plugin_uninstall": { + "button": "解除安裝", + "title": "解除安裝 {{name}}", + "desc": "您確定要解除安裝 {{name}} 嗎?" + }, + "decky_update_available": "可更新至版本 {{tag_name}}!", + "plugin_update_other": "可更新 {{count}} 個外掛程式!" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "允許您的網路中的任何人未經認證地存取 CEF 偵錯器", + "label": "允許 CEF 遠端偵錯" + } + }, + "SettingsDeveloperIndex": { + "third_party_plugins": { + "button_zip": "開啟", + "label_desc": "網址", + "label_url": "從網址安裝外掛程式", + "label_zip": "從 ZIP 檔案安裝外掛程式", + "button_install": "安裝", + "header": "第三方外掛程式" + }, + "valve_internal": { + "desc2": "除非您知道它的作用,否則不要碰這個選單中的任何東西。", + "desc1": "啟用 Valve 內建開發人員選單。", + "label": "啟用 Valve 內建" + }, + "react_devtools": { + "desc": "啟用與執行 React DevTools 的電腦的連接。改變這個設定將重新載入 Steam。啟用前必須設定 IP 位址。", + "ip_label": "IP", + "label": "啟用 React DevTools" + }, + "header": "其他", + "cef_console": { + "button": "開啟控制台", + "label": "CEF 控制台", + "desc": "開啟 CEF 控制台。僅用於偵錯。這裡的東西有潛在的風險,只有當您是一個外掛程式開發者或者被外掛程式開發者引導到這裡時,才應該使用。" + } + }, + "SettingsGeneralIndex": { + "about": { + "header": "關於", + "decky_version": "Decky 版本" + }, + "beta": { + "header": "參與測試" + }, + "developer_mode": { + "label": "開發人員模式" + }, + "other": { + "header": "其他" + }, + "updates": { + "header": "更新" + }, + "notifications": { + "decky_updates_label": "Decky 可更新", + "header": "通知", + "plugin_updates_label": "外掛程式有更新" + } + }, + "SettingsIndex": { + "developer_title": "開發人員", + "general_title": "一般", + "plugins_title": "外掛程式" + }, + "Store": { + "store_contrib": { + "label": "貢獻", + "desc": "如果您想為 Decky 外掛程式商店做貢獻,請查看 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 儲存庫。README 中提供了有關開發和發佈的資訊。" + }, + "store_filter": { + "label": "過濾", + "label_def": "全部" + }, + "store_search": { + "label": "搜尋" + }, + "store_sort": { + "label": "排序", + "label_def": "最後更新 (最新)" + }, + "store_source": { + "label": "原始碼", + "desc": "所有外掛程式原始碼可以在 GitHub 的 SteamDeckHomebrew/decky-plugin-database 儲存庫查看。" + }, + "store_tabs": { + "about": "關於", + "alph_asce": "依字母排序 (Z 到 A)", + "alph_desc": "依字母排序 (A 到 Z)", + "title": "瀏覽" + }, + "store_testing_cta": "請考慮測試新的外掛程式來幫助 Decky Loader 團隊!" + }, + "StoreSelect": { + "custom_store": { + "label": "自訂商店", + "url_label": "網址" + }, + "store_channel": { + "custom": "自訂", + "default": "預設", + "label": "商店頻道", + "testing": "測試" + } + }, + "Updater": { + "decky_updates": "Decky 更新", + "no_patch_notes_desc": "這個版本沒有更新日誌", + "patch_notes_desc": "更新日誌", + "updates": { + "checking": "正在檢查", + "install_button": "安裝更新", + "label": "更新", + "lat_version": "已是最新:執行 {{ver}}", + "reloading": "正在重新載入", + "check_button": "檢查更新", + "cur_version": "目前版本:{{ver}}", + "updating": "正在更新" + } + }, + "PluginView": { + "hidden_other": "{{count}} 個外掛程式已隱藏" + }, + "PluginListLabel": { + "hidden": "已從快速存取選單中移除" + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_other": "修改 {{count}} 個外掛程式", + "update_other": "更新 {{count}} 個外掛程式", + "reinstall_other": "重新安裝 {{count}} 個外掛程式", + "install_other": "安裝 {{count}} 個外掛程式" + }, + "ok_button": { + "idle": "確定", + "loading": "執行中" + }, + "confirm": "您確定要進行以下的修改嗎?", + "description": { + "install": "安裝 {{name}} {{version}}", + "update": "更新 {{name}} 到 {{version}}", + "reinstall": "重新安裝 {{name}} {{version}}" + } + }, + "FilePickerError": { + "errors": { + "perm_denied": "您沒有瀏覽此目錄的權限。請檢查您的使用者(Steam Deck 中的 deck 帳號)有權限瀏覽特定的資料夾或檔案。", + "unknown": "發生未知錯誤。錯誤詳細資料:{{raw_error}}", + "file_not_found": "指定路徑無效。請檢查並輸入正確路徑。" + } + }, + "DropdownMultiselect": { + "button": { + "back": "返回" + } + } +} diff --git a/backend/src/localplatform.py b/backend/src/localplatform.py new file mode 100644 index 00000000..028eff8f --- /dev/null +++ b/backend/src/localplatform.py @@ -0,0 +1,52 @@ +import platform, os + +ON_WINDOWS = platform.system() == "Windows" +ON_LINUX = not ON_WINDOWS + +if ON_WINDOWS: + from .localplatformwin import * + from . import localplatformwin as localplatform +else: + from .localplatformlinux import * + from . import localplatformlinux as localplatform + +def get_privileged_path() -> str: + '''Get path accessible by elevated user. Holds plugins, decky loader and decky loader configs''' + return localplatform.get_privileged_path() + +def get_unprivileged_path() -> str: + '''Get path accessible by non-elevated user. Holds plugin configuration, plugin data and plugin logs. Externally referred to as the 'Homebrew' directory''' + return localplatform.get_unprivileged_path() + +def get_unprivileged_user() -> str: + '''Get user that should own files made in unprivileged path''' + return localplatform.get_unprivileged_user() + +def get_chown_plugin_path() -> bool: + return os.getenv("CHOWN_PLUGIN_PATH", "1") == "1" + +def get_server_host() -> str: + return os.getenv("SERVER_HOST", "127.0.0.1") + +def get_server_port() -> int: + return int(os.getenv("SERVER_PORT", "1337")) + +def get_live_reload() -> bool: + return os.getenv("LIVE_RELOAD", "1") == "1" + +def get_keep_systemd_service() -> bool: + return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1" + +def get_log_level() -> int: + return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[ + os.getenv("LOG_LEVEL", "INFO") + ] + +def get_selinux() -> bool: + if ON_LINUX: + from subprocess import check_output + try: + if (check_output("getenforce").decode("ascii").strip("\n") == "Enforcing"): return True + except FileNotFoundError: + pass + return False diff --git a/backend/src/localplatformlinux.py b/backend/src/localplatformlinux.py new file mode 100644 index 00000000..bde2caac --- /dev/null +++ b/backend/src/localplatformlinux.py @@ -0,0 +1,192 @@ +import os, pwd, grp, sys, logging +from subprocess import call, run, DEVNULL, PIPE, STDOUT +from .customtypes import UserType + +logger = logging.getLogger("localplatform") + +# Get the user id hosting the plugin loader +def _get_user_id() -> int: + return pwd.getpwnam(_get_user()).pw_uid + +# Get the user hosting the plugin loader +def _get_user() -> str: + return get_unprivileged_user() + +# Get the effective user id of the running process +def _get_effective_user_id() -> int: + return os.geteuid() + +# Get the effective user of the running process +def _get_effective_user() -> str: + return pwd.getpwuid(_get_effective_user_id()).pw_name + +# Get the effective user group id of the running process +def _get_effective_user_group_id() -> int: + return os.getegid() + +# Get the effective user group of the running process +def _get_effective_user_group() -> str: + return grp.getgrgid(_get_effective_user_group_id()).gr_name + +# Get the user owner of the given file path. +def _get_user_owner(file_path: str) -> str: + return pwd.getpwuid(os.stat(file_path).st_uid).pw_name + +# Get the user group of the given file path, or the user group hosting the plugin loader +def _get_user_group(file_path: str | None = None) -> str: + return grp.getgrgid(os.stat(file_path).st_gid if file_path is not None else _get_user_group_id()).gr_name + +# Get the group id of the user hosting the plugin loader +def _get_user_group_id() -> int: + return pwd.getpwuid(_get_user_id()).pw_gid + +def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: + user_str = "" + + if user == UserType.HOST_USER: + user_str = _get_user()+":"+_get_user_group() + elif user == UserType.EFFECTIVE_USER: + user_str = _get_effective_user()+":"+_get_effective_user_group() + elif user == UserType.ROOT: + user_str = "root:root" + else: + raise Exception("Unknown User Type") + + result = call(["chown", "-R", user_str, path] if recursive else ["chown", user_str, path]) + return result == 0 + +def chmod(path : str, permissions : int, recursive : bool = True) -> bool: + if _get_effective_user_id() != 0: + return True + result = call(["chmod", "-R", str(permissions), path] if recursive else ["chmod", str(permissions), path]) + return result == 0 + +def folder_owner(path : str) -> UserType|None: + user_owner = _get_user_owner(path) + + if (user_owner == _get_user()): + return UserType.HOST_USER + + elif (user_owner == _get_effective_user()): + return UserType.EFFECTIVE_USER + + else: + return None + +def get_home_path(user : UserType = UserType.HOST_USER) -> str: + user_name = "root" + + if user == UserType.HOST_USER: + user_name = _get_user() + elif user == UserType.EFFECTIVE_USER: + user_name = _get_effective_user() + elif user == UserType.ROOT: + pass + else: + raise Exception("Unknown User Type") + + return pwd.getpwnam(user_name).pw_dir + +def get_username() -> str: + return _get_user() + +def setgid(user : UserType = UserType.HOST_USER): + user_id = 0 + + if user == UserType.HOST_USER: + user_id = _get_user_group_id() + elif user == UserType.ROOT: + pass + else: + raise Exception("Unknown user type") + + os.setgid(user_id) + +def setuid(user : UserType = UserType.HOST_USER): + user_id = 0 + + if user == UserType.HOST_USER: + user_id = _get_user_id() + elif user == UserType.ROOT: + pass + else: + raise Exception("Unknown user type") + + os.setuid(user_id) + +async def service_active(service_name : str) -> bool: + res = run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL) + return res.returncode == 0 + +async def service_restart(service_name : str) -> bool: + call(["systemctl", "daemon-reload"]) + cmd = ["systemctl", "restart", service_name] + res = run(cmd, stdout=PIPE, stderr=STDOUT) + return res.returncode == 0 + +async def service_stop(service_name : str) -> bool: + cmd = ["systemctl", "stop", service_name] + res = run(cmd, stdout=PIPE, stderr=STDOUT) + return res.returncode == 0 + +async def service_start(service_name : str) -> bool: + cmd = ["systemctl", "start", service_name] + res = run(cmd, stdout=PIPE, stderr=STDOUT) + return res.returncode == 0 + +def get_privileged_path() -> str: + path = os.getenv("PRIVILEGED_PATH") + + if path == None: + path = get_unprivileged_path() + + return path + +def _parent_dir(path : str | None) -> str | None: + if path == None: + return None + + if path.endswith('/'): + path = path[:-1] + + return os.path.dirname(path) + +def get_unprivileged_path() -> str: + path = os.getenv("UNPRIVILEGED_PATH") + + if path == None: + path = _parent_dir(os.getenv("PLUGIN_PATH")) + + if path == None: + logger.debug("Unprivileged path is not properly configured. Making something up!") + # Expected path of loader binary is /home/deck/homebrew/service/PluginLoader + path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0]))) + + if path != None and not os.path.exists(path): + path = None + + if path == None: + logger.warn("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew") + path = "/home/deck/homebrew" # We give up + + return path + + +def get_unprivileged_user() -> str: + user = os.getenv("UNPRIVILEGED_USER") + + if user == None: + # Lets hope we can extract it from the unprivileged dir + dir = os.path.realpath(get_unprivileged_path()) + + pws = sorted(pwd.getpwall(), reverse=True, key=lambda pw: len(pw.pw_dir)) + for pw in pws: + if dir.startswith(os.path.realpath(pw.pw_dir)): + user = pw.pw_name + break + + if user == None: + logger.warn("Unprivileged user is not properly configured. Defaulting to 'deck'") + user = 'deck' + + return user diff --git a/backend/src/localplatformwin.py b/backend/src/localplatformwin.py new file mode 100644 index 00000000..4c4e9439 --- /dev/null +++ b/backend/src/localplatformwin.py @@ -0,0 +1,53 @@ +from .customtypes import UserType +import os, sys + +def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: + return True # Stubbed + +def chmod(path : str, permissions : int, recursive : bool = True) -> bool: + return True # Stubbed + +def folder_owner(path : str) -> UserType|None: + return UserType.HOST_USER # Stubbed + +def get_home_path(user : UserType = UserType.HOST_USER) -> str: + return os.path.expanduser("~") # Mostly stubbed + +def setgid(user : UserType = UserType.HOST_USER): + pass # Stubbed + +def setuid(user : UserType = UserType.HOST_USER): + pass # Stubbed + +async def service_active(service_name : str) -> bool: + return True # Stubbed + +async def service_stop(service_name : str) -> bool: + return True # Stubbed + +async def service_start(service_name : str) -> bool: + return True # Stubbed + +async def service_restart(service_name : str) -> bool: + if service_name == "plugin_loader": + sys.exit(42) + + return True # Stubbed + +def get_username() -> str: + return os.getlogin() + +def get_privileged_path() -> str: + '''On windows, privileged_path is equal to unprivileged_path''' + return get_unprivileged_path() + +def get_unprivileged_path() -> str: + path = os.getenv("UNPRIVILEGED_PATH") + + if path == None: + path = os.getenv("PRIVILEGED_PATH", os.path.join(os.path.expanduser("~"), "homebrew")) + + return path + +def get_unprivileged_user() -> str: + return os.getenv("UNPRIVILEGED_USER", os.getlogin()) diff --git a/backend/src/localsocket.py b/backend/src/localsocket.py new file mode 100644 index 00000000..f38fe5e7 --- /dev/null +++ b/backend/src/localsocket.py @@ -0,0 +1,139 @@ +import asyncio, time +from typing import Awaitable, Callable +import random + +from .localplatform import ON_WINDOWS + +BUFFER_LIMIT = 2 ** 20 # 1 MiB + +class UnixSocket: + def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): + ''' + on_new_message takes 1 string argument. + It's return value gets used, if not None, to write data to the socket. + Method should be async + ''' + self.socket_addr = f"/tmp/plugin_socket_{time.time()}" + self.on_new_message = on_new_message + self.socket = None + self.reader = None + self.writer = None + + async def setup_server(self): + self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT) + + async def _open_socket_if_not_exists(self): + if not self.reader: + retries = 0 + while retries < 10: + try: + self.reader, self.writer = await asyncio.open_unix_connection(self.socket_addr, limit=BUFFER_LIMIT) + return True + except: + await asyncio.sleep(2) + retries += 1 + return False + else: + return True + + async def get_socket_connection(self): + if not await self._open_socket_if_not_exists(): + return None, None + + return self.reader, self.writer + + async def close_socket_connection(self): + if self.writer != None: + self.writer.close() + + self.reader = None + + async def read_single_line(self) -> str|None: + reader, _ = await self.get_socket_connection() + + try: + assert reader + except AssertionError: + return + + return await self._read_single_line(reader) + + async def write_single_line(self, message : str): + _, writer = await self.get_socket_connection() + + try: + assert writer + except AssertionError: + return + + await self._write_single_line(writer, message) + + async def _read_single_line(self, reader: asyncio.StreamReader) -> str: + line = bytearray() + while True: + try: + line.extend(await reader.readuntil()) + except asyncio.LimitOverrunError: + line.extend(await reader.read(reader._limit)) # type: ignore + continue + except asyncio.IncompleteReadError as err: + line.extend(err.partial) + break + else: + break + + return line.decode("utf-8") + + async def _write_single_line(self, writer: asyncio.StreamWriter, message : str): + if not message.endswith("\n"): + message += "\n" + + writer.write(message.encode("utf-8")) + await writer.drain() + + async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): + while True: + line = await self._read_single_line(reader) + + try: + res = await self.on_new_message(line) + except Exception: + return + + if res != None: + await self._write_single_line(writer, res) + +class PortSocket (UnixSocket): + def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): + ''' + on_new_message takes 1 string argument. + It's return value gets used, if not None, to write data to the socket. + Method should be async + ''' + super().__init__(on_new_message) + self.host = "127.0.0.1" + self.port = random.sample(range(40000, 60000), 1)[0] + + async def setup_server(self): + self.socket = await asyncio.start_server(self._listen_for_method_call, host=self.host, port=self.port, limit=BUFFER_LIMIT) + + async def _open_socket_if_not_exists(self): + if not self.reader: + retries = 0 + while retries < 10: + try: + self.reader, self.writer = await asyncio.open_connection(host=self.host, port=self.port, limit=BUFFER_LIMIT) + return True + except: + await asyncio.sleep(2) + retries += 1 + return False + else: + return True + +if ON_WINDOWS: + class LocalSocket (PortSocket): # type: ignore + pass +else: + class LocalSocket (UnixSocket): + pass \ No newline at end of file diff --git a/backend/src/main.py b/backend/src/main.py new file mode 100644 index 00000000..793d000c --- /dev/null +++ b/backend/src/main.py @@ -0,0 +1,192 @@ +# Change PyInstaller files permissions +import sys +from typing import Dict +from .localplatform import (chmod, chown, service_stop, service_start, + ON_WINDOWS, get_log_level, get_live_reload, + get_server_port, get_server_host, get_chown_plugin_path, + get_privileged_path) +if hasattr(sys, '_MEIPASS'): + chmod(sys._MEIPASS, 755) # type: ignore +# Full imports +from asyncio import AbstractEventLoop, new_event_loop, set_event_loop, sleep +from logging import basicConfig, getLogger +from os import path +from traceback import format_exc +import multiprocessing + +import aiohttp_cors # type: ignore +# Partial imports +from aiohttp import client_exceptions +from aiohttp.web import Application, Response, Request, get, run_app, static # type: ignore +from aiohttp_jinja2 import setup as jinja_setup + +# local modules +from .browser import PluginBrowser +from .helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, + mkdir_as_user, get_system_pythonpaths, get_effective_user_id) + +from .injector import get_gamepadui_tab, Tab, close_old_tabs +from .loader import Loader +from .settings import SettingsManager +from .updater import Updater +from .utilities import Utilities +from .customtypes import UserType + + +basicConfig( + level=get_log_level(), + format="[%(module)s][%(levelname)s]: %(message)s" +) + +logger = getLogger("Main") +plugin_path = path.join(get_privileged_path(), "plugins") + +def chown_plugin_dir(): + if not path.exists(plugin_path): # For safety, create the folder before attempting to do anything with it + mkdir_as_user(plugin_path) + + if not chown(plugin_path, UserType.HOST_USER) or not chmod(plugin_path, 555): + logger.error(f"chown/chmod exited with a non-zero exit code") + +if get_chown_plugin_path() == True: + chown_plugin_dir() + +class PluginManager: + def __init__(self, loop: AbstractEventLoop) -> None: + self.loop = loop + self.web_app = Application() + self.web_app.middlewares.append(csrf_middleware) + self.cors = aiohttp_cors.setup(self.web_app, defaults={ + "https://steamloopback.host": aiohttp_cors.ResourceOptions( + expose_headers="*", + allow_headers="*", + allow_credentials=True + ) + }) + self.plugin_loader = Loader(self, plugin_path, self.loop, get_live_reload()) + self.settings = SettingsManager("loader", path.join(get_privileged_path(), "settings")) + self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings) + self.utilities = Utilities(self) + self.updater = Updater(self) + + jinja_setup(self.web_app) + + async def startup(_: Application): + if self.settings.getSetting("cef_forward", False): + self.loop.create_task(service_start(REMOTE_DEBUGGER_UNIT)) + else: + self.loop.create_task(service_stop(REMOTE_DEBUGGER_UNIT)) + self.loop.create_task(self.loader_reinjector()) + self.loop.create_task(self.load_plugins()) + + self.web_app.on_startup.append(startup) + + self.loop.set_exception_handler(self.exception_handler) + self.web_app.add_routes([get("/auth/token", self.get_auth_token)]) + + for route in list(self.web_app.router.routes()): + self.cors.add(route) # type: ignore + self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))]) + self.web_app.add_routes([static("/legacy", path.join(path.dirname(__file__), 'legacy'))]) + + def exception_handler(self, loop: AbstractEventLoop, context: Dict[str, str]): + if context["message"] == "Unclosed connection": + return + loop.default_exception_handler(context) + + async def get_auth_token(self, request: Request): + return Response(text=get_csrf_token()) + + async def load_plugins(self): + # await self.wait_for_server() + logger.debug("Loading plugins") + self.plugin_loader.import_plugins() + # await inject_to_tab("SP", "window.syncDeckyPlugins();") + if self.settings.getSetting("pluginOrder", None) == None: + self.settings.setSetting("pluginOrder", list(self.plugin_loader.plugins.keys())) + logger.debug("Did not find pluginOrder setting, set it to default") + + async def loader_reinjector(self): + while True: + tab = None + nf = False + dc = False + while not tab: + try: + tab = await get_gamepadui_tab() + except (client_exceptions.ClientConnectorError, client_exceptions.ServerDisconnectedError): + if not dc: + logger.debug("Couldn't connect to debugger, waiting...") + dc = True + pass + except ValueError: + if not nf: + logger.debug("Couldn't find GamepadUI tab, waiting...") + nf = True + pass + if not tab: + await sleep(5) + await tab.open_websocket() + await tab.enable() + await self.inject_javascript(tab, True) + try: + async for msg in tab.listen_for_message(): + # this gets spammed a lot + if msg.get("method", None) != "Page.navigatedWithinDocument": + logger.debug("Page event: " + str(msg.get("method", None))) + if msg.get("method", None) == "Page.domContentEventFired": + if not await tab.has_global_var("deckyHasLoaded", False): + await self.inject_javascript(tab) + if msg.get("method", None) == "Inspector.detached": + logger.info("CEF has requested that we detach.") + await tab.close_websocket() + break + # If this is a forceful disconnect the loop will just stop without any failure message. In this case, injector.py will handle this for us so we don't need to close the socket. + # This is because of https://github.com/aio-libs/aiohttp/blob/3ee7091b40a1bc58a8d7846e7878a77640e96996/aiohttp/client_ws.py#L321 + logger.info("CEF has disconnected...") + # At this point the loop starts again and we connect to the freshly started Steam client once it is ready. + except Exception: + logger.error("Exception while reading page events " + format_exc()) + await tab.close_websocket() + pass + # while True: + # await sleep(5) + # if not await tab.has_global_var("deckyHasLoaded", False): + # logger.info("Plugin loader isn't present in Steam anymore, reinjecting...") + # await self.inject_javascript(tab) + + async def inject_javascript(self, tab: Tab, first: bool=False, request: Request|None=None): + logger.info("Loading Decky frontend!") + try: + if first: + if await tab.has_global_var("deckyHasLoaded", False): + await close_old_tabs() + await tab.evaluate_js("try{if (window.deckyHasLoaded){setTimeout(() => location.reload(), 100)}else{window.deckyHasLoaded = true;(async()=>{try{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')}catch(e){console.error(e)};})();}}catch(e){console.error(e)}", False, False, False) + except: + logger.info("Failed to inject JavaScript into tab\n" + format_exc()) + pass + + def run(self): + return run_app(self.web_app, host=get_server_host(), port=get_server_port(), loop=self.loop, access_log=None) + +def main(): + if ON_WINDOWS: + # Fix windows/flask not recognising that .js means 'application/javascript' + import mimetypes + mimetypes.add_type('application/javascript', '.js') + + # Required for multiprocessing support in frozen files + multiprocessing.freeze_support() + else: + if get_effective_user_id() != 0: + logger.warning(f"decky is running as an unprivileged user, this is not officially supported and may cause issues") + + # Append the loader's plugin path to the recognized python paths + sys.path.append(path.join(path.dirname(__file__), "plugin")) + + # Append the system and user python paths + sys.path.extend(get_system_pythonpaths()) + + loop = new_event_loop() + set_event_loop(loop) + PluginManager(loop).run() diff --git a/backend/src/plugin.py b/backend/src/plugin.py new file mode 100644 index 00000000..163bb9b6 --- /dev/null +++ b/backend/src/plugin.py @@ -0,0 +1,163 @@ +import multiprocessing +from asyncio import (Lock, get_event_loop, new_event_loop, + set_event_loop, sleep) +from importlib.util import module_from_spec, spec_from_file_location +from json import dumps, load, loads +from logging import getLogger +from traceback import format_exc +from os import path, environ +from signal import SIGINT, signal +from sys import exit, path as syspath +from typing import Any, Dict +from .localsocket import LocalSocket +from .localplatform import setgid, setuid, get_username, get_home_path +from .customtypes import UserType +from . import helpers + +class PluginWrapper: + def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: + self.file = file + self.plugin_path = plugin_path + self.plugin_directory = plugin_directory + self.method_call_lock = Lock() + self.socket: LocalSocket = LocalSocket(self._on_new_message) + + self.version = None + + json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8")) + if path.isfile(path.join(plugin_path, plugin_directory, "package.json")): + package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8")) + self.version = package_json["version"] + + self.legacy = False + self.main_view_html = json["main_view_html"] if "main_view_html" in json else "" + self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else "" + self.legacy = self.main_view_html or self.tile_view_html + + self.name = json["name"] + self.author = json["author"] + self.flags = json["flags"] + + self.log = getLogger("plugin") + + self.passive = not path.isfile(self.file) + + def __str__(self) -> str: + return self.name + + def _init(self): + try: + signal(SIGINT, lambda s, f: exit(0)) + + set_event_loop(new_event_loop()) + if self.passive: + return + setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) + setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) + # export a bunch of environment variables to help plugin developers + environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) + environ["USER"] = "root" if "root" in self.flags else get_username() + environ["DECKY_VERSION"] = helpers.get_loader_version() + environ["DECKY_USER"] = get_username() + environ["DECKY_USER_HOME"] = helpers.get_home_path() + environ["DECKY_HOME"] = helpers.get_homebrew_path() + environ["DECKY_PLUGIN_SETTINGS_DIR"] = path.join(environ["DECKY_HOME"], "settings", self.plugin_directory) + helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "settings")) + helpers.mkdir_as_user(environ["DECKY_PLUGIN_SETTINGS_DIR"]) + environ["DECKY_PLUGIN_RUNTIME_DIR"] = path.join(environ["DECKY_HOME"], "data", self.plugin_directory) + helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "data")) + helpers.mkdir_as_user(environ["DECKY_PLUGIN_RUNTIME_DIR"]) + environ["DECKY_PLUGIN_LOG_DIR"] = path.join(environ["DECKY_HOME"], "logs", self.plugin_directory) + helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "logs")) + helpers.mkdir_as_user(environ["DECKY_PLUGIN_LOG_DIR"]) + environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory) + environ["DECKY_PLUGIN_NAME"] = self.name + if self.version: + environ["DECKY_PLUGIN_VERSION"] = self.version + environ["DECKY_PLUGIN_AUTHOR"] = self.author + + # append the plugin's `py_modules` to the recognized python paths + syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules")) + + spec = spec_from_file_location("_", self.file) + assert spec is not None + module = module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + self.Plugin = module.Plugin + + if hasattr(self.Plugin, "_migration"): + get_event_loop().run_until_complete(self.Plugin._migration(self.Plugin)) + if hasattr(self.Plugin, "_main"): + get_event_loop().create_task(self.Plugin._main(self.Plugin)) + get_event_loop().create_task(self.socket.setup_server()) + get_event_loop().run_forever() + except: + self.log.error("Failed to start " + self.name + "!\n" + format_exc()) + exit(0) + + async def _unload(self): + try: + self.log.info("Attempting to unload with plugin " + self.name + "'s \"_unload\" function.\n") + if hasattr(self.Plugin, "_unload"): + await self.Plugin._unload(self.Plugin) + self.log.info("Unloaded " + self.name + "\n") + else: + self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n") + except: + self.log.error("Failed to unload " + self.name + "!\n" + format_exc()) + exit(0) + + async def _on_new_message(self, message : str) -> str|None: + data = loads(message) + + if "stop" in data: + self.log.info("Calling Loader unload function.") + await self._unload() + get_event_loop().stop() + while get_event_loop().is_running(): + await sleep(0) + get_event_loop().close() + raise Exception("Closing message listener") + + # TODO there is definitely a better way to type this + d: Dict[str, Any] = {"res": None, "success": True} + try: + d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"]) + except Exception as e: + d["res"] = str(e) + d["success"] = False + finally: + return dumps(d, ensure_ascii=False) + + def start(self): + if self.passive: + return self + multiprocessing.Process(target=self._init).start() + return self + + def stop(self): + if self.passive: + return + + async def _(self: PluginWrapper): + await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) + await self.socket.close_socket_connection() + + get_event_loop().create_task(_(self)) + + async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]): + if self.passive: + raise RuntimeError("This plugin is passive (aka does not implement main.py)") + async with self.method_call_lock: + # reader, writer = + await self.socket.get_socket_connection() + + await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False)) + + line = await self.socket.read_single_line() + if line != None: + res = loads(line) + if not res["success"]: + raise Exception(res["res"]) + return res["res"] \ No newline at end of file diff --git a/backend/src/settings.py b/backend/src/settings.py new file mode 100644 index 00000000..a9ab3daa --- /dev/null +++ b/backend/src/settings.py @@ -0,0 +1,60 @@ +from json import dump, load +from os import mkdir, path, listdir, rename +from typing import Any, Dict +from .localplatform import chown, folder_owner, get_chown_plugin_path +from .customtypes import UserType + +from .helpers import get_homebrew_path + + +class SettingsManager: + def __init__(self, name: str, settings_directory: str | None = None) -> None: + wrong_dir = get_homebrew_path() + if settings_directory == None: + settings_directory = path.join(wrong_dir, "settings") + + self.path = path.join(settings_directory, name + ".json") + + #Create the folder with the correct permission + if not path.exists(settings_directory): + mkdir(settings_directory) + + #Copy all old settings file in the root directory to the correct folder + for file in listdir(wrong_dir): + if file.endswith(".json"): + rename(path.join(wrong_dir,file), + path.join(settings_directory, file)) + self.path = path.join(settings_directory, name + ".json") + + + #If the owner of the settings directory is not the user, then set it as the user: + expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.ROOT + if folder_owner(settings_directory) != expected_user: + chown(settings_directory, expected_user, False) + + self.settings: Dict[str, Any] = {} + + try: + open(self.path, "x", encoding="utf-8") + except FileExistsError as _: + self.read() + pass + + def read(self): + try: + with open(self.path, "r", encoding="utf-8") as file: + self.settings = load(file) + except Exception as e: + print(e) + pass + + def commit(self): + with open(self.path, "w+", encoding="utf-8") as file: + dump(self.settings, file, indent=4, ensure_ascii=False) + + def getSetting(self, key: str, default: Any = None) -> Any: + return self.settings.get(key, default) + + def setSetting(self, key: str, value: Any) -> Any: + self.settings[key] = value + self.commit() diff --git a/backend/src/updater.py b/backend/src/updater.py new file mode 100644 index 00000000..ac7c78d8 --- /dev/null +++ b/backend/src/updater.py @@ -0,0 +1,237 @@ +import os +import shutil +from asyncio import sleep +from json.decoder import JSONDecodeError +from logging import getLogger +from os import getcwd, path, remove +from typing import TYPE_CHECKING, List, TypedDict +if TYPE_CHECKING: + from .main import PluginManager +from .localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux + +from aiohttp import ClientSession, web + +from .import helpers +from .injector import get_gamepadui_tab +from .settings import SettingsManager + +logger = getLogger("Updater") + +class RemoteVerAsset(TypedDict): + name: str + browser_download_url: str +class RemoteVer(TypedDict): + tag_name: str + prerelease: bool + assets: List[RemoteVerAsset] + +class Updater: + def __init__(self, context: PluginManager) -> None: + self.context = context + self.settings = self.context.settings + # Exposes updater methods to frontend + self.updater_methods = { + "get_branch": self._get_branch, + "get_version": self.get_version, + "do_update": self.do_update, + "do_restart": self.do_restart, + "check_for_updates": self.check_for_updates + } + self.remoteVer: RemoteVer | None = None + self.allRemoteVers: List[RemoteVer] = [] + self.localVer = helpers.get_loader_version() + + try: + self.currentBranch = self.get_branch(self.context.settings) + except: + self.currentBranch = 0 + logger.error("Current branch could not be determined, defaulting to \"Stable\"") + + if context: + context.web_app.add_routes([ + web.post("/updater/{method_name}", self._handle_server_method_call) + ]) + context.loop.create_task(self.version_reloader()) + + async def _handle_server_method_call(self, request: web.Request): + method_name = request.match_info["method_name"] + try: + args = await request.json() + except JSONDecodeError: + args = {} + res = {} + try: + r = await self.updater_methods[method_name](**args) # type: ignore + res["result"] = r + res["success"] = True + except Exception as e: + res["result"] = str(e) + res["success"] = False + return web.json_response(res) + + def get_branch(self, manager: SettingsManager): + ver = manager.getSetting("branch", -1) + logger.debug("current branch: %i" % ver) + if ver == -1: + logger.info("Current branch is not set, determining branch from version...") + if self.localVer.startswith("v") and "-pre" in self.localVer: + logger.info("Current version determined to be pre-release") + manager.setSetting('branch', 1) + return 1 + else: + logger.info("Current version determined to be stable") + manager.setSetting('branch', 0) + return 0 + return ver + + async def _get_branch(self, manager: SettingsManager): + return self.get_branch(manager) + + # retrieve relevant service file's url for each branch + def get_service_url(self): + logger.debug("Getting service URL") + branch = self.get_branch(self.context.settings) + match branch: + case 0: + url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-release.service" + case 1 | 2: + url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service" + case _: + logger.error("You have an invalid branch set... Defaulting to prerelease service, please send the logs to the devs!") + url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service" + return str(url) + + async def get_version(self): + return { + "current": self.localVer, + "remote": self.remoteVer, + "all": self.allRemoteVers, + "updatable": self.localVer != "unknown" + } + + async def check_for_updates(self): + logger.debug("checking for updates") + selectedBranch = self.get_branch(self.context.settings) + async with ClientSession() as web: + async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", ssl=helpers.get_ssl_context()) as res: + remoteVersions: List[RemoteVer] = await res.json() + if selectedBranch == 0: + logger.debug("release type: release") + remoteVersions = list(filter(lambda ver: ver["tag_name"].startswith("v") and not ver["prerelease"] and not ver["tag_name"].find("-pre") > 0 and ver["tag_name"], remoteVersions)) + elif selectedBranch == 1: + logger.debug("release type: pre-release") + remoteVersions = list(filter(lambda ver:ver["tag_name"].startswith("v"), remoteVersions)) + else: + logger.error("release type: NOT FOUND") + raise ValueError("no valid branch found") + self.allRemoteVers = remoteVersions + logger.debug("determining release type to find, branch is %i" % selectedBranch) + if selectedBranch == 0: + logger.debug("release type: release") + self.remoteVer = next(filter(lambda ver: ver["tag_name"].startswith("v") and not ver["prerelease"] and not ver["tag_name"].find("-pre") > 0 and ver["tag_name"], remoteVersions), None) + elif selectedBranch == 1: + logger.debug("release type: pre-release") + self.remoteVer = next(filter(lambda ver:ver["tag_name"].startswith("v"), remoteVersions), None) + else: + logger.error("release type: NOT FOUND") + raise ValueError("no valid branch found") + logger.info("Updated remote version information") + tab = await get_gamepadui_tab() + await tab.evaluate_js(f"window.DeckyPluginLoader.notifyUpdates()", False, True, False) + return await self.get_version() + + async def version_reloader(self): + await sleep(30) + while True: + try: + await self.check_for_updates() + except: + pass + await sleep(60 * 60 * 6) # 6 hours + + async def do_update(self): + logger.debug("Starting update.") + try: + assert self.remoteVer + except AssertionError: + logger.error("Unable to update as remoteVer is missing") + return + + version = self.remoteVer["tag_name"] + download_url = None + download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe" + download_temp_filename = download_filename + ".new" + + for x in self.remoteVer["assets"]: + if x["name"] == download_filename: + download_url = x["browser_download_url"] + break + + if download_url == None: + raise Exception("Download url not found") + + service_url = self.get_service_url() + logger.debug("Retrieved service URL") + + tab = await get_gamepadui_tab() + await tab.open_websocket() + async with ClientSession() as web: + if ON_LINUX and not get_keep_systemd_service(): + logger.debug("Downloading systemd service") + # download the relevant systemd service depending upon branch + async with web.request("GET", service_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res: + logger.debug("Downloading service file") + data = await res.content.read() + logger.debug(str(data)) + service_file_path = path.join(getcwd(), "plugin_loader.service") + try: + with open(path.join(getcwd(), "plugin_loader.service"), "wb") as out: + out.write(data) + except Exception as e: + logger.error(f"Error at %s", exc_info=e) + with open(path.join(getcwd(), "plugin_loader.service"), "r", encoding="utf-8") as service_file: + service_data = service_file.read() + service_data = service_data.replace("${HOMEBREW_FOLDER}", helpers.get_homebrew_path()) + with open(path.join(getcwd(), "plugin_loader.service"), "w", encoding="utf-8") as service_file: + service_file.write(service_data) + + logger.debug("Saved service file") + logger.debug("Copying service file over current file.") + shutil.copy(service_file_path, "/etc/systemd/system/plugin_loader.service") + if not os.path.exists(path.join(getcwd(), ".systemd")): + os.mkdir(path.join(getcwd(), ".systemd")) + shutil.move(service_file_path, path.join(getcwd(), ".systemd")+"/plugin_loader.service") + + logger.debug("Downloading binary") + async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res: + total = int(res.headers.get('content-length', 0)) + with open(path.join(getcwd(), download_temp_filename), "wb") as out: + progress = 0 + raw = 0 + async for c in res.content.iter_chunked(512): + out.write(c) + raw += len(c) + new_progress = round((raw / total) * 100) + if progress != new_progress: + self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({new_progress})", False, False, False)) + progress = new_progress + + with open(path.join(getcwd(), ".loader.version"), "w", encoding="utf-8") as out: + out.write(version) + + if ON_LINUX: + remove(path.join(getcwd(), download_filename)) + shutil.move(path.join(getcwd(), download_temp_filename), path.join(getcwd(), download_filename)) + chmod(path.join(getcwd(), download_filename), 777, False) + if get_selinux(): + from asyncio.subprocess import create_subprocess_exec + process = await create_subprocess_exec("chcon", "-t", "bin_t", path.join(getcwd(), download_filename)) + logger.info(f"Setting the executable flag with chcon returned {await process.wait()}") + + logger.info("Updated loader installation.") + await tab.evaluate_js("window.DeckyUpdater.finish()", False, False) + await self.do_restart() + await tab.close_websocket() + + async def do_restart(self): + await service_restart("plugin_loader") diff --git a/backend/src/utilities.py b/backend/src/utilities.py new file mode 100644 index 00000000..3c7c8c2e --- /dev/null +++ b/backend/src/utilities.py @@ -0,0 +1,373 @@ +from __future__ import annotations +from os import stat_result +import uuid +from json.decoder import JSONDecodeError +from os.path import splitext +import re +from traceback import format_exc +from stat import FILE_ATTRIBUTE_HIDDEN # type: ignore + +from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection +from aiohttp import ClientSession, web +from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Any, List, TypedDict + +from logging import getLogger +from pathlib import Path + +from .browser import PluginInstallRequest, PluginInstallType +if TYPE_CHECKING: + from .main import PluginManager +from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab +from .localplatform import ON_WINDOWS +from .import helpers +from .localplatform import service_stop, service_start, get_home_path, get_username + +class FilePickerObj(TypedDict): + file: Path + filest: stat_result + is_dir: bool + +class Utilities: + def __init__(self, context: PluginManager) -> None: + self.context = context + self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = { + "ping": self.ping, + "http_request": self.http_request, + "install_plugin": self.install_plugin, + "install_plugins": self.install_plugins, + "cancel_plugin_install": self.cancel_plugin_install, + "confirm_plugin_install": self.confirm_plugin_install, + "uninstall_plugin": self.uninstall_plugin, + "execute_in_tab": self.execute_in_tab, + "inject_css_into_tab": self.inject_css_into_tab, + "remove_css_from_tab": self.remove_css_from_tab, + "allow_remote_debugging": self.allow_remote_debugging, + "disallow_remote_debugging": self.disallow_remote_debugging, + "set_setting": self.set_setting, + "get_setting": self.get_setting, + "filepicker_ls": self.filepicker_ls, + "disable_rdt": self.disable_rdt, + "enable_rdt": self.enable_rdt, + "get_tab_id": self.get_tab_id, + "get_user_info": self.get_user_info, + } + + self.logger = getLogger("Utilities") + + self.rdt_proxy_server = None + self.rdt_script_id = None + self.rdt_proxy_task = None + + if context: + context.web_app.add_routes([ + web.post("/methods/{method_name}", self._handle_server_method_call) + ]) + + async def _handle_server_method_call(self, request: web.Request): + method_name = request.match_info["method_name"] + try: + args = await request.json() + except JSONDecodeError: + args = {} + res = {} + try: + r = await self.util_methods[method_name](**args) + res["result"] = r + res["success"] = True + except Exception as e: + res["result"] = str(e) + res["success"] = False + return web.json_response(res) + + async def install_plugin(self, artifact: str="", name: str="No name", version: str="dev", hash: str="", install_type: PluginInstallType=PluginInstallType.INSTALL): + return await self.context.plugin_browser.request_plugin_install( + artifact=artifact, + name=name, + version=version, + hash=hash, + install_type=install_type + ) + + async def install_plugins(self, requests: List[PluginInstallRequest]): + return await self.context.plugin_browser.request_multiple_plugin_installs( + requests=requests + ) + + async def confirm_plugin_install(self, request_id: str): + return await self.context.plugin_browser.confirm_plugin_install(request_id) + + async def cancel_plugin_install(self, request_id: str): + return self.context.plugin_browser.cancel_plugin_install(request_id) + + async def uninstall_plugin(self, name: str): + return await self.context.plugin_browser.uninstall_plugin(name) + + async def http_request(self, method: str="", url: str="", **kwargs: Any): + async with ClientSession() as web: + res = await web.request(method, url, ssl=helpers.get_ssl_context(), **kwargs) + text = await res.text() + return { + "status": res.status, + "headers": dict(res.headers), + "body": text + } + + async def ping(self, **kwargs: Any): + return "pong" + + async def execute_in_tab(self, tab: str, run_async: bool, code: str): + try: + result = await inject_to_tab(tab, code, run_async) + assert result + if "exceptionDetails" in result["result"]: + return { + "success": False, + "result": result["result"] + } + + return { + "success": True, + "result": result["result"]["result"].get("value") + } + except Exception as e: + return { + "success": False, + "result": e + } + + async def inject_css_into_tab(self, tab: str, style: str): + try: + css_id = str(uuid.uuid4()) + + result = await inject_to_tab(tab, + f""" + (function() {{ + const style = document.createElement('style'); + style.id = "{css_id}"; + document.head.append(style); + style.textContent = `{style}`; + }})() + """, False) + + if result and "exceptionDetails" in result["result"]: + return { + "success": False, + "result": result["result"] + } + + return { + "success": True, + "result": css_id + } + except Exception as e: + return { + "success": False, + "result": e + } + + async def remove_css_from_tab(self, tab: str, css_id: str): + try: + result = await inject_to_tab(tab, + f""" + (function() {{ + let style = document.getElementById("{css_id}"); + + if (style.nodeName.toLowerCase() == 'style') + style.parentNode.removeChild(style); + }})() + """, False) + + if result and "exceptionDetails" in result["result"]: + return { + "success": False, + "result": result + } + + return { + "success": True + } + except Exception as e: + return { + "success": False, + "result": e + } + + async def get_setting(self, key: str, default: Any): + return self.context.settings.getSetting(key, default) + + async def set_setting(self, key: str, value: Any): + return self.context.settings.setSetting(key, value) + + async def allow_remote_debugging(self): + await service_start(helpers.REMOTE_DEBUGGER_UNIT) + return True + + async def disallow_remote_debugging(self): + await service_stop(helpers.REMOTE_DEBUGGER_UNIT) + return True + + async def filepicker_ls(self, + path : str | None = None, + include_files: bool = True, + include_folders: bool = True, + include_ext: list[str] = [], + include_hidden: bool = False, + order_by: str = "name_asc", + filter_for: str | None = None, + page: int = 1, + max: int = 1000): + + if path == None: + path = get_home_path() + + path_obj = Path(path).resolve() + + files: List[FilePickerObj] = [] + folders: List[FilePickerObj] = [] + + #Resolving all files/folders in the requested directory + for file in path_obj.iterdir(): + if file.exists(): + filest = file.stat() + is_hidden = file.name.startswith('.') + if ON_WINDOWS and not is_hidden: + is_hidden = bool(filest.st_file_attributes & FILE_ATTRIBUTE_HIDDEN) # type: ignore + if include_folders and file.is_dir(): + if (is_hidden and include_hidden) or not is_hidden: + folders.append({"file": file, "filest": filest, "is_dir": True}) + elif include_files: + # Handle requested extensions if present + if len(include_ext) == 0 or 'all_files' in include_ext \ + or splitext(file.name)[1].lstrip('.') in include_ext: + if (is_hidden and include_hidden) or not is_hidden: + files.append({"file": file, "filest": filest, "is_dir": False}) + # Filter logic + if filter_for is not None: + try: + if re.compile(filter_for): + files = list(filter(lambda file: re.search(filter_for, file["file"].name) != None, files)) + except re.error: + files = list(filter(lambda file: file["file"].name.find(filter_for) != -1, files)) + + # Ordering logic + ord_arg = order_by.split("_") + ord = ord_arg[0] + rev = True if ord_arg[1] == "asc" else False + match ord: + case 'name': + files.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) + folders.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) + case 'modified': + files.sort(key=lambda x: x['filest'].st_mtime, reverse = not rev) + folders.sort(key=lambda x: x['filest'].st_mtime, reverse = not rev) + case 'created': + files.sort(key=lambda x: x['filest'].st_ctime, reverse = not rev) + folders.sort(key=lambda x: x['filest'].st_ctime, reverse = not rev) + case 'size': + files.sort(key=lambda x: x['filest'].st_size, reverse = not rev) + # Folders has no file size, order by name instead + folders.sort(key=lambda x: x['file'].name.casefold()) + case _: + files.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) + folders.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) + + #Constructing the final file list, folders first + all = [{ + "isdir": x['is_dir'], + "name": str(x['file'].name), + "realpath": str(x['file']), + "size": x['filest'].st_size, + "modified": x['filest'].st_mtime, + "created": x['filest'].st_ctime, + } for x in folders + files ] + + return { + "realpath": str(path), + "files": all[(page-1)*max:(page)*max], + "total": len(all), + } + + + # Based on https://stackoverflow.com/a/46422554/13174603 + def start_rdt_proxy(self, ip: str, port: int): + async def pipe(reader: StreamReader, writer: StreamWriter): + try: + while not reader.at_eof(): + writer.write(await reader.read(2048)) + finally: + writer.close() + async def handle_client(local_reader: StreamReader, local_writer: StreamWriter): + try: + remote_reader, remote_writer = await open_connection( + ip, port) + pipe1 = pipe(local_reader, remote_writer) + pipe2 = pipe(remote_reader, local_writer) + await gather(pipe1, pipe2) + finally: + local_writer.close() + + self.rdt_proxy_server = start_server(handle_client, "127.0.0.1", port) + self.rdt_proxy_task = self.context.loop.create_task(self.rdt_proxy_server) + + def stop_rdt_proxy(self): + if self.rdt_proxy_server != None: + self.rdt_proxy_server.close() + if self.rdt_proxy_task: + self.rdt_proxy_task.cancel() + + async def _enable_rdt(self): + # TODO un-hardcode port + try: + self.stop_rdt_proxy() + ip = self.context.settings.getSetting("developer.rdt.ip", None) + + if ip != None: + self.logger.info("Connecting to React DevTools at " + ip) + async with ClientSession() as web: + res = await web.request("GET", "http://" + ip + ":8097", ssl=helpers.get_ssl_context()) + script = """ + if (!window.deckyHasConnectedRDT) { + window.deckyHasConnectedRDT = true; + // This fixes the overlay when hovering over an element in RDT + Object.defineProperty(window, '__REACT_DEVTOOLS_TARGET_WINDOW__', { + enumerable: true, + configurable: true, + get: function() { + return (GamepadNavTree?.m_context?.m_controller || FocusNavController)?.m_ActiveContext?.ActiveWindow || window; + } + }); + """ + await res.text() + "\n}" + if res.status != 200: + self.logger.error("Failed to connect to React DevTools at " + ip) + return False + self.start_rdt_proxy(ip, 8097) + self.logger.info("Connected to React DevTools, loading script") + tab = await get_gamepadui_tab() + # RDT needs to load before React itself to work. + await close_old_tabs() + result = await tab.reload_and_evaluate(script) + self.logger.info(result) + + except Exception: + self.logger.error("Failed to connect to React DevTools") + self.logger.error(format_exc()) + + async def enable_rdt(self): + self.context.loop.create_task(self._enable_rdt()) + + async def disable_rdt(self): + self.logger.info("Disabling React DevTools") + tab = await get_gamepadui_tab() + self.rdt_script_id = None + await close_old_tabs() + await tab.evaluate_js("location.reload();", False, True, False) + self.logger.info("React DevTools disabled") + + async def get_user_info(self) -> Dict[str, str]: + return { + "username": get_username(), + "path": get_home_path() + } + + async def get_tab_id(self, name: str): + return (await get_tab(name)).id diff --git a/backend/updater.py b/backend/updater.py deleted file mode 100644 index ac7c78d8..00000000 --- a/backend/updater.py +++ /dev/null @@ -1,237 +0,0 @@ -import os -import shutil -from asyncio import sleep -from json.decoder import JSONDecodeError -from logging import getLogger -from os import getcwd, path, remove -from typing import TYPE_CHECKING, List, TypedDict -if TYPE_CHECKING: - from .main import PluginManager -from .localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux - -from aiohttp import ClientSession, web - -from .import helpers -from .injector import get_gamepadui_tab -from .settings import SettingsManager - -logger = getLogger("Updater") - -class RemoteVerAsset(TypedDict): - name: str - browser_download_url: str -class RemoteVer(TypedDict): - tag_name: str - prerelease: bool - assets: List[RemoteVerAsset] - -class Updater: - def __init__(self, context: PluginManager) -> None: - self.context = context - self.settings = self.context.settings - # Exposes updater methods to frontend - self.updater_methods = { - "get_branch": self._get_branch, - "get_version": self.get_version, - "do_update": self.do_update, - "do_restart": self.do_restart, - "check_for_updates": self.check_for_updates - } - self.remoteVer: RemoteVer | None = None - self.allRemoteVers: List[RemoteVer] = [] - self.localVer = helpers.get_loader_version() - - try: - self.currentBranch = self.get_branch(self.context.settings) - except: - self.currentBranch = 0 - logger.error("Current branch could not be determined, defaulting to \"Stable\"") - - if context: - context.web_app.add_routes([ - web.post("/updater/{method_name}", self._handle_server_method_call) - ]) - context.loop.create_task(self.version_reloader()) - - async def _handle_server_method_call(self, request: web.Request): - method_name = request.match_info["method_name"] - try: - args = await request.json() - except JSONDecodeError: - args = {} - res = {} - try: - r = await self.updater_methods[method_name](**args) # type: ignore - res["result"] = r - res["success"] = True - except Exception as e: - res["result"] = str(e) - res["success"] = False - return web.json_response(res) - - def get_branch(self, manager: SettingsManager): - ver = manager.getSetting("branch", -1) - logger.debug("current branch: %i" % ver) - if ver == -1: - logger.info("Current branch is not set, determining branch from version...") - if self.localVer.startswith("v") and "-pre" in self.localVer: - logger.info("Current version determined to be pre-release") - manager.setSetting('branch', 1) - return 1 - else: - logger.info("Current version determined to be stable") - manager.setSetting('branch', 0) - return 0 - return ver - - async def _get_branch(self, manager: SettingsManager): - return self.get_branch(manager) - - # retrieve relevant service file's url for each branch - def get_service_url(self): - logger.debug("Getting service URL") - branch = self.get_branch(self.context.settings) - match branch: - case 0: - url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-release.service" - case 1 | 2: - url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service" - case _: - logger.error("You have an invalid branch set... Defaulting to prerelease service, please send the logs to the devs!") - url = "https://raw.githubusercontent.com/SteamDeckHomebrew/decky-loader/main/dist/plugin_loader-prerelease.service" - return str(url) - - async def get_version(self): - return { - "current": self.localVer, - "remote": self.remoteVer, - "all": self.allRemoteVers, - "updatable": self.localVer != "unknown" - } - - async def check_for_updates(self): - logger.debug("checking for updates") - selectedBranch = self.get_branch(self.context.settings) - async with ClientSession() as web: - async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", ssl=helpers.get_ssl_context()) as res: - remoteVersions: List[RemoteVer] = await res.json() - if selectedBranch == 0: - logger.debug("release type: release") - remoteVersions = list(filter(lambda ver: ver["tag_name"].startswith("v") and not ver["prerelease"] and not ver["tag_name"].find("-pre") > 0 and ver["tag_name"], remoteVersions)) - elif selectedBranch == 1: - logger.debug("release type: pre-release") - remoteVersions = list(filter(lambda ver:ver["tag_name"].startswith("v"), remoteVersions)) - else: - logger.error("release type: NOT FOUND") - raise ValueError("no valid branch found") - self.allRemoteVers = remoteVersions - logger.debug("determining release type to find, branch is %i" % selectedBranch) - if selectedBranch == 0: - logger.debug("release type: release") - self.remoteVer = next(filter(lambda ver: ver["tag_name"].startswith("v") and not ver["prerelease"] and not ver["tag_name"].find("-pre") > 0 and ver["tag_name"], remoteVersions), None) - elif selectedBranch == 1: - logger.debug("release type: pre-release") - self.remoteVer = next(filter(lambda ver:ver["tag_name"].startswith("v"), remoteVersions), None) - else: - logger.error("release type: NOT FOUND") - raise ValueError("no valid branch found") - logger.info("Updated remote version information") - tab = await get_gamepadui_tab() - await tab.evaluate_js(f"window.DeckyPluginLoader.notifyUpdates()", False, True, False) - return await self.get_version() - - async def version_reloader(self): - await sleep(30) - while True: - try: - await self.check_for_updates() - except: - pass - await sleep(60 * 60 * 6) # 6 hours - - async def do_update(self): - logger.debug("Starting update.") - try: - assert self.remoteVer - except AssertionError: - logger.error("Unable to update as remoteVer is missing") - return - - version = self.remoteVer["tag_name"] - download_url = None - download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe" - download_temp_filename = download_filename + ".new" - - for x in self.remoteVer["assets"]: - if x["name"] == download_filename: - download_url = x["browser_download_url"] - break - - if download_url == None: - raise Exception("Download url not found") - - service_url = self.get_service_url() - logger.debug("Retrieved service URL") - - tab = await get_gamepadui_tab() - await tab.open_websocket() - async with ClientSession() as web: - if ON_LINUX and not get_keep_systemd_service(): - logger.debug("Downloading systemd service") - # download the relevant systemd service depending upon branch - async with web.request("GET", service_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res: - logger.debug("Downloading service file") - data = await res.content.read() - logger.debug(str(data)) - service_file_path = path.join(getcwd(), "plugin_loader.service") - try: - with open(path.join(getcwd(), "plugin_loader.service"), "wb") as out: - out.write(data) - except Exception as e: - logger.error(f"Error at %s", exc_info=e) - with open(path.join(getcwd(), "plugin_loader.service"), "r", encoding="utf-8") as service_file: - service_data = service_file.read() - service_data = service_data.replace("${HOMEBREW_FOLDER}", helpers.get_homebrew_path()) - with open(path.join(getcwd(), "plugin_loader.service"), "w", encoding="utf-8") as service_file: - service_file.write(service_data) - - logger.debug("Saved service file") - logger.debug("Copying service file over current file.") - shutil.copy(service_file_path, "/etc/systemd/system/plugin_loader.service") - if not os.path.exists(path.join(getcwd(), ".systemd")): - os.mkdir(path.join(getcwd(), ".systemd")) - shutil.move(service_file_path, path.join(getcwd(), ".systemd")+"/plugin_loader.service") - - logger.debug("Downloading binary") - async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res: - total = int(res.headers.get('content-length', 0)) - with open(path.join(getcwd(), download_temp_filename), "wb") as out: - progress = 0 - raw = 0 - async for c in res.content.iter_chunked(512): - out.write(c) - raw += len(c) - new_progress = round((raw / total) * 100) - if progress != new_progress: - self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({new_progress})", False, False, False)) - progress = new_progress - - with open(path.join(getcwd(), ".loader.version"), "w", encoding="utf-8") as out: - out.write(version) - - if ON_LINUX: - remove(path.join(getcwd(), download_filename)) - shutil.move(path.join(getcwd(), download_temp_filename), path.join(getcwd(), download_filename)) - chmod(path.join(getcwd(), download_filename), 777, False) - if get_selinux(): - from asyncio.subprocess import create_subprocess_exec - process = await create_subprocess_exec("chcon", "-t", "bin_t", path.join(getcwd(), download_filename)) - logger.info(f"Setting the executable flag with chcon returned {await process.wait()}") - - logger.info("Updated loader installation.") - await tab.evaluate_js("window.DeckyUpdater.finish()", False, False) - await self.do_restart() - await tab.close_websocket() - - async def do_restart(self): - await service_restart("plugin_loader") diff --git a/backend/utilities.py b/backend/utilities.py deleted file mode 100644 index 3c7c8c2e..00000000 --- a/backend/utilities.py +++ /dev/null @@ -1,373 +0,0 @@ -from __future__ import annotations -from os import stat_result -import uuid -from json.decoder import JSONDecodeError -from os.path import splitext -import re -from traceback import format_exc -from stat import FILE_ATTRIBUTE_HIDDEN # type: ignore - -from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection -from aiohttp import ClientSession, web -from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Any, List, TypedDict - -from logging import getLogger -from pathlib import Path - -from .browser import PluginInstallRequest, PluginInstallType -if TYPE_CHECKING: - from .main import PluginManager -from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab -from .localplatform import ON_WINDOWS -from .import helpers -from .localplatform import service_stop, service_start, get_home_path, get_username - -class FilePickerObj(TypedDict): - file: Path - filest: stat_result - is_dir: bool - -class Utilities: - def __init__(self, context: PluginManager) -> None: - self.context = context - self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = { - "ping": self.ping, - "http_request": self.http_request, - "install_plugin": self.install_plugin, - "install_plugins": self.install_plugins, - "cancel_plugin_install": self.cancel_plugin_install, - "confirm_plugin_install": self.confirm_plugin_install, - "uninstall_plugin": self.uninstall_plugin, - "execute_in_tab": self.execute_in_tab, - "inject_css_into_tab": self.inject_css_into_tab, - "remove_css_from_tab": self.remove_css_from_tab, - "allow_remote_debugging": self.allow_remote_debugging, - "disallow_remote_debugging": self.disallow_remote_debugging, - "set_setting": self.set_setting, - "get_setting": self.get_setting, - "filepicker_ls": self.filepicker_ls, - "disable_rdt": self.disable_rdt, - "enable_rdt": self.enable_rdt, - "get_tab_id": self.get_tab_id, - "get_user_info": self.get_user_info, - } - - self.logger = getLogger("Utilities") - - self.rdt_proxy_server = None - self.rdt_script_id = None - self.rdt_proxy_task = None - - if context: - context.web_app.add_routes([ - web.post("/methods/{method_name}", self._handle_server_method_call) - ]) - - async def _handle_server_method_call(self, request: web.Request): - method_name = request.match_info["method_name"] - try: - args = await request.json() - except JSONDecodeError: - args = {} - res = {} - try: - r = await self.util_methods[method_name](**args) - res["result"] = r - res["success"] = True - except Exception as e: - res["result"] = str(e) - res["success"] = False - return web.json_response(res) - - async def install_plugin(self, artifact: str="", name: str="No name", version: str="dev", hash: str="", install_type: PluginInstallType=PluginInstallType.INSTALL): - return await self.context.plugin_browser.request_plugin_install( - artifact=artifact, - name=name, - version=version, - hash=hash, - install_type=install_type - ) - - async def install_plugins(self, requests: List[PluginInstallRequest]): - return await self.context.plugin_browser.request_multiple_plugin_installs( - requests=requests - ) - - async def confirm_plugin_install(self, request_id: str): - return await self.context.plugin_browser.confirm_plugin_install(request_id) - - async def cancel_plugin_install(self, request_id: str): - return self.context.plugin_browser.cancel_plugin_install(request_id) - - async def uninstall_plugin(self, name: str): - return await self.context.plugin_browser.uninstall_plugin(name) - - async def http_request(self, method: str="", url: str="", **kwargs: Any): - async with ClientSession() as web: - res = await web.request(method, url, ssl=helpers.get_ssl_context(), **kwargs) - text = await res.text() - return { - "status": res.status, - "headers": dict(res.headers), - "body": text - } - - async def ping(self, **kwargs: Any): - return "pong" - - async def execute_in_tab(self, tab: str, run_async: bool, code: str): - try: - result = await inject_to_tab(tab, code, run_async) - assert result - if "exceptionDetails" in result["result"]: - return { - "success": False, - "result": result["result"] - } - - return { - "success": True, - "result": result["result"]["result"].get("value") - } - except Exception as e: - return { - "success": False, - "result": e - } - - async def inject_css_into_tab(self, tab: str, style: str): - try: - css_id = str(uuid.uuid4()) - - result = await inject_to_tab(tab, - f""" - (function() {{ - const style = document.createElement('style'); - style.id = "{css_id}"; - document.head.append(style); - style.textContent = `{style}`; - }})() - """, False) - - if result and "exceptionDetails" in result["result"]: - return { - "success": False, - "result": result["result"] - } - - return { - "success": True, - "result": css_id - } - except Exception as e: - return { - "success": False, - "result": e - } - - async def remove_css_from_tab(self, tab: str, css_id: str): - try: - result = await inject_to_tab(tab, - f""" - (function() {{ - let style = document.getElementById("{css_id}"); - - if (style.nodeName.toLowerCase() == 'style') - style.parentNode.removeChild(style); - }})() - """, False) - - if result and "exceptionDetails" in result["result"]: - return { - "success": False, - "result": result - } - - return { - "success": True - } - except Exception as e: - return { - "success": False, - "result": e - } - - async def get_setting(self, key: str, default: Any): - return self.context.settings.getSetting(key, default) - - async def set_setting(self, key: str, value: Any): - return self.context.settings.setSetting(key, value) - - async def allow_remote_debugging(self): - await service_start(helpers.REMOTE_DEBUGGER_UNIT) - return True - - async def disallow_remote_debugging(self): - await service_stop(helpers.REMOTE_DEBUGGER_UNIT) - return True - - async def filepicker_ls(self, - path : str | None = None, - include_files: bool = True, - include_folders: bool = True, - include_ext: list[str] = [], - include_hidden: bool = False, - order_by: str = "name_asc", - filter_for: str | None = None, - page: int = 1, - max: int = 1000): - - if path == None: - path = get_home_path() - - path_obj = Path(path).resolve() - - files: List[FilePickerObj] = [] - folders: List[FilePickerObj] = [] - - #Resolving all files/folders in the requested directory - for file in path_obj.iterdir(): - if file.exists(): - filest = file.stat() - is_hidden = file.name.startswith('.') - if ON_WINDOWS and not is_hidden: - is_hidden = bool(filest.st_file_attributes & FILE_ATTRIBUTE_HIDDEN) # type: ignore - if include_folders and file.is_dir(): - if (is_hidden and include_hidden) or not is_hidden: - folders.append({"file": file, "filest": filest, "is_dir": True}) - elif include_files: - # Handle requested extensions if present - if len(include_ext) == 0 or 'all_files' in include_ext \ - or splitext(file.name)[1].lstrip('.') in include_ext: - if (is_hidden and include_hidden) or not is_hidden: - files.append({"file": file, "filest": filest, "is_dir": False}) - # Filter logic - if filter_for is not None: - try: - if re.compile(filter_for): - files = list(filter(lambda file: re.search(filter_for, file["file"].name) != None, files)) - except re.error: - files = list(filter(lambda file: file["file"].name.find(filter_for) != -1, files)) - - # Ordering logic - ord_arg = order_by.split("_") - ord = ord_arg[0] - rev = True if ord_arg[1] == "asc" else False - match ord: - case 'name': - files.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) - folders.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) - case 'modified': - files.sort(key=lambda x: x['filest'].st_mtime, reverse = not rev) - folders.sort(key=lambda x: x['filest'].st_mtime, reverse = not rev) - case 'created': - files.sort(key=lambda x: x['filest'].st_ctime, reverse = not rev) - folders.sort(key=lambda x: x['filest'].st_ctime, reverse = not rev) - case 'size': - files.sort(key=lambda x: x['filest'].st_size, reverse = not rev) - # Folders has no file size, order by name instead - folders.sort(key=lambda x: x['file'].name.casefold()) - case _: - files.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) - folders.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) - - #Constructing the final file list, folders first - all = [{ - "isdir": x['is_dir'], - "name": str(x['file'].name), - "realpath": str(x['file']), - "size": x['filest'].st_size, - "modified": x['filest'].st_mtime, - "created": x['filest'].st_ctime, - } for x in folders + files ] - - return { - "realpath": str(path), - "files": all[(page-1)*max:(page)*max], - "total": len(all), - } - - - # Based on https://stackoverflow.com/a/46422554/13174603 - def start_rdt_proxy(self, ip: str, port: int): - async def pipe(reader: StreamReader, writer: StreamWriter): - try: - while not reader.at_eof(): - writer.write(await reader.read(2048)) - finally: - writer.close() - async def handle_client(local_reader: StreamReader, local_writer: StreamWriter): - try: - remote_reader, remote_writer = await open_connection( - ip, port) - pipe1 = pipe(local_reader, remote_writer) - pipe2 = pipe(remote_reader, local_writer) - await gather(pipe1, pipe2) - finally: - local_writer.close() - - self.rdt_proxy_server = start_server(handle_client, "127.0.0.1", port) - self.rdt_proxy_task = self.context.loop.create_task(self.rdt_proxy_server) - - def stop_rdt_proxy(self): - if self.rdt_proxy_server != None: - self.rdt_proxy_server.close() - if self.rdt_proxy_task: - self.rdt_proxy_task.cancel() - - async def _enable_rdt(self): - # TODO un-hardcode port - try: - self.stop_rdt_proxy() - ip = self.context.settings.getSetting("developer.rdt.ip", None) - - if ip != None: - self.logger.info("Connecting to React DevTools at " + ip) - async with ClientSession() as web: - res = await web.request("GET", "http://" + ip + ":8097", ssl=helpers.get_ssl_context()) - script = """ - if (!window.deckyHasConnectedRDT) { - window.deckyHasConnectedRDT = true; - // This fixes the overlay when hovering over an element in RDT - Object.defineProperty(window, '__REACT_DEVTOOLS_TARGET_WINDOW__', { - enumerable: true, - configurable: true, - get: function() { - return (GamepadNavTree?.m_context?.m_controller || FocusNavController)?.m_ActiveContext?.ActiveWindow || window; - } - }); - """ + await res.text() + "\n}" - if res.status != 200: - self.logger.error("Failed to connect to React DevTools at " + ip) - return False - self.start_rdt_proxy(ip, 8097) - self.logger.info("Connected to React DevTools, loading script") - tab = await get_gamepadui_tab() - # RDT needs to load before React itself to work. - await close_old_tabs() - result = await tab.reload_and_evaluate(script) - self.logger.info(result) - - except Exception: - self.logger.error("Failed to connect to React DevTools") - self.logger.error(format_exc()) - - async def enable_rdt(self): - self.context.loop.create_task(self._enable_rdt()) - - async def disable_rdt(self): - self.logger.info("Disabling React DevTools") - tab = await get_gamepadui_tab() - self.rdt_script_id = None - await close_old_tabs() - await tab.evaluate_js("location.reload();", False, True, False) - self.logger.info("React DevTools disabled") - - async def get_user_info(self) -> Dict[str, str]: - return { - "username": get_username(), - "path": get_home_path() - } - - async def get_tab_id(self, name: str): - return (await get_tab(name)).id -- cgit v1.2.3 From 37b8c5264fd61ec217d73f2eef0acbe19496a6e2 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 26 Sep 2023 14:55:09 +0200 Subject: Moved main.py --- backend/main.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 backend/main.py (limited to 'backend') diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 00000000..c2b99089 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,4 @@ +# This file is needed to make the relative imports in backend/ work properly. +if __name__ == "__main__": + from backend.main import main + main() -- cgit v1.2.3 From 45353c87c22613735bc81be8eaa985ce6acddf7a Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 26 Sep 2023 14:58:27 +0200 Subject: Moved locales folder and requirements.txt --- backend/locales/bg-BG.json | 252 ++++++++++++++++++++++++++++++++++++++ backend/locales/cs-CZ.json | 267 +++++++++++++++++++++++++++++++++++++++++ backend/locales/de-DE.json | 195 ++++++++++++++++++++++++++++++ backend/locales/el-GR.json | 260 +++++++++++++++++++++++++++++++++++++++ backend/locales/en-US.json | 260 +++++++++++++++++++++++++++++++++++++++ backend/locales/es-ES.json | 217 +++++++++++++++++++++++++++++++++ backend/locales/fi-FI.json | 260 +++++++++++++++++++++++++++++++++++++++ backend/locales/fr-FR.json | 201 +++++++++++++++++++++++++++++++ backend/locales/it-IT.json | 267 +++++++++++++++++++++++++++++++++++++++++ backend/locales/ko-KR.json | 253 ++++++++++++++++++++++++++++++++++++++ backend/locales/nl-NL.json | 243 +++++++++++++++++++++++++++++++++++++ backend/locales/pl-PL.json | 267 +++++++++++++++++++++++++++++++++++++++++ backend/locales/pt-BR.json | 259 +++++++++++++++++++++++++++++++++++++++ backend/locales/pt-PT.json | 222 ++++++++++++++++++++++++++++++++++ backend/locales/ru-RU.json | 267 +++++++++++++++++++++++++++++++++++++++++ backend/locales/sq-AL.json | 131 ++++++++++++++++++++ backend/locales/uk-UA.json | 222 ++++++++++++++++++++++++++++++++++ backend/locales/zh-CN.json | 253 ++++++++++++++++++++++++++++++++++++++ backend/locales/zh-TW.json | 245 +++++++++++++++++++++++++++++++++++++ backend/requirements.txt | 5 + backend/src/locales/bg-BG.json | 252 -------------------------------------- backend/src/locales/cs-CZ.json | 267 ----------------------------------------- backend/src/locales/de-DE.json | 195 ------------------------------ backend/src/locales/el-GR.json | 260 --------------------------------------- backend/src/locales/en-US.json | 260 --------------------------------------- backend/src/locales/es-ES.json | 217 --------------------------------- backend/src/locales/fi-FI.json | 260 --------------------------------------- backend/src/locales/fr-FR.json | 201 ------------------------------- backend/src/locales/it-IT.json | 267 ----------------------------------------- backend/src/locales/ko-KR.json | 253 -------------------------------------- backend/src/locales/nl-NL.json | 243 ------------------------------------- backend/src/locales/pl-PL.json | 267 ----------------------------------------- backend/src/locales/pt-BR.json | 259 --------------------------------------- backend/src/locales/pt-PT.json | 222 ---------------------------------- backend/src/locales/ru-RU.json | 267 ----------------------------------------- backend/src/locales/sq-AL.json | 131 -------------------- backend/src/locales/uk-UA.json | 222 ---------------------------------- backend/src/locales/zh-CN.json | 253 -------------------------------------- backend/src/locales/zh-TW.json | 245 ------------------------------------- 39 files changed, 4546 insertions(+), 4541 deletions(-) create mode 100644 backend/locales/bg-BG.json create mode 100644 backend/locales/cs-CZ.json create mode 100644 backend/locales/de-DE.json create mode 100644 backend/locales/el-GR.json create mode 100644 backend/locales/en-US.json create mode 100644 backend/locales/es-ES.json create mode 100644 backend/locales/fi-FI.json create mode 100644 backend/locales/fr-FR.json create mode 100644 backend/locales/it-IT.json create mode 100644 backend/locales/ko-KR.json create mode 100644 backend/locales/nl-NL.json create mode 100644 backend/locales/pl-PL.json create mode 100644 backend/locales/pt-BR.json create mode 100644 backend/locales/pt-PT.json create mode 100644 backend/locales/ru-RU.json create mode 100644 backend/locales/sq-AL.json create mode 100644 backend/locales/uk-UA.json create mode 100644 backend/locales/zh-CN.json create mode 100644 backend/locales/zh-TW.json create mode 100644 backend/requirements.txt delete mode 100644 backend/src/locales/bg-BG.json delete mode 100644 backend/src/locales/cs-CZ.json delete mode 100644 backend/src/locales/de-DE.json delete mode 100644 backend/src/locales/el-GR.json delete mode 100644 backend/src/locales/en-US.json delete mode 100644 backend/src/locales/es-ES.json delete mode 100644 backend/src/locales/fi-FI.json delete mode 100644 backend/src/locales/fr-FR.json delete mode 100644 backend/src/locales/it-IT.json delete mode 100644 backend/src/locales/ko-KR.json delete mode 100644 backend/src/locales/nl-NL.json delete mode 100644 backend/src/locales/pl-PL.json delete mode 100644 backend/src/locales/pt-BR.json delete mode 100644 backend/src/locales/pt-PT.json delete mode 100644 backend/src/locales/ru-RU.json delete mode 100644 backend/src/locales/sq-AL.json delete mode 100644 backend/src/locales/uk-UA.json delete mode 100644 backend/src/locales/zh-CN.json delete mode 100644 backend/src/locales/zh-TW.json (limited to 'backend') diff --git a/backend/locales/bg-BG.json b/backend/locales/bg-BG.json new file mode 100644 index 00000000..b9c4d803 --- /dev/null +++ b/backend/locales/bg-BG.json @@ -0,0 +1,252 @@ +{ + "BranchSelect": { + "update_channel": { + "stable": "Стабилен", + "testing": "Тестване", + "label": "Канал за обновления", + "prerelease": "Предварителни издания" + } + }, + "Developer": { + "5secreload": "Презареждане след 5 секунди", + "disabling": "Изключване на React DevTools", + "enabling": "Включване на React DevTools" + }, + "DropdownMultiselect": { + "button": { + "back": "Назад" + } + }, + "FilePickerError": { + "errors": { + "unknown": "Възникна неизвестна грешка. Грешката в суров вид е: {{raw_error}}", + "file_not_found": "Посоченият път е неправилен. Проверете го и го въведете правилно.", + "perm_denied": "Нямате достъп до посочената папка. Проверете дали потребителят (deck на Steam Deck) има съответните правомощия за достъп до посочената папка/файл." + } + }, + "FilePickerIndex": { + "file": { + "select": "Избиране на този файл" + }, + "files": { + "all_files": "Всички файлове", + "file_type": "Файлов тип", + "show_hidden": "Показване на скритите файлове" + }, + "filter": { + "created_asce": "Дата на създаване (първо най-старите)", + "created_desc": "Дата на създаване (първо най-новите)", + "modified_asce": "Дата на промяна (първо най-старите)", + "modified_desc": "Дата на промяна (първо най-новите)", + "name_asce": "Я-А", + "name_desc": "А-Я", + "size_asce": "Размер (първо най-малките)", + "size_desc": "Размер (първо най-големите)" + }, + "folder": { + "label": "Папка", + "show_more": "Показване на още файлове", + "select": "Използване на тази папка" + } + }, + "MultiplePluginsInstallModal": { + "description": { + "install": "Инсталиране на {{name}} {{version}}", + "reinstall": "Преинсталиране на {{name}} {{version}}", + "update": "Обновяване на {{name}} до {{version}}" + }, + "ok_button": { + "idle": "Потвърждаване", + "loading": "В процес на работа" + }, + "title": { + "mixed_one": "Промяна на {{count}} добавка", + "mixed_other": "Промяна на {{count}} добавки", + "update_one": "Обновяване на 1 добавка", + "update_other": "Обновяване на {{count}} добавки", + "install_one": "Инсталиране на 1 добавка", + "install_other": "Инсталиране на {{count}} добавки", + "reinstall_one": "Преинсталиране на 1 добавка", + "reinstall_other": "Преинсталиране на {{count}} добавки" + }, + "confirm": "Наистина ли искате да направите следните промени?" + }, + "PluginCard": { + "plugin_full_access": "Тази добавка има пълен достъп до Вашия Steam Deck.", + "plugin_install": "Инсталиране", + "plugin_no_desc": "Няма описание.", + "plugin_version_label": "Версия на добавката" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Инсталиране", + "desc": "Наистина ли искате да инсталирате {{artifact}} {{version}}?", + "title": "Инсталиране на {{artifact}}", + "button_processing": "Инсталиране" + }, + "reinstall": { + "button_idle": "Преинсталиране", + "button_processing": "Преинсталиране", + "desc": "Наистина ли искате да преинсталирате {{artifact}} {{version}}?", + "title": "Преинсталиране на {{artifact}}" + }, + "update": { + "button_idle": "Обновяване", + "title": "Обновяване на {{artifact}}", + "button_processing": "Обновяване", + "desc": "Наистина ли искате да обновите {{artifact}} {{version}}?" + }, + "no_hash": "Тази добавка няма хеш. Инсталирате я на свой собствен риск." + }, + "PluginListIndex": { + "hide": "Бърз достъп: Скриване", + "no_plugin": "Няма инсталирани добавки!", + "plugin_actions": "Действия с добавката", + "reinstall": "Преинсталиране", + "uninstall": "Деинсталиране", + "update_to": "Обновяване до {{name}}", + "reload": "Презареждане", + "show": "Бърз достъп: Показване", + "update_all_one": "Обновяване на 1 добавка", + "update_all_other": "Обновяване на {{count}} добавки" + }, + "PluginListLabel": { + "hidden": "Скрито от менюто за бърз достъп" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Грешка", + "plugin_load_error": { + "message": "Грешка при зареждането на добавката {{name}}", + "toast": "Грешка при зареждането на {{name}}" + }, + "plugin_uninstall": { + "button": "Деинсталиране", + "desc": "Наистина ли искате да деинсталирате {{name}}?", + "title": "Деинсталиране на {{name}}" + }, + "plugin_update_one": "Има налично обновление за 1 добавка!", + "plugin_update_other": "Има налични обновления за {{count}} добавки!", + "decky_update_available": "Има налично обновление до {{tag_name}}!", + "plugin_error_uninstall": "Зареждането на {{name}} предизвика грешка, както се вижда по-горе. Това обикновено означава, че добавката изисква обновяване на новата версия на SteamUI. Проверете дали има обновление или изберете да я премахнете в настройките на Decky, в раздела с добавките." + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Разрешаване на достъп без удостоверяване до дебъгера на CEF на всеки от Вашата мрежа", + "label": "Разрешаване на отдалеченото дебъгване на CEF" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Отваряне на конзолата", + "label": "Конзола на CEF", + "desc": "Отваря конзолата на CEF. Това има смисъл единствено за дебъгване. Нещата тук може да са опасни и трябва да бъдат използвани само ако Вие сте разработчик на добавка, или получавате насоки от такъв." + }, + "header": "Други", + "react_devtools": { + "ip_label": "IP", + "label": "Включване на React DevTools", + "desc": "Включва свързването към компютър, на който работи React DevTools. Промяната на тази настройка ще презареди Steam. Задайте IP адреса преди да включите това." + }, + "third_party_plugins": { + "button_install": "Инсталиране", + "button_zip": "Разглеждане", + "header": "Добавки от външен източник", + "label_desc": "Адрес", + "label_zip": "Инсталиране на добавка от файл ZIP", + "label_url": "Инсталиране на добавка от адрес в Интернет" + }, + "valve_internal": { + "desc2": "Не пипайте нищо в това меню, освен ако не знаете какво правите.", + "label": "Включване на вътрешното меню на Valve", + "desc1": "Включва вътрешното меню за разработчици на Valve." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Версия на Decky", + "header": "Относно" + }, + "developer_mode": { + "label": "Режим за разработчици" + }, + "notifications": { + "decky_updates_label": "Има налично обновление на Decky", + "header": "Известия", + "plugin_updates_label": "Има налични обновления на добавките" + }, + "other": { + "header": "Други" + }, + "updates": { + "header": "Обновления" + }, + "beta": { + "header": "Участие в бета-версии" + } + }, + "SettingsIndex": { + "developer_title": "Разработчик", + "general_title": "Общи", + "plugins_title": "Добавки" + }, + "Store": { + "store_contrib": { + "label": "Допринасяне", + "desc": "Ако искате да допринесете към магазина за добавки на Decky, разгледайте хранилището SteamDeckHomebrew/decky-plugin-template в GitHub. Може да намерите информация относно разработката и разпространението във файла README." + }, + "store_filter": { + "label": "Филтър", + "label_def": "Всички" + }, + "store_search": { + "label": "Търсене" + }, + "store_sort": { + "label": "Подредба", + "label_def": "Последно обновление (първо най-новите)" + }, + "store_source": { + "label": "Изходен код", + "desc": "Целият изходен код е наличен в хранилището SteamDeckHomebrew/decky-plugin-database в GitHub." + }, + "store_tabs": { + "about": "Относно", + "alph_asce": "По азбучен ред (Я -> А)", + "alph_desc": "По азбучен ред (А -> Я)", + "title": "Разглеждане" + }, + "store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!" + }, + "StoreSelect": { + "custom_store": { + "label": "Персонализиран магазин", + "url_label": "Адрес" + }, + "store_channel": { + "custom": "Персонализиран", + "default": "По подразбиране", + "label": "Канал за магазина", + "testing": "Тестване" + } + }, + "Updater": { + "decky_updates": "Обновления на Decky", + "patch_notes_desc": "Бележки за промените", + "updates": { + "check_button": "Проверка за обновления", + "checking": "Проверяване", + "cur_version": "Текуща версия: {{ver}}", + "label": "Обновления", + "lat_version": "Използвате най-новата версия: {{ver}}", + "reloading": "Презареждане", + "updating": "Обновяване", + "install_button": "Инсталиране на обновлението" + }, + "no_patch_notes_desc": "няма бележки за промените в тази версия" + }, + "PluginView": { + "hidden_one": "1 добавка е скрита от този списък", + "hidden_other": "{{count}} добавки са скрити от този списък" + } +} diff --git a/backend/locales/cs-CZ.json b/backend/locales/cs-CZ.json new file mode 100644 index 00000000..74b7230c --- /dev/null +++ b/backend/locales/cs-CZ.json @@ -0,0 +1,267 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "Aktualizační kanál", + "prerelease": "Předběžná vydání", + "stable": "Stabilní", + "testing": "Testování" + } + }, + "Developer": { + "disabling": "Vypínám React DevTools", + "enabling": "Zapínám React DevTools", + "5secreload": "Znovu načtení za 5 vteřin" + }, + "FilePickerIndex": { + "folder": { + "select": "Použít tuto složku", + "label": "Složka", + "show_more": "Zobrazit více souborů" + }, + "filter": { + "created_asce": "Vytvořeno (Nejstarší)", + "created_desc": "Vytvořeno (Nejnovější)", + "modified_asce": "Upraveno (Nejstarší)", + "modified_desc": "Upraveno (Nejnovější)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Velikost (Nejmenší)", + "size_desc": "Velikost (Největší)" + }, + "files": { + "show_hidden": "Zobrazit skryté soubory", + "all_files": "Všechny soubory", + "file_type": "Typ souboru" + }, + "file": { + "select": "Vybrat tento soubor" + } + }, + "PluginView": { + "hidden_one": "1 plugin je v tomto seznamu skrytý", + "hidden_few": "{{count}} pluginů je v tomto seznamu skryto", + "hidden_other": "{{count}} pluginů je v tomto seznamu skryto" + }, + "PluginListLabel": { + "hidden": "Skryto z nabídky rychlého přístupu" + }, + "PluginCard": { + "plugin_full_access": "Tento plugin má plný přístup k vašemu Steam Decku.", + "plugin_install": "Instalovat", + "plugin_no_desc": "Nebyl uveden žádný popis.", + "plugin_version_label": "Verze pluginu" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalovat", + "button_processing": "Instalování", + "title": "Instalovat {{artifact}}", + "desc": "Jste si jisti, že chcete nainstalovat {{artifact}} {{version}}?" + }, + "no_hash": "Tento plugin nemá hash, instalujete jej na vlastní nebezpečí.", + "reinstall": { + "button_idle": "Přeinstalovat", + "button_processing": "Přeinstalování", + "title": "Přeinstalovat {{artifact}}", + "desc": "Jste si jisti, že chcete přeinstalovat {{artifact}} {{version}}?" + }, + "update": { + "button_idle": "Aktualizovat", + "button_processing": "Aktualizování", + "desc": "Jste si jisti, že chcete aktualizovat {{artifact}} {{version}}?", + "title": "Aktualizovat {{artifact}}" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Upravit {{count}} plugin", + "mixed_few": "Upravit {{count}} pluginů", + "mixed_other": "Upravit {{count}} pluginů", + "reinstall_one": "Přeinstalovat 1 plugin", + "reinstall_few": "Přeinstalovat {{count}} pluginů", + "reinstall_other": "Přeinstalovat {{count}} pluginů", + "install_one": "Instalovat 1 plugin", + "install_few": "Instalovat {{count}} pluginů", + "install_other": "Instalovat {{count}} pluginů", + "update_one": "Aktualizovat 1 plugin", + "update_few": "Aktualizovat {{count}} pluginů", + "update_other": "Aktualizovat {{count}} pluginů" + }, + "ok_button": { + "idle": "Potvrdit", + "loading": "Probíhá" + }, + "description": { + "install": "Instalovat {{name}} {{version}}", + "update": "Aktualizovat {{name}} na {{version}}", + "reinstall": "Přeinstalovat {{name}} {{version}}" + }, + "confirm": "Jste si jisti, že chcete udělat následující úpravy?" + }, + "PluginListIndex": { + "no_plugin": "Nejsou nainstalovány žádné pluginy!", + "plugin_actions": "Akce pluginu", + "reinstall": "Přeinstalovat", + "reload": "Znovu načíst", + "uninstall": "Odinstalovat", + "update_to": "Aktualizovat na {{name}}", + "show": "Rychlý přístup: Zobrazit", + "hide": "Rychlý přístup: Skrýt", + "update_all_one": "Aktualizovat 1 plugin", + "update_all_few": "Aktualizovat {{count}} pluginů", + "update_all_other": "Aktualizovat {{count}} pluginů" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Aktualizace na {{tag_name}} dostupná!", + "error": "Chyba", + "plugin_load_error": { + "message": "Chyba při načítání pluginu {{name}}", + "toast": "Chyba při načítání {{name}}" + }, + "plugin_uninstall": { + "button": "Odinstalovat", + "desc": "Opravdu chcete odinstalovat {{name}}?", + "title": "Odinstalovat {{name}}" + }, + "plugin_update_one": "Je dostupná aktualizace pro 1 plugin!", + "plugin_update_few": "Jsou dostupné aktualizace pro {{count}} pluginů!", + "plugin_update_other": "Jsou dostupné aktualizace pro {{count}} pluginů!", + "plugin_error_uninstall": "Načítání {{name}} způsobilo chybu uvedenou výše. To obvykle znamená, že plugin vyžaduje aktualizaci SteamUI. Zkontrolujte, zda je aktualizace k dispozici, nebo zvažte odstranění pluginu v nastavení Decky v sekci Pluginy." + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Otevřít konzoli", + "label": "CEF konzole", + "desc": "Otevře CEF konzoli. Užitečné pouze pro účely ladění. Věci zde jsou potenciálně nebezpečné a měly by být používány pouze v případě, že jste vývojář pluginů, nebo vás sem nějaký nasměroval." + }, + "header": "Ostatní", + "react_devtools": { + "desc": "Umožňuje připojení k počítači, na kterém běží React DevTools. Změnou tohoto nastavení se znovu načte Steam. Před povolením nastavte IP adresu.", + "ip_label": "IP adresa", + "label": "Zapnout React DevTools" + }, + "third_party_plugins": { + "button_install": "Instalovat", + "button_zip": "Procházet", + "header": "Pluginy třetí strany", + "label_desc": "URL", + "label_url": "Instalovat plugin z URL", + "label_zip": "Instalovat plugin ze ZIP souboru" + }, + "valve_internal": { + "desc1": "Zapíná interní vývojářské menu Valve.", + "desc2": "Nedotýkejte se ničeho v této nabídce, pokud nevíte, co děláte.", + "label": "Zapnout Valve Internal" + } + }, + "RemoteDebugging": { + "remote_cef": { + "label": "Povolit vzdálené CEF ladění", + "desc": "Umožní neověřený přístup k CEF ladění komukoli ve vaší síti" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky verze", + "header": "O Decky" + }, + "beta": { + "header": "Účast v betě" + }, + "developer_mode": { + "label": "Vývojářský režim" + }, + "other": { + "header": "Ostatní" + }, + "updates": { + "header": "Aktualizace" + }, + "notifications": { + "decky_updates_label": "Dostupná aktualizace Decky", + "header": "Notifikace", + "plugin_updates_label": "Dostupná aktualizace pluginu" + } + }, + "SettingsIndex": { + "developer_title": "Vývojář", + "general_title": "Obecné", + "plugins_title": "Pluginy" + }, + "Store": { + "store_contrib": { + "label": "Přispívání", + "desc": "Pokud byste chtěli přispět do obchodu Decky Plugin Store, podívejte se na repozitář SteamDeckHomebrew/decky-plugin-template na GitHubu. Informace o vývoji a distribuci jsou k dispozici v README." + }, + "store_filter": { + "label": "Filtr", + "label_def": "Vše" + }, + "store_search": { + "label": "Hledat" + }, + "store_sort": { + "label": "Seřadit", + "label_def": "Naposledy aktualizováno (Nejnovější)" + }, + "store_source": { + "desc": "Veškerý zdrojový kód pluginu je dostupný v repozitáři SteamDeckHomebrew/decky-plugin-database na GitHubu.", + "label": "Zdrojový kód" + }, + "store_tabs": { + "about": "O Decky Plugin Store", + "alph_asce": "Abecedně (Z do A)", + "alph_desc": "Abecedně (A do Z)", + "title": "Procházet" + }, + "store_testing_cta": "Zvažte prosím testování nových pluginů, pomůžete tím týmu Decky Loader!", + "store_testing_warning": { + "desc": "Tento kanál obchodu můžete použít k testování nejnovějších verzí pluginů. Nezapomeňte zanechat zpětnou vazbu na GitHubu, aby bylo možné plugin aktualizovat pro všechny uživatele.", + "label": "Vítejte na testovacím kanálu obchodu" + } + }, + "StoreSelect": { + "custom_store": { + "label": "Vlastní obchod", + "url_label": "URL" + }, + "store_channel": { + "custom": "Vlastní", + "default": "Výchozí", + "label": "Kanál obchodu", + "testing": "Testování" + } + }, + "Updater": { + "updates": { + "lat_version": "Aktuální: běží na verzi {{ver}}", + "reloading": "Znovu načítání", + "updating": "Aktualizování", + "check_button": "Zkontrolovat aktualizace", + "checking": "Kontrolování", + "cur_version": "Aktuální verze: {{ver}}", + "install_button": "Instalovat aktualizaci", + "label": "Aktualizace" + }, + "decky_updates": "Aktualizace Decky", + "patch_notes_desc": "Poznámky k verzi", + "no_patch_notes_desc": "žádné poznámky pro tuto verzi" + }, + "DropdownMultiselect": { + "button": { + "back": "Zpět" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "Zadaná cesta není platná. Zkontrolujte ji a zadejte znovu správně.", + "unknown": "Nastala neznámá chyba. Nezpracovaná chyba je: {{raw_error}}", + "perm_denied": "Nemáte přístup k zadanému adresáři. Zkontrolujte, zda jako uživatel (deck na Steam Decku) máte odpovídající oprávnění pro přístup k dané složce/souboru." + } + }, + "TitleView": { + "settings_desc": "Otevřít nastavení Decky", + "decky_store_desc": "Otevřít obchod Decky" + } +} diff --git a/backend/locales/de-DE.json b/backend/locales/de-DE.json new file mode 100644 index 00000000..4ded8703 --- /dev/null +++ b/backend/locales/de-DE.json @@ -0,0 +1,195 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "Updatekanal", + "prerelease": "Vorabveröffentlichung", + "stable": "Standard", + "testing": "Test" + } + }, + "Developer": { + "disabling": "Deaktiviere", + "enabling": "Aktiviere", + "5secreload": "Neu laden in 5 Sekunden" + }, + "FilePickerIndex": { + "folder": { + "select": "Diesen Ordner verwenden" + } + }, + "PluginCard": { + "plugin_install": "Installieren", + "plugin_no_desc": "Keine Beschreibung angegeben.", + "plugin_version_label": "Erweiterungs Version", + "plugin_full_access": "Diese Erweiterung hat uneingeschränkten Zugriff auf dein Steam Deck." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Installieren", + "button_processing": "Wird installiert", + "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} installieren willst?", + "title": "Installiere {{artifact}}" + }, + "reinstall": { + "button_idle": "Neu installieren", + "button_processing": "Wird neu installiert", + "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} neu installieren willst?", + "title": "Neu installation {{artifact}}" + }, + "update": { + "button_idle": "Aktualisieren", + "button_processing": "Wird aktualisiert", + "title": "Aktualisiere {{artifact}}", + "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} aktualisieren willst?" + }, + "no_hash": "Diese Erweiterung besitzt keine Prüfsumme, Installation auf eigene Gefahr." + }, + "PluginListIndex": { + "no_plugin": "Keine Erweiterungen installiert!", + "plugin_actions": "Erweiterungs Aktionen", + "reinstall": "Neu installieren", + "reload": "Neu laden", + "uninstall": "Deinstallieren", + "update_to": "Aktualisieren zu {{name}}", + "update_all_one": "", + "update_all_other": "" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Eine neue Version ({{tag_name}}) ist verfügbar!", + "error": "Fehler", + "plugin_load_error": { + "toast": "Fehler beim Laden von {{name}}", + "message": "Fehler beim Laden von {{name}}" + }, + "plugin_uninstall": { + "button": "Deinstallieren", + "desc": "Bist du dir sicher, dass du {{name}} deinstallieren willst?", + "title": "Deinstalliere {{name}}" + }, + "plugin_error_uninstall": "Das Laden von {{name}} hat einen Fehler verursacht. Dies bedeutet normalerweise, dass die Erweiterung ein Update für die neue Version von SteamUI benötigt. Prüfe in den Decky-Einstellungen im Bereich Erweiterungen, ob ein Update vorhanden ist.", + "plugin_update_one": "1 Erweiterung kann aktualisiert werden!", + "plugin_update_other": "{{count}} Erweiterungen können aktualisiert werden!" + }, + "RemoteDebugging": { + "remote_cef": { + "label": "Remote CEF Debugging Zugriff", + "desc": "Erlaubt jedem aus dem Neztwerk unautorisierten Zugriff auf den CEF Debugger" + } + }, + "SettingsDeveloperIndex": { + "header": "Sonstiges", + "react_devtools": { + "ip_label": "IP", + "label": "Aktiviere React DevTools", + "desc": "Erlaubt die Verbindung mit einem anderen Rechner, auf welchem React DevTools läuft. Eine Änderung startet Steam neu. Die IP Adresse muss vor Aktivierung ausgefüllt sein." + }, + "third_party_plugins": { + "button_zip": "Durchsuchen", + "header": "Erweiterungen von Drittanbietern", + "label_desc": "URL", + "label_zip": "Installiere Erweiterung via ZIP Datei", + "button_install": "Installieren", + "label_url": "Installiere Erweiterung via URL" + }, + "valve_internal": { + "desc2": "Fasse in diesem Menü nichts an, es sei denn, du weißt was du tust.", + "label": "Aktiviere Valve-internes Menü", + "desc1": "Aktiviert das Valve-interne Entwickler Menü." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky Version", + "header": "Über" + }, + "beta": { + "header": "Beta Teilnahme" + }, + "developer_mode": { + "label": "Entwickleroptionen" + }, + "other": { + "header": "Sonstiges" + }, + "updates": { + "header": "Aktualisierungen" + } + }, + "SettingsIndex": { + "developer_title": "Entwickler", + "general_title": "Allgemein", + "plugins_title": "Erweiterungen" + }, + "Store": { + "store_contrib": { + "label": "Mitwirken", + "desc": "Wenn du Erweiterungen im Decky Store veröffentlichen willst, besuche die SteamDeckHomebrew/decky-plugin-template Repository auf GitHub. Informationen rund um Entwicklung und Veröffentlichung findest du in der README." + }, + "store_filter": { + "label": "Filter", + "label_def": "Alle" + }, + "store_search": { + "label": "Suche" + }, + "store_sort": { + "label": "Sortierung", + "label_def": "Zuletzt aktualisiert" + }, + "store_source": { + "desc": "Jeder Erweiterungs Quellcode ist in der SteamDeckHomebrew/decky-plugin-database Repository auf GitHub verfügbar.", + "label": "Quellcode" + }, + "store_tabs": { + "about": "Über", + "alph_asce": "Alphabetisch (Z zu A)", + "alph_desc": "Alphabetisch (A zu Z)", + "title": "Durchstöbern" + }, + "store_testing_cta": "Unterstütze das Decky Loader Team mit dem Testen von neuen Erweiterungen!" + }, + "StoreSelect": { + "custom_store": { + "label": "Benutzerdefinierter Marktplatz", + "url_label": "URL" + }, + "store_channel": { + "custom": "Benutzerdefiniert", + "default": "Standard", + "label": "Marktplatz Kanal", + "testing": "Test" + } + }, + "Updater": { + "decky_updates": "Decky Aktualisierungen", + "patch_notes_desc": "Patchnotizen", + "updates": { + "check_button": "Auf Aktualisierungen prüfen", + "checking": "Wird überprüft", + "cur_version": "Aktualle Version: {{ver}}", + "install_button": "Aktualisierung installieren", + "label": "Aktualisierungen", + "lat_version": "{{ver}} ist die aktuellste", + "reloading": "Lade neu", + "updating": "Aktualisiere" + }, + "no_patch_notes_desc": "Für diese Version gibt es keine Patchnotizen" + }, + "PluginView": { + "hidden_one": "", + "hidden_other": "" + }, + "MultiplePluginsInstallModal": { + "title": { + "install_one": "", + "install_other": "", + "mixed_one": "", + "mixed_other": "", + "update_one": "", + "update_other": "", + "reinstall_one": "", + "reinstall_other": "" + } + } +} diff --git a/backend/locales/el-GR.json b/backend/locales/el-GR.json new file mode 100644 index 00000000..62562935 --- /dev/null +++ b/backend/locales/el-GR.json @@ -0,0 +1,260 @@ +{ + "SettingsDeveloperIndex": { + "react_devtools": { + "desc": "Επιτρέπει την σύνδεση με υπολογιστή που τρέχει React DevTools. Η αλλαγή αυτής της ρύθμισης θα προκαλέσει επαναφόρτωση του Steam. Ωρίστε την διεύθυνση IP πριν την ενεργοποιήσετε.", + "ip_label": "IP", + "label": "Ενεργοποίηση React DevTools" + }, + "third_party_plugins": { + "button_install": "Εγκατάσταση", + "button_zip": "Περιήγηση", + "header": "Επεκτάσεις τρίτων", + "label_desc": "URL", + "label_url": "Εγκατάσταση επέκτασης απο URL", + "label_zip": "Εγκατάσταση επέκτασης από αρχείο ZIP" + }, + "valve_internal": { + "desc1": "Ενεργοποιεί το μενού προγραμματιστή της Valve.", + "desc2": "Μην αγγίξετε τίποτα σε αυτό το μενού εκτός και αν ξέρετε τι κάνει.", + "label": "Ενεργοποιήση εσωτερικού μενού Valve" + }, + "cef_console": { + "button": "Άνοιγμα Κονσόλας", + "desc": "Ανοίγει την Κονσόλα CEF. Χρήσιμο μόνο για εντοπισμό σφαλμάτων. Τα πράγματα εδώ είναι δυνητικά επικίνδυνα και θα πρέπει να χρησιμοποιηθεί μόνο εάν είστε προγραμματιστής επεκτάσεων, ή κατευθυνθήκατε εδώ από έναν προγραμματιστή.", + "label": "Κονσόλα CEF" + }, + "header": "Άλλα" + }, + "BranchSelect": { + "update_channel": { + "prerelease": "Προ-κυκλοφορία", + "stable": "Σταθερό", + "label": "Κανάλι ενημερώσεων", + "testing": "Δοκιμαστικό" + } + }, + "Developer": { + "5secreload": "Γίνεται επαναφόρτωση σε 5 δευτερόλεπτα", + "disabling": "Γίνεται απενεργοποίηση των React DevTools", + "enabling": "Γίνεται ενεργοποίηση των React DevTools" + }, + "PluginCard": { + "plugin_no_desc": "Δεν υπάρχει περιγραφή.", + "plugin_full_access": "Αυτή η επέκταση έχει πλήρη πρόσβαση στο Steam Deck σας.", + "plugin_install": "Εγκατάσταση", + "plugin_version_label": "Έκδοση επέκτασης" + }, + "PluginInstallModal": { + "install": { + "desc": "Σίγουρα θέλετε να εγκαταστήσετε το {{artifact}}{{version}};", + "button_idle": "Εγκατάσταση", + "button_processing": "Γίνεται εγκατάσταση", + "title": "Εγκατάσταση {{artifact}}" + }, + "no_hash": "Αυτή η επέκταση δεν έχει υπογραφή, την εγκαθηστάτε με δικό σας ρίσκο.", + "reinstall": { + "button_idle": "Επανεγκατάσταση", + "button_processing": "Γίνεται επανεγκατάσταση", + "desc": "Σίγουρα θέλετε να επανεγκαταστήσετε το {{artifact}}{{version}};", + "title": "Επανεγκατάσταση {{artifact}}" + }, + "update": { + "button_idle": "Ενημέρωση", + "desc": "Σίγουρα θέλετε να ενημερώσετε το {{artifact}} {{version}};", + "title": "Ενημέρωση {{artifact}}", + "button_processing": "Γίνεται ενημέρωση" + } + }, + "PluginListIndex": { + "no_plugin": "Δεν υπάρχουν εγκατεστημένες επεκτάσεις!", + "plugin_actions": "Ενέργειες επεκτάσεων", + "reinstall": "Επανεγκατάσταση", + "reload": "Επαναφόρτωση", + "uninstall": "Απεγκατάσταση", + "update_to": "Ενημέρωση σε {{name}}", + "update_all_one": "Ενημέρωση 1 επέκτασης", + "update_all_other": "Ενημέρωση {{count}} επεκτάσεων", + "show": "Γρήγορη πρόσβαση: Εμφάνιση", + "hide": "Γρήγορη πρόσβαση: Απόκρυψη" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Ενημέρωση σε {{tag_name}} διαθέσιμη!", + "error": "Σφάλμα", + "plugin_error_uninstall": "Η φόρτωση του {{name}} προκάλεσε το παραπάνω σφάλμα. Αυτό συνήθως σημαίνει ότι η επέκταση απαιτεί ενημέρωση για τη νέα έκδοση του SteamUI. Ελέγξτε εάν υπάρχει ενημέρωση ή αξιολογήστε την απεγκαταστήσετε της επέκτασης στις ρυθμίσεις του Decky, στην ενότητα Επεκτάσεις.", + "plugin_load_error": { + "message": "Σφάλμα στη φόρτωση της επέκτασης {{name}}", + "toast": "Σφάλμα φόρτωσης {{name}}" + }, + "plugin_uninstall": { + "button": "Απεγκατάσταση", + "desc": "Σίγουρα θέλετε να απεγκαταστήσετε το {{name}};", + "title": "Απεγκατάσταση {{name}}" + }, + "plugin_update_one": "Διαθέσιμη ενημέρωση για 1 επέκταση!", + "plugin_update_other": "Διαθέσιμες ενημερώσεις για {{count}} επεκτάσεις!" + }, + "RemoteDebugging": { + "remote_cef": { + "label": "Να επιτρέπεται η απομακρυσμένη πρόσβαση στον CEF debugger", + "desc": "Να επιτρέπεται η ανεξέλεγκτη πρόσβαση στον CEF debugger σε οποιονδήποτε στο τοπικό δίκτυο" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Έκδοση Decky", + "header": "Σχετικά" + }, + "developer_mode": { + "label": "Λειτουργία προγραμματιστή" + }, + "other": { + "header": "Άλλα" + }, + "updates": { + "header": "Ενημερώσεις" + }, + "beta": { + "header": "Συμμετοχή στη Beta" + }, + "notifications": { + "decky_updates_label": "Διαθέσιμη ενημέρωση του Decky", + "header": "Ειδοποιήσεις", + "plugin_updates_label": "Διαθέσιμες ενημερώσεις επεκτάσεων" + } + }, + "SettingsIndex": { + "plugins_title": "Επεκτάσεις", + "developer_title": "Προγραμματιστής", + "general_title": "Γενικά" + }, + "Store": { + "store_contrib": { + "label": "Συνεισφέροντας", + "desc": "Αν θέλετε να συνεισφέρετε στο κατάστημα επεκτάσεων του Decky, τσεκάρετε το SteamDeckHomebrew/decky-plugin-template repository στο GitHub. Πληροφορίες σχετικά με τη δημιουργία και τη διανομή επεκτάσεων είναι διαθέσιμες στο README." + }, + "store_filter": { + "label": "Φίλτρο", + "label_def": "Όλα" + }, + "store_search": { + "label": "Αναζήτηση" + }, + "store_sort": { + "label": "Ταξινόμηση", + "label_def": "Τελευταία ενημέρωση (Νεότερα)" + }, + "store_source": { + "desc": "Ο πηγαίος κώδικας όλων των επεκτάσεων είναι διαθέσιμος στο SteamDeckHomebrew/decky-plugin-database repository στο GitHub.", + "label": "Πηγαίος κώδικας" + }, + "store_tabs": { + "about": "Σχετικά", + "alph_asce": "Αλφαβητικά (Ζ σε Α)", + "alph_desc": "Αλφαβητικά (Α σε Ζ)", + "title": "Περιήγηση" + }, + "store_testing_cta": "Παρακαλώ σκεφτείτε να τεστάρετε νέες επεκτάσεις για να βοηθήσετε την ομάδα του Decky Loader!", + "store_testing_warning": { + "desc": "Μπορείτε να χρησιμοποιήσετε αυτό το κανάλι του καταστήματος για να δοκιμάσετε τις νεότερες εκδόσεις των επεκτάσεων. Φροντίστε να αφήσετε σχόλια στο GitHub, ώστε να βοηθήσετε στην ενημέρωση της εκάστοτε επέκταση για όλους τους χρήστες.", + "label": "Καλώς ήρθατε στο Δοκιμαστικό Κανάλι τους Καταστήματος" + } + }, + "StoreSelect": { + "custom_store": { + "label": "Προσαρμοσμένο κατάστημα", + "url_label": "URL" + }, + "store_channel": { + "custom": "Προσαρμοσμένο", + "default": "Προεπιλεγμένο", + "label": "Κανάλι καταστήματος", + "testing": "Δοκιμαστικό" + } + }, + "Updater": { + "no_patch_notes_desc": "Κανένα ενημερωτικό σημείωμα για αυτή την έκδοση", + "patch_notes_desc": "Σημειώσεις ενημέρωσης", + "updates": { + "check_button": "Έλεγχος για ενημερώσεις", + "checking": "Γίνεται έλεγχος", + "cur_version": "Τρέχουσα έκδοση: {{ver}}", + "install_button": "Εγκατάσταση ενημέρωσης", + "label": "Ενημερώσεις", + "updating": "Γίνεται ενημέρωση", + "lat_version": "Ενημερωμένο: τρέχουσα έκδοση {{ver}}", + "reloading": "Γίνεται επαναφόρτωση" + }, + "decky_updates": "Ενημερώσεις Decky" + }, + "FilePickerIndex": { + "folder": { + "select": "Χρησιμοποιήστε αυτό το φάκελο", + "label": "Φάκελος", + "show_more": "Εμφάνιση περισσότερων αρχείων" + }, + "filter": { + "modified_asce": "Τροποποιήθηκε (Παλαιότερο)", + "modified_desc": "Τροποποιήθηκε (Νεότερο)", + "created_desc": "Δημιουργήθηκε (Νεότερο)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "created_asce": "Δημιουργήθηκε (Παλαιότερο)", + "size_asce": "Μέγεθος (Μικρότερο)", + "size_desc": "Μέγεθος (Μεγαλύτερο)" + }, + "file": { + "select": "Επιλογή αυτού του αρχείου" + }, + "files": { + "show_hidden": "Εμφάνιση Κρυφών Αρχείων", + "all_files": "Όλα Τα Αρχεία", + "file_type": "Τύπος Αρχείου" + } + }, + "PluginView": { + "hidden_one": "1 επέκταση είναι κρυμμένη σε αυτήν τη λίστα", + "hidden_other": "{{count}} επεκτάσεις είναι κρυμμένες σε αυτήν τη λίστα" + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Τροποποίηση 1 επέκτασης", + "mixed_other": "Τροποποίηση {{count}} επεκτάσεων", + "update_one": "Ενημέρωση 1 επέκτασης", + "update_other": "Ενημέρωση {{count}} επεκτάσεων", + "reinstall_one": "Επανεγκατάσταση 1 επέκτασης", + "reinstall_other": "Επανεγκατάσταση {{count}} επεκτάσεων", + "install_one": "Εγκατάσταση 1 επέκτασης", + "install_other": "Εγκατάσταση {{count}} επεκτάσεων" + }, + "confirm": "Είστε βέβαιοι ότι θέλετε να κάνετε τις ακόλουθες τροποποιήσεις;", + "description": { + "reinstall": "Επανεγκατάσταση {{name}} {{version}}", + "update": "Ενημέρωση {{name}} to {{version}}", + "install": "Εγκατάσταση {{name}} {{version}}" + }, + "ok_button": { + "idle": "Επιβεβαίωση", + "loading": "Φόρτωση" + } + }, + "PluginListLabel": { + "hidden": "Κρυφό στο μενού γρήγορης πρόσβασης" + }, + "TitleView": { + "settings_desc": "Άνοιγμα Ρυθμίσεων Decky", + "decky_store_desc": "Άνοιγμα Καταστήματος Decky" + }, + "DropdownMultiselect": { + "button": { + "back": "Πίσω" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "Η καθορισμένη διαδρομή δεν είναι έγκυρη. Παρακαλούμε ελέγξτε τη και εισάγετέ τη ξανά σωστά.", + "perm_denied": "Δεν έχετε πρόσβαση στην καθορισμένη διαδρομή. Ελέγξτε εάν ο χρήστης σας (deck στο Steam Deck) έχει τα αντίστοιχα δικαιώματα πρόσβασης στον καθορισμένο φάκελο/αρχείο.", + "unknown": "Παρουσιάστηκε άγνωστο σφάλμα. Το σφάλμα είναι: {{raw_error}}" + } + } +} diff --git a/backend/locales/en-US.json b/backend/locales/en-US.json new file mode 100644 index 00000000..7845ae4f --- /dev/null +++ b/backend/locales/en-US.json @@ -0,0 +1,260 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "Update Channel", + "prerelease": "Prerelease", + "stable": "Stable", + "testing": "Testing" + } + }, + "Developer": { + "5secreload": "Reloading in 5 seconds", + "disabling": "Disabling React DevTools", + "enabling": "Enabling React DevTools" + }, + "DropdownMultiselect": { + "button": { + "back": "Back" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "The path specified is not valid. Please check it and reenter it correctly.", + "perm_denied": "You do not have access to the specified directory. Please check if your user (deck on Steam Deck) has the corresponding permission to access the specified folder/file.", + "unknown": "An unknown error occurred. The raw error is: {{raw_error}}" + } + }, + "FilePickerIndex": { + "file": { + "select": "Select this file" + }, + "files": { + "all_files": "All Files", + "file_type": "File Type", + "show_hidden": "Show Hidden Files" + }, + "filter": { + "created_asce": "Created (Oldest)", + "created_desc": "Created (Newest)", + "modified_asce": "Modified (Oldest)", + "modified_desc": "Modified (Newest)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Size (Smallest)", + "size_desc": "Size (Largest)" + }, + "folder": { + "label": "Folder", + "select": "Use this folder", + "show_more": "Show more files" + } + }, + "MultiplePluginsInstallModal": { + "confirm": "Are you sure you want to make the following modifications?", + "description": { + "install": "Install {{name}} {{version}}", + "reinstall": "Reinstall {{name}} {{version}}", + "update": "Update {{name}} to {{version}}" + }, + "ok_button": { + "idle": "Confirm", + "loading": "Working" + }, + "title": { + "install_one": "Install 1 plugin", + "install_other": "Install {{count}} plugins", + "mixed_one": "Modify {{count}} plugin", + "mixed_other": "Modify {{count}} plugins", + "reinstall_one": "Reinstall 1 plugin", + "reinstall_other": "Reinstall {{count}} plugins", + "update_one": "Update 1 plugin", + "update_other": "Update {{count}} plugins" + } + }, + "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 {{artifact}}" + } + }, + "PluginListIndex": { + "hide": "Quick access: Hide", + "no_plugin": "No plugins installed!", + "plugin_actions": "Plugin Actions", + "reinstall": "Reinstall", + "reload": "Reload", + "show": "Quick access: Show", + "uninstall": "Uninstall", + "update_all_one": "Update 1 plugin", + "update_all_other": "Update {{count}} plugins", + "update_to": "Update to {{name}}" + }, + "PluginListLabel": { + "hidden": "Hidden from the quick access menu" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Update to {{tag_name}} available!", + "error": "Error", + "plugin_error_uninstall": "Loading {{name}} caused an exception as shown above. This usually means that the plugin requires an update for the new version of SteamUI. Check if an update is present or evaluate its removal in the Decky settings, in the Plugins section.", + "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!" + }, + "PluginView": { + "hidden_one": "1 plugin is hidden from this list", + "hidden_other": "{{count}} plugins are hidden from this list" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Allow unauthenticated access to the CEF debugger to anyone in your network", + "label": "Allow Remote CEF Debugging" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Open Console", + "desc": "Opens the CEF Console. Only useful for debugging purposes. Stuff here is potentially dangerous and should only be used if you are a plugin dev, or are directed here by one.", + "label": "CEF Console" + }, + "header": "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" + }, + "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": { + "label": "Developer mode" + }, + "notifications": { + "decky_updates_label": "Decky update available", + "header": "Notifications", + "plugin_updates_label": "Plugin updates available" + }, + "other": { + "header": "Other" + }, + "updates": { + "header": "Updates" + } + }, + "SettingsIndex": { + "developer_title": "Developer", + "general_title": "General", + "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!", + "store_testing_warning": { + "desc": "You can use this store channel to test bleeding-edge plugin versions. Be sure to leave feedback on GitHub so the plugin can be updated for all users.", + "label": "Welcome to the Testing Store Channel" + } + }, + "StoreSelect": { + "custom_store": { + "label": "Custom Store", + "url_label": "URL" + }, + "store_channel": { + "custom": "Custom", + "default": "Default", + "label": "Store Channel", + "testing": "Testing" + } + }, + "TitleView": { + "decky_store_desc": "Open Decky Store", + "settings_desc": "Open Decky Settings" + }, + "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" + } + } +} diff --git a/backend/locales/es-ES.json b/backend/locales/es-ES.json new file mode 100644 index 00000000..6c47eb06 --- /dev/null +++ b/backend/locales/es-ES.json @@ -0,0 +1,217 @@ +{ + "SettingsDeveloperIndex": { + "third_party_plugins": { + "button_install": "Instalar", + "button_zip": "Navegar", + "label_desc": "URL", + "label_url": "Instalar plugin desde URL", + "label_zip": "Instalar plugin desde archivo ZIP", + "header": "Plugins de terceros" + }, + "valve_internal": { + "desc2": "No toques nada en este menú a menos que sepas lo que haces.", + "label": "Activar menú interno de Valve", + "desc1": "Activa el menú interno de desarrollo de Valve." + }, + "cef_console": { + "button": "Abrir consola", + "label": "Consola CEF", + "desc": "Abre la consola del CEF. Solamente es útil para propósitos de depuración. Las cosas que hagas aquí pueden ser potencialmente peligrosas y solo se debería usar si eres un desarrollador de plugins, o uno te ha dirigido aquí." + }, + "react_devtools": { + "ip_label": "IP", + "label": "Activar DevTools de React", + "desc": "Permite la conexión a un ordenador ejecutando las DevTools de React. Cambiar este ajuste recargará Steam. Configura la dirección IP antes de activarlo." + }, + "header": "Otros" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalar", + "button_processing": "Instalando", + "title": "Instalar {{artifact}}", + "desc": "¿Estás seguro de que quieres instalar {{artifact}} {{version}}?" + }, + "reinstall": { + "button_idle": "Reinstalar", + "button_processing": "Reinstalando", + "desc": "¿Estás seguro de que quieres reinstalar {{artifact}} {{version}}?", + "title": "Reinstalar {{artifact}}" + }, + "update": { + "button_processing": "Actualizando", + "button_idle": "Actualizar", + "desc": "¿Estás seguro de que quieres actualizar {{artifact}} {{version}}?", + "title": "Actualizar {{artifact}}" + }, + "no_hash": "Este plugin no tiene un hash, lo estás instalando bajo tu propia responsabilidad." + }, + "Developer": { + "disabling": "Desactivando DevTools de React", + "enabling": "Activando DevTools de React", + "5secreload": "Recargando en 5 segundos" + }, + "BranchSelect": { + "update_channel": { + "prerelease": "Prelanzamiento", + "stable": "Estable", + "label": "Canal de actualización", + "testing": "Pruebas" + } + }, + "PluginCard": { + "plugin_full_access": "Este plugin tiene acceso completo a su Steam Deck.", + "plugin_install": "Instalar", + "plugin_version_label": "Versión de Plugin", + "plugin_no_desc": "No se proporcionó una descripción." + }, + "FilePickerIndex": { + "folder": { + "select": "Usar esta carpeta" + } + }, + "PluginListIndex": { + "uninstall": "Desinstalar", + "reinstall": "Reinstalar", + "reload": "Recargar", + "plugin_actions": "Acciones de plugin", + "no_plugin": "¡No hay plugins instalados!", + "update_all_one": "Actualizar 1 plugin", + "update_all_many": "Actualizar {{count}} plugins", + "update_all_other": "Actualizar {{count}} plugins", + "update_to": "Actualizar a {{name}}" + }, + "PluginLoader": { + "error": "Error", + "plugin_uninstall": { + "button": "Desinstalar", + "desc": "¿Estás seguro de que quieres desinstalar {{name}}?", + "title": "Desinstalar {{name}}" + }, + "decky_title": "Decky", + "plugin_update_one": "¡Actualización disponible para 1 plugin!", + "plugin_update_many": "¡Actualizaciones disponibles para {{count}} plugins!", + "plugin_update_other": "¡Actualizaciones disponibles para {{count}} plugins!", + "decky_update_available": "¡Actualización {{tag_name}} disponible!", + "plugin_load_error": { + "message": "Se ha producido un error al cargar el plugin {{name}}", + "toast": "Se ha producido un error al cargar {{name}}" + }, + "plugin_error_uninstall": "Al cargar {{name}} se ha producido una excepción como se muestra arriba. Esto suele significar que el plugin requiere una actualización para la nueva versión de SteamUI. Comprueba si hay una actualización disponible o valora eliminarlo en los ajustes de Decky, en la sección Plugins." + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Permitir acceso no autenticado al CEF debugger a cualquier persona en su red", + "label": "Permitir depuración remota del CEF" + } + }, + "SettingsGeneralIndex": { + "updates": { + "header": "Actualizaciones" + }, + "about": { + "header": "Acerca de", + "decky_version": "Versión de Decky" + }, + "developer_mode": { + "label": "Modo desarrollador" + }, + "beta": { + "header": "Participación en la beta" + }, + "other": { + "header": "Otros" + } + }, + "SettingsIndex": { + "developer_title": "Desarrollador", + "general_title": "General", + "plugins_title": "Plugins" + }, + "Store": { + "store_search": { + "label": "Buscar" + }, + "store_sort": { + "label": "Ordenar", + "label_def": "Actualizado por última vez (Nuevos)" + }, + "store_contrib": { + "desc": "Si desea contribuir a la tienda de plugins de Decky, mira el repositorio SteamDeckHomebrew/decky-plugin-template en GitHub. Hay información acerca del desarrollo y distribución en el archivo README.", + "label": "Contribuyendo" + }, + "store_tabs": { + "about": "Información", + "title": "Navegar", + "alph_asce": "Alfabéticamente (Z-A)", + "alph_desc": "Alfabéticamente (A-Z)" + }, + "store_testing_cta": "¡Por favor considera probar plugins nuevos para ayudar al equipo de Decky Loader!", + "store_source": { + "desc": "El código fuente de los plugins está disponible en el repositiorio SteamDeckHomebrew/decky-plugin-database en GitHub.", + "label": "Código fuente" + }, + "store_filter": { + "label_def": "Todos", + "label": "Filtrar" + } + }, + "Updater": { + "updates": { + "reloading": "Recargando", + "updating": "Actualizando", + "checking": "Buscando", + "check_button": "Buscar actualizaciones", + "install_button": "Instalar actualización", + "label": "Actualizaciones", + "lat_version": "Actualizado: ejecutando {{ver}}", + "cur_version": "Versión actual: {{ver}}" + }, + "decky_updates": "Actualizaciones de Decky", + "no_patch_notes_desc": "No hay notas de parche para esta versión", + "patch_notes_desc": "Notas de parche" + }, + "MultiplePluginsInstallModal": { + "title": { + "reinstall_one": "Reinstalar 1 plugin", + "reinstall_many": "Reinstalar {{count}} plugins", + "reinstall_other": "Reinstalar {{count}} plugins", + "update_one": "Actualizar 1 plugin", + "update_many": "Actualizar {{count}} plugins", + "update_other": "Actualizar {{count}} plugins", + "mixed_one": "Modificar 1 plugin", + "mixed_many": "Modificar {{count}} plugins", + "mixed_other": "Modificar {{count}} plugins", + "install_one": "Instalar 1 plugin", + "install_many": "Instalar {{count}} plugins", + "install_other": "Instalar {{count}} plugins" + }, + "ok_button": { + "idle": "Confirmar", + "loading": "Trabajando" + }, + "confirm": "¿Estás seguro de que quieres hacer las siguientes modificaciones?", + "description": { + "install": "Instalar {{name}} {{version}}", + "update": "Actualizar {{name}} a {{version}}", + "reinstall": "Reinstalar {{name}} {{version}}" + } + }, + "StoreSelect": { + "custom_store": { + "url_label": "URL", + "label": "Tienda personalizada" + }, + "store_channel": { + "custom": "Personalizada", + "default": "Por defecto", + "label": "Canál de la tienda", + "testing": "Pruebas" + } + }, + "PluginView": { + "hidden_one": "", + "hidden_many": "", + "hidden_other": "" + } +} diff --git a/backend/locales/fi-FI.json b/backend/locales/fi-FI.json new file mode 100644 index 00000000..b0ff1309 --- /dev/null +++ b/backend/locales/fi-FI.json @@ -0,0 +1,260 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "Esijulkaisu", + "testing": "Testiversio", + "stable": "Vakaa versio", + "label": "Päivityskanava" + } + }, + "Developer": { + "5secreload": "Uudelleenladataan 5 sekunin kuluttua", + "disabling": "Poistetaan React DevTools käytöstä", + "enabling": "Otetaan React DevTools käyttöön" + }, + "FilePickerError": { + "errors": { + "perm_denied": "Sinulla ei ole käyttöoikeutta määritettyyn hakemistoon. Tarkista, onko käyttäjälläsi (käyttäjä 'deck' Steam Deckillä) vastaavat oikeudet käyttää määritettyä kansiota/tiedostoa.", + "unknown": "Tapahtui tuntematon virhe. Raaka virhe on: {{raw_error}}", + "file_not_found": "Määritetty polku ei kelpaa. Tarkista se ja kirjoita se uudelleen oikein." + } + }, + "FilePickerIndex": { + "file": { + "select": "Valitse tämä tiedosto" + }, + "files": { + "all_files": "Kaikki tiedostot", + "file_type": "Tiedostotyyppi", + "show_hidden": "Näytä piilotetut tiedostot" + }, + "filter": { + "created_desc": "Luotu (uusin ensin)", + "modified_asce": "Muokattu (vanhin)", + "modified_desc": "Muokattu (uusin)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Koko (pienin ensin)", + "size_desc": "Koko (suurin ensin)", + "created_asce": "Luotu (vanhin ensin)" + }, + "folder": { + "label": "Kansio", + "select": "Käytä tätä kansiota", + "show_more": "Näytä lisää tiedostoja" + } + }, + "MultiplePluginsInstallModal": { + "confirm": "Haluatko varmasti tehdä seuraavat muutokset?", + "description": { + "reinstall": "Uudelleenasenna {{name}} {{version}}", + "update": "Päivitä {{name}} versioon {{version}}", + "install": "Asenna {{name}} {{version}}" + }, + "ok_button": { + "idle": "Vahvista", + "loading": "Ladataan" + }, + "title": { + "install_one": "Asenna yksi laajennus", + "install_other": "Asenna {{count}} laajennusta", + "update_one": "Päivitä yksi laajennus", + "update_other": "Päivitä {{count}} laajennusta", + "mixed_one": "Muuta yhtä laajennusta", + "mixed_other": "Muuta {{count}} laajennusta", + "reinstall_one": "Uudelleenasenna yksi laajennus", + "reinstall_other": "Uudelleenasenna {{count}} laajennusta" + } + }, + "PluginCard": { + "plugin_install": "Asenna", + "plugin_no_desc": "Ei kuvausta.", + "plugin_version_label": "Laajennuksen versio", + "plugin_full_access": "Tällä laajennuksella on täysi pääsy Steam Deckkiisi." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Asenna", + "button_processing": "Asennetaan", + "desc": "Haluatko varmasti asentaa {{artifact}} {{version}}?", + "title": "Asenna {{artifact}}" + }, + "no_hash": "Tällä laajennuksella ei ole hashia, asennat sen omalla vastuullasi.", + "reinstall": { + "button_idle": "Uudelleenasenna", + "button_processing": "Uudelleenasennetaan", + "desc": "Haluatko varmasti uudelleenasentaa {{artifact}} {{version}}?", + "title": "Uudelleenasenna {{artifact}}" + }, + "update": { + "button_idle": "Päivitä", + "button_processing": "Päivitetään", + "desc": "Haluatko varmasti päivittää {{artifact}} {{version}}?", + "title": "Päivitä {{artifact}}" + } + }, + "DropdownMultiselect": { + "button": { + "back": "Takaisin" + } + }, + "PluginListIndex": { + "no_plugin": "Ei asennettuja laajennuksia!", + "plugin_actions": "Laajennustoiminnot", + "reinstall": "Uudelleenasenna", + "reload": "Lataa uudelleen", + "uninstall": "Poista asennus", + "update_all_one": "Päivitä yksi laajennus", + "update_all_other": "Päivitä {{count}} laajennusta", + "update_to": "Päivitä versioon {{name}}", + "hide": "Pikavalikko: Piilota", + "show": "Pikavalikko: Näytä" + }, + "PluginListLabel": { + "hidden": "Piilotettu pikavalikosta" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Päivitys versioon {{tag_name}} on saatavilla!", + "error": "Virhe", + "plugin_load_error": { + "message": "Virhe ladattaessa {{name}}-laajennusta", + "toast": "Virhe ladattaessa {{name}}" + }, + "plugin_uninstall": { + "button": "Poista asennus", + "desc": "Haluatko varmasti poistaa {{name}} asennuksen?", + "title": "Poista {{name}}" + }, + "plugin_update_one": "Päivityksiä saatavilla yhdelle laajennukselle!", + "plugin_update_other": "Päivityksiä saatavilla {{count}} laajennukselle!", + "plugin_error_uninstall": "{{name}} lataaminen aiheutti yllä olevan poikkeuksen. Tämä tarkoittaa yleensä sitä, että laajennus vaatii päivityksen uudelle SteamUI-versiolle. Tarkista, onko päivitystä saatavilla, tai harkitse laajennuksen poistoa Decky-asetuksista, laajennukset-osiosta." + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Salli todentamaton pääsy CEF-debuggeriin kenelle tahansa verkossasi", + "label": "Salli CEF-etädebugaus" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Avaa konsoli", + "desc": "Avaa CEF-konsolin. Hyödyllinen vain debugaustarkoituksiin. Täällä olevat jutut ovat mahdollisesti vaarallisia, ja niitä tulisi käyttää vain, jos olet laajennuksen kehittäjä tai jos kehittäjä on ohjannut sinut tänne.", + "label": "CEF-konsoli" + }, + "header": "Muu", + "react_devtools": { + "desc": "Mahdollistaa yhteyden tietokoneeseen, jossa on käytössä React DevTools. Tämän asetuksen muuttaminen lataa Steamin uudelleen. Aseta IP-osoite ennen käyttöönottoa.", + "ip_label": "IP-osoite", + "label": "Ota React DevTools käyttöön" + }, + "third_party_plugins": { + "button_install": "Asenna", + "button_zip": "Selaa", + "header": "Kolmannen osapuolen laajennukset", + "label_desc": "URL-osoite", + "label_zip": "Asenna laajennus ZIP-tiedostosta", + "label_url": "Asenna laajennus URL-osoitteesta" + }, + "valve_internal": { + "desc2": "Älä koske mihinkään tässä valikossa, ellet tiedä mitä se tekee.", + "label": "Ota Valve Internal käyttöön", + "desc1": "Ottaa käyttöön Valven sisäisen kehittäjävalikon." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky-versio", + "header": "Tietoja" + }, + "beta": { + "header": "Beta-osallistuminen" + }, + "developer_mode": { + "label": "Kehittäjätila" + }, + "notifications": { + "decky_updates_label": "Decky-päivitys saatavilla", + "header": "Ilmoitukset", + "plugin_updates_label": "Laajennuspäivityksiä saatavilla" + }, + "other": { + "header": "Muu" + }, + "updates": { + "header": "Päivitykset" + } + }, + "SettingsIndex": { + "developer_title": "Kehittäjä", + "general_title": "Yleinen", + "plugins_title": "Laajennukset" + }, + "Store": { + "store_contrib": { + "label": "Osallistuminen", + "desc": "Mikäli haluat julkaista Decky Plugin Storeen, tarkista GitHubin SteamDeckHomebrew/decky-plugin-template -esimerkkitietovarasto. Tietoa kehityksestä ja jakelusta löytyy README:stä." + }, + "store_filter": { + "label": "Suodin", + "label_def": "Kaikki" + }, + "store_search": { + "label": "Hae" + }, + "store_sort": { + "label": "Järjestä", + "label_def": "Viimeksi päivitetty (uusin ensin)" + }, + "store_source": { + "desc": "Kaikken laajennusten lähdekoodit ovat saatavilla SteamDeckHomebrew/decky-plugin-database -arkistosta GitHubissa.", + "label": "Lähdekoodi" + }, + "store_tabs": { + "about": "Tietoja", + "alph_asce": "Aakkosjärjestyksessä (Z–A)", + "alph_desc": "Aakkosjärjestyksessä (A–Z)", + "title": "Selaa" + }, + "store_testing_cta": "Harkitse uusien lisäosien testaamista auttaaksesi Decky Loader -tiimiä!", + "store_testing_warning": { + "label": "Tervetuloa testausmyymälä-kanavalle", + "desc": "Voit käyttää tätä myymäläkanavaa testataksesi uusimpia laajennusversioita. Muista jättää palautetta GitHubissa, jotta laajennus voidaan päivittää kaikille käyttäjille." + } + }, + "StoreSelect": { + "custom_store": { + "label": "Mukautettu myymälä", + "url_label": "URL-osoite" + }, + "store_channel": { + "custom": "Mukautettu", + "default": "Oletus", + "label": "Myymäläkanava", + "testing": "Testaus" + } + }, + "TitleView": { + "decky_store_desc": "Avaa Decky-myymälä", + "settings_desc": "Avaa Decky-asetukset" + }, + "Updater": { + "decky_updates": "Decky-päivitykset", + "no_patch_notes_desc": "tälle versiolle ei ole korjausmerkintöjä", + "patch_notes_desc": "Korjausmerkinnät", + "updates": { + "check_button": "Tarkista päivitykset", + "checking": "Tarkistetaan", + "cur_version": "Nykyinen versio: {{ver}}", + "install_button": "Asenna päivitys", + "label": "Päivitykset", + "lat_version": "Ajan tasalla: versio {{ver}}", + "reloading": "Uudelleenladataan", + "updating": "Päivitetään" + } + }, + "PluginView": { + "hidden_one": "Yksi laajennus on piilotettu tästä luettelosta", + "hidden_other": "{{count}} laajennusta on piilotettu tästä luettelosta" + } +} diff --git a/backend/locales/fr-FR.json b/backend/locales/fr-FR.json new file mode 100644 index 00000000..f1c305f9 --- /dev/null +++ b/backend/locales/fr-FR.json @@ -0,0 +1,201 @@ +{ + "SettingsDeveloperIndex": { + "react_devtools": { + "desc": "Permet la connexion à un ordinateur exécutant React DevTools. Changer ce paramètre rechargera Steam. Définissez l'adresse IP avant l'activation.", + "ip_label": "IP", + "label": "Activer React DevTools" + }, + "third_party_plugins": { + "button_install": "Installer", + "button_zip": "Parcourir", + "header": "Plugins tiers", + "label_desc": "URL", + "label_url": "Installer le plugin à partir d'un URL", + "label_zip": "Installer le plugin à partir d'un fichier ZIP" + }, + "valve_internal": { + "desc1": "Active le menu développeur interne de Valve.", + "desc2": "Ne touchez à rien dans ce menu à moins que vous ne sachiez ce qu'il fait.", + "label": "Activer Valve Internal" + } + }, + "BranchSelect": { + "update_channel": { + "prerelease": "Avant-première", + "label": "Canal de mise à jour", + "stable": "Stable", + "testing": "Test" + } + }, + "StoreSelect": { + "store_channel": { + "label": "Canal du Plugin Store", + "testing": "Test", + "custom": "Personnalisé", + "default": "Par défaut" + }, + "custom_store": { + "label": "Plugin Store personnalisé", + "url_label": "URL" + } + }, + "Updater": { + "decky_updates": "Mises à jour de Decky", + "no_patch_notes_desc": "pas de notes de mise à jour pour cette version", + "patch_notes_desc": "Notes de mise à jour", + "updates": { + "check_button": "Chercher les mises à jour", + "checking": "Recherche", + "cur_version": "Version actuelle: {{ver}}", + "install_button": "Installer la mise à jour", + "label": "Mises à jour", + "lat_version": "À jour: version {{ver}}", + "reloading": "Rechargement", + "updating": "Mise à jour en cours" + } + }, + "Developer": { + "5secreload": "Rechargement dans 5 secondes", + "disabling": "Désactivation", + "enabling": "Activation" + }, + "FilePickerIndex": { + "folder": { + "select": "Utiliser ce dossier" + } + }, + "PluginCard": { + "plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.", + "plugin_install": "Installer", + "plugin_no_desc": "Aucune description fournie.", + "plugin_version_label": "Version du plugin" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Installer", + "button_processing": "Installation en cours", + "title": "Installer {{artifact}}", + "desc": "Êtes-vous sûr de vouloir installer {{artifact}} {{version}} ?" + }, + "no_hash": "Ce plugin n'a pas de somme de contrôle, vous l'installez à vos risques et périls.", + "reinstall": { + "button_idle": "Réinstaller", + "button_processing": "Réinstallation en cours", + "desc": "Êtes-vous sûr de vouloir réinstaller {{artifact}} {{version}} ?", + "title": "Réinstaller {{artifact}}" + }, + "update": { + "button_idle": "Mettre à jour", + "button_processing": "Mise à jour", + "title": "Mettre à jour {{artifact}}", + "desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} {{version}} ?" + } + }, + "PluginListIndex": { + "plugin_actions": "Plugin Actions", + "reinstall": "Réinstaller", + "reload": "Recharger", + "uninstall": "Désinstaller", + "update_to": "Mettre à jour vers {{name}}", + "no_plugin": "Aucun plugin installé !", + "update_all_one": "", + "update_all_many": "", + "update_all_other": "" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Erreur", + "plugin_error_uninstall": "Allez sur {{name}} dans le menu de Decky si vous voulez désinstaller ce plugin.", + "plugin_load_error": { + "message": "Erreur lors du chargement du plugin {{name}}", + "toast": "Erreur lors du chargement de {{name}}" + }, + "decky_update_available": "Mise à jour vers {{tag_name}} disponible !", + "plugin_uninstall": { + "button": "Désinstaller", + "title": "Désinstaller {{name}}", + "desc": "Êtes-vous sûr.e de vouloir désinstaller {{name}} ?" + }, + "plugin_update_one": "", + "plugin_update_many": "", + "plugin_update_other": "" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Autoriser l'accès non authentifié au débogueur CEF à toute personne de votre réseau", + "label": "Autoriser le débogage CEF à distance" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Version de Decky", + "header": "À propos" + }, + "beta": { + "header": "Participation à la Bêta" + }, + "developer_mode": { + "label": "Mode développeur" + }, + "other": { + "header": "Autre" + }, + "updates": { + "header": "Mises à jour" + } + }, + "SettingsIndex": { + "developer_title": "Développeur", + "general_title": "Général", + "plugins_title": "Plugins" + }, + "Store": { + "store_contrib": { + "desc": "Si vous souhaitez contribuer au Decky Plugin Store, consultez le dépôt SteamDeckHomebrew/decky-plugin-template sur GitHub. Des informations sur le développement et la distribution sont disponibles dans le fichier README.", + "label": "Contributions" + }, + "store_filter": { + "label": "Filtrer", + "label_def": "Tous" + }, + "store_search": { + "label": "Rechercher" + }, + "store_sort": { + "label": "Trier", + "label_def": "Mises à jour (Plus récentes)" + }, + "store_source": { + "desc": "Tout le code source des plugins est disponible sur le dépôt SteamDeckHomebrew/decky-plugin-database sur GitHub.", + "label": "Code Source" + }, + "store_tabs": { + "about": "À propos", + "alph_asce": "Alphabétique (Z à A)", + "alph_desc": "Alphabétique (A à Z)", + "title": "Explorer" + }, + "store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !" + }, + "PluginView": { + "hidden_one": "", + "hidden_many": "", + "hidden_other": "" + }, + "MultiplePluginsInstallModal": { + "title": { + "reinstall_one": "", + "reinstall_many": "", + "reinstall_other": "", + "install_one": "", + "install_many": "", + "install_other": "", + "mixed_one": "", + "mixed_many": "", + "mixed_other": "", + "update_one": "", + "update_many": "", + "update_other": "" + } + } +} diff --git a/backend/locales/it-IT.json b/backend/locales/it-IT.json new file mode 100644 index 00000000..237bcdf4 --- /dev/null +++ b/backend/locales/it-IT.json @@ -0,0 +1,267 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "Canale di aggiornamento", + "prerelease": "Prerilascio", + "stable": "Stabile", + "testing": "In prova" + } + }, + "Developer": { + "5secreload": "Ricarico tra 5 secondi", + "disabling": "Disabilito i tools di React", + "enabling": "Abilito i tools di React" + }, + "DropdownMultiselect": { + "button": { + "back": "Indietro" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "Il percorso specificato non è valido. Controllalo e prova a reinserirlo di nuovo.", + "unknown": "È avvenuto un'errore sconosciuto. L'errore segnalato è {{raw_error}}", + "perm_denied": "Il tuo utente non ha accesso alla directory specificata. Verifica se l'utente corrente (è deck su Steam Deck di default) ha i permessi corrispondenti per accedere alla cartella/file desiderato." + } + }, + "FilePickerIndex": { + "file": { + "select": "Seleziona questo file" + }, + "files": { + "all_files": "Tutti i file", + "file_type": "Tipo di file", + "show_hidden": "Mostra nascosti" + }, + "filter": { + "created_asce": "Creazione (meno recente)", + "created_desc": "Creazione (più recente)", + "modified_asce": "Modifica (meno recente)", + "modified_desc": "Modifica (più recente)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Dimensione (più piccolo)", + "size_desc": "Dimensione (più grande)" + }, + "folder": { + "label": "Cartella", + "select": "Usa questa cartella", + "show_more": "Mostra più file" + } + }, + "MultiplePluginsInstallModal": { + "confirm": "Sei sicuro di voler effettuare le modifiche seguenti?", + "description": { + "install": "Installa {{name}} {{version}}", + "reinstall": "Reinstalla {{name}} {{version}}", + "update": "Aggiorna {{name}} alla versione {{version}}" + }, + "ok_button": { + "idle": "Conferma", + "loading": "Elaboro" + }, + "title": { + "install_one": "Installa un plugin", + "install_many": "Installa {{count}} plugins", + "install_other": "Installa {{count}} plugins", + "mixed_one": "Modifica un plugin", + "mixed_many": "Modifica {{count}} plugins", + "mixed_other": "Modifica {{count}} plugins", + "reinstall_one": "Reinstalla un plugin", + "reinstall_many": "Reinstalla {{count}} plugins", + "reinstall_other": "Reinstalla {{count}} plugins", + "update_one": "Aggiorna un plugin", + "update_many": "Aggiorna {{count}} plugins", + "update_other": "Aggiorna {{count}} plugins" + } + }, + "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": { + "hide": "Accesso rapido: Nascondi", + "no_plugin": "Nessun plugin installato!", + "plugin_actions": "Operazioni sui plugins", + "reinstall": "Reinstalla", + "reload": "Ricarica", + "show": "Accesso rapido: Mostra", + "uninstall": "Rimuovi", + "update_all_one": "Aggiorna un plugin", + "update_all_many": "Aggiorna {{count}} plugins", + "update_all_other": "Aggiorna {{count}} plugins", + "update_to": "Aggiorna a {{name}}" + }, + "PluginListLabel": { + "hidden": "Nascosto dal menu di accesso rapido" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Disponibile aggiornamento a {{tag_name}}!", + "error": "Errore", + "plugin_error_uninstall": "Il plugin {{name}} ha causato un'eccezione che è descritta sopra. Questo tipicamente significa che il plugin deve essere aggiornato per funzionare sulla nuova versione di SteamUI. Controlla se è disponibile un aggiornamento o valutane la rimozione andando nelle impostazioni di Decky nella sezione Plugins.", + "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 1 plugin!", + "plugin_update_many": "Aggiornamenti disponibili per {{count}} plugins!", + "plugin_update_other": "Aggiornamenti disponibili per {{count}} plugins!" + }, + "PluginView": { + "hidden_one": "Un plugin è nascosto dalla lista", + "hidden_many": "Sono nascosti {{count}} plugin dalla lista", + "hidden_other": "Sono nascosti {{count}} plugin dalla lista" + }, + "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": { + "cef_console": { + "button": "Apri la console", + "desc": "Apri la console di CEF. Utile solamente per ragioni di debug. Questa opzione deve essere usata solo se sei uno sviluppatore di plugin o se uno di questi ti ha chiesto di farlo, visto che questa feature potrebbe essere potenzialmente pericolosa.", + "label": "Console CEF" + }, + "header": "Altro", + "react_devtools": { + "desc": "Abilita la connessione ad un computer che esegue i DevTools di React. Steam verrà ricaricato se lo stato cambia. Imposta il tuo 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" + }, + "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": { + "label": "Modalità sviluppatore" + }, + "other": { + "header": "Altro" + }, + "updates": { + "header": "Aggiornamenti" + }, + "notifications": { + "header": "Notifiche", + "decky_updates_label": "Aggiornamenti di Decky", + "plugin_updates_label": "Aggiornamenti dei plugins" + } + }, + "SettingsIndex": { + "developer_title": "Sviluppatore", + "general_title": "Generali", + "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!", + "store_testing_warning": { + "label": "Benvenuto nel Negozio di Test dei Plugins", + "desc": "Puoi usare questo canale del negozio per testare versioni di plugin sperimentali. Assicurati di lasciare un feedback su Github dopo averlo testato in modo che il plugin possa essere promosso a stabile per tutti gli altri utenti o per permettere allo sviluppatore di plugin di correggere eventuali errori." + } + }, + "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" + } + }, + "TitleView": { + "settings_desc": "Apri le impostazioni di Decky", + "decky_store_desc": "Apri lo store di Decky" + } +} diff --git a/backend/locales/ko-KR.json b/backend/locales/ko-KR.json new file mode 100644 index 00000000..48698c5c --- /dev/null +++ b/backend/locales/ko-KR.json @@ -0,0 +1,253 @@ +{ + "BranchSelect": { + "update_channel": { + "label": "업데이트 배포 채널", + "stable": "안정", + "testing": "테스트", + "prerelease": "사전 출시" + } + }, + "Developer": { + "disabling": "React DevTools 비활성화", + "enabling": "React DevTools 활성화", + "5secreload": "5초 내로 다시 로드 됩니다" + }, + "FilePickerIndex": { + "folder": { + "select": "이 폴더 사용", + "label": "폴더", + "show_more": "더 많은 파일 표시" + }, + "filter": { + "created_asce": "만든 날짜 (오름차순)", + "modified_asce": "수정한 날짜 (오름차순)", + "created_desc": "만든 날짜 (내림차 순)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "크기 (오름차순)", + "modified_desc": "수정한 날짜 (내림차순)", + "size_desc": "크기 (내림차순)" + }, + "files": { + "all_files": "모든 파일", + "show_hidden": "숨김 파일 표시", + "file_type": "파일 형식" + }, + "file": { + "select": "이 파일 선택" + } + }, + "PluginView": { + "hidden_other": "플러그인 {{count}}개 숨김" + }, + "PluginListLabel": { + "hidden": "빠른 액세스 메뉴에서 숨김" + }, + "PluginCard": { + "plugin_install": "설치", + "plugin_no_desc": "플러그인 설명이 제공되지 않았습니다.", + "plugin_version_label": "플러그인 버전", + "plugin_full_access": "이 플러그인은 Steam Deck의 모든 접근 권한을 가집니다." + }, + "PluginInstallModal": { + "install": { + "button_idle": "설치", + "button_processing": "설치 중", + "desc": "{{artifact}} {{version}}을(를) 설치하겠습니까?", + "title": "{{artifact}} 설치" + }, + "reinstall": { + "button_idle": "재설치", + "button_processing": "재설치 중", + "desc": "{{artifact}} {{version}}을(를) 재설치하겠습니까?", + "title": "{{artifact}} 재설치" + }, + "update": { + "button_idle": "업데이트", + "button_processing": "업데이트 중", + "title": "{{artifact}} 업데이트", + "desc": "{{artifact}} {{version}} 업데이트를 설치하겠습니까?" + }, + "no_hash": "이 플러그인은 해시 확인을 하지 않습니다, 설치에 따른 위험은 사용자가 감수해야 합니다." + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_other": "플러그인 {{count}}개 수정", + "update_other": "플러그인 {{count}}개 업데이트", + "reinstall_other": "플러그인 {{count}}개 재설치", + "install_other": "플러그인 {{count}}개 설치" + }, + "ok_button": { + "idle": "확인", + "loading": "작업 중" + }, + "confirm": "해당 수정을 적용하겠습니까?", + "description": { + "install": "{{name}} {{version}} 플러그인 설치", + "update": "{{name}}의 {{version}} 업데이트 설치", + "reinstall": "{{name}} {{version}} 재설치" + } + }, + "PluginListIndex": { + "plugin_actions": "플러그인 동작", + "reinstall": "재설치", + "reload": "다시 로드", + "uninstall": "설치 제거", + "show": "빠른 액세스 메뉴: 표시", + "hide": "빠른 액세스 메뉴: 숨김", + "update_all_other": "플러그인 {{count}}개 업데이트", + "no_plugin": "설치된 플러그인이 없습니다!", + "update_to": "{{name}}(으)로 업데이트" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "{{tag_name}} 업데이트를 설치할 수 있습니다!", + "error": "오류", + "plugin_load_error": { + "message": "{{name}} 플러그인 불러오기 오류", + "toast": "{{name}} 불러오기 오류" + }, + "plugin_uninstall": { + "button": "설치 제거", + "desc": "{{name}}을(를) 설치 제거하겠습니까?", + "title": "{{name}} 설치 제거" + }, + "plugin_update_other": "플러그인 {{count}}개를 업데이트 할 수 있습니다!", + "plugin_error_uninstall": "{{name}} 플러그인을 불러올 때 위와 같은 예외가 발생했습니다. 이는 보통 SteamUI 최신 버전에 맞는 플러그인 업데이트가 필요할 때 발생합니다. Decky 설정의 플러그인 섹션에서 업데이트가 있는지 확인하거나 설치 제거를 시도 해 보세요." + }, + "RemoteDebugging": { + "remote_cef": { + "label": "리모트 CEF 디버그 허용", + "desc": "네트워크의 모든 사용자에게 CEF 디버거에 대한 인증되지 않은 액세스 허용" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "콘솔 열기", + "label": "CEF 콘솔", + "desc": "CEF 콘솔을 엽니다. 디버그 전용입니다. 이 항목들은 위험 가능성이 있으므로 플러그인 개발자이거나 개발자의 가이드를 따를 경우에만 사용해야 합니다." + }, + "header": "기타", + "react_devtools": { + "ip_label": "IP", + "label": "React DevTools 활성화", + "desc": "React DevTools를 실행하고 있는 컴퓨터에 연결을 활성화합니다. 이 설정을 변경하면 Steam이 다시 로드됩니다. 활성화하기 전에 IP 주소를 설정하세요." + }, + "third_party_plugins": { + "button_install": "설치", + "button_zip": "검색", + "header": "서드파티 플러그인", + "label_desc": "URL", + "label_url": "URL에서 플러그인 설치", + "label_zip": "ZIP 파일에서 플러그인 설치" + }, + "valve_internal": { + "desc1": "Valve 내부 개발자 메뉴를 활성화합니다.", + "label": "Valve 내부 개발자 메뉴 활성화", + "desc2": "이 메뉴의 기능을 모른다면 어떤 것도 건드리지 마세요." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky 버전", + "header": "정보" + }, + "beta": { + "header": "베타 참가" + }, + "developer_mode": { + "label": "개발자 모드" + }, + "other": { + "header": "기타" + }, + "updates": { + "header": "업데이트" + }, + "notifications": { + "header": "알림", + "plugin_updates_label": "플러그인 업데이트 가능", + "decky_updates_label": "Decky 업데이트 가능" + } + }, + "SettingsIndex": { + "developer_title": "개발자", + "general_title": "일반", + "plugins_title": "플러그인" + }, + "Store": { + "store_contrib": { + "desc": "Decky 플러그인 스토어에 기여하고 싶다면 SteamDeckHomebrew/decky-plugin-template Github 저장소를 확인하세요. 개발 및 배포에 대한 정보는 README에서 확인할 수 있습니다.", + "label": "기여하기" + }, + "store_filter": { + "label": "필터", + "label_def": "모두" + }, + "store_search": { + "label": "검색" + }, + "store_sort": { + "label": "정렬", + "label_def": "최근 업데이트 순" + }, + "store_source": { + "desc": "모든 플러그인 소스 코드는 SteamDeckHomebrew/decky-plugin-database Github 저장소에서 확인할 수 있습니다.", + "label": "소스 코드" + }, + "store_tabs": { + "about": "정보", + "alph_asce": "알파벳순 (Z-A)", + "alph_desc": "알파벳순 (A-Z)", + "title": "검색" + }, + "store_testing_cta": "새로운 플러그인을 테스트하여 Decky Loader 팀을 도와주세요!", + "store_testing_warning": { + "desc": "이 스토어 채널을 사용하여 가장 최신 버전의 플러그인을 테스트할 수 있습니다. GitHub에 피드백을 남겨서 모든 사용자가 업데이트 할 수 있게 해주세요.", + "label": "테스트 스토어 채널에 오신 것을 환영합니다" + } + }, + "StoreSelect": { + "custom_store": { + "label": "사용자 지정 스토어", + "url_label": "URL" + }, + "store_channel": { + "custom": "사용자 지정", + "label": "스토어 배포 채널", + "default": "기본", + "testing": "테스트" + } + }, + "Updater": { + "decky_updates": "Decky 업데이트", + "no_patch_notes_desc": "이 버전에는 패치 노트가 없습니다", + "patch_notes_desc": "패치 노트", + "updates": { + "check_button": "업데이트 확인", + "checking": "확인 중", + "cur_version": "현재 버전: {{ver}}", + "install_button": "업데이트 설치", + "label": "업데이트", + "lat_version": "최신 상태: {{ver}} 실행 중", + "reloading": "다시 로드 중", + "updating": "업데이트 중" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "지정된 경로가 잘못되었습니다. 확인 후에 다시 입력해 주세요.", + "unknown": "알 수 없는 오류가 발생했습니다. Raw 오류: {{raw_error}}", + "perm_denied": "선택한 경로에 접근 할 수 없습니다. 선택한 폴더/파일 접근 권한이 유저(Steam Deck의 deck 유저)에 맞게 올바르게 설정 되었는지 확인하세요." + } + }, + "DropdownMultiselect": { + "button": { + "back": "뒤로" + } + }, + "TitleView": { + "settings_desc": "Decky 설정 열기", + "decky_store_desc": "Decky 스토어 열기" + } +} diff --git a/backend/locales/nl-NL.json b/backend/locales/nl-NL.json new file mode 100644 index 00000000..1adde308 --- /dev/null +++ b/backend/locales/nl-NL.json @@ -0,0 +1,243 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "Vooruitgave", + "stable": "Stabiel", + "label": "Update Kanaal", + "testing": "Test" + } + }, + "Developer": { + "5secreload": "Herlaad in 5 seconden", + "disabling": "Uitschakelen React DevTools", + "enabling": "Inschakelen React DevTools" + }, + "DropdownMultiselect": { + "button": { + "back": "Terug" + } + }, + "FilePickerError": { + "errors": { + "unknown": "Een onbekende fout is opgetreden. De ruwe fout is: {{raw_error}}", + "file_not_found": "Het opgegeven pad is niet geldig. Controleer het en voer het opnieuw correct in." + } + }, + "FilePickerIndex": { + "files": { + "all_files": "Alle bestanden", + "file_type": "Bestandstype", + "show_hidden": "Toon verborgen bestanden" + }, + "filter": { + "created_desc": "Gecreëerd ( Nieuwste)", + "modified_asce": "Veranderd (Oudste)", + "modified_desc": "Veranderd (Nieuwste)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Grootte (Kleinste)", + "size_desc": "Grootte (Grootste)", + "created_asce": "Gecreëerd (Oudste)" + }, + "folder": { + "label": "Map", + "select": "Gebruik deze map", + "show_more": "Toon meer bestanden" + } + }, + "PluginView": { + "hidden_one": "1 plug-in is verborgen in deze lijst", + "hidden_other": "{{count}} plug-ins zijn verborgen in deze lijst" + }, + "PluginListLabel": { + "hidden": "Verborgen in het snelmenu" + }, + "PluginCard": { + "plugin_install": "Installeren", + "plugin_no_desc": "Geen beschrijving gegeven.", + "plugin_version_label": "Plugin Versie", + "plugin_full_access": "Deze plug-in heeft volledige toegang tot je Steam Deck." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Installeren", + "button_processing": "Bezig met installeren", + "title": "Installeer {{artifact}}", + "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt installeren?" + }, + "no_hash": "Deze plug-in heeft geen hash, u installeert deze op eigen risico.", + "reinstall": { + "button_idle": "Herinstalleren", + "button_processing": "Bezig te herinstalleren", + "desc": "Weet je zeker dat je {{artifact}} {{version}} opnieuw wilt installeren?", + "title": "Installeer {{artifact}} opnieuw" + }, + "update": { + "button_idle": "Update", + "button_processing": "Bezig met updaten", + "title": "{{artifact}} bijwerken", + "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt updaten?" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Wijzig {{count}} plug-in", + "mixed_other": "Pas {{count}} plug-ins aan", + "update_one": "1 plugin bijwerken", + "update_other": "{{count}} plug-ins bijwerken", + "install_one": "Installeer 1 plugin", + "install_other": "Installeer {{count}} plugins", + "reinstall_one": "1 plugin opnieuw installeren", + "reinstall_other": "{{count}} plugins opnieuw installeren" + }, + "ok_button": { + "idle": "Bevestigen", + "loading": "Werkend" + }, + "confirm": "Weet u zeker dat u de volgende wijzigingen wilt aanbrengen?", + "description": { + "install": "Installeer {{name}} {{version}}", + "update": "Update {{name}} naar {{version}}", + "reinstall": "Installeer opnieuw {{name}} {{version}}" + } + }, + "PluginListIndex": { + "no_plugin": "Geen plugins geïnstalleerd!", + "plugin_actions": "Plugin Acties", + "reload": "Herladen", + "uninstall": "Verwijderen", + "update_to": "Update naar {{name}}", + "hide": "Snelle toegang: Verberg", + "update_all_one": "Update 1 plugin", + "update_all_other": "Update {{count}} plugins", + "reinstall": "Opnieuw installeren", + "show": "Snelle toegang: Toon" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Fout", + "plugin_load_error": { + "message": "Fout bij het laden van plugin {{name}}", + "toast": "Fout bij het laden van {{name}}" + }, + "plugin_uninstall": { + "button": "Verwijderen", + "desc": "Weet je zeker dat je {{name}} wilt verwijderen?", + "title": "Verwijder {{name}}" + }, + "plugin_update_one": "Updates beschikbaar voor 1 plugin!", + "plugin_update_other": "Updates beschikbaar voor {{count}} plugins!", + "decky_update_available": "Update naar {{tag_name}} beschikbaar!", + "plugin_error_uninstall": "Het laden van {{name}} veroorzaakte een uitzondering zoals hierboven weergegeven. Dit betekent meestal dat de plug-in een update vereist voor de nieuwe versie van SteamUI. Controleer of er een update aanwezig is of evalueer de verwijdering ervan in de Decky-instellingen, in het gedeelte Plug-ins." + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Sta ongeauthenticeerde toegang tot de CEF-foutopsporing toe aan iedereen in uw netwerk", + "label": "Externe CEF-foutopsporing toestaan" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Console openen", + "label": "CEF Bedieningsscherm", + "desc": "Opent de CEF-console. Alleen nuttig voor foutopsporingsdoeleinden. Dingen hier zijn potentieel gevaarlijk en mogen alleen worden gebruikt als u een ontwikkelaar van plug-ins bent, of hier door een ontwikkelaar naartoe wordt geleid." + }, + "header": "Andere", + "react_devtools": { + "ip_label": "IP", + "label": "Aanzetten React DevTools", + "desc": "Maakt verbinding met een computer met React DevTools mogelijk. Als je deze instelling wijzigt, wordt Steam opnieuw geladen. Stel het IP-adres in voordat u het inschakelt." + }, + "third_party_plugins": { + "header": "Plug-ins van derden", + "label_desc": "URL", + "label_url": "Installeer Plugin van URL", + "label_zip": "Installeer Plugin van Zip bestand", + "button_install": "Installeren", + "button_zip": "Bladeren" + }, + "valve_internal": { + "desc1": "Schakelt het interne ontwikkelaarsmenu van Valve in.", + "desc2": "Raak niets in dit menu aan tenzij u weet wat het doet.", + "label": "Valve Internal inschakelen" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky versie", + "header": "Over" + }, + "beta": { + "header": "Beta deelname" + }, + "developer_mode": { + "label": "Ontwikkelaars modus" + }, + "other": { + "header": "Overige" + }, + "updates": { + "header": "Nieuwe Versies" + } + }, + "SettingsIndex": { + "developer_title": "Ontwikkelaar", + "general_title": "Algemeen", + "plugins_title": "Plugins" + }, + "Store": { + "store_filter": { + "label": "Filter", + "label_def": "Alles" + }, + "store_search": { + "label": "Zoek" + }, + "store_sort": { + "label": "Sorteren", + "label_def": "Laatste Geupdate (Nieuwste)" + }, + "store_source": { + "label": "Bron Code", + "desc": "Alle broncode van de plug-in is beschikbaar in de SteamDeckHomebrew/decky-plugin-database-repository op GitHub." + }, + "store_tabs": { + "about": "Over", + "alph_asce": "Alfabetisch (Z naar A)", + "alph_desc": "Alfabetisch (A naar Z)", + "title": "Bladeren" + }, + "store_testing_cta": "Overweeg alsjeblieft om nieuwe plug-ins te testen om het Decky Loader-team te helpen!", + "store_contrib": { + "desc": "Als je wilt bijdragen aan de Decky Plugin winkel, kijk dan in de SteamDeckHomebrew/decky-plugin-template repository op GitHub. Informatie over ontwikkeling en distributie is beschikbaar in de README.", + "label": "Bijdragende" + } + }, + "StoreSelect": { + "custom_store": { + "label": "Aangepassingen winkel", + "url_label": "URL" + }, + "store_channel": { + "custom": "Aanpassingen", + "default": "Standaard", + "label": "Winkel Kanaal", + "testing": "Testen" + } + }, + "Updater": { + "patch_notes_desc": "Correctie opmerkingen", + "updates": { + "check_button": "Controleer op updates", + "checking": "Controleren", + "cur_version": "Huidige versie: {{ver}}", + "install_button": "Installeer Update", + "label": "Update", + "lat_version": "Up-to-date: loopt {{ver}}", + "reloading": "Herstarten", + "updating": "Aan het updaten" + }, + "decky_updates": "Decky Nieuwe Versies", + "no_patch_notes_desc": "geen correctie-opmerkingen voor deze versie" + } +} diff --git a/backend/locales/pl-PL.json b/backend/locales/pl-PL.json new file mode 100644 index 00000000..5231fa37 --- /dev/null +++ b/backend/locales/pl-PL.json @@ -0,0 +1,267 @@ +{ + "BranchSelect": { + "update_channel": { + "testing": "Testowy", + "label": "Kanał aktualizacji", + "stable": "Stabilny", + "prerelease": "Przedpremierowy" + } + }, + "Developer": { + "enabling": "Włączanie React DevTools", + "5secreload": "Ponowne załadowanie za 5 sekund", + "disabling": "Wyłączanie React DevTools" + }, + "DropdownMultiselect": { + "button": { + "back": "Powrót" + } + }, + "FilePickerError": { + "errors": { + "perm_denied": "Nie masz dostępu do podanego katalogu. Sprawdź, czy twój użytkownik (deck na Steam Deck) ma odpowiednie uprawnienia dostępu do określonego katalogu/pliku.", + "unknown": "Wystąpił nieznany błąd. Surowy błąd to {{raw_error}}", + "file_not_found": "Podana ścieżka jest nieprawidłowa. Sprawdź ją i wprowadź ponownie poprawnie." + } + }, + "FilePickerIndex": { + "file": { + "select": "Wybierz ten plik" + }, + "files": { + "all_files": "Wszystkie pliki", + "file_type": "Typ pliku", + "show_hidden": "Pokaż ukryte pliki" + }, + "filter": { + "created_asce": "Utworzono (najstarszy)", + "created_desc": "Utworzono (najnowszy)", + "modified_asce": "Zmodyfikowany (najstarszy)", + "modified_desc": "Zmodyfikowany (najnowszy)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Rozmiar (najmniejszy)", + "size_desc": "Rozmiar (największy)" + }, + "folder": { + "label": "Katalog", + "select": "Użyj tego katalogu", + "show_more": "Pokaż więcej plików" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Zmodyfikuj {{count}} plugin", + "mixed_few": "Zmodyfikuj {{count}} pluginy", + "mixed_many": "Zmodyfikuj {{count}} pluginów", + "reinstall_one": "Reinstaluj 1 plugin", + "reinstall_few": "Reinstaluj {{count}} pluginy", + "reinstall_many": "Reinstaluj {{count}} pluginów", + "install_one": "Zainstaluj 1 plugin", + "install_few": "Zainstaluj {{count}} pluginy", + "install_many": "Zainstaluj {{count}} pluginów", + "update_one": "Zaktualizuj 1 plugin", + "update_few": "Zaktualizuj {{count}} pluginy", + "update_many": "Zaktualizuj {{count}} pluginów" + }, + "confirm": "Czy na pewno chcesz wprowadzić następujące modyfikacje?", + "description": { + "install": "Zainstaluj {{name}} {{version}}", + "reinstall": "Reinstaluj {{name}} {{version}}", + "update": "Zaktualizuj {{name}} do {{version}}" + }, + "ok_button": { + "idle": "Potwierdź", + "loading": "W toku" + } + }, + "PluginCard": { + "plugin_install": "Zainstaluj", + "plugin_no_desc": "Brak opisu.", + "plugin_version_label": "Wersja pluginu", + "plugin_full_access": "Ten plugin ma pełny dostęp do twojego Steam Decka." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Zainstaluj", + "button_processing": "Instalowanie", + "desc": "Czy na pewno chcesz zainstalować {{artifact}} {{version}}?", + "title": "Zainstaluj {{artifact}}" + }, + "reinstall": { + "button_idle": "Reinstaluj", + "button_processing": "Reinstalowanie", + "desc": "Czy na pewno chcesz ponownie zainstalować {{artifact}} {{version}}?", + "title": "Reinstaluj {{artifact}}" + }, + "update": { + "button_idle": "Aktualizacja", + "button_processing": "Aktualizowanie", + "desc": "Czy na pewno chcesz zaktualizować {{artifact}} {{version}}?", + "title": "Zaktualizuj {{artifact}}" + }, + "no_hash": "Ten plugin nie ma hasha, instalujesz go na własne ryzyko." + }, + "PluginListIndex": { + "hide": "Szybki dostęp: Ukryj", + "no_plugin": "Brak zainstalowanych pluginów!", + "reload": "Załaduj ponownie", + "update_all_one": "Zaktualizuj 1 plugin", + "update_all_few": "Zaktualizuj {{count}} pluginy", + "update_all_many": "Zaktualizuj {{count}} pluginów", + "plugin_actions": "Akcje pluginów", + "reinstall": "Reinstalacja", + "show": "Szybki dostęp: Pokaż", + "uninstall": "Odinstaluj", + "update_to": "Zaktualizuj do {{name}}" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Dostępna aktualizacja do {{tag_name}}!", + "error": "Błąd", + "plugin_error_uninstall": "Ładowanie {{name}} spowodowało wyjątek, jak pokazano powyżej. Zwykle oznacza to, że plugin wymaga aktualizacji do nowej wersji SteamUI. Sprawdź, czy aktualizacja jest obecna lub rozważ usunięcie go w ustawieniach Decky, w sekcji Pluginy.", + "plugin_load_error": { + "message": "Błąd ładowania plugin {{name}}", + "toast": "Błąd ładowania {{name}}" + }, + "plugin_uninstall": { + "button": "Odinstaluj", + "title": "Odinstaluj {{name}}", + "desc": "Czy na pewno chcesz odinstalować {{name}}?" + }, + "plugin_update_one": "Aktualizacje dostępne dla 1 pluginu!", + "plugin_update_few": "Aktualizacje dostępne dla {{count}} pluginów!", + "plugin_update_many": "Aktualizacje dostępne dla {{count}} pluginów!" + }, + "PluginListLabel": { + "hidden": "Ukryty w menu szybkiego dostępu" + }, + "PluginView": { + "hidden_one": "1 plugin jest ukryty na tej liście", + "hidden_few": "{{count}} pluginy jest ukryty na tej liście", + "hidden_many": "{{count}} pluginów jest ukryty na tej liście" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Zezwalaj na nieuwierzytelniony dostęp do debugera CEF wszystkim osobom w Twojej sieci", + "label": "Zezwól na zdalne debugowanie CEF" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Otwórz konsolę", + "desc": "Otwiera konsolę CEF. Przydatne tylko do celów debugowania. Rzeczy tutaj są potencjalnie niebezpieczne i powinny być używane tylko wtedy, gdy jesteś twórcą wtyczek lub zostałeś tu przez kogoś skierowany.", + "label": "Konsola CEF" + }, + "header": "Inne", + "react_devtools": { + "desc": "Umożliwia połączenie z komputerem z uruchomionym React DevTools. Zmiana tego ustawienia spowoduje ponowne załadowanie Steam. Ustaw adres IP przed włączeniem.", + "ip_label": "IP", + "label": "Włącz React DevTools" + }, + "third_party_plugins": { + "button_install": "Zainstaluj", + "button_zip": "Przeglądaj", + "header": "Pluginy zewnętrzne", + "label_desc": "URL", + "label_url": "Zainstaluj plugin z adresu URL", + "label_zip": "Zainstaluj plugin z pliku ZIP" + }, + "valve_internal": { + "desc1": "Włącza wewnętrzne menu programisty Valve.", + "desc2": "Nie dotykaj niczego w tym menu, chyba że wiesz, co robi.", + "label": "Włącz Valve Internal" + } + }, + "SettingsGeneralIndex": { + "notifications": { + "decky_updates_label": "Dostępna aktualizacja Decky", + "header": "Powiadomienia", + "plugin_updates_label": "Dostępne aktualizacje pluginów" + }, + "other": { + "header": "Inne" + }, + "updates": { + "header": "Aktualizacje" + }, + "about": { + "header": "Informacje", + "decky_version": "Wersja Decky" + }, + "beta": { + "header": "Udział w becie" + }, + "developer_mode": { + "label": "Tryb dewelopera" + } + }, + "SettingsIndex": { + "developer_title": "Deweloper", + "general_title": "Ogólne", + "plugins_title": "Pluginy" + }, + "Store": { + "store_contrib": { + "desc": "Jeśli chcesz przyczynić się do rozwoju Decky Plugin Store, sprawdź repozytorium SteamDeckHomebrew/decky-plugin-template na GitHub. Informacje na temat rozwoju i dystrybucji są dostępne w pliku README.", + "label": "Współtworzenie" + }, + "store_filter": { + "label": "Filtr", + "label_def": "Wszystko" + }, + "store_search": { + "label": "Szukaj" + }, + "store_sort": { + "label": "Sortowanie", + "label_def": "Ostatnia aktualizacja (najnowsza)" + }, + "store_source": { + "desc": "Cały kod źródłowy pluginów jest dostępny w repozytorium SteamDeckHomebrew/decky-plugin-database na GitHub.", + "label": "Kod źródłowy" + }, + "store_tabs": { + "alph_asce": "Alfabetycznie (od Z do A)", + "alph_desc": "Alfabetycznie (od A do Z)", + "title": "Przeglądaj", + "about": "Informacje" + }, + "store_testing_cta": "Rozważ przetestowanie nowych pluginów, aby pomóc zespołowi Decky Loader!", + "store_testing_warning": { + "label": "Witamy w Testowym Kanale Sklepu", + "desc": "Możesz użyć tego kanału sklepu do testowania najnowszych wersji pluginów. Pamiętaj, aby zostawić opinię na GitHub, aby plugin mogła zostać zaktualizowana dla wszystkich użytkowników." + } + }, + "StoreSelect": { + "custom_store": { + "label": "Niestandardowy sklep", + "url_label": "URL" + }, + "store_channel": { + "custom": "Niestandardowy", + "default": "Domyślny", + "label": "Kanał sklepu", + "testing": "Testowy" + } + }, + "Updater": { + "decky_updates": "Aktualizacje Decky", + "no_patch_notes_desc": "Brak informacji o poprawkach dla tej wersji", + "patch_notes_desc": "Opis zmian", + "updates": { + "check_button": "Sprawdź aktualizacje", + "checking": "Sprawdzanie", + "cur_version": "Aktualna wersja: {{ver}}", + "install_button": "Zainstaluj aktualizację", + "label": "Aktualizacje", + "lat_version": "Aktualizacje zainstalowane. Aktualna wersja: {{ver}}", + "reloading": "Ponowne ładowanie", + "updating": "Aktualizowanie" + } + }, + "TitleView": { + "settings_desc": "Otwórz ustawienia Decky", + "decky_store_desc": "Otwórz sklep Decky" + } +} diff --git a/backend/locales/pt-BR.json b/backend/locales/pt-BR.json new file mode 100644 index 00000000..2a7c173b --- /dev/null +++ b/backend/locales/pt-BR.json @@ -0,0 +1,259 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "Pré-lançamento", + "stable": "Estável", + "testing": "Em Teste", + "label": "Canal de Atualização" + } + }, + "Developer": { + "5secreload": "Recarregando em 5 segundos", + "enabling": "Habilitando React DevTools", + "disabling": "Desabilitando React DevTools" + }, + "FilePickerIndex": { + "folder": { + "select": "Use esta pasta", + "label": "Pasta", + "show_more": "Mostrar mais arquivos" + }, + "files": { + "show_hidden": "Mostrar Arquivos Ocultos", + "all_files": "Todos os arquivos", + "file_type": "Formato de arquivo" + }, + "filter": { + "created_asce": "Criado (Mais antigo)", + "created_desc": "Criado (Mais recente)", + "modified_asce": "Alterado (Mais antigo)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Tamanho (Menor)", + "size_desc": "Tamanho (Maior)", + "modified_desc": "Alterado (Mais recente)" + }, + "file": { + "select": "Selecione este arquivo" + } + }, + "PluginListLabel": { + "hidden": "Oculto no menu de acesso rápido" + }, + "PluginCard": { + "plugin_full_access": "Este plugin tem acesso total ao seu Steam Deck.", + "plugin_install": "Instalar", + "plugin_no_desc": "Nenhuma descrição fornecida.", + "plugin_version_label": "Versão do plugin" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalar", + "button_processing": "Instalando", + "desc": "Você tem certeza que deseja instalar {{artifact}} {{version}}?", + "title": "Instalar {{artifact}}" + }, + "reinstall": { + "button_idle": "Reinstalar", + "button_processing": "Reinstalando", + "desc": "Tem certeza que voce deseja reinstalar {{artifact}} {{version}}?", + "title": "Reinstalar {{artifact}}" + }, + "update": { + "button_idle": "Atualizar", + "button_processing": "Atualizando", + "desc": "Tem certeza que voce deseja atualizar {{artifact}} {{version}}?", + "title": "Atualizar {{artifact}}" + }, + "no_hash": "Este plugin não tem um hash, você o está instalando por sua conta em risco." + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Modificar {{count}} plugin", + "mixed_many": "Modificar {{count}} plugins", + "mixed_other": "Modificar {{count}} plugins", + "update_one": "Atualizar 1 plugin", + "update_many": "Atualizar {{count}} plugins", + "update_other": "Atualizar {{count}} plugins", + "install_one": "Instalar 1 plugin", + "install_many": "Instalar {{count}} plugins", + "install_other": "Instalar {{count}} plugins", + "reinstall_one": "Reinstalar 1 plugin", + "reinstall_many": "Reinstalar {{count}} plugins", + "reinstall_other": "Reinstalar {{count}} plugins" + }, + "ok_button": { + "idle": "Confirmar", + "loading": "Carregando" + }, + "description": { + "install": "Instalar {{name}} {{version}}", + "update": "Atualizar {{name}} para {{version}}", + "reinstall": "Reinstalar {{name}} {{version}}" + }, + "confirm": "Tem certeza que deseja fazer as seguintes modificações?" + }, + "PluginListIndex": { + "no_plugin": "Nenhum plugin instalado!", + "plugin_actions": "Ações do plugin", + "reinstall": "Reinstalar", + "reload": "Recarregar", + "uninstall": "Desinstalar", + "update_to": "Atualizar para {{name}}", + "show": "Acesso Rápido: Mostrar", + "update_all_one": "Atualizar 1 plugin", + "update_all_many": "Atualizar {{count}} plugins", + "update_all_other": "Atualizar {{count}} plugins", + "hide": "Acesso Rápido: Ocultar" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Erro", + "plugin_load_error": { + "message": "Erro ao carregar o plugin {{name}}", + "toast": "Erro ao carregar {{name}}" + }, + "plugin_uninstall": { + "button": "Desinstalar", + "desc": "Você tem certeza que deseja desinstalar {{name}}?", + "title": "Desinstalar {{name}}" + }, + "decky_update_available": "Atualização para {{tag_name}} disponível!", + "plugin_error_uninstall": "Um erro aconteceu ao carregar {{name}}, como mostrado acima. Isso normalmente significa que o plugin precisa de uma atualização para a nova versão do SteamUI. Confira se existe uma atualização ou avalie a remoção do plugin nas configurações do Decky, na sessão de plugins.", + "plugin_update_one": "Atualização disponível para 1 plugin!", + "plugin_update_many": "Atualizações disponíveis para {{count}} plugins!", + "plugin_update_other": "Atualizações disponíveis para {{count}} plugins!" + }, + "RemoteDebugging": { + "remote_cef": { + "label": "Permitir Depuração CEF Demota", + "desc": "Permitir acesso não autenticato ao depurador CEF para qualquer um na sua rede" + } + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Abrir o Console", + "label": "Console CEF", + "desc": "Abre o Console CEF. Somente útil para fins de depuração. O material aqui é potencialmente perigoso e só deve ser usado se você for um desenvolvedor de plugin, ou direcionado até aqui por um." + }, + "header": "Outros", + "react_devtools": { + "desc": "Habilita a conexão a um computador executando React DevTools. Alterar essa configuração irá recarregar a Steam. Defina o endereço IP antes de habilitar.", + "ip_label": "IP", + "label": "Habilitar React DevTools" + }, + "third_party_plugins": { + "button_install": "Instalar", + "button_zip": "Navegar", + "header": "Plugins de terceiros", + "label_url": "Instalar Plugin a partir da URL", + "label_zip": "Instalar Plugin a partir de um arquivo ZIP", + "label_desc": "URL" + }, + "valve_internal": { + "desc1": "Habilita o menu interno de desenvolvedor da Valve.", + "desc2": "Não toque em nada neste menu, a não ser que você saiba o que está fazendo.", + "label": "Habilitar Menu Interno da Valve" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Versão do Decky", + "header": "Sobre" + }, + "developer_mode": { + "label": "Modo Deselvolvedor" + }, + "other": { + "header": "Outros" + }, + "updates": { + "header": "Atualizações" + }, + "beta": { + "header": "Participação no Beta" + }, + "notifications": { + "decky_updates_label": "Atualização do Decky disponível", + "header": "Noificações", + "plugin_updates_label": "Atualizações de Plugin disponíveis" + } + }, + "SettingsIndex": { + "developer_title": "Desenvolvedor", + "general_title": "Geral", + "plugins_title": "Plugins" + }, + "Store": { + "store_contrib": { + "label": "Contribuindo", + "desc": "Se você deseja contribuir para a Loja de Plugins para o Decky, confira o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Informações sobre o desenvolvimento e distribuição estão disponíveis no README." + }, + "store_filter": { + "label": "Filtros", + "label_def": "Todos" + }, + "store_search": { + "label": "Buscar" + }, + "store_sort": { + "label": "Ordenar", + "label_def": "Último atualizado (Mais recente)" + }, + "store_source": { + "desc": "Todos os códigos fonte dos plugins estão disponíveis no repositório SteamDeckHomebrew/decky-plugin-database no GitHub.", + "label": "Código Fonte" + }, + "store_tabs": { + "about": "Sobre", + "alph_desc": "Alfabética (A - Z)", + "title": "Navegar", + "alph_asce": "Alfabética (Z - A)" + }, + "store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!" + }, + "StoreSelect": { + "custom_store": { + "label": "Loja Personalizada", + "url_label": "URL" + }, + "store_channel": { + "custom": "Personalizada", + "default": "Padrão", + "label": "Canal da Loja", + "testing": "Em Teste" + } + }, + "Updater": { + "no_patch_notes_desc": "nenhuma nota de alteração para esta versão", + "patch_notes_desc": "Notas de alteração", + "updates": { + "check_button": "Buscar Atualizações", + "checking": "Buscando", + "cur_version": "Versão atual: {{ver}}", + "install_button": "Instalar Atualização", + "label": "Atualizações", + "lat_version": "Atualizado: rodando {{ver}}", + "reloading": "Recarregando", + "updating": "Atualizando" + }, + "decky_updates": "Atualizações do Decky" + }, + "PluginView": { + "hidden_one": "1 plugin está oculto nesta lista", + "hidden_many": "{{count}} plugins estão ocultos nesta lista", + "hidden_other": "{{count}} plugins estão ocultos nesta lista" + }, + "DropdownMultiselect": { + "button": { + "back": "Voltar" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "O caminho especificado não é válido. Por favor, confira e reinsira corretamente.", + "unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}", + "perm_denied": "Você não tem acesso à este diretório. Por favor, verifiquei se seu usuário (deck no Steam Deck) tem as permissões necessárias para acessar este arquivo/pasta." + } + } +} diff --git a/backend/locales/pt-PT.json b/backend/locales/pt-PT.json new file mode 100644 index 00000000..9b273569 --- /dev/null +++ b/backend/locales/pt-PT.json @@ -0,0 +1,222 @@ +{ + "FilePickerIndex": { + "folder": { + "select": "Usar esta pasta" + } + }, + "PluginView": { + "hidden_one": "1 plugin está oculto desta lista", + "hidden_many": "{{count}} plugins estão ocultos desta lista", + "hidden_other": "{{count}} plugins estão ocultos desta lista" + }, + "PluginCard": { + "plugin_full_access": "Este plugin tem acesso total à tua Steam Deck.", + "plugin_install": "Instalar", + "plugin_version_label": "Versão do plugin", + "plugin_no_desc": "Não tem descrição." + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalar", + "button_processing": "Instalação em curso", + "title": "Instalar {{artifact}}", + "desc": "De certeza que queres instalar {{artifact}} {{version}}?" + }, + "reinstall": { + "button_idle": "Reinstalar", + "button_processing": "Reinstalação em curso", + "title": "Reinstalar {{artifact}}", + "desc": "De certeza que queres reinstalar {{artifact}} {{version}}?" + }, + "update": { + "button_idle": "Actualizar", + "button_processing": "Actualização em curso", + "title": "Actualizar {{artifact}}", + "desc": "De certeza que queres actualizar {{artifact}} {{version}}?" + }, + "no_hash": "Este plugin não tem um hash, estás a instalá-lo por tua conta e risco." + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Alterar 1 plugin", + "mixed_many": "Alterar {{count}} plugins", + "mixed_other": "Alterar {{count}} plugins", + "update_one": "Actualizar 1 plugin", + "update_many": "Actualizar {{count}} plugins", + "update_other": "Actualizar {{count}} plugins", + "reinstall_one": "Reinstalar 1 plugin", + "reinstall_many": "Reinstalar {{count}} plugins", + "reinstall_other": "Reinstalar {{count}} plugins", + "install_one": "Instalar 1 plugin", + "install_many": "Instalar {{count}} plugins", + "install_other": "Instalar {{count}} plugins" + }, + "ok_button": { + "idle": "Confirmar", + "loading": "Em curso" + }, + "description": { + "install": "Instalar {{name}} {{version}}", + "update": "Actualizar {{name}} para {{version}}", + "reinstall": "Reinstalar {{name}} {{version}}" + }, + "confirm": "De certeza que queres fazer as seguintes alterações?" + }, + "PluginListIndex": { + "no_plugin": "Nenhum plugin instalado!", + "reinstall": "Reinstalar", + "uninstall": "Desinstalar", + "update_to": "Actualizar para {{name}}", + "update_all_one": "Actualizar 1 plugin", + "update_all_many": "Actualizar {{count}} plugins", + "update_all_other": "Actualizar {{count}} plugins", + "plugin_actions": "Operações de plugin", + "reload": "Recarregar", + "show": "Acesso rápido: Mostrar", + "hide": "Acesso rápido: Ocultar" + }, + "BranchSelect": { + "update_channel": { + "stable": "Estável", + "testing": "Em teste", + "label": "Canal de actualização", + "prerelease": "Pré-lançamento" + } + }, + "Developer": { + "5secreload": "Vai recarregar em 5 segundos", + "disabling": "Desactivando React DevTools", + "enabling": "Activando React DevTools" + }, + "PluginListLabel": { + "hidden": "Oculto do menu de acesso rápido" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "Erro", + "plugin_load_error": { + "message": "Erro ao carregar o plugin {{name}}", + "toast": "Erro ao carregar {{name}}" + }, + "plugin_uninstall": { + "button": "Desinstalar", + "title": "Desinstalar {{name}}", + "desc": "De certeza que queres desinstalar {{name}}?" + }, + "decky_update_available": "Está disponível uma nova versão de {{tag_name}} !", + "plugin_update_one": "1 plugin tem actualizações disponíveis!", + "plugin_update_many": "{{count}} plugins têm actualizações disponíveis!", + "plugin_update_other": "{{count}} plugins têm actualizações disponíveis!", + "plugin_error_uninstall": "Houve uma excepção ao carregar {{name}}, como mostrado em cima. Pode ter sido porque o plugin requere a última versão do SteamUI. Verifica se há uma actualização disponível ou desinstala o plugin nas definições do Decky." + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Abrir consola", + "label": "Consola CEF", + "desc": "Abre a consola do CEF. Só é útil para efeitos de debugging. Pode ser perigosa e só deve ser usada se és um desenvolvedor de plugins, ou se foste aqui indicado por um desenvolvedor." + }, + "header": "Outros", + "react_devtools": { + "desc": "Permite a conecção a um computador que está a correr React DevTools. Mudar esta definição vai recarregar o Steam. Define o endereço de IP antes de activar.", + "ip_label": "IP", + "label": "Activar React DevTools" + }, + "third_party_plugins": { + "button_install": "Instalar", + "button_zip": "Navegar", + "header": "Plugins de terceiros", + "label_desc": "URl", + "label_url": "Instalar plugin a partir dum URL", + "label_zip": "Instalar plugin a partir dum ficheiro ZIP" + }, + "valve_internal": { + "label": "Activar menu interno da Valve", + "desc1": "Activa o menu interno de programador da Valve.", + "desc2": "Não toques em nada deste menu se não souberes a sua função." + } + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Permitir acesso não autenticado ao debugger do CEF a qualquer pessoa na tua rede", + "label": "Permitir debugging remoto do CEF" + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Versão do Decky", + "header": "Sobre" + }, + "beta": { + "header": "Participação na versão Beta" + }, + "developer_mode": { + "label": "Modo de programador" + }, + "other": { + "header": "Outros" + }, + "updates": { + "header": "Actualizações" + } + }, + "SettingsIndex": { + "developer_title": "Programador", + "general_title": "Geral", + "plugins_title": "Plugins" + }, + "Store": { + "store_contrib": { + "label": "Contribuir", + "desc": "Se queres contribuir com um novo plugin, vai ao repositório SteamDeckHomebrew/decky-plugin-template no GitHub. No README, podes encontrar mais informação sobre desenvolvimento e distribuição." + }, + "store_filter": { + "label": "Filtro", + "label_def": "Todos" + }, + "store_search": { + "label": "Procurar" + }, + "store_sort": { + "label": "Ordenar", + "label_def": "Última actualização (mais recente)" + }, + "store_source": { + "label": "Código fonte", + "desc": "O código fonte de cada plugin está disponível no repositório SteamDeckHomebrew/decky-plugin-database no GitHub." + }, + "store_tabs": { + "about": "Sobre", + "alph_asce": "Alfabeticamente (Z-A)", + "alph_desc": "Alfabeticamente (A-Z)", + "title": "Navegar" + }, + "store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!" + }, + "StoreSelect": { + "custom_store": { + "url_label": "URL", + "label": "Loja personalizada" + }, + "store_channel": { + "custom": "Personalizada", + "default": "Standard", + "testing": "Em teste", + "label": "Canal de loja" + } + }, + "Updater": { + "decky_updates": "Actualizações do Decky", + "no_patch_notes_desc": "sem registo de alterações desta versão", + "patch_notes_desc": "Registo de alterações", + "updates": { + "check_button": "Procurar actualizações", + "checking": "Busca de actualizações em curso", + "cur_version": "Versão actual: {{ver}}", + "label": "Actualizações", + "lat_version": "Actualizado: a correr {{ver}}", + "updating": "Actualização em curso", + "reloading": "Recarregar", + "install_button": "Instalar actualização" + } + } +} diff --git a/backend/locales/ru-RU.json b/backend/locales/ru-RU.json new file mode 100644 index 00000000..776ffa12 --- /dev/null +++ b/backend/locales/ru-RU.json @@ -0,0 +1,267 @@ +{ + "MultiplePluginsInstallModal": { + "title": { + "update_one": "Переустановить {{count}} плагин", + "update_few": "Переустановить {{count}} плагинов", + "update_many": "Переустановить {{count}} плагинов", + "reinstall_one": "Переустановить {{count}} плагин", + "reinstall_few": "Переустановить {{count}} плагинов", + "reinstall_many": "Переустановить {{count}} плагинов", + "install_one": "Установить {{count}} плагин", + "install_few": "Установить {{count}} плагинов", + "install_many": "Установить {{count}} плагинов", + "mixed_one": "Изменить {{count}} плагин", + "mixed_few": "Изменить {{count}} плагинов", + "mixed_many": "Изменить {{count}} плагинов" + }, + "description": { + "install": "Установить {{name}} {{version}}", + "reinstall": "Переустановить {{name}} {{version}}", + "update": "Обновить с {{name}} на {{version}}" + }, + "confirm": "Вы уверены, что хотите внести следующие изменения?", + "ok_button": { + "idle": "Подтвердить", + "loading": "В процессе" + } + }, + "PluginListIndex": { + "update_all_one": "Обновить {{count}} плагин", + "update_all_few": "Обновить {{count}} плагинов", + "update_all_many": "Обновить {{count}} плагинов", + "hide": "Быстрый доступ: Скрыть", + "reload": "Перезагрузить", + "uninstall": "Удалить", + "update_to": "Обновить на {{name}}", + "show": "Быстрый доступ: Показать", + "plugin_actions": "Действия с плагинами", + "no_plugin": "Не установлено ни одного плагина!", + "reinstall": "Переустановить" + }, + "PluginLoader": { + "plugin_update_one": "Обновления доступны для {{count}} плагина!", + "plugin_update_few": "Обновления доступны для {{count}} плагинов!", + "plugin_update_many": "Обновления доступны для {{count}} плагинов!", + "plugin_error_uninstall": "Загрузка {{name}} вызвала исключение, указанное выше. Обычно это означает, что плагин требует обновления для новой версии SteamUI. Проверьте наличие обновления или попробуйте его удалить в настройках Decky, в разделе Плагины.", + "plugin_load_error": { + "message": "Ошибка загрузки плагина {{name}}", + "toast": "Ошибка загрузки {{name}}" + }, + "plugin_uninstall": { + "button": "Удалить", + "desc": "Вы уверены, что хотите удалить {{name}}?", + "title": "Удалить {{name}}" + }, + "decky_title": "Decky", + "decky_update_available": "Доступно обновление на {{tag_name}}!", + "error": "Ошибка" + }, + "PluginView": { + "hidden_one": "{{count}} плагин скрыт из списка", + "hidden_few": "{{count}} плагинов скрыт из списка", + "hidden_many": "{{count}} плагинов скрыт из списка" + }, + "FilePickerIndex": { + "files": { + "show_hidden": "Показать скрытые файлы", + "all_files": "Все файлы", + "file_type": "Тип файла" + }, + "filter": { + "created_asce": "Создан (самый старый)", + "modified_asce": "Модифицирован (самый новый)", + "modified_desc": "Модифицирован (самый старый)", + "size_asce": "Размер (самый малый)", + "size_desc": "Размер (самый большой)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "created_desc": "Создан (самый новый)" + }, + "folder": { + "label": "Папка", + "show_more": "Показать больше файлов", + "select": "Использовать этот каталог" + }, + "file": { + "select": "Выберите этот файл" + } + }, + "PluginCard": { + "plugin_install": "Установить", + "plugin_no_desc": "Нет описания.", + "plugin_version_label": "Версия плагина", + "plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck." + }, + "PluginInstallModal": { + "install": { + "button_processing": "Установка", + "title": "Установить {{artifact}}", + "button_idle": "Установить", + "desc": "Вы уверены, что хотите установить {{artifact}} {{version}}?" + }, + "no_hash": "У данного плагина отсутствует хэш, устанавливайте на свой страх и риск.", + "reinstall": { + "title": "Переустановить {{artifact}}", + "desc": "Вы уверены, что хотите переустановить {{artifact}} {{version}}?", + "button_idle": "Переустановить", + "button_processing": "Переустановка" + }, + "update": { + "button_idle": "Обновить", + "button_processing": "Обновление", + "desc": "Вы уверены, что хотите обновить {{artifact}} {{version}}?", + "title": "Обновить {{artifact}}" + } + }, + "PluginListLabel": { + "hidden": "Скрыто из меню быстрого доступа" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Разрешить неаутентифицированный доступ к отладчику CEF всем в вашей сети", + "label": "Разрешить удаленную отладку CEF" + } + }, + "SettingsDeveloperIndex": { + "header": "Другое", + "third_party_plugins": { + "button_install": "Установить", + "label_zip": "Установить плагин из ZIP файла", + "label_url": "Установить плагин из URL", + "button_zip": "Обзор", + "header": "Сторонние плагины", + "label_desc": "Ссылка" + }, + "react_devtools": { + "ip_label": "IP", + "desc": "Позволяет подключиться к компьютеру, на котором работает React DevTools. Изменение этого параметра приведет к перезагрузке Steam. Установите IP-адрес перед включением.", + "label": "Включить React DevTools" + }, + "cef_console": { + "button": "Открыть консоль", + "desc": "Открывает консоль CEF. Полезно только для целей отладки. Настройки здесь потенциально опасны и должны использоваться только в том случае, если вы являетесь разработчиком плагинов или направленны сюда одним из них.", + "label": "CEF Консоль" + }, + "valve_internal": { + "desc1": "Включает внутреннее меню разработчика Valve.", + "label": "Включить Valve Internal", + "desc2": "Ничего не трогайте в этом меню, если не знаете, что оно делает." + } + }, + "SettingsGeneralIndex": { + "beta": { + "header": "Бета программа" + }, + "developer_mode": { + "label": "Режим разработчика" + }, + "other": { + "header": "Другое" + }, + "about": { + "decky_version": "Версия Decky", + "header": "Информация" + }, + "updates": { + "header": "Обновления" + }, + "notifications": { + "decky_updates_label": "Обновление Decky доступно", + "header": "Уведомления", + "plugin_updates_label": "Доступны обновления плагинов" + } + }, + "Store": { + "store_sort": { + "label": "Сортировка", + "label_def": "Последнее обновление(самые новые)" + }, + "store_source": { + "label": "Исходный код", + "desc": "Весь исходный код плагина доступен в репозитории SteamDeckHomebrew/decky-plugin-database на GitHub." + }, + "store_tabs": { + "about": "Информация", + "alph_desc": "По алфавиту (A - Z)", + "title": "Обзор", + "alph_asce": "По алфавиту (Z - A)" + }, + "store_testing_cta": "Пожалуйста, рассмотрите возможность тестирования новых плагинов, чтобы помочь команде Decky Loader!", + "store_contrib": { + "desc": "Если вы хотите внести свой вклад в магазин плагинов Decky, проверьте репозиторий SteamDeckHomebrew/decky-plugin-template на GitHub. Информация о разработке и распространении доступна в README.", + "label": "Помощь проекту" + }, + "store_filter": { + "label": "Фильтр", + "label_def": "Все" + }, + "store_search": { + "label": "Поиск" + }, + "store_testing_warning": { + "label": "Добро пожаловать в тестовый канал магазина", + "desc": "Вы можете использовать этот канал магазина для тестирования новейших версий плагинов. Не забудьте оставить отзыв на GitHub, чтобы плагин можно было обновить для всех пользователей." + } + }, + "StoreSelect": { + "custom_store": { + "label": "Сторонний магазин", + "url_label": "URL" + }, + "store_channel": { + "custom": "Сторонний", + "default": "По-умолчанию", + "label": "Канал магазина", + "testing": "Тестовый" + } + }, + "Updater": { + "decky_updates": "Обновления Decky", + "no_patch_notes_desc": "нет примечаний к патчу для этой версии", + "updates": { + "check_button": "Проверить обновления", + "checking": "Проверка", + "cur_version": "Текущая версия: {{ver}}", + "updating": "Обновление", + "install_button": "Установить обновление", + "label": "Обновления", + "lat_version": "Обновлено: версия {{ver}}", + "reloading": "Перезагрузка" + }, + "patch_notes_desc": "Примечания к патчу" + }, + "FilePickerError": { + "errors": { + "perm_denied": "У вас нет доступа к указанному каталогу.. Пожалуйста, проверьте имеет ли пользователь (deck на Steam Deck) соответствующие права доступа к указанной папке/файлу.", + "file_not_found": "Указан недействительный путь. Пожалуйста, проверьте его и повторите ввод.", + "unknown": "Произошла неизвестная ошибка. Текст ошибки: {{raw_error}}" + } + }, + "DropdownMultiselect": { + "button": { + "back": "Назад" + } + }, + "BranchSelect": { + "update_channel": { + "prerelease": "Предрелиз", + "stable": "Стабильный", + "testing": "Тестовый", + "label": "Канал обновлений" + } + }, + "Developer": { + "5secreload": "Перезагрузка через 5 секунд", + "disabling": "Выключение React DevTools", + "enabling": "Включение React DevTools" + }, + "SettingsIndex": { + "developer_title": "Разработчик", + "general_title": "Общее", + "plugins_title": "Плагины" + }, + "TitleView": { + "decky_store_desc": "Открыть магазин Decky", + "settings_desc": "Открыть настройки Decky" + } +} diff --git a/backend/locales/sq-AL.json b/backend/locales/sq-AL.json new file mode 100644 index 00000000..fe9d7eec --- /dev/null +++ b/backend/locales/sq-AL.json @@ -0,0 +1,131 @@ +{ + "SettingsDeveloperIndex": { + "react_devtools": { + "ip_label": "IP", + "label": "Aktivizo React DevTools" + }, + "third_party_plugins": { + "button_zip": "Kërko", + "header": "Shtesa të Huaj", + "button_install": "Instalo", + "label_desc": "URL", + "label_url": "Instalo Shtes Nga URL", + "label_zip": "Instalo Shtes Nga ZIP" + } + }, + "BranchSelect": { + "update_channel": { + "stable": "Fiksuar", + "label": "Kanali Përditësimet" + } + }, + "FilePickerIndex": { + "folder": { + "select": "Përdore këtë folder" + } + }, + "PluginCard": { + "plugin_install": "Instalo", + "plugin_version_label": "Versioni Shteses" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Instalo", + "button_processing": "Instalohet", + "desc": "Je i sigurt që don ta instalojsh {{artifact}} {{version}}?", + "title": "Instalo {{artifact}}" + }, + "no_hash": "Ky shtesë nuk ka hash, ti e instalon me rrezikun tuaj.", + "reinstall": { + "button_idle": "Riinstalo", + "button_processing": "Riinstalohet", + "desc": "Je i sigurt a don ta riinstalojsh {{artifact}} {{version}}?", + "title": "Riinstalo {{artifact}}" + }, + "update": { + "button_processing": "Përditësohet", + "desc": "Je i sigurt a don ta përditësojsh {{artifact}} {{version}}?", + "title": "Përditëso {{artifact}}" + } + }, + "PluginLoader": { + "decky_title": "Decky", + "plugin_uninstall": { + "title": "Çinstalo {{name}}", + "button": "Çinstalo", + "desc": "Je i sigurt që don ta çinstalojsh {{name}}?" + }, + "error": "Gabim", + "plugin_error_uninstall": "Ju lutem shko nga {{name}} në Decky menu nëse don ta çinstalojsh këtë shtese.", + "plugin_update_one": "", + "plugin_update_other": "" + }, + "PluginListIndex": { + "no_plugin": "Nuk ka shtesa të instaluar!", + "uninstall": "Çinstalo", + "update_all_one": "", + "update_all_other": "" + }, + "SettingsGeneralIndex": { + "other": { + "header": "Të Tjera" + }, + "about": { + "decky_version": "Versioni Decky" + }, + "updates": { + "header": "Përmirësimet" + } + }, + "SettingsIndex": { + "developer_title": "Zhvillues", + "general_title": "Gjeneral" + }, + "Store": { + "store_sort": { + "label": "Rendit" + }, + "store_tabs": { + "title": "Kërko" + }, + "store_contrib": { + "label": "Kontributi" + }, + "store_filter": { + "label": "Filtro", + "label_def": "Të Gjitha" + }, + "store_search": { + "label": "Kërko" + }, + "store_source": { + "label": "Kodin Burimor" + } + }, + "StoreSelect": { + "store_channel": { + "label": "Kanali Dyqanit" + } + }, + "Updater": { + "updates": { + "cur_version": "Versioni e tanishëme: {{ver}}" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "", + "mixed_other": "", + "update_one": "", + "update_other": "", + "reinstall_one": "", + "reinstall_other": "", + "install_one": "", + "install_other": "" + } + }, + "PluginView": { + "hidden_one": "", + "hidden_other": "" + } +} diff --git a/backend/locales/uk-UA.json b/backend/locales/uk-UA.json new file mode 100644 index 00000000..09fbca1b --- /dev/null +++ b/backend/locales/uk-UA.json @@ -0,0 +1,222 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "Передреліз", + "testing": "Тестовий", + "label": "Канал оновлень", + "stable": "Стабільний" + } + }, + "Developer": { + "5secreload": "Перезавантаження за 5 секунд", + "enabling": "Увімкнення React DevTools", + "disabling": "Вимкнення React DevTools" + }, + "FilePickerIndex": { + "folder": { + "select": "Використовувати цю папку" + } + }, + "PluginListLabel": { + "hidden": "Приховано з меню швидкого доступу" + }, + "PluginCard": { + "plugin_full_access": "Цей плагін має повний доступ до вашого Steam Deck.", + "plugin_install": "Встановити", + "plugin_no_desc": "Опис не надано.", + "plugin_version_label": "Версія плагіна" + }, + "PluginInstallModal": { + "install": { + "button_idle": "Встановити", + "button_processing": "Встановлення", + "title": "Встановити {{artifact}}", + "desc": "Ви впевнені, що хочете встановити {{artifact}} {{version}}?" + }, + "reinstall": { + "button_idle": "Перевстановити", + "desc": "Ви впевнені, що хочете перевстановити {{artifact}} {{version}}?", + "title": "Перевстановити {{artifact}}", + "button_processing": "Перевстановлення" + }, + "update": { + "button_idle": "Оновити", + "button_processing": "Оновлення", + "title": "Оновити {{artifact}}", + "desc": "Ви впевнені, що хочете оновити {{artifact}} {{version}}?" + }, + "no_hash": "Цей плагін не має хешу, ви встановлюєте його на власний ризик." + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_one": "Модифікувати 1 плагін", + "mixed_few": "Модифікувати {{count}} плагінів", + "mixed_many": "", + "reinstall_one": "Перевстановити 1 плагін", + "reinstall_few": "Перевстановити {{count}} плагінів", + "reinstall_many": "Перевстановити {{count}} плагінів", + "update_one": "Оновити 1 плагін", + "update_few": "Оновити {{count}} плагінів", + "update_many": "Оновити {{count}} плагінів", + "install_one": "Встановити 1 плагін", + "install_few": "Встановити {{count}} плагінів", + "install_many": "Встановити {{count}} плагінів" + }, + "ok_button": { + "idle": "Підтвердити", + "loading": "Опрацювання" + }, + "description": { + "install": "Встановити {{name}} {{version}}", + "update": "Оновити {{name}} до {{version}}", + "reinstall": "Перевстановити {{name}} {{version}}" + }, + "confirm": "Ви впевнені, що хочете застосувати такі модифікації?" + }, + "PluginListIndex": { + "no_plugin": "Плагінів не встановлено!", + "plugin_actions": "Дії плагінів", + "reinstall": "Перевстановити", + "reload": "Перезавантажити", + "update_to": "Оновити {{name}}", + "show": "Швидкий доступ: Показати", + "hide": "Швидкий доступ: Приховати", + "uninstall": "Видалити", + "update_all_one": "Оновити 1 плагін", + "update_all_few": "Оновити {{count}} плагінів", + "update_all_many": "Оновити {{count}} плагінів" + }, + "PluginLoader": { + "decky_title": "Decky", + "decky_update_available": "Доступне оновлення до {{tag_name}}!", + "error": "Помилка", + "plugin_load_error": { + "message": "Помилка завантаження плагіна {{name}}", + "toast": "Помилка завантаження {{name}}" + }, + "plugin_uninstall": { + "desc": "Ви впевнені, що хочете видалити {{name}}?", + "title": "Видалити {{name}}", + "button": "Видалення" + }, + "plugin_error_uninstall": "Завантаження {{name}} спровокувало помилку показану вище. Зазвичай це означає, що плагін вимагає оновлення до нової версії SteamUI. Перевірте чи таке оновлення доступне або виконайте його видалення у налаштуваннях Decky, у секції Плагіни.", + "plugin_update_one": "Доступне оновлення для 1 плагіна!", + "plugin_update_few": "Доступне оновлення для {{count}} плагінів!", + "plugin_update_many": "Доступне оновлення для {{count}} плагінів!" + }, + "SettingsDeveloperIndex": { + "cef_console": { + "button": "Відкрити консоль", + "label": "CEF-консоль", + "desc": "Відкрити CEF-консоль. Корисно тільки для дебагу. Ця штука потенційно небезпечна і повинна використовувати виключно якщо ви розробник плагіна, або якщо розробник спрямував вас сюди." + }, + "header": "Інше", + "react_devtools": { + "desc": "Вмикає доступ до компʼютера із запущеним React DevTools. Зміна цього налаштування перезавантажить Steam. Вкажіть IP перед увімкненням.", + "label": "Увімкнути React DevTools", + "ip_label": "IP" + }, + "third_party_plugins": { + "button_install": "Встановити", + "header": "Сторонні плагіни", + "label_desc": "URL", + "label_url": "Встановити плагін з URL", + "label_zip": "Встановити плагін з ZIP-файлу", + "button_zip": "Огляд" + }, + "valve_internal": { + "desc1": "Вмикає внутрішнє розробницьке меню Valve.", + "label": "Увімкнути Valve Internal", + "desc2": "Нічого не торкайтесь у цьому меню, якщо не розумієте, що ви робите." + } + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Версія Decky", + "header": "Про нас" + }, + "beta": { + "header": "Участь у Beta" + }, + "developer_mode": { + "label": "Розробницький режим" + }, + "other": { + "header": "Інше" + }, + "updates": { + "header": "Оновлення" + } + }, + "SettingsIndex": { + "developer_title": "Розробник", + "general_title": "Загальне", + "plugins_title": "Плагіни" + }, + "Store": { + "store_contrib": { + "label": "Зробити внесок", + "desc": "Якщо ви бажаєте додати щось у Decky Plugin Store, завітайте у репозиторій SteamDeckHomebrew/decky-plugin-template на GitHub. Інформація про розробку та поширення доступна у README." + }, + "store_filter": { + "label": "Фільтр", + "label_def": "Усе" + }, + "store_search": { + "label": "Пошук" + }, + "store_sort": { + "label": "Сортування", + "label_def": "Востаннє оновлені (Найновіші)" + }, + "store_source": { + "label": "Вихідний код", + "desc": "Код усіх плагінів доступний у репозиторії SteamDeckHomebrew/decky-plugin-database на GitHub." + }, + "store_tabs": { + "about": "Інформація", + "alph_asce": "За алфавітом (Z до A)", + "alph_desc": "За алфавітом (A до Z)", + "title": "Огляд" + }, + "store_testing_cta": "Розгляньте можливість тестування нових плагінів, щоб допомогти команді Decky Loader!" + }, + "StoreSelect": { + "custom_store": { + "label": "Власний магазин", + "url_label": "URL" + }, + "store_channel": { + "custom": "Власний", + "default": "За замовчуванням", + "testing": "Тестування", + "label": "Канал магазину" + } + }, + "Updater": { + "decky_updates": "Оновлення Decky", + "no_patch_notes_desc": "Немає нотаток до цієї версії", + "patch_notes_desc": "Перелік змін", + "updates": { + "checking": "Перевірка", + "cur_version": "Поточна версія: {{ver}}", + "install_button": "Встановити оновлення", + "label": "Оновлення", + "reloading": "Перезавантаження", + "updating": "Оновлення", + "check_button": "Перевірити оновлення", + "lat_version": "Оновлено: використовується {{ver}}" + } + }, + "PluginView": { + "hidden_one": "{{count}} плагін приховано з цього списку", + "hidden_few": "{{count}} плагінів приховано з цього списку", + "hidden_many": "{{count}} плагінів приховано з цього списку" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "Дозволити доступ до CEF-дебагера без аутентифікації для будь-кого у вашій мережі", + "label": "Дозволити віддалений CEF-дебагінг" + } + } +} diff --git a/backend/locales/zh-CN.json b/backend/locales/zh-CN.json new file mode 100644 index 00000000..d9d12aa0 --- /dev/null +++ b/backend/locales/zh-CN.json @@ -0,0 +1,253 @@ +{ + "BranchSelect": { + "update_channel": { + "prerelease": "发布候选", + "stable": "稳定", + "testing": "测试", + "label": "更新通道" + } + }, + "Developer": { + "5secreload": "5 秒钟后重新加载", + "disabling": "正在禁用 React DevTools", + "enabling": "正在启用 React DevTools" + }, + "FilePickerIndex": { + "folder": { + "select": "使用这个文件夹", + "label": "文件夹", + "show_more": "显示更多文件" + }, + "filter": { + "created_asce": "创建日期(最旧)", + "created_desc": "创建日期(最新)", + "modified_asce": "修改日期(最旧)", + "modified_desc": "修改日期(最新)", + "name_asce": "字母降序", + "name_desc": "字母升序", + "size_asce": "大小(最小)", + "size_desc": "大小(最大)" + }, + "files": { + "all_files": "全部文件", + "file_type": "文件类型", + "show_hidden": "显示隐藏文件" + }, + "file": { + "select": "选择此文件" + } + }, + "PluginCard": { + "plugin_install": "安装", + "plugin_no_desc": "无描述提供。", + "plugin_version_label": "插件版本", + "plugin_full_access": "此插件可以完全访问你的 Steam Deck。" + }, + "PluginInstallModal": { + "install": { + "button_idle": "安装", + "button_processing": "安装中", + "desc": "你确定要安装 {{artifact}} {{version}} 吗?", + "title": "安装 {{artifact}}" + }, + "reinstall": { + "button_idle": "重新安装", + "button_processing": "正在重新安装", + "desc": "你确定要重新安装 {{artifact}} {{version}} 吗?", + "title": "重新安装 {{artifact}}" + }, + "update": { + "button_idle": "更新", + "button_processing": "正在更新", + "desc": "你确定要更新 {{artifact}} {{version}} 吗?", + "title": "更新 {{artifact}}" + }, + "no_hash": "此插件没有哈希校验值,你需要自行承担安装风险。" + }, + "PluginListIndex": { + "no_plugin": "没有安装插件!", + "plugin_actions": "插件操作", + "reinstall": "重新安装", + "reload": "重新加载", + "uninstall": "卸载", + "update_to": "更新 {{name}}", + "update_all_other": "更新 {{count}} 个插件", + "show": "在快速访问菜单中显示", + "hide": "在快速访问菜单中隐藏" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "错误", + "plugin_error_uninstall": "加载 {{name}} 时引起了上述异常。这通常意味着插件需要更新以适应 SteamUI 的新版本。请检查插件是否有更新,或在 Decky 设置中的插件部分将其移除。", + "plugin_load_error": { + "message": "加载插件 {{name}} 错误", + "toast": "加载插件 {{name}} 发生了错误" + }, + "plugin_uninstall": { + "button": "卸载", + "title": "卸载 {{name}}", + "desc": "你确定要卸载 {{name}} 吗?" + }, + "decky_update_available": "新版本 {{tag_name}} 可用!", + "plugin_update_other": "{{count}} 个插件有更新!" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "允许你网络中的任何人无需身份验证即可访问CEF调试器", + "label": "允许远程访问CEF调试" + } + }, + "SettingsDeveloperIndex": { + "react_devtools": { + "ip_label": "IP", + "label": "启用 React DevTools", + "desc": "允许连接到运行着 React DevTools 的计算机,更改此设置将重新加载Steam,请在启用前设置IP地址。" + }, + "third_party_plugins": { + "button_install": "安装", + "button_zip": "浏览文件", + "header": "第三方插件", + "label_desc": "URL", + "label_url": "从 URL 安装插件", + "label_zip": "从 ZIP 压缩文件安装插件" + }, + "valve_internal": { + "desc1": "启用 Valve 内部开发者菜单。", + "desc2": "除非你知道你在干什么,否则请不要修改此菜单中的任何内容。", + "label": "启用 Valve 内部开发者" + }, + "cef_console": { + "button": "打开控制台", + "label": "CEF 控制台", + "desc": "打开 CEF 控制台。仅在调试目的下使用。这列选项均有风险,请仅在您是插件开发者或是在插件开发者指导时访问使用。" + }, + "header": "其他" + }, + "SettingsGeneralIndex": { + "about": { + "decky_version": "Decky 版本", + "header": "关于" + }, + "beta": { + "header": "参与测试" + }, + "developer_mode": { + "label": "开发者模式" + }, + "other": { + "header": "其他" + }, + "updates": { + "header": "更新" + }, + "notifications": { + "header": "通知", + "decky_updates_label": "Decky 更新可用", + "plugin_updates_label": "插件更新可用" + } + }, + "SettingsIndex": { + "developer_title": "开发者", + "general_title": "通用", + "plugins_title": "插件" + }, + "Store": { + "store_contrib": { + "label": "贡献", + "desc": "如果你想要提交你的插件到 Decky 插件商店,请访问 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 存储库,关于开发和分发的相关信息,请查看 README 文件。" + }, + "store_filter": { + "label": "过滤器", + "label_def": "全部" + }, + "store_search": { + "label": "搜索" + }, + "store_sort": { + "label": "排序", + "label_def": "最后更新 (最新)" + }, + "store_source": { + "label": "源代码", + "desc": "所有插件的源代码都可以在 GitHub 上的 SteamDeckHomebrew/decky-plugin-database 存储库中获得。" + }, + "store_tabs": { + "about": "关于", + "alph_asce": "字母排序 (Z 到 A)", + "alph_desc": "字母排序 (A 到 Z)", + "title": "浏览" + }, + "store_testing_cta": "请考虑测试新插件以帮助 Decky Loader 团队!", + "store_testing_warning": { + "desc": "你可以使用该商店频道以体验最新版本的插件。 请在插件 Github 页面留言以使插件可以正式面向所有用户。", + "label": "欢迎来到商店测试频道" + } + }, + "StoreSelect": { + "store_channel": { + "default": "默认", + "label": "商店通道", + "testing": "测试", + "custom": "自定义" + }, + "custom_store": { + "label": "自定义商店", + "url_label": "URL" + } + }, + "Updater": { + "decky_updates": "Decky 更新", + "no_patch_notes_desc": "此版本没有补丁说明", + "patch_notes_desc": "补丁说明", + "updates": { + "check_button": "检查更新", + "checking": "检查中", + "cur_version": "当前版本: {{ver}}", + "install_button": "安装更新", + "label": "更新", + "lat_version": "已是最新版本: {{ver}} 运行中", + "reloading": "重新加载中", + "updating": "更新中" + } + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_other": "更改 {{count}} 个插件", + "update_other": "更新 {{count}} 个插件", + "reinstall_other": "重装 {{count}} 个插件", + "install_other": "安装 {{count}} 个插件" + }, + "ok_button": { + "idle": "确认", + "loading": "工作中" + }, + "confirm": "确定要进行以下修改吗?", + "description": { + "install": "安装 {{name}} {{version}}", + "update": "更新 {{name}} to {{version}}", + "reinstall": "重装 {{name}} {{version}}" + } + }, + "PluginListLabel": { + "hidden": "在快速访问菜单中已隐藏" + }, + "PluginView": { + "hidden_other": "此列表隐藏了 {{count}} 个插件" + }, + "DropdownMultiselect": { + "button": { + "back": "返回" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "指定路径无效。请检查并输入正确的路径。", + "unknown": "发生了一个未知错误。原始错误为:{{raw_error}}", + "perm_denied": "你没有访问特定目录的权限。请检查你的用户(Steam Deck 中的 deck 账户)有着相对应的权限以访问特定的文件夹或文件。" + } + }, + "TitleView": { + "decky_store_desc": "打开 Decky 商店", + "settings_desc": "打开 Decky 设置" + } +} diff --git a/backend/locales/zh-TW.json b/backend/locales/zh-TW.json new file mode 100644 index 00000000..2891aa9c --- /dev/null +++ b/backend/locales/zh-TW.json @@ -0,0 +1,245 @@ +{ + "BranchSelect": { + "update_channel": { + "testing": "測試版", + "label": "更新頻道", + "prerelease": "預發佈", + "stable": "穩定版" + } + }, + "Developer": { + "5secreload": "5 秒後重新載入", + "disabling": "正在停用 React DevTools", + "enabling": "正在啟用 React DevTools" + }, + "FilePickerIndex": { + "folder": { + "select": "使用此資料夾", + "show_more": "顯示更多檔案", + "label": "資料夾" + }, + "filter": { + "modified_asce": "修改日期(舊到新)", + "created_desc": "建立日期(新到舊)", + "modified_desc": "修改日期(新到舊)", + "name_desc": "子母排序(A到Z)", + "name_asce": "子母排序(Z到A)", + "size_asce": "檔案大小(小到大)", + "size_desc": "檔案大小(大到小)", + "created_asce": "建立日期(舊到新)" + }, + "file": { + "select": "選擇此檔案" + }, + "files": { + "all_files": "所有檔案", + "file_type": "檔案類型", + "show_hidden": "顯示隱藏檔" + } + }, + "PluginCard": { + "plugin_install": "安裝", + "plugin_no_desc": "未提示描述。", + "plugin_version_label": "外掛程式版本", + "plugin_full_access": "此外掛程式擁有您的 Steam Deck 的完整存取權。" + }, + "PluginInstallModal": { + "install": { + "button_idle": "安裝", + "button_processing": "正在安裝", + "title": "安裝 {{artifact}}", + "desc": "您確定要安裝 {{artifact}} {{version}} 嗎?" + }, + "reinstall": { + "button_idle": "重新安裝", + "button_processing": "正在重新安裝", + "desc": "您確定要重新安裝 {{artifact}} {{version}} 嗎?", + "title": "重新安裝 {{artifact}}" + }, + "update": { + "button_idle": "更新", + "button_processing": "正在更新", + "desc": "您確定要更新 {{artifact}} {{version}} 嗎?", + "title": "更新 {{artifact}}" + }, + "no_hash": "此外掛程式沒有提供 hash 驗證,安裝可能有風險。" + }, + "PluginListIndex": { + "no_plugin": "未安裝外掛程式!", + "plugin_actions": "外掛程式操作", + "uninstall": "解除安裝", + "update_to": "更新到 {{name}}", + "reinstall": "重新安裝", + "reload": "重新載入", + "show": "快速存取:顯示", + "hide": "快速存取:隱藏", + "update_all_other": "更新 {{count}} 個外掛程式" + }, + "PluginLoader": { + "decky_title": "Decky", + "error": "錯誤", + "plugin_error_uninstall": "載入 {{name}} 導致上述異常。這通常意味著該外掛程式需要針對新版本的 SteamUI 進行更新。在 Decky 設定中檢查是否存在更新,或評估刪除此外掛程式。", + "plugin_load_error": { + "message": "載入外掛程式 {{name}} 發生錯誤", + "toast": "{{name}} 載入出錯" + }, + "plugin_uninstall": { + "button": "解除安裝", + "title": "解除安裝 {{name}}", + "desc": "您確定要解除安裝 {{name}} 嗎?" + }, + "decky_update_available": "可更新至版本 {{tag_name}}!", + "plugin_update_other": "可更新 {{count}} 個外掛程式!" + }, + "RemoteDebugging": { + "remote_cef": { + "desc": "允許您的網路中的任何人未經認證地存取 CEF 偵錯器", + "label": "允許 CEF 遠端偵錯" + } + }, + "SettingsDeveloperIndex": { + "third_party_plugins": { + "button_zip": "開啟", + "label_desc": "網址", + "label_url": "從網址安裝外掛程式", + "label_zip": "從 ZIP 檔案安裝外掛程式", + "button_install": "安裝", + "header": "第三方外掛程式" + }, + "valve_internal": { + "desc2": "除非您知道它的作用,否則不要碰這個選單中的任何東西。", + "desc1": "啟用 Valve 內建開發人員選單。", + "label": "啟用 Valve 內建" + }, + "react_devtools": { + "desc": "啟用與執行 React DevTools 的電腦的連接。改變這個設定將重新載入 Steam。啟用前必須設定 IP 位址。", + "ip_label": "IP", + "label": "啟用 React DevTools" + }, + "header": "其他", + "cef_console": { + "button": "開啟控制台", + "label": "CEF 控制台", + "desc": "開啟 CEF 控制台。僅用於偵錯。這裡的東西有潛在的風險,只有當您是一個外掛程式開發者或者被外掛程式開發者引導到這裡時,才應該使用。" + } + }, + "SettingsGeneralIndex": { + "about": { + "header": "關於", + "decky_version": "Decky 版本" + }, + "beta": { + "header": "參與測試" + }, + "developer_mode": { + "label": "開發人員模式" + }, + "other": { + "header": "其他" + }, + "updates": { + "header": "更新" + }, + "notifications": { + "decky_updates_label": "Decky 可更新", + "header": "通知", + "plugin_updates_label": "外掛程式有更新" + } + }, + "SettingsIndex": { + "developer_title": "開發人員", + "general_title": "一般", + "plugins_title": "外掛程式" + }, + "Store": { + "store_contrib": { + "label": "貢獻", + "desc": "如果您想為 Decky 外掛程式商店做貢獻,請查看 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 儲存庫。README 中提供了有關開發和發佈的資訊。" + }, + "store_filter": { + "label": "過濾", + "label_def": "全部" + }, + "store_search": { + "label": "搜尋" + }, + "store_sort": { + "label": "排序", + "label_def": "最後更新 (最新)" + }, + "store_source": { + "label": "原始碼", + "desc": "所有外掛程式原始碼可以在 GitHub 的 SteamDeckHomebrew/decky-plugin-database 儲存庫查看。" + }, + "store_tabs": { + "about": "關於", + "alph_asce": "依字母排序 (Z 到 A)", + "alph_desc": "依字母排序 (A 到 Z)", + "title": "瀏覽" + }, + "store_testing_cta": "請考慮測試新的外掛程式來幫助 Decky Loader 團隊!" + }, + "StoreSelect": { + "custom_store": { + "label": "自訂商店", + "url_label": "網址" + }, + "store_channel": { + "custom": "自訂", + "default": "預設", + "label": "商店頻道", + "testing": "測試" + } + }, + "Updater": { + "decky_updates": "Decky 更新", + "no_patch_notes_desc": "這個版本沒有更新日誌", + "patch_notes_desc": "更新日誌", + "updates": { + "checking": "正在檢查", + "install_button": "安裝更新", + "label": "更新", + "lat_version": "已是最新:執行 {{ver}}", + "reloading": "正在重新載入", + "check_button": "檢查更新", + "cur_version": "目前版本:{{ver}}", + "updating": "正在更新" + } + }, + "PluginView": { + "hidden_other": "{{count}} 個外掛程式已隱藏" + }, + "PluginListLabel": { + "hidden": "已從快速存取選單中移除" + }, + "MultiplePluginsInstallModal": { + "title": { + "mixed_other": "修改 {{count}} 個外掛程式", + "update_other": "更新 {{count}} 個外掛程式", + "reinstall_other": "重新安裝 {{count}} 個外掛程式", + "install_other": "安裝 {{count}} 個外掛程式" + }, + "ok_button": { + "idle": "確定", + "loading": "執行中" + }, + "confirm": "您確定要進行以下的修改嗎?", + "description": { + "install": "安裝 {{name}} {{version}}", + "update": "更新 {{name}} 到 {{version}}", + "reinstall": "重新安裝 {{name}} {{version}}" + } + }, + "FilePickerError": { + "errors": { + "perm_denied": "您沒有瀏覽此目錄的權限。請檢查您的使用者(Steam Deck 中的 deck 帳號)有權限瀏覽特定的資料夾或檔案。", + "unknown": "發生未知錯誤。錯誤詳細資料:{{raw_error}}", + "file_not_found": "指定路徑無效。請檢查並輸入正確路徑。" + } + }, + "DropdownMultiselect": { + "button": { + "back": "返回" + } + } +} diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 00000000..326a924c --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,5 @@ +aiohttp==3.8.4 +aiohttp-jinja2==1.5.1 +aiohttp_cors==0.7.0 +watchdog==2.1.7 +certifi==2023.7.22 diff --git a/backend/src/locales/bg-BG.json b/backend/src/locales/bg-BG.json deleted file mode 100644 index b9c4d803..00000000 --- a/backend/src/locales/bg-BG.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "stable": "Стабилен", - "testing": "Тестване", - "label": "Канал за обновления", - "prerelease": "Предварителни издания" - } - }, - "Developer": { - "5secreload": "Презареждане след 5 секунди", - "disabling": "Изключване на React DevTools", - "enabling": "Включване на React DevTools" - }, - "DropdownMultiselect": { - "button": { - "back": "Назад" - } - }, - "FilePickerError": { - "errors": { - "unknown": "Възникна неизвестна грешка. Грешката в суров вид е: {{raw_error}}", - "file_not_found": "Посоченият път е неправилен. Проверете го и го въведете правилно.", - "perm_denied": "Нямате достъп до посочената папка. Проверете дали потребителят (deck на Steam Deck) има съответните правомощия за достъп до посочената папка/файл." - } - }, - "FilePickerIndex": { - "file": { - "select": "Избиране на този файл" - }, - "files": { - "all_files": "Всички файлове", - "file_type": "Файлов тип", - "show_hidden": "Показване на скритите файлове" - }, - "filter": { - "created_asce": "Дата на създаване (първо най-старите)", - "created_desc": "Дата на създаване (първо най-новите)", - "modified_asce": "Дата на промяна (първо най-старите)", - "modified_desc": "Дата на промяна (първо най-новите)", - "name_asce": "Я-А", - "name_desc": "А-Я", - "size_asce": "Размер (първо най-малките)", - "size_desc": "Размер (първо най-големите)" - }, - "folder": { - "label": "Папка", - "show_more": "Показване на още файлове", - "select": "Използване на тази папка" - } - }, - "MultiplePluginsInstallModal": { - "description": { - "install": "Инсталиране на {{name}} {{version}}", - "reinstall": "Преинсталиране на {{name}} {{version}}", - "update": "Обновяване на {{name}} до {{version}}" - }, - "ok_button": { - "idle": "Потвърждаване", - "loading": "В процес на работа" - }, - "title": { - "mixed_one": "Промяна на {{count}} добавка", - "mixed_other": "Промяна на {{count}} добавки", - "update_one": "Обновяване на 1 добавка", - "update_other": "Обновяване на {{count}} добавки", - "install_one": "Инсталиране на 1 добавка", - "install_other": "Инсталиране на {{count}} добавки", - "reinstall_one": "Преинсталиране на 1 добавка", - "reinstall_other": "Преинсталиране на {{count}} добавки" - }, - "confirm": "Наистина ли искате да направите следните промени?" - }, - "PluginCard": { - "plugin_full_access": "Тази добавка има пълен достъп до Вашия Steam Deck.", - "plugin_install": "Инсталиране", - "plugin_no_desc": "Няма описание.", - "plugin_version_label": "Версия на добавката" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Инсталиране", - "desc": "Наистина ли искате да инсталирате {{artifact}} {{version}}?", - "title": "Инсталиране на {{artifact}}", - "button_processing": "Инсталиране" - }, - "reinstall": { - "button_idle": "Преинсталиране", - "button_processing": "Преинсталиране", - "desc": "Наистина ли искате да преинсталирате {{artifact}} {{version}}?", - "title": "Преинсталиране на {{artifact}}" - }, - "update": { - "button_idle": "Обновяване", - "title": "Обновяване на {{artifact}}", - "button_processing": "Обновяване", - "desc": "Наистина ли искате да обновите {{artifact}} {{version}}?" - }, - "no_hash": "Тази добавка няма хеш. Инсталирате я на свой собствен риск." - }, - "PluginListIndex": { - "hide": "Бърз достъп: Скриване", - "no_plugin": "Няма инсталирани добавки!", - "plugin_actions": "Действия с добавката", - "reinstall": "Преинсталиране", - "uninstall": "Деинсталиране", - "update_to": "Обновяване до {{name}}", - "reload": "Презареждане", - "show": "Бърз достъп: Показване", - "update_all_one": "Обновяване на 1 добавка", - "update_all_other": "Обновяване на {{count}} добавки" - }, - "PluginListLabel": { - "hidden": "Скрито от менюто за бърз достъп" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Грешка", - "plugin_load_error": { - "message": "Грешка при зареждането на добавката {{name}}", - "toast": "Грешка при зареждането на {{name}}" - }, - "plugin_uninstall": { - "button": "Деинсталиране", - "desc": "Наистина ли искате да деинсталирате {{name}}?", - "title": "Деинсталиране на {{name}}" - }, - "plugin_update_one": "Има налично обновление за 1 добавка!", - "plugin_update_other": "Има налични обновления за {{count}} добавки!", - "decky_update_available": "Има налично обновление до {{tag_name}}!", - "plugin_error_uninstall": "Зареждането на {{name}} предизвика грешка, както се вижда по-горе. Това обикновено означава, че добавката изисква обновяване на новата версия на SteamUI. Проверете дали има обновление или изберете да я премахнете в настройките на Decky, в раздела с добавките." - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Разрешаване на достъп без удостоверяване до дебъгера на CEF на всеки от Вашата мрежа", - "label": "Разрешаване на отдалеченото дебъгване на CEF" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Отваряне на конзолата", - "label": "Конзола на CEF", - "desc": "Отваря конзолата на CEF. Това има смисъл единствено за дебъгване. Нещата тук може да са опасни и трябва да бъдат използвани само ако Вие сте разработчик на добавка, или получавате насоки от такъв." - }, - "header": "Други", - "react_devtools": { - "ip_label": "IP", - "label": "Включване на React DevTools", - "desc": "Включва свързването към компютър, на който работи React DevTools. Промяната на тази настройка ще презареди Steam. Задайте IP адреса преди да включите това." - }, - "third_party_plugins": { - "button_install": "Инсталиране", - "button_zip": "Разглеждане", - "header": "Добавки от външен източник", - "label_desc": "Адрес", - "label_zip": "Инсталиране на добавка от файл ZIP", - "label_url": "Инсталиране на добавка от адрес в Интернет" - }, - "valve_internal": { - "desc2": "Не пипайте нищо в това меню, освен ако не знаете какво правите.", - "label": "Включване на вътрешното меню на Valve", - "desc1": "Включва вътрешното меню за разработчици на Valve." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Версия на Decky", - "header": "Относно" - }, - "developer_mode": { - "label": "Режим за разработчици" - }, - "notifications": { - "decky_updates_label": "Има налично обновление на Decky", - "header": "Известия", - "plugin_updates_label": "Има налични обновления на добавките" - }, - "other": { - "header": "Други" - }, - "updates": { - "header": "Обновления" - }, - "beta": { - "header": "Участие в бета-версии" - } - }, - "SettingsIndex": { - "developer_title": "Разработчик", - "general_title": "Общи", - "plugins_title": "Добавки" - }, - "Store": { - "store_contrib": { - "label": "Допринасяне", - "desc": "Ако искате да допринесете към магазина за добавки на Decky, разгледайте хранилището SteamDeckHomebrew/decky-plugin-template в GitHub. Може да намерите информация относно разработката и разпространението във файла README." - }, - "store_filter": { - "label": "Филтър", - "label_def": "Всички" - }, - "store_search": { - "label": "Търсене" - }, - "store_sort": { - "label": "Подредба", - "label_def": "Последно обновление (първо най-новите)" - }, - "store_source": { - "label": "Изходен код", - "desc": "Целият изходен код е наличен в хранилището SteamDeckHomebrew/decky-plugin-database в GitHub." - }, - "store_tabs": { - "about": "Относно", - "alph_asce": "По азбучен ред (Я -> А)", - "alph_desc": "По азбучен ред (А -> Я)", - "title": "Разглеждане" - }, - "store_testing_cta": "Помислете дали искате да тествате новите добавки, за да помогнете на екипа на Decky Loader!" - }, - "StoreSelect": { - "custom_store": { - "label": "Персонализиран магазин", - "url_label": "Адрес" - }, - "store_channel": { - "custom": "Персонализиран", - "default": "По подразбиране", - "label": "Канал за магазина", - "testing": "Тестване" - } - }, - "Updater": { - "decky_updates": "Обновления на Decky", - "patch_notes_desc": "Бележки за промените", - "updates": { - "check_button": "Проверка за обновления", - "checking": "Проверяване", - "cur_version": "Текуща версия: {{ver}}", - "label": "Обновления", - "lat_version": "Използвате най-новата версия: {{ver}}", - "reloading": "Презареждане", - "updating": "Обновяване", - "install_button": "Инсталиране на обновлението" - }, - "no_patch_notes_desc": "няма бележки за промените в тази версия" - }, - "PluginView": { - "hidden_one": "1 добавка е скрита от този списък", - "hidden_other": "{{count}} добавки са скрити от този списък" - } -} diff --git a/backend/src/locales/cs-CZ.json b/backend/src/locales/cs-CZ.json deleted file mode 100644 index 74b7230c..00000000 --- a/backend/src/locales/cs-CZ.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "Aktualizační kanál", - "prerelease": "Předběžná vydání", - "stable": "Stabilní", - "testing": "Testování" - } - }, - "Developer": { - "disabling": "Vypínám React DevTools", - "enabling": "Zapínám React DevTools", - "5secreload": "Znovu načtení za 5 vteřin" - }, - "FilePickerIndex": { - "folder": { - "select": "Použít tuto složku", - "label": "Složka", - "show_more": "Zobrazit více souborů" - }, - "filter": { - "created_asce": "Vytvořeno (Nejstarší)", - "created_desc": "Vytvořeno (Nejnovější)", - "modified_asce": "Upraveno (Nejstarší)", - "modified_desc": "Upraveno (Nejnovější)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Velikost (Nejmenší)", - "size_desc": "Velikost (Největší)" - }, - "files": { - "show_hidden": "Zobrazit skryté soubory", - "all_files": "Všechny soubory", - "file_type": "Typ souboru" - }, - "file": { - "select": "Vybrat tento soubor" - } - }, - "PluginView": { - "hidden_one": "1 plugin je v tomto seznamu skrytý", - "hidden_few": "{{count}} pluginů je v tomto seznamu skryto", - "hidden_other": "{{count}} pluginů je v tomto seznamu skryto" - }, - "PluginListLabel": { - "hidden": "Skryto z nabídky rychlého přístupu" - }, - "PluginCard": { - "plugin_full_access": "Tento plugin má plný přístup k vašemu Steam Decku.", - "plugin_install": "Instalovat", - "plugin_no_desc": "Nebyl uveden žádný popis.", - "plugin_version_label": "Verze pluginu" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalovat", - "button_processing": "Instalování", - "title": "Instalovat {{artifact}}", - "desc": "Jste si jisti, že chcete nainstalovat {{artifact}} {{version}}?" - }, - "no_hash": "Tento plugin nemá hash, instalujete jej na vlastní nebezpečí.", - "reinstall": { - "button_idle": "Přeinstalovat", - "button_processing": "Přeinstalování", - "title": "Přeinstalovat {{artifact}}", - "desc": "Jste si jisti, že chcete přeinstalovat {{artifact}} {{version}}?" - }, - "update": { - "button_idle": "Aktualizovat", - "button_processing": "Aktualizování", - "desc": "Jste si jisti, že chcete aktualizovat {{artifact}} {{version}}?", - "title": "Aktualizovat {{artifact}}" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Upravit {{count}} plugin", - "mixed_few": "Upravit {{count}} pluginů", - "mixed_other": "Upravit {{count}} pluginů", - "reinstall_one": "Přeinstalovat 1 plugin", - "reinstall_few": "Přeinstalovat {{count}} pluginů", - "reinstall_other": "Přeinstalovat {{count}} pluginů", - "install_one": "Instalovat 1 plugin", - "install_few": "Instalovat {{count}} pluginů", - "install_other": "Instalovat {{count}} pluginů", - "update_one": "Aktualizovat 1 plugin", - "update_few": "Aktualizovat {{count}} pluginů", - "update_other": "Aktualizovat {{count}} pluginů" - }, - "ok_button": { - "idle": "Potvrdit", - "loading": "Probíhá" - }, - "description": { - "install": "Instalovat {{name}} {{version}}", - "update": "Aktualizovat {{name}} na {{version}}", - "reinstall": "Přeinstalovat {{name}} {{version}}" - }, - "confirm": "Jste si jisti, že chcete udělat následující úpravy?" - }, - "PluginListIndex": { - "no_plugin": "Nejsou nainstalovány žádné pluginy!", - "plugin_actions": "Akce pluginu", - "reinstall": "Přeinstalovat", - "reload": "Znovu načíst", - "uninstall": "Odinstalovat", - "update_to": "Aktualizovat na {{name}}", - "show": "Rychlý přístup: Zobrazit", - "hide": "Rychlý přístup: Skrýt", - "update_all_one": "Aktualizovat 1 plugin", - "update_all_few": "Aktualizovat {{count}} pluginů", - "update_all_other": "Aktualizovat {{count}} pluginů" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Aktualizace na {{tag_name}} dostupná!", - "error": "Chyba", - "plugin_load_error": { - "message": "Chyba při načítání pluginu {{name}}", - "toast": "Chyba při načítání {{name}}" - }, - "plugin_uninstall": { - "button": "Odinstalovat", - "desc": "Opravdu chcete odinstalovat {{name}}?", - "title": "Odinstalovat {{name}}" - }, - "plugin_update_one": "Je dostupná aktualizace pro 1 plugin!", - "plugin_update_few": "Jsou dostupné aktualizace pro {{count}} pluginů!", - "plugin_update_other": "Jsou dostupné aktualizace pro {{count}} pluginů!", - "plugin_error_uninstall": "Načítání {{name}} způsobilo chybu uvedenou výše. To obvykle znamená, že plugin vyžaduje aktualizaci SteamUI. Zkontrolujte, zda je aktualizace k dispozici, nebo zvažte odstranění pluginu v nastavení Decky v sekci Pluginy." - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Otevřít konzoli", - "label": "CEF konzole", - "desc": "Otevře CEF konzoli. Užitečné pouze pro účely ladění. Věci zde jsou potenciálně nebezpečné a měly by být používány pouze v případě, že jste vývojář pluginů, nebo vás sem nějaký nasměroval." - }, - "header": "Ostatní", - "react_devtools": { - "desc": "Umožňuje připojení k počítači, na kterém běží React DevTools. Změnou tohoto nastavení se znovu načte Steam. Před povolením nastavte IP adresu.", - "ip_label": "IP adresa", - "label": "Zapnout React DevTools" - }, - "third_party_plugins": { - "button_install": "Instalovat", - "button_zip": "Procházet", - "header": "Pluginy třetí strany", - "label_desc": "URL", - "label_url": "Instalovat plugin z URL", - "label_zip": "Instalovat plugin ze ZIP souboru" - }, - "valve_internal": { - "desc1": "Zapíná interní vývojářské menu Valve.", - "desc2": "Nedotýkejte se ničeho v této nabídce, pokud nevíte, co děláte.", - "label": "Zapnout Valve Internal" - } - }, - "RemoteDebugging": { - "remote_cef": { - "label": "Povolit vzdálené CEF ladění", - "desc": "Umožní neověřený přístup k CEF ladění komukoli ve vaší síti" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky verze", - "header": "O Decky" - }, - "beta": { - "header": "Účast v betě" - }, - "developer_mode": { - "label": "Vývojářský režim" - }, - "other": { - "header": "Ostatní" - }, - "updates": { - "header": "Aktualizace" - }, - "notifications": { - "decky_updates_label": "Dostupná aktualizace Decky", - "header": "Notifikace", - "plugin_updates_label": "Dostupná aktualizace pluginu" - } - }, - "SettingsIndex": { - "developer_title": "Vývojář", - "general_title": "Obecné", - "plugins_title": "Pluginy" - }, - "Store": { - "store_contrib": { - "label": "Přispívání", - "desc": "Pokud byste chtěli přispět do obchodu Decky Plugin Store, podívejte se na repozitář SteamDeckHomebrew/decky-plugin-template na GitHubu. Informace o vývoji a distribuci jsou k dispozici v README." - }, - "store_filter": { - "label": "Filtr", - "label_def": "Vše" - }, - "store_search": { - "label": "Hledat" - }, - "store_sort": { - "label": "Seřadit", - "label_def": "Naposledy aktualizováno (Nejnovější)" - }, - "store_source": { - "desc": "Veškerý zdrojový kód pluginu je dostupný v repozitáři SteamDeckHomebrew/decky-plugin-database na GitHubu.", - "label": "Zdrojový kód" - }, - "store_tabs": { - "about": "O Decky Plugin Store", - "alph_asce": "Abecedně (Z do A)", - "alph_desc": "Abecedně (A do Z)", - "title": "Procházet" - }, - "store_testing_cta": "Zvažte prosím testování nových pluginů, pomůžete tím týmu Decky Loader!", - "store_testing_warning": { - "desc": "Tento kanál obchodu můžete použít k testování nejnovějších verzí pluginů. Nezapomeňte zanechat zpětnou vazbu na GitHubu, aby bylo možné plugin aktualizovat pro všechny uživatele.", - "label": "Vítejte na testovacím kanálu obchodu" - } - }, - "StoreSelect": { - "custom_store": { - "label": "Vlastní obchod", - "url_label": "URL" - }, - "store_channel": { - "custom": "Vlastní", - "default": "Výchozí", - "label": "Kanál obchodu", - "testing": "Testování" - } - }, - "Updater": { - "updates": { - "lat_version": "Aktuální: běží na verzi {{ver}}", - "reloading": "Znovu načítání", - "updating": "Aktualizování", - "check_button": "Zkontrolovat aktualizace", - "checking": "Kontrolování", - "cur_version": "Aktuální verze: {{ver}}", - "install_button": "Instalovat aktualizaci", - "label": "Aktualizace" - }, - "decky_updates": "Aktualizace Decky", - "patch_notes_desc": "Poznámky k verzi", - "no_patch_notes_desc": "žádné poznámky pro tuto verzi" - }, - "DropdownMultiselect": { - "button": { - "back": "Zpět" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "Zadaná cesta není platná. Zkontrolujte ji a zadejte znovu správně.", - "unknown": "Nastala neznámá chyba. Nezpracovaná chyba je: {{raw_error}}", - "perm_denied": "Nemáte přístup k zadanému adresáři. Zkontrolujte, zda jako uživatel (deck na Steam Decku) máte odpovídající oprávnění pro přístup k dané složce/souboru." - } - }, - "TitleView": { - "settings_desc": "Otevřít nastavení Decky", - "decky_store_desc": "Otevřít obchod Decky" - } -} diff --git a/backend/src/locales/de-DE.json b/backend/src/locales/de-DE.json deleted file mode 100644 index 4ded8703..00000000 --- a/backend/src/locales/de-DE.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "Updatekanal", - "prerelease": "Vorabveröffentlichung", - "stable": "Standard", - "testing": "Test" - } - }, - "Developer": { - "disabling": "Deaktiviere", - "enabling": "Aktiviere", - "5secreload": "Neu laden in 5 Sekunden" - }, - "FilePickerIndex": { - "folder": { - "select": "Diesen Ordner verwenden" - } - }, - "PluginCard": { - "plugin_install": "Installieren", - "plugin_no_desc": "Keine Beschreibung angegeben.", - "plugin_version_label": "Erweiterungs Version", - "plugin_full_access": "Diese Erweiterung hat uneingeschränkten Zugriff auf dein Steam Deck." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Installieren", - "button_processing": "Wird installiert", - "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} installieren willst?", - "title": "Installiere {{artifact}}" - }, - "reinstall": { - "button_idle": "Neu installieren", - "button_processing": "Wird neu installiert", - "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} neu installieren willst?", - "title": "Neu installation {{artifact}}" - }, - "update": { - "button_idle": "Aktualisieren", - "button_processing": "Wird aktualisiert", - "title": "Aktualisiere {{artifact}}", - "desc": "Bist du dir sicher, dass du {{artifact}} {{version}} aktualisieren willst?" - }, - "no_hash": "Diese Erweiterung besitzt keine Prüfsumme, Installation auf eigene Gefahr." - }, - "PluginListIndex": { - "no_plugin": "Keine Erweiterungen installiert!", - "plugin_actions": "Erweiterungs Aktionen", - "reinstall": "Neu installieren", - "reload": "Neu laden", - "uninstall": "Deinstallieren", - "update_to": "Aktualisieren zu {{name}}", - "update_all_one": "", - "update_all_other": "" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Eine neue Version ({{tag_name}}) ist verfügbar!", - "error": "Fehler", - "plugin_load_error": { - "toast": "Fehler beim Laden von {{name}}", - "message": "Fehler beim Laden von {{name}}" - }, - "plugin_uninstall": { - "button": "Deinstallieren", - "desc": "Bist du dir sicher, dass du {{name}} deinstallieren willst?", - "title": "Deinstalliere {{name}}" - }, - "plugin_error_uninstall": "Das Laden von {{name}} hat einen Fehler verursacht. Dies bedeutet normalerweise, dass die Erweiterung ein Update für die neue Version von SteamUI benötigt. Prüfe in den Decky-Einstellungen im Bereich Erweiterungen, ob ein Update vorhanden ist.", - "plugin_update_one": "1 Erweiterung kann aktualisiert werden!", - "plugin_update_other": "{{count}} Erweiterungen können aktualisiert werden!" - }, - "RemoteDebugging": { - "remote_cef": { - "label": "Remote CEF Debugging Zugriff", - "desc": "Erlaubt jedem aus dem Neztwerk unautorisierten Zugriff auf den CEF Debugger" - } - }, - "SettingsDeveloperIndex": { - "header": "Sonstiges", - "react_devtools": { - "ip_label": "IP", - "label": "Aktiviere React DevTools", - "desc": "Erlaubt die Verbindung mit einem anderen Rechner, auf welchem React DevTools läuft. Eine Änderung startet Steam neu. Die IP Adresse muss vor Aktivierung ausgefüllt sein." - }, - "third_party_plugins": { - "button_zip": "Durchsuchen", - "header": "Erweiterungen von Drittanbietern", - "label_desc": "URL", - "label_zip": "Installiere Erweiterung via ZIP Datei", - "button_install": "Installieren", - "label_url": "Installiere Erweiterung via URL" - }, - "valve_internal": { - "desc2": "Fasse in diesem Menü nichts an, es sei denn, du weißt was du tust.", - "label": "Aktiviere Valve-internes Menü", - "desc1": "Aktiviert das Valve-interne Entwickler Menü." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky Version", - "header": "Über" - }, - "beta": { - "header": "Beta Teilnahme" - }, - "developer_mode": { - "label": "Entwickleroptionen" - }, - "other": { - "header": "Sonstiges" - }, - "updates": { - "header": "Aktualisierungen" - } - }, - "SettingsIndex": { - "developer_title": "Entwickler", - "general_title": "Allgemein", - "plugins_title": "Erweiterungen" - }, - "Store": { - "store_contrib": { - "label": "Mitwirken", - "desc": "Wenn du Erweiterungen im Decky Store veröffentlichen willst, besuche die SteamDeckHomebrew/decky-plugin-template Repository auf GitHub. Informationen rund um Entwicklung und Veröffentlichung findest du in der README." - }, - "store_filter": { - "label": "Filter", - "label_def": "Alle" - }, - "store_search": { - "label": "Suche" - }, - "store_sort": { - "label": "Sortierung", - "label_def": "Zuletzt aktualisiert" - }, - "store_source": { - "desc": "Jeder Erweiterungs Quellcode ist in der SteamDeckHomebrew/decky-plugin-database Repository auf GitHub verfügbar.", - "label": "Quellcode" - }, - "store_tabs": { - "about": "Über", - "alph_asce": "Alphabetisch (Z zu A)", - "alph_desc": "Alphabetisch (A zu Z)", - "title": "Durchstöbern" - }, - "store_testing_cta": "Unterstütze das Decky Loader Team mit dem Testen von neuen Erweiterungen!" - }, - "StoreSelect": { - "custom_store": { - "label": "Benutzerdefinierter Marktplatz", - "url_label": "URL" - }, - "store_channel": { - "custom": "Benutzerdefiniert", - "default": "Standard", - "label": "Marktplatz Kanal", - "testing": "Test" - } - }, - "Updater": { - "decky_updates": "Decky Aktualisierungen", - "patch_notes_desc": "Patchnotizen", - "updates": { - "check_button": "Auf Aktualisierungen prüfen", - "checking": "Wird überprüft", - "cur_version": "Aktualle Version: {{ver}}", - "install_button": "Aktualisierung installieren", - "label": "Aktualisierungen", - "lat_version": "{{ver}} ist die aktuellste", - "reloading": "Lade neu", - "updating": "Aktualisiere" - }, - "no_patch_notes_desc": "Für diese Version gibt es keine Patchnotizen" - }, - "PluginView": { - "hidden_one": "", - "hidden_other": "" - }, - "MultiplePluginsInstallModal": { - "title": { - "install_one": "", - "install_other": "", - "mixed_one": "", - "mixed_other": "", - "update_one": "", - "update_other": "", - "reinstall_one": "", - "reinstall_other": "" - } - } -} diff --git a/backend/src/locales/el-GR.json b/backend/src/locales/el-GR.json deleted file mode 100644 index 62562935..00000000 --- a/backend/src/locales/el-GR.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "SettingsDeveloperIndex": { - "react_devtools": { - "desc": "Επιτρέπει την σύνδεση με υπολογιστή που τρέχει React DevTools. Η αλλαγή αυτής της ρύθμισης θα προκαλέσει επαναφόρτωση του Steam. Ωρίστε την διεύθυνση IP πριν την ενεργοποιήσετε.", - "ip_label": "IP", - "label": "Ενεργοποίηση React DevTools" - }, - "third_party_plugins": { - "button_install": "Εγκατάσταση", - "button_zip": "Περιήγηση", - "header": "Επεκτάσεις τρίτων", - "label_desc": "URL", - "label_url": "Εγκατάσταση επέκτασης απο URL", - "label_zip": "Εγκατάσταση επέκτασης από αρχείο ZIP" - }, - "valve_internal": { - "desc1": "Ενεργοποιεί το μενού προγραμματιστή της Valve.", - "desc2": "Μην αγγίξετε τίποτα σε αυτό το μενού εκτός και αν ξέρετε τι κάνει.", - "label": "Ενεργοποιήση εσωτερικού μενού Valve" - }, - "cef_console": { - "button": "Άνοιγμα Κονσόλας", - "desc": "Ανοίγει την Κονσόλα CEF. Χρήσιμο μόνο για εντοπισμό σφαλμάτων. Τα πράγματα εδώ είναι δυνητικά επικίνδυνα και θα πρέπει να χρησιμοποιηθεί μόνο εάν είστε προγραμματιστής επεκτάσεων, ή κατευθυνθήκατε εδώ από έναν προγραμματιστή.", - "label": "Κονσόλα CEF" - }, - "header": "Άλλα" - }, - "BranchSelect": { - "update_channel": { - "prerelease": "Προ-κυκλοφορία", - "stable": "Σταθερό", - "label": "Κανάλι ενημερώσεων", - "testing": "Δοκιμαστικό" - } - }, - "Developer": { - "5secreload": "Γίνεται επαναφόρτωση σε 5 δευτερόλεπτα", - "disabling": "Γίνεται απενεργοποίηση των React DevTools", - "enabling": "Γίνεται ενεργοποίηση των React DevTools" - }, - "PluginCard": { - "plugin_no_desc": "Δεν υπάρχει περιγραφή.", - "plugin_full_access": "Αυτή η επέκταση έχει πλήρη πρόσβαση στο Steam Deck σας.", - "plugin_install": "Εγκατάσταση", - "plugin_version_label": "Έκδοση επέκτασης" - }, - "PluginInstallModal": { - "install": { - "desc": "Σίγουρα θέλετε να εγκαταστήσετε το {{artifact}}{{version}};", - "button_idle": "Εγκατάσταση", - "button_processing": "Γίνεται εγκατάσταση", - "title": "Εγκατάσταση {{artifact}}" - }, - "no_hash": "Αυτή η επέκταση δεν έχει υπογραφή, την εγκαθηστάτε με δικό σας ρίσκο.", - "reinstall": { - "button_idle": "Επανεγκατάσταση", - "button_processing": "Γίνεται επανεγκατάσταση", - "desc": "Σίγουρα θέλετε να επανεγκαταστήσετε το {{artifact}}{{version}};", - "title": "Επανεγκατάσταση {{artifact}}" - }, - "update": { - "button_idle": "Ενημέρωση", - "desc": "Σίγουρα θέλετε να ενημερώσετε το {{artifact}} {{version}};", - "title": "Ενημέρωση {{artifact}}", - "button_processing": "Γίνεται ενημέρωση" - } - }, - "PluginListIndex": { - "no_plugin": "Δεν υπάρχουν εγκατεστημένες επεκτάσεις!", - "plugin_actions": "Ενέργειες επεκτάσεων", - "reinstall": "Επανεγκατάσταση", - "reload": "Επαναφόρτωση", - "uninstall": "Απεγκατάσταση", - "update_to": "Ενημέρωση σε {{name}}", - "update_all_one": "Ενημέρωση 1 επέκτασης", - "update_all_other": "Ενημέρωση {{count}} επεκτάσεων", - "show": "Γρήγορη πρόσβαση: Εμφάνιση", - "hide": "Γρήγορη πρόσβαση: Απόκρυψη" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Ενημέρωση σε {{tag_name}} διαθέσιμη!", - "error": "Σφάλμα", - "plugin_error_uninstall": "Η φόρτωση του {{name}} προκάλεσε το παραπάνω σφάλμα. Αυτό συνήθως σημαίνει ότι η επέκταση απαιτεί ενημέρωση για τη νέα έκδοση του SteamUI. Ελέγξτε εάν υπάρχει ενημέρωση ή αξιολογήστε την απεγκαταστήσετε της επέκτασης στις ρυθμίσεις του Decky, στην ενότητα Επεκτάσεις.", - "plugin_load_error": { - "message": "Σφάλμα στη φόρτωση της επέκτασης {{name}}", - "toast": "Σφάλμα φόρτωσης {{name}}" - }, - "plugin_uninstall": { - "button": "Απεγκατάσταση", - "desc": "Σίγουρα θέλετε να απεγκαταστήσετε το {{name}};", - "title": "Απεγκατάσταση {{name}}" - }, - "plugin_update_one": "Διαθέσιμη ενημέρωση για 1 επέκταση!", - "plugin_update_other": "Διαθέσιμες ενημερώσεις για {{count}} επεκτάσεις!" - }, - "RemoteDebugging": { - "remote_cef": { - "label": "Να επιτρέπεται η απομακρυσμένη πρόσβαση στον CEF debugger", - "desc": "Να επιτρέπεται η ανεξέλεγκτη πρόσβαση στον CEF debugger σε οποιονδήποτε στο τοπικό δίκτυο" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Έκδοση Decky", - "header": "Σχετικά" - }, - "developer_mode": { - "label": "Λειτουργία προγραμματιστή" - }, - "other": { - "header": "Άλλα" - }, - "updates": { - "header": "Ενημερώσεις" - }, - "beta": { - "header": "Συμμετοχή στη Beta" - }, - "notifications": { - "decky_updates_label": "Διαθέσιμη ενημέρωση του Decky", - "header": "Ειδοποιήσεις", - "plugin_updates_label": "Διαθέσιμες ενημερώσεις επεκτάσεων" - } - }, - "SettingsIndex": { - "plugins_title": "Επεκτάσεις", - "developer_title": "Προγραμματιστής", - "general_title": "Γενικά" - }, - "Store": { - "store_contrib": { - "label": "Συνεισφέροντας", - "desc": "Αν θέλετε να συνεισφέρετε στο κατάστημα επεκτάσεων του Decky, τσεκάρετε το SteamDeckHomebrew/decky-plugin-template repository στο GitHub. Πληροφορίες σχετικά με τη δημιουργία και τη διανομή επεκτάσεων είναι διαθέσιμες στο README." - }, - "store_filter": { - "label": "Φίλτρο", - "label_def": "Όλα" - }, - "store_search": { - "label": "Αναζήτηση" - }, - "store_sort": { - "label": "Ταξινόμηση", - "label_def": "Τελευταία ενημέρωση (Νεότερα)" - }, - "store_source": { - "desc": "Ο πηγαίος κώδικας όλων των επεκτάσεων είναι διαθέσιμος στο SteamDeckHomebrew/decky-plugin-database repository στο GitHub.", - "label": "Πηγαίος κώδικας" - }, - "store_tabs": { - "about": "Σχετικά", - "alph_asce": "Αλφαβητικά (Ζ σε Α)", - "alph_desc": "Αλφαβητικά (Α σε Ζ)", - "title": "Περιήγηση" - }, - "store_testing_cta": "Παρακαλώ σκεφτείτε να τεστάρετε νέες επεκτάσεις για να βοηθήσετε την ομάδα του Decky Loader!", - "store_testing_warning": { - "desc": "Μπορείτε να χρησιμοποιήσετε αυτό το κανάλι του καταστήματος για να δοκιμάσετε τις νεότερες εκδόσεις των επεκτάσεων. Φροντίστε να αφήσετε σχόλια στο GitHub, ώστε να βοηθήσετε στην ενημέρωση της εκάστοτε επέκταση για όλους τους χρήστες.", - "label": "Καλώς ήρθατε στο Δοκιμαστικό Κανάλι τους Καταστήματος" - } - }, - "StoreSelect": { - "custom_store": { - "label": "Προσαρμοσμένο κατάστημα", - "url_label": "URL" - }, - "store_channel": { - "custom": "Προσαρμοσμένο", - "default": "Προεπιλεγμένο", - "label": "Κανάλι καταστήματος", - "testing": "Δοκιμαστικό" - } - }, - "Updater": { - "no_patch_notes_desc": "Κανένα ενημερωτικό σημείωμα για αυτή την έκδοση", - "patch_notes_desc": "Σημειώσεις ενημέρωσης", - "updates": { - "check_button": "Έλεγχος για ενημερώσεις", - "checking": "Γίνεται έλεγχος", - "cur_version": "Τρέχουσα έκδοση: {{ver}}", - "install_button": "Εγκατάσταση ενημέρωσης", - "label": "Ενημερώσεις", - "updating": "Γίνεται ενημέρωση", - "lat_version": "Ενημερωμένο: τρέχουσα έκδοση {{ver}}", - "reloading": "Γίνεται επαναφόρτωση" - }, - "decky_updates": "Ενημερώσεις Decky" - }, - "FilePickerIndex": { - "folder": { - "select": "Χρησιμοποιήστε αυτό το φάκελο", - "label": "Φάκελος", - "show_more": "Εμφάνιση περισσότερων αρχείων" - }, - "filter": { - "modified_asce": "Τροποποιήθηκε (Παλαιότερο)", - "modified_desc": "Τροποποιήθηκε (Νεότερο)", - "created_desc": "Δημιουργήθηκε (Νεότερο)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "created_asce": "Δημιουργήθηκε (Παλαιότερο)", - "size_asce": "Μέγεθος (Μικρότερο)", - "size_desc": "Μέγεθος (Μεγαλύτερο)" - }, - "file": { - "select": "Επιλογή αυτού του αρχείου" - }, - "files": { - "show_hidden": "Εμφάνιση Κρυφών Αρχείων", - "all_files": "Όλα Τα Αρχεία", - "file_type": "Τύπος Αρχείου" - } - }, - "PluginView": { - "hidden_one": "1 επέκταση είναι κρυμμένη σε αυτήν τη λίστα", - "hidden_other": "{{count}} επεκτάσεις είναι κρυμμένες σε αυτήν τη λίστα" - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Τροποποίηση 1 επέκτασης", - "mixed_other": "Τροποποίηση {{count}} επεκτάσεων", - "update_one": "Ενημέρωση 1 επέκτασης", - "update_other": "Ενημέρωση {{count}} επεκτάσεων", - "reinstall_one": "Επανεγκατάσταση 1 επέκτασης", - "reinstall_other": "Επανεγκατάσταση {{count}} επεκτάσεων", - "install_one": "Εγκατάσταση 1 επέκτασης", - "install_other": "Εγκατάσταση {{count}} επεκτάσεων" - }, - "confirm": "Είστε βέβαιοι ότι θέλετε να κάνετε τις ακόλουθες τροποποιήσεις;", - "description": { - "reinstall": "Επανεγκατάσταση {{name}} {{version}}", - "update": "Ενημέρωση {{name}} to {{version}}", - "install": "Εγκατάσταση {{name}} {{version}}" - }, - "ok_button": { - "idle": "Επιβεβαίωση", - "loading": "Φόρτωση" - } - }, - "PluginListLabel": { - "hidden": "Κρυφό στο μενού γρήγορης πρόσβασης" - }, - "TitleView": { - "settings_desc": "Άνοιγμα Ρυθμίσεων Decky", - "decky_store_desc": "Άνοιγμα Καταστήματος Decky" - }, - "DropdownMultiselect": { - "button": { - "back": "Πίσω" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "Η καθορισμένη διαδρομή δεν είναι έγκυρη. Παρακαλούμε ελέγξτε τη και εισάγετέ τη ξανά σωστά.", - "perm_denied": "Δεν έχετε πρόσβαση στην καθορισμένη διαδρομή. Ελέγξτε εάν ο χρήστης σας (deck στο Steam Deck) έχει τα αντίστοιχα δικαιώματα πρόσβασης στον καθορισμένο φάκελο/αρχείο.", - "unknown": "Παρουσιάστηκε άγνωστο σφάλμα. Το σφάλμα είναι: {{raw_error}}" - } - } -} diff --git a/backend/src/locales/en-US.json b/backend/src/locales/en-US.json deleted file mode 100644 index 7845ae4f..00000000 --- a/backend/src/locales/en-US.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "Update Channel", - "prerelease": "Prerelease", - "stable": "Stable", - "testing": "Testing" - } - }, - "Developer": { - "5secreload": "Reloading in 5 seconds", - "disabling": "Disabling React DevTools", - "enabling": "Enabling React DevTools" - }, - "DropdownMultiselect": { - "button": { - "back": "Back" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "The path specified is not valid. Please check it and reenter it correctly.", - "perm_denied": "You do not have access to the specified directory. Please check if your user (deck on Steam Deck) has the corresponding permission to access the specified folder/file.", - "unknown": "An unknown error occurred. The raw error is: {{raw_error}}" - } - }, - "FilePickerIndex": { - "file": { - "select": "Select this file" - }, - "files": { - "all_files": "All Files", - "file_type": "File Type", - "show_hidden": "Show Hidden Files" - }, - "filter": { - "created_asce": "Created (Oldest)", - "created_desc": "Created (Newest)", - "modified_asce": "Modified (Oldest)", - "modified_desc": "Modified (Newest)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Size (Smallest)", - "size_desc": "Size (Largest)" - }, - "folder": { - "label": "Folder", - "select": "Use this folder", - "show_more": "Show more files" - } - }, - "MultiplePluginsInstallModal": { - "confirm": "Are you sure you want to make the following modifications?", - "description": { - "install": "Install {{name}} {{version}}", - "reinstall": "Reinstall {{name}} {{version}}", - "update": "Update {{name}} to {{version}}" - }, - "ok_button": { - "idle": "Confirm", - "loading": "Working" - }, - "title": { - "install_one": "Install 1 plugin", - "install_other": "Install {{count}} plugins", - "mixed_one": "Modify {{count}} plugin", - "mixed_other": "Modify {{count}} plugins", - "reinstall_one": "Reinstall 1 plugin", - "reinstall_other": "Reinstall {{count}} plugins", - "update_one": "Update 1 plugin", - "update_other": "Update {{count}} plugins" - } - }, - "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 {{artifact}}" - } - }, - "PluginListIndex": { - "hide": "Quick access: Hide", - "no_plugin": "No plugins installed!", - "plugin_actions": "Plugin Actions", - "reinstall": "Reinstall", - "reload": "Reload", - "show": "Quick access: Show", - "uninstall": "Uninstall", - "update_all_one": "Update 1 plugin", - "update_all_other": "Update {{count}} plugins", - "update_to": "Update to {{name}}" - }, - "PluginListLabel": { - "hidden": "Hidden from the quick access menu" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Update to {{tag_name}} available!", - "error": "Error", - "plugin_error_uninstall": "Loading {{name}} caused an exception as shown above. This usually means that the plugin requires an update for the new version of SteamUI. Check if an update is present or evaluate its removal in the Decky settings, in the Plugins section.", - "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!" - }, - "PluginView": { - "hidden_one": "1 plugin is hidden from this list", - "hidden_other": "{{count}} plugins are hidden from this list" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Allow unauthenticated access to the CEF debugger to anyone in your network", - "label": "Allow Remote CEF Debugging" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Open Console", - "desc": "Opens the CEF Console. Only useful for debugging purposes. Stuff here is potentially dangerous and should only be used if you are a plugin dev, or are directed here by one.", - "label": "CEF Console" - }, - "header": "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" - }, - "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": { - "label": "Developer mode" - }, - "notifications": { - "decky_updates_label": "Decky update available", - "header": "Notifications", - "plugin_updates_label": "Plugin updates available" - }, - "other": { - "header": "Other" - }, - "updates": { - "header": "Updates" - } - }, - "SettingsIndex": { - "developer_title": "Developer", - "general_title": "General", - "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!", - "store_testing_warning": { - "desc": "You can use this store channel to test bleeding-edge plugin versions. Be sure to leave feedback on GitHub so the plugin can be updated for all users.", - "label": "Welcome to the Testing Store Channel" - } - }, - "StoreSelect": { - "custom_store": { - "label": "Custom Store", - "url_label": "URL" - }, - "store_channel": { - "custom": "Custom", - "default": "Default", - "label": "Store Channel", - "testing": "Testing" - } - }, - "TitleView": { - "decky_store_desc": "Open Decky Store", - "settings_desc": "Open Decky Settings" - }, - "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" - } - } -} diff --git a/backend/src/locales/es-ES.json b/backend/src/locales/es-ES.json deleted file mode 100644 index 6c47eb06..00000000 --- a/backend/src/locales/es-ES.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "SettingsDeveloperIndex": { - "third_party_plugins": { - "button_install": "Instalar", - "button_zip": "Navegar", - "label_desc": "URL", - "label_url": "Instalar plugin desde URL", - "label_zip": "Instalar plugin desde archivo ZIP", - "header": "Plugins de terceros" - }, - "valve_internal": { - "desc2": "No toques nada en este menú a menos que sepas lo que haces.", - "label": "Activar menú interno de Valve", - "desc1": "Activa el menú interno de desarrollo de Valve." - }, - "cef_console": { - "button": "Abrir consola", - "label": "Consola CEF", - "desc": "Abre la consola del CEF. Solamente es útil para propósitos de depuración. Las cosas que hagas aquí pueden ser potencialmente peligrosas y solo se debería usar si eres un desarrollador de plugins, o uno te ha dirigido aquí." - }, - "react_devtools": { - "ip_label": "IP", - "label": "Activar DevTools de React", - "desc": "Permite la conexión a un ordenador ejecutando las DevTools de React. Cambiar este ajuste recargará Steam. Configura la dirección IP antes de activarlo." - }, - "header": "Otros" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalar", - "button_processing": "Instalando", - "title": "Instalar {{artifact}}", - "desc": "¿Estás seguro de que quieres instalar {{artifact}} {{version}}?" - }, - "reinstall": { - "button_idle": "Reinstalar", - "button_processing": "Reinstalando", - "desc": "¿Estás seguro de que quieres reinstalar {{artifact}} {{version}}?", - "title": "Reinstalar {{artifact}}" - }, - "update": { - "button_processing": "Actualizando", - "button_idle": "Actualizar", - "desc": "¿Estás seguro de que quieres actualizar {{artifact}} {{version}}?", - "title": "Actualizar {{artifact}}" - }, - "no_hash": "Este plugin no tiene un hash, lo estás instalando bajo tu propia responsabilidad." - }, - "Developer": { - "disabling": "Desactivando DevTools de React", - "enabling": "Activando DevTools de React", - "5secreload": "Recargando en 5 segundos" - }, - "BranchSelect": { - "update_channel": { - "prerelease": "Prelanzamiento", - "stable": "Estable", - "label": "Canal de actualización", - "testing": "Pruebas" - } - }, - "PluginCard": { - "plugin_full_access": "Este plugin tiene acceso completo a su Steam Deck.", - "plugin_install": "Instalar", - "plugin_version_label": "Versión de Plugin", - "plugin_no_desc": "No se proporcionó una descripción." - }, - "FilePickerIndex": { - "folder": { - "select": "Usar esta carpeta" - } - }, - "PluginListIndex": { - "uninstall": "Desinstalar", - "reinstall": "Reinstalar", - "reload": "Recargar", - "plugin_actions": "Acciones de plugin", - "no_plugin": "¡No hay plugins instalados!", - "update_all_one": "Actualizar 1 plugin", - "update_all_many": "Actualizar {{count}} plugins", - "update_all_other": "Actualizar {{count}} plugins", - "update_to": "Actualizar a {{name}}" - }, - "PluginLoader": { - "error": "Error", - "plugin_uninstall": { - "button": "Desinstalar", - "desc": "¿Estás seguro de que quieres desinstalar {{name}}?", - "title": "Desinstalar {{name}}" - }, - "decky_title": "Decky", - "plugin_update_one": "¡Actualización disponible para 1 plugin!", - "plugin_update_many": "¡Actualizaciones disponibles para {{count}} plugins!", - "plugin_update_other": "¡Actualizaciones disponibles para {{count}} plugins!", - "decky_update_available": "¡Actualización {{tag_name}} disponible!", - "plugin_load_error": { - "message": "Se ha producido un error al cargar el plugin {{name}}", - "toast": "Se ha producido un error al cargar {{name}}" - }, - "plugin_error_uninstall": "Al cargar {{name}} se ha producido una excepción como se muestra arriba. Esto suele significar que el plugin requiere una actualización para la nueva versión de SteamUI. Comprueba si hay una actualización disponible o valora eliminarlo en los ajustes de Decky, en la sección Plugins." - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Permitir acceso no autenticado al CEF debugger a cualquier persona en su red", - "label": "Permitir depuración remota del CEF" - } - }, - "SettingsGeneralIndex": { - "updates": { - "header": "Actualizaciones" - }, - "about": { - "header": "Acerca de", - "decky_version": "Versión de Decky" - }, - "developer_mode": { - "label": "Modo desarrollador" - }, - "beta": { - "header": "Participación en la beta" - }, - "other": { - "header": "Otros" - } - }, - "SettingsIndex": { - "developer_title": "Desarrollador", - "general_title": "General", - "plugins_title": "Plugins" - }, - "Store": { - "store_search": { - "label": "Buscar" - }, - "store_sort": { - "label": "Ordenar", - "label_def": "Actualizado por última vez (Nuevos)" - }, - "store_contrib": { - "desc": "Si desea contribuir a la tienda de plugins de Decky, mira el repositorio SteamDeckHomebrew/decky-plugin-template en GitHub. Hay información acerca del desarrollo y distribución en el archivo README.", - "label": "Contribuyendo" - }, - "store_tabs": { - "about": "Información", - "title": "Navegar", - "alph_asce": "Alfabéticamente (Z-A)", - "alph_desc": "Alfabéticamente (A-Z)" - }, - "store_testing_cta": "¡Por favor considera probar plugins nuevos para ayudar al equipo de Decky Loader!", - "store_source": { - "desc": "El código fuente de los plugins está disponible en el repositiorio SteamDeckHomebrew/decky-plugin-database en GitHub.", - "label": "Código fuente" - }, - "store_filter": { - "label_def": "Todos", - "label": "Filtrar" - } - }, - "Updater": { - "updates": { - "reloading": "Recargando", - "updating": "Actualizando", - "checking": "Buscando", - "check_button": "Buscar actualizaciones", - "install_button": "Instalar actualización", - "label": "Actualizaciones", - "lat_version": "Actualizado: ejecutando {{ver}}", - "cur_version": "Versión actual: {{ver}}" - }, - "decky_updates": "Actualizaciones de Decky", - "no_patch_notes_desc": "No hay notas de parche para esta versión", - "patch_notes_desc": "Notas de parche" - }, - "MultiplePluginsInstallModal": { - "title": { - "reinstall_one": "Reinstalar 1 plugin", - "reinstall_many": "Reinstalar {{count}} plugins", - "reinstall_other": "Reinstalar {{count}} plugins", - "update_one": "Actualizar 1 plugin", - "update_many": "Actualizar {{count}} plugins", - "update_other": "Actualizar {{count}} plugins", - "mixed_one": "Modificar 1 plugin", - "mixed_many": "Modificar {{count}} plugins", - "mixed_other": "Modificar {{count}} plugins", - "install_one": "Instalar 1 plugin", - "install_many": "Instalar {{count}} plugins", - "install_other": "Instalar {{count}} plugins" - }, - "ok_button": { - "idle": "Confirmar", - "loading": "Trabajando" - }, - "confirm": "¿Estás seguro de que quieres hacer las siguientes modificaciones?", - "description": { - "install": "Instalar {{name}} {{version}}", - "update": "Actualizar {{name}} a {{version}}", - "reinstall": "Reinstalar {{name}} {{version}}" - } - }, - "StoreSelect": { - "custom_store": { - "url_label": "URL", - "label": "Tienda personalizada" - }, - "store_channel": { - "custom": "Personalizada", - "default": "Por defecto", - "label": "Canál de la tienda", - "testing": "Pruebas" - } - }, - "PluginView": { - "hidden_one": "", - "hidden_many": "", - "hidden_other": "" - } -} diff --git a/backend/src/locales/fi-FI.json b/backend/src/locales/fi-FI.json deleted file mode 100644 index b0ff1309..00000000 --- a/backend/src/locales/fi-FI.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "Esijulkaisu", - "testing": "Testiversio", - "stable": "Vakaa versio", - "label": "Päivityskanava" - } - }, - "Developer": { - "5secreload": "Uudelleenladataan 5 sekunin kuluttua", - "disabling": "Poistetaan React DevTools käytöstä", - "enabling": "Otetaan React DevTools käyttöön" - }, - "FilePickerError": { - "errors": { - "perm_denied": "Sinulla ei ole käyttöoikeutta määritettyyn hakemistoon. Tarkista, onko käyttäjälläsi (käyttäjä 'deck' Steam Deckillä) vastaavat oikeudet käyttää määritettyä kansiota/tiedostoa.", - "unknown": "Tapahtui tuntematon virhe. Raaka virhe on: {{raw_error}}", - "file_not_found": "Määritetty polku ei kelpaa. Tarkista se ja kirjoita se uudelleen oikein." - } - }, - "FilePickerIndex": { - "file": { - "select": "Valitse tämä tiedosto" - }, - "files": { - "all_files": "Kaikki tiedostot", - "file_type": "Tiedostotyyppi", - "show_hidden": "Näytä piilotetut tiedostot" - }, - "filter": { - "created_desc": "Luotu (uusin ensin)", - "modified_asce": "Muokattu (vanhin)", - "modified_desc": "Muokattu (uusin)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Koko (pienin ensin)", - "size_desc": "Koko (suurin ensin)", - "created_asce": "Luotu (vanhin ensin)" - }, - "folder": { - "label": "Kansio", - "select": "Käytä tätä kansiota", - "show_more": "Näytä lisää tiedostoja" - } - }, - "MultiplePluginsInstallModal": { - "confirm": "Haluatko varmasti tehdä seuraavat muutokset?", - "description": { - "reinstall": "Uudelleenasenna {{name}} {{version}}", - "update": "Päivitä {{name}} versioon {{version}}", - "install": "Asenna {{name}} {{version}}" - }, - "ok_button": { - "idle": "Vahvista", - "loading": "Ladataan" - }, - "title": { - "install_one": "Asenna yksi laajennus", - "install_other": "Asenna {{count}} laajennusta", - "update_one": "Päivitä yksi laajennus", - "update_other": "Päivitä {{count}} laajennusta", - "mixed_one": "Muuta yhtä laajennusta", - "mixed_other": "Muuta {{count}} laajennusta", - "reinstall_one": "Uudelleenasenna yksi laajennus", - "reinstall_other": "Uudelleenasenna {{count}} laajennusta" - } - }, - "PluginCard": { - "plugin_install": "Asenna", - "plugin_no_desc": "Ei kuvausta.", - "plugin_version_label": "Laajennuksen versio", - "plugin_full_access": "Tällä laajennuksella on täysi pääsy Steam Deckkiisi." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Asenna", - "button_processing": "Asennetaan", - "desc": "Haluatko varmasti asentaa {{artifact}} {{version}}?", - "title": "Asenna {{artifact}}" - }, - "no_hash": "Tällä laajennuksella ei ole hashia, asennat sen omalla vastuullasi.", - "reinstall": { - "button_idle": "Uudelleenasenna", - "button_processing": "Uudelleenasennetaan", - "desc": "Haluatko varmasti uudelleenasentaa {{artifact}} {{version}}?", - "title": "Uudelleenasenna {{artifact}}" - }, - "update": { - "button_idle": "Päivitä", - "button_processing": "Päivitetään", - "desc": "Haluatko varmasti päivittää {{artifact}} {{version}}?", - "title": "Päivitä {{artifact}}" - } - }, - "DropdownMultiselect": { - "button": { - "back": "Takaisin" - } - }, - "PluginListIndex": { - "no_plugin": "Ei asennettuja laajennuksia!", - "plugin_actions": "Laajennustoiminnot", - "reinstall": "Uudelleenasenna", - "reload": "Lataa uudelleen", - "uninstall": "Poista asennus", - "update_all_one": "Päivitä yksi laajennus", - "update_all_other": "Päivitä {{count}} laajennusta", - "update_to": "Päivitä versioon {{name}}", - "hide": "Pikavalikko: Piilota", - "show": "Pikavalikko: Näytä" - }, - "PluginListLabel": { - "hidden": "Piilotettu pikavalikosta" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Päivitys versioon {{tag_name}} on saatavilla!", - "error": "Virhe", - "plugin_load_error": { - "message": "Virhe ladattaessa {{name}}-laajennusta", - "toast": "Virhe ladattaessa {{name}}" - }, - "plugin_uninstall": { - "button": "Poista asennus", - "desc": "Haluatko varmasti poistaa {{name}} asennuksen?", - "title": "Poista {{name}}" - }, - "plugin_update_one": "Päivityksiä saatavilla yhdelle laajennukselle!", - "plugin_update_other": "Päivityksiä saatavilla {{count}} laajennukselle!", - "plugin_error_uninstall": "{{name}} lataaminen aiheutti yllä olevan poikkeuksen. Tämä tarkoittaa yleensä sitä, että laajennus vaatii päivityksen uudelle SteamUI-versiolle. Tarkista, onko päivitystä saatavilla, tai harkitse laajennuksen poistoa Decky-asetuksista, laajennukset-osiosta." - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Salli todentamaton pääsy CEF-debuggeriin kenelle tahansa verkossasi", - "label": "Salli CEF-etädebugaus" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Avaa konsoli", - "desc": "Avaa CEF-konsolin. Hyödyllinen vain debugaustarkoituksiin. Täällä olevat jutut ovat mahdollisesti vaarallisia, ja niitä tulisi käyttää vain, jos olet laajennuksen kehittäjä tai jos kehittäjä on ohjannut sinut tänne.", - "label": "CEF-konsoli" - }, - "header": "Muu", - "react_devtools": { - "desc": "Mahdollistaa yhteyden tietokoneeseen, jossa on käytössä React DevTools. Tämän asetuksen muuttaminen lataa Steamin uudelleen. Aseta IP-osoite ennen käyttöönottoa.", - "ip_label": "IP-osoite", - "label": "Ota React DevTools käyttöön" - }, - "third_party_plugins": { - "button_install": "Asenna", - "button_zip": "Selaa", - "header": "Kolmannen osapuolen laajennukset", - "label_desc": "URL-osoite", - "label_zip": "Asenna laajennus ZIP-tiedostosta", - "label_url": "Asenna laajennus URL-osoitteesta" - }, - "valve_internal": { - "desc2": "Älä koske mihinkään tässä valikossa, ellet tiedä mitä se tekee.", - "label": "Ota Valve Internal käyttöön", - "desc1": "Ottaa käyttöön Valven sisäisen kehittäjävalikon." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky-versio", - "header": "Tietoja" - }, - "beta": { - "header": "Beta-osallistuminen" - }, - "developer_mode": { - "label": "Kehittäjätila" - }, - "notifications": { - "decky_updates_label": "Decky-päivitys saatavilla", - "header": "Ilmoitukset", - "plugin_updates_label": "Laajennuspäivityksiä saatavilla" - }, - "other": { - "header": "Muu" - }, - "updates": { - "header": "Päivitykset" - } - }, - "SettingsIndex": { - "developer_title": "Kehittäjä", - "general_title": "Yleinen", - "plugins_title": "Laajennukset" - }, - "Store": { - "store_contrib": { - "label": "Osallistuminen", - "desc": "Mikäli haluat julkaista Decky Plugin Storeen, tarkista GitHubin SteamDeckHomebrew/decky-plugin-template -esimerkkitietovarasto. Tietoa kehityksestä ja jakelusta löytyy README:stä." - }, - "store_filter": { - "label": "Suodin", - "label_def": "Kaikki" - }, - "store_search": { - "label": "Hae" - }, - "store_sort": { - "label": "Järjestä", - "label_def": "Viimeksi päivitetty (uusin ensin)" - }, - "store_source": { - "desc": "Kaikken laajennusten lähdekoodit ovat saatavilla SteamDeckHomebrew/decky-plugin-database -arkistosta GitHubissa.", - "label": "Lähdekoodi" - }, - "store_tabs": { - "about": "Tietoja", - "alph_asce": "Aakkosjärjestyksessä (Z–A)", - "alph_desc": "Aakkosjärjestyksessä (A–Z)", - "title": "Selaa" - }, - "store_testing_cta": "Harkitse uusien lisäosien testaamista auttaaksesi Decky Loader -tiimiä!", - "store_testing_warning": { - "label": "Tervetuloa testausmyymälä-kanavalle", - "desc": "Voit käyttää tätä myymäläkanavaa testataksesi uusimpia laajennusversioita. Muista jättää palautetta GitHubissa, jotta laajennus voidaan päivittää kaikille käyttäjille." - } - }, - "StoreSelect": { - "custom_store": { - "label": "Mukautettu myymälä", - "url_label": "URL-osoite" - }, - "store_channel": { - "custom": "Mukautettu", - "default": "Oletus", - "label": "Myymäläkanava", - "testing": "Testaus" - } - }, - "TitleView": { - "decky_store_desc": "Avaa Decky-myymälä", - "settings_desc": "Avaa Decky-asetukset" - }, - "Updater": { - "decky_updates": "Decky-päivitykset", - "no_patch_notes_desc": "tälle versiolle ei ole korjausmerkintöjä", - "patch_notes_desc": "Korjausmerkinnät", - "updates": { - "check_button": "Tarkista päivitykset", - "checking": "Tarkistetaan", - "cur_version": "Nykyinen versio: {{ver}}", - "install_button": "Asenna päivitys", - "label": "Päivitykset", - "lat_version": "Ajan tasalla: versio {{ver}}", - "reloading": "Uudelleenladataan", - "updating": "Päivitetään" - } - }, - "PluginView": { - "hidden_one": "Yksi laajennus on piilotettu tästä luettelosta", - "hidden_other": "{{count}} laajennusta on piilotettu tästä luettelosta" - } -} diff --git a/backend/src/locales/fr-FR.json b/backend/src/locales/fr-FR.json deleted file mode 100644 index f1c305f9..00000000 --- a/backend/src/locales/fr-FR.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "SettingsDeveloperIndex": { - "react_devtools": { - "desc": "Permet la connexion à un ordinateur exécutant React DevTools. Changer ce paramètre rechargera Steam. Définissez l'adresse IP avant l'activation.", - "ip_label": "IP", - "label": "Activer React DevTools" - }, - "third_party_plugins": { - "button_install": "Installer", - "button_zip": "Parcourir", - "header": "Plugins tiers", - "label_desc": "URL", - "label_url": "Installer le plugin à partir d'un URL", - "label_zip": "Installer le plugin à partir d'un fichier ZIP" - }, - "valve_internal": { - "desc1": "Active le menu développeur interne de Valve.", - "desc2": "Ne touchez à rien dans ce menu à moins que vous ne sachiez ce qu'il fait.", - "label": "Activer Valve Internal" - } - }, - "BranchSelect": { - "update_channel": { - "prerelease": "Avant-première", - "label": "Canal de mise à jour", - "stable": "Stable", - "testing": "Test" - } - }, - "StoreSelect": { - "store_channel": { - "label": "Canal du Plugin Store", - "testing": "Test", - "custom": "Personnalisé", - "default": "Par défaut" - }, - "custom_store": { - "label": "Plugin Store personnalisé", - "url_label": "URL" - } - }, - "Updater": { - "decky_updates": "Mises à jour de Decky", - "no_patch_notes_desc": "pas de notes de mise à jour pour cette version", - "patch_notes_desc": "Notes de mise à jour", - "updates": { - "check_button": "Chercher les mises à jour", - "checking": "Recherche", - "cur_version": "Version actuelle: {{ver}}", - "install_button": "Installer la mise à jour", - "label": "Mises à jour", - "lat_version": "À jour: version {{ver}}", - "reloading": "Rechargement", - "updating": "Mise à jour en cours" - } - }, - "Developer": { - "5secreload": "Rechargement dans 5 secondes", - "disabling": "Désactivation", - "enabling": "Activation" - }, - "FilePickerIndex": { - "folder": { - "select": "Utiliser ce dossier" - } - }, - "PluginCard": { - "plugin_full_access": "Ce plugin a un accès complet à votre Steam Deck.", - "plugin_install": "Installer", - "plugin_no_desc": "Aucune description fournie.", - "plugin_version_label": "Version du plugin" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Installer", - "button_processing": "Installation en cours", - "title": "Installer {{artifact}}", - "desc": "Êtes-vous sûr de vouloir installer {{artifact}} {{version}} ?" - }, - "no_hash": "Ce plugin n'a pas de somme de contrôle, vous l'installez à vos risques et périls.", - "reinstall": { - "button_idle": "Réinstaller", - "button_processing": "Réinstallation en cours", - "desc": "Êtes-vous sûr de vouloir réinstaller {{artifact}} {{version}} ?", - "title": "Réinstaller {{artifact}}" - }, - "update": { - "button_idle": "Mettre à jour", - "button_processing": "Mise à jour", - "title": "Mettre à jour {{artifact}}", - "desc": "Êtes-vous sûr de vouloir mettre à jour {{artifact}} {{version}} ?" - } - }, - "PluginListIndex": { - "plugin_actions": "Plugin Actions", - "reinstall": "Réinstaller", - "reload": "Recharger", - "uninstall": "Désinstaller", - "update_to": "Mettre à jour vers {{name}}", - "no_plugin": "Aucun plugin installé !", - "update_all_one": "", - "update_all_many": "", - "update_all_other": "" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Erreur", - "plugin_error_uninstall": "Allez sur {{name}} dans le menu de Decky si vous voulez désinstaller ce plugin.", - "plugin_load_error": { - "message": "Erreur lors du chargement du plugin {{name}}", - "toast": "Erreur lors du chargement de {{name}}" - }, - "decky_update_available": "Mise à jour vers {{tag_name}} disponible !", - "plugin_uninstall": { - "button": "Désinstaller", - "title": "Désinstaller {{name}}", - "desc": "Êtes-vous sûr.e de vouloir désinstaller {{name}} ?" - }, - "plugin_update_one": "", - "plugin_update_many": "", - "plugin_update_other": "" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Autoriser l'accès non authentifié au débogueur CEF à toute personne de votre réseau", - "label": "Autoriser le débogage CEF à distance" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Version de Decky", - "header": "À propos" - }, - "beta": { - "header": "Participation à la Bêta" - }, - "developer_mode": { - "label": "Mode développeur" - }, - "other": { - "header": "Autre" - }, - "updates": { - "header": "Mises à jour" - } - }, - "SettingsIndex": { - "developer_title": "Développeur", - "general_title": "Général", - "plugins_title": "Plugins" - }, - "Store": { - "store_contrib": { - "desc": "Si vous souhaitez contribuer au Decky Plugin Store, consultez le dépôt SteamDeckHomebrew/decky-plugin-template sur GitHub. Des informations sur le développement et la distribution sont disponibles dans le fichier README.", - "label": "Contributions" - }, - "store_filter": { - "label": "Filtrer", - "label_def": "Tous" - }, - "store_search": { - "label": "Rechercher" - }, - "store_sort": { - "label": "Trier", - "label_def": "Mises à jour (Plus récentes)" - }, - "store_source": { - "desc": "Tout le code source des plugins est disponible sur le dépôt SteamDeckHomebrew/decky-plugin-database sur GitHub.", - "label": "Code Source" - }, - "store_tabs": { - "about": "À propos", - "alph_asce": "Alphabétique (Z à A)", - "alph_desc": "Alphabétique (A à Z)", - "title": "Explorer" - }, - "store_testing_cta": "Pensez à tester de nouveaux plugins pour aider l'équipe Decky Loader !" - }, - "PluginView": { - "hidden_one": "", - "hidden_many": "", - "hidden_other": "" - }, - "MultiplePluginsInstallModal": { - "title": { - "reinstall_one": "", - "reinstall_many": "", - "reinstall_other": "", - "install_one": "", - "install_many": "", - "install_other": "", - "mixed_one": "", - "mixed_many": "", - "mixed_other": "", - "update_one": "", - "update_many": "", - "update_other": "" - } - } -} diff --git a/backend/src/locales/it-IT.json b/backend/src/locales/it-IT.json deleted file mode 100644 index 237bcdf4..00000000 --- a/backend/src/locales/it-IT.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "Canale di aggiornamento", - "prerelease": "Prerilascio", - "stable": "Stabile", - "testing": "In prova" - } - }, - "Developer": { - "5secreload": "Ricarico tra 5 secondi", - "disabling": "Disabilito i tools di React", - "enabling": "Abilito i tools di React" - }, - "DropdownMultiselect": { - "button": { - "back": "Indietro" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "Il percorso specificato non è valido. Controllalo e prova a reinserirlo di nuovo.", - "unknown": "È avvenuto un'errore sconosciuto. L'errore segnalato è {{raw_error}}", - "perm_denied": "Il tuo utente non ha accesso alla directory specificata. Verifica se l'utente corrente (è deck su Steam Deck di default) ha i permessi corrispondenti per accedere alla cartella/file desiderato." - } - }, - "FilePickerIndex": { - "file": { - "select": "Seleziona questo file" - }, - "files": { - "all_files": "Tutti i file", - "file_type": "Tipo di file", - "show_hidden": "Mostra nascosti" - }, - "filter": { - "created_asce": "Creazione (meno recente)", - "created_desc": "Creazione (più recente)", - "modified_asce": "Modifica (meno recente)", - "modified_desc": "Modifica (più recente)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Dimensione (più piccolo)", - "size_desc": "Dimensione (più grande)" - }, - "folder": { - "label": "Cartella", - "select": "Usa questa cartella", - "show_more": "Mostra più file" - } - }, - "MultiplePluginsInstallModal": { - "confirm": "Sei sicuro di voler effettuare le modifiche seguenti?", - "description": { - "install": "Installa {{name}} {{version}}", - "reinstall": "Reinstalla {{name}} {{version}}", - "update": "Aggiorna {{name}} alla versione {{version}}" - }, - "ok_button": { - "idle": "Conferma", - "loading": "Elaboro" - }, - "title": { - "install_one": "Installa un plugin", - "install_many": "Installa {{count}} plugins", - "install_other": "Installa {{count}} plugins", - "mixed_one": "Modifica un plugin", - "mixed_many": "Modifica {{count}} plugins", - "mixed_other": "Modifica {{count}} plugins", - "reinstall_one": "Reinstalla un plugin", - "reinstall_many": "Reinstalla {{count}} plugins", - "reinstall_other": "Reinstalla {{count}} plugins", - "update_one": "Aggiorna un plugin", - "update_many": "Aggiorna {{count}} plugins", - "update_other": "Aggiorna {{count}} plugins" - } - }, - "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": { - "hide": "Accesso rapido: Nascondi", - "no_plugin": "Nessun plugin installato!", - "plugin_actions": "Operazioni sui plugins", - "reinstall": "Reinstalla", - "reload": "Ricarica", - "show": "Accesso rapido: Mostra", - "uninstall": "Rimuovi", - "update_all_one": "Aggiorna un plugin", - "update_all_many": "Aggiorna {{count}} plugins", - "update_all_other": "Aggiorna {{count}} plugins", - "update_to": "Aggiorna a {{name}}" - }, - "PluginListLabel": { - "hidden": "Nascosto dal menu di accesso rapido" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Disponibile aggiornamento a {{tag_name}}!", - "error": "Errore", - "plugin_error_uninstall": "Il plugin {{name}} ha causato un'eccezione che è descritta sopra. Questo tipicamente significa che il plugin deve essere aggiornato per funzionare sulla nuova versione di SteamUI. Controlla se è disponibile un aggiornamento o valutane la rimozione andando nelle impostazioni di Decky nella sezione Plugins.", - "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 1 plugin!", - "plugin_update_many": "Aggiornamenti disponibili per {{count}} plugins!", - "plugin_update_other": "Aggiornamenti disponibili per {{count}} plugins!" - }, - "PluginView": { - "hidden_one": "Un plugin è nascosto dalla lista", - "hidden_many": "Sono nascosti {{count}} plugin dalla lista", - "hidden_other": "Sono nascosti {{count}} plugin dalla lista" - }, - "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": { - "cef_console": { - "button": "Apri la console", - "desc": "Apri la console di CEF. Utile solamente per ragioni di debug. Questa opzione deve essere usata solo se sei uno sviluppatore di plugin o se uno di questi ti ha chiesto di farlo, visto che questa feature potrebbe essere potenzialmente pericolosa.", - "label": "Console CEF" - }, - "header": "Altro", - "react_devtools": { - "desc": "Abilita la connessione ad un computer che esegue i DevTools di React. Steam verrà ricaricato se lo stato cambia. Imposta il tuo 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" - }, - "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": { - "label": "Modalità sviluppatore" - }, - "other": { - "header": "Altro" - }, - "updates": { - "header": "Aggiornamenti" - }, - "notifications": { - "header": "Notifiche", - "decky_updates_label": "Aggiornamenti di Decky", - "plugin_updates_label": "Aggiornamenti dei plugins" - } - }, - "SettingsIndex": { - "developer_title": "Sviluppatore", - "general_title": "Generali", - "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!", - "store_testing_warning": { - "label": "Benvenuto nel Negozio di Test dei Plugins", - "desc": "Puoi usare questo canale del negozio per testare versioni di plugin sperimentali. Assicurati di lasciare un feedback su Github dopo averlo testato in modo che il plugin possa essere promosso a stabile per tutti gli altri utenti o per permettere allo sviluppatore di plugin di correggere eventuali errori." - } - }, - "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" - } - }, - "TitleView": { - "settings_desc": "Apri le impostazioni di Decky", - "decky_store_desc": "Apri lo store di Decky" - } -} diff --git a/backend/src/locales/ko-KR.json b/backend/src/locales/ko-KR.json deleted file mode 100644 index 48698c5c..00000000 --- a/backend/src/locales/ko-KR.json +++ /dev/null @@ -1,253 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "label": "업데이트 배포 채널", - "stable": "안정", - "testing": "테스트", - "prerelease": "사전 출시" - } - }, - "Developer": { - "disabling": "React DevTools 비활성화", - "enabling": "React DevTools 활성화", - "5secreload": "5초 내로 다시 로드 됩니다" - }, - "FilePickerIndex": { - "folder": { - "select": "이 폴더 사용", - "label": "폴더", - "show_more": "더 많은 파일 표시" - }, - "filter": { - "created_asce": "만든 날짜 (오름차순)", - "modified_asce": "수정한 날짜 (오름차순)", - "created_desc": "만든 날짜 (내림차 순)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "크기 (오름차순)", - "modified_desc": "수정한 날짜 (내림차순)", - "size_desc": "크기 (내림차순)" - }, - "files": { - "all_files": "모든 파일", - "show_hidden": "숨김 파일 표시", - "file_type": "파일 형식" - }, - "file": { - "select": "이 파일 선택" - } - }, - "PluginView": { - "hidden_other": "플러그인 {{count}}개 숨김" - }, - "PluginListLabel": { - "hidden": "빠른 액세스 메뉴에서 숨김" - }, - "PluginCard": { - "plugin_install": "설치", - "plugin_no_desc": "플러그인 설명이 제공되지 않았습니다.", - "plugin_version_label": "플러그인 버전", - "plugin_full_access": "이 플러그인은 Steam Deck의 모든 접근 권한을 가집니다." - }, - "PluginInstallModal": { - "install": { - "button_idle": "설치", - "button_processing": "설치 중", - "desc": "{{artifact}} {{version}}을(를) 설치하겠습니까?", - "title": "{{artifact}} 설치" - }, - "reinstall": { - "button_idle": "재설치", - "button_processing": "재설치 중", - "desc": "{{artifact}} {{version}}을(를) 재설치하겠습니까?", - "title": "{{artifact}} 재설치" - }, - "update": { - "button_idle": "업데이트", - "button_processing": "업데이트 중", - "title": "{{artifact}} 업데이트", - "desc": "{{artifact}} {{version}} 업데이트를 설치하겠습니까?" - }, - "no_hash": "이 플러그인은 해시 확인을 하지 않습니다, 설치에 따른 위험은 사용자가 감수해야 합니다." - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_other": "플러그인 {{count}}개 수정", - "update_other": "플러그인 {{count}}개 업데이트", - "reinstall_other": "플러그인 {{count}}개 재설치", - "install_other": "플러그인 {{count}}개 설치" - }, - "ok_button": { - "idle": "확인", - "loading": "작업 중" - }, - "confirm": "해당 수정을 적용하겠습니까?", - "description": { - "install": "{{name}} {{version}} 플러그인 설치", - "update": "{{name}}의 {{version}} 업데이트 설치", - "reinstall": "{{name}} {{version}} 재설치" - } - }, - "PluginListIndex": { - "plugin_actions": "플러그인 동작", - "reinstall": "재설치", - "reload": "다시 로드", - "uninstall": "설치 제거", - "show": "빠른 액세스 메뉴: 표시", - "hide": "빠른 액세스 메뉴: 숨김", - "update_all_other": "플러그인 {{count}}개 업데이트", - "no_plugin": "설치된 플러그인이 없습니다!", - "update_to": "{{name}}(으)로 업데이트" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "{{tag_name}} 업데이트를 설치할 수 있습니다!", - "error": "오류", - "plugin_load_error": { - "message": "{{name}} 플러그인 불러오기 오류", - "toast": "{{name}} 불러오기 오류" - }, - "plugin_uninstall": { - "button": "설치 제거", - "desc": "{{name}}을(를) 설치 제거하겠습니까?", - "title": "{{name}} 설치 제거" - }, - "plugin_update_other": "플러그인 {{count}}개를 업데이트 할 수 있습니다!", - "plugin_error_uninstall": "{{name}} 플러그인을 불러올 때 위와 같은 예외가 발생했습니다. 이는 보통 SteamUI 최신 버전에 맞는 플러그인 업데이트가 필요할 때 발생합니다. Decky 설정의 플러그인 섹션에서 업데이트가 있는지 확인하거나 설치 제거를 시도 해 보세요." - }, - "RemoteDebugging": { - "remote_cef": { - "label": "리모트 CEF 디버그 허용", - "desc": "네트워크의 모든 사용자에게 CEF 디버거에 대한 인증되지 않은 액세스 허용" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "콘솔 열기", - "label": "CEF 콘솔", - "desc": "CEF 콘솔을 엽니다. 디버그 전용입니다. 이 항목들은 위험 가능성이 있으므로 플러그인 개발자이거나 개발자의 가이드를 따를 경우에만 사용해야 합니다." - }, - "header": "기타", - "react_devtools": { - "ip_label": "IP", - "label": "React DevTools 활성화", - "desc": "React DevTools를 실행하고 있는 컴퓨터에 연결을 활성화합니다. 이 설정을 변경하면 Steam이 다시 로드됩니다. 활성화하기 전에 IP 주소를 설정하세요." - }, - "third_party_plugins": { - "button_install": "설치", - "button_zip": "검색", - "header": "서드파티 플러그인", - "label_desc": "URL", - "label_url": "URL에서 플러그인 설치", - "label_zip": "ZIP 파일에서 플러그인 설치" - }, - "valve_internal": { - "desc1": "Valve 내부 개발자 메뉴를 활성화합니다.", - "label": "Valve 내부 개발자 메뉴 활성화", - "desc2": "이 메뉴의 기능을 모른다면 어떤 것도 건드리지 마세요." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky 버전", - "header": "정보" - }, - "beta": { - "header": "베타 참가" - }, - "developer_mode": { - "label": "개발자 모드" - }, - "other": { - "header": "기타" - }, - "updates": { - "header": "업데이트" - }, - "notifications": { - "header": "알림", - "plugin_updates_label": "플러그인 업데이트 가능", - "decky_updates_label": "Decky 업데이트 가능" - } - }, - "SettingsIndex": { - "developer_title": "개발자", - "general_title": "일반", - "plugins_title": "플러그인" - }, - "Store": { - "store_contrib": { - "desc": "Decky 플러그인 스토어에 기여하고 싶다면 SteamDeckHomebrew/decky-plugin-template Github 저장소를 확인하세요. 개발 및 배포에 대한 정보는 README에서 확인할 수 있습니다.", - "label": "기여하기" - }, - "store_filter": { - "label": "필터", - "label_def": "모두" - }, - "store_search": { - "label": "검색" - }, - "store_sort": { - "label": "정렬", - "label_def": "최근 업데이트 순" - }, - "store_source": { - "desc": "모든 플러그인 소스 코드는 SteamDeckHomebrew/decky-plugin-database Github 저장소에서 확인할 수 있습니다.", - "label": "소스 코드" - }, - "store_tabs": { - "about": "정보", - "alph_asce": "알파벳순 (Z-A)", - "alph_desc": "알파벳순 (A-Z)", - "title": "검색" - }, - "store_testing_cta": "새로운 플러그인을 테스트하여 Decky Loader 팀을 도와주세요!", - "store_testing_warning": { - "desc": "이 스토어 채널을 사용하여 가장 최신 버전의 플러그인을 테스트할 수 있습니다. GitHub에 피드백을 남겨서 모든 사용자가 업데이트 할 수 있게 해주세요.", - "label": "테스트 스토어 채널에 오신 것을 환영합니다" - } - }, - "StoreSelect": { - "custom_store": { - "label": "사용자 지정 스토어", - "url_label": "URL" - }, - "store_channel": { - "custom": "사용자 지정", - "label": "스토어 배포 채널", - "default": "기본", - "testing": "테스트" - } - }, - "Updater": { - "decky_updates": "Decky 업데이트", - "no_patch_notes_desc": "이 버전에는 패치 노트가 없습니다", - "patch_notes_desc": "패치 노트", - "updates": { - "check_button": "업데이트 확인", - "checking": "확인 중", - "cur_version": "현재 버전: {{ver}}", - "install_button": "업데이트 설치", - "label": "업데이트", - "lat_version": "최신 상태: {{ver}} 실행 중", - "reloading": "다시 로드 중", - "updating": "업데이트 중" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "지정된 경로가 잘못되었습니다. 확인 후에 다시 입력해 주세요.", - "unknown": "알 수 없는 오류가 발생했습니다. Raw 오류: {{raw_error}}", - "perm_denied": "선택한 경로에 접근 할 수 없습니다. 선택한 폴더/파일 접근 권한이 유저(Steam Deck의 deck 유저)에 맞게 올바르게 설정 되었는지 확인하세요." - } - }, - "DropdownMultiselect": { - "button": { - "back": "뒤로" - } - }, - "TitleView": { - "settings_desc": "Decky 설정 열기", - "decky_store_desc": "Decky 스토어 열기" - } -} diff --git a/backend/src/locales/nl-NL.json b/backend/src/locales/nl-NL.json deleted file mode 100644 index 1adde308..00000000 --- a/backend/src/locales/nl-NL.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "Vooruitgave", - "stable": "Stabiel", - "label": "Update Kanaal", - "testing": "Test" - } - }, - "Developer": { - "5secreload": "Herlaad in 5 seconden", - "disabling": "Uitschakelen React DevTools", - "enabling": "Inschakelen React DevTools" - }, - "DropdownMultiselect": { - "button": { - "back": "Terug" - } - }, - "FilePickerError": { - "errors": { - "unknown": "Een onbekende fout is opgetreden. De ruwe fout is: {{raw_error}}", - "file_not_found": "Het opgegeven pad is niet geldig. Controleer het en voer het opnieuw correct in." - } - }, - "FilePickerIndex": { - "files": { - "all_files": "Alle bestanden", - "file_type": "Bestandstype", - "show_hidden": "Toon verborgen bestanden" - }, - "filter": { - "created_desc": "Gecreëerd ( Nieuwste)", - "modified_asce": "Veranderd (Oudste)", - "modified_desc": "Veranderd (Nieuwste)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Grootte (Kleinste)", - "size_desc": "Grootte (Grootste)", - "created_asce": "Gecreëerd (Oudste)" - }, - "folder": { - "label": "Map", - "select": "Gebruik deze map", - "show_more": "Toon meer bestanden" - } - }, - "PluginView": { - "hidden_one": "1 plug-in is verborgen in deze lijst", - "hidden_other": "{{count}} plug-ins zijn verborgen in deze lijst" - }, - "PluginListLabel": { - "hidden": "Verborgen in het snelmenu" - }, - "PluginCard": { - "plugin_install": "Installeren", - "plugin_no_desc": "Geen beschrijving gegeven.", - "plugin_version_label": "Plugin Versie", - "plugin_full_access": "Deze plug-in heeft volledige toegang tot je Steam Deck." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Installeren", - "button_processing": "Bezig met installeren", - "title": "Installeer {{artifact}}", - "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt installeren?" - }, - "no_hash": "Deze plug-in heeft geen hash, u installeert deze op eigen risico.", - "reinstall": { - "button_idle": "Herinstalleren", - "button_processing": "Bezig te herinstalleren", - "desc": "Weet je zeker dat je {{artifact}} {{version}} opnieuw wilt installeren?", - "title": "Installeer {{artifact}} opnieuw" - }, - "update": { - "button_idle": "Update", - "button_processing": "Bezig met updaten", - "title": "{{artifact}} bijwerken", - "desc": "Weet je zeker dat je {{artifact}} {{version}} wilt updaten?" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Wijzig {{count}} plug-in", - "mixed_other": "Pas {{count}} plug-ins aan", - "update_one": "1 plugin bijwerken", - "update_other": "{{count}} plug-ins bijwerken", - "install_one": "Installeer 1 plugin", - "install_other": "Installeer {{count}} plugins", - "reinstall_one": "1 plugin opnieuw installeren", - "reinstall_other": "{{count}} plugins opnieuw installeren" - }, - "ok_button": { - "idle": "Bevestigen", - "loading": "Werkend" - }, - "confirm": "Weet u zeker dat u de volgende wijzigingen wilt aanbrengen?", - "description": { - "install": "Installeer {{name}} {{version}}", - "update": "Update {{name}} naar {{version}}", - "reinstall": "Installeer opnieuw {{name}} {{version}}" - } - }, - "PluginListIndex": { - "no_plugin": "Geen plugins geïnstalleerd!", - "plugin_actions": "Plugin Acties", - "reload": "Herladen", - "uninstall": "Verwijderen", - "update_to": "Update naar {{name}}", - "hide": "Snelle toegang: Verberg", - "update_all_one": "Update 1 plugin", - "update_all_other": "Update {{count}} plugins", - "reinstall": "Opnieuw installeren", - "show": "Snelle toegang: Toon" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Fout", - "plugin_load_error": { - "message": "Fout bij het laden van plugin {{name}}", - "toast": "Fout bij het laden van {{name}}" - }, - "plugin_uninstall": { - "button": "Verwijderen", - "desc": "Weet je zeker dat je {{name}} wilt verwijderen?", - "title": "Verwijder {{name}}" - }, - "plugin_update_one": "Updates beschikbaar voor 1 plugin!", - "plugin_update_other": "Updates beschikbaar voor {{count}} plugins!", - "decky_update_available": "Update naar {{tag_name}} beschikbaar!", - "plugin_error_uninstall": "Het laden van {{name}} veroorzaakte een uitzondering zoals hierboven weergegeven. Dit betekent meestal dat de plug-in een update vereist voor de nieuwe versie van SteamUI. Controleer of er een update aanwezig is of evalueer de verwijdering ervan in de Decky-instellingen, in het gedeelte Plug-ins." - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Sta ongeauthenticeerde toegang tot de CEF-foutopsporing toe aan iedereen in uw netwerk", - "label": "Externe CEF-foutopsporing toestaan" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Console openen", - "label": "CEF Bedieningsscherm", - "desc": "Opent de CEF-console. Alleen nuttig voor foutopsporingsdoeleinden. Dingen hier zijn potentieel gevaarlijk en mogen alleen worden gebruikt als u een ontwikkelaar van plug-ins bent, of hier door een ontwikkelaar naartoe wordt geleid." - }, - "header": "Andere", - "react_devtools": { - "ip_label": "IP", - "label": "Aanzetten React DevTools", - "desc": "Maakt verbinding met een computer met React DevTools mogelijk. Als je deze instelling wijzigt, wordt Steam opnieuw geladen. Stel het IP-adres in voordat u het inschakelt." - }, - "third_party_plugins": { - "header": "Plug-ins van derden", - "label_desc": "URL", - "label_url": "Installeer Plugin van URL", - "label_zip": "Installeer Plugin van Zip bestand", - "button_install": "Installeren", - "button_zip": "Bladeren" - }, - "valve_internal": { - "desc1": "Schakelt het interne ontwikkelaarsmenu van Valve in.", - "desc2": "Raak niets in dit menu aan tenzij u weet wat het doet.", - "label": "Valve Internal inschakelen" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky versie", - "header": "Over" - }, - "beta": { - "header": "Beta deelname" - }, - "developer_mode": { - "label": "Ontwikkelaars modus" - }, - "other": { - "header": "Overige" - }, - "updates": { - "header": "Nieuwe Versies" - } - }, - "SettingsIndex": { - "developer_title": "Ontwikkelaar", - "general_title": "Algemeen", - "plugins_title": "Plugins" - }, - "Store": { - "store_filter": { - "label": "Filter", - "label_def": "Alles" - }, - "store_search": { - "label": "Zoek" - }, - "store_sort": { - "label": "Sorteren", - "label_def": "Laatste Geupdate (Nieuwste)" - }, - "store_source": { - "label": "Bron Code", - "desc": "Alle broncode van de plug-in is beschikbaar in de SteamDeckHomebrew/decky-plugin-database-repository op GitHub." - }, - "store_tabs": { - "about": "Over", - "alph_asce": "Alfabetisch (Z naar A)", - "alph_desc": "Alfabetisch (A naar Z)", - "title": "Bladeren" - }, - "store_testing_cta": "Overweeg alsjeblieft om nieuwe plug-ins te testen om het Decky Loader-team te helpen!", - "store_contrib": { - "desc": "Als je wilt bijdragen aan de Decky Plugin winkel, kijk dan in de SteamDeckHomebrew/decky-plugin-template repository op GitHub. Informatie over ontwikkeling en distributie is beschikbaar in de README.", - "label": "Bijdragende" - } - }, - "StoreSelect": { - "custom_store": { - "label": "Aangepassingen winkel", - "url_label": "URL" - }, - "store_channel": { - "custom": "Aanpassingen", - "default": "Standaard", - "label": "Winkel Kanaal", - "testing": "Testen" - } - }, - "Updater": { - "patch_notes_desc": "Correctie opmerkingen", - "updates": { - "check_button": "Controleer op updates", - "checking": "Controleren", - "cur_version": "Huidige versie: {{ver}}", - "install_button": "Installeer Update", - "label": "Update", - "lat_version": "Up-to-date: loopt {{ver}}", - "reloading": "Herstarten", - "updating": "Aan het updaten" - }, - "decky_updates": "Decky Nieuwe Versies", - "no_patch_notes_desc": "geen correctie-opmerkingen voor deze versie" - } -} diff --git a/backend/src/locales/pl-PL.json b/backend/src/locales/pl-PL.json deleted file mode 100644 index 5231fa37..00000000 --- a/backend/src/locales/pl-PL.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "testing": "Testowy", - "label": "Kanał aktualizacji", - "stable": "Stabilny", - "prerelease": "Przedpremierowy" - } - }, - "Developer": { - "enabling": "Włączanie React DevTools", - "5secreload": "Ponowne załadowanie za 5 sekund", - "disabling": "Wyłączanie React DevTools" - }, - "DropdownMultiselect": { - "button": { - "back": "Powrót" - } - }, - "FilePickerError": { - "errors": { - "perm_denied": "Nie masz dostępu do podanego katalogu. Sprawdź, czy twój użytkownik (deck na Steam Deck) ma odpowiednie uprawnienia dostępu do określonego katalogu/pliku.", - "unknown": "Wystąpił nieznany błąd. Surowy błąd to {{raw_error}}", - "file_not_found": "Podana ścieżka jest nieprawidłowa. Sprawdź ją i wprowadź ponownie poprawnie." - } - }, - "FilePickerIndex": { - "file": { - "select": "Wybierz ten plik" - }, - "files": { - "all_files": "Wszystkie pliki", - "file_type": "Typ pliku", - "show_hidden": "Pokaż ukryte pliki" - }, - "filter": { - "created_asce": "Utworzono (najstarszy)", - "created_desc": "Utworzono (najnowszy)", - "modified_asce": "Zmodyfikowany (najstarszy)", - "modified_desc": "Zmodyfikowany (najnowszy)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Rozmiar (najmniejszy)", - "size_desc": "Rozmiar (największy)" - }, - "folder": { - "label": "Katalog", - "select": "Użyj tego katalogu", - "show_more": "Pokaż więcej plików" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Zmodyfikuj {{count}} plugin", - "mixed_few": "Zmodyfikuj {{count}} pluginy", - "mixed_many": "Zmodyfikuj {{count}} pluginów", - "reinstall_one": "Reinstaluj 1 plugin", - "reinstall_few": "Reinstaluj {{count}} pluginy", - "reinstall_many": "Reinstaluj {{count}} pluginów", - "install_one": "Zainstaluj 1 plugin", - "install_few": "Zainstaluj {{count}} pluginy", - "install_many": "Zainstaluj {{count}} pluginów", - "update_one": "Zaktualizuj 1 plugin", - "update_few": "Zaktualizuj {{count}} pluginy", - "update_many": "Zaktualizuj {{count}} pluginów" - }, - "confirm": "Czy na pewno chcesz wprowadzić następujące modyfikacje?", - "description": { - "install": "Zainstaluj {{name}} {{version}}", - "reinstall": "Reinstaluj {{name}} {{version}}", - "update": "Zaktualizuj {{name}} do {{version}}" - }, - "ok_button": { - "idle": "Potwierdź", - "loading": "W toku" - } - }, - "PluginCard": { - "plugin_install": "Zainstaluj", - "plugin_no_desc": "Brak opisu.", - "plugin_version_label": "Wersja pluginu", - "plugin_full_access": "Ten plugin ma pełny dostęp do twojego Steam Decka." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Zainstaluj", - "button_processing": "Instalowanie", - "desc": "Czy na pewno chcesz zainstalować {{artifact}} {{version}}?", - "title": "Zainstaluj {{artifact}}" - }, - "reinstall": { - "button_idle": "Reinstaluj", - "button_processing": "Reinstalowanie", - "desc": "Czy na pewno chcesz ponownie zainstalować {{artifact}} {{version}}?", - "title": "Reinstaluj {{artifact}}" - }, - "update": { - "button_idle": "Aktualizacja", - "button_processing": "Aktualizowanie", - "desc": "Czy na pewno chcesz zaktualizować {{artifact}} {{version}}?", - "title": "Zaktualizuj {{artifact}}" - }, - "no_hash": "Ten plugin nie ma hasha, instalujesz go na własne ryzyko." - }, - "PluginListIndex": { - "hide": "Szybki dostęp: Ukryj", - "no_plugin": "Brak zainstalowanych pluginów!", - "reload": "Załaduj ponownie", - "update_all_one": "Zaktualizuj 1 plugin", - "update_all_few": "Zaktualizuj {{count}} pluginy", - "update_all_many": "Zaktualizuj {{count}} pluginów", - "plugin_actions": "Akcje pluginów", - "reinstall": "Reinstalacja", - "show": "Szybki dostęp: Pokaż", - "uninstall": "Odinstaluj", - "update_to": "Zaktualizuj do {{name}}" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Dostępna aktualizacja do {{tag_name}}!", - "error": "Błąd", - "plugin_error_uninstall": "Ładowanie {{name}} spowodowało wyjątek, jak pokazano powyżej. Zwykle oznacza to, że plugin wymaga aktualizacji do nowej wersji SteamUI. Sprawdź, czy aktualizacja jest obecna lub rozważ usunięcie go w ustawieniach Decky, w sekcji Pluginy.", - "plugin_load_error": { - "message": "Błąd ładowania plugin {{name}}", - "toast": "Błąd ładowania {{name}}" - }, - "plugin_uninstall": { - "button": "Odinstaluj", - "title": "Odinstaluj {{name}}", - "desc": "Czy na pewno chcesz odinstalować {{name}}?" - }, - "plugin_update_one": "Aktualizacje dostępne dla 1 pluginu!", - "plugin_update_few": "Aktualizacje dostępne dla {{count}} pluginów!", - "plugin_update_many": "Aktualizacje dostępne dla {{count}} pluginów!" - }, - "PluginListLabel": { - "hidden": "Ukryty w menu szybkiego dostępu" - }, - "PluginView": { - "hidden_one": "1 plugin jest ukryty na tej liście", - "hidden_few": "{{count}} pluginy jest ukryty na tej liście", - "hidden_many": "{{count}} pluginów jest ukryty na tej liście" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Zezwalaj na nieuwierzytelniony dostęp do debugera CEF wszystkim osobom w Twojej sieci", - "label": "Zezwól na zdalne debugowanie CEF" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Otwórz konsolę", - "desc": "Otwiera konsolę CEF. Przydatne tylko do celów debugowania. Rzeczy tutaj są potencjalnie niebezpieczne i powinny być używane tylko wtedy, gdy jesteś twórcą wtyczek lub zostałeś tu przez kogoś skierowany.", - "label": "Konsola CEF" - }, - "header": "Inne", - "react_devtools": { - "desc": "Umożliwia połączenie z komputerem z uruchomionym React DevTools. Zmiana tego ustawienia spowoduje ponowne załadowanie Steam. Ustaw adres IP przed włączeniem.", - "ip_label": "IP", - "label": "Włącz React DevTools" - }, - "third_party_plugins": { - "button_install": "Zainstaluj", - "button_zip": "Przeglądaj", - "header": "Pluginy zewnętrzne", - "label_desc": "URL", - "label_url": "Zainstaluj plugin z adresu URL", - "label_zip": "Zainstaluj plugin z pliku ZIP" - }, - "valve_internal": { - "desc1": "Włącza wewnętrzne menu programisty Valve.", - "desc2": "Nie dotykaj niczego w tym menu, chyba że wiesz, co robi.", - "label": "Włącz Valve Internal" - } - }, - "SettingsGeneralIndex": { - "notifications": { - "decky_updates_label": "Dostępna aktualizacja Decky", - "header": "Powiadomienia", - "plugin_updates_label": "Dostępne aktualizacje pluginów" - }, - "other": { - "header": "Inne" - }, - "updates": { - "header": "Aktualizacje" - }, - "about": { - "header": "Informacje", - "decky_version": "Wersja Decky" - }, - "beta": { - "header": "Udział w becie" - }, - "developer_mode": { - "label": "Tryb dewelopera" - } - }, - "SettingsIndex": { - "developer_title": "Deweloper", - "general_title": "Ogólne", - "plugins_title": "Pluginy" - }, - "Store": { - "store_contrib": { - "desc": "Jeśli chcesz przyczynić się do rozwoju Decky Plugin Store, sprawdź repozytorium SteamDeckHomebrew/decky-plugin-template na GitHub. Informacje na temat rozwoju i dystrybucji są dostępne w pliku README.", - "label": "Współtworzenie" - }, - "store_filter": { - "label": "Filtr", - "label_def": "Wszystko" - }, - "store_search": { - "label": "Szukaj" - }, - "store_sort": { - "label": "Sortowanie", - "label_def": "Ostatnia aktualizacja (najnowsza)" - }, - "store_source": { - "desc": "Cały kod źródłowy pluginów jest dostępny w repozytorium SteamDeckHomebrew/decky-plugin-database na GitHub.", - "label": "Kod źródłowy" - }, - "store_tabs": { - "alph_asce": "Alfabetycznie (od Z do A)", - "alph_desc": "Alfabetycznie (od A do Z)", - "title": "Przeglądaj", - "about": "Informacje" - }, - "store_testing_cta": "Rozważ przetestowanie nowych pluginów, aby pomóc zespołowi Decky Loader!", - "store_testing_warning": { - "label": "Witamy w Testowym Kanale Sklepu", - "desc": "Możesz użyć tego kanału sklepu do testowania najnowszych wersji pluginów. Pamiętaj, aby zostawić opinię na GitHub, aby plugin mogła zostać zaktualizowana dla wszystkich użytkowników." - } - }, - "StoreSelect": { - "custom_store": { - "label": "Niestandardowy sklep", - "url_label": "URL" - }, - "store_channel": { - "custom": "Niestandardowy", - "default": "Domyślny", - "label": "Kanał sklepu", - "testing": "Testowy" - } - }, - "Updater": { - "decky_updates": "Aktualizacje Decky", - "no_patch_notes_desc": "Brak informacji o poprawkach dla tej wersji", - "patch_notes_desc": "Opis zmian", - "updates": { - "check_button": "Sprawdź aktualizacje", - "checking": "Sprawdzanie", - "cur_version": "Aktualna wersja: {{ver}}", - "install_button": "Zainstaluj aktualizację", - "label": "Aktualizacje", - "lat_version": "Aktualizacje zainstalowane. Aktualna wersja: {{ver}}", - "reloading": "Ponowne ładowanie", - "updating": "Aktualizowanie" - } - }, - "TitleView": { - "settings_desc": "Otwórz ustawienia Decky", - "decky_store_desc": "Otwórz sklep Decky" - } -} diff --git a/backend/src/locales/pt-BR.json b/backend/src/locales/pt-BR.json deleted file mode 100644 index 2a7c173b..00000000 --- a/backend/src/locales/pt-BR.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "Pré-lançamento", - "stable": "Estável", - "testing": "Em Teste", - "label": "Canal de Atualização" - } - }, - "Developer": { - "5secreload": "Recarregando em 5 segundos", - "enabling": "Habilitando React DevTools", - "disabling": "Desabilitando React DevTools" - }, - "FilePickerIndex": { - "folder": { - "select": "Use esta pasta", - "label": "Pasta", - "show_more": "Mostrar mais arquivos" - }, - "files": { - "show_hidden": "Mostrar Arquivos Ocultos", - "all_files": "Todos os arquivos", - "file_type": "Formato de arquivo" - }, - "filter": { - "created_asce": "Criado (Mais antigo)", - "created_desc": "Criado (Mais recente)", - "modified_asce": "Alterado (Mais antigo)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "size_asce": "Tamanho (Menor)", - "size_desc": "Tamanho (Maior)", - "modified_desc": "Alterado (Mais recente)" - }, - "file": { - "select": "Selecione este arquivo" - } - }, - "PluginListLabel": { - "hidden": "Oculto no menu de acesso rápido" - }, - "PluginCard": { - "plugin_full_access": "Este plugin tem acesso total ao seu Steam Deck.", - "plugin_install": "Instalar", - "plugin_no_desc": "Nenhuma descrição fornecida.", - "plugin_version_label": "Versão do plugin" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalar", - "button_processing": "Instalando", - "desc": "Você tem certeza que deseja instalar {{artifact}} {{version}}?", - "title": "Instalar {{artifact}}" - }, - "reinstall": { - "button_idle": "Reinstalar", - "button_processing": "Reinstalando", - "desc": "Tem certeza que voce deseja reinstalar {{artifact}} {{version}}?", - "title": "Reinstalar {{artifact}}" - }, - "update": { - "button_idle": "Atualizar", - "button_processing": "Atualizando", - "desc": "Tem certeza que voce deseja atualizar {{artifact}} {{version}}?", - "title": "Atualizar {{artifact}}" - }, - "no_hash": "Este plugin não tem um hash, você o está instalando por sua conta em risco." - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Modificar {{count}} plugin", - "mixed_many": "Modificar {{count}} plugins", - "mixed_other": "Modificar {{count}} plugins", - "update_one": "Atualizar 1 plugin", - "update_many": "Atualizar {{count}} plugins", - "update_other": "Atualizar {{count}} plugins", - "install_one": "Instalar 1 plugin", - "install_many": "Instalar {{count}} plugins", - "install_other": "Instalar {{count}} plugins", - "reinstall_one": "Reinstalar 1 plugin", - "reinstall_many": "Reinstalar {{count}} plugins", - "reinstall_other": "Reinstalar {{count}} plugins" - }, - "ok_button": { - "idle": "Confirmar", - "loading": "Carregando" - }, - "description": { - "install": "Instalar {{name}} {{version}}", - "update": "Atualizar {{name}} para {{version}}", - "reinstall": "Reinstalar {{name}} {{version}}" - }, - "confirm": "Tem certeza que deseja fazer as seguintes modificações?" - }, - "PluginListIndex": { - "no_plugin": "Nenhum plugin instalado!", - "plugin_actions": "Ações do plugin", - "reinstall": "Reinstalar", - "reload": "Recarregar", - "uninstall": "Desinstalar", - "update_to": "Atualizar para {{name}}", - "show": "Acesso Rápido: Mostrar", - "update_all_one": "Atualizar 1 plugin", - "update_all_many": "Atualizar {{count}} plugins", - "update_all_other": "Atualizar {{count}} plugins", - "hide": "Acesso Rápido: Ocultar" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Erro", - "plugin_load_error": { - "message": "Erro ao carregar o plugin {{name}}", - "toast": "Erro ao carregar {{name}}" - }, - "plugin_uninstall": { - "button": "Desinstalar", - "desc": "Você tem certeza que deseja desinstalar {{name}}?", - "title": "Desinstalar {{name}}" - }, - "decky_update_available": "Atualização para {{tag_name}} disponível!", - "plugin_error_uninstall": "Um erro aconteceu ao carregar {{name}}, como mostrado acima. Isso normalmente significa que o plugin precisa de uma atualização para a nova versão do SteamUI. Confira se existe uma atualização ou avalie a remoção do plugin nas configurações do Decky, na sessão de plugins.", - "plugin_update_one": "Atualização disponível para 1 plugin!", - "plugin_update_many": "Atualizações disponíveis para {{count}} plugins!", - "plugin_update_other": "Atualizações disponíveis para {{count}} plugins!" - }, - "RemoteDebugging": { - "remote_cef": { - "label": "Permitir Depuração CEF Demota", - "desc": "Permitir acesso não autenticato ao depurador CEF para qualquer um na sua rede" - } - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Abrir o Console", - "label": "Console CEF", - "desc": "Abre o Console CEF. Somente útil para fins de depuração. O material aqui é potencialmente perigoso e só deve ser usado se você for um desenvolvedor de plugin, ou direcionado até aqui por um." - }, - "header": "Outros", - "react_devtools": { - "desc": "Habilita a conexão a um computador executando React DevTools. Alterar essa configuração irá recarregar a Steam. Defina o endereço IP antes de habilitar.", - "ip_label": "IP", - "label": "Habilitar React DevTools" - }, - "third_party_plugins": { - "button_install": "Instalar", - "button_zip": "Navegar", - "header": "Plugins de terceiros", - "label_url": "Instalar Plugin a partir da URL", - "label_zip": "Instalar Plugin a partir de um arquivo ZIP", - "label_desc": "URL" - }, - "valve_internal": { - "desc1": "Habilita o menu interno de desenvolvedor da Valve.", - "desc2": "Não toque em nada neste menu, a não ser que você saiba o que está fazendo.", - "label": "Habilitar Menu Interno da Valve" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Versão do Decky", - "header": "Sobre" - }, - "developer_mode": { - "label": "Modo Deselvolvedor" - }, - "other": { - "header": "Outros" - }, - "updates": { - "header": "Atualizações" - }, - "beta": { - "header": "Participação no Beta" - }, - "notifications": { - "decky_updates_label": "Atualização do Decky disponível", - "header": "Noificações", - "plugin_updates_label": "Atualizações de Plugin disponíveis" - } - }, - "SettingsIndex": { - "developer_title": "Desenvolvedor", - "general_title": "Geral", - "plugins_title": "Plugins" - }, - "Store": { - "store_contrib": { - "label": "Contribuindo", - "desc": "Se você deseja contribuir para a Loja de Plugins para o Decky, confira o repositório SteamDeckHomebrew/decky-plugin-template no GitHub. Informações sobre o desenvolvimento e distribuição estão disponíveis no README." - }, - "store_filter": { - "label": "Filtros", - "label_def": "Todos" - }, - "store_search": { - "label": "Buscar" - }, - "store_sort": { - "label": "Ordenar", - "label_def": "Último atualizado (Mais recente)" - }, - "store_source": { - "desc": "Todos os códigos fonte dos plugins estão disponíveis no repositório SteamDeckHomebrew/decky-plugin-database no GitHub.", - "label": "Código Fonte" - }, - "store_tabs": { - "about": "Sobre", - "alph_desc": "Alfabética (A - Z)", - "title": "Navegar", - "alph_asce": "Alfabética (Z - A)" - }, - "store_testing_cta": "Por favor, considere testar os novos plugins para ajudar o time do Decky Loader!" - }, - "StoreSelect": { - "custom_store": { - "label": "Loja Personalizada", - "url_label": "URL" - }, - "store_channel": { - "custom": "Personalizada", - "default": "Padrão", - "label": "Canal da Loja", - "testing": "Em Teste" - } - }, - "Updater": { - "no_patch_notes_desc": "nenhuma nota de alteração para esta versão", - "patch_notes_desc": "Notas de alteração", - "updates": { - "check_button": "Buscar Atualizações", - "checking": "Buscando", - "cur_version": "Versão atual: {{ver}}", - "install_button": "Instalar Atualização", - "label": "Atualizações", - "lat_version": "Atualizado: rodando {{ver}}", - "reloading": "Recarregando", - "updating": "Atualizando" - }, - "decky_updates": "Atualizações do Decky" - }, - "PluginView": { - "hidden_one": "1 plugin está oculto nesta lista", - "hidden_many": "{{count}} plugins estão ocultos nesta lista", - "hidden_other": "{{count}} plugins estão ocultos nesta lista" - }, - "DropdownMultiselect": { - "button": { - "back": "Voltar" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "O caminho especificado não é válido. Por favor, confira e reinsira corretamente.", - "unknown": "Ocorreu um erro desconhecido. O erro completo é: {{raw_error}}", - "perm_denied": "Você não tem acesso à este diretório. Por favor, verifiquei se seu usuário (deck no Steam Deck) tem as permissões necessárias para acessar este arquivo/pasta." - } - } -} diff --git a/backend/src/locales/pt-PT.json b/backend/src/locales/pt-PT.json deleted file mode 100644 index 9b273569..00000000 --- a/backend/src/locales/pt-PT.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "FilePickerIndex": { - "folder": { - "select": "Usar esta pasta" - } - }, - "PluginView": { - "hidden_one": "1 plugin está oculto desta lista", - "hidden_many": "{{count}} plugins estão ocultos desta lista", - "hidden_other": "{{count}} plugins estão ocultos desta lista" - }, - "PluginCard": { - "plugin_full_access": "Este plugin tem acesso total à tua Steam Deck.", - "plugin_install": "Instalar", - "plugin_version_label": "Versão do plugin", - "plugin_no_desc": "Não tem descrição." - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalar", - "button_processing": "Instalação em curso", - "title": "Instalar {{artifact}}", - "desc": "De certeza que queres instalar {{artifact}} {{version}}?" - }, - "reinstall": { - "button_idle": "Reinstalar", - "button_processing": "Reinstalação em curso", - "title": "Reinstalar {{artifact}}", - "desc": "De certeza que queres reinstalar {{artifact}} {{version}}?" - }, - "update": { - "button_idle": "Actualizar", - "button_processing": "Actualização em curso", - "title": "Actualizar {{artifact}}", - "desc": "De certeza que queres actualizar {{artifact}} {{version}}?" - }, - "no_hash": "Este plugin não tem um hash, estás a instalá-lo por tua conta e risco." - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Alterar 1 plugin", - "mixed_many": "Alterar {{count}} plugins", - "mixed_other": "Alterar {{count}} plugins", - "update_one": "Actualizar 1 plugin", - "update_many": "Actualizar {{count}} plugins", - "update_other": "Actualizar {{count}} plugins", - "reinstall_one": "Reinstalar 1 plugin", - "reinstall_many": "Reinstalar {{count}} plugins", - "reinstall_other": "Reinstalar {{count}} plugins", - "install_one": "Instalar 1 plugin", - "install_many": "Instalar {{count}} plugins", - "install_other": "Instalar {{count}} plugins" - }, - "ok_button": { - "idle": "Confirmar", - "loading": "Em curso" - }, - "description": { - "install": "Instalar {{name}} {{version}}", - "update": "Actualizar {{name}} para {{version}}", - "reinstall": "Reinstalar {{name}} {{version}}" - }, - "confirm": "De certeza que queres fazer as seguintes alterações?" - }, - "PluginListIndex": { - "no_plugin": "Nenhum plugin instalado!", - "reinstall": "Reinstalar", - "uninstall": "Desinstalar", - "update_to": "Actualizar para {{name}}", - "update_all_one": "Actualizar 1 plugin", - "update_all_many": "Actualizar {{count}} plugins", - "update_all_other": "Actualizar {{count}} plugins", - "plugin_actions": "Operações de plugin", - "reload": "Recarregar", - "show": "Acesso rápido: Mostrar", - "hide": "Acesso rápido: Ocultar" - }, - "BranchSelect": { - "update_channel": { - "stable": "Estável", - "testing": "Em teste", - "label": "Canal de actualização", - "prerelease": "Pré-lançamento" - } - }, - "Developer": { - "5secreload": "Vai recarregar em 5 segundos", - "disabling": "Desactivando React DevTools", - "enabling": "Activando React DevTools" - }, - "PluginListLabel": { - "hidden": "Oculto do menu de acesso rápido" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "Erro", - "plugin_load_error": { - "message": "Erro ao carregar o plugin {{name}}", - "toast": "Erro ao carregar {{name}}" - }, - "plugin_uninstall": { - "button": "Desinstalar", - "title": "Desinstalar {{name}}", - "desc": "De certeza que queres desinstalar {{name}}?" - }, - "decky_update_available": "Está disponível uma nova versão de {{tag_name}} !", - "plugin_update_one": "1 plugin tem actualizações disponíveis!", - "plugin_update_many": "{{count}} plugins têm actualizações disponíveis!", - "plugin_update_other": "{{count}} plugins têm actualizações disponíveis!", - "plugin_error_uninstall": "Houve uma excepção ao carregar {{name}}, como mostrado em cima. Pode ter sido porque o plugin requere a última versão do SteamUI. Verifica se há uma actualização disponível ou desinstala o plugin nas definições do Decky." - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Abrir consola", - "label": "Consola CEF", - "desc": "Abre a consola do CEF. Só é útil para efeitos de debugging. Pode ser perigosa e só deve ser usada se és um desenvolvedor de plugins, ou se foste aqui indicado por um desenvolvedor." - }, - "header": "Outros", - "react_devtools": { - "desc": "Permite a conecção a um computador que está a correr React DevTools. Mudar esta definição vai recarregar o Steam. Define o endereço de IP antes de activar.", - "ip_label": "IP", - "label": "Activar React DevTools" - }, - "third_party_plugins": { - "button_install": "Instalar", - "button_zip": "Navegar", - "header": "Plugins de terceiros", - "label_desc": "URl", - "label_url": "Instalar plugin a partir dum URL", - "label_zip": "Instalar plugin a partir dum ficheiro ZIP" - }, - "valve_internal": { - "label": "Activar menu interno da Valve", - "desc1": "Activa o menu interno de programador da Valve.", - "desc2": "Não toques em nada deste menu se não souberes a sua função." - } - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Permitir acesso não autenticado ao debugger do CEF a qualquer pessoa na tua rede", - "label": "Permitir debugging remoto do CEF" - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Versão do Decky", - "header": "Sobre" - }, - "beta": { - "header": "Participação na versão Beta" - }, - "developer_mode": { - "label": "Modo de programador" - }, - "other": { - "header": "Outros" - }, - "updates": { - "header": "Actualizações" - } - }, - "SettingsIndex": { - "developer_title": "Programador", - "general_title": "Geral", - "plugins_title": "Plugins" - }, - "Store": { - "store_contrib": { - "label": "Contribuir", - "desc": "Se queres contribuir com um novo plugin, vai ao repositório SteamDeckHomebrew/decky-plugin-template no GitHub. No README, podes encontrar mais informação sobre desenvolvimento e distribuição." - }, - "store_filter": { - "label": "Filtro", - "label_def": "Todos" - }, - "store_search": { - "label": "Procurar" - }, - "store_sort": { - "label": "Ordenar", - "label_def": "Última actualização (mais recente)" - }, - "store_source": { - "label": "Código fonte", - "desc": "O código fonte de cada plugin está disponível no repositório SteamDeckHomebrew/decky-plugin-database no GitHub." - }, - "store_tabs": { - "about": "Sobre", - "alph_asce": "Alfabeticamente (Z-A)", - "alph_desc": "Alfabeticamente (A-Z)", - "title": "Navegar" - }, - "store_testing_cta": "Testa novos plugins e ajuda a equipa do Decky Loader!" - }, - "StoreSelect": { - "custom_store": { - "url_label": "URL", - "label": "Loja personalizada" - }, - "store_channel": { - "custom": "Personalizada", - "default": "Standard", - "testing": "Em teste", - "label": "Canal de loja" - } - }, - "Updater": { - "decky_updates": "Actualizações do Decky", - "no_patch_notes_desc": "sem registo de alterações desta versão", - "patch_notes_desc": "Registo de alterações", - "updates": { - "check_button": "Procurar actualizações", - "checking": "Busca de actualizações em curso", - "cur_version": "Versão actual: {{ver}}", - "label": "Actualizações", - "lat_version": "Actualizado: a correr {{ver}}", - "updating": "Actualização em curso", - "reloading": "Recarregar", - "install_button": "Instalar actualização" - } - } -} diff --git a/backend/src/locales/ru-RU.json b/backend/src/locales/ru-RU.json deleted file mode 100644 index 776ffa12..00000000 --- a/backend/src/locales/ru-RU.json +++ /dev/null @@ -1,267 +0,0 @@ -{ - "MultiplePluginsInstallModal": { - "title": { - "update_one": "Переустановить {{count}} плагин", - "update_few": "Переустановить {{count}} плагинов", - "update_many": "Переустановить {{count}} плагинов", - "reinstall_one": "Переустановить {{count}} плагин", - "reinstall_few": "Переустановить {{count}} плагинов", - "reinstall_many": "Переустановить {{count}} плагинов", - "install_one": "Установить {{count}} плагин", - "install_few": "Установить {{count}} плагинов", - "install_many": "Установить {{count}} плагинов", - "mixed_one": "Изменить {{count}} плагин", - "mixed_few": "Изменить {{count}} плагинов", - "mixed_many": "Изменить {{count}} плагинов" - }, - "description": { - "install": "Установить {{name}} {{version}}", - "reinstall": "Переустановить {{name}} {{version}}", - "update": "Обновить с {{name}} на {{version}}" - }, - "confirm": "Вы уверены, что хотите внести следующие изменения?", - "ok_button": { - "idle": "Подтвердить", - "loading": "В процессе" - } - }, - "PluginListIndex": { - "update_all_one": "Обновить {{count}} плагин", - "update_all_few": "Обновить {{count}} плагинов", - "update_all_many": "Обновить {{count}} плагинов", - "hide": "Быстрый доступ: Скрыть", - "reload": "Перезагрузить", - "uninstall": "Удалить", - "update_to": "Обновить на {{name}}", - "show": "Быстрый доступ: Показать", - "plugin_actions": "Действия с плагинами", - "no_plugin": "Не установлено ни одного плагина!", - "reinstall": "Переустановить" - }, - "PluginLoader": { - "plugin_update_one": "Обновления доступны для {{count}} плагина!", - "plugin_update_few": "Обновления доступны для {{count}} плагинов!", - "plugin_update_many": "Обновления доступны для {{count}} плагинов!", - "plugin_error_uninstall": "Загрузка {{name}} вызвала исключение, указанное выше. Обычно это означает, что плагин требует обновления для новой версии SteamUI. Проверьте наличие обновления или попробуйте его удалить в настройках Decky, в разделе Плагины.", - "plugin_load_error": { - "message": "Ошибка загрузки плагина {{name}}", - "toast": "Ошибка загрузки {{name}}" - }, - "plugin_uninstall": { - "button": "Удалить", - "desc": "Вы уверены, что хотите удалить {{name}}?", - "title": "Удалить {{name}}" - }, - "decky_title": "Decky", - "decky_update_available": "Доступно обновление на {{tag_name}}!", - "error": "Ошибка" - }, - "PluginView": { - "hidden_one": "{{count}} плагин скрыт из списка", - "hidden_few": "{{count}} плагинов скрыт из списка", - "hidden_many": "{{count}} плагинов скрыт из списка" - }, - "FilePickerIndex": { - "files": { - "show_hidden": "Показать скрытые файлы", - "all_files": "Все файлы", - "file_type": "Тип файла" - }, - "filter": { - "created_asce": "Создан (самый старый)", - "modified_asce": "Модифицирован (самый новый)", - "modified_desc": "Модифицирован (самый старый)", - "size_asce": "Размер (самый малый)", - "size_desc": "Размер (самый большой)", - "name_asce": "Z-A", - "name_desc": "A-Z", - "created_desc": "Создан (самый новый)" - }, - "folder": { - "label": "Папка", - "show_more": "Показать больше файлов", - "select": "Использовать этот каталог" - }, - "file": { - "select": "Выберите этот файл" - } - }, - "PluginCard": { - "plugin_install": "Установить", - "plugin_no_desc": "Нет описания.", - "plugin_version_label": "Версия плагина", - "plugin_full_access": "Этот плагин имеет полный доступ к вашему Steam Deck." - }, - "PluginInstallModal": { - "install": { - "button_processing": "Установка", - "title": "Установить {{artifact}}", - "button_idle": "Установить", - "desc": "Вы уверены, что хотите установить {{artifact}} {{version}}?" - }, - "no_hash": "У данного плагина отсутствует хэш, устанавливайте на свой страх и риск.", - "reinstall": { - "title": "Переустановить {{artifact}}", - "desc": "Вы уверены, что хотите переустановить {{artifact}} {{version}}?", - "button_idle": "Переустановить", - "button_processing": "Переустановка" - }, - "update": { - "button_idle": "Обновить", - "button_processing": "Обновление", - "desc": "Вы уверены, что хотите обновить {{artifact}} {{version}}?", - "title": "Обновить {{artifact}}" - } - }, - "PluginListLabel": { - "hidden": "Скрыто из меню быстрого доступа" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Разрешить неаутентифицированный доступ к отладчику CEF всем в вашей сети", - "label": "Разрешить удаленную отладку CEF" - } - }, - "SettingsDeveloperIndex": { - "header": "Другое", - "third_party_plugins": { - "button_install": "Установить", - "label_zip": "Установить плагин из ZIP файла", - "label_url": "Установить плагин из URL", - "button_zip": "Обзор", - "header": "Сторонние плагины", - "label_desc": "Ссылка" - }, - "react_devtools": { - "ip_label": "IP", - "desc": "Позволяет подключиться к компьютеру, на котором работает React DevTools. Изменение этого параметра приведет к перезагрузке Steam. Установите IP-адрес перед включением.", - "label": "Включить React DevTools" - }, - "cef_console": { - "button": "Открыть консоль", - "desc": "Открывает консоль CEF. Полезно только для целей отладки. Настройки здесь потенциально опасны и должны использоваться только в том случае, если вы являетесь разработчиком плагинов или направленны сюда одним из них.", - "label": "CEF Консоль" - }, - "valve_internal": { - "desc1": "Включает внутреннее меню разработчика Valve.", - "label": "Включить Valve Internal", - "desc2": "Ничего не трогайте в этом меню, если не знаете, что оно делает." - } - }, - "SettingsGeneralIndex": { - "beta": { - "header": "Бета программа" - }, - "developer_mode": { - "label": "Режим разработчика" - }, - "other": { - "header": "Другое" - }, - "about": { - "decky_version": "Версия Decky", - "header": "Информация" - }, - "updates": { - "header": "Обновления" - }, - "notifications": { - "decky_updates_label": "Обновление Decky доступно", - "header": "Уведомления", - "plugin_updates_label": "Доступны обновления плагинов" - } - }, - "Store": { - "store_sort": { - "label": "Сортировка", - "label_def": "Последнее обновление(самые новые)" - }, - "store_source": { - "label": "Исходный код", - "desc": "Весь исходный код плагина доступен в репозитории SteamDeckHomebrew/decky-plugin-database на GitHub." - }, - "store_tabs": { - "about": "Информация", - "alph_desc": "По алфавиту (A - Z)", - "title": "Обзор", - "alph_asce": "По алфавиту (Z - A)" - }, - "store_testing_cta": "Пожалуйста, рассмотрите возможность тестирования новых плагинов, чтобы помочь команде Decky Loader!", - "store_contrib": { - "desc": "Если вы хотите внести свой вклад в магазин плагинов Decky, проверьте репозиторий SteamDeckHomebrew/decky-plugin-template на GitHub. Информация о разработке и распространении доступна в README.", - "label": "Помощь проекту" - }, - "store_filter": { - "label": "Фильтр", - "label_def": "Все" - }, - "store_search": { - "label": "Поиск" - }, - "store_testing_warning": { - "label": "Добро пожаловать в тестовый канал магазина", - "desc": "Вы можете использовать этот канал магазина для тестирования новейших версий плагинов. Не забудьте оставить отзыв на GitHub, чтобы плагин можно было обновить для всех пользователей." - } - }, - "StoreSelect": { - "custom_store": { - "label": "Сторонний магазин", - "url_label": "URL" - }, - "store_channel": { - "custom": "Сторонний", - "default": "По-умолчанию", - "label": "Канал магазина", - "testing": "Тестовый" - } - }, - "Updater": { - "decky_updates": "Обновления Decky", - "no_patch_notes_desc": "нет примечаний к патчу для этой версии", - "updates": { - "check_button": "Проверить обновления", - "checking": "Проверка", - "cur_version": "Текущая версия: {{ver}}", - "updating": "Обновление", - "install_button": "Установить обновление", - "label": "Обновления", - "lat_version": "Обновлено: версия {{ver}}", - "reloading": "Перезагрузка" - }, - "patch_notes_desc": "Примечания к патчу" - }, - "FilePickerError": { - "errors": { - "perm_denied": "У вас нет доступа к указанному каталогу.. Пожалуйста, проверьте имеет ли пользователь (deck на Steam Deck) соответствующие права доступа к указанной папке/файлу.", - "file_not_found": "Указан недействительный путь. Пожалуйста, проверьте его и повторите ввод.", - "unknown": "Произошла неизвестная ошибка. Текст ошибки: {{raw_error}}" - } - }, - "DropdownMultiselect": { - "button": { - "back": "Назад" - } - }, - "BranchSelect": { - "update_channel": { - "prerelease": "Предрелиз", - "stable": "Стабильный", - "testing": "Тестовый", - "label": "Канал обновлений" - } - }, - "Developer": { - "5secreload": "Перезагрузка через 5 секунд", - "disabling": "Выключение React DevTools", - "enabling": "Включение React DevTools" - }, - "SettingsIndex": { - "developer_title": "Разработчик", - "general_title": "Общее", - "plugins_title": "Плагины" - }, - "TitleView": { - "decky_store_desc": "Открыть магазин Decky", - "settings_desc": "Открыть настройки Decky" - } -} diff --git a/backend/src/locales/sq-AL.json b/backend/src/locales/sq-AL.json deleted file mode 100644 index fe9d7eec..00000000 --- a/backend/src/locales/sq-AL.json +++ /dev/null @@ -1,131 +0,0 @@ -{ - "SettingsDeveloperIndex": { - "react_devtools": { - "ip_label": "IP", - "label": "Aktivizo React DevTools" - }, - "third_party_plugins": { - "button_zip": "Kërko", - "header": "Shtesa të Huaj", - "button_install": "Instalo", - "label_desc": "URL", - "label_url": "Instalo Shtes Nga URL", - "label_zip": "Instalo Shtes Nga ZIP" - } - }, - "BranchSelect": { - "update_channel": { - "stable": "Fiksuar", - "label": "Kanali Përditësimet" - } - }, - "FilePickerIndex": { - "folder": { - "select": "Përdore këtë folder" - } - }, - "PluginCard": { - "plugin_install": "Instalo", - "plugin_version_label": "Versioni Shteses" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Instalo", - "button_processing": "Instalohet", - "desc": "Je i sigurt që don ta instalojsh {{artifact}} {{version}}?", - "title": "Instalo {{artifact}}" - }, - "no_hash": "Ky shtesë nuk ka hash, ti e instalon me rrezikun tuaj.", - "reinstall": { - "button_idle": "Riinstalo", - "button_processing": "Riinstalohet", - "desc": "Je i sigurt a don ta riinstalojsh {{artifact}} {{version}}?", - "title": "Riinstalo {{artifact}}" - }, - "update": { - "button_processing": "Përditësohet", - "desc": "Je i sigurt a don ta përditësojsh {{artifact}} {{version}}?", - "title": "Përditëso {{artifact}}" - } - }, - "PluginLoader": { - "decky_title": "Decky", - "plugin_uninstall": { - "title": "Çinstalo {{name}}", - "button": "Çinstalo", - "desc": "Je i sigurt që don ta çinstalojsh {{name}}?" - }, - "error": "Gabim", - "plugin_error_uninstall": "Ju lutem shko nga {{name}} në Decky menu nëse don ta çinstalojsh këtë shtese.", - "plugin_update_one": "", - "plugin_update_other": "" - }, - "PluginListIndex": { - "no_plugin": "Nuk ka shtesa të instaluar!", - "uninstall": "Çinstalo", - "update_all_one": "", - "update_all_other": "" - }, - "SettingsGeneralIndex": { - "other": { - "header": "Të Tjera" - }, - "about": { - "decky_version": "Versioni Decky" - }, - "updates": { - "header": "Përmirësimet" - } - }, - "SettingsIndex": { - "developer_title": "Zhvillues", - "general_title": "Gjeneral" - }, - "Store": { - "store_sort": { - "label": "Rendit" - }, - "store_tabs": { - "title": "Kërko" - }, - "store_contrib": { - "label": "Kontributi" - }, - "store_filter": { - "label": "Filtro", - "label_def": "Të Gjitha" - }, - "store_search": { - "label": "Kërko" - }, - "store_source": { - "label": "Kodin Burimor" - } - }, - "StoreSelect": { - "store_channel": { - "label": "Kanali Dyqanit" - } - }, - "Updater": { - "updates": { - "cur_version": "Versioni e tanishëme: {{ver}}" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "", - "mixed_other": "", - "update_one": "", - "update_other": "", - "reinstall_one": "", - "reinstall_other": "", - "install_one": "", - "install_other": "" - } - }, - "PluginView": { - "hidden_one": "", - "hidden_other": "" - } -} diff --git a/backend/src/locales/uk-UA.json b/backend/src/locales/uk-UA.json deleted file mode 100644 index 09fbca1b..00000000 --- a/backend/src/locales/uk-UA.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "Передреліз", - "testing": "Тестовий", - "label": "Канал оновлень", - "stable": "Стабільний" - } - }, - "Developer": { - "5secreload": "Перезавантаження за 5 секунд", - "enabling": "Увімкнення React DevTools", - "disabling": "Вимкнення React DevTools" - }, - "FilePickerIndex": { - "folder": { - "select": "Використовувати цю папку" - } - }, - "PluginListLabel": { - "hidden": "Приховано з меню швидкого доступу" - }, - "PluginCard": { - "plugin_full_access": "Цей плагін має повний доступ до вашого Steam Deck.", - "plugin_install": "Встановити", - "plugin_no_desc": "Опис не надано.", - "plugin_version_label": "Версія плагіна" - }, - "PluginInstallModal": { - "install": { - "button_idle": "Встановити", - "button_processing": "Встановлення", - "title": "Встановити {{artifact}}", - "desc": "Ви впевнені, що хочете встановити {{artifact}} {{version}}?" - }, - "reinstall": { - "button_idle": "Перевстановити", - "desc": "Ви впевнені, що хочете перевстановити {{artifact}} {{version}}?", - "title": "Перевстановити {{artifact}}", - "button_processing": "Перевстановлення" - }, - "update": { - "button_idle": "Оновити", - "button_processing": "Оновлення", - "title": "Оновити {{artifact}}", - "desc": "Ви впевнені, що хочете оновити {{artifact}} {{version}}?" - }, - "no_hash": "Цей плагін не має хешу, ви встановлюєте його на власний ризик." - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_one": "Модифікувати 1 плагін", - "mixed_few": "Модифікувати {{count}} плагінів", - "mixed_many": "", - "reinstall_one": "Перевстановити 1 плагін", - "reinstall_few": "Перевстановити {{count}} плагінів", - "reinstall_many": "Перевстановити {{count}} плагінів", - "update_one": "Оновити 1 плагін", - "update_few": "Оновити {{count}} плагінів", - "update_many": "Оновити {{count}} плагінів", - "install_one": "Встановити 1 плагін", - "install_few": "Встановити {{count}} плагінів", - "install_many": "Встановити {{count}} плагінів" - }, - "ok_button": { - "idle": "Підтвердити", - "loading": "Опрацювання" - }, - "description": { - "install": "Встановити {{name}} {{version}}", - "update": "Оновити {{name}} до {{version}}", - "reinstall": "Перевстановити {{name}} {{version}}" - }, - "confirm": "Ви впевнені, що хочете застосувати такі модифікації?" - }, - "PluginListIndex": { - "no_plugin": "Плагінів не встановлено!", - "plugin_actions": "Дії плагінів", - "reinstall": "Перевстановити", - "reload": "Перезавантажити", - "update_to": "Оновити {{name}}", - "show": "Швидкий доступ: Показати", - "hide": "Швидкий доступ: Приховати", - "uninstall": "Видалити", - "update_all_one": "Оновити 1 плагін", - "update_all_few": "Оновити {{count}} плагінів", - "update_all_many": "Оновити {{count}} плагінів" - }, - "PluginLoader": { - "decky_title": "Decky", - "decky_update_available": "Доступне оновлення до {{tag_name}}!", - "error": "Помилка", - "plugin_load_error": { - "message": "Помилка завантаження плагіна {{name}}", - "toast": "Помилка завантаження {{name}}" - }, - "plugin_uninstall": { - "desc": "Ви впевнені, що хочете видалити {{name}}?", - "title": "Видалити {{name}}", - "button": "Видалення" - }, - "plugin_error_uninstall": "Завантаження {{name}} спровокувало помилку показану вище. Зазвичай це означає, що плагін вимагає оновлення до нової версії SteamUI. Перевірте чи таке оновлення доступне або виконайте його видалення у налаштуваннях Decky, у секції Плагіни.", - "plugin_update_one": "Доступне оновлення для 1 плагіна!", - "plugin_update_few": "Доступне оновлення для {{count}} плагінів!", - "plugin_update_many": "Доступне оновлення для {{count}} плагінів!" - }, - "SettingsDeveloperIndex": { - "cef_console": { - "button": "Відкрити консоль", - "label": "CEF-консоль", - "desc": "Відкрити CEF-консоль. Корисно тільки для дебагу. Ця штука потенційно небезпечна і повинна використовувати виключно якщо ви розробник плагіна, або якщо розробник спрямував вас сюди." - }, - "header": "Інше", - "react_devtools": { - "desc": "Вмикає доступ до компʼютера із запущеним React DevTools. Зміна цього налаштування перезавантажить Steam. Вкажіть IP перед увімкненням.", - "label": "Увімкнути React DevTools", - "ip_label": "IP" - }, - "third_party_plugins": { - "button_install": "Встановити", - "header": "Сторонні плагіни", - "label_desc": "URL", - "label_url": "Встановити плагін з URL", - "label_zip": "Встановити плагін з ZIP-файлу", - "button_zip": "Огляд" - }, - "valve_internal": { - "desc1": "Вмикає внутрішнє розробницьке меню Valve.", - "label": "Увімкнути Valve Internal", - "desc2": "Нічого не торкайтесь у цьому меню, якщо не розумієте, що ви робите." - } - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Версія Decky", - "header": "Про нас" - }, - "beta": { - "header": "Участь у Beta" - }, - "developer_mode": { - "label": "Розробницький режим" - }, - "other": { - "header": "Інше" - }, - "updates": { - "header": "Оновлення" - } - }, - "SettingsIndex": { - "developer_title": "Розробник", - "general_title": "Загальне", - "plugins_title": "Плагіни" - }, - "Store": { - "store_contrib": { - "label": "Зробити внесок", - "desc": "Якщо ви бажаєте додати щось у Decky Plugin Store, завітайте у репозиторій SteamDeckHomebrew/decky-plugin-template на GitHub. Інформація про розробку та поширення доступна у README." - }, - "store_filter": { - "label": "Фільтр", - "label_def": "Усе" - }, - "store_search": { - "label": "Пошук" - }, - "store_sort": { - "label": "Сортування", - "label_def": "Востаннє оновлені (Найновіші)" - }, - "store_source": { - "label": "Вихідний код", - "desc": "Код усіх плагінів доступний у репозиторії SteamDeckHomebrew/decky-plugin-database на GitHub." - }, - "store_tabs": { - "about": "Інформація", - "alph_asce": "За алфавітом (Z до A)", - "alph_desc": "За алфавітом (A до Z)", - "title": "Огляд" - }, - "store_testing_cta": "Розгляньте можливість тестування нових плагінів, щоб допомогти команді Decky Loader!" - }, - "StoreSelect": { - "custom_store": { - "label": "Власний магазин", - "url_label": "URL" - }, - "store_channel": { - "custom": "Власний", - "default": "За замовчуванням", - "testing": "Тестування", - "label": "Канал магазину" - } - }, - "Updater": { - "decky_updates": "Оновлення Decky", - "no_patch_notes_desc": "Немає нотаток до цієї версії", - "patch_notes_desc": "Перелік змін", - "updates": { - "checking": "Перевірка", - "cur_version": "Поточна версія: {{ver}}", - "install_button": "Встановити оновлення", - "label": "Оновлення", - "reloading": "Перезавантаження", - "updating": "Оновлення", - "check_button": "Перевірити оновлення", - "lat_version": "Оновлено: використовується {{ver}}" - } - }, - "PluginView": { - "hidden_one": "{{count}} плагін приховано з цього списку", - "hidden_few": "{{count}} плагінів приховано з цього списку", - "hidden_many": "{{count}} плагінів приховано з цього списку" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "Дозволити доступ до CEF-дебагера без аутентифікації для будь-кого у вашій мережі", - "label": "Дозволити віддалений CEF-дебагінг" - } - } -} diff --git a/backend/src/locales/zh-CN.json b/backend/src/locales/zh-CN.json deleted file mode 100644 index d9d12aa0..00000000 --- a/backend/src/locales/zh-CN.json +++ /dev/null @@ -1,253 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "prerelease": "发布候选", - "stable": "稳定", - "testing": "测试", - "label": "更新通道" - } - }, - "Developer": { - "5secreload": "5 秒钟后重新加载", - "disabling": "正在禁用 React DevTools", - "enabling": "正在启用 React DevTools" - }, - "FilePickerIndex": { - "folder": { - "select": "使用这个文件夹", - "label": "文件夹", - "show_more": "显示更多文件" - }, - "filter": { - "created_asce": "创建日期(最旧)", - "created_desc": "创建日期(最新)", - "modified_asce": "修改日期(最旧)", - "modified_desc": "修改日期(最新)", - "name_asce": "字母降序", - "name_desc": "字母升序", - "size_asce": "大小(最小)", - "size_desc": "大小(最大)" - }, - "files": { - "all_files": "全部文件", - "file_type": "文件类型", - "show_hidden": "显示隐藏文件" - }, - "file": { - "select": "选择此文件" - } - }, - "PluginCard": { - "plugin_install": "安装", - "plugin_no_desc": "无描述提供。", - "plugin_version_label": "插件版本", - "plugin_full_access": "此插件可以完全访问你的 Steam Deck。" - }, - "PluginInstallModal": { - "install": { - "button_idle": "安装", - "button_processing": "安装中", - "desc": "你确定要安装 {{artifact}} {{version}} 吗?", - "title": "安装 {{artifact}}" - }, - "reinstall": { - "button_idle": "重新安装", - "button_processing": "正在重新安装", - "desc": "你确定要重新安装 {{artifact}} {{version}} 吗?", - "title": "重新安装 {{artifact}}" - }, - "update": { - "button_idle": "更新", - "button_processing": "正在更新", - "desc": "你确定要更新 {{artifact}} {{version}} 吗?", - "title": "更新 {{artifact}}" - }, - "no_hash": "此插件没有哈希校验值,你需要自行承担安装风险。" - }, - "PluginListIndex": { - "no_plugin": "没有安装插件!", - "plugin_actions": "插件操作", - "reinstall": "重新安装", - "reload": "重新加载", - "uninstall": "卸载", - "update_to": "更新 {{name}}", - "update_all_other": "更新 {{count}} 个插件", - "show": "在快速访问菜单中显示", - "hide": "在快速访问菜单中隐藏" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "错误", - "plugin_error_uninstall": "加载 {{name}} 时引起了上述异常。这通常意味着插件需要更新以适应 SteamUI 的新版本。请检查插件是否有更新,或在 Decky 设置中的插件部分将其移除。", - "plugin_load_error": { - "message": "加载插件 {{name}} 错误", - "toast": "加载插件 {{name}} 发生了错误" - }, - "plugin_uninstall": { - "button": "卸载", - "title": "卸载 {{name}}", - "desc": "你确定要卸载 {{name}} 吗?" - }, - "decky_update_available": "新版本 {{tag_name}} 可用!", - "plugin_update_other": "{{count}} 个插件有更新!" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "允许你网络中的任何人无需身份验证即可访问CEF调试器", - "label": "允许远程访问CEF调试" - } - }, - "SettingsDeveloperIndex": { - "react_devtools": { - "ip_label": "IP", - "label": "启用 React DevTools", - "desc": "允许连接到运行着 React DevTools 的计算机,更改此设置将重新加载Steam,请在启用前设置IP地址。" - }, - "third_party_plugins": { - "button_install": "安装", - "button_zip": "浏览文件", - "header": "第三方插件", - "label_desc": "URL", - "label_url": "从 URL 安装插件", - "label_zip": "从 ZIP 压缩文件安装插件" - }, - "valve_internal": { - "desc1": "启用 Valve 内部开发者菜单。", - "desc2": "除非你知道你在干什么,否则请不要修改此菜单中的任何内容。", - "label": "启用 Valve 内部开发者" - }, - "cef_console": { - "button": "打开控制台", - "label": "CEF 控制台", - "desc": "打开 CEF 控制台。仅在调试目的下使用。这列选项均有风险,请仅在您是插件开发者或是在插件开发者指导时访问使用。" - }, - "header": "其他" - }, - "SettingsGeneralIndex": { - "about": { - "decky_version": "Decky 版本", - "header": "关于" - }, - "beta": { - "header": "参与测试" - }, - "developer_mode": { - "label": "开发者模式" - }, - "other": { - "header": "其他" - }, - "updates": { - "header": "更新" - }, - "notifications": { - "header": "通知", - "decky_updates_label": "Decky 更新可用", - "plugin_updates_label": "插件更新可用" - } - }, - "SettingsIndex": { - "developer_title": "开发者", - "general_title": "通用", - "plugins_title": "插件" - }, - "Store": { - "store_contrib": { - "label": "贡献", - "desc": "如果你想要提交你的插件到 Decky 插件商店,请访问 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 存储库,关于开发和分发的相关信息,请查看 README 文件。" - }, - "store_filter": { - "label": "过滤器", - "label_def": "全部" - }, - "store_search": { - "label": "搜索" - }, - "store_sort": { - "label": "排序", - "label_def": "最后更新 (最新)" - }, - "store_source": { - "label": "源代码", - "desc": "所有插件的源代码都可以在 GitHub 上的 SteamDeckHomebrew/decky-plugin-database 存储库中获得。" - }, - "store_tabs": { - "about": "关于", - "alph_asce": "字母排序 (Z 到 A)", - "alph_desc": "字母排序 (A 到 Z)", - "title": "浏览" - }, - "store_testing_cta": "请考虑测试新插件以帮助 Decky Loader 团队!", - "store_testing_warning": { - "desc": "你可以使用该商店频道以体验最新版本的插件。 请在插件 Github 页面留言以使插件可以正式面向所有用户。", - "label": "欢迎来到商店测试频道" - } - }, - "StoreSelect": { - "store_channel": { - "default": "默认", - "label": "商店通道", - "testing": "测试", - "custom": "自定义" - }, - "custom_store": { - "label": "自定义商店", - "url_label": "URL" - } - }, - "Updater": { - "decky_updates": "Decky 更新", - "no_patch_notes_desc": "此版本没有补丁说明", - "patch_notes_desc": "补丁说明", - "updates": { - "check_button": "检查更新", - "checking": "检查中", - "cur_version": "当前版本: {{ver}}", - "install_button": "安装更新", - "label": "更新", - "lat_version": "已是最新版本: {{ver}} 运行中", - "reloading": "重新加载中", - "updating": "更新中" - } - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_other": "更改 {{count}} 个插件", - "update_other": "更新 {{count}} 个插件", - "reinstall_other": "重装 {{count}} 个插件", - "install_other": "安装 {{count}} 个插件" - }, - "ok_button": { - "idle": "确认", - "loading": "工作中" - }, - "confirm": "确定要进行以下修改吗?", - "description": { - "install": "安装 {{name}} {{version}}", - "update": "更新 {{name}} to {{version}}", - "reinstall": "重装 {{name}} {{version}}" - } - }, - "PluginListLabel": { - "hidden": "在快速访问菜单中已隐藏" - }, - "PluginView": { - "hidden_other": "此列表隐藏了 {{count}} 个插件" - }, - "DropdownMultiselect": { - "button": { - "back": "返回" - } - }, - "FilePickerError": { - "errors": { - "file_not_found": "指定路径无效。请检查并输入正确的路径。", - "unknown": "发生了一个未知错误。原始错误为:{{raw_error}}", - "perm_denied": "你没有访问特定目录的权限。请检查你的用户(Steam Deck 中的 deck 账户)有着相对应的权限以访问特定的文件夹或文件。" - } - }, - "TitleView": { - "decky_store_desc": "打开 Decky 商店", - "settings_desc": "打开 Decky 设置" - } -} diff --git a/backend/src/locales/zh-TW.json b/backend/src/locales/zh-TW.json deleted file mode 100644 index 2891aa9c..00000000 --- a/backend/src/locales/zh-TW.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "BranchSelect": { - "update_channel": { - "testing": "測試版", - "label": "更新頻道", - "prerelease": "預發佈", - "stable": "穩定版" - } - }, - "Developer": { - "5secreload": "5 秒後重新載入", - "disabling": "正在停用 React DevTools", - "enabling": "正在啟用 React DevTools" - }, - "FilePickerIndex": { - "folder": { - "select": "使用此資料夾", - "show_more": "顯示更多檔案", - "label": "資料夾" - }, - "filter": { - "modified_asce": "修改日期(舊到新)", - "created_desc": "建立日期(新到舊)", - "modified_desc": "修改日期(新到舊)", - "name_desc": "子母排序(A到Z)", - "name_asce": "子母排序(Z到A)", - "size_asce": "檔案大小(小到大)", - "size_desc": "檔案大小(大到小)", - "created_asce": "建立日期(舊到新)" - }, - "file": { - "select": "選擇此檔案" - }, - "files": { - "all_files": "所有檔案", - "file_type": "檔案類型", - "show_hidden": "顯示隱藏檔" - } - }, - "PluginCard": { - "plugin_install": "安裝", - "plugin_no_desc": "未提示描述。", - "plugin_version_label": "外掛程式版本", - "plugin_full_access": "此外掛程式擁有您的 Steam Deck 的完整存取權。" - }, - "PluginInstallModal": { - "install": { - "button_idle": "安裝", - "button_processing": "正在安裝", - "title": "安裝 {{artifact}}", - "desc": "您確定要安裝 {{artifact}} {{version}} 嗎?" - }, - "reinstall": { - "button_idle": "重新安裝", - "button_processing": "正在重新安裝", - "desc": "您確定要重新安裝 {{artifact}} {{version}} 嗎?", - "title": "重新安裝 {{artifact}}" - }, - "update": { - "button_idle": "更新", - "button_processing": "正在更新", - "desc": "您確定要更新 {{artifact}} {{version}} 嗎?", - "title": "更新 {{artifact}}" - }, - "no_hash": "此外掛程式沒有提供 hash 驗證,安裝可能有風險。" - }, - "PluginListIndex": { - "no_plugin": "未安裝外掛程式!", - "plugin_actions": "外掛程式操作", - "uninstall": "解除安裝", - "update_to": "更新到 {{name}}", - "reinstall": "重新安裝", - "reload": "重新載入", - "show": "快速存取:顯示", - "hide": "快速存取:隱藏", - "update_all_other": "更新 {{count}} 個外掛程式" - }, - "PluginLoader": { - "decky_title": "Decky", - "error": "錯誤", - "plugin_error_uninstall": "載入 {{name}} 導致上述異常。這通常意味著該外掛程式需要針對新版本的 SteamUI 進行更新。在 Decky 設定中檢查是否存在更新,或評估刪除此外掛程式。", - "plugin_load_error": { - "message": "載入外掛程式 {{name}} 發生錯誤", - "toast": "{{name}} 載入出錯" - }, - "plugin_uninstall": { - "button": "解除安裝", - "title": "解除安裝 {{name}}", - "desc": "您確定要解除安裝 {{name}} 嗎?" - }, - "decky_update_available": "可更新至版本 {{tag_name}}!", - "plugin_update_other": "可更新 {{count}} 個外掛程式!" - }, - "RemoteDebugging": { - "remote_cef": { - "desc": "允許您的網路中的任何人未經認證地存取 CEF 偵錯器", - "label": "允許 CEF 遠端偵錯" - } - }, - "SettingsDeveloperIndex": { - "third_party_plugins": { - "button_zip": "開啟", - "label_desc": "網址", - "label_url": "從網址安裝外掛程式", - "label_zip": "從 ZIP 檔案安裝外掛程式", - "button_install": "安裝", - "header": "第三方外掛程式" - }, - "valve_internal": { - "desc2": "除非您知道它的作用,否則不要碰這個選單中的任何東西。", - "desc1": "啟用 Valve 內建開發人員選單。", - "label": "啟用 Valve 內建" - }, - "react_devtools": { - "desc": "啟用與執行 React DevTools 的電腦的連接。改變這個設定將重新載入 Steam。啟用前必須設定 IP 位址。", - "ip_label": "IP", - "label": "啟用 React DevTools" - }, - "header": "其他", - "cef_console": { - "button": "開啟控制台", - "label": "CEF 控制台", - "desc": "開啟 CEF 控制台。僅用於偵錯。這裡的東西有潛在的風險,只有當您是一個外掛程式開發者或者被外掛程式開發者引導到這裡時,才應該使用。" - } - }, - "SettingsGeneralIndex": { - "about": { - "header": "關於", - "decky_version": "Decky 版本" - }, - "beta": { - "header": "參與測試" - }, - "developer_mode": { - "label": "開發人員模式" - }, - "other": { - "header": "其他" - }, - "updates": { - "header": "更新" - }, - "notifications": { - "decky_updates_label": "Decky 可更新", - "header": "通知", - "plugin_updates_label": "外掛程式有更新" - } - }, - "SettingsIndex": { - "developer_title": "開發人員", - "general_title": "一般", - "plugins_title": "外掛程式" - }, - "Store": { - "store_contrib": { - "label": "貢獻", - "desc": "如果您想為 Decky 外掛程式商店做貢獻,請查看 GitHub 上的 SteamDeckHomebrew/decky-plugin-template 儲存庫。README 中提供了有關開發和發佈的資訊。" - }, - "store_filter": { - "label": "過濾", - "label_def": "全部" - }, - "store_search": { - "label": "搜尋" - }, - "store_sort": { - "label": "排序", - "label_def": "最後更新 (最新)" - }, - "store_source": { - "label": "原始碼", - "desc": "所有外掛程式原始碼可以在 GitHub 的 SteamDeckHomebrew/decky-plugin-database 儲存庫查看。" - }, - "store_tabs": { - "about": "關於", - "alph_asce": "依字母排序 (Z 到 A)", - "alph_desc": "依字母排序 (A 到 Z)", - "title": "瀏覽" - }, - "store_testing_cta": "請考慮測試新的外掛程式來幫助 Decky Loader 團隊!" - }, - "StoreSelect": { - "custom_store": { - "label": "自訂商店", - "url_label": "網址" - }, - "store_channel": { - "custom": "自訂", - "default": "預設", - "label": "商店頻道", - "testing": "測試" - } - }, - "Updater": { - "decky_updates": "Decky 更新", - "no_patch_notes_desc": "這個版本沒有更新日誌", - "patch_notes_desc": "更新日誌", - "updates": { - "checking": "正在檢查", - "install_button": "安裝更新", - "label": "更新", - "lat_version": "已是最新:執行 {{ver}}", - "reloading": "正在重新載入", - "check_button": "檢查更新", - "cur_version": "目前版本:{{ver}}", - "updating": "正在更新" - } - }, - "PluginView": { - "hidden_other": "{{count}} 個外掛程式已隱藏" - }, - "PluginListLabel": { - "hidden": "已從快速存取選單中移除" - }, - "MultiplePluginsInstallModal": { - "title": { - "mixed_other": "修改 {{count}} 個外掛程式", - "update_other": "更新 {{count}} 個外掛程式", - "reinstall_other": "重新安裝 {{count}} 個外掛程式", - "install_other": "安裝 {{count}} 個外掛程式" - }, - "ok_button": { - "idle": "確定", - "loading": "執行中" - }, - "confirm": "您確定要進行以下的修改嗎?", - "description": { - "install": "安裝 {{name}} {{version}}", - "update": "更新 {{name}} 到 {{version}}", - "reinstall": "重新安裝 {{name}} {{version}}" - } - }, - "FilePickerError": { - "errors": { - "perm_denied": "您沒有瀏覽此目錄的權限。請檢查您的使用者(Steam Deck 中的 deck 帳號)有權限瀏覽特定的資料夾或檔案。", - "unknown": "發生未知錯誤。錯誤詳細資料:{{raw_error}}", - "file_not_found": "指定路徑無效。請檢查並輸入正確路徑。" - } - }, - "DropdownMultiselect": { - "button": { - "back": "返回" - } - } -} -- cgit v1.2.3 From 06fccb792f33fbf728940fdcfb62db9017fb05c6 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 30 Sep 2023 12:42:02 -0400 Subject: fix ci (hopefully, because act wont work) --- backend/pyrightconfig.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backend/pyrightconfig.json (limited to 'backend') diff --git a/backend/pyrightconfig.json b/backend/pyrightconfig.json new file mode 100644 index 00000000..9937f227 --- /dev/null +++ b/backend/pyrightconfig.json @@ -0,0 +1,3 @@ +{ + "strict": ["*"] +} \ No newline at end of file -- cgit v1.2.3 From 458fa6a66c74e44297443e211eca7ef6a3cb9b58 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 30 Sep 2023 12:43:35 -0400 Subject: fix broken import --- backend/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'backend') diff --git a/backend/main.py b/backend/main.py index c2b99089..46a0671a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,4 +1,4 @@ -# This file is needed to make the relative imports in backend/ work properly. +# This file is needed to make the relative imports in src/ work properly. if __name__ == "__main__": - from backend.main import main + from src.main import main main() -- cgit v1.2.3 From 949244e8e6b54921beeefab3524c2de63914f737 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 30 Sep 2023 13:15:35 -0400 Subject: fix paths --- backend/src/loader.py | 4 ++-- backend/src/main.py | 2 +- backend/src/updater.py | 3 ++- backend/src/utilities.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) (limited to 'backend') diff --git a/backend/src/loader.py b/backend/src/loader.py index 684570f7..e59cbcaf 100644 --- a/backend/src/loader.py +++ b/backend/src/loader.py @@ -107,13 +107,13 @@ class Loader: self.watcher.disabled = False async def handle_frontend_assets(self, request: web.Request): - file = path.join(path.dirname(__file__), "static", request.match_info["path"]) + file = path.join(path.dirname(__file__), "..", "static", request.match_info["path"]) return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) async def handle_frontend_locales(self, request: web.Request): req_lang = request.match_info["path"] - file = path.join(path.dirname(__file__), "locales", req_lang) + file = path.join(path.dirname(__file__), "..", "locales", req_lang) if exists(file): return web.FileResponse(file, headers={"Cache-Control": "no-cache", "Content-Type": "application/json"}) else: diff --git a/backend/src/main.py b/backend/src/main.py index 793d000c..83a4b997 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -86,7 +86,7 @@ class PluginManager: for route in list(self.web_app.router.routes()): self.cors.add(route) # type: ignore - self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))]) + self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), '..', 'static'))]) self.web_app.add_routes([static("/legacy", path.join(path.dirname(__file__), 'legacy'))]) def exception_handler(self, loop: AbstractEventLoop, context: Dict[str, str]): diff --git a/backend/src/updater.py b/backend/src/updater.py index ac7c78d8..d28e67b0 100644 --- a/backend/src/updater.py +++ b/backend/src/updater.py @@ -1,3 +1,4 @@ +from __future__ import annotations import os import shutil from asyncio import sleep @@ -11,7 +12,7 @@ from .localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_se from aiohttp import ClientSession, web -from .import helpers +from . import helpers from .injector import get_gamepadui_tab from .settings import SettingsManager diff --git a/backend/src/utilities.py b/backend/src/utilities.py index 3c7c8c2e..b0e23b88 100644 --- a/backend/src/utilities.py +++ b/backend/src/utilities.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: from .main import PluginManager from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab from .localplatform import ON_WINDOWS -from .import helpers +from . import helpers from .localplatform import service_stop, service_start, get_home_path, get_username class FilePickerObj(TypedDict): -- cgit v1.2.3 From 796b8b49f41657a44764c8557b5413bb3935e790 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Tue, 17 Oct 2023 16:07:43 +0300 Subject: Drop support for legacy plugins --- backend/src/helpers.py | 2 +- backend/src/loader.py | 48 +++--------------------------------------------- backend/src/main.py | 1 - backend/src/plugin.py | 5 ----- 4 files changed, 4 insertions(+), 52 deletions(-) (limited to 'backend') diff --git a/backend/src/helpers.py b/backend/src/helpers.py index f8796bd8..0acfd929 100644 --- a/backend/src/helpers.py +++ b/backend/src/helpers.py @@ -32,7 +32,7 @@ def get_csrf_token(): @middleware async def csrf_middleware(request: Request, handler: Handler): - if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): + if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): return await handler(request) return Response(text='Forbidden', status=403) diff --git a/backend/src/loader.py b/backend/src/loader.py index e59cbcaf..f1ba662f 100644 --- a/backend/src/loader.py +++ b/backend/src/loader.py @@ -91,12 +91,7 @@ class Loader: 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.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets), - web.post("/plugins/{plugin_name}/reload", self.handle_backend_reload_request), - - # The following is legacy plugin code. - web.get("/plugins/load_main/{name}", self.load_plugin_main_view), - web.get("/plugins/plugin_resource/{name}/{path:.+}", self.handle_sub_route), - web.get("/steam_resource/{path:.+}", self.get_steam_resource) + web.post("/plugins/{plugin_name}/reload", self.handle_backend_reload_request) ]) async def enable_reload_wait(self): @@ -122,7 +117,7 @@ class Loader: async def get_plugins(self, request: web.Request): 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), "version": i.version} for i in plugins]) async def handle_plugin_frontend_assets(self, request: web.Request): plugin = self.plugins[request.match_info["plugin_name"]] @@ -151,7 +146,7 @@ class Loader: self.plugins[plugin.name] = plugin.start() self.logger.info(f"Loaded {plugin.name}") if not batch: - self.loop.create_task(self.dispatch_plugin(plugin.name if not plugin.legacy else "$LEGACY_" + plugin.name, plugin.version)) + self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version)) except Exception as e: self.logger.error(f"Could not load {file}. {e}") print_exc() @@ -192,43 +187,6 @@ class Loader: res["success"] = False return web.json_response(res) - """ - The following methods are used to load legacy plugins, which are considered deprecated. - I made the choice to re-add them so that the first iteration/version of the react loader - can work as a drop-in replacement for the stable branch of the PluginLoader, so that we - can introduce it more smoothly and give people the chance to sample the new features even - without plugin support. They will be removed once legacy plugins are no longer relevant. - """ - async def load_plugin_main_view(self, request: web.Request): - plugin = self.plugins[request.match_info["name"]] - with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), "r", encoding="utf-8") as template: - template_data = template.read() - ret = f""" - - - - {template_data} - """ - return web.Response(text=ret, content_type="text/html") - - async def handle_sub_route(self, request: web.Request): - plugin = self.plugins[request.match_info["name"]] - route_path = request.match_info["path"] - self.logger.info(path) - ret = "" - file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path) - with open(file_path, "r", encoding="utf-8") as resource_data: - ret = resource_data.read() - - return web.Response(text=ret) - - async def get_steam_resource(self, request: web.Request): - tab = await get_tab("SP") - try: - return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html") - except Exception as e: - return web.Response(text=str(e), status=400) - async def handle_backend_reload_request(self, request: web.Request): plugin_name : str = request.match_info["plugin_name"] plugin = self.plugins[plugin_name] diff --git a/backend/src/main.py b/backend/src/main.py index 83a4b997..1c7c44d1 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -87,7 +87,6 @@ class PluginManager: for route in list(self.web_app.router.routes()): self.cors.add(route) # type: ignore self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), '..', 'static'))]) - self.web_app.add_routes([static("/legacy", path.join(path.dirname(__file__), 'legacy'))]) def exception_handler(self, loop: AbstractEventLoop, context: Dict[str, str]): if context["message"] == "Unclosed connection": diff --git a/backend/src/plugin.py b/backend/src/plugin.py index 163bb9b6..481ede3f 100644 --- a/backend/src/plugin.py +++ b/backend/src/plugin.py @@ -29,11 +29,6 @@ class PluginWrapper: package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8")) self.version = package_json["version"] - self.legacy = False - self.main_view_html = json["main_view_html"] if "main_view_html" in json else "" - self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else "" - self.legacy = self.main_view_html or self.tile_view_html - self.name = json["name"] self.author = json["author"] self.flags = json["flags"] -- cgit v1.2.3 From 36c145bb3a6e2e1c4026a4193620aec21959d684 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Tue, 17 Oct 2023 23:51:57 +0300 Subject: Add localplatform stuff to its own package --- backend/src/browser.py | 2 +- backend/src/helpers.py | 2 +- backend/src/legacy/library.js | 84 ----------- backend/src/loader.py | 6 +- backend/src/localplatform.py | 52 ------- backend/src/localplatform/localplatform.py | 52 +++++++ backend/src/localplatform/localplatformlinux.py | 192 ++++++++++++++++++++++++ backend/src/localplatform/localplatformwin.py | 53 +++++++ backend/src/localplatform/localsocket.py | 139 +++++++++++++++++ backend/src/localplatformlinux.py | 192 ------------------------ backend/src/localplatformwin.py | 53 ------- backend/src/localsocket.py | 139 ----------------- backend/src/main.py | 2 +- backend/src/settings.py | 2 +- backend/src/updater.py | 2 +- backend/src/utilities.py | 4 +- 16 files changed, 446 insertions(+), 530 deletions(-) delete mode 100644 backend/src/legacy/library.js delete mode 100644 backend/src/localplatform.py create mode 100644 backend/src/localplatform/localplatform.py create mode 100644 backend/src/localplatform/localplatformlinux.py create mode 100644 backend/src/localplatform/localplatformwin.py create mode 100644 backend/src/localplatform/localsocket.py delete mode 100644 backend/src/localplatformlinux.py delete mode 100644 backend/src/localplatformwin.py delete mode 100644 backend/src/localsocket.py (limited to 'backend') diff --git a/backend/src/browser.py b/backend/src/browser.py index 08560749..fd2cf487 100644 --- a/backend/src/browser.py +++ b/backend/src/browser.py @@ -17,7 +17,7 @@ from enum import IntEnum from typing import Dict, List, TypedDict # Local modules -from .localplatform import chown, chmod +from .localplatform.localplatform import chown, chmod from .loader import Loader, Plugins from .helpers import get_ssl_context, download_remote_binary_to_path from .settings import SettingsManager diff --git a/backend/src/helpers.py b/backend/src/helpers.py index 0acfd929..e3770c63 100644 --- a/backend/src/helpers.py +++ b/backend/src/helpers.py @@ -10,7 +10,7 @@ import certifi from aiohttp.web import Request, Response, middleware from aiohttp.typedefs import Handler from aiohttp import ClientSession -from . import localplatform +from .localplatform import localplatform from .customtypes import UserType from logging import getLogger diff --git a/backend/src/legacy/library.js b/backend/src/legacy/library.js deleted file mode 100644 index 17f4e46f..00000000 --- a/backend/src/legacy/library.js +++ /dev/null @@ -1,84 +0,0 @@ -class PluginEventTarget extends EventTarget { } -method_call_ev_target = new PluginEventTarget(); - -window.addEventListener("message", function(evt) { - let ev = new Event(evt.data.call_id); - ev.data = evt.data.result; - method_call_ev_target.dispatchEvent(ev); -}, false); - -async function call_server_method(method_name, arg_object={}) { - const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text()); - const response = await fetch(`http://127.0.0.1:1337/methods/${method_name}`, { - method: 'POST', - credentials: "include", - headers: { - 'Content-Type': 'application/json', - Authentication: token - }, - body: JSON.stringify(arg_object), - }); - - const dta = await response.json(); - if (!dta.success) throw dta.result; - return dta.result; -} - -// Source: https://stackoverflow.com/a/2117523 Thanks! -function uuidv4() { - return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => - (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) - ); -} - -async function fetch_nocors(url, request={}) { - let args = { method: "POST", headers: {}, body: "" }; - request = {...args, ...request}; - request.url = url; - request.data = request.body; - delete request.body; //maintain api-compatibility with fetch - return await call_server_method("http_request", request); -} - -async function call_plugin_method(method_name, arg_object={}) { - if (plugin_name == undefined) - throw new Error("Plugin methods can only be called from inside plugins (duh)"); - const token = await fetch("http://127.0.0.1:1337/auth/token").then(r => r.text()); - const response = await fetch(`http://127.0.0.1:1337/plugins/${plugin_name}/methods/${method_name}`, { - method: 'POST', - credentials: "include", - headers: { - 'Content-Type': 'application/json', - Authentication: token - }, - body: JSON.stringify({ - args: arg_object, - }), - }); - - const dta = await response.json(); - if (!dta.success) throw dta.result; - return dta.result; -} - -async function execute_in_tab(tab, run_async, code) { - return await call_server_method("execute_in_tab", { - 'tab': tab, - 'run_async': run_async, - 'code': code - }); -} - -async function inject_css_into_tab(tab, style) { - return await call_server_method("inject_css_into_tab", { - 'tab': tab, - 'style': style - }); -} - -async function remove_css_from_tab(tab, css_id) { - return await call_server_method("remove_css_from_tab", { - 'tab': tab, - 'css_id': css_id - }); -} \ No newline at end of file diff --git a/backend/src/loader.py b/backend/src/loader.py index f1ba662f..49f80c64 100644 --- a/backend/src/loader.py +++ b/backend/src/loader.py @@ -16,8 +16,8 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from .main import PluginManager -from .injector import get_tab, get_gamepadui_tab -from .plugin import PluginWrapper +from .injector import get_gamepadui_tab +from .plugin.plugin import PluginWrapper Plugins = dict[str, PluginWrapper] ReloadQueue = Queue[Tuple[str, str, bool | None] | Tuple[str, str]] @@ -143,7 +143,7 @@ class Loader: self.plugins.pop(plugin.name, None) if plugin.passive: self.logger.info(f"Plugin {plugin.name} is passive") - self.plugins[plugin.name] = plugin.start() + self.plugins[plugin.name] = plugin self.logger.info(f"Loaded {plugin.name}") if not batch: self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version)) diff --git a/backend/src/localplatform.py b/backend/src/localplatform.py deleted file mode 100644 index 028eff8f..00000000 --- a/backend/src/localplatform.py +++ /dev/null @@ -1,52 +0,0 @@ -import platform, os - -ON_WINDOWS = platform.system() == "Windows" -ON_LINUX = not ON_WINDOWS - -if ON_WINDOWS: - from .localplatformwin import * - from . import localplatformwin as localplatform -else: - from .localplatformlinux import * - from . import localplatformlinux as localplatform - -def get_privileged_path() -> str: - '''Get path accessible by elevated user. Holds plugins, decky loader and decky loader configs''' - return localplatform.get_privileged_path() - -def get_unprivileged_path() -> str: - '''Get path accessible by non-elevated user. Holds plugin configuration, plugin data and plugin logs. Externally referred to as the 'Homebrew' directory''' - return localplatform.get_unprivileged_path() - -def get_unprivileged_user() -> str: - '''Get user that should own files made in unprivileged path''' - return localplatform.get_unprivileged_user() - -def get_chown_plugin_path() -> bool: - return os.getenv("CHOWN_PLUGIN_PATH", "1") == "1" - -def get_server_host() -> str: - return os.getenv("SERVER_HOST", "127.0.0.1") - -def get_server_port() -> int: - return int(os.getenv("SERVER_PORT", "1337")) - -def get_live_reload() -> bool: - return os.getenv("LIVE_RELOAD", "1") == "1" - -def get_keep_systemd_service() -> bool: - return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1" - -def get_log_level() -> int: - return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[ - os.getenv("LOG_LEVEL", "INFO") - ] - -def get_selinux() -> bool: - if ON_LINUX: - from subprocess import check_output - try: - if (check_output("getenforce").decode("ascii").strip("\n") == "Enforcing"): return True - except FileNotFoundError: - pass - return False diff --git a/backend/src/localplatform/localplatform.py b/backend/src/localplatform/localplatform.py new file mode 100644 index 00000000..028eff8f --- /dev/null +++ b/backend/src/localplatform/localplatform.py @@ -0,0 +1,52 @@ +import platform, os + +ON_WINDOWS = platform.system() == "Windows" +ON_LINUX = not ON_WINDOWS + +if ON_WINDOWS: + from .localplatformwin import * + from . import localplatformwin as localplatform +else: + from .localplatformlinux import * + from . import localplatformlinux as localplatform + +def get_privileged_path() -> str: + '''Get path accessible by elevated user. Holds plugins, decky loader and decky loader configs''' + return localplatform.get_privileged_path() + +def get_unprivileged_path() -> str: + '''Get path accessible by non-elevated user. Holds plugin configuration, plugin data and plugin logs. Externally referred to as the 'Homebrew' directory''' + return localplatform.get_unprivileged_path() + +def get_unprivileged_user() -> str: + '''Get user that should own files made in unprivileged path''' + return localplatform.get_unprivileged_user() + +def get_chown_plugin_path() -> bool: + return os.getenv("CHOWN_PLUGIN_PATH", "1") == "1" + +def get_server_host() -> str: + return os.getenv("SERVER_HOST", "127.0.0.1") + +def get_server_port() -> int: + return int(os.getenv("SERVER_PORT", "1337")) + +def get_live_reload() -> bool: + return os.getenv("LIVE_RELOAD", "1") == "1" + +def get_keep_systemd_service() -> bool: + return os.getenv("KEEP_SYSTEMD_SERVICE", "0") == "1" + +def get_log_level() -> int: + return {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[ + os.getenv("LOG_LEVEL", "INFO") + ] + +def get_selinux() -> bool: + if ON_LINUX: + from subprocess import check_output + try: + if (check_output("getenforce").decode("ascii").strip("\n") == "Enforcing"): return True + except FileNotFoundError: + pass + return False diff --git a/backend/src/localplatform/localplatformlinux.py b/backend/src/localplatform/localplatformlinux.py new file mode 100644 index 00000000..1ec3fc1a --- /dev/null +++ b/backend/src/localplatform/localplatformlinux.py @@ -0,0 +1,192 @@ +import os, pwd, grp, sys, logging +from subprocess import call, run, DEVNULL, PIPE, STDOUT +from ..customtypes import UserType + +logger = logging.getLogger("localplatform") + +# Get the user id hosting the plugin loader +def _get_user_id() -> int: + return pwd.getpwnam(_get_user()).pw_uid + +# Get the user hosting the plugin loader +def _get_user() -> str: + return get_unprivileged_user() + +# Get the effective user id of the running process +def _get_effective_user_id() -> int: + return os.geteuid() + +# Get the effective user of the running process +def _get_effective_user() -> str: + return pwd.getpwuid(_get_effective_user_id()).pw_name + +# Get the effective user group id of the running process +def _get_effective_user_group_id() -> int: + return os.getegid() + +# Get the effective user group of the running process +def _get_effective_user_group() -> str: + return grp.getgrgid(_get_effective_user_group_id()).gr_name + +# Get the user owner of the given file path. +def _get_user_owner(file_path: str) -> str: + return pwd.getpwuid(os.stat(file_path).st_uid).pw_name + +# Get the user group of the given file path, or the user group hosting the plugin loader +def _get_user_group(file_path: str | None = None) -> str: + return grp.getgrgid(os.stat(file_path).st_gid if file_path is not None else _get_user_group_id()).gr_name + +# Get the group id of the user hosting the plugin loader +def _get_user_group_id() -> int: + return pwd.getpwuid(_get_user_id()).pw_gid + +def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: + user_str = "" + + if user == UserType.HOST_USER: + user_str = _get_user()+":"+_get_user_group() + elif user == UserType.EFFECTIVE_USER: + user_str = _get_effective_user()+":"+_get_effective_user_group() + elif user == UserType.ROOT: + user_str = "root:root" + else: + raise Exception("Unknown User Type") + + result = call(["chown", "-R", user_str, path] if recursive else ["chown", user_str, path]) + return result == 0 + +def chmod(path : str, permissions : int, recursive : bool = True) -> bool: + if _get_effective_user_id() != 0: + return True + result = call(["chmod", "-R", str(permissions), path] if recursive else ["chmod", str(permissions), path]) + return result == 0 + +def folder_owner(path : str) -> UserType|None: + user_owner = _get_user_owner(path) + + if (user_owner == _get_user()): + return UserType.HOST_USER + + elif (user_owner == _get_effective_user()): + return UserType.EFFECTIVE_USER + + else: + return None + +def get_home_path(user : UserType = UserType.HOST_USER) -> str: + user_name = "root" + + if user == UserType.HOST_USER: + user_name = _get_user() + elif user == UserType.EFFECTIVE_USER: + user_name = _get_effective_user() + elif user == UserType.ROOT: + pass + else: + raise Exception("Unknown User Type") + + return pwd.getpwnam(user_name).pw_dir + +def get_username() -> str: + return _get_user() + +def setgid(user : UserType = UserType.HOST_USER): + user_id = 0 + + if user == UserType.HOST_USER: + user_id = _get_user_group_id() + elif user == UserType.ROOT: + pass + else: + raise Exception("Unknown user type") + + os.setgid(user_id) + +def setuid(user : UserType = UserType.HOST_USER): + user_id = 0 + + if user == UserType.HOST_USER: + user_id = _get_user_id() + elif user == UserType.ROOT: + pass + else: + raise Exception("Unknown user type") + + os.setuid(user_id) + +async def service_active(service_name : str) -> bool: + res = run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL) + return res.returncode == 0 + +async def service_restart(service_name : str) -> bool: + call(["systemctl", "daemon-reload"]) + cmd = ["systemctl", "restart", service_name] + res = run(cmd, stdout=PIPE, stderr=STDOUT) + return res.returncode == 0 + +async def service_stop(service_name : str) -> bool: + cmd = ["systemctl", "stop", service_name] + res = run(cmd, stdout=PIPE, stderr=STDOUT) + return res.returncode == 0 + +async def service_start(service_name : str) -> bool: + cmd = ["systemctl", "start", service_name] + res = run(cmd, stdout=PIPE, stderr=STDOUT) + return res.returncode == 0 + +def get_privileged_path() -> str: + path = os.getenv("PRIVILEGED_PATH") + + if path == None: + path = get_unprivileged_path() + + return path + +def _parent_dir(path : str | None) -> str | None: + if path == None: + return None + + if path.endswith('/'): + path = path[:-1] + + return os.path.dirname(path) + +def get_unprivileged_path() -> str: + path = os.getenv("UNPRIVILEGED_PATH") + + if path == None: + path = _parent_dir(os.getenv("PLUGIN_PATH")) + + if path == None: + logger.debug("Unprivileged path is not properly configured. Making something up!") + # Expected path of loader binary is /home/deck/homebrew/service/PluginLoader + path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0]))) + + if path != None and not os.path.exists(path): + path = None + + if path == None: + logger.warn("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew") + path = "/home/deck/homebrew" # We give up + + return path + + +def get_unprivileged_user() -> str: + user = os.getenv("UNPRIVILEGED_USER") + + if user == None: + # Lets hope we can extract it from the unprivileged dir + dir = os.path.realpath(get_unprivileged_path()) + + pws = sorted(pwd.getpwall(), reverse=True, key=lambda pw: len(pw.pw_dir)) + for pw in pws: + if dir.startswith(os.path.realpath(pw.pw_dir)): + user = pw.pw_name + break + + if user == None: + logger.warn("Unprivileged user is not properly configured. Defaulting to 'deck'") + user = 'deck' + + return user diff --git a/backend/src/localplatform/localplatformwin.py b/backend/src/localplatform/localplatformwin.py new file mode 100644 index 00000000..4c4e9439 --- /dev/null +++ b/backend/src/localplatform/localplatformwin.py @@ -0,0 +1,53 @@ +from .customtypes import UserType +import os, sys + +def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: + return True # Stubbed + +def chmod(path : str, permissions : int, recursive : bool = True) -> bool: + return True # Stubbed + +def folder_owner(path : str) -> UserType|None: + return UserType.HOST_USER # Stubbed + +def get_home_path(user : UserType = UserType.HOST_USER) -> str: + return os.path.expanduser("~") # Mostly stubbed + +def setgid(user : UserType = UserType.HOST_USER): + pass # Stubbed + +def setuid(user : UserType = UserType.HOST_USER): + pass # Stubbed + +async def service_active(service_name : str) -> bool: + return True # Stubbed + +async def service_stop(service_name : str) -> bool: + return True # Stubbed + +async def service_start(service_name : str) -> bool: + return True # Stubbed + +async def service_restart(service_name : str) -> bool: + if service_name == "plugin_loader": + sys.exit(42) + + return True # Stubbed + +def get_username() -> str: + return os.getlogin() + +def get_privileged_path() -> str: + '''On windows, privileged_path is equal to unprivileged_path''' + return get_unprivileged_path() + +def get_unprivileged_path() -> str: + path = os.getenv("UNPRIVILEGED_PATH") + + if path == None: + path = os.getenv("PRIVILEGED_PATH", os.path.join(os.path.expanduser("~"), "homebrew")) + + return path + +def get_unprivileged_user() -> str: + return os.getenv("UNPRIVILEGED_USER", os.getlogin()) diff --git a/backend/src/localplatform/localsocket.py b/backend/src/localplatform/localsocket.py new file mode 100644 index 00000000..f38fe5e7 --- /dev/null +++ b/backend/src/localplatform/localsocket.py @@ -0,0 +1,139 @@ +import asyncio, time +from typing import Awaitable, Callable +import random + +from .localplatform import ON_WINDOWS + +BUFFER_LIMIT = 2 ** 20 # 1 MiB + +class UnixSocket: + def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): + ''' + on_new_message takes 1 string argument. + It's return value gets used, if not None, to write data to the socket. + Method should be async + ''' + self.socket_addr = f"/tmp/plugin_socket_{time.time()}" + self.on_new_message = on_new_message + self.socket = None + self.reader = None + self.writer = None + + async def setup_server(self): + self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT) + + async def _open_socket_if_not_exists(self): + if not self.reader: + retries = 0 + while retries < 10: + try: + self.reader, self.writer = await asyncio.open_unix_connection(self.socket_addr, limit=BUFFER_LIMIT) + return True + except: + await asyncio.sleep(2) + retries += 1 + return False + else: + return True + + async def get_socket_connection(self): + if not await self._open_socket_if_not_exists(): + return None, None + + return self.reader, self.writer + + async def close_socket_connection(self): + if self.writer != None: + self.writer.close() + + self.reader = None + + async def read_single_line(self) -> str|None: + reader, _ = await self.get_socket_connection() + + try: + assert reader + except AssertionError: + return + + return await self._read_single_line(reader) + + async def write_single_line(self, message : str): + _, writer = await self.get_socket_connection() + + try: + assert writer + except AssertionError: + return + + await self._write_single_line(writer, message) + + async def _read_single_line(self, reader: asyncio.StreamReader) -> str: + line = bytearray() + while True: + try: + line.extend(await reader.readuntil()) + except asyncio.LimitOverrunError: + line.extend(await reader.read(reader._limit)) # type: ignore + continue + except asyncio.IncompleteReadError as err: + line.extend(err.partial) + break + else: + break + + return line.decode("utf-8") + + async def _write_single_line(self, writer: asyncio.StreamWriter, message : str): + if not message.endswith("\n"): + message += "\n" + + writer.write(message.encode("utf-8")) + await writer.drain() + + async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): + while True: + line = await self._read_single_line(reader) + + try: + res = await self.on_new_message(line) + except Exception: + return + + if res != None: + await self._write_single_line(writer, res) + +class PortSocket (UnixSocket): + def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): + ''' + on_new_message takes 1 string argument. + It's return value gets used, if not None, to write data to the socket. + Method should be async + ''' + super().__init__(on_new_message) + self.host = "127.0.0.1" + self.port = random.sample(range(40000, 60000), 1)[0] + + async def setup_server(self): + self.socket = await asyncio.start_server(self._listen_for_method_call, host=self.host, port=self.port, limit=BUFFER_LIMIT) + + async def _open_socket_if_not_exists(self): + if not self.reader: + retries = 0 + while retries < 10: + try: + self.reader, self.writer = await asyncio.open_connection(host=self.host, port=self.port, limit=BUFFER_LIMIT) + return True + except: + await asyncio.sleep(2) + retries += 1 + return False + else: + return True + +if ON_WINDOWS: + class LocalSocket (PortSocket): # type: ignore + pass +else: + class LocalSocket (UnixSocket): + pass \ No newline at end of file diff --git a/backend/src/localplatformlinux.py b/backend/src/localplatformlinux.py deleted file mode 100644 index bde2caac..00000000 --- a/backend/src/localplatformlinux.py +++ /dev/null @@ -1,192 +0,0 @@ -import os, pwd, grp, sys, logging -from subprocess import call, run, DEVNULL, PIPE, STDOUT -from .customtypes import UserType - -logger = logging.getLogger("localplatform") - -# Get the user id hosting the plugin loader -def _get_user_id() -> int: - return pwd.getpwnam(_get_user()).pw_uid - -# Get the user hosting the plugin loader -def _get_user() -> str: - return get_unprivileged_user() - -# Get the effective user id of the running process -def _get_effective_user_id() -> int: - return os.geteuid() - -# Get the effective user of the running process -def _get_effective_user() -> str: - return pwd.getpwuid(_get_effective_user_id()).pw_name - -# Get the effective user group id of the running process -def _get_effective_user_group_id() -> int: - return os.getegid() - -# Get the effective user group of the running process -def _get_effective_user_group() -> str: - return grp.getgrgid(_get_effective_user_group_id()).gr_name - -# Get the user owner of the given file path. -def _get_user_owner(file_path: str) -> str: - return pwd.getpwuid(os.stat(file_path).st_uid).pw_name - -# Get the user group of the given file path, or the user group hosting the plugin loader -def _get_user_group(file_path: str | None = None) -> str: - return grp.getgrgid(os.stat(file_path).st_gid if file_path is not None else _get_user_group_id()).gr_name - -# Get the group id of the user hosting the plugin loader -def _get_user_group_id() -> int: - return pwd.getpwuid(_get_user_id()).pw_gid - -def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: - user_str = "" - - if user == UserType.HOST_USER: - user_str = _get_user()+":"+_get_user_group() - elif user == UserType.EFFECTIVE_USER: - user_str = _get_effective_user()+":"+_get_effective_user_group() - elif user == UserType.ROOT: - user_str = "root:root" - else: - raise Exception("Unknown User Type") - - result = call(["chown", "-R", user_str, path] if recursive else ["chown", user_str, path]) - return result == 0 - -def chmod(path : str, permissions : int, recursive : bool = True) -> bool: - if _get_effective_user_id() != 0: - return True - result = call(["chmod", "-R", str(permissions), path] if recursive else ["chmod", str(permissions), path]) - return result == 0 - -def folder_owner(path : str) -> UserType|None: - user_owner = _get_user_owner(path) - - if (user_owner == _get_user()): - return UserType.HOST_USER - - elif (user_owner == _get_effective_user()): - return UserType.EFFECTIVE_USER - - else: - return None - -def get_home_path(user : UserType = UserType.HOST_USER) -> str: - user_name = "root" - - if user == UserType.HOST_USER: - user_name = _get_user() - elif user == UserType.EFFECTIVE_USER: - user_name = _get_effective_user() - elif user == UserType.ROOT: - pass - else: - raise Exception("Unknown User Type") - - return pwd.getpwnam(user_name).pw_dir - -def get_username() -> str: - return _get_user() - -def setgid(user : UserType = UserType.HOST_USER): - user_id = 0 - - if user == UserType.HOST_USER: - user_id = _get_user_group_id() - elif user == UserType.ROOT: - pass - else: - raise Exception("Unknown user type") - - os.setgid(user_id) - -def setuid(user : UserType = UserType.HOST_USER): - user_id = 0 - - if user == UserType.HOST_USER: - user_id = _get_user_id() - elif user == UserType.ROOT: - pass - else: - raise Exception("Unknown user type") - - os.setuid(user_id) - -async def service_active(service_name : str) -> bool: - res = run(["systemctl", "is-active", service_name], stdout=DEVNULL, stderr=DEVNULL) - return res.returncode == 0 - -async def service_restart(service_name : str) -> bool: - call(["systemctl", "daemon-reload"]) - cmd = ["systemctl", "restart", service_name] - res = run(cmd, stdout=PIPE, stderr=STDOUT) - return res.returncode == 0 - -async def service_stop(service_name : str) -> bool: - cmd = ["systemctl", "stop", service_name] - res = run(cmd, stdout=PIPE, stderr=STDOUT) - return res.returncode == 0 - -async def service_start(service_name : str) -> bool: - cmd = ["systemctl", "start", service_name] - res = run(cmd, stdout=PIPE, stderr=STDOUT) - return res.returncode == 0 - -def get_privileged_path() -> str: - path = os.getenv("PRIVILEGED_PATH") - - if path == None: - path = get_unprivileged_path() - - return path - -def _parent_dir(path : str | None) -> str | None: - if path == None: - return None - - if path.endswith('/'): - path = path[:-1] - - return os.path.dirname(path) - -def get_unprivileged_path() -> str: - path = os.getenv("UNPRIVILEGED_PATH") - - if path == None: - path = _parent_dir(os.getenv("PLUGIN_PATH")) - - if path == None: - logger.debug("Unprivileged path is not properly configured. Making something up!") - # Expected path of loader binary is /home/deck/homebrew/service/PluginLoader - path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0]))) - - if path != None and not os.path.exists(path): - path = None - - if path == None: - logger.warn("Unprivileged path is not properly configured. Defaulting to /home/deck/homebrew") - path = "/home/deck/homebrew" # We give up - - return path - - -def get_unprivileged_user() -> str: - user = os.getenv("UNPRIVILEGED_USER") - - if user == None: - # Lets hope we can extract it from the unprivileged dir - dir = os.path.realpath(get_unprivileged_path()) - - pws = sorted(pwd.getpwall(), reverse=True, key=lambda pw: len(pw.pw_dir)) - for pw in pws: - if dir.startswith(os.path.realpath(pw.pw_dir)): - user = pw.pw_name - break - - if user == None: - logger.warn("Unprivileged user is not properly configured. Defaulting to 'deck'") - user = 'deck' - - return user diff --git a/backend/src/localplatformwin.py b/backend/src/localplatformwin.py deleted file mode 100644 index 4c4e9439..00000000 --- a/backend/src/localplatformwin.py +++ /dev/null @@ -1,53 +0,0 @@ -from .customtypes import UserType -import os, sys - -def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: - return True # Stubbed - -def chmod(path : str, permissions : int, recursive : bool = True) -> bool: - return True # Stubbed - -def folder_owner(path : str) -> UserType|None: - return UserType.HOST_USER # Stubbed - -def get_home_path(user : UserType = UserType.HOST_USER) -> str: - return os.path.expanduser("~") # Mostly stubbed - -def setgid(user : UserType = UserType.HOST_USER): - pass # Stubbed - -def setuid(user : UserType = UserType.HOST_USER): - pass # Stubbed - -async def service_active(service_name : str) -> bool: - return True # Stubbed - -async def service_stop(service_name : str) -> bool: - return True # Stubbed - -async def service_start(service_name : str) -> bool: - return True # Stubbed - -async def service_restart(service_name : str) -> bool: - if service_name == "plugin_loader": - sys.exit(42) - - return True # Stubbed - -def get_username() -> str: - return os.getlogin() - -def get_privileged_path() -> str: - '''On windows, privileged_path is equal to unprivileged_path''' - return get_unprivileged_path() - -def get_unprivileged_path() -> str: - path = os.getenv("UNPRIVILEGED_PATH") - - if path == None: - path = os.getenv("PRIVILEGED_PATH", os.path.join(os.path.expanduser("~"), "homebrew")) - - return path - -def get_unprivileged_user() -> str: - return os.getenv("UNPRIVILEGED_USER", os.getlogin()) diff --git a/backend/src/localsocket.py b/backend/src/localsocket.py deleted file mode 100644 index f38fe5e7..00000000 --- a/backend/src/localsocket.py +++ /dev/null @@ -1,139 +0,0 @@ -import asyncio, time -from typing import Awaitable, Callable -import random - -from .localplatform import ON_WINDOWS - -BUFFER_LIMIT = 2 ** 20 # 1 MiB - -class UnixSocket: - def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): - ''' - on_new_message takes 1 string argument. - It's return value gets used, if not None, to write data to the socket. - Method should be async - ''' - self.socket_addr = f"/tmp/plugin_socket_{time.time()}" - self.on_new_message = on_new_message - self.socket = None - self.reader = None - self.writer = None - - async def setup_server(self): - self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT) - - async def _open_socket_if_not_exists(self): - if not self.reader: - retries = 0 - while retries < 10: - try: - self.reader, self.writer = await asyncio.open_unix_connection(self.socket_addr, limit=BUFFER_LIMIT) - return True - except: - await asyncio.sleep(2) - retries += 1 - return False - else: - return True - - async def get_socket_connection(self): - if not await self._open_socket_if_not_exists(): - return None, None - - return self.reader, self.writer - - async def close_socket_connection(self): - if self.writer != None: - self.writer.close() - - self.reader = None - - async def read_single_line(self) -> str|None: - reader, _ = await self.get_socket_connection() - - try: - assert reader - except AssertionError: - return - - return await self._read_single_line(reader) - - async def write_single_line(self, message : str): - _, writer = await self.get_socket_connection() - - try: - assert writer - except AssertionError: - return - - await self._write_single_line(writer, message) - - async def _read_single_line(self, reader: asyncio.StreamReader) -> str: - line = bytearray() - while True: - try: - line.extend(await reader.readuntil()) - except asyncio.LimitOverrunError: - line.extend(await reader.read(reader._limit)) # type: ignore - continue - except asyncio.IncompleteReadError as err: - line.extend(err.partial) - break - else: - break - - return line.decode("utf-8") - - async def _write_single_line(self, writer: asyncio.StreamWriter, message : str): - if not message.endswith("\n"): - message += "\n" - - writer.write(message.encode("utf-8")) - await writer.drain() - - async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): - while True: - line = await self._read_single_line(reader) - - try: - res = await self.on_new_message(line) - except Exception: - return - - if res != None: - await self._write_single_line(writer, res) - -class PortSocket (UnixSocket): - def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): - ''' - on_new_message takes 1 string argument. - It's return value gets used, if not None, to write data to the socket. - Method should be async - ''' - super().__init__(on_new_message) - self.host = "127.0.0.1" - self.port = random.sample(range(40000, 60000), 1)[0] - - async def setup_server(self): - self.socket = await asyncio.start_server(self._listen_for_method_call, host=self.host, port=self.port, limit=BUFFER_LIMIT) - - async def _open_socket_if_not_exists(self): - if not self.reader: - retries = 0 - while retries < 10: - try: - self.reader, self.writer = await asyncio.open_connection(host=self.host, port=self.port, limit=BUFFER_LIMIT) - return True - except: - await asyncio.sleep(2) - retries += 1 - return False - else: - return True - -if ON_WINDOWS: - class LocalSocket (PortSocket): # type: ignore - pass -else: - class LocalSocket (UnixSocket): - pass \ No newline at end of file diff --git a/backend/src/main.py b/backend/src/main.py index 1c7c44d1..ef090df4 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -1,7 +1,7 @@ # Change PyInstaller files permissions import sys from typing import Dict -from .localplatform import (chmod, chown, service_stop, service_start, +from .localplatform.localplatform import (chmod, chown, service_stop, service_start, ON_WINDOWS, get_log_level, get_live_reload, get_server_port, get_server_host, get_chown_plugin_path, get_privileged_path) diff --git a/backend/src/settings.py b/backend/src/settings.py index a9ab3daa..c0f2b90c 100644 --- a/backend/src/settings.py +++ b/backend/src/settings.py @@ -1,7 +1,7 @@ from json import dump, load from os import mkdir, path, listdir, rename from typing import Any, Dict -from .localplatform import chown, folder_owner, get_chown_plugin_path +from .localplatform.localplatform import chown, folder_owner, get_chown_plugin_path from .customtypes import UserType from .helpers import get_homebrew_path diff --git a/backend/src/updater.py b/backend/src/updater.py index d28e67b0..f8aef429 100644 --- a/backend/src/updater.py +++ b/backend/src/updater.py @@ -8,7 +8,7 @@ from os import getcwd, path, remove from typing import TYPE_CHECKING, List, TypedDict if TYPE_CHECKING: from .main import PluginManager -from .localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux +from .localplatform.localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux from aiohttp import ClientSession, web diff --git a/backend/src/utilities.py b/backend/src/utilities.py index b0e23b88..f04ed371 100644 --- a/backend/src/utilities.py +++ b/backend/src/utilities.py @@ -18,9 +18,9 @@ from .browser import PluginInstallRequest, PluginInstallType if TYPE_CHECKING: from .main import PluginManager from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab -from .localplatform import ON_WINDOWS +from .localplatform.localplatform import ON_WINDOWS from . import helpers -from .localplatform import service_stop, service_start, get_home_path, get_username +from .localplatform.localplatform import service_stop, service_start, get_home_path, get_username class FilePickerObj(TypedDict): file: Path -- cgit v1.2.3 From 07c8ddc0b298894f3da77fe3f8aca1b18189b79b Mon Sep 17 00:00:00 2001 From: marios8543 Date: Tue, 17 Oct 2023 23:52:18 +0300 Subject: Experimental support for async method calls --- backend/src/plugin.py | 158 ------------------------------ backend/src/plugin/method_call_request.py | 29 ++++++ backend/src/plugin/plugin.py | 55 +++++++++++ backend/src/plugin/sandboxed_plugin.py | 131 +++++++++++++++++++++++++ 4 files changed, 215 insertions(+), 158 deletions(-) delete mode 100644 backend/src/plugin.py create mode 100644 backend/src/plugin/method_call_request.py create mode 100644 backend/src/plugin/plugin.py create mode 100644 backend/src/plugin/sandboxed_plugin.py (limited to 'backend') diff --git a/backend/src/plugin.py b/backend/src/plugin.py deleted file mode 100644 index 481ede3f..00000000 --- a/backend/src/plugin.py +++ /dev/null @@ -1,158 +0,0 @@ -import multiprocessing -from asyncio import (Lock, get_event_loop, new_event_loop, - set_event_loop, sleep) -from importlib.util import module_from_spec, spec_from_file_location -from json import dumps, load, loads -from logging import getLogger -from traceback import format_exc -from os import path, environ -from signal import SIGINT, signal -from sys import exit, path as syspath -from typing import Any, Dict -from .localsocket import LocalSocket -from .localplatform import setgid, setuid, get_username, get_home_path -from .customtypes import UserType -from . import helpers - -class PluginWrapper: - def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: - self.file = file - self.plugin_path = plugin_path - self.plugin_directory = plugin_directory - self.method_call_lock = Lock() - self.socket: LocalSocket = LocalSocket(self._on_new_message) - - self.version = None - - json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8")) - if path.isfile(path.join(plugin_path, plugin_directory, "package.json")): - package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8")) - self.version = package_json["version"] - - self.name = json["name"] - self.author = json["author"] - self.flags = json["flags"] - - self.log = getLogger("plugin") - - self.passive = not path.isfile(self.file) - - def __str__(self) -> str: - return self.name - - def _init(self): - try: - signal(SIGINT, lambda s, f: exit(0)) - - set_event_loop(new_event_loop()) - if self.passive: - return - setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) - setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) - # export a bunch of environment variables to help plugin developers - environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) - environ["USER"] = "root" if "root" in self.flags else get_username() - environ["DECKY_VERSION"] = helpers.get_loader_version() - environ["DECKY_USER"] = get_username() - environ["DECKY_USER_HOME"] = helpers.get_home_path() - environ["DECKY_HOME"] = helpers.get_homebrew_path() - environ["DECKY_PLUGIN_SETTINGS_DIR"] = path.join(environ["DECKY_HOME"], "settings", self.plugin_directory) - helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "settings")) - helpers.mkdir_as_user(environ["DECKY_PLUGIN_SETTINGS_DIR"]) - environ["DECKY_PLUGIN_RUNTIME_DIR"] = path.join(environ["DECKY_HOME"], "data", self.plugin_directory) - helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "data")) - helpers.mkdir_as_user(environ["DECKY_PLUGIN_RUNTIME_DIR"]) - environ["DECKY_PLUGIN_LOG_DIR"] = path.join(environ["DECKY_HOME"], "logs", self.plugin_directory) - helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "logs")) - helpers.mkdir_as_user(environ["DECKY_PLUGIN_LOG_DIR"]) - environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory) - environ["DECKY_PLUGIN_NAME"] = self.name - if self.version: - environ["DECKY_PLUGIN_VERSION"] = self.version - environ["DECKY_PLUGIN_AUTHOR"] = self.author - - # append the plugin's `py_modules` to the recognized python paths - syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules")) - - spec = spec_from_file_location("_", self.file) - assert spec is not None - module = module_from_spec(spec) - assert spec.loader is not None - spec.loader.exec_module(module) - self.Plugin = module.Plugin - - if hasattr(self.Plugin, "_migration"): - get_event_loop().run_until_complete(self.Plugin._migration(self.Plugin)) - if hasattr(self.Plugin, "_main"): - get_event_loop().create_task(self.Plugin._main(self.Plugin)) - get_event_loop().create_task(self.socket.setup_server()) - get_event_loop().run_forever() - except: - self.log.error("Failed to start " + self.name + "!\n" + format_exc()) - exit(0) - - async def _unload(self): - try: - self.log.info("Attempting to unload with plugin " + self.name + "'s \"_unload\" function.\n") - if hasattr(self.Plugin, "_unload"): - await self.Plugin._unload(self.Plugin) - self.log.info("Unloaded " + self.name + "\n") - else: - self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n") - except: - self.log.error("Failed to unload " + self.name + "!\n" + format_exc()) - exit(0) - - async def _on_new_message(self, message : str) -> str|None: - data = loads(message) - - if "stop" in data: - self.log.info("Calling Loader unload function.") - await self._unload() - get_event_loop().stop() - while get_event_loop().is_running(): - await sleep(0) - get_event_loop().close() - raise Exception("Closing message listener") - - # TODO there is definitely a better way to type this - d: Dict[str, Any] = {"res": None, "success": True} - try: - d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"]) - except Exception as e: - d["res"] = str(e) - d["success"] = False - finally: - return dumps(d, ensure_ascii=False) - - def start(self): - if self.passive: - return self - multiprocessing.Process(target=self._init).start() - return self - - def stop(self): - if self.passive: - return - - async def _(self: PluginWrapper): - await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) - await self.socket.close_socket_connection() - - get_event_loop().create_task(_(self)) - - async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]): - if self.passive: - raise RuntimeError("This plugin is passive (aka does not implement main.py)") - async with self.method_call_lock: - # reader, writer = - await self.socket.get_socket_connection() - - await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False)) - - line = await self.socket.read_single_line() - if line != None: - res = loads(line) - if not res["success"]: - raise Exception(res["res"]) - return res["res"] \ No newline at end of file diff --git a/backend/src/plugin/method_call_request.py b/backend/src/plugin/method_call_request.py new file mode 100644 index 00000000..8d93a6cc --- /dev/null +++ b/backend/src/plugin/method_call_request.py @@ -0,0 +1,29 @@ +from typing import Any, TypedDict +from uuid import uuid4 +from asyncio import Event + +class SocketResponseDict(TypedDict): + id: str + success: bool + res: Any + +class MethodCallResponse: + def __init__(self, success: bool, result: Any) -> None: + self.success = success + self.result = result + +class MethodCallRequest: + def __init__(self) -> None: + self.id = str(uuid4()) + self.event = Event() + self.response: MethodCallResponse + + def set_result(self, dc: SocketResponseDict): + self.response = MethodCallResponse(dc["success"], dc["res"]) + self.event.set() + + async def wait_for_result(self): + await self.event.wait() + if not self.response.success: + raise Exception(self.response.result) + return self.response \ No newline at end of file diff --git a/backend/src/plugin/plugin.py b/backend/src/plugin/plugin.py new file mode 100644 index 00000000..b7d0a44a --- /dev/null +++ b/backend/src/plugin/plugin.py @@ -0,0 +1,55 @@ +from json import dumps, load, loads +from logging import getLogger +from os import path + +from .sandboxed_plugin import SandboxedPlugin +from .method_call_request import MethodCallRequest +from ..localplatform.localsocket import LocalSocket + +from typing import Any, Dict + +class PluginWrapper: + def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: + self.file = file + self.plugin_path = plugin_path + self.plugin_directory = plugin_directory + + self.version = None + + json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8")) + if path.isfile(path.join(plugin_path, plugin_directory, "package.json")): + package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8")) + self.version = package_json["version"] + + self.name = json["name"] + self.author = json["author"] + self.flags = json["flags"] + self.passive = not path.isfile(self.file) + + self.log = getLogger("plugin") + self.method_call_requests: Dict[str, MethodCallRequest] = {} + self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author) + #TODO: Maybe somehow make LocalSocket not require on_new_message to make this more clear + self.socket = LocalSocket(self.sandboxed_plugin.on_new_message) + self.sandboxed_plugin.start(self.socket) + + def __str__(self) -> str: + return self.name + + async def response_listener(self): + while True: + line = await self.socket.read_single_line() + if line != None: + res = loads(line) + self.method_call_requests.pop(res["id"]).set_result(res) + + async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]): + if self.passive: + raise RuntimeError("This plugin is passive (aka does not implement main.py)") + + request = MethodCallRequest() + await self.socket.get_socket_connection() + await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs, "id": request.id }, ensure_ascii=False)) + self.method_call_requests[request.id] = request + + return await request.wait_for_result() \ No newline at end of file diff --git a/backend/src/plugin/sandboxed_plugin.py b/backend/src/plugin/sandboxed_plugin.py new file mode 100644 index 00000000..7c96da01 --- /dev/null +++ b/backend/src/plugin/sandboxed_plugin.py @@ -0,0 +1,131 @@ +from os import path, environ +from signal import SIGINT, signal +from importlib.util import module_from_spec, spec_from_file_location +from json import dumps, loads +from logging import getLogger +import multiprocessing +from sys import exit, path as syspath +from traceback import format_exc +from asyncio import (get_event_loop, new_event_loop, + set_event_loop, sleep) + +from .method_call_request import SocketResponseDict +from ..localplatform.localsocket import LocalSocket +from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path +from ..customtypes import UserType +from .. import helpers + +from typing import List + +class SandboxedPlugin: + def __init__(self, + name: str, + passive: bool, + flags: List[str], + file: str, + plugin_directory: str, + plugin_path: str, + version: str|None, + author: str) -> None: + self.name = name + self.passive = passive + self.flags = flags + self.file = file + self.plugin_path = plugin_path + self.plugin_directory = plugin_directory + self.version = version + self.author = author + + self.log = getLogger("plugin") + + def _init(self, socket: LocalSocket): + try: + signal(SIGINT, lambda s, f: exit(0)) + + set_event_loop(new_event_loop()) + if self.passive: + return + setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) + setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) + # export a bunch of environment variables to help plugin developers + environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER) + environ["USER"] = "root" if "root" in self.flags else get_username() + environ["DECKY_VERSION"] = helpers.get_loader_version() + environ["DECKY_USER"] = get_username() + environ["DECKY_USER_HOME"] = helpers.get_home_path() + environ["DECKY_HOME"] = helpers.get_homebrew_path() + environ["DECKY_PLUGIN_SETTINGS_DIR"] = path.join(environ["DECKY_HOME"], "settings", self.plugin_directory) + helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "settings")) + helpers.mkdir_as_user(environ["DECKY_PLUGIN_SETTINGS_DIR"]) + environ["DECKY_PLUGIN_RUNTIME_DIR"] = path.join(environ["DECKY_HOME"], "data", self.plugin_directory) + helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "data")) + helpers.mkdir_as_user(environ["DECKY_PLUGIN_RUNTIME_DIR"]) + environ["DECKY_PLUGIN_LOG_DIR"] = path.join(environ["DECKY_HOME"], "logs", self.plugin_directory) + helpers.mkdir_as_user(path.join(environ["DECKY_HOME"], "logs")) + helpers.mkdir_as_user(environ["DECKY_PLUGIN_LOG_DIR"]) + environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory) + environ["DECKY_PLUGIN_NAME"] = self.name + if self.version: + environ["DECKY_PLUGIN_VERSION"] = self.version + environ["DECKY_PLUGIN_AUTHOR"] = self.author + + # append the plugin's `py_modules` to the recognized python paths + syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules")) + + spec = spec_from_file_location("_", self.file) + assert spec is not None + module = module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + self.Plugin = module.Plugin + + if hasattr(self.Plugin, "_migration"): + get_event_loop().run_until_complete(self.Plugin._migration(self.Plugin)) + if hasattr(self.Plugin, "_main"): + get_event_loop().create_task(self.Plugin._main(self.Plugin)) + get_event_loop().create_task(socket.setup_server()) + get_event_loop().run_forever() + except: + self.log.error("Failed to start " + self.name + "!\n" + format_exc()) + exit(0) + + async def _unload(self): + try: + self.log.info("Attempting to unload with plugin " + self.name + "'s \"_unload\" function.\n") + if hasattr(self.Plugin, "_unload"): + await self.Plugin._unload(self.Plugin) + self.log.info("Unloaded " + self.name + "\n") + else: + self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n") + except: + self.log.error("Failed to unload " + self.name + "!\n" + format_exc()) + exit(0) + + async def on_new_message(self, message : str) -> str|None: + data = loads(message) + + if "stop" in data: + self.log.info("Calling Loader unload function.") + await self._unload() + get_event_loop().stop() + while get_event_loop().is_running(): + await sleep(0) + get_event_loop().close() + raise Exception("Closing message listener") + + # TODO there is definitely a better way to type this + d: SocketResponseDict = {"res": None, "success": True, "id": data["id"]} + try: + d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"]) + except Exception as e: + d["res"] = str(e) + d["success"] = False + finally: + return dumps(d, ensure_ascii=False) + + + def start(self, socket: LocalSocket): + if self.passive: + return self + multiprocessing.Process(target=self._init, args=[socket]).start() + return self \ No newline at end of file -- cgit v1.2.3 From 315b2f9cda6def0c93bc7a0fa302415d2f972c85 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Wed, 18 Oct 2023 00:04:14 +0300 Subject: Run response_listener task --- backend/src/plugin/plugin.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'backend') diff --git a/backend/src/plugin/plugin.py b/backend/src/plugin/plugin.py index b7d0a44a..d5f79d42 100644 --- a/backend/src/plugin/plugin.py +++ b/backend/src/plugin/plugin.py @@ -1,3 +1,4 @@ +from asyncio import Task, create_task from json import dumps, load, loads from logging import getLogger from os import path @@ -31,12 +32,12 @@ class PluginWrapper: self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author) #TODO: Maybe somehow make LocalSocket not require on_new_message to make this more clear self.socket = LocalSocket(self.sandboxed_plugin.on_new_message) - self.sandboxed_plugin.start(self.socket) + self.listener_task: Task[Any] def __str__(self) -> str: return self.name - async def response_listener(self): + async def _response_listener(self): while True: line = await self.socket.read_single_line() if line != None: @@ -52,4 +53,15 @@ class PluginWrapper: await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs, "id": request.id }, ensure_ascii=False)) self.method_call_requests[request.id] = request - return await request.wait_for_result() \ No newline at end of file + return await request.wait_for_result() + + async def start(self): + self.sandboxed_plugin.start(self.socket) + self.listener_task = create_task(self._response_listener()) + + async def stop(self): + self.listener_task.cancel() + async def _(self: PluginWrapper): + await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) + await self.socket.close_socket_connection() + create_task(_(self)) \ No newline at end of file -- cgit v1.2.3 From dffa82a5557dd24c10a510a9a9454aebf78a3a4e Mon Sep 17 00:00:00 2001 From: marios8543 Date: Tue, 17 Oct 2023 18:59:13 +0300 Subject: fix uninstall bug --- backend/src/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'backend') diff --git a/backend/src/browser.py b/backend/src/browser.py index fd2cf487..7260db8e 100644 --- a/backend/src/browser.py +++ b/backend/src/browser.py @@ -125,7 +125,7 @@ class PluginBrowser: tab = await get_gamepadui_tab() plugin_folder = self.find_plugin_folder(name) assert plugin_folder is not None - plugin_dir = path.join(self.plugin_path, ) + plugin_dir = path.join(self.plugin_path, plugin_folder) try: logger.info("uninstalling " + name) logger.info(" at dir " + plugin_dir) -- cgit v1.2.3 From d9ba637cd9c44bc7c7ec4e8381f4f05fae8e4244 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Wed, 18 Oct 2023 14:45:36 +0300 Subject: Add message emit mechanism --- backend/src/plugin/plugin.py | 41 ++++++++++++++++++++++------------ backend/src/plugin/sandboxed_plugin.py | 22 +++++++++--------- 2 files changed, 39 insertions(+), 24 deletions(-) (limited to 'backend') diff --git a/backend/src/plugin/plugin.py b/backend/src/plugin/plugin.py index d5f79d42..a1b43ba9 100644 --- a/backend/src/plugin/plugin.py +++ b/backend/src/plugin/plugin.py @@ -2,12 +2,13 @@ from asyncio import Task, create_task from json import dumps, load, loads from logging import getLogger from os import path +from multiprocessing import Process from .sandboxed_plugin import SandboxedPlugin from .method_call_request import MethodCallRequest from ..localplatform.localsocket import LocalSocket -from typing import Any, Dict +from typing import Any, Callable, Coroutine, Dict class PluginWrapper: def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: @@ -28,40 +29,52 @@ class PluginWrapper: self.passive = not path.isfile(self.file) self.log = getLogger("plugin") - self.method_call_requests: Dict[str, MethodCallRequest] = {} + self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author) - #TODO: Maybe somehow make LocalSocket not require on_new_message to make this more clear - self.socket = LocalSocket(self.sandboxed_plugin.on_new_message) - self.listener_task: Task[Any] + #TODO: Maybe make LocalSocket not require on_new_message to make this cleaner + self._socket = LocalSocket(self.sandboxed_plugin.on_new_message) + self._listener_task: Task[Any] + self._method_call_requests: Dict[str, MethodCallRequest] = {} + + self.emitted_message_callback: Callable[[Dict[Any, Any]], Coroutine[Any, Any, Any]] def __str__(self) -> str: return self.name async def _response_listener(self): while True: - line = await self.socket.read_single_line() + line = await self._socket.read_single_line() if line != None: res = loads(line) - self.method_call_requests.pop(res["id"]).set_result(res) + if res["id"] == 0: + create_task(self.emitted_message_callback(res["payload"])) + return + self._method_call_requests.pop(res["id"]).set_result(res) + + async def set_emitted_message_callback(self, callback: Callable[[Dict[Any, Any]], Coroutine[Any, Any, Any]]): + self.emitted_message_callback = callback async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]): if self.passive: raise RuntimeError("This plugin is passive (aka does not implement main.py)") request = MethodCallRequest() - await self.socket.get_socket_connection() - await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs, "id": request.id }, ensure_ascii=False)) - self.method_call_requests[request.id] = request + await self._socket.get_socket_connection() + await self._socket.write_single_line(dumps({ "method": method_name, "args": kwargs, "id": request.id }, ensure_ascii=False)) + self._method_call_requests[request.id] = request return await request.wait_for_result() async def start(self): - self.sandboxed_plugin.start(self.socket) + if self.passive: + return self + Process(target=self.sandboxed_plugin.initialize, args=[self._socket]).start() self.listener_task = create_task(self._response_listener()) + return self async def stop(self): - self.listener_task.cancel() + self._listener_task.cancel() async def _(self: PluginWrapper): - await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) - await self.socket.close_socket_connection() + await self._socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) + await self._socket.close_socket_connection() create_task(_(self)) \ No newline at end of file diff --git a/backend/src/plugin/sandboxed_plugin.py b/backend/src/plugin/sandboxed_plugin.py index 7c96da01..972a535a 100644 --- a/backend/src/plugin/sandboxed_plugin.py +++ b/backend/src/plugin/sandboxed_plugin.py @@ -3,7 +3,6 @@ from signal import SIGINT, signal from importlib.util import module_from_spec, spec_from_file_location from json import dumps, loads from logging import getLogger -import multiprocessing from sys import exit, path as syspath from traceback import format_exc from asyncio import (get_event_loop, new_event_loop, @@ -15,7 +14,7 @@ from ..localplatform.localplatform import setgid, setuid, get_username, get_home from ..customtypes import UserType from .. import helpers -from typing import List +from typing import Any, Dict, List class SandboxedPlugin: def __init__(self, @@ -38,7 +37,9 @@ class SandboxedPlugin: self.log = getLogger("plugin") - def _init(self, socket: LocalSocket): + def initialize(self, socket: LocalSocket): + self._socket = socket + try: signal(SIGINT, lambda s, f: exit(0)) @@ -79,6 +80,9 @@ class SandboxedPlugin: spec.loader.exec_module(module) self.Plugin = module.Plugin + setattr(self.Plugin, "emit_message", self.emit_message) + #TODO: Find how to put emit_message on global namespace so it doesn't pollute Plugin + if hasattr(self.Plugin, "_migration"): get_event_loop().run_until_complete(self.Plugin._migration(self.Plugin)) if hasattr(self.Plugin, "_main"): @@ -113,7 +117,6 @@ class SandboxedPlugin: get_event_loop().close() raise Exception("Closing message listener") - # TODO there is definitely a better way to type this d: SocketResponseDict = {"res": None, "success": True, "id": data["id"]} try: d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"]) @@ -123,9 +126,8 @@ class SandboxedPlugin: finally: return dumps(d, ensure_ascii=False) - - def start(self, socket: LocalSocket): - if self.passive: - return self - multiprocessing.Process(target=self._init, args=[socket]).start() - return self \ No newline at end of file + async def emit_message(self, message: Dict[Any, Any]): + await self._socket.write_single_line(dumps({ + "id": 0, + "payload": message + })) \ No newline at end of file -- cgit v1.2.3 From 88250b3e20a6635bb1e14d4033f8e5be8ef19c6f Mon Sep 17 00:00:00 2001 From: marios8543 Date: Wed, 18 Oct 2023 14:59:05 +0300 Subject: fix customtypes import --- backend/src/localplatform/localplatformwin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'backend') diff --git a/backend/src/localplatform/localplatformwin.py b/backend/src/localplatform/localplatformwin.py index 4c4e9439..212ff2fe 100644 --- a/backend/src/localplatform/localplatformwin.py +++ b/backend/src/localplatform/localplatformwin.py @@ -1,4 +1,4 @@ -from .customtypes import UserType +from ..customtypes import UserType import os, sys def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: -- cgit v1.2.3 From 934b1b35ad2ab2743a9d55de21eea73a54df72b9 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Wed, 18 Oct 2023 15:34:25 +0300 Subject: fix start/stop methods --- backend/src/plugin/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'backend') diff --git a/backend/src/plugin/plugin.py b/backend/src/plugin/plugin.py index a1b43ba9..a0f926cd 100644 --- a/backend/src/plugin/plugin.py +++ b/backend/src/plugin/plugin.py @@ -65,14 +65,14 @@ class PluginWrapper: return await request.wait_for_result() - async def start(self): + def start(self): if self.passive: return self Process(target=self.sandboxed_plugin.initialize, args=[self._socket]).start() self.listener_task = create_task(self._response_listener()) return self - async def stop(self): + def stop(self): self._listener_task.cancel() async def _(self: PluginWrapper): await self._socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) -- cgit v1.2.3 From 47e9708a209c9f48159880b90a710ea26ec09a29 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Wed, 18 Oct 2023 17:21:57 +0300 Subject: fix static/lang file fetch and method call --- backend/src/loader.py | 7 +++---- backend/src/plugin/method_call_request.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'backend') diff --git a/backend/src/loader.py b/backend/src/loader.py index 49f80c64..162cf498 100644 --- a/backend/src/loader.py +++ b/backend/src/loader.py @@ -102,13 +102,12 @@ class Loader: self.watcher.disabled = False async def handle_frontend_assets(self, request: web.Request): - file = path.join(path.dirname(__file__), "..", "static", request.match_info["path"]) - + file = Path(__file__).parents[1].joinpath("static").joinpath(request.match_info["path"]) return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) async def handle_frontend_locales(self, request: web.Request): req_lang = request.match_info["path"] - file = path.join(path.dirname(__file__), "..", "locales", req_lang) + file = Path(__file__).parents[1].joinpath("locales").joinpath(req_lang) if exists(file): return web.FileResponse(file, headers={"Cache-Control": "no-cache", "Content-Type": "application/json"}) else: @@ -143,7 +142,7 @@ class Loader: self.plugins.pop(plugin.name, None) if plugin.passive: self.logger.info(f"Plugin {plugin.name} is passive") - self.plugins[plugin.name] = plugin + self.plugins[plugin.name] = plugin.start() self.logger.info(f"Loaded {plugin.name}") if not batch: self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version)) diff --git a/backend/src/plugin/method_call_request.py b/backend/src/plugin/method_call_request.py index 8d93a6cc..cebe34f8 100644 --- a/backend/src/plugin/method_call_request.py +++ b/backend/src/plugin/method_call_request.py @@ -26,4 +26,4 @@ class MethodCallRequest: await self.event.wait() if not self.response.success: raise Exception(self.response.result) - return self.response \ No newline at end of file + return self.response.result \ No newline at end of file -- cgit v1.2.3 From feabb582b2def5ad437a57dc9600d398d32fcfab Mon Sep 17 00:00:00 2001 From: marios8543 Date: Wed, 18 Oct 2023 19:38:07 +0300 Subject: run method calls asynchronously --- backend/src/localplatform/localsocket.py | 19 +++++++++---------- backend/src/plugin/plugin.py | 22 +++++++++++++--------- backend/src/plugin/sandboxed_plugin.py | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) (limited to 'backend') diff --git a/backend/src/localplatform/localsocket.py b/backend/src/localplatform/localsocket.py index f38fe5e7..2ef039ee 100644 --- a/backend/src/localplatform/localsocket.py +++ b/backend/src/localplatform/localsocket.py @@ -1,5 +1,5 @@ import asyncio, time -from typing import Awaitable, Callable +from typing import Any, Callable, Coroutine import random from .localplatform import ON_WINDOWS @@ -7,7 +7,7 @@ from .localplatform import ON_WINDOWS BUFFER_LIMIT = 2 ** 20 # 1 MiB class UnixSocket: - def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): + def __init__(self, on_new_message: Callable[[str], Coroutine[Any, Any, Any]]): ''' on_new_message takes 1 string argument. It's return value gets used, if not None, to write data to the socket. @@ -93,18 +93,17 @@ class UnixSocket: async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): while True: - line = await self._read_single_line(reader) - try: - res = await self.on_new_message(line) - except Exception: - return + def _(task: asyncio.Task[str|None]): + res = task.result() + if res is not None: + asyncio.create_task(self._write_single_line(writer, res)) - if res != None: - await self._write_single_line(writer, res) + line = await self._read_single_line(reader) + asyncio.create_task(self.on_new_message(line)).add_done_callback(_) class PortSocket (UnixSocket): - def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): + def __init__(self, on_new_message: Callable[[str], Coroutine[Any, Any, Any]]): ''' on_new_message takes 1 string argument. It's return value gets used, if not None, to write data to the socket. diff --git a/backend/src/plugin/plugin.py b/backend/src/plugin/plugin.py index a0f926cd..befd1569 100644 --- a/backend/src/plugin/plugin.py +++ b/backend/src/plugin/plugin.py @@ -26,6 +26,7 @@ class PluginWrapper: self.name = json["name"] self.author = json["author"] self.flags = json["flags"] + self.passive = not path.isfile(self.file) self.log = getLogger("plugin") @@ -43,15 +44,18 @@ class PluginWrapper: async def _response_listener(self): while True: - line = await self._socket.read_single_line() - if line != None: - res = loads(line) - if res["id"] == 0: - create_task(self.emitted_message_callback(res["payload"])) - return - self._method_call_requests.pop(res["id"]).set_result(res) + try: + line = await self._socket.read_single_line() + if line != None: + res = loads(line) + if res["id"] == "0": + create_task(self.emitted_message_callback(res["payload"])) + return + self._method_call_requests.pop(res["id"]).set_result(res) + except: + pass - async def set_emitted_message_callback(self, callback: Callable[[Dict[Any, Any]], Coroutine[Any, Any, Any]]): + def set_emitted_message_callback(self, callback: Callable[[Dict[Any, Any]], Coroutine[Any, Any, Any]]): self.emitted_message_callback = callback async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]): @@ -69,7 +73,7 @@ class PluginWrapper: if self.passive: return self Process(target=self.sandboxed_plugin.initialize, args=[self._socket]).start() - self.listener_task = create_task(self._response_listener()) + self._listener_task = create_task(self._response_listener()) return self def stop(self): diff --git a/backend/src/plugin/sandboxed_plugin.py b/backend/src/plugin/sandboxed_plugin.py index 972a535a..8540bebc 100644 --- a/backend/src/plugin/sandboxed_plugin.py +++ b/backend/src/plugin/sandboxed_plugin.py @@ -128,6 +128,6 @@ class SandboxedPlugin: async def emit_message(self, message: Dict[Any, Any]): await self._socket.write_single_line(dumps({ - "id": 0, + "id": "0", "payload": message })) \ No newline at end of file -- cgit v1.2.3 From 28ca7b5c904ab67fab4147a340f2d78ca9a6e473 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Wed, 18 Oct 2023 21:04:51 +0300 Subject: fix emit_message mechanism --- backend/src/loader.py | 5 +++++ backend/src/localplatform/localsocket.py | 7 +++++++ backend/src/plugin/plugin.py | 4 ++-- backend/src/plugin/sandboxed_plugin.py | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) (limited to 'backend') diff --git a/backend/src/loader.py b/backend/src/loader.py index 162cf498..7567912c 100644 --- a/backend/src/loader.py +++ b/backend/src/loader.py @@ -22,6 +22,10 @@ from .plugin.plugin import PluginWrapper Plugins = dict[str, PluginWrapper] ReloadQueue = Queue[Tuple[str, str, bool | None] | Tuple[str, str]] +#TODO: Remove placeholder method +async def log_plugin_emitted_message(message: Any): + getLogger().debug(f"EMITTED MESSAGE: " + str(message)) + class FileChangeHandler(RegexMatchingEventHandler): def __init__(self, queue: ReloadQueue, plugin_path: str) -> None: super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) # type: ignore @@ -143,6 +147,7 @@ class Loader: if plugin.passive: self.logger.info(f"Plugin {plugin.name} is passive") self.plugins[plugin.name] = plugin.start() + self.plugins[plugin.name].set_emitted_message_callback(log_plugin_emitted_message) self.logger.info(f"Loaded {plugin.name}") if not batch: self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version)) diff --git a/backend/src/localplatform/localsocket.py b/backend/src/localplatform/localsocket.py index 2ef039ee..93b1ea18 100644 --- a/backend/src/localplatform/localsocket.py +++ b/backend/src/localplatform/localsocket.py @@ -18,6 +18,7 @@ class UnixSocket: self.socket = None self.reader = None self.writer = None + self.server_writer = None async def setup_server(self): self.socket = await asyncio.start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT) @@ -90,8 +91,14 @@ class UnixSocket: writer.write(message.encode("utf-8")) await writer.drain() + + async def write_single_line_server(self, message: str): + if self.server_writer is None: + return + await self._write_single_line(self.server_writer, message) async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): + self.server_writer = writer while True: def _(task: asyncio.Task[str|None]): diff --git a/backend/src/plugin/plugin.py b/backend/src/plugin/plugin.py index befd1569..6c338106 100644 --- a/backend/src/plugin/plugin.py +++ b/backend/src/plugin/plugin.py @@ -50,8 +50,8 @@ class PluginWrapper: res = loads(line) if res["id"] == "0": create_task(self.emitted_message_callback(res["payload"])) - return - self._method_call_requests.pop(res["id"]).set_result(res) + else: + self._method_call_requests.pop(res["id"]).set_result(res) except: pass diff --git a/backend/src/plugin/sandboxed_plugin.py b/backend/src/plugin/sandboxed_plugin.py index 8540bebc..d44794fa 100644 --- a/backend/src/plugin/sandboxed_plugin.py +++ b/backend/src/plugin/sandboxed_plugin.py @@ -127,7 +127,7 @@ class SandboxedPlugin: return dumps(d, ensure_ascii=False) async def emit_message(self, message: Dict[Any, Any]): - await self._socket.write_single_line(dumps({ + await self._socket.write_single_line_server(dumps({ "id": "0", "payload": message })) \ No newline at end of file -- cgit v1.2.3