mirror of
https://github.com/drakmor/ShadowMountPlus.git
synced 2026-10-21 06:10:01 -05:00
1736 lines
180 KiB
HTML
1736 lines
180 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="color-scheme" content="dark">
|
||
<title>ShadowMount Library</title>
|
||
<style>
|
||
:root {
|
||
color-scheme: dark;
|
||
--bg: #080b12;
|
||
--surface: #111722;
|
||
--surface-2: #171f2d;
|
||
--line: #263145;
|
||
--text: #f4f7fb;
|
||
--muted: #95a2b8;
|
||
--blue: #55a7ff;
|
||
--blue-strong: #147ee8;
|
||
--green: #52d39a;
|
||
--amber: #f2bd5d;
|
||
--red: #ff6c79;
|
||
--radius: 14px;
|
||
--shadow: 0 18px 50px rgba(0, 0, 0, .34);
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
body {
|
||
margin: 0;
|
||
min-width: 320px;
|
||
min-height: 100vh;
|
||
background:
|
||
radial-gradient(circle at 15% -10%, rgba(20, 126, 232, .17), transparent 34rem),
|
||
var(--bg);
|
||
color: var(--text);
|
||
font: 15px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif;
|
||
}
|
||
|
||
button, input, select { font: inherit; }
|
||
button, select { cursor: pointer; }
|
||
|
||
button:focus-visible, input:focus-visible, select:focus-visible {
|
||
outline: 2px solid var(--blue);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.shell { width: min(1500px, 100%); margin: 0 auto; padding: 22px; }
|
||
|
||
.topbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 18px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.brand { display: flex; align-items: center; gap: 13px; }
|
||
|
||
.brand-mark {
|
||
display: grid;
|
||
place-items: center;
|
||
width: 42px;
|
||
height: 42px;
|
||
border: 1px solid rgba(85, 167, 255, .45);
|
||
border-radius: 12px;
|
||
background: linear-gradient(145deg, #147ee8, #74baff);
|
||
box-shadow: 0 8px 24px rgba(20, 126, 232, .25);
|
||
font-size: 20px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
h1, h2, h3, p { margin: 0; }
|
||
h1 { font-size: 20px; letter-spacing: -.02em; }
|
||
h2 { font-size: 17px; }
|
||
.subtle { color: var(--muted); font-size: 13px; }
|
||
|
||
.connection {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.topbar-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.language-select {
|
||
min-width: 110px;
|
||
min-height: 36px;
|
||
}
|
||
|
||
.connection-dot {
|
||
width: 9px;
|
||
height: 9px;
|
||
border-radius: 50%;
|
||
background: var(--amber);
|
||
box-shadow: 0 0 0 4px rgba(242, 189, 93, .12);
|
||
}
|
||
|
||
.connection.online .connection-dot {
|
||
background: var(--green);
|
||
box-shadow: 0 0 0 4px rgba(82, 211, 154, .12);
|
||
}
|
||
|
||
.connection.offline .connection-dot {
|
||
background: var(--red);
|
||
box-shadow: 0 0 0 4px rgba(255, 108, 121, .12);
|
||
}
|
||
|
||
.toolbar, .panel, .stat, .game-card, .modal-card {
|
||
border: 1px solid var(--line);
|
||
background: rgba(17, 23, 34, .96);
|
||
box-shadow: var(--shadow);
|
||
}
|
||
|
||
.toolbar {
|
||
display: grid;
|
||
grid-template-columns: minmax(220px, 1fr) 150px auto auto auto;
|
||
gap: 10px;
|
||
padding: 13px;
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
input, select {
|
||
min-height: 40px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 10px;
|
||
background: #0c111a;
|
||
color: var(--text);
|
||
padding: 0 12px;
|
||
}
|
||
|
||
input::placeholder { color: #6f7c92; }
|
||
|
||
.check {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-height: 40px;
|
||
color: var(--muted);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.check input { min-height: 0; width: 17px; height: 17px; accent-color: var(--blue); }
|
||
|
||
.btn {
|
||
min-height: 40px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 10px;
|
||
background: var(--surface-2);
|
||
color: var(--text);
|
||
padding: 0 14px;
|
||
transition: border-color .15s ease, background .15s ease, transform .15s ease;
|
||
}
|
||
|
||
.btn:hover { border-color: #3b4a63; background: #1c2637; }
|
||
.btn:active { transform: translateY(1px); }
|
||
.btn:disabled { cursor: wait; opacity: .55; }
|
||
.btn.primary { border-color: var(--blue-strong); background: var(--blue-strong); }
|
||
.btn.primary:hover { background: #238cf3; }
|
||
.btn.danger { border-color: rgba(255, 108, 121, .45); color: #ff9aa3; }
|
||
.btn.small { min-height: 34px; padding: 0 11px; font-size: 13px; }
|
||
|
||
.stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 12px;
|
||
margin: 14px 0;
|
||
}
|
||
|
||
.stat { padding: 15px 17px; border-radius: var(--radius); box-shadow: none; }
|
||
.stat-value { display: block; margin-top: 3px; font-size: 25px; font-weight: 750; }
|
||
|
||
.layout {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 310px;
|
||
align-items: start;
|
||
gap: 14px;
|
||
}
|
||
|
||
.section-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin: 4px 0 11px;
|
||
}
|
||
|
||
.games {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.game-card {
|
||
display: grid;
|
||
grid-template-columns: 88px minmax(0, 1fr);
|
||
align-items: start;
|
||
gap: 14px;
|
||
min-height: 116px;
|
||
padding: 13px;
|
||
border-radius: var(--radius);
|
||
box-shadow: none;
|
||
}
|
||
|
||
.cover {
|
||
position: relative;
|
||
width: 88px;
|
||
aspect-ratio: 1;
|
||
overflow: hidden;
|
||
border: 1px solid var(--line);
|
||
border-radius: 12px;
|
||
background: linear-gradient(145deg, #1d2c43, #0c111a);
|
||
}
|
||
|
||
.cover img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
|
||
.cover-fallback { display: grid; place-items: center; width: 100%; height: 100%; color: var(--blue); font-weight: 800; }
|
||
.game-main { min-width: 0; }
|
||
.game-heading { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 10px; }
|
||
.game-identity { min-width: 0; }
|
||
.game-title { overflow: hidden; font-size: 15px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
|
||
.game-id { color: var(--muted); font: 12px/1.5 ui-monospace, "SFMono-Regular", Consolas, monospace; }
|
||
.game-card-size { color: var(--blue); font-size: 14px; font-weight: 750; white-space: nowrap; }
|
||
|
||
.badges { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
|
||
.badge { border: 1px solid var(--line); border-radius: 999px; color: var(--muted); padding: 2px 7px; font-size: 11px; text-transform: uppercase; }
|
||
.badge.live { border-color: rgba(82, 211, 154, .35); color: var(--green); }
|
||
.badge.image { border-color: rgba(85, 167, 255, .38); color: #8cc4ff; }
|
||
|
||
.game-path { overflow: hidden; color: var(--muted); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
|
||
.game-actions { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 10px; }
|
||
|
||
.panel { padding: 16px; border-radius: var(--radius); box-shadow: none; }
|
||
.sidebar { display: grid; gap: 14px; position: sticky; top: 14px; }
|
||
.storage-list { display: grid; gap: 9px; margin-top: 12px; }
|
||
|
||
.storage-row {
|
||
min-width: 0;
|
||
padding: 10px;
|
||
border-radius: 10px;
|
||
background: #0c111a;
|
||
}
|
||
|
||
.storage-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
||
.storage-path { overflow: hidden; font-size: 13px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
|
||
.storage-meta { overflow: hidden; margin-top: 3px; color: var(--muted); font: 11px/1.4 ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
||
.storage-space { display: flex; justify-content: space-between; gap: 8px; margin-top: 7px; color: var(--muted); font-size: 11px; }
|
||
|
||
.storage-meter {
|
||
height: 6px;
|
||
margin-top: 6px;
|
||
overflow: hidden;
|
||
border-radius: 999px;
|
||
background: #202a3a;
|
||
}
|
||
|
||
.storage-fill { height: 100%; border-radius: inherit; background: var(--blue); }
|
||
.storage-fill.warning { background: var(--amber); }
|
||
.storage-fill.danger { background: var(--red); }
|
||
.transfer-details { display: grid; gap: 10px; margin-top: 12px; }
|
||
.transfer-details[hidden] { display: none; }
|
||
.transfer-route { overflow-wrap: anywhere; color: var(--muted); font: 11px/1.45 ui-monospace, monospace; }
|
||
.transfer-meter { height: 9px; margin-top: 0; }
|
||
.transfer-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||
.transfer-stat { padding: 8px 9px; border-radius: 9px; background: #0c111a; }
|
||
.transfer-stat strong { display: block; margin-top: 2px; font-size: 13px; }
|
||
.transfer-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
||
.manual-form { display: flex; gap: 7px; margin-top: 12px; }
|
||
.manual-form input { min-width: 0; width: 100%; }
|
||
.manual-list, .activity-list { display: grid; gap: 7px; margin-top: 12px; }
|
||
|
||
.manual-row {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) auto;
|
||
align-items: center;
|
||
gap: 7px;
|
||
padding: 8px 9px;
|
||
border-radius: 9px;
|
||
background: #0c111a;
|
||
}
|
||
|
||
.manual-path { overflow: hidden; color: var(--muted); font: 11px/1.4 ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
|
||
.settings-form { display: grid; gap: 10px; margin-top: 12px; }
|
||
.settings-form .check { white-space: normal; }
|
||
.setting-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; color: var(--muted); font-size: 12px; }
|
||
.setting-row select, .setting-row input { width: 112px; }
|
||
.settings-actions { display: flex; flex-wrap: wrap; gap: 7px; }
|
||
.subheading { margin-top: 17px; font-size: 14px; }
|
||
.log-view { min-height: 460px; max-height: 76vh; margin: 0; overflow: auto; padding: 20px; background: #080c12; color: #c8d7ea; font: 14px/1.55 ui-monospace, "SFMono-Regular", Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||
.activity-item { border-left: 2px solid var(--line); padding-left: 9px; color: var(--muted); font-size: 12px; }
|
||
.activity-item.ok { border-color: var(--green); }
|
||
.activity-item.error { border-color: var(--red); color: #ff9aa3; }
|
||
|
||
.empty {
|
||
grid-column: 1 / -1;
|
||
padding: 46px 20px;
|
||
border: 1px dashed var(--line);
|
||
border-radius: var(--radius);
|
||
color: var(--muted);
|
||
text-align: center;
|
||
}
|
||
|
||
.modal {
|
||
position: fixed;
|
||
z-index: 20;
|
||
inset: 0;
|
||
display: grid;
|
||
place-items: center;
|
||
padding: 20px;
|
||
background: rgba(2, 5, 10, .78);
|
||
}
|
||
|
||
.modal[hidden] { display: none; }
|
||
.modal-card { width: min(720px, 100%); max-height: 90vh; overflow: auto; border-radius: 18px; }
|
||
.modal-card.log-card { width: min(1440px, 96vw); }
|
||
.modal-head { display: flex; align-items: start; justify-content: space-between; gap: 20px; padding: 18px; border-bottom: 1px solid var(--line); }
|
||
.close { width: 36px; min-height: 36px; padding: 0; font-size: 22px; }
|
||
.details { display: grid; grid-template-columns: 168px minmax(0, 1fr); align-items: start; gap: 18px; padding: 18px; }
|
||
.details .cover { width: 168px; }
|
||
.detail-grid { display: grid; grid-template-columns: 130px minmax(0, 1fr); gap: 7px 12px; margin-top: 13px; font-size: 13px; }
|
||
.detail-key { color: var(--muted); }
|
||
.detail-value { min-width: 0; overflow-wrap: anywhere; }
|
||
.modal-actions { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 18px 18px; }
|
||
|
||
.toast {
|
||
position: fixed;
|
||
right: 20px;
|
||
bottom: 20px;
|
||
z-index: 30;
|
||
max-width: min(420px, calc(100vw - 40px));
|
||
border: 1px solid var(--line);
|
||
border-radius: 11px;
|
||
background: #172131;
|
||
box-shadow: var(--shadow);
|
||
padding: 12px 15px;
|
||
}
|
||
|
||
.toast[hidden] { display: none; }
|
||
.toast.error { border-color: rgba(255, 108, 121, .5); color: #ffabb2; }
|
||
|
||
@media (max-width: 980px) {
|
||
.toolbar { grid-template-columns: minmax(180px, 1fr) 150px auto; }
|
||
.layout { grid-template-columns: 1fr; }
|
||
.sidebar { position: static; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
}
|
||
|
||
@media (max-width: 660px) {
|
||
.modal { padding: 10px; }
|
||
.modal-card.log-card { width: 100%; }
|
||
.log-view { min-height: 60vh; max-height: 75vh; padding: 14px; font-size: 13px; }
|
||
.shell { padding: 14px; }
|
||
.topbar { flex-direction: column; align-items: stretch; }
|
||
.topbar-actions { justify-content: space-between; }
|
||
.toolbar { grid-template-columns: 1fr 1fr; }
|
||
.toolbar #search { grid-column: 1 / -1; }
|
||
.stats { grid-template-columns: 1fr 1fr; }
|
||
.games { grid-template-columns: 1fr; }
|
||
.sidebar { grid-template-columns: 1fr; }
|
||
.details { grid-template-columns: 1fr; }
|
||
.details .cover { width: 120px; }
|
||
.detail-grid { grid-template-columns: 105px minmax(0, 1fr); }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main class="shell">
|
||
<header class="topbar">
|
||
<div class="brand">
|
||
<div class="brand-mark" aria-hidden="true">S</div>
|
||
<div>
|
||
<h1 data-i18n="pageTitle">ShadowMount Library</h1>
|
||
<p class="subtle" id="version" data-i18n="connecting">Connecting to API…</p>
|
||
</div>
|
||
</div>
|
||
<div class="topbar-actions">
|
||
<div class="connection" id="connection" role="status">
|
||
<span class="connection-dot"></span>
|
||
<span id="connectionText" data-i18n="checking">Checking</span>
|
||
</div>
|
||
<select class="language-select" id="language" aria-label="Language" data-i18n-aria-label="language">
|
||
<option value="ja-JP">日本語</option>
|
||
<option value="en-US">English (US)</option>
|
||
<option value="fr-FR">Français (France)</option>
|
||
<option value="es-ES">Español (España)</option>
|
||
<option value="de-DE">Deutsch</option>
|
||
<option value="it-IT">Italiano</option>
|
||
<option value="nl-NL">Nederlands</option>
|
||
<option value="pt-PT">Português (Portugal)</option>
|
||
<option value="ru-RU">Русский</option>
|
||
<option value="ko-KR">한국어</option>
|
||
<option value="zh-TW">繁體中文</option>
|
||
<option value="zh-CN">简体中文</option>
|
||
<option value="fi-FI">Suomi</option>
|
||
<option value="sv-SE">Svenska</option>
|
||
<option value="da-DK">Dansk</option>
|
||
<option value="no-NO">Norsk</option>
|
||
<option value="pl-PL">Polski</option>
|
||
<option value="pt-BR">Português (Brasil)</option>
|
||
<option value="en-GB">English (UK)</option>
|
||
<option value="tr-TR">Türkçe</option>
|
||
<option value="es-MX">Español (México)</option>
|
||
<option value="ar-SA">العربية</option>
|
||
<option value="fr-CA">Français (Canada)</option>
|
||
<option value="cs-CZ">Čeština</option>
|
||
<option value="hu-HU">Magyar</option>
|
||
<option value="el-GR">Ελληνικά</option>
|
||
<option value="ro-RO">Română</option>
|
||
<option value="th-TH">ไทย</option>
|
||
<option value="vi-VN">Tiếng Việt</option>
|
||
<option value="id-ID">Bahasa Indonesia</option>
|
||
<option value="uk-UA">Українська</option>
|
||
</select>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="toolbar" aria-label="Library controls" data-i18n-aria-label="libraryControls">
|
||
<input id="search" type="search" autocomplete="off" placeholder="Name, Title ID, Content ID, or path" data-i18n-placeholder="searchPlaceholder">
|
||
<select id="filter" aria-label="Library filter" data-i18n-aria-label="libraryFilter">
|
||
<option value="all" data-i18n="allGames">All games</option>
|
||
<option value="folder" data-i18n="folders">Folders</option>
|
||
<option value="image" data-i18n="images">Images</option>
|
||
<option value="ps4">PS4</option>
|
||
<option value="ps5">PS5</option>
|
||
</select>
|
||
<label class="check"><input id="includeSize" type="checkbox"> <span data-i18n="calculateSize">Calculate size</span></label>
|
||
<button class="btn" id="refreshBtn" type="button" data-i18n="refresh">Refresh</button>
|
||
<button class="btn primary" id="scanBtn" type="button" data-i18n="scan">Scan</button>
|
||
</section>
|
||
|
||
<section class="stats" aria-label="Library summary" data-i18n-aria-label="librarySummary">
|
||
<div class="stat"><span class="subtle" data-i18n="totalGames">Total games</span><strong class="stat-value" id="totalGames">—</strong></div>
|
||
<div class="stat"><span class="subtle" data-i18n="images">Images</span><strong class="stat-value" id="imageGames">—</strong></div>
|
||
<div class="stat"><span class="subtle" data-i18n="folders">Folders</span><strong class="stat-value" id="folderGames">—</strong></div>
|
||
<div class="stat"><span class="subtle" data-i18n="mounted">Mounted</span><strong class="stat-value" id="mountedGames">—</strong></div>
|
||
</section>
|
||
|
||
<div class="layout">
|
||
<section aria-labelledby="gamesHeading">
|
||
<div class="section-head">
|
||
<h2 id="gamesHeading" data-i18n="library">Library</h2>
|
||
<span class="subtle" id="visibleCount"></span>
|
||
</div>
|
||
<div class="games" id="games" aria-live="polite">
|
||
<div class="empty" data-i18n="loadingLibrary">Loading library…</div>
|
||
</div>
|
||
</section>
|
||
|
||
<aside class="sidebar">
|
||
<section class="panel" aria-labelledby="storageHeading">
|
||
<h2 id="storageHeading" data-i18n="storage">Storage</h2>
|
||
<p class="subtle" data-i18n="storageDescription">Free space at available mount points</p>
|
||
<div class="storage-list" id="storageList" aria-live="polite"><span class="subtle" data-i18n="loading">Loading…</span></div>
|
||
</section>
|
||
|
||
<section class="panel" aria-labelledby="transferHeading">
|
||
<div class="storage-head">
|
||
<h2 id="transferHeading" data-i18n="dataOperations">Data operations</h2>
|
||
<span class="badge" id="transferState" data-i18n="checking">Checking</span>
|
||
</div>
|
||
<p class="subtle" id="transferIdle" data-i18n="gettingOperationStatus">Getting operation status…</p>
|
||
<div class="transfer-details" id="transferDetails" hidden>
|
||
<strong id="transferSummary"></strong>
|
||
<div class="transfer-route" id="transferRoute"></div>
|
||
<div class="storage-meter transfer-meter" id="transferMeter" role="progressbar" aria-label="Operation progress" data-i18n-aria-label="operationProgress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
|
||
<div class="storage-fill" id="transferFill"></div>
|
||
</div>
|
||
<div class="transfer-stats">
|
||
<div class="transfer-stat"><span class="subtle" data-i18n="size">Size</span><strong id="transferBytes">—</strong></div>
|
||
<div class="transfer-stat"><span class="subtle" data-i18n="files">Files</span><strong id="transferFiles">—</strong></div>
|
||
<div class="transfer-stat"><span class="subtle" data-i18n="speed">Speed</span><strong id="transferSpeed">—</strong></div>
|
||
<div class="transfer-stat"><span class="subtle" data-i18n="time">Time</span><strong id="transferElapsed">—</strong></div>
|
||
</div>
|
||
<div class="transfer-actions">
|
||
<strong id="transferPercent">0%</strong>
|
||
<button class="btn small danger" id="cancelTransfer" type="button" data-i18n="cancel">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel" aria-labelledby="settingsHeading">
|
||
<h2 id="settingsHeading" data-i18n="settings">Settings</h2>
|
||
<form class="settings-form" id="settingsForm">
|
||
<label class="check"><input id="debugEnabled" type="checkbox"> <span data-i18n="debugLog">Debug log</span></label>
|
||
<label class="check"><input id="quietMode" type="checkbox"> <span data-i18n="quietMode">Quiet mode</span></label>
|
||
<label class="check"><input id="updateEmulators" type="checkbox"> <span data-i18n="updateFakelibEmulators">Update game fakelib emulators</span></label>
|
||
<label class="check"><input id="autoUpdateAmpr" type="checkbox"> <span data-i18n="autoUpdateAmpr">Update AMPR emulator from GitHub</span></label>
|
||
<label class="check"><input id="autoRemoveMissingGames" type="checkbox"> <span data-i18n="autoRemoveMissingGames">Remove unavailable app icons</span></label>
|
||
<label class="setting-row"><span data-i18n="autoRemoveDelay">Removal delay (seconds)</span><input id="autoRemoveMissingDelay" type="number" min="1" max="86400" step="1" value="300" required></label>
|
||
<label class="check"><input id="allowLanAccess" type="checkbox"> <span data-i18n="allowLanAccess">Allow local network access</span></label>
|
||
<label class="setting-row"><span data-i18n="fanTemperature">Fan target</span><select id="fanTemperature"><option value="0" data-i18n="systemControlled">System controlled</option></select></label>
|
||
<div class="settings-actions">
|
||
<button class="btn small primary" type="submit" data-i18n="saveSettings">Save settings</button>
|
||
<button class="btn small" id="viewLog" type="button" data-i18n="viewLog">View debug log</button>
|
||
<button class="btn small" id="viewKernelLog" type="button" data-i18n="viewKernelLog">View kernel log</button>
|
||
</div>
|
||
</form>
|
||
<h3 class="subheading" data-i18n="scanPaths">Scan paths</h3>
|
||
<p class="subtle" data-i18n="scanPathsDescription">Custom recursive roots; defaults stay hidden</p>
|
||
<form class="manual-form" id="scanPathForm">
|
||
<input id="scanPath" required placeholder="/mnt/usb0/games">
|
||
<button class="btn small" type="submit" data-i18n="add">Add</button>
|
||
</form>
|
||
<div class="manual-list" id="scanPathList"><span class="subtle" data-i18n="loading">Loading…</span></div>
|
||
<h3 class="subheading" id="manualHeading">manual.lst</h3>
|
||
<p class="subtle" data-i18n="manualDescription">Direct paths to individual game folders or images</p>
|
||
<form class="manual-form" id="manualForm">
|
||
<input id="manualPath" required placeholder="/mnt/usb0/games/PPSA12345">
|
||
<button class="btn small" type="submit" data-i18n="add">Add</button>
|
||
</form>
|
||
<div class="manual-list" id="manualList"><span class="subtle" data-i18n="loading">Loading…</span></div>
|
||
</section>
|
||
|
||
<section class="panel" aria-labelledby="scanHeading">
|
||
<h2 id="scanHeading" data-i18n="scanOptions">Scan options</h2>
|
||
<label class="check"><input id="resetAttempts" type="checkbox"> <span data-i18n="resetAttempts">Reset attempt counters</span></label>
|
||
<p class="subtle" data-i18n="resetAttemptsDescription">This option applies only to the next forced scan.</p>
|
||
</section>
|
||
|
||
<section class="panel" aria-labelledby="activityHeading">
|
||
<h2 id="activityHeading" data-i18n="recentOperations">Recent operations</h2>
|
||
<div class="activity-list" id="activity"><span class="subtle" data-i18n="noOperations">No operations yet</span></div>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
</main>
|
||
|
||
<div class="modal" id="modal" hidden role="dialog" aria-modal="true" aria-labelledby="modalTitle">
|
||
<div class="modal-card">
|
||
<div class="modal-head">
|
||
<div>
|
||
<h2 id="modalTitle" data-i18n="gameInformation">Game information</h2>
|
||
<p class="subtle" id="modalSubtitle"></p>
|
||
</div>
|
||
<button class="btn close" id="closeModal" type="button" aria-label="Close" data-i18n-aria-label="close">×</button>
|
||
</div>
|
||
<div class="details" id="details"></div>
|
||
<div class="modal-actions" id="modalActions"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal" id="logModal" hidden role="dialog" aria-modal="true" aria-labelledby="logTitle">
|
||
<div class="modal-card log-card">
|
||
<div class="modal-head">
|
||
<div>
|
||
<h2 id="logTitle" data-i18n="debugLogTitle">Debug log</h2>
|
||
<p class="subtle" id="logSubtitle"></p>
|
||
</div>
|
||
<div class="settings-actions">
|
||
<button class="btn small" id="refreshLog" type="button" data-i18n="refresh">Refresh</button>
|
||
<button class="btn close" id="closeLog" type="button" aria-label="Close" data-i18n-aria-label="close">×</button>
|
||
</div>
|
||
</div>
|
||
<pre class="log-view" id="logContent"></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast" id="toast" hidden role="status"></div>
|
||
|
||
<script>
|
||
(() => {
|
||
"use strict";
|
||
|
||
const API = "/api/v1";
|
||
const state = {
|
||
games: [],
|
||
selected: null,
|
||
activity: [],
|
||
busy: false,
|
||
storageJob: null,
|
||
storagePollTimer: 0,
|
||
reportedStorageJob: "",
|
||
scanPaths: [],
|
||
apiVersion: null,
|
||
connectionStatus: "",
|
||
connectionKey: "checking",
|
||
logRoute: "/debug-log",
|
||
logTitleKey: "debugLogTitle",
|
||
logTriggerId: "viewLog"
|
||
};
|
||
const $ = (id) => document.getElementById(id);
|
||
|
||
const translations = {
|
||
"en-US": {
|
||
pageTitle: "ShadowMount Library",
|
||
connecting: "Connecting to API…",
|
||
checking: "Checking",
|
||
libraryControls: "Library controls",
|
||
searchPlaceholder: "Name, Title ID, Content ID, or path",
|
||
libraryFilter: "Library filter",
|
||
allGames: "All games",
|
||
folders: "Folders",
|
||
images: "Images",
|
||
language: "Language",
|
||
calculateSize: "Calculate size",
|
||
refresh: "Refresh",
|
||
scan: "Scan",
|
||
librarySummary: "Library summary",
|
||
totalGames: "Total games",
|
||
mounted: "Mounted",
|
||
library: "Library",
|
||
loadingLibrary: "Loading library…",
|
||
storage: "Storage",
|
||
storageDescription: "Free space at available mount points",
|
||
loading: "Loading…",
|
||
dataOperations: "Data operations",
|
||
gettingOperationStatus: "Getting operation status…",
|
||
operationProgress: "Operation progress",
|
||
size: "Size",
|
||
files: "Files",
|
||
speed: "Speed",
|
||
perSecond: "/s",
|
||
time: "Time",
|
||
cancel: "Cancel",
|
||
manualDescription: "Direct paths to individual game folders or images",
|
||
add: "Add",
|
||
settings: "Settings",
|
||
debugLog: "Debug log",
|
||
quietMode: "Quiet mode",
|
||
updateFakelibEmulators: "Update game fakelib emulators",
|
||
autoUpdateAmpr: "Update AMPR emulator from GitHub",
|
||
autoRemoveMissingGames: "Remove unavailable app icons",
|
||
autoRemoveDelay: "Removal delay (seconds)",
|
||
allowLanAccess: "Allow local network access",
|
||
fanTemperature: "Fan target",
|
||
systemControlled: "System controlled",
|
||
saveSettings: "Save settings",
|
||
settingsSaved: "Settings saved; runtime reload queued",
|
||
viewLog: "View debug log",
|
||
viewKernelLog: "View kernel log",
|
||
debugLogTitle: "Debug log",
|
||
kernelLogTitle: "Kernel log",
|
||
logBytes: "Showing {shown} of {total}",
|
||
scanPaths: "Scan paths",
|
||
scanPathsDescription: "Custom recursive roots; an empty list uses built-in defaults",
|
||
scanPathAdded: "Scan path added locally; save settings to apply",
|
||
scanPathRemoved: "Scan path removed locally; save settings to apply",
|
||
invalidAbsolutePath: "Enter a non-root absolute path",
|
||
scanOptions: "Scan options",
|
||
resetAttempts: "Reset attempt counters",
|
||
resetAttemptsDescription: "This option applies only to the next forced scan.",
|
||
recentOperations: "Recent operations",
|
||
noOperations: "No operations yet",
|
||
gameInformation: "Game information",
|
||
close: "Close",
|
||
coverAlt: "Cover for {title}",
|
||
unknown: "unknown",
|
||
folder: "folder",
|
||
image: "image",
|
||
sourceUnavailable: "Source unavailable",
|
||
details: "Details",
|
||
connect: "Mount",
|
||
disconnect: "Unmount",
|
||
nothingFound: "Nothing found",
|
||
libraryEmpty: "The library is empty",
|
||
shown: "Shown: {count}",
|
||
jobIdle: "No job",
|
||
jobPreparing: "Preparing",
|
||
jobMeasuring: "Measuring",
|
||
jobDeleting: "Deleting",
|
||
jobFinalizing: "Finalizing",
|
||
jobCompleted: "Completed",
|
||
jobFailed: "Failed",
|
||
jobCancelled: "Cancelled",
|
||
operationCopy: "Copying",
|
||
operationMove: "Moving",
|
||
operationDelete: "Deleting",
|
||
operationUnpack: "Unpacking",
|
||
noActiveOperations: "No active operations",
|
||
calculating: "Calculating…",
|
||
cancelling: "Cancelling…",
|
||
operationCompleted: "{operation} {title} completed",
|
||
operationCompletedShort: "{operation} completed",
|
||
operationCancelled: "{operation} {title} cancelled",
|
||
operationCancelledShort: "{operation} cancelled",
|
||
statusError: "error {status}",
|
||
operationError: "{operation} {title}: {error}",
|
||
unavailable: "Unavailable",
|
||
cancelRequested: "Cancellation requested for job {id}",
|
||
storageNotFound: "No storage found",
|
||
freeSpace: "Free {size}",
|
||
totalSpace: "of {size}",
|
||
storageUsed: "Used space at {path}",
|
||
connected: "PS5 connected",
|
||
disconnected: "No connection",
|
||
apiError: "API error: {error}",
|
||
listEmpty: "The list is empty",
|
||
remove: "Remove",
|
||
manualAdded: "Source added to manual.lst",
|
||
manualExists: "Source is already in manual.lst",
|
||
removeManualConfirm: "Remove this source from manual.lst?\n\n{path}",
|
||
manualRemoved: "Source removed from manual.lst",
|
||
scanQueuedReset: "Scan queued with attempt counters reset",
|
||
scanQueued: "Scan queued",
|
||
scanStarted: "Scan started",
|
||
mountedTitle: "Mounted {title}{mode}",
|
||
unmountedTitle: "Unmounted {title}",
|
||
source: "Source",
|
||
runtimePath: "Runtime path",
|
||
sizeError: "Error {status}",
|
||
installed: "Installed",
|
||
lastLaunch: "Last launch",
|
||
mountReadOnly: "Mount R/O",
|
||
mountReadWrite: "Mount R/W",
|
||
copy: "Copy",
|
||
move: "Move",
|
||
unpack: "Unpack",
|
||
deleteSource: "Delete source",
|
||
storageBusyHint: "Wait for the current storage operation to finish",
|
||
uninstall: "Uninstall",
|
||
loadingSource: "Calculating source size…",
|
||
storageBusy: "A storage operation is already running",
|
||
copyDestination: "Destination folder for copying:",
|
||
moveDestination: "Destination folder for moving:",
|
||
unpackDestination: "Destination folder for unpacking. A {title} subfolder will be created:",
|
||
unpackDeleteConfirm: "Delete the source image only after unpacking completes successfully?\n\nCancel keeps the image.",
|
||
jobStarted: "{operation} {title} started as job {id}",
|
||
operationStarted: "Operation started",
|
||
uninstallConfirm: "Request uninstall of {game}?\n\nThe source image or folder will not be deleted.",
|
||
uninstallAccepted: "Uninstall request for {title} accepted",
|
||
deleteConfirm: "Permanently delete the physical source for {game}?\n\nThis cannot be undone."
|
||
},
|
||
"ru-RU": {
|
||
pageTitle: "Библиотека ShadowMount",
|
||
connecting: "Подключение к API…",
|
||
checking: "Проверка",
|
||
libraryControls: "Управление библиотекой",
|
||
searchPlaceholder: "Название, Title ID, Content ID или путь",
|
||
libraryFilter: "Фильтр библиотеки",
|
||
allGames: "Все игры",
|
||
folders: "Папки",
|
||
images: "Образы",
|
||
language: "Язык",
|
||
calculateSize: "Подсчитать размер",
|
||
refresh: "Обновить",
|
||
scan: "Сканировать",
|
||
librarySummary: "Сводка библиотеки",
|
||
totalGames: "Всего игр",
|
||
mounted: "Смонтировано",
|
||
library: "Библиотека",
|
||
loadingLibrary: "Загрузка библиотеки…",
|
||
storage: "Хранилища",
|
||
storageDescription: "Свободное место в доступных точках монтирования",
|
||
loading: "Загрузка…",
|
||
dataOperations: "Операции с данными",
|
||
gettingOperationStatus: "Получение статуса операции…",
|
||
operationProgress: "Прогресс операции",
|
||
size: "Размер",
|
||
files: "Файлы",
|
||
speed: "Скорость",
|
||
perSecond: "/с",
|
||
time: "Время",
|
||
cancel: "Отменить",
|
||
manualDescription: "Прямые пути к отдельным папкам игр или образам",
|
||
add: "Добавить",
|
||
settings: "Настройки",
|
||
debugLog: "Журнал отладки",
|
||
quietMode: "Тихий режим",
|
||
updateFakelibEmulators: "Обновлять эмуляторы в fakelib игр",
|
||
autoUpdateAmpr: "Обновлять эмулятор AMPR с GitHub",
|
||
autoRemoveMissingGames: "Удалять иконки недоступных приложений",
|
||
autoRemoveDelay: "Задержка удаления (секунды)",
|
||
allowLanAccess: "Разрешить доступ из локальной сети",
|
||
fanTemperature: "Целевая температура",
|
||
systemControlled: "Управление системой",
|
||
saveSettings: "Сохранить настройки",
|
||
settingsSaved: "Настройки сохранены; обновление конфигурации поставлено в очередь",
|
||
viewLog: "Просмотреть журнал",
|
||
viewKernelLog: "Просмотреть журнал ядра",
|
||
debugLogTitle: "Журнал отладки",
|
||
kernelLogTitle: "Журнал ядра",
|
||
logBytes: "Показано {shown} из {total}",
|
||
scanPaths: "Точки сканирования",
|
||
scanPathsDescription: "Пользовательские корни; при пустом списке используются встроенные пути",
|
||
scanPathAdded: "Точка добавлена локально; сохраните настройки для применения",
|
||
scanPathRemoved: "Точка удалена локально; сохраните настройки для применения",
|
||
invalidAbsolutePath: "Введите абсолютный путь, отличный от корня",
|
||
scanOptions: "Параметры сканирования",
|
||
resetAttempts: "Сбросить счётчики попыток",
|
||
resetAttemptsDescription: "Параметр применяется только к следующему принудительному сканированию.",
|
||
recentOperations: "Последние операции",
|
||
noOperations: "Операций пока нет",
|
||
gameInformation: "Информация об игре",
|
||
close: "Закрыть",
|
||
coverAlt: "Обложка {title}",
|
||
unknown: "неизвестно",
|
||
folder: "папка",
|
||
image: "образ",
|
||
sourceUnavailable: "Источник недоступен",
|
||
details: "Подробнее",
|
||
connect: "Подключить",
|
||
disconnect: "Отключить",
|
||
nothingFound: "Ничего не найдено",
|
||
libraryEmpty: "Библиотека пуста",
|
||
shown: "Показано: {count}",
|
||
jobIdle: "Нет задачи",
|
||
jobPreparing: "Подготовка",
|
||
jobMeasuring: "Подсчёт",
|
||
jobDeleting: "Удаление",
|
||
jobFinalizing: "Завершение",
|
||
jobCompleted: "Готово",
|
||
jobFailed: "Ошибка",
|
||
jobCancelled: "Отменено",
|
||
operationCopy: "Копирование",
|
||
operationMove: "Перемещение",
|
||
operationDelete: "Удаление",
|
||
operationUnpack: "Распаковка",
|
||
noActiveOperations: "Активных операций нет",
|
||
calculating: "Подсчёт…",
|
||
cancelling: "Отмена…",
|
||
operationCompleted: "{operation} {title} завершено",
|
||
operationCompletedShort: "{operation} завершено",
|
||
operationCancelled: "{operation} {title} отменено",
|
||
operationCancelledShort: "{operation} отменено",
|
||
statusError: "ошибка {status}",
|
||
operationError: "{operation} {title}: {error}",
|
||
unavailable: "Недоступно",
|
||
cancelRequested: "Запрошена отмена задачи {id}",
|
||
storageNotFound: "Хранилища не найдены",
|
||
freeSpace: "Свободно {size}",
|
||
totalSpace: "из {size}",
|
||
storageUsed: "Заполнено {path}",
|
||
connected: "PS5 подключена",
|
||
disconnected: "Нет связи",
|
||
apiError: "Ошибка API: {error}",
|
||
listEmpty: "Список пуст",
|
||
remove: "Удалить",
|
||
manualAdded: "Источник добавлен в manual.lst",
|
||
manualExists: "Источник уже есть в manual.lst",
|
||
removeManualConfirm: "Удалить источник из manual.lst?\n\n{path}",
|
||
manualRemoved: "Источник удалён из manual.lst",
|
||
scanQueuedReset: "Сканирование поставлено в очередь со сбросом попыток",
|
||
scanQueued: "Сканирование поставлено в очередь",
|
||
scanStarted: "Сканирование запущено",
|
||
mountedTitle: "Смонтировано {title}{mode}",
|
||
unmountedTitle: "Размонтировано {title}",
|
||
source: "Источник",
|
||
runtimePath: "Runtime path",
|
||
sizeError: "Ошибка {status}",
|
||
installed: "Установлена",
|
||
lastLaunch: "Последний запуск",
|
||
mountReadOnly: "Подключить R/O",
|
||
mountReadWrite: "Подключить R/W",
|
||
copy: "Копировать",
|
||
move: "Переместить",
|
||
unpack: "Распаковать",
|
||
deleteSource: "Удалить источник",
|
||
storageBusyHint: "Дождитесь завершения текущей операции с хранилищем",
|
||
uninstall: "Деинсталлировать",
|
||
loadingSource: "Подсчёт размера источника…",
|
||
storageBusy: "Уже выполняется операция с хранилищем",
|
||
copyDestination: "Папка назначения для копирования:",
|
||
moveDestination: "Папка назначения для перемещения:",
|
||
unpackDestination: "Папка назначения для распаковки. В ней будет создана подпапка {title}:",
|
||
unpackDeleteConfirm: "Удалить исходный образ только после успешного завершения распаковки?\n\nОтмена сохранит образ.",
|
||
jobStarted: "{operation} {title} запущено, задача {id}",
|
||
operationStarted: "Операция запущена",
|
||
uninstallConfirm: "Запросить деинсталляцию {game}?\n\nИсходный образ или папка не удаляются.",
|
||
uninstallAccepted: "Деинсталляция {title} принята",
|
||
deleteConfirm: "Удалить физический источник игры {game}?\n\nОперацию нельзя отменить."
|
||
}
|
||
};
|
||
const translationKeys = ["pageTitle","connecting","checking","libraryControls","searchPlaceholder","libraryFilter","allGames","folders","images","language","calculateSize","refresh","scan","librarySummary","totalGames","mounted","library","loadingLibrary","storage","storageDescription","loading","dataOperations","gettingOperationStatus","operationProgress","size","files","speed","perSecond","time","cancel","manualDescription","add","settings","debugLog","quietMode","updateFakelibEmulators","autoUpdateAmpr","autoRemoveMissingGames","autoRemoveDelay","allowLanAccess","fanTemperature","systemControlled","saveSettings","settingsSaved","viewLog","viewKernelLog","debugLogTitle","kernelLogTitle","logBytes","scanPaths","scanPathsDescription","scanPathAdded","scanPathRemoved","invalidAbsolutePath","scanOptions","resetAttempts","resetAttemptsDescription","recentOperations","noOperations","gameInformation","close","coverAlt","unknown","folder","image","sourceUnavailable","details","connect","disconnect","nothingFound","libraryEmpty","shown","jobIdle","jobPreparing","jobMeasuring","jobDeleting","jobFinalizing","jobCompleted","jobFailed","jobCancelled","operationCopy","operationMove","operationDelete","operationUnpack","noActiveOperations","calculating","cancelling","operationCompleted","operationCompletedShort","operationCancelled","operationCancelledShort","statusError","operationError","unavailable","cancelRequested","storageNotFound","freeSpace","totalSpace","storageUsed","connected","disconnected","apiError","listEmpty","remove","manualAdded","manualExists","removeManualConfirm","manualRemoved","scanQueuedReset","scanQueued","scanStarted","mountedTitle","unmountedTitle","source","runtimePath","sizeError","installed","lastLaunch","mountReadOnly","mountReadWrite","copy","move","unpack","deleteSource","storageBusyHint","uninstall","loadingSource","storageBusy","copyDestination","moveDestination","unpackDestination","unpackDeleteConfirm","jobStarted","operationStarted","uninstallConfirm","uninstallAccepted","deleteConfirm"];
|
||
const translationRows = {
|
||
"ja-JP": ["シャドウマウントライブラリ","APIに接続中…","チェック中","ライブラリコントロール","名前、タイトルID、コンテンツID、またはパス","ライブラリフィルター","すべてのゲーム","フォルダー","画像","言語","サイズを計算する","リフレッシュ","スキャン","ライブラリの概要","合計ゲーム数","取り付け済み","図書館","ライブラリを読み込み中…","ストレージ","利用可能なマウントポイントの空きスペース","読み込み中…","データ操作","動作状況を取得中…","オペレーションの進捗状況","サイズ","ファイル","速度","/秒","時間","キャンセル","個々のゲームフォルダーまたは画像への直接パス","追加","設定","デバッグログ","静音モード","ゲームのfakelibエミュレータを更新する","GitHub から AMPR エミュレータを更新する","使用できないアプリのアイコンを削除する","削除遅延 (秒)","ローカルネットワークアクセスを許可する","ファンターゲット","システム制御","設定を保存する","設定が保存されました。ランタイムリロードがキューに入れられました","デバッグログを表示する","カーネルログを表示する","デバッグログ","カーネルログ","{total}中{shown}を表示中","スキャンパス","カスタム再帰ルート。空のリストは組み込みのデフォルトを使用します","スキャン パスがローカルに追加されました。適用する設定を保存する","スキャンパスがローカルに削除されました。適用する設定を保存する","ルート以外の絶対パスを入力してください","スキャンオプション","試行カウンタをリセットする","このオプションは、次回の強制スキャンにのみ適用されます。","最近の運営","まだ操作はありません","ゲーム情報","閉じる","{title}のカバー","不明","フォルダ","画像","ソースが利用できません","詳細","マウント","アンマウント","何も見つかりませんでした","図書館は空です","表示: {count}","仕事がない","準備中","測定する","削除中","ファイナライズ中","完了","失敗しました","キャンセルされました","コピーする","移動","削除中","開梱","アクティブな操作はありません","計算中…","キャンセル中…","{operation} {title} 完了","{operation} 完了","{operation} {title} キャンセルされました","{operation}はキャンセルされました","エラー {status}","{operation} {title}: {error}","利用不可","ジョブ {id} のキャンセルが要求されました","ストレージが見つかりません","無料 {size}","{size}の","{path} の使用済みスペース","PS5接続済み","接続がありません","API エラー: {error}","リストは空です","削除","ソースがmanual.lstに追加されました","ソースはすでにmanual.lstにあります","このソースをマニュアル.lst から削除しますか?\n\n{path}","ソースがマニュアル.lst から削除されました","スキャンがキューに入れられ、試行カウンタがリセットされました","スキャンがキューに入れられました","スキャンが開始されました","マウント済み{title}{mode}","アンマウント{title}","ソース","ランタイムパス","エラー {status}","インストール済み","前回の打ち上げ","マウント R/O","マウント R/W","コピー","移動","開梱","ソースの削除","現在のストレージ操作が完了するまで待ちます","アンインストール","ソースサイズを計算中…","ストレージ操作はすでに実行中です","コピー先のフォルダー:","移動先フォルダ:","解凍先のフォルダー。 {title} サブフォルダーが作成されます。","解凍が正常に完了した後でのみソースイメージを削除しますか?\n\nキャンセルすると画像が保持されます。","{operation} {title} がジョブ {id} として開始されました","運用開始","{game} のアンインストールをリクエストしますか?\n\n元の画像やフォルダーは削除されません。","{title} のアンインストール リクエストが承認されました","{game} の物理ソースを完全に削除しますか?\n\nこれを元に戻すことはできません。"],
|
||
"fr-FR": ["Bibliothèque ShadowMount","Connexion à l'API…","Vérification","Contrôles de la bibliothèque","Nom, ID de titre, ID de contenu ou chemin d'accès","Filtre de bibliothèque","Tous les jeux","Dossiers","Images","Langue","Calculer la taille","Actualiser","Numériser","Résumé de la bibliothèque","Total des jeux","Monté","Bibliothèque","Chargement de la bibliothèque…","Stockage","Espace libre aux points de montage disponibles","Chargement…","Opérations sur les données","Obtention de l'état de l'opération…","Avancement de l'opération","Taille","Fichiers","Vitesse","/s","Temps","Annuler","Chemins directs vers des dossiers ou des images de jeu individuels","Ajouter","Paramètres","Journal de débogage","Mode silencieux","Mettre à jour les émulateurs de jeu fakelib","Mettre à jour l'émulateur AMPR depuis GitHub","Supprimer les icônes d'applications indisponibles","Délai de suppression (secondes)","Autoriser l'accès au réseau local","Cible des fans","Système contrôlé","Enregistrer les paramètres","Paramètres enregistrés ; rechargement de l'exécution en file d'attente","Afficher le journal de débogage","Afficher le journal du noyau","Journal de débogage","Journal du noyau","Affichage de {shown} sur {total}","Chemins d'analyse","Racines récursives personnalisées ; une liste vide utilise les valeurs par défaut intégrées","Chemin d'analyse ajouté localement ; enregistrer les paramètres à appliquer","Chemin d'analyse supprimé localement ; enregistrer les paramètres à appliquer","Entrez un chemin absolu non racine","Options de numérisation","Réinitialiser les compteurs de tentatives","Cette option s'applique uniquement à la prochaine analyse forcée.","Opérations récentes","Aucune opération pour l'instant","Informations sur le jeu","Fermer","Couverture pour {title}","inconnu","dossier","photo","Source indisponible","Détails","Monter","Démonter","Rien trouvé","La bibliothèque est vide","Affiché : {count}","Pas de travail","Préparation","Mesurer","Suppression","Finalisation","Terminé","Échec","Annulé","Copie","Déménagement","Suppression","Déballage","Aucune opération active","Calcul…","Annulation…","{operation} {title} terminé","{operation} terminé","{operation} {title} annulé","{operation} annulé","erreur {status}","{operation} {title} : {error}","Indisponible","Annulation demandée pour l'emploi {id}","Aucun espace de stockage trouvé","Gratuit {size}","de {size}","Espace utilisé à {path}","PS5 connectée","Aucune connexion","Erreur API : {error}","La liste est vide","Supprimer","Source ajoutée à manual.lst","La source est déjà dans manual.lst","Supprimer cette source de manual.lst ?\n\n{path}","Source supprimée de manual.lst","Analyse mise en file d'attente avec réinitialisation des compteurs de tentatives","Analyse en file d'attente","Analyse lancée","Monté {title}{mode}","Non monté {title}","Source","Chemin d'exécution","Erreur {status}","Installé","Dernier lancement","Monter R/O","Monter R/W","Copier","Déplacer","Déballer","Supprimer la source","Attendez la fin de l'opération de stockage en cours","Désinstaller","Calcul de la taille de la source…","Une opération de stockage est déjà en cours","Dossier de destination pour la copie :","Dossier de destination du déplacement :","Dossier de destination pour le déballage. Un sous-dossier {title} sera créé :","Supprimer l'image source uniquement une fois le déballage terminé avec succès ?\n\nAnnuler conserve l'image.","{operation} {title} démarré en tant que travail {id}","L'opération a commencé","Demander la désinstallation de {game} ?\n\nL'image ou le dossier source ne sera pas supprimé.","Demande de désinstallation pour {title} acceptée","Supprimer définitivement la source physique de {game} ?\n\nCela ne peut pas être annulé."],
|
||
"es-ES": ["Biblioteca ShadowMount","Conectándose a la API...","comprobando","Controles de biblioteca","Nombre, ID de título, ID de contenido o ruta","Filtro de biblioteca","Todos los juegos","Carpetas","Imágenes","Idioma","Calcular tamaño","Actualizar","Escanear","Resumen de la biblioteca","Juegos totales","Montado","Biblioteca","Cargando biblioteca…","Almacenamiento","Espacio libre en los puntos de montaje disponibles","Cargando…","Operaciones de datos","Obteniendo el estado de la operación...","Progreso de la operación","Tamaño","Archivos","Velocidad","/s","tiempo","Cancelar","Rutas directas a carpetas o imágenes de juegos individuales.","Añadir","Configuración","Registro de depuración","Modo silencioso","Actualizar emuladores de juegos fakelib","Actualizar el emulador AMPR desde GitHub","Eliminar íconos de aplicaciones no disponibles","Retraso en la eliminación (segundos)","Permitir el acceso a la red local","Objetivo del fan","Sistema controlado","Guardar configuración","Configuración guardada; recarga de tiempo de ejecución en cola","Ver registro de depuración","Ver registro del kernel","Registro de depuración","Registro del núcleo","Mostrando {shown} de {total}","Rutas de escaneo","Raíces recursivas personalizadas; una lista vacía utiliza valores predeterminados integrados","Ruta de escaneo agregada localmente; guardar la configuración para aplicar","Ruta de escaneo eliminada localmente; guardar la configuración para aplicar","Ingrese una ruta absoluta no raíz","Opciones de escaneo","Restablecer contadores de intentos","Esta opción se aplica sólo al siguiente escaneo forzado.","Operaciones recientes","Aún no hay operaciones","Información del juego","Cerrar","Portada para {title}","desconocido","carpeta","imagen","Fuente no disponible","Detalles","montar","Desmontar","No se encontró nada","la biblioteca esta vacia","Mostrado: {count}","Sin trabajo","preparando","Medición","Eliminando","Finalizando","Completado","Fallido","Cancelado","Copiando","mudanza","Eliminando","Desembalaje","Sin operaciones activas","Calculando…","Cancelando…","{operation} {title} completado","{operation} completado","{operation} {title} cancelado","{operation} cancelado","error {status}","{operation} {title}: {error}","No disponible","Cancelación solicitada para trabajo {id}","No se encontró almacenamiento","Gratis {size}","de {size}","Espacio usado en {path}","PS5 conectada","Sin conexión","Error de API: {error}","La lista esta vacia","Quitar","Fuente agregada a manual.lst","La fuente ya está en manual.lst","¿Eliminar esta fuente de manual.lst?\n\n{path}","Fuente eliminada de manual.lst","Escaneo en cola con contadores de intentos reiniciados","Escaneo en cola","Escaneo iniciado","Montado {title}{mode}","Desmontado {title}","Fuente","Ruta de ejecución","Error {status}","Instalado","último lanzamiento","Montaje R/O","Monte R/W","Copiar","Mover","Desempacar","Eliminar fuente","Espere a que finalice la operación de almacenamiento actual","Desinstalar","Calculando el tamaño de la fuente…","Ya se está ejecutando una operación de almacenamiento","Carpeta de destino para copiar:","Carpeta de destino para mover:","Carpeta de destino para descomprimir. Se creará una subcarpeta {title}:","¿Eliminar la imagen de origen solo después de que el descomprimido se complete correctamente?\n\nCancelar mantiene la imagen.","{operation} {title} comenzó como trabajo {id}","Operación iniciada","¿Solicitar desinstalación de {game}?\n\nLa imagen o carpeta de origen no se eliminará.","Solicitud de desinstalación de {title} aceptada","¿Eliminar permanentemente la fuente física de {game}?\n\nEsto no se puede deshacer."],
|
||
"de-DE": ["ShadowMount-Bibliothek","Verbindung zur API herstellen…","Überprüfen","Bibliothekssteuerelemente","Name, Titel-ID, Inhalts-ID oder Pfad","Bibliotheksfilter","Alle Spiele","Ordner","Bilder","Sprache","Größe berechnen","Aktualisieren","Scannen","Zusammenfassung der Bibliothek","Gesamtzahl der Spiele","Montiert","Bibliothek","Bibliothek wird geladen…","Lagerung","Freier Speicherplatz an verfügbaren Mount-Punkten","Laden…","Datenoperationen","Betriebsstatus abrufen…","Betriebsfortschritt","Größe","Dateien","Geschwindigkeit","/s","Zeit","Abbrechen","Direkte Pfade zu einzelnen Spielordnern oder Bildern","Hinzufügen","Einstellungen","Debug-Protokoll","Leiser Modus","Aktualisieren Sie die Fakelib-Emulatoren des Spiels","Aktualisieren Sie den AMPR-Emulator von GitHub","Entfernen Sie nicht verfügbare App-Symbole","Entfernungsverzögerung (Sekunden)","Erlauben Sie den Zugriff auf das lokale Netzwerk","Fan-Ziel","Systemgesteuert","Einstellungen speichern","Einstellungen gespeichert; Laufzeit-Neuladen in der Warteschlange","Debug-Protokoll anzeigen","Kernel-Protokoll anzeigen","Debug-Protokoll","Kernel-Protokoll","Zeigt {shown} von {total}","Scanpfade","Benutzerdefinierte rekursive Wurzeln; Eine leere Liste verwendet integrierte Standardwerte","Scanpfad lokal hinzugefügt; Speichern Sie die Einstellungen, um sie anzuwenden","Scanpfad lokal entfernt; Speichern Sie die Einstellungen, um sie anzuwenden","Geben Sie einen absoluten Nicht-Root-Pfad ein","Scanoptionen","Versuchszähler zurücksetzen","Diese Option gilt nur für den nächsten erzwungenen Scan.","Letzte Operationen","Noch keine Operationen","Spielinformationen","Schließen","Abdeckung für {title}","unbekannt","Ordner","Bild","Quelle nicht verfügbar","Details","Montieren","Aushängen","Nichts gefunden","Die Bibliothek ist leer","Angezeigt: {count}","Kein Job","Vorbereiten","Messen","Löschen","Finalisierung","Abgeschlossen","Fehlgeschlagen","Abgesagt","Kopieren","Umzug","Löschen","Auspacken","Keine aktiven Operationen","Berechnen…","Stornieren…","{operation} {title} abgeschlossen","{operation} abgeschlossen","{operation} {title} abgebrochen","{operation} abgebrochen","Fehler {status}","{operation} {title}: {error}","Nicht verfügbar","Stornierung für Job {id} beantragt","Kein Speicher gefunden","Kostenlos {size}","von {size}","Benutzter Platz bei {path}","PS5 verbunden","Keine Verbindung","API-Fehler: {error}","Die Liste ist leer","Entfernen","Quelle zu „manual.lst“ hinzugefügt","Die Quelle befindet sich bereits in „manual.lst“.","Diese Quelle aus „manual.lst“ entfernen?\n\n{path}","Quelle aus „manual.lst“ entfernt","Der Scan steht in der Warteschlange und die Versuchszähler wurden zurückgesetzt","Scan in der Warteschlange","Scan gestartet","Beritten {title}{mode}","Nicht gemountet {title}","Quelle","Laufzeitpfad","Fehler {status}","Installiert","Letzter Start","Montieren Sie R/O","Montieren Sie R/W","Kopieren","Bewegen","Auspacken","Quelle löschen","Warten Sie, bis der aktuelle Speichervorgang abgeschlossen ist","Deinstallieren","Berechnung der Quellengröße…","Ein Speichervorgang läuft bereits","Zielordner zum Kopieren:","Zielordner für den Umzug:","Zielordner zum Entpacken. Es wird ein Unterordner {title} erstellt:","Das Quellbild erst löschen, nachdem das Entpacken erfolgreich abgeschlossen wurde?\n\nAbbrechen behält das Bild.","{operation} {title} gestartet als Job {id}","Der Betrieb wurde gestartet","Deinstallation von {game} anfordern?\n\nDas Quellbild oder der Quellordner wird nicht gelöscht.","Deinstallationsanfrage für {title} akzeptiert","Physische Quelle für {game} dauerhaft löschen?\n\nDies kann nicht rückgängig gemacht werden."],
|
||
"it-IT": ["Libreria ShadowMount","Connessione all'API…","Controllo","Controlli della libreria","Nome, ID titolo, ID contenuto o percorso","Filtro libreria","Tutti i giochi","Cartelle","Immagini","Lingua","Calcola la dimensione","Aggiorna","Scansione","Riepilogo della biblioteca","Giochi totali","Montato","Biblioteca","Caricamento libreria…","Stoccaggio","Spazio libero nei punti di montaggio disponibili","Caricamento…","Operazioni sui dati","Ottenere lo stato dell'operazione…","Avanzamento dell'operazione","Dimensioni","File","Velocità","/s","Tempo","Annulla","Percorsi diretti a cartelle o immagini di singoli giochi","Aggiungi","Impostazioni","Registro di debug","Modalità silenziosa","Aggiorna gli emulatori del gioco fakelib","Aggiorna l'emulatore AMPR da GitHub","Rimuovi le icone delle app non disponibili","Ritardo rimozione (secondi)","Consenti l'accesso alla rete locale","Obiettivo dei tifosi","Controllato dal sistema","Salva impostazioni","Impostazioni salvate; ricarica runtime in coda","Visualizza il registro di debug","Visualizza il registro del kernel","Registro di debug","Registro del kernel","Mostrando {shown} di {total}","Scansione percorsi","Radici ricorsive personalizzate; un elenco vuoto utilizza le impostazioni predefinite integrate","Percorso di scansione aggiunto localmente; salvare le impostazioni da applicare","Percorso di scansione rimosso localmente; salvare le impostazioni da applicare","Immettere un percorso assoluto non root","Opzioni di scansione","Azzera i contatori dei tentativi","Questa opzione si applica solo alla successiva scansione forzata.","Operazioni recenti","Nessuna operazione ancora","Informazioni sul gioco","Chiudi","Copertina per {title}","sconosciuto","cartella","immagine","Fonte non disponibile","Dettagli","Montare","Smonta","Niente trovato","La biblioteca è vuota","Mostrato: {count}","Nessun lavoro","Preparazione","Misurare","Eliminazione","Finalizzazione","Completato","Fallito","Annullato","Copiare","In movimento","Eliminazione","Disimballaggio","Nessuna operazione attiva","Calcolo…","Annullamento…","{operation} {title} completato","{operation} completato","{operation} {title} annullato","{operation} annullato","errore {status}","{operation} {title}: {error}","Non disponibile","Cancellazione richiesta per il lavoro {id}","Nessun spazio di archiviazione trovato","Gratuito {size}","di {size}","Spazio utilizzato presso {path}","PS5 connessa","Nessuna connessione","Errore API: {error}","L'elenco è vuoto","Rimuovi","Sorgente aggiunta a manual.lst","Il sorgente è già in manual.lst","Rimuovere questa fonte da manual.lst?\n\n{path}","Sorgente rimossa da manual.lst","Scansione in coda con reimpostazione dei contatori dei tentativi","Scansione in coda","La scansione è iniziata","Montato {title}{mode}","Smontato {title}","Fonte","Percorso di esecuzione","Errore {status}","Installato","Ultimo lancio","Montare R/O","Monta R/W","Copia","Muoviti","Disimballare","Elimina fonte","Attendere il completamento dell'operazione di archiviazione corrente","Disinstallare","Calcolo delle dimensioni della sorgente…","Un'operazione di archiviazione è già in esecuzione","Cartella di destinazione per la copia:","Cartella di destinazione per lo spostamento:","Cartella di destinazione per il disimballaggio. Verrà creata una sottocartella {title}:","Eliminare l'immagine sorgente solo dopo aver completato correttamente la decompressione?\n\nAnnulla mantiene l'immagine.","{operation} {title} iniziato come lavoro {id}","L'operazione è iniziata","Richiedi la disinstallazione di {game}?\n\nL'immagine o la cartella di origine non verranno eliminate.","Richiesta di disinstallazione per {title} accettata","Eliminare definitivamente la fonte fisica per {game}?\n\nQuesta operazione non può essere annullata."],
|
||
"nl-NL": ["ShadowMount-bibliotheek","Verbinding maken met API…","Controleren","Bibliotheekbediening","Naam, titel-ID, inhouds-ID of pad","Bibliotheekfilter","Alle spellen","Mappen","Afbeeldingen","Taal","Bereken de maat","Vernieuwen","Scannen","Samenvatting van de bibliotheek","Totaal spellen","Gemonteerd","Bibliotheek","Bibliotheek laden…","Opslag","Vrije ruimte op beschikbare bevestigingspunten","Laden...","Gegevensbewerkingen","Bewerkingsstatus ophalen...","Voortgang operatie","Grootte","Bestanden","Snelheid","/s","Tijd","Annuleer","Directe paden naar individuele gamemappen of afbeeldingen","Toevoegen","Instellingen","Foutopsporingslogboek","Stille modus","Update game fakelib-emulators","Update de AMPR-emulator vanuit GitHub","Verwijder niet-beschikbare app-pictogrammen","Verwijderingsvertraging (seconden)","Sta lokale netwerktoegang toe","Fan doelwit","Systeemgestuurd","Instellingen opslaan","Instellingen opgeslagen; runtime herladen in de wachtrij geplaatst","Bekijk het foutopsporingslogboek","Kernellogboek bekijken","Foutopsporingslogboek","Kernellogboek","Toont {shown} van {total}","Paden scannen","Aangepaste recursieve wortels; een lege lijst gebruikt ingebouwde standaardwaarden","Scanpad lokaal toegevoegd; instellingen opslaan om toe te passen","Scanpad lokaal verwijderd; instellingen opslaan om toe te passen","Voer een absoluut pad in dat geen hoofdmap is","Scanopties","Pogingtellers resetten","Deze optie is alleen van toepassing op de volgende geforceerde scan.","Recente operaties","Nog geen operaties","Spelinformatie","Sluiten","Hoes voor {title}","onbekend","map","afbeelding","Bron niet beschikbaar","Details","Monteren","Ontkoppelen","Niets gevonden","De bibliotheek is leeg","Getoond: {count}","Geen baan","Voorbereiden","Meten","Verwijderen","Finaliseren","Voltooid","Mislukt","Geannuleerd","Kopiëren","Bewegen","Verwijderen","Uitpakken","Geen actieve activiteiten","Berekenen…","Annuleren…","{operation} {title} voltooid","{operation} voltooid","{operation} {title} geannuleerd","{operation} geannuleerd","fout {status}","{operation} {title}: {error}","Niet beschikbaar","Annulering aangevraagd voor taak {id}","Geen opslag gevonden","Gratis {size}","van {size}","Gebruikte ruimte op {path}","PS5 aangesloten","Geen verbinding","API-fout: {error}","De lijst is leeg","Verwijderen","Bron toegevoegd aan manual.lst","De bron staat al in manual.lst","Deze bron verwijderen uit manual.lst?\n\n{path}","Bron verwijderd uit manual.lst","Scan staat in de wachtrij en de pogingentellers zijn opnieuw ingesteld","Scannen in wachtrij","Scannen gestart","Gemonteerd {title}{mode}","Niet gemonteerd {title}","Bron","Runtime-pad","Fout {status}","Geïnstalleerd","Laatste lancering","Monteer R/O","Monteer R/W","Kopieer","Verplaatsen","Uitpakken","Bron verwijderen","Wacht tot de huidige opslagbewerking is voltooid","Verwijderen","Brongrootte berekenen…","Er wordt al een opslagbewerking uitgevoerd","Doelmap voor kopiëren:","Bestemmingsmap voor verplaatsen:","Bestemmingsmap voor uitpakken. Er wordt een submap {title} aangemaakt:","De bronafbeelding pas verwijderen nadat het uitpakken met succes is voltooid?\n\nAnnuleren behoudt de afbeelding.","{operation} {title} gestart als taak {id}","De operatie is gestart","Verwijdering van {game} aanvragen?\n\nDe bronafbeelding of -map wordt niet verwijderd.","Verwijderingsverzoek voor {title} geaccepteerd","De fysieke bron voor {game} definitief verwijderen?\n\nDit kan niet ongedaan worden gemaakt."],
|
||
"pt-PT": ["Biblioteca ShadowMount","Ligar à API…","Verificando","Controles de biblioteca","Nome, ID do título, ID do conteúdo ou caminho","Filtro de biblioteca","Todos os jogos","Pastas","Imagens","Idioma","Calcular tamanho","Atualizar","Digitalizar","Resumo da biblioteca","Total de jogos","Montado","Biblioteca","Biblioteca de carregamento…","Armazenamento","Espaço livre nos pontos de montagem disponíveis","Carregando…","Operações de dados","Obtenção de estado da operação…","Progresso da operação","Tamanho","Arquivos","Velocidade","/s","Hora","Cancelar","Caminhos diretos para pastas ou imagens individuais do jogo","Adicionar","Configurações","Registo de depuração","Modo silencioso","Atualizar os emuladores fakelib do jogo","Atualize o emulador AMPR do GitHub","Remova ícones de aplicações indisponíveis","Atraso de remoção (segundos)","Permitir o acesso à rede local","Alvo dos fãs","Controlado pelo sistema","Guardar configurações","Configurações guardadas; recarga de tempo de execução na fila","Ver registo de depuração","Ver log do kernel","Registo de depuração","Registo do kernel","A mostrar {shown} de {total}","Caminhos de verificação","Raízes recursivas personalizadas; uma lista vazia utiliza padrões internos","Caminho de digitalização adicionado localmente; guarde as definições para aplicar","Caminho de digitalização removido localmente; guarde as definições para aplicar","Introduza um caminho absoluto não raiz","Opções de digitalização","Repor contadores de tentativas","Esta opção aplica-se apenas à verificação forçada seguinte.","Operações recentes","Nenhuma operação ainda","Informações do jogo","Fechar","Capa para {title}","desconhecido","pasta","imagem","Fonte indisponível","Detalhes","Montar","Desmontar","Nada encontrado","A biblioteca está vazia","Apresentado: {count}","Sem trabalho","Preparando","Medindo","Excluindo","Finalizando","Concluído","Falha","Cancelado","Copiando","Movendo-se","Excluindo","Desempacotando","Nenhuma operação ativa","Calculando…","Cancelando…","{operation} {title} concluído","{operation} concluído","{operation} {title} cancelado","{operation} cancelado","erro {status}","{operation} {title}: {error}","Indisponível","Cancelamento solicitado para trabalho {id}","Nenhum armazenamento encontrado","Grátis {size}","de {size}","Espaço utilizado em {path}","PS5 ligado","Sem ligação","Erro API: {error}","A lista está vazia","Remover","Fonte adicionada a manual.lst","A fonte já está em manual.lst","Remover esta fonte de manual.lst?\n\n{path}","Fonte removida de manual.lst","Varrimento na fila com contadores de tentativas reiniciados","Verificação na fila","Verificação iniciada","Montado {title}{mode}","Desmontado {title}","Fonte","Caminho do tempo de execução","Erro {status}","Instalado","Último lançamento","Montar R/O","Montagem R/W","Copiar","Mover","Descompacte","Apagar fonte","Aguarde a conclusão da operação de armazenamento atual","Desinstalar","Calculando o tamanho da letra…","Uma operação de armazenamento já está em execução","Pasta de destino para cópia:","Pasta de destino para movimentação:","Pasta de destino para descompressão. Será criada uma subpasta {title}:","Apagar a imagem de origem apenas após a descompressão ser concluída com êxito?\n\nCancelar mantém a imagem.","{operation} {title} iniciado como trabalho {id}","Operação iniciada","Solicitar desinstalação de {game}?\n\nA imagem ou pasta de origem não será eliminada.","Pedido de desinstalação para {title} aceite","Apagar permanentemente a fonte física de {game}?\n\nIsto não pode ser desfeito."],
|
||
"ko-KR": ["ShadowMount 라이브러리","API에 연결하는 중…","확인 중","라이브러리 컨트롤","이름, 제목 ID, 콘텐츠 ID 또는 경로","라이브러리 필터","모든 게임","폴더","이미지","언어","크기 계산","새로고침","스캔","도서관 요약","전체 게임","탑재","도서관","라이브러리 로드 중…","저장","사용 가능한 마운트 지점의 여유 공간","로드 중…","데이터 작업","작업 상태를 가져오는 중…","운영진행","크기","파일","속도","/초","시간","취소","개별 게임 폴더 또는 이미지에 대한 직접 경로","추가","설정","디버그 로그","정숙 모드","게임 fakelib 에뮬레이터 업데이트","GitHub에서 AMPR 에뮬레이터 업데이트","사용할 수 없는 앱 아이콘 제거","제거 지연(초)","로컬 네트워크 액세스 허용","팬 타겟","시스템 제어","설정 저장","설정이 저장되었습니다. 런타임 재로드 대기 중","디버그 로그 보기","커널 로그 보기","디버그 로그","커널 로그","{total} 중 {shown} 표시 중","스캔 경로","사용자 정의 재귀 루트 빈 목록은 내장된 기본값을 사용합니다.","스캔 경로가 로컬로 추가되었습니다. 적용할 설정 저장","스캔 경로가 로컬에서 제거되었습니다. 적용할 설정 저장","루트가 아닌 절대 경로를 입력하세요.","스캔 옵션","시도 카운터 재설정","이 옵션은 다음 강제 검사에만 적용됩니다.","최근 작업","아직 작업이 없습니다.","게임정보","닫기","{title} 표지","알 수 없음","폴더","이미지","소스를 사용할 수 없음","세부정보","마운트","마운트 해제","아무것도 발견되지 않았습니다","도서관이 비어 있습니다","표시됨: {count}","직업 없음","준비 중","측정","삭제 중","마무리 중","완료됨","실패","취소됨","복사 중","이사","삭제 중","포장 풀기","활성 작업 없음","계산 중…","취소 중…","{operation} {title} 완료됨","{operation} 완료됨","{operation} {title} 취소됨","{operation} 취소됨","오류 {status}","{operation} {title}: {error}","이용 불가","작업 {id}에 대한 취소가 요청되었습니다.","저장소를 찾을 수 없습니다.","무료 {size}","{size}의","{path}에서 사용된 공간","PS5 연결됨","연결되지 않음","API 오류: {error}","목록이 비어 있습니다.","제거","manual.lst에 소스가 추가되었습니다.","소스가 이미 manual.lst에 있습니다.","manual.lst에서 이 소스를 제거하시겠습니까?\n\n{path}","manual.lst에서 소스가 제거되었습니다.","시도 카운터 재설정으로 대기 중인 스캔","스캔 대기 중","스캔이 시작되었습니다","마운트됨 {title}{mode}","마운트 해제됨 {title}","소스","런타임 경로","오류 {status}","설치됨","마지막 출시","마운트 R/O","마운트 R/W","복사","이동","포장 풀기","소스 삭제","현재 저장 작업이 완료될 때까지 기다립니다.","제거","소스 크기 계산 중…","저장 작업이 이미 실행 중입니다.","복사할 대상 폴더:","이동할 대상 폴더:","압축을 풀 대상 폴더입니다. {title} 하위 폴더가 생성됩니다.","압축 풀기가 성공적으로 완료된 후에만 소스 이미지를 삭제하시겠습니까?\n\n취소하면 이미지가 유지됩니다.","{operation} {title} 작업 {id}로 시작됨","작업이 시작되었습니다","{game} 제거를 요청하시겠습니까?\n\n원본 이미지나 폴더는 삭제되지 않습니다.","{title} 제거 요청이 승인되었습니다.","{game}의 물리적 소스를 영구적으로 삭제하시겠습니까?\n\n이 작업은 취소할 수 없습니다."],
|
||
"zh-TW": ["ShadowMount 庫","正在連接 API...","檢查","庫控件","名稱、標題 ID、內容 ID 或路徑","庫過濾器","所有遊戲","資料夾","圖片","語言","計算尺寸","重新整理","掃描","庫摘要","總比賽數","已安裝","圖書館","正在加載庫...","儲存","可用安裝點的可用空間","加載中...","數據操作","正在獲取運行狀態...","操作進度","尺寸","文件","速度","/秒","時間","取消","單一遊戲資料夾或圖像的直接路徑","添加","設定","偵錯日誌","靜音模式","更新遊戲 fakelib 模擬器","從 GitHub 更新 AMPR 模擬器","刪除不可用的應用程式圖標","移除延遲(秒)","允許本地網路存取","粉絲目標","系統控制","儲存設定","設定已儲存;運行時重新載入排隊","查看調試日誌","查看核心日誌","偵錯日誌","內核日誌","顯示 {shown} 的 {total}","掃描路徑","自訂遞歸根;空列表使用內建預設值","本機新增的掃描路徑;儲存要套用的設定","本機刪除掃描路徑;儲存要套用的設定","輸入非根絕對路徑","掃描選項","重置嘗試計數器","此選項僅適用於下一次強制掃描。","最近的操作","還沒有任何操作","遊戲資訊","關閉","{title} 的封面","未知","資料夾","影像","來源不可用","詳情","坐騎","解除安裝","沒有找到任何內容","圖書館空無一人","顯示:{count}","沒有工作","準備中","測量","正在刪除","敲定","已完成","失敗","取消","複印","搬家","正在刪除","開箱","沒有活躍的操作","正在計算...","取消…","{operation} {title} 已完成","{operation}已完成","{operation} {title} 已取消","{operation} 已取消","錯誤{status}","{operation} {title}:{error}","不可用","請求取消作業 {id}","未找到儲存空間","免費{size}","{size}的","{path} 處的已用空間","PS5已連接","無連接","API錯誤:{error}","列表為空","刪除","來源已新增至manual.lst","來源已經在manual.lst中","從 Manual.lst 中刪除此來源嗎?\n\n{path}","從 Manual.lst 中刪除來源","掃描排隊並重置嘗試計數器","掃描已排隊","掃描開始","已安裝 {title}{mode}","未安裝{title}","來源","運行時路徑","錯誤{status}","已安裝","上次發射","安裝R/O","安裝讀/寫","複製","移動","打開包裝","刪除來源","等待目前儲存操作完成","解除安裝","正在計算來源大小...","存儲操作已在運行","複製的目標資料夾:","移動的目標資料夾:","解壓縮的目標資料夾。將會建立 {title} 子資料夾:","解壓縮成功後才刪除來源鏡像嗎?\n\n取消保留映像。","{operation} {title} 作為作業 {id} 開始","開始營運","請求卸載 {game}?\n\n來源影像或資料夾不會被刪除。","已接受 {title} 的卸載請求","永久刪除 {game} 的實體來源?\n\n此操作無法撤銷。"],
|
||
"zh-CN": ["ShadowMount 库","正在连接 API...","检查","库控件","名称、标题 ID、内容 ID 或路径","库过滤器","所有游戏","文件夹","图片","语言","计算尺寸","刷新","扫描","库摘要","总比赛数","已安装","图书馆","正在加载库...","存储","可用安装点的可用空间","加载中...","数据操作","正在获取运行状态...","操作进度","尺寸","文件","速度","/秒","时间","取消","单个游戏文件夹或图像的直接路径","添加","设置","调试日志","静音模式","更新游戏 fakelib 模拟器","从 GitHub 更新 AMPR 模拟器","删除不可用的应用程序图标","移除延迟(秒)","允许本地网络访问","粉丝目标","系统控制","保存设置","设置已保存;运行时重新加载排队","查看调试日志","查看内核日志","调试日志","内核日志","显示 {shown} 的 {total}","扫描路径","自定义递归根;空列表使用内置默认值","本地添加的扫描路径;保存要应用的设置","本地删除扫描路径;保存要应用的设置","输入非根绝对路径","扫描选项","重置尝试计数器","该选项仅适用于下一次强制扫描。","最近的操作","还没有任何操作","游戏资讯","关闭","{title} 的封面","未知","文件夹","图像","来源不可用","详情","坐骑","卸载","没有找到任何内容","图书馆空无一人","显示:{count}","没有工作","准备中","测量","正在删除","敲定","已完成","失败","取消","复印","搬家","正在删除","开箱","没有活跃的操作","正在计算...","取消…","{operation} {title} 已完成","{operation}已完成","{operation} {title} 已取消","{operation} 已取消","错误{status}","{operation} {title}:{error}","不可用","请求取消作业 {id}","未找到存储空间","免费{size}","{size}的","{path} 处的已用空间","PS5已连接","无连接","API错误:{error}","列表为空","删除","源已添加到manual.lst","源已经在manual.lst中","从 Manual.lst 中删除此源吗?\n\n{path}","从 Manual.lst 中删除源","扫描排队并重置尝试计数器","扫描已排队","扫描开始","已安装 {title}{mode}","未安装{title}","来源","运行时路径","错误{status}","已安装","上次发射","安装R/O","安装读/写","复制","移动","打开包装","删除源","等待当前存储操作完成","卸载","正在计算源大小...","存储操作已在运行","复制的目标文件夹:","移动的目标文件夹:","解压的目标文件夹。将创建 {title} 子文件夹:","解压成功后才删除源镜像吗?\n\n取消保留图像。","{operation} {title} 作为作业 {id} 开始","开始运营","请求卸载 {game}?\n\n源图像或文件夹不会被删除。","已接受 {title} 的卸载请求","永久删除 {game} 的物理源?\n\n此操作无法撤消。"],
|
||
"fi-FI": ["ShadowMount kirjasto","Yhdistetään API:hen…","Tarkistetaan","Kirjaston säätimet","Nimi, otsikon tunnus, sisällön tunnus tai polku","Kirjaston suodatin","Kaikki pelit","Kansiot","Kuvat","Kieli","Laske koko","Päivitä","Skannaa","Kirjaston yhteenveto","Pelit yhteensä","Asennettu","Kirjasto","Ladataan kirjastoa…","Varastointi","Vapaata tilaa käytettävissä olevissa kiinnityspisteissä","Ladataan…","Datatoiminnot","Haetaan toimintatilaa…","Toiminnan edistyminen","Koko","Tiedostot","Nopeus","/s","Aika","Peruuta","Suorat reitit yksittäisiin pelikansioihin tai kuviin","Lisää","Asetukset","Virheenkorjausloki","Hiljainen tila","Päivitä pelin fakelib-emulaattorit","Päivitä AMPR-emulaattori GitHubista","Poista käytettävissä olevat sovelluskuvakkeet","Poistoviive (sekuntia)","Salli pääsy paikalliseen verkkoon","Fanikohde","Järjestelmä ohjattu","Tallenna asetukset","Asetukset tallennettu; ajonaikainen uudelleenlataus jonossa","Näytä virheenkorjausloki","Näytä ytimen loki","Virheenkorjausloki","Ytimen loki","Näytetään {shown} / {total}","Skannaa polut","Mukautetut rekursiiviset juuret; tyhjä luettelo käyttää sisäänrakennettuja oletusasetuksia","Skannauspolku lisätty paikallisesti; tallenna asetukset käytettäväksi","Skannauspolku poistettu paikallisesti; tallenna asetukset käytettäväksi","Anna absoluuttinen polku, joka ei ole juuri","Skannausasetukset","Nollaa yrityslaskurit","Tämä vaihtoehto koskee vain seuraavaa pakotettua tarkistusta.","Viimeaikaiset operaatiot","Ei operaatioita vielä","Pelin tiedot","Sulje","Kansi: {title}","tuntematon","kansio","kuva","Lähde ei ole käytettävissä","Yksityiskohdat","Kiinnitä","Irrota","Mitään ei löytynyt","Kirjasto on tyhjä","Näytetään: {count}","Ei työtä","Valmistellaan","Mittaus","Poistetaan","Viimeistellään","Valmis","Epäonnistui","Peruutettu","Kopioidaan","Liikkuminen","Poistetaan","Purkaminen","Ei aktiivisia toimintoja","Lasketaan…","Peruutetaan…","{operation} {title} valmis","{operation} valmis","{operation} {title} peruutettu","{operation} peruttu","virhe {status}","{operation} {title}: {error}","Ei saatavilla","Työn peruutuspyyntö {id}","Tallennustilaa ei löytynyt","Ilmainen {size}","{size}","Käytetty tila paikassa {path}","PS5 kytketty","Ei yhteyttä","API-virhe: {error}","Lista on tyhjä","Poista","Lähde lisätty tiedostoon manual.lst","Lähde on jo manuaalissa.lst","Poistetaanko tämä lähde tiedostosta manual.lst?\n\n{path}","Lähde poistettu osoitteesta manual.lst","Skannaus on jonossa ja yrityslaskurit nollattu","Skannaus jonossa","Skannaus aloitettu","Asennettu {title}{mode}","Irrotettu {title}","lähde","Suoritusaikapolku","Virhe {status}","Asennettu","Viimeinen käynnistys","Kiinnitä R/O","Kiinnitä R/W","Kopioi","Siirrä","Pura pakkauksesta","Poista lähde","Odota, että nykyinen tallennustoiminto päättyy","Poista asennus","Lasketaan lähteen kokoa…","Tallennustoiminto on jo käynnissä","Kopioinnin kohdekansio:","Siirron kohdekansio:","Pakkauksen purkamisen kohdekansio. Alikansio {title} luodaan:","Poistetaanko lähdekuva vasta pakkauksen purkamisen jälkeen?\n\nPeruuta säilyttää kuvan.","{operation} {title} aloitti työnä {id}","Toiminta alkoi","Pyydä {game}:n asennuksen poistamista?\n\nLähdekuvaa tai -kansiota ei poisteta.","{title}:n asennuksen poistopyyntö hyväksytty","Poistetaanko {game} fyysinen lähde pysyvästi?\n\nTätä ei voi kumota."],
|
||
"sv-SE": ["ShadowMount bibliotek","Ansluter till API...","Kontrollerar","Bibliotekskontroller","Namn, titel-ID, innehålls-ID eller sökväg","Biblioteksfilter","Alla spel","Mappar","Bilder","Språk","Beräkna storlek","Uppdatera","Skanna","Bibliotekssammanfattning","Totalt spel","Monterad","Bibliotek","Laddar bibliotek...","Förvaring","Ledigt utrymme vid tillgängliga monteringspunkter","Laddar...","Dataoperationer","Hämtar driftstatus...","Operationens framsteg","Storlek","Filer","Hastighet","/s","Tid","Avbryt","Direkta sökvägar till enskilda spelmappar eller bilder","Lägg till","Inställningar","Felsökningslogg","Tyst läge","Uppdatera spelet fakelib emulatorer","Uppdatera AMPR-emulatorn från GitHub","Ta bort otillgängliga appikoner","Borttagningsfördröjning (sekunder)","Tillåt lokal nätverksåtkomst","Fläktmål","Systemstyrt","Spara inställningar","Inställningar sparade; runtime reload kö","Visa felsökningslogg","Visa kärnlogg","Felsökningslogg","Kärnlogg","Visar {shown} av {total}","Skanna vägar","Anpassade rekursiva rötter; en tom lista använder inbyggda standardvärden","Sökväg tillagd lokalt; spara inställningar för att tillämpa","Sökvägen togs bort lokalt; spara inställningar för att tillämpa","Ange en absolut sökväg som inte är roten","Skanna alternativ","Återställ försöksräknare","Det här alternativet gäller bara för nästa tvångssökning.","Senaste operationerna","Inga operationer än","Spelinformation","Stäng","Omslag för {title}","okänt","mapp","bild","Källa inte tillgänglig","Detaljer","Montera","Avmontera","Inget hittades","Biblioteket är tomt","Visas: {count}","Inget jobb","Förbereder","Mätning","Tar bort","Slutför","Avslutad","Misslyckades","Avbruten","Kopiering","Flyttar","Tar bort","Packar upp","Inga aktiva operationer","Beräknar...","Avbryter...","{operation} {title} slutfört","{operation} slutfört","{operation} {title} avbröts","{operation} avbröts","fel {status}","{operation} {title}: {error}","Ej tillgänglig","Uppsägning begärd för jobbet {id}","Ingen lagring hittades","Gratis {size}","av {size}","Använd utrymme på {path}","PS5 ansluten","Ingen anslutning","API-fel: {error}","Listan är tom","Ta bort","Källa tillagd till manual.lst","Källan finns redan i manual.lst","Ta bort den här källan från manual.lst?\n\n{path}","Källan har tagits bort från manual.lst","Skanna i kö med försöksräknare återställda","Skanning i kö","Skanningen startade","Monterad {title}{mode}","Omonterad {title}","Källa","Runtime sökväg","Fel {status}","Installerad","Senaste lanseringen","Montera R/O","Montera R/W","Kopiera","Flytta","Packa upp","Ta bort källa","Vänta tills den aktuella lagringsåtgärden är klar","Avinstallera","Beräknar källstorlek...","En lagringsåtgärd körs redan","Målmapp för kopiering:","Destinationsmapp för flytt:","Destinationsmapp för uppackning. En {title} undermapp kommer att skapas:","Ta bort källbilden först efter att uppackningen har slutförts?\n\nAvbryt behåller bilden.","{operation} {title} började som jobb {id}","Operationen startade","Begära avinstallation av {game}?\n\nKällbilden eller mappen kommer inte att raderas.","Begäran om avinstallation för {title} accepteras","Vill du ta bort den fysiska källan för {game} permanent?\n\nDetta kan inte ångras."],
|
||
"da-DK": ["ShadowMount bibliotek","Opretter forbindelse til API...","Tjekker","Bibliotekskontrol","Navn, titel-id, indholds-id eller sti","Biblioteksfilter","Alle spil","Mapper","Billeder","Sprog","Beregn størrelse","Opdater","Scan","Biblioteksoversigt","Samlede spil","Monteret","Bibliotek","Indlæser bibliotek...","Opbevaring","Fri plads ved tilgængelige monteringspunkter","Indlæser...","Data operationer","Henter driftsstatus...","Driftsforløb","Størrelse","Filer","Hastighed","/s","Tid","Annuller","Direkte stier til individuelle spilmapper eller billeder","Tilføj","Indstillinger","Debug log","Stille tilstand","Opdater spillet fakelib emulatorer","Opdater AMPR-emulator fra GitHub","Fjern utilgængelige appikoner","Fjernelsesforsinkelse (sekunder)","Tillad lokal netværksadgang","Fan mål","System styret","Gem indstillinger","Indstillinger gemt; runtime genindlæsning i kø","Se fejlretningslog","Se kernelog","Debug log","Kernel log","Viser {shown} af {total}","Scan stier","Brugerdefinerede rekursive rødder; en tom liste bruger indbyggede standardindstillinger","Scanningssti tilføjet lokalt; gemme indstillinger for at anvende","Scanningssti fjernet lokalt; gemme indstillinger for at anvende","Indtast en absolut sti uden rod","Scanningsmuligheder","Nulstil forsøgstællere","Denne indstilling gælder kun for den næste tvungne scanning.","Seneste operationer","Ingen operationer endnu","Spiloplysninger","Luk","Cover til {title}","ukendt","mappe","billede","Kilden er ikke tilgængelig","Detaljer","Monter","Afmonter","Intet fundet","Biblioteket er tomt","Vist: {count}","Intet job","Forbereder","Måling","Sletter","Afslutter","Afsluttet","Mislykkedes","Annulleret","Kopiering","Flytning","Sletter","Udpakning","Ingen aktive operationer","Beregner...","Annullerer...","{operation} {title} afsluttet","{operation} afsluttet","{operation} {title} annulleret","{operation} annulleret","fejl {status}","{operation} {title}: {error}","Ikke tilgængelig","Annullering anmodet om jobbet {id}","Ingen lager fundet","Gratis {size}","af {size}","Brugt plads hos {path}","PS5 tilsluttet","Ingen forbindelse","API-fejl: {error}","Listen er tom","Fjern","Kilde tilføjet til manual.lst","Kilden er allerede i manual.lst","Vil du fjerne denne kilde fra manual.lst?\n\n{path}","Kilde fjernet fra manual.lst","Scanning i kø med nulstillet forsøgstællere","Scanning i kø","Scanningen startede","Monteret {title}{mode}","Afmonteret {title}","Kilde","Runtime-sti","Fejl {status}","Installeret","Sidste lancering","Monter R/O","Monter R/W","Kopiér","Flyt","Pak ud","Slet kilde","Vent på, at den aktuelle lagringshandling afsluttes","Afinstaller","Beregner kildestørrelse...","En lagerhandling kører allerede","Destinationsmappe til kopiering:","Destinationsmappe for flytning:","Destinationsmappe til udpakning. En {title} undermappe vil blive oprettet:","Vil du først slette kildebilledet, når udpakningen er fuldført?\n\nAnnuller beholder billedet.","{operation} {title} startede som job {id}","Operation startede","Anmode om afinstallation af {game}?\n\nKildebilledet eller mappen slettes ikke.","Anmodning om afinstallation af {title} accepteret","Vil du permanent slette den fysiske kilde for {game}?\n\nDette kan ikke fortrydes."],
|
||
"no-NO": ["ShadowMount bibliotek","Kobler til API …","Sjekker","Bibliotek kontroller","Navn, tittel-ID, innholds-ID eller bane","Bibliotekfilter","Alle spill","Mapper","Bilder","Språk","Beregn størrelse","Oppdater","Skann","Biblioteksammendrag","Totalt spill","Montert","Bibliotek","Laster inn biblioteket …","Oppbevaring","Ledig plass på tilgjengelige monteringspunkter","Laster inn …","Dataoperasjoner","Henter driftsstatus …","Driftsfremdrift","Størrelse","Filer","Hastighet","/s","Tid","Avbryt","Direkte stier til individuelle spillmapper eller bilder","Legg til","Innstillinger","Feilsøkingslogg","Stille modus","Oppdater spillet fakelib-emulatorer","Oppdater AMPR-emulator fra GitHub","Fjern utilgjengelige appikoner","Fjerningsforsinkelse (sekunder)","Tillat lokal nettverkstilgang","Viftemål","System kontrollert","Lagre innstillinger","Innstillinger lagret; runtime reload kø","Se feilsøkingsloggen","Se kjernelogg","Feilsøkingslogg","Kjernelogg","Viser {shown} av {total}","Skann stier","Tilpassede rekursive røtter; en tom liste bruker innebygde standardinnstillinger","Skannbane lagt til lokalt; lagre innstillinger for å bruke","Skannebane fjernet lokalt; lagre innstillinger for å bruke","Angi en absolutt bane uten rot","Skannealternativer","Tilbakestill forsøktellere","Dette alternativet gjelder bare for neste tvungen skanning.","Nylige operasjoner","Ingen operasjoner ennå","Spillinformasjon","Lukk","Cover for {title}","ukjent","mappe","bilde","Kilden er ikke tilgjengelig","Detaljer","Monter","Demonter","Ingenting funnet","Biblioteket er tomt","Vist: {count}","Ingen jobb","Forbereder","Måling","Sletter","Fullføres","Fullført","Mislyktes","Kansellert","Kopierer","Flytte","Sletter","Pakker ut","Ingen aktive operasjoner","Beregner …","Avbryter …","{operation} {title} fullført","{operation} fullført","{operation} {title} kansellert","{operation} kansellert","feil {status}","{operation} {title}: {error}","Utilgjengelig","Kansellering forespurt for jobb {id}","Fant ingen lagring","Gratis {size}","av {size}","Brukt plass på {path}","PS5 tilkoblet","Ingen forbindelse","API-feil: {error}","Listen er tom","Fjern","Kilde lagt til manual.lst","Kilden er allerede i manual.lst","Vil du fjerne denne kilden fra manual.lst?\n\n{path}","Kilde fjernet fra manual.lst","Skann i kø med tilbakestilt forsøktellere","Skann i kø","Skanningen startet","Montert {title}{mode}","Avmontert {title}","Kilde","Kjøretidsbane","Feil {status}","Installert","Siste lansering","Fest R/O","Fest R/W","Kopier","Flytt","Pakk ut","Slett kilde","Vent til den gjeldende lagringsoperasjonen er ferdig","Avinstaller","Beregner kildestørrelse …","En lagringsoperasjon kjører allerede","Destinasjonsmappe for kopiering:","Destinasjonsmappe for flytting:","Destinasjonsmappe for utpakking. En {title} undermappe vil bli opprettet:","Vil du slette kildebildet først etter at utpakkingen er fullført?\n\nAvbryt beholder bildet.","{operation} {title} startet som jobb {id}","Operasjonen startet","Be om avinstallering av {game}?\n\nKildebildet eller mappen vil ikke bli slettet.","Avinstalleringsforespørsel for {title} akseptert","Vil du slette den fysiske kilden for {game} permanent?\n\nDette kan ikke angres."],
|
||
"pl-PL": ["Biblioteka ShadowMount","Łączę z API…","Sprawdzanie","Sterowanie biblioteką","Nazwa, identyfikator tytułu, identyfikator treści lub ścieżka","Filtr biblioteczny","Wszystkie gry","Foldery","Obrazy","Język","Oblicz rozmiar","Odśwież","Skanuj","Podsumowanie biblioteki","Razem gry","Zamontowany","Biblioteka","Ładowanie biblioteki…","Przechowywanie","Wolne miejsce w dostępnych punktach montażu","Ładowanie…","Operacje na danych","Pobieram stan operacji…","Postęp operacji","Rozmiar","Pliki","Prędkość","/s","Czas","Anuluj","Bezpośrednie ścieżki do poszczególnych folderów gier lub obrazów","Dodaj","Ustawienia","Dziennik debugowania","Tryb cichy","Zaktualizuj emulatory gry Fakelib","Zaktualizuj emulator AMPR z GitHub","Usuń niedostępne ikony aplikacji","Opóźnienie usunięcia (sekundy)","Zezwól na dostęp do sieci lokalnej","Cel fanów","Sterowanie systemem","Zapisz ustawienia","Ustawienia zapisane; przeładowanie środowiska uruchomieniowego w kolejce","Wyświetl dziennik debugowania","Wyświetl dziennik jądra","Dziennik debugowania","Dziennik jądra","Wyświetlanie {shown} z {total}","Skanuj ścieżki","Niestandardowe korzenie rekurencyjne; pusta lista używa wbudowanych wartości domyślnych","Ścieżka skanowania dodana lokalnie; zapisz ustawienia, aby je zastosować","Ścieżka skanowania została usunięta lokalnie; zapisz ustawienia, aby je zastosować","Wprowadź ścieżkę bezwzględną inną niż główna","Opcje skanowania","Zresetuj liczniki prób","Ta opcja dotyczy tylko następnego wymuszonego skanowania.","Ostatnie operacje","Nie ma jeszcze żadnych operacji","Informacje o grze","Zamknij","Osłona na {title}","nieznany","folderze","obraz","Źródło niedostępne","Szczegóły","Góra","Odmontuj","Nic nie znaleziono","Biblioteka jest pusta","Pokazano: {count}","Brak pracy","Przygotowanie","Pomiar","Usuwanie","Finalizowanie","Ukończono","Nie udało się","Anulowano","Kopiowanie","Poruszanie się","Usuwanie","Rozpakowywanie","Brak aktywnych operacji","Obliczanie…","Anuluję…","{operation} {title} ukończono","{operation} ukończono","{operation} {title} anulowane","{operation} anulowane","błąd {status}","{operation} {title}: {error}","Niedostępne","Zażądano anulowania zadania {id}","Nie znaleziono miejsca na dane","Bezpłatny {size}","z {size}","Używana przestrzeń w {path}","Podłączono PS5","Brak połączenia","Błąd API: {error}","Lista jest pusta","Usuń","Źródło dodane do manual.lst","Źródło znajduje się już w manual.lst","Usunąć to źródło z manual.lst?\n\n{path}","Źródło usunięte z manual.lst","Skanowanie w kolejce ze zresetowanymi licznikami prób","Skanowanie w kolejce","Skanowanie rozpoczęte","Zamontowany {title}{mode}","Odmontowany {title}","Źródło","Ścieżka środowiska wykonawczego","Błąd {status}","Zainstalowany","Ostatnie uruchomienie","Zamontuj R/O","Zamontuj R/W","Kopiuj","Rusz się","Rozpakuj","Usuń źródło","Poczekaj na zakończenie bieżącej operacji przechowywania","Odinstaluj","Obliczanie rozmiaru źródła…","Operacja przechowywania jest już uruchomiona","Folder docelowy do kopiowania:","Folder docelowy do przeniesienia:","Folder docelowy do rozpakowania. Zostanie utworzony podfolder {title}:","Usunąć obraz źródłowy dopiero po pomyślnym zakończeniu rozpakowywania?\n\nAnuluj zachowuje obraz.","{operation} {title} rozpoczęło się jako zadanie {id}","Rozpoczęto operację","Poprosić o odinstalowanie {game}?\n\nObraz źródłowy lub folder nie zostaną usunięte.","Żądanie odinstalowania dla {title} zostało zaakceptowane","Trwale usunąć fizyczne źródło {game}?\n\nTego nie można cofnąć."],
|
||
"pt-BR": ["Biblioteca ShadowMount","Conectando à API…","Verificando","Controles de biblioteca","Nome, ID do título, ID do conteúdo ou caminho","Filtro de biblioteca","Todos os jogos","Pastas","Imagens","Idioma","Calcular tamanho","Atualizar","Digitalizar","Resumo da biblioteca","Total de jogos","Montado","Biblioteca","Carregando biblioteca…","Armazenamento","Espaço livre nos pontos de montagem disponíveis","Carregando…","Operações de dados","Obtendo status da operação…","Progresso da operação","Tamanho","Arquivos","Velocidade","/s","Hora","Cancelar","Caminhos diretos para pastas ou imagens individuais do jogo","Adicionar","Configurações","Registro de depuração","Modo silencioso","Atualizar emuladores fakelib do jogo","Atualize o emulador AMPR do GitHub","Remova ícones de aplicativos indisponíveis","Atraso de remoção (segundos)","Permitir acesso à rede local","Alvo dos fãs","Controlado pelo sistema","Salvar configurações","Configurações salvas; recarga de tempo de execução na fila","Ver log de depuração","Ver log do kernel","Registro de depuração","Registro do kernel","Mostrando {shown} de {total}","Caminhos de verificação","Raízes recursivas personalizadas; uma lista vazia usa padrões internos","Caminho de digitalização adicionado localmente; salve as configurações para aplicar","Caminho de digitalização removido localmente; salve as configurações para aplicar","Insira um caminho absoluto não raiz","Opções de digitalização","Redefinir contadores de tentativas","Esta opção aplica-se apenas à próxima verificação forçada.","Operações recentes","Nenhuma operação ainda","Informações do jogo","Fechar","Capa para {title}","desconhecido","pasta","imagem","Fonte indisponível","Detalhes","Montar","Desmontar","Nada encontrado","A biblioteca está vazia","Mostrado: {count}","Sem trabalho","Preparando","Medindo","Excluindo","Finalizando","Concluído","Falha","Cancelado","Copiando","Movendo-se","Excluindo","Desempacotando","Nenhuma operação ativa","Calculando…","Cancelando…","{operation} {title} concluído","{operation} concluído","{operation} {title} cancelado","{operation} cancelado","erro {status}","{operation} {title}: {error}","Indisponível","Cancelamento solicitado para trabalho {id}","Nenhum armazenamento encontrado","Grátis {size}","de {size}","Espaço usado em {path}","PS5 conectado","Sem conexão","Erro de API: {error}","A lista está vazia","Remover","Fonte adicionada a manual.lst","A fonte já está em manual.lst","Remover esta fonte de manual.lst?\n\n{path}","Fonte removida de manual.lst","Varredura na fila com contadores de tentativas redefinidos","Verificação na fila","Verificação iniciada","Montado {title}{mode}","Desmontado {title}","Fonte","Caminho do tempo de execução","Erro {status}","Instalado","Último lançamento","Montar R/O","Montagem R/W","Copiar","Mover","Descompacte","Excluir fonte","Aguarde a conclusão da operação de armazenamento atual","Desinstalar","Calculando o tamanho da fonte…","Uma operação de armazenamento já está em execução","Pasta de destino para cópia:","Pasta de destino para movimentação:","Pasta de destino para descompactação. Uma subpasta {title} será criada:","Excluir a imagem de origem somente após a descompactação ser concluída com êxito?\n\nCancelar mantém a imagem.","{operation} {title} iniciado como trabalho {id}","Operação iniciada","Solicitar desinstalação de {game}?\n\nA imagem ou pasta de origem não será excluída.","Solicitação de desinstalação para {title} aceita","Excluir permanentemente a fonte física de {game}?\n\nIsto não pode ser desfeito."],
|
||
"tr-TR": ["ShadowMount Kütüphanesi","API'ye bağlanılıyor…","Kontrol ediliyor","Kütüphane kontrolleri","Ad, Başlık Kimliği, İçerik Kimliği veya yol","Kitaplık filtresi","Tüm oyunlar","Klasörler","Görseller","Dil","Boyutu hesapla","Yenile","Tara","Kitaplık özeti","Toplam oyunlar","Monte edilmiş","Kütüphane","Kitaplık yükleniyor…","Depolama","Mevcut bağlama noktalarında boş alan","Yükleniyor…","Veri işlemleri","Operasyon durumu alınıyor…","Operasyon ilerlemesi","Boyut","Dosyalar","Hız","/s","Zaman","İptal","Bireysel oyun klasörlerine veya görsellerine doğrudan yollar","Ekle","Ayarlar","Hata ayıklama günlüğü","Sessiz mod","Oyun fakelib emülatörlerini güncelleyin","AMPR öykünücüsünü GitHub'dan güncelleyin","Kullanılamayan uygulama simgelerini kaldırın","Kaldırma gecikmesi (saniye)","Yerel ağ erişimine izin ver","Hayran hedefi","Sistem kontrollü","Ayarları kaydet","Ayarlar kaydedildi; çalışma zamanı yeniden yüklemesi sıraya alındı","Hata ayıklama günlüğünü görüntüle","Çekirdek günlüğünü görüntüle","Hata ayıklama günlüğü","Çekirdek günlüğü","Gösterilen {shown} / {total}","Yolları tara","Özel özyinelemeli kökler; boş bir liste yerleşik varsayılanları kullanır","Tarama yolu yerel olarak eklendi; uygulamak için ayarları kaydedin","Tarama yolu yerel olarak kaldırıldı; uygulamak için ayarları kaydedin","Kök olmayan bir mutlak yol girin","Tarama seçenekleri","Deneme sayaçlarını sıfırla","Bu seçenek yalnızca bir sonraki zorunlu tarama için geçerlidir.","Son operasyonlar","Henüz işlem yok","Oyun bilgileri","Kapat","{title} için kapak","bilinmiyor","klasör","görüntü","Kaynak mevcut değil","Ayrıntılar","Montaj","Bağlantıyı kaldır","Hiçbir şey bulunamadı","Kütüphane boş","Gösterilen: {count}","iş yok","Hazırlanıyor","Ölçme","Silme","Sonlandırılıyor","Tamamlandı","Başarısız","İptal edildi","Kopyalanıyor","Hareketli","Silme","Paketi açma","Aktif işlem yok","Hesaplanıyor…","İptal ediliyor…","{operation} {title} tamamlandı","{operation} tamamlandı","{operation} {title} iptal edildi","{operation} iptal edildi","hata {status}","{operation} {title}: {error}","Kullanılamıyor","{id} işi için iptal istendi","Depolama alanı bulunamadı","Ücretsiz {size}","/{size}","{path} adresinde kullanılan alan","PS5 bağlandı","Bağlantı yok","API hatası: {error}","Liste boş","Kaldır","Kaynak manual.lst'e eklendi","Kaynak zaten manual.lst'te","Bu kaynak manual.lst'ten kaldırılsın mı?\n\n{path}","Kaynak manual.lst'ten kaldırıldı","Deneme sayaçları sıfırlanarak tarama sıraya alındı","Tarama sıraya alındı","Tarama başlatıldı","Monte edilmiş {title}{mode}","Bağlantısı kesildi {title}","Kaynak","Çalışma zamanı yolu","Hata {status}","Yüklendi","Son lansman","R/O'yu monte edin","R/W montajı","Kopyala","Taşı","Paketi aç","Kaynağı sil","Mevcut depolama işleminin bitmesini bekleyin","Kaldır","Kaynak boyutu hesaplanıyor…","Bir depolama işlemi zaten çalışıyor","Kopyalama için hedef klasör:","Taşıma için hedef klasör:","Paketi açmak için hedef klasör. Bir {title} alt klasörü oluşturulacak:","Kaynak görüntü yalnızca paketi açma başarıyla tamamlandıktan sonra silinsin mi?\n\nİptal görüntüyü korur.","{operation} {title}, {id} işi olarak başladı","Operasyon başladı","{game} uygulamasının kaldırılması istensin mi?\n\nKaynak görüntü veya klasör silinmeyecektir.","{title} için kaldırma isteği kabul edildi","{game} için fiziksel kaynak kalıcı olarak silinsin mi?\n\nBu geri alınamaz."],
|
||
"ar-SA": ["مكتبة ShadowMount","جارٍ الاتصال بواجهة برمجة التطبيقات…","التحقق","ضوابط المكتبة","الاسم أو معرف العنوان أو معرف المحتوى أو المسار","مرشح المكتبة","جميع الألعاب","المجلدات","الصور","اللغة","احسب الحجم","تحديث","مسح","ملخص المكتبة","مجموع الألعاب","شنت","مكتبة","جارٍ تحميل المكتبة…","التخزين","مساحة حرة في نقاط التثبيت المتاحة","جارٍ التحميل…","عمليات البيانات","جارٍ الحصول على حالة التشغيل...","تقدم العملية","الحجم","ملفات","السرعة","/ث","الوقت","إلغاء","مسارات مباشرة إلى مجلدات أو صور اللعبة الفردية","أضف","الإعدادات","سجل التصحيح","الوضع الهادئ","تحديث محاكيات لعبة Fakelib","قم بتحديث محاكي AMPR من GitHub","إزالة أيقونات التطبيقات غير المتاحة","تأخير الإزالة (ثواني)","السماح بالوصول إلى الشبكة المحلية","هدف المروحة","يتم التحكم في النظام","حفظ الإعدادات","تم حفظ الإعدادات؛ إعادة تحميل وقت التشغيل في قائمة الانتظار","عرض سجل التصحيح","عرض سجل النواة","سجل التصحيح","سجل النواة","عرض {shown} من {total}","مسارات المسح","جذور العودية المخصصة؛ تستخدم القائمة الفارغة الإعدادات الافتراضية المضمنة","تمت إضافة مسار المسح محليًا؛ حفظ الإعدادات للتطبيق","تمت إزالة مسار المسح محليًا؛ حفظ الإعدادات للتطبيق","أدخل مسارًا مطلقًا غير جذري","خيارات المسح","إعادة ضبط عدادات المحاولات","ينطبق هذا الخيار فقط على الفحص القسري التالي.","العمليات الأخيرة","لا توجد عمليات حتى الآن","معلومات اللعبة","إغلاق","غطاء ل{title}","غير معروف","المجلد","صورة","المصدر غير متاح","التفاصيل","جبل","إلغاء التحميل","لم يتم العثور على شيء","المكتبة فارغة","يظهر: {count}","لا وظيفة","تحضير","القياس","حذف","وضع اللمسات النهائية","مكتمل","فشل","تم الإلغاء","النسخ","تتحرك","حذف","التفريغ","لا توجد عمليات نشطة","جارٍ الحساب…","جارٍ الإلغاء…","{operation} {title} اكتمل","{operation} اكتمل","{operation} {title} تم الإلغاء","{operation} تم الإلغاء","خطأ {status}","{operation} {title}: {error}","غير متاح","طلب الإلغاء للوظيفة {id}","لم يتم العثور على مساحة تخزين","مجاني {size}","من {size}","المساحة المستخدمة في {path}","تم توصيل PS5","لا يوجد اتصال","خطأ في واجهة برمجة التطبيقات: {error}","القائمة فارغة","إزالة","تمت إضافة المصدر إلى manual.lst","المصدر موجود بالفعل في manual.lst","هل تريد إزالة هذا المصدر من manual.lst؟\n\n{path}","تمت إزالة المصدر من manual.lst","تم وضع المسح الضوئي في قائمة الانتظار مع إعادة تعيين عدادات المحاولات","المسح في قائمة الانتظار","بدأ المسح","مثبتة {title}{mode}","غير مثبتة {title}","المصدر","مسار وقت التشغيل","خطأ {status}","تم التثبيت","الإطلاق الأخير","جبل R/O","جبل آر / دبليو","نسخ","تحرك","فك","حذف المصدر","انتظر حتى تنتهي عملية التخزين الحالية","إلغاء التثبيت","جارٍ حساب حجم المصدر…","عملية تخزين قيد التشغيل بالفعل","المجلد الوجهة للنسخ:","المجلد الوجهة للتحرك:","المجلد الوجهة للتفريغ. سيتم إنشاء مجلد فرعي {title}:","هل تريد حذف الصورة المصدر فقط بعد اكتمال عملية التفريغ بنجاح؟\n\nإلغاء يحافظ على الصورة.","{operation} {title} بدأ كوظيفة {id}","بدأت العملية","هل تريد إلغاء تثبيت {game}؟\n\nلن يتم حذف الصورة أو المجلد المصدر.","تم قبول طلب إلغاء تثبيت {title}","هل تريد حذف المصدر الفعلي لـ {game} نهائيًا؟\n\nلا يمكن التراجع عن هذا."],
|
||
"cs-CZ": ["Knihovna ShadowMount","Připojování k API…","Kontrola","Ovládací prvky knihovny","Název, ID titulu, ID obsahu nebo cesta","Filtr knihovny","Všechny hry","Složky","Obrázky","Jazyk","Vypočítejte velikost","Obnovit","Skenujte","Shrnutí knihovny","Celkem her","Namontováno","Knihovna","Načítání knihovny…","Skladování","Volné místo v dostupných přípojných bodech","Načítání…","Datové operace","Načítání stavu operace…","Průběh operace","Velikost","Soubory","Rychlost","/s","Čas","Zrušit","Přímé cesty k jednotlivým složkám hry nebo obrázkům","Přidat","Nastavení","Protokol ladění","Tichý režim","Aktualizujte emulátory fakelib hry","Aktualizujte emulátor AMPR z GitHubu","Odstraňte ikony nedostupných aplikací","Zpoždění odstranění (sekundy)","Povolit přístup k místní síti","Cíl fanoušků","Řízený systémem","Uložte nastavení","Nastavení uloženo; opětovné načtení za běhu ve frontě","Zobrazit protokol ladění","Zobrazit protokol jádra","Protokol ladění","Protokol jádra","Zobrazeno {shown} z {total}","Skenování cest","Vlastní rekurzivní kořeny; prázdný seznam používá vestavěné výchozí hodnoty","Cesta skenování přidána místně; uložit nastavení pro použití","Cesta skenování byla místně odstraněna; uložit nastavení pro použití","Zadejte nekořenovou absolutní cestu","Možnosti skenování","Vynulujte počítadla pokusů","Tato možnost platí pouze pro příští vynucené skenování.","Nedávné operace","Zatím žádné operace","Informace o hře","Zavřít","Obal pro {title}","neznámý","složku","obrázek","Zdroj nedostupný","Podrobnosti","Mount","Odpojit","Nic nenalezeno","Knihovna je prázdná","Zobrazeno: {count}","Žádná práce","Příprava","Měření","Mazání","Finalizace","Dokončeno","Nepodařilo se","Zrušeno","Kopírování","Stěhování","Mazání","Vybalování","Žádné aktivní operace","Počítání…","Rušení…","{operation} {title} dokončeno","{operation} dokončeno","{operation} {title} zrušeno","{operation} zrušeno","chyba {status}","{operation} {title}: {error}","Není k dispozici","Žádost o zrušení úlohy {id}","Nebylo nalezeno žádné úložiště","Zdarma {size}","z {size}","Využité místo na {path}","PS5 připojeno","Žádné spojení","Chyba API: {error}","Seznam je prázdný","Odebrat","Zdroj přidán do manual.lst","Zdroj je již v manual.lst","Odebrat tento zdroj z manual.lst?\n\n{path}","Zdroj odstraněn z manual.lst","Skenování ve frontě s vynulováním počítadel pokusů","Skenování ve frontě","Skenování začalo","Namontováno {title}{mode}","Odpojeno {title}","Zdroj","Cesta běhu","Chyba {status}","Instalováno","Poslední spuštění","Montáž R/O","Montáž R/W","Kopírovat","Pohybujte se","Rozbalit","Smazat zdroj","Počkejte na dokončení aktuální operace úložiště","Odinstalovat","Výpočet velikosti zdroje…","Operace úložiště již běží","Cílová složka pro kopírování:","Cílová složka pro přesun:","Cílová složka pro rozbalení. Bude vytvořena podsložka {title}:","Smazat zdrojový obrázek až po úspěšném rozbalení?\n\nZrušit zachová obrázek.","{operation} {title} zahájeno jako úloha {id}","Provoz zahájen","Požádat o odinstalaci {game}?\n\nZdrojový obrázek nebo složka nebudou odstraněny.","Požadavek na odinstalaci pro {title} byl přijat","Trvale odstranit fyzický zdroj pro {game}?\n\nToto nelze vrátit zpět."],
|
||
"hu-HU": ["ShadowMount könyvtár","Csatlakozás az API-hoz…","Ellenőrzés","Könyvtári vezérlők","Név, címazonosító, tartalomazonosító vagy elérési út","Könyvtári szűrő","Minden játék","Mappák","Képek","Nyelv","Számítsa ki a méretet","Frissítés","Scan","Könyvtári összefoglaló","Összes játék","Felszerelve","Könyvtár","Könyvtár betöltése…","Tárolás","Szabad hely a rendelkezésre álló rögzítési pontokon","Betöltés…","Adatműveletek","Működési állapot lekérése…","A művelet előrehaladása","Méret","Fájlok","Sebesség","/s","Idő","Mégse","Közvetlen utak az egyes játékmappákhoz vagy képekhez","Hozzáadás","Beállítások elemre","Hibakeresési napló","Csendes üzemmód","Frissítse a játék fakelib emulátorait","Frissítse az AMPR emulátort a GitHubról","Távolítsa el a nem elérhető alkalmazásikonokat","Eltávolítási késleltetés (másodperc)","Helyi hálózati hozzáférés engedélyezése","Rajongói célpont","Rendszer vezérelt","Mentse el a beállításokat","A beállítások elmentve; futásidejű újratöltési sor","Hibakeresési napló megtekintése","Kernelnapló megtekintése","Hibakeresési napló","Kernelnapló","{shown} / {total} megjelenítése","Útvonalak vizsgálata","Egyéni rekurzív gyökerek; egy üres lista beépített alapértelmezett értékeket használ","Keresési útvonal helyileg hozzáadva; mentse a beállításokat az alkalmazáshoz","A vizsgálati útvonal helyben eltávolítva; mentse a beállításokat az alkalmazáshoz","Adjon meg egy nem gyökér abszolút elérési utat","Szkennelési beállítások","Reset kísérletszámlálók","Ez az opció csak a következő kényszerített vizsgálatra vonatkozik.","Legutóbbi műveletek","Még nincs művelet","Játékinformációk","Bezárás","Borító: {title}","ismeretlen","mappát","kép","A forrás nem elérhető","Részletek","Mount","Leválasztás","Semmi sem található","A könyvtár üres","Megjelenítve: {count}","Nincs munka","Felkészülés","Mérés","Törlés","Véglegesítés","Befejezve","Sikertelen","Törölve","Másolás","Mozgás","Törlés","Kicsomagolás","Nincsenek aktív műveletek","Számítás…","Lemondás…","{operation} {title} befejeződött","{operation} befejeződött","{operation} {title} törölve","{operation} törölve","hiba {status}","{operation} {title}: {error}","Nem elérhető","Lemondást kértek a munkához {id}","Nem található tárhely","Ingyenes {size}","{size}","Használt hely a {path}","PS5 csatlakoztatva","Nincs kapcsolat","API hiba: {error}","A lista üres","Távolítsa el","A forrás hozzáadva a manual.lst-hez","A forrás már megtalálható a manual.lst fájlban","Eltávolítja ezt a forrást a manual.lst fájlból?\n\n{path}","A forrás eltávolítva a manual.lst fájlból","Keresés sorba állva, kísérletszámlálók nullázva","Beolvasás sorban","A szkennelés elindult","Szerelt {title}{mode}","Lecsatolt {title}","Forrás","Futási útvonal","Hiba {status}","Telepítve","Utolsó indítás","Szerelje fel az R/O-t","R/W rögzítés","Másolás","Mozgás","Csomagolja ki","Forrás törlése","Várja meg, amíg az aktuális tárolási művelet befejeződik","Eltávolítás","Forrás méretének kiszámítása…","Egy tárolási művelet már fut","Másolás célmappa:","Az áthelyezés célmappa:","A kicsomagolás célmappa. Létrejön egy {title} almappa:","Csak a kicsomagolás sikeres befejezése után törli a forrásképet?\n\nA Mégse funkció megtartja a képet.","{operation} {title} munkakörben indult {id}","A művelet megkezdődött","Kéri a {game} eltávolítását?\n\nA forráskép vagy mappa nem törlődik.","A {title} eltávolítási kérelme elfogadva","Véglegesen törli a {game} fizikai forrását?\n\nEzt nem lehet visszavonni."],
|
||
"el-GR": ["ShadowMount Library","Σύνδεση σε API…","Έλεγχος","Έλεγχοι βιβλιοθήκης","Όνομα, αναγνωριστικό τίτλου, αναγνωριστικό περιεχομένου ή διαδρομή","Φίλτρο βιβλιοθήκης","Όλα τα παιχνίδια","Φάκελοι","εικόνες","Γλώσσα","Υπολογίστε το μέγεθος","Ανανέωση","Σάρωση","Περίληψη βιβλιοθήκης","Σύνολο παιχνιδιών","Τοποθετημένο","Βιβλιοθήκη","Φόρτωση βιβλιοθήκης…","Αποθήκευση","Ελεύθερος χώρος στα διαθέσιμα σημεία τοποθέτησης","Φόρτωση…","Λειτουργίες δεδομένων","Λήψη κατάστασης λειτουργίας…","Πρόοδος λειτουργίας","Μέγεθος","Αρχεία","Ταχύτητα","/s","Χρόνος","Ακύρωση","Απευθείας διαδρομές σε μεμονωμένους φακέλους παιχνιδιών ή εικόνες","Προσθήκη","Ρυθμίσεις","Ημερολόγιο εντοπισμού σφαλμάτων","Αθόρυβη λειτουργία","Ενημερώστε τους εξομοιωτές του παιχνιδιού fakelib","Ενημερώστε τον εξομοιωτή AMPR από το GitHub","Καταργήστε τα μη διαθέσιμα εικονίδια εφαρμογών","Καθυστέρηση αφαίρεσης (δευτερόλεπτα)","Επιτρέψτε την πρόσβαση στο τοπικό δίκτυο","Στόχος θαυμαστής","Ελεγχόμενο σύστημα","Αποθήκευση ρυθμίσεων","Οι ρυθμίσεις αποθηκεύτηκαν. ουρά επαναφόρτωσης χρόνου εκτέλεσης","Προβολή αρχείου καταγραφής εντοπισμού σφαλμάτων","Προβολή αρχείου καταγραφής πυρήνα","Ημερολόγιο εντοπισμού σφαλμάτων","Ημερολόγιο πυρήνα","Εμφάνιση {shown} από {total}","Σάρωση μονοπατιών","Προσαρμοσμένες αναδρομικές ρίζες. μια κενή λίστα χρησιμοποιεί ενσωματωμένες προεπιλογές","Η διαδρομή σάρωσης προστέθηκε τοπικά. αποθηκεύστε τις ρυθμίσεις για εφαρμογή","Η διαδρομή σάρωσης καταργήθηκε τοπικά. αποθηκεύστε τις ρυθμίσεις για εφαρμογή","Εισαγάγετε μια απόλυτη διαδρομή χωρίς ρίζα","Επιλογές σάρωσης","Επαναφορά μετρητών προσπάθειας","Αυτή η επιλογή ισχύει μόνο για την επόμενη αναγκαστική σάρωση.","Πρόσφατες λειτουργίες","Δεν υπάρχουν ακόμη επεμβάσεις","Πληροφορίες παιχνιδιού","Κλείσιμο","Κάλυμμα για {title}","άγνωστο","φάκελο","εικόνα","Η πηγή δεν είναι διαθέσιμη","Λεπτομέρειες","Όρος","Αποπροσάρτηση","Δεν βρέθηκε τίποτα","Η βιβλιοθήκη είναι άδεια","Εμφανίζεται: {count}","Καμία δουλειά","Προετοιμασία","Μέτρηση","Διαγραφή","Οριστικοποίηση","Ολοκληρώθηκε","Απέτυχε","Ακυρώθηκε","Αντιγραφή","Μετακίνηση","Διαγραφή","Αποσυσκευασία","Δεν υπάρχουν ενεργές λειτουργίες","Υπολογισμός…","Ακύρωση…","{operation} {title} ολοκληρώθηκε","{operation} ολοκληρώθηκε","{operation} {title} ακυρώθηκε","{operation} ακυρώθηκε","σφάλμα {status}","{operation} {title}: {error}","Μη διαθέσιμο","Ζητείται ακύρωση για εργασία {id}","Δεν βρέθηκε χώρος αποθήκευσης","Δωρεάν {size}","από {size}","Χρησιμοποιημένος χώρος στο {path}","PS5 συνδεδεμένο","Καμία σύνδεση","Σφάλμα API: {error}","Η λίστα είναι άδεια","Αφαίρεση","Η πηγή προστέθηκε στο manual.lst","Η πηγή είναι ήδη στο manual.lst","Κατάργηση αυτής της πηγής από το manual.lst;\n\n{path}","Η πηγή καταργήθηκε από το manual.lst","Σάρωση σε ουρά με επαναφορά των μετρητών προσπάθειας","Η σάρωση βρίσκεται στην ουρά","Η σάρωση ξεκίνησε","Τοποθετήθηκε {title}{mode}","Αποπροσαρτήθηκε {title}","Πηγή","Διαδρομή χρόνου εκτέλεσης","Σφάλμα {status}","Εγκατεστημένο","Τελευταία εκτόξευση","Mount R/O","Βάση R/W","Αντιγραφή","Μετακίνηση","Αποσυσκευάστε","Διαγραφή πηγής","Περιμένετε να ολοκληρωθεί η τρέχουσα λειτουργία αποθήκευσης","Απεγκατάσταση","Υπολογισμός μεγέθους πηγής…","Μια λειτουργία αποθήκευσης εκτελείται ήδη","Φάκελος προορισμού για αντιγραφή:","Φάκελος προορισμού για μετακίνηση:","Φάκελος προορισμού για αποσυσκευασία. Θα δημιουργηθεί ένας υποφάκελος {title}:","Διαγραφή της εικόνας πηγής μόνο αφού ολοκληρωθεί με επιτυχία η αποσυσκευασία;\n\nΤο Cancel διατηρεί την εικόνα.","{operation} {title} ξεκίνησε ως δουλειά {id}","Η λειτουργία ξεκίνησε","Ζητήστε απεγκατάσταση του {game};\n\nΗ εικόνα προέλευσης ή ο φάκελος δεν θα διαγραφεί.","Το αίτημα απεγκατάστασης για {title} έγινε αποδεκτό","Να διαγραφεί οριστικά η φυσική πηγή για το {game};\n\nΑυτό δεν μπορεί να αναιρεθεί."],
|
||
"ro-RO": ["Biblioteca ShadowMount","Se conectează la API...","Verificarea","Controale bibliotecii","Nume, ID titlu, ID conținut sau cale","Filtru de bibliotecă","Toate jocurile","Foldere","Imagini","Limba","Calculați dimensiunea","Reîmprospătați","Scanează","Rezumatul bibliotecii","Total jocuri","Montat","Biblioteca","Se încarcă biblioteca...","Depozitare","Spațiu liber la punctele de montare disponibile","Se încarcă…","Operațiuni de date","Se obține starea de funcționare...","Progresul operațiunii","Dimensiune","Fișiere","Viteza","/s","timpul","Anulează","Căi directe către dosare sau imagini individuale ale jocului","Adăugați","Setări","Jurnal de depanare","Mod silențios","Actualizați emulatorii jocului fakelib","Actualizați emulatorul AMPR din GitHub","Eliminați pictogramele aplicațiilor indisponibile","Întârziere de eliminare (secunde)","Permite accesul la rețeaua locală","ținta fanilor","Sistem controlat","Salvați setările","Setări salvate; runtime reîncărcare în coadă","Vedeți jurnalul de depanare","Vizualizați jurnalul kernelului","Jurnal de depanare","Jurnalul kernelului","Se afișează {shown} din {total}","Scanați căi","Rădăcini recursive personalizate; o listă goală folosește valori implicite încorporate","Calea de scanare adăugată local; salvați setările pentru a le aplica","Calea de scanare a fost eliminată local; salvați setările pentru a le aplica","Introduceți o cale absolută non-rădăcină","Opțiuni de scanare","Resetați contoarele de încercări","Această opțiune se aplică numai următoarei scanări forțate.","Operațiuni recente","Nicio operațiune încă","Informații despre joc","Închide","Husă pentru {title}","necunoscut","folder","imagine","Sursa indisponibilă","Detalii","Muntele","Demontați","Nu s-a găsit nimic","Biblioteca este goală","Afișat: {count}","Nicio slujbă","Pregătirea","Măsurarea","Ștergerea","Finalizarea","Terminat","A eșuat","Anulat","Copierea","În mișcare","Ștergerea","Despachetarea","Nu există operațiuni active","Se calculează...","Se anulează…","{operation} {title} finalizat","{operation} finalizat","{operation} {title} anulat","{operation} anulat","eroare {status}","{operation} {title}: {error}","Indisponibil","Anularea solicitată pentru job {id}","Nu s-a găsit spațiu de stocare","Gratuit {size}","din {size}","Spațiu folosit la {path}","PS5 conectat","Nicio conexiune","Eroare API: {error}","Lista este goală","Eliminați","Sursa adăugată la manual.lst","Sursa este deja în manual.lst","Eliminați această sursă din manual.lst?\n\n{path}","Sursa a fost eliminată din manual.lst","Scanare pusă în coadă cu contoarele de încercări resetate","Scanare în coadă","Scanarea a început","Montat {title}{mode}","Nemontat {title}","Sursa","Calea de rulare","Eroare {status}","Instalat","Ultima lansare","Montare R/O","Montare R/W","Copiere","Mișcă-te","Despachetează","Ștergeți sursa","Așteptați ca operațiunea de stocare curentă să se termine","Dezinstalează","Se calculează dimensiunea sursei...","O operațiune de stocare este deja în curs de desfășurare","Dosarul de destinație pentru copiere:","Dosarul de destinație pentru mutare:","Dosarul de destinație pentru despachetare. Va fi creat un subdosar {title}:","Ștergeți imaginea sursă numai după ce despachetarea s-a încheiat cu succes?\n\nAnulare păstrează imaginea.","{operation} {title} a început ca job {id}","Operațiunea a început","Solicitați dezinstalarea {game}?\n\nImaginea sursă sau folderul nu vor fi șterse.","Solicitarea de dezinstalare pentru {title} a fost acceptată","Ștergeți definitiv sursa fizică pentru {game}?\n\nAcest lucru nu poate fi anulat."],
|
||
"th-TH": ["ห้องสมุด ShadowMount","กำลังเชื่อมต่อกับ API...","กำลังตรวจสอบ","การควบคุมห้องสมุด","ชื่อ รหัสชื่อเรื่อง รหัสเนื้อหา หรือเส้นทาง","ตัวกรองห้องสมุด","ทุกเกม","โฟลเดอร์","รูปภาพ","ภาษา","คำนวณขนาด","รีเฟรช","สแกน","สรุปห้องสมุด","รวมเกม","ติดแล้ว","ห้องสมุด","กำลังโหลดไลบรารี...","ที่เก็บของ","พื้นที่ว่างที่จุดยึดที่มีอยู่","กำลังโหลด...","การดำเนินงานข้อมูล","กำลังรับสถานะการดำเนินการ...","ความคืบหน้าการดำเนินงาน","ขนาด","ไฟล์","ความเร็ว","/วิ","เวลา","ยกเลิก","เส้นทางตรงไปยังโฟลเดอร์หรือรูปภาพของเกมแต่ละเกม","เพิ่ม","การตั้งค่า","บันทึกการแก้ไขข้อบกพร่อง","โหมดเงียบ","อัพเดตเกมจำลอง fakelib","อัปเดตโปรแกรมจำลอง AMPR จาก GitHub","ลบไอคอนแอปที่ไม่พร้อมใช้งาน","ความล่าช้าในการกำจัด (วินาที)","อนุญาตการเข้าถึงเครือข่ายท้องถิ่น","เป้าหมายแฟน","ระบบควบคุม","บันทึกการตั้งค่า","บันทึกการตั้งค่าแล้ว โหลดรันไทม์ซ้ำอยู่ในคิว","ดูบันทึกการแก้ไขข้อบกพร่อง","ดูบันทึกเคอร์เนล","บันทึกการแก้ไขข้อบกพร่อง","บันทึกเคอร์เนล","กำลังแสดง {shown} จาก {total}","สแกนเส้นทาง","รากแบบเรียกซ้ำแบบกำหนดเอง รายการว่างใช้ค่าเริ่มต้นในตัว","เพิ่มเส้นทางการสแกนในเครื่อง บันทึกการตั้งค่าเพื่อนำไปใช้","เส้นทางการสแกนถูกลบออกในเครื่อง บันทึกการตั้งค่าเพื่อนำไปใช้","ป้อนเส้นทางสัมบูรณ์ที่ไม่ใช่รูท","ตัวเลือกการสแกน","รีเซ็ตตัวนับความพยายาม","ตัวเลือกนี้ใช้กับการสแกนที่บังคับครั้งถัดไปเท่านั้น","การดำเนินงานล่าสุด","ยังไม่มีการดำเนินการใดๆ","ข้อมูลเกม","ปิด","ปกสำหรับ {title}","ไม่ทราบ","โฟลเดอร์","รูปภาพ","แหล่งที่มาไม่พร้อมใช้งาน","รายละเอียด","ภูเขา","ถอนติดตั้ง","ไม่พบอะไรเลย","ห้องสมุดว่างเปล่า","แสดง: {count}","ไม่มีงาน","กำลังเตรียมตัว","การวัด","กำลังลบ","กำลังสรุป","เสร็จสิ้น","ล้มเหลว","ยกเลิกแล้ว","กำลังคัดลอก","การย้าย","กำลังลบ","กำลังแกะกล่อง","ไม่มีการดำเนินการที่ใช้งานอยู่","กำลังคำนวณ...","กำลังยกเลิก...","{operation} {title} เสร็จสิ้น","{operation} เสร็จสิ้น","{operation} {title} ถูกยกเลิก","{operation} ถูกยกเลิก","ข้อผิดพลาด {status}","{operation} {title}: {error}","ไม่พร้อมใช้งาน","ขอยกเลิกงาน {id}","ไม่พบที่เก็บข้อมูล","ฟรี {size}","ของ {size}","พื้นที่ใช้งานที่ {path}","เชื่อมต่อ PS5 แล้ว","ไม่มีการเชื่อมต่อ","ข้อผิดพลาด API: {error}","รายการว่างเปล่า","ลบ","เพิ่มแหล่งที่มาใน manual.lst","แหล่งที่มามีอยู่แล้วใน manual.lst","ลบแหล่งที่มานี้ออกจาก manual.lst หรือไม่\n\n{path}","แหล่งที่มาถูกลบออกจาก manual.lst","สแกนเข้าคิวโดยรีเซ็ตตัวนับความพยายาม","สแกนเข้าคิวแล้ว","การสแกนเริ่มต้นขึ้น","ติดตั้งแล้ว {title}{mode}","ยกเลิกการต่อเชื่อม {title}","แหล่งที่มา","เส้นทางรันไทม์","ข้อผิดพลาด {status}","ติดตั้งแล้ว","เปิดตัวครั้งสุดท้าย","เมานต์ R/O","เมาท์ R/W","คัดลอก","ย้าย","แกะออก","ลบแหล่งที่มา","รอให้การดำเนินการจัดเก็บข้อมูลปัจจุบันเสร็จสิ้น","ถอนการติดตั้ง","กำลังคำนวณขนาดแหล่งที่มา...","การดำเนินการจัดเก็บข้อมูลกำลังทำงานอยู่","โฟลเดอร์ปลายทางสำหรับการคัดลอก:","โฟลเดอร์ปลายทางสำหรับการย้าย:","โฟลเดอร์ปลายทางสำหรับการแกะออก โฟลเดอร์ย่อย {title} จะถูกสร้างขึ้น:","ลบอิมเมจต้นฉบับเฉพาะหลังจากที่คลายแพ็กเสร็จเรียบร้อยแล้วใช่ไหม\n\nยกเลิก เก็บภาพไว้","{operation} {title} เริ่มต้นเป็นงาน {id}","การดำเนินการเริ่มต้นขึ้น","ขอถอนการติดตั้ง {game} หรือไม่\n\nรูปภาพหรือโฟลเดอร์ต้นฉบับจะไม่ถูกลบ","ยอมรับคำขอถอนการติดตั้งสำหรับ {title} แล้ว","ลบแหล่งข้อมูลทางกายภาพสำหรับ {game} อย่างถาวรหรือไม่\n\nสิ่งนี้ไม่สามารถยกเลิกได้"],
|
||
"vi-VN": ["Thư viện ShadowMount","Đang kết nối với API…","Kiểm tra","Kiểm soát thư viện","Tên, ID tiêu đề, ID nội dung hoặc đường dẫn","Bộ lọc thư viện","Tất cả trò chơi","Thư mục","Hình ảnh","Ngôn ngữ","Tính kích thước","Làm mới","Quét","Tóm tắt thư viện","Tổng số trò chơi","Đã gắn kết","Thư viện","Đang tải thư viện…","Lưu trữ","Không gian trống tại các điểm gắn kết có sẵn","Đang tải…","Hoạt động dữ liệu","Đang tải trạng thái hoạt động…","Tiến độ hoạt động","Kích thước","Tập tin","Tốc độ","/s","thời gian","Hủy bỏ","Đường dẫn trực tiếp đến các thư mục hoặc hình ảnh trò chơi riêng lẻ","Thêm","Cài đặt","Nhật ký gỡ lỗi","Chế độ im lặng","Cập nhật giả lập game fakelib","Cập nhật trình giả lập AMPR từ GitHub","Xóa các biểu tượng ứng dụng không có sẵn","Độ trễ xóa (giây)","Cho phép truy cập mạng cục bộ","Mục tiêu của người hâm mộ","Hệ thống điều khiển","Lưu cài đặt","Đã lưu cài đặt; xếp hàng đợi tải lại thời gian chạy","Xem nhật ký gỡ lỗi","Xem nhật ký hạt nhân","Nhật ký gỡ lỗi","Nhật ký hạt nhân","Đang hiển thị {shown} trong số {total}","Quét đường dẫn","Gốc đệ quy tùy chỉnh; một danh sách trống sử dụng các giá trị mặc định tích hợp","Đường dẫn quét được thêm cục bộ; lưu cài đặt để áp dụng","Đường dẫn quét bị xóa cục bộ; lưu cài đặt để áp dụng","Nhập đường dẫn tuyệt đối không phải gốc","Tùy chọn quét","Đặt lại bộ đếm lần thử","Tùy chọn này chỉ áp dụng cho lần quét bắt buộc tiếp theo.","Hoạt động gần đây","Chưa có hoạt động nào","Thông tin trò chơi","Đóng","Bìa cho {title}","không rõ","thư mục","hình ảnh","Nguồn không có sẵn","Chi tiết","Gắn kết","Ngắt kết nối","Không tìm thấy gì","Thư viện trống","Được hiển thị: {count}","Không có việc làm","chuẩn bị","Đo lường","Đang xóa","Đang hoàn thiện","Đã hoàn thành","thất bại","Đã hủy","Sao chép","Di chuyển","Đang xóa","Giải nén","Không có hoạt động tích cực","Đang tính…","Đang hủy…","{operation} {title} đã hoàn thành","{operation} đã hoàn thành","{operation} {title} đã hủy","{operation} đã hủy","lỗi {status}","{operation} {title}: {error}","Không có sẵn","Yêu cầu hủy công việc {id}","Không tìm thấy bộ nhớ","Miễn phí {size}","của {size}","Dung lượng đã sử dụng tại {path}","Đã kết nối PS5","Không có kết nối","Lỗi API: {error}","Danh sách trống","Xóa","Nguồn được thêm vào manual.lst","Nguồn đã có trong manual.lst","Xóa nguồn này khỏi manual.lst?\n\n{path}","Đã xóa nguồn khỏi manual.lst","Quét được xếp hàng đợi với việc đặt lại bộ đếm lần thử","Quét hàng đợi","Đã bắt đầu quét","Đã gắn {title}{mode}","Chưa được kết nối {title}","Nguồn","Đường dẫn thời gian chạy","Lỗi {status}","Đã cài đặt","Lần ra mắt gần đây nhất","Gắn R/O","Gắn R/W","Sao chép","Di chuyển","Giải nén","Xóa nguồn","Đợi thao tác lưu trữ hiện tại kết thúc","Gỡ cài đặt","Đang tính toán kích thước nguồn…","Một hoạt động lưu trữ đang chạy","Thư mục đích để sao chép:","Thư mục đích để di chuyển:","Thư mục đích để giải nén. Một thư mục con {title} sẽ được tạo:","Chỉ xóa hình ảnh nguồn sau khi giải nén thành công?\n\nHủy bỏ giữ hình ảnh.","{operation} {title} đã bắt đầu làm công việc {id}","Bắt đầu hoạt động","Yêu cầu gỡ cài đặt {game}?\n\nHình ảnh hoặc thư mục nguồn sẽ không bị xóa.","Yêu cầu gỡ cài đặt {title} đã được chấp nhận","Xóa vĩnh viễn nguồn vật lý cho {game}?\n\nĐiều này không thể hoàn tác được."],
|
||
"id-ID": ["Perpustakaan ShadowMount","Menghubungkan ke API…","Memeriksa","Kontrol perpustakaan","Nama, ID Judul, ID Konten, atau jalur","Penyaring perpustakaan","Semua permainan","Folder","Gambar","Bahasa","Hitung ukuran","Segarkan","Pindai","Ringkasan perpustakaan","Jumlah permainan","Dipasang","Perpustakaan","Memuat pustaka…","Penyimpanan","Ruang kosong di titik pemasangan yang tersedia","Memuat…","Operasi data","Mendapatkan status operasi…","Kemajuan operasi","Ukuran","File","Kecepatan","/s","Waktu","Batalkan","Jalur langsung ke folder atau gambar game individual","Tambahkan","Pengaturan","Log debug","Modus senyap","Perbarui emulator game fakelib","Perbarui emulator AMPR dari GitHub","Hapus ikon aplikasi yang tidak tersedia","Penundaan penghapusan (detik)","Izinkan akses jaringan lokal","Sasaran penggemar","Sistem dikendalikan","Simpan pengaturan","Pengaturan disimpan; pemuatan ulang runtime dalam antrean","Lihat log debug","Lihat log kernel","Log debug","Catatan inti","Menampilkan {shown} dari {total}","Pindai jalur","Akar rekursif khusus; daftar kosong menggunakan default bawaan","Jalur pemindaian ditambahkan secara lokal; simpan pengaturan untuk diterapkan","Jalur pemindaian dihapus secara lokal; simpan pengaturan untuk diterapkan","Masukkan jalur absolut non-root","Opsi pemindaian","Setel ulang penghitung upaya","Opsi ini hanya berlaku untuk pemindaian paksa berikutnya.","Operasi terkini","Belum ada operasi","Informasi permainan","Tutup","Sampul untuk {title}","tidak diketahui","map","gambar","Sumber tidak tersedia","Detail","Gunung","Lepas","Tidak ada yang ditemukan","Perpustakaannya kosong","Tampil: {count}","Tidak ada pekerjaan","Mempersiapkan","Mengukur","Menghapus","Menyelesaikan","Selesai","Gagal","Dibatalkan","Menyalin","Bergerak","Menghapus","Membongkar","Tidak ada operasi aktif","Menghitung…","Membatalkan…","{operation} {title} selesai","{operation} selesai","{operation} {title} dibatalkan","{operation} dibatalkan","kesalahan {status}","{operation} {title}: {error}","Tidak tersedia","Pembatalan diminta untuk pekerjaan {id}","Tidak ada penyimpanan yang ditemukan","Gratis {size}","dari {size}","Ruang yang digunakan di {path}","PS5 terhubung","Tidak ada koneksi","Kesalahan API: {error}","Daftarnya kosong","Hapus","Sumber ditambahkan ke manual.lst","Sumber sudah ada di manual.lst","Hapus sumber ini dari manual.lst?\n\n{path}","Sumber dihapus dari manual.lst","Pemindaian dalam antrean dengan penghitung upaya disetel ulang","Pemindaian dalam antrean","Pemindaian dimulai","Dipasang {title}{mode}","Dilepas {title}","Sumber","Jalur waktu proses","Kesalahan {status}","Dipasang","Peluncuran terakhir","Pasang R/O","Pasang R/W","Salin","Pindah","Buka kemasan","Hapus sumber","Tunggu hingga operasi penyimpanan saat ini selesai","Copot pemasangan","Menghitung ukuran sumber…","Operasi penyimpanan sudah berjalan","Folder tujuan penyalinan:","Folder tujuan untuk dipindahkan:","Folder tujuan untuk dibongkar. Subfolder {title} akan dibuat:","Hapus gambar sumber hanya setelah pembongkaran selesai dengan sukses?\n\nBatal menyimpan gambar.","{operation} {title} dimulai sebagai pekerjaan {id}","Operasi dimulai","Minta uninstall {game}?\n\nGambar atau folder sumber tidak akan dihapus.","Permintaan penghapusan instalasi untuk {title} diterima","Hapus sumber fisik untuk {game} secara permanen?\n\nHal ini tidak dapat dibatalkan."],
|
||
"uk-UA": ["Бібліотека ShadowMount","Підключення до API…","Перевірка","Елементи керування бібліотекою","Ім’я, ідентифікатор заголовка, ідентифікатор вмісту або шлях","Бібліотечний фільтр","Всі ігри","Папки","Зображення","Мова","Розрахувати розмір","Оновити","Сканувати","Анотація по бібліотеці","Всього ігор","Змонтований","Бібліотека","Завантаження бібліотеки…","Зберігання","Вільний простір у доступних точках монтування","Завантаження…","Операції з даними","Отримання статусу операції…","Хід операції","Розмір","Файли","швидкість","/с","час","Скасувати","Прямі шляхи до окремих папок гри або зображень","додати","Налаштування","Журнал налагодження","Тихий режим","Оновити ігрові емулятори fakelib","Оновіть емулятор AMPR з GitHub","Видалити недоступні значки програм","Затримка видалення (секунди)","Дозволити доступ до локальної мережі","Вентилятор ціль","Контролюється системою","Зберегти налаштування","Налаштування збережено; перезавантаження часу виконання в черзі","Переглянути журнал налагодження","Переглянути журнал ядра","Журнал налагодження","Журнал ядра","Показано {shown} з {total}","Шляхи сканування","Спеціальні рекурсивні корені; порожній список використовує вбудовані параметри за замовчуванням","Шлях сканування додано локально; зберегти налаштування для застосування","Шлях сканування видалено локально; зберегти налаштування для застосування","Введіть некореневий абсолютний шлях","Параметри сканування","Скинути лічильники спроб","Цей параметр застосовується лише до наступного примусового сканування.","Останні операції","Операцій ще немає","Інформація про гру","Закрити","Обкладинка для {title}","невідомий","папку","зображення","Джерело недоступне","Подробиці","кріплення","Відключити","Нічого не знайдено","Бібліотека порожня","Показано: {count}","Немає роботи","Готується","Вимірювання","Видалення","Доопрацювання","Виконано","Не вдалося","Скасовано","Копіювання","Переїзд","Видалення","Розпакування","Немає активних операцій","Розрахунок…","Скасування…","{operation} {title} завершено","{operation} завершено","{operation} {title} скасовано","{operation} скасовано","помилка {status}","{operation} {title}: {error}","Недоступний","Запит на скасування роботи {id}","Пам'ять не знайдено","Безкоштовно {size}","з {size}","Використаний простір на {path}","PS5 підключено","Немає зв'язку","Помилка API: {error}","Список порожній","видалити","Джерело додано до manual.lst","Джерело вже є в manual.lst","Видалити це джерело з manual.lst?\n\n{path}","Джерело видалено з manual.lst","Сканування в черзі зі скиданням лічильників спроб","Сканування в черзі","Розпочато сканування","Встановлений {title}{mode}","Немонтований {title}","Джерело","Шлях виконання","Помилка {status}","встановлено","Останній запуск","Кріплення R/O","Кріплення R/W","Копія","рухатися","Розпакуйте","Видалити джерело","Дочекайтеся завершення поточної операції зберігання","Видалити","Обчислення розміру джерела…","Операція зберігання вже виконується","Цільова папка для копіювання:","Цільова папка для переміщення:","Цільова папка для розпакування. Буде створено підпапку {title}:","Видалити вихідне зображення лише після успішного розпакування?\n\nCancel зберігає зображення.","{operation} {title} розпочато як робота {id}","Операція розпочата","Надіслати запит на видалення {game}?\n\nВихідне зображення або папку не буде видалено.","Запит на видалення {title} прийнято","Назавжди видалити фізичне джерело для {game}?\n\nЦе неможливо скасувати."]
|
||
};
|
||
for (const [locale, values] of Object.entries(translationRows)) {
|
||
if (values.length !== translationKeys.length) throw new Error(`Invalid translation catalog: ${locale}`);
|
||
translations[locale] = Object.fromEntries(translationKeys.map((key, index) => [key, values[index]]));
|
||
translations[locale].pageTitle = `ShadowMount — ${translations[locale].library}`;
|
||
}
|
||
translations["en-GB"] = translations["en-US"];
|
||
translations["es-MX"] = translations["es-ES"];
|
||
translations["fr-CA"] = translations["fr-FR"];
|
||
const byteUnits = {
|
||
"en-US": ["B", "KiB", "MiB", "GiB", "TiB"],
|
||
"ru-RU": ["Б", "КиБ", "МиБ", "ГиБ", "ТиБ"]
|
||
};
|
||
const supportedLanguages = Object.keys(translations);
|
||
const defaultLanguage = "en-US";
|
||
function resolveLanguage(language) {
|
||
const normalized = String(language || "").replace("_", "-").toLowerCase();
|
||
if (normalized === "en") return "en-US";
|
||
if (normalized === "ru") return "ru-RU";
|
||
const exact = supportedLanguages.find((locale) => locale.toLowerCase() === normalized);
|
||
if (exact) return exact;
|
||
const base = normalized.split("-")[0];
|
||
if (base === "zh") return normalized.includes("-tw") || normalized.includes("-hk") || normalized.includes("-mo") ? "zh-TW" : "zh-CN";
|
||
if (base === "pt") return normalized.includes("-br") ? "pt-BR" : "pt-PT";
|
||
return supportedLanguages.find((locale) => locale.toLowerCase().startsWith(`${base}-`)) || defaultLanguage;
|
||
}
|
||
let savedLanguage = null;
|
||
try { savedLanguage = localStorage.getItem("shadowmount.language"); } catch (_) { /* Storage may be disabled. */ }
|
||
let currentLanguage = resolveLanguage(savedLanguage || navigator.language);
|
||
|
||
function t(key, values = {}) {
|
||
const template = translations[currentLanguage][key] || translations[defaultLanguage][key] || key;
|
||
return template.replace(/\{(\w+)\}/g, (match, name) =>
|
||
Object.prototype.hasOwnProperty.call(values, name) ? String(values[name]) : match
|
||
);
|
||
}
|
||
|
||
function applyLanguage(language, persist = false) {
|
||
currentLanguage = resolveLanguage(language);
|
||
document.documentElement.lang = currentLanguage;
|
||
document.documentElement.dir = currentLanguage === "ar-SA" ? "rtl" : "ltr";
|
||
document.title = t("pageTitle");
|
||
$("language").value = currentLanguage;
|
||
document.querySelectorAll("[data-i18n]").forEach((item) => {
|
||
item.textContent = t(item.dataset.i18n);
|
||
});
|
||
document.querySelectorAll("[data-i18n-placeholder]").forEach((item) => {
|
||
item.placeholder = t(item.dataset.i18nPlaceholder);
|
||
});
|
||
document.querySelectorAll("[data-i18n-aria-label]").forEach((item) => {
|
||
item.setAttribute("aria-label", t(item.dataset.i18nAriaLabel));
|
||
});
|
||
if (state.apiVersion) {
|
||
$("version").textContent = `ShadowMount ${state.apiVersion.shadowmount_version} · API v${state.apiVersion.api_version}`;
|
||
}
|
||
if (state.connectionStatus) setConnection(state.connectionStatus, state.connectionKey);
|
||
if (!$("logModal").hidden) $("logTitle").textContent = t(state.logTitleKey);
|
||
if (persist) {
|
||
try { localStorage.setItem("shadowmount.language", currentLanguage); } catch (_) { /* Keep the in-memory selection. */ }
|
||
}
|
||
}
|
||
|
||
function node(tag, className, text) {
|
||
const item = document.createElement(tag);
|
||
if (className) item.className = className;
|
||
if (text !== undefined) item.textContent = text;
|
||
return item;
|
||
}
|
||
|
||
function formatBytes(value) {
|
||
if (!Number.isFinite(value) || value < 0) return "—";
|
||
const units = byteUnits[currentLanguage] || byteUnits[defaultLanguage];
|
||
if (value === 0) return `0 ${units[0]}`;
|
||
const index = Math.min(Math.floor(Math.log(value) / Math.log(1024)), units.length - 1);
|
||
const precision = index < 2 ? 0 : 1;
|
||
return `${(value / (1024 ** index)).toFixed(precision)} ${units[index]}`;
|
||
}
|
||
|
||
function formatDuration(value) {
|
||
const totalSeconds = Math.max(0, Math.floor((Number(value) || 0) / 1000));
|
||
const hours = Math.floor(totalSeconds / 3600);
|
||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||
const seconds = totalSeconds % 60;
|
||
return hours > 0
|
||
? `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`
|
||
: `${minutes}:${String(seconds).padStart(2, "0")}`;
|
||
}
|
||
|
||
function errorMessage(error) {
|
||
return error instanceof Error ? error.message : String(error);
|
||
}
|
||
|
||
async function api(path, payload = {}) {
|
||
const response = await fetch(`${API}${path}`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload),
|
||
cache: "no-store"
|
||
});
|
||
const type = response.headers.get("content-type") || "";
|
||
const data = type.includes("application/json") ? await response.json() : null;
|
||
if (!response.ok || !data || data.status !== 0) {
|
||
const reason = data && data.error ? data.error : `HTTP ${response.status}`;
|
||
throw new Error(reason);
|
||
}
|
||
return data;
|
||
}
|
||
|
||
function setConnection(status, textKey) {
|
||
state.connectionStatus = status;
|
||
state.connectionKey = textKey;
|
||
const item = $("connection");
|
||
item.className = `connection ${status}`;
|
||
$("connectionText").textContent = t(textKey);
|
||
}
|
||
|
||
let toastTimer = 0;
|
||
function toast(message, isError = false) {
|
||
const item = $("toast");
|
||
item.textContent = message;
|
||
item.className = isError ? "toast error" : "toast";
|
||
item.hidden = false;
|
||
clearTimeout(toastTimer);
|
||
toastTimer = setTimeout(() => { item.hidden = true; }, 4500);
|
||
}
|
||
|
||
function logActivity(message, kind = "ok") {
|
||
state.activity.unshift({ message, kind, time: new Date().toLocaleTimeString(currentLanguage, { hour: "2-digit", minute: "2-digit" }) });
|
||
state.activity = state.activity.slice(0, 8);
|
||
const list = $("activity");
|
||
list.replaceChildren();
|
||
state.activity.forEach((entry) => {
|
||
list.append(node("div", `activity-item ${entry.kind}`, `${entry.time} · ${entry.message}`));
|
||
});
|
||
}
|
||
|
||
function coverFor(game, large = false) {
|
||
const cover = node("div", "cover");
|
||
const fallback = node("div", "cover-fallback", (game.title_id || "SM").slice(0, 4));
|
||
cover.append(fallback);
|
||
if (game.icon_url) {
|
||
const image = new Image();
|
||
image.alt = large ? t("coverAlt", { title: game.title_name || game.title_id }) : "";
|
||
image.addEventListener("load", () => fallback.remove(), { once: true });
|
||
image.addEventListener("error", () => image.remove(), { once: true });
|
||
cover.append(image);
|
||
image.src = game.icon_url;
|
||
}
|
||
return cover;
|
||
}
|
||
|
||
function badge(text, extra = "") {
|
||
return node("span", `badge ${extra}`.trim(), text);
|
||
}
|
||
|
||
function gameLabel(game) {
|
||
const name = game.title_name || game.title_id;
|
||
return name === game.title_id ? game.title_id : `${name} (${game.title_id})`;
|
||
}
|
||
|
||
function gameCard(game) {
|
||
const card = node("article", "game-card");
|
||
card.append(coverFor(game));
|
||
const body = node("div", "game-main");
|
||
const heading = node("div", "game-heading");
|
||
const identity = node("div", "game-identity");
|
||
identity.append(node("h3", "game-title", game.title_name || game.title_id));
|
||
identity.append(node("div", "game-id", game.title_id));
|
||
heading.append(identity);
|
||
if (game.size_bytes !== undefined) {
|
||
heading.append(node("strong", "game-card-size", formatBytes(game.size_bytes)));
|
||
}
|
||
body.append(heading);
|
||
const badges = node("div", "badges");
|
||
badges.append(badge(game.platform || t("unknown")));
|
||
badges.append(badge(game.image_backed ? (game.image_type || t("image")) : t("folder"), game.image_backed ? "image" : ""));
|
||
if (game.mounted) badges.append(badge(t("mounted"), "live"));
|
||
body.append(badges);
|
||
const path = node("div", "game-path", game.path || t("sourceUnavailable"));
|
||
path.title = game.path || "";
|
||
body.append(path);
|
||
|
||
const actions = node("div", "game-actions");
|
||
const details = node("button", "btn small", t("details"));
|
||
details.type = "button";
|
||
details.addEventListener("click", () => showDetails(game.title_id));
|
||
actions.append(details);
|
||
if (game.mounted) {
|
||
actions.append(actionButton(t("disconnect"), () => titleRuntime(game, "unmount")));
|
||
} else if (game.image_backed) {
|
||
actions.append(actionButton(t("mountReadOnly"), () => titleRuntime(game, "mount", "ro")));
|
||
actions.append(actionButton(t("mountReadWrite"), () => titleRuntime(game, "mount", "rw")));
|
||
} else {
|
||
actions.append(actionButton(t("connect"), () => titleRuntime(game, "mount")));
|
||
}
|
||
body.append(actions);
|
||
card.append(body);
|
||
return card;
|
||
}
|
||
|
||
function filteredGames() {
|
||
const query = $("search").value.trim().toLocaleLowerCase(currentLanguage);
|
||
const filter = $("filter").value;
|
||
return state.games.filter((game) => {
|
||
const matchesFilter = filter === "all" ||
|
||
(filter === "folder" && !game.image_backed) ||
|
||
(filter === "image" && game.image_backed) ||
|
||
game.platform === filter;
|
||
if (!matchesFilter) return false;
|
||
if (!query) return true;
|
||
return [game.title_name, game.title_id, game.content_id, game.path]
|
||
.some((value) => String(value || "").toLocaleLowerCase(currentLanguage).includes(query));
|
||
});
|
||
}
|
||
|
||
function renderGames() {
|
||
const games = filteredGames();
|
||
const list = $("games");
|
||
list.replaceChildren();
|
||
if (!games.length) {
|
||
list.append(node("div", "empty", t(state.games.length ? "nothingFound" : "libraryEmpty")));
|
||
} else {
|
||
games.forEach((game) => list.append(gameCard(game)));
|
||
}
|
||
$("visibleCount").textContent = t("shown", { count: games.length });
|
||
}
|
||
|
||
function renderStats() {
|
||
const imageCount = state.games.filter((game) => game.image_backed).length;
|
||
$("totalGames").textContent = state.games.length;
|
||
$("imageGames").textContent = imageCount;
|
||
$("folderGames").textContent = state.games.length - imageCount;
|
||
$("mountedGames").textContent = state.games.filter((game) => game.mounted).length;
|
||
}
|
||
|
||
function storageJobStateLabel(job) {
|
||
const labels = {
|
||
idle: t("jobIdle"),
|
||
preparing: t("jobPreparing"),
|
||
measuring: t("jobMeasuring"),
|
||
transferring: storageOperationLabel(job.operation),
|
||
deleting: t("jobDeleting"),
|
||
finalizing: t("jobFinalizing"),
|
||
completed: t("jobCompleted"),
|
||
failed: t("jobFailed"),
|
||
cancelled: t("jobCancelled")
|
||
};
|
||
return labels[job.state] || job.state;
|
||
}
|
||
|
||
function storageOperationLabel(operation) {
|
||
if (operation === "move") return t("operationMove");
|
||
if (operation === "delete") return t("operationDelete");
|
||
if (operation === "unpack") return t("operationUnpack");
|
||
return t("operationCopy");
|
||
}
|
||
|
||
function renderStorageJob(job) {
|
||
state.storageJob = job;
|
||
const hasJob = job && Number(job.job_id) > 0;
|
||
$("transferState").textContent = hasJob ? storageJobStateLabel(job) : t("jobIdle");
|
||
$("transferState").className = `badge ${job && job.active ? "live" : ""}`.trim();
|
||
$("transferIdle").hidden = hasJob;
|
||
$("transferIdle").textContent = t("noActiveOperations");
|
||
$("transferDetails").hidden = !hasJob;
|
||
if (!hasJob) return;
|
||
|
||
const percent = Math.max(0, Math.min(100, Number(job.progress_percent) || 0));
|
||
const operation = storageOperationLabel(job.operation);
|
||
$("transferSummary").textContent = `${operation} ${job.title_id}`;
|
||
$("transferRoute").textContent = job.operation === "delete"
|
||
? job.source
|
||
: `${job.source} → ${job.destination}`;
|
||
$("transferFill").style.width = `${percent}%`;
|
||
$("transferMeter").setAttribute("aria-valuenow", String(Math.round(percent)));
|
||
$("transferBytes").textContent = job.total_bytes > 0
|
||
? `${formatBytes(Number(job.processed_bytes))} / ${formatBytes(Number(job.total_bytes))}`
|
||
: job.state === "measuring" ? t("calculating") : formatBytes(Number(job.processed_bytes));
|
||
$("transferFiles").textContent = job.total_files > 0
|
||
? `${job.processed_files} / ${job.total_files}`
|
||
: job.state === "measuring" ? t("calculating") : String(job.processed_files || 0);
|
||
$("transferSpeed").textContent = job.speed_bytes_per_second > 0
|
||
? `${formatBytes(Number(job.speed_bytes_per_second))}${t("perSecond")}`
|
||
: "—";
|
||
$("transferElapsed").textContent = formatDuration(job.elapsed_ms);
|
||
$("transferPercent").textContent = `${percent.toFixed(percent < 10 && percent > 0 ? 1 : 0)}%`;
|
||
$("cancelTransfer").disabled = !job.cancellable || job.cancel_requested;
|
||
$("cancelTransfer").textContent = job.cancel_requested ? t("cancelling") : t("cancel");
|
||
}
|
||
|
||
function reportStorageJob(job) {
|
||
const key = `${job.job_id}:${job.state}`;
|
||
if (!job.job_id || job.active || state.reportedStorageJob === key) return;
|
||
state.reportedStorageJob = key;
|
||
const operation = storageOperationLabel(job.operation);
|
||
if (job.state === "completed") {
|
||
logActivity(t("operationCompleted", { operation, title: job.title_id }));
|
||
toast(t("operationCompletedShort", { operation }));
|
||
} else if (job.state === "cancelled") {
|
||
logActivity(t("operationCancelled", { operation, title: job.title_id }));
|
||
toast(t("operationCancelledShort", { operation }));
|
||
} else if (job.state === "failed") {
|
||
const reason = job.result_error || t("statusError", { status: job.result_status });
|
||
logActivity(t("operationError", { operation, title: job.title_id, error: reason }), "error");
|
||
toast(reason, true);
|
||
}
|
||
refreshAll();
|
||
}
|
||
|
||
function scheduleStorageJobPoll() {
|
||
clearTimeout(state.storagePollTimer);
|
||
const delay = state.storageJob && state.storageJob.active ? 750 : 5000;
|
||
state.storagePollTimer = setTimeout(refreshStorageJob, delay);
|
||
}
|
||
|
||
async function refreshStorageJob() {
|
||
try {
|
||
const wasActive = Boolean(state.storageJob && state.storageJob.active);
|
||
const job = await api("/games/storage/status");
|
||
renderStorageJob(job);
|
||
if (wasActive && !job.active) reportStorageJob(job);
|
||
} catch (error) {
|
||
if (!state.storageJob) {
|
||
$("transferState").textContent = t("unavailable");
|
||
$("transferIdle").textContent = errorMessage(error);
|
||
}
|
||
} finally {
|
||
scheduleStorageJobPoll();
|
||
}
|
||
}
|
||
|
||
async function cancelStorageJob() {
|
||
const job = state.storageJob;
|
||
if (!job || !job.active || !job.cancellable) return;
|
||
$("cancelTransfer").disabled = true;
|
||
try {
|
||
renderStorageJob(await api("/games/storage/cancel", { job_id: job.job_id }));
|
||
logActivity(t("cancelRequested", { id: job.job_id }));
|
||
scheduleStorageJobPoll();
|
||
} catch (error) {
|
||
toast(errorMessage(error), true);
|
||
await refreshStorageJob();
|
||
}
|
||
}
|
||
|
||
async function refreshStorage() {
|
||
const list = $("storageList");
|
||
try {
|
||
const data = await api("/storage");
|
||
const allMounts = (data.mounts || []).slice().sort((left, right) =>
|
||
String(left.mount_point).localeCompare(String(right.mount_point))
|
||
);
|
||
const libraryMounts = allMounts.filter((mount) =>
|
||
mount.mount_point === "/user" || /^\/mnt\/(?:usb|ext)\d+(?:\/|$)/.test(mount.mount_point)
|
||
);
|
||
const mounts = libraryMounts.length ? libraryMounts : allMounts.filter((mount) => !mount.read_only);
|
||
list.replaceChildren();
|
||
if (!mounts.length) {
|
||
list.append(node("span", "subtle", t("storageNotFound")));
|
||
return;
|
||
}
|
||
|
||
mounts.forEach((mount) => {
|
||
const total = Number(mount.total_bytes) || 0;
|
||
const available = Number(mount.available_bytes) || 0;
|
||
const usedPercent = total > 0 ? Math.max(0, Math.min(100, ((total - available) / total) * 100)) : 0;
|
||
const freePercent = 100 - usedPercent;
|
||
const row = node("div", "storage-row");
|
||
const head = node("div", "storage-head");
|
||
const path = node("span", "storage-path", mount.mount_point);
|
||
path.title = mount.mount_point;
|
||
head.append(path, badge(mount.read_only ? "R/O" : mount.filesystem || "FS", mount.read_only ? "" : "live"));
|
||
row.append(head);
|
||
const meta = node("div", "storage-meta", `${mount.filesystem || t("unknown")} · ${mount.source || "—"}`);
|
||
meta.title = mount.source || "";
|
||
row.append(meta);
|
||
const space = node("div", "storage-space");
|
||
space.append(node("span", "", t("freeSpace", { size: formatBytes(available) })));
|
||
space.append(node("span", "", t("totalSpace", { size: formatBytes(total) })));
|
||
row.append(space);
|
||
const meter = node("div", "storage-meter");
|
||
meter.setAttribute("role", "progressbar");
|
||
meter.setAttribute("aria-label", t("storageUsed", { path: mount.mount_point }));
|
||
meter.setAttribute("aria-valuemin", "0");
|
||
meter.setAttribute("aria-valuemax", "100");
|
||
meter.setAttribute("aria-valuenow", String(Math.round(usedPercent)));
|
||
const level = freePercent < 10 ? "danger" : freePercent < 20 ? "warning" : "";
|
||
const fill = node("div", `storage-fill ${level}`.trim());
|
||
fill.style.width = `${usedPercent}%`;
|
||
meter.append(fill);
|
||
row.append(meter);
|
||
list.append(row);
|
||
});
|
||
} catch (error) {
|
||
list.replaceChildren(node("span", "subtle", errorMessage(error)));
|
||
}
|
||
}
|
||
|
||
async function refreshLibrary() {
|
||
if (state.busy) return;
|
||
state.busy = true;
|
||
$("refreshBtn").disabled = true;
|
||
try {
|
||
const [version, games] = await Promise.all([
|
||
api("/version"),
|
||
api("/games", { include_size: $("includeSize").checked })
|
||
]);
|
||
state.games = games.games || [];
|
||
state.apiVersion = version;
|
||
$("version").textContent = `ShadowMount ${version.shadowmount_version} · API v${version.api_version}`;
|
||
setConnection("online", "connected");
|
||
renderStats();
|
||
renderGames();
|
||
} catch (error) {
|
||
setConnection("offline", "disconnected");
|
||
$("games").replaceChildren(node("div", "empty", t("apiError", { error: errorMessage(error) })));
|
||
toast(errorMessage(error), true);
|
||
} finally {
|
||
state.busy = false;
|
||
$("refreshBtn").disabled = false;
|
||
}
|
||
}
|
||
|
||
function refreshAll() {
|
||
return Promise.all([refreshLibrary(), refreshStorage()]);
|
||
}
|
||
|
||
async function refreshManual() {
|
||
const list = $("manualList");
|
||
try {
|
||
const data = await api("/manual/list");
|
||
list.replaceChildren();
|
||
if (!data.paths.length) {
|
||
list.append(node("span", "subtle", t("listEmpty")));
|
||
return;
|
||
}
|
||
data.paths.forEach((path) => {
|
||
const row = node("div", "manual-row");
|
||
const value = node("span", "manual-path", path);
|
||
value.title = path;
|
||
const remove = node("button", "btn small danger", t("remove"));
|
||
remove.type = "button";
|
||
remove.addEventListener("click", () => removeManual(path));
|
||
row.append(value, remove);
|
||
list.append(row);
|
||
});
|
||
} catch (error) {
|
||
list.replaceChildren(node("span", "subtle", errorMessage(error)));
|
||
}
|
||
}
|
||
|
||
function renderScanPaths() {
|
||
const list = $("scanPathList");
|
||
list.replaceChildren();
|
||
if (!state.scanPaths.length) {
|
||
list.append(node("span", "subtle", t("listEmpty")));
|
||
return;
|
||
}
|
||
state.scanPaths.forEach((path, index) => {
|
||
const row = node("div", "manual-row");
|
||
const value = node("span", "manual-path", path);
|
||
value.title = path;
|
||
const remove = node("button", "btn small danger", t("remove"));
|
||
remove.type = "button";
|
||
remove.addEventListener("click", () => {
|
||
state.scanPaths.splice(index, 1);
|
||
renderScanPaths();
|
||
toast(t("scanPathRemoved"));
|
||
});
|
||
row.append(value, remove);
|
||
list.append(row);
|
||
});
|
||
}
|
||
|
||
async function refreshSettings() {
|
||
try {
|
||
const settings = await api("/settings");
|
||
$("debugEnabled").checked = Boolean(settings.debug);
|
||
$("quietMode").checked = Boolean(settings.quiet_mode);
|
||
$("updateEmulators").checked = Boolean(settings.update_emulators);
|
||
$("autoUpdateAmpr").checked = Boolean(settings.auto_update_ampr);
|
||
$("autoRemoveMissingGames").checked = Boolean(settings.auto_remove_missing_games);
|
||
$("autoRemoveMissingDelay").value = String(settings.auto_remove_missing_delay_seconds || 300);
|
||
syncAutoRemoveControls();
|
||
$("allowLanAccess").checked = Boolean(settings.allow_lan_access);
|
||
$("fanTemperature").value = String(settings.fan_target_temperature || 0);
|
||
state.scanPaths = Array.isArray(settings.scan_paths) ? settings.scan_paths.slice() : [];
|
||
renderScanPaths();
|
||
} catch (error) {
|
||
$("scanPathList").replaceChildren(node("span", "subtle", errorMessage(error)));
|
||
}
|
||
}
|
||
|
||
function syncAutoRemoveControls() {
|
||
$("autoRemoveMissingDelay").disabled = !$("autoRemoveMissingGames").checked;
|
||
}
|
||
|
||
function addScanPath(event) {
|
||
event.preventDefault();
|
||
const input = $("scanPath");
|
||
let path = input.value.trim();
|
||
while (path.length > 1 && path.endsWith("/")) path = path.slice(0, -1);
|
||
if (!path.startsWith("/") || path === "/") {
|
||
toast(t("invalidAbsolutePath"), true);
|
||
return;
|
||
}
|
||
if (!state.scanPaths.includes(path)) state.scanPaths.push(path);
|
||
input.value = "";
|
||
renderScanPaths();
|
||
toast(t("scanPathAdded"));
|
||
}
|
||
|
||
async function saveSettings(event) {
|
||
event.preventDefault();
|
||
const submit = $("settingsForm").querySelector('button[type="submit"]');
|
||
submit.disabled = true;
|
||
try {
|
||
await api("/settings/update", {
|
||
debug: $("debugEnabled").checked,
|
||
quiet_mode: $("quietMode").checked,
|
||
update_emulators: $("updateEmulators").checked,
|
||
auto_update_ampr: $("autoUpdateAmpr").checked,
|
||
auto_remove_missing_games: $("autoRemoveMissingGames").checked,
|
||
auto_remove_missing_delay_seconds: Number($("autoRemoveMissingDelay").value),
|
||
allow_lan_access: $("allowLanAccess").checked,
|
||
fan_target_temperature: Number($("fanTemperature").value),
|
||
scan_paths: state.scanPaths
|
||
});
|
||
logActivity(t("settingsSaved"));
|
||
toast(t("settingsSaved"));
|
||
setTimeout(refreshSettings, 1200);
|
||
} catch (error) {
|
||
logActivity(errorMessage(error), "error");
|
||
toast(errorMessage(error), true);
|
||
} finally {
|
||
submit.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function refreshLog() {
|
||
$("refreshLog").disabled = true;
|
||
try {
|
||
const result = await api(state.logRoute, { max_bytes: 262144 });
|
||
$("logContent").textContent = result.content || "";
|
||
$("logSubtitle").textContent = t("logBytes", {
|
||
shown: formatBytes(Number(result.returned_bytes)),
|
||
total: formatBytes(Number(result.total_bytes !== undefined
|
||
? result.total_bytes : result.file_size))
|
||
});
|
||
$("logContent").scrollTop = $("logContent").scrollHeight;
|
||
} catch (error) {
|
||
$("logContent").textContent = errorMessage(error);
|
||
$("logSubtitle").textContent = "";
|
||
} finally {
|
||
$("refreshLog").disabled = false;
|
||
}
|
||
}
|
||
|
||
function showLog(route, titleKey, triggerId) {
|
||
state.logRoute = route;
|
||
state.logTitleKey = titleKey;
|
||
state.logTriggerId = triggerId;
|
||
$("logModal").hidden = false;
|
||
$("logTitle").textContent = t(titleKey);
|
||
$("logContent").textContent = t("loading");
|
||
$("refreshLog").focus();
|
||
refreshLog();
|
||
}
|
||
|
||
function showDebugLog() { showLog("/debug-log", "debugLogTitle", "viewLog"); }
|
||
function showKernelLog() { showLog("/kernel-log", "kernelLogTitle", "viewKernelLog"); }
|
||
|
||
function closeDebugLog() {
|
||
$("logModal").hidden = true;
|
||
$(state.logTriggerId).focus();
|
||
}
|
||
|
||
async function addManual(event) {
|
||
event.preventDefault();
|
||
const input = $("manualPath");
|
||
const path = input.value.trim();
|
||
if (!path) return;
|
||
try {
|
||
const result = await api("/manual/add", { path });
|
||
input.value = "";
|
||
logActivity(t(result.changed ? "manualAdded" : "manualExists"));
|
||
await refreshManual();
|
||
} catch (error) {
|
||
logActivity(errorMessage(error), "error");
|
||
toast(errorMessage(error), true);
|
||
}
|
||
}
|
||
|
||
async function removeManual(path) {
|
||
if (!confirm(t("removeManualConfirm", { path }))) return;
|
||
try {
|
||
await api("/manual/remove", { path });
|
||
logActivity(t("manualRemoved"));
|
||
await refreshManual();
|
||
} catch (error) {
|
||
logActivity(errorMessage(error), "error");
|
||
toast(errorMessage(error), true);
|
||
}
|
||
}
|
||
|
||
async function requestScan() {
|
||
const resetAttempts = $("resetAttempts").checked;
|
||
$("scanBtn").disabled = true;
|
||
try {
|
||
await api("/scan", { reset_attempts: resetAttempts });
|
||
logActivity(t(resetAttempts ? "scanQueuedReset" : "scanQueued"));
|
||
toast(t("scanStarted"));
|
||
setTimeout(refreshAll, 1600);
|
||
} catch (error) {
|
||
logActivity(errorMessage(error), "error");
|
||
toast(errorMessage(error), true);
|
||
} finally {
|
||
$("scanBtn").disabled = false;
|
||
}
|
||
}
|
||
|
||
async function runMutation(label, task, closeAfter = false) {
|
||
try {
|
||
await task();
|
||
logActivity(label);
|
||
toast(label);
|
||
if (closeAfter) closeModal();
|
||
await refreshAll();
|
||
} catch (error) {
|
||
logActivity(`${label}: ${errorMessage(error)}`, "error");
|
||
toast(errorMessage(error), true);
|
||
}
|
||
}
|
||
|
||
function titleRuntime(game, operation, mode) {
|
||
const payload = { title_id: game.title_id };
|
||
if (mode) payload.mode = mode;
|
||
const label = operation === "mount"
|
||
? t("mountedTitle", { title: game.title_id, mode: mode ? ` (${mode})` : "" })
|
||
: t("unmountedTitle", { title: game.title_id });
|
||
return runMutation(label, async () => {
|
||
await api(`/games/${operation}`, payload);
|
||
if (!$('modal').hidden && state.selected &&
|
||
state.selected.title_id === game.title_id) {
|
||
state.selected.mounted = operation === "mount";
|
||
renderDetails(state.selected, true);
|
||
}
|
||
});
|
||
}
|
||
|
||
function actionButton(text, action, extra = "") {
|
||
const button = node("button", `btn small ${extra}`.trim(), text);
|
||
button.type = "button";
|
||
button.addEventListener("click", action);
|
||
return button;
|
||
}
|
||
|
||
function detailRow(grid, key, value) {
|
||
grid.append(node("div", "detail-key", key));
|
||
grid.append(node("div", "detail-value", value || "—"));
|
||
}
|
||
|
||
function renderDetails(game, focusRuntimeAction = false) {
|
||
state.selected = game;
|
||
$("modalTitle").textContent = game.title_name || game.title_id;
|
||
$("modalSubtitle").textContent = `${game.title_id} · ${(game.platform || t("unknown")).toUpperCase()}`;
|
||
const details = $("details");
|
||
details.replaceChildren();
|
||
details.append(coverFor(game, true));
|
||
const content = node("div");
|
||
const badges = node("div", "badges");
|
||
badges.append(badge(game.source_type === "folder" ? t("folder") : game.source_type === "image" ? t("image") : game.source_type));
|
||
if (game.image_type) badges.append(badge(game.image_type, "image"));
|
||
if (game.mounted) badges.append(badge(t("mounted"), "live"));
|
||
content.append(badges);
|
||
const grid = node("div", "detail-grid");
|
||
detailRow(grid, "Content ID", game.content_id);
|
||
detailRow(grid, t("source"), game.path);
|
||
detailRow(grid, t("runtimePath"), game.runtime_path);
|
||
detailRow(grid, t("size"), game.size_status === 0 ? formatBytes(game.size_bytes) : t("sizeError", { status: game.size_status }));
|
||
detailRow(grid, "app.db", formatBytes(game.app_db_size_bytes));
|
||
detailRow(grid, t("installed"), game.install_time);
|
||
detailRow(grid, t("lastLaunch"), game.last_access_time);
|
||
content.append(grid);
|
||
details.append(content);
|
||
|
||
const actions = $("modalActions");
|
||
actions.replaceChildren();
|
||
let runtimeAction;
|
||
if (game.mounted) {
|
||
runtimeAction = actionButton(t("disconnect"), () => titleRuntime(game, "unmount"));
|
||
actions.append(runtimeAction);
|
||
} else if (game.image_backed) {
|
||
runtimeAction = actionButton(t("mountReadOnly"), () => titleRuntime(game, "mount", "ro"));
|
||
actions.append(runtimeAction);
|
||
actions.append(actionButton(t("mountReadWrite"), () => titleRuntime(game, "mount", "rw")));
|
||
} else {
|
||
runtimeAction = actionButton(t("connect"), () => titleRuntime(game, "mount"));
|
||
actions.append(runtimeAction);
|
||
}
|
||
const storageBusy = Boolean(state.storageJob && state.storageJob.active);
|
||
const copy = actionButton(t("copy"), () => storageOperation(game, "copy"));
|
||
const move = actionButton(t("move"), () => storageOperation(game, "move"));
|
||
const unpack = game.image_backed
|
||
? actionButton(t("unpack"), () => unpackImage(game))
|
||
: null;
|
||
const remove = actionButton(t("deleteSource"), () => deleteSource(game), "danger");
|
||
copy.disabled = storageBusy;
|
||
move.disabled = storageBusy;
|
||
if (unpack) unpack.disabled = storageBusy || game.mounted;
|
||
remove.disabled = storageBusy;
|
||
if (storageBusy) {
|
||
copy.title = t("storageBusyHint");
|
||
move.title = copy.title;
|
||
if (unpack) unpack.title = copy.title;
|
||
remove.title = copy.title;
|
||
}
|
||
actions.append(copy, move);
|
||
if (unpack) actions.append(unpack);
|
||
actions.append(actionButton(t("uninstall"), () => uninstallGame(game), "danger"));
|
||
actions.append(remove);
|
||
if (focusRuntimeAction) runtimeAction.focus();
|
||
}
|
||
|
||
async function showDetails(titleId) {
|
||
$("modal").hidden = false;
|
||
$("modalTitle").textContent = t("loading");
|
||
$("modalSubtitle").textContent = titleId;
|
||
$("details").replaceChildren(node("div", "empty", t("loadingSource")));
|
||
$("modalActions").replaceChildren();
|
||
try {
|
||
renderDetails(await api("/games/info", { title_id: titleId }), true);
|
||
} catch (error) {
|
||
$("details").replaceChildren(node("div", "empty", errorMessage(error)));
|
||
}
|
||
}
|
||
|
||
function closeModal() {
|
||
$("modal").hidden = true;
|
||
state.selected = null;
|
||
}
|
||
|
||
async function storageOperation(game, operation) {
|
||
if (state.storageJob && state.storageJob.active) {
|
||
toast(t("storageBusy"), true);
|
||
return;
|
||
}
|
||
const destination = prompt(t(operation === "copy" ? "copyDestination" : "moveDestination"), "/mnt/usb0");
|
||
if (!destination) return;
|
||
await startStorageJob(game, operation, { title_id: game.title_id, destination_dir: destination });
|
||
}
|
||
|
||
async function unpackImage(game) {
|
||
if (state.storageJob && state.storageJob.active) {
|
||
toast(t("storageBusy"), true);
|
||
return;
|
||
}
|
||
const destination = prompt(t("unpackDestination", { title: game.title_id }), "/mnt/usb0");
|
||
if (!destination) return;
|
||
const deleteSourceAfter = confirm(t("unpackDeleteConfirm"));
|
||
await startStorageJob(game, "unpack", {
|
||
title_id: game.title_id,
|
||
destination_dir: destination,
|
||
delete_source: deleteSourceAfter
|
||
});
|
||
}
|
||
|
||
async function startStorageJob(game, operation, payload) {
|
||
try {
|
||
const job = await api(`/games/${operation}`, payload);
|
||
renderStorageJob(job);
|
||
closeModal();
|
||
logActivity(t("jobStarted", { operation: storageOperationLabel(operation), title: game.title_id, id: job.job_id }));
|
||
toast(t("operationStarted"));
|
||
if (job.active) scheduleStorageJobPoll();
|
||
else reportStorageJob(job);
|
||
} catch (error) {
|
||
logActivity(`${storageOperationLabel(operation)} ${game.title_id}: ${errorMessage(error)}`, "error");
|
||
toast(errorMessage(error), true);
|
||
await refreshStorageJob();
|
||
}
|
||
}
|
||
|
||
function uninstallGame(game) {
|
||
if (!confirm(t("uninstallConfirm", { game: gameLabel(game) }))) return;
|
||
runMutation(t("uninstallAccepted", { title: game.title_id }), () => api("/games/uninstall", { title_id: game.title_id }), true);
|
||
}
|
||
|
||
async function deleteSource(game) {
|
||
if (state.storageJob && state.storageJob.active) {
|
||
toast(t("storageBusy"), true);
|
||
return;
|
||
}
|
||
if (!confirm(t("deleteConfirm", { game: gameLabel(game) }))) return;
|
||
await startStorageJob(game, "delete", { title_id: game.title_id, confirm: true });
|
||
}
|
||
|
||
$("search").addEventListener("input", renderGames);
|
||
$("filter").addEventListener("change", renderGames);
|
||
$("language").addEventListener("change", () => {
|
||
applyLanguage($("language").value, true);
|
||
renderStats();
|
||
renderGames();
|
||
if (state.storageJob) renderStorageJob(state.storageJob);
|
||
if (state.selected) renderDetails(state.selected);
|
||
renderScanPaths();
|
||
refreshStorage();
|
||
refreshManual();
|
||
});
|
||
$("includeSize").addEventListener("change", refreshLibrary);
|
||
$("refreshBtn").addEventListener("click", refreshAll);
|
||
$("scanBtn").addEventListener("click", requestScan);
|
||
$("cancelTransfer").addEventListener("click", cancelStorageJob);
|
||
$("manualForm").addEventListener("submit", addManual);
|
||
$("settingsForm").addEventListener("submit", saveSettings);
|
||
$("autoRemoveMissingGames").addEventListener("change", syncAutoRemoveControls);
|
||
$("scanPathForm").addEventListener("submit", addScanPath);
|
||
$("viewLog").addEventListener("click", showDebugLog);
|
||
$("viewKernelLog").addEventListener("click", showKernelLog);
|
||
$("refreshLog").addEventListener("click", refreshLog);
|
||
$("closeLog").addEventListener("click", closeDebugLog);
|
||
$("logModal").addEventListener("click", (event) => { if (event.target === $("logModal")) closeDebugLog(); });
|
||
$("closeModal").addEventListener("click", closeModal);
|
||
$("modal").addEventListener("click", (event) => { if (event.target === $("modal")) closeModal(); });
|
||
document.addEventListener("keydown", (event) => {
|
||
if (event.key !== "Escape") return;
|
||
if (!$("logModal").hidden) closeDebugLog();
|
||
else if (!$("modal").hidden) closeModal();
|
||
});
|
||
|
||
for (let temperature = 50; temperature <= 91; temperature += 1) {
|
||
const option = node("option", "", `${temperature} °C`);
|
||
option.value = String(temperature);
|
||
$("fanTemperature").append(option);
|
||
}
|
||
applyLanguage(currentLanguage);
|
||
Promise.all([refreshAll(), refreshManual(), refreshSettings(), refreshStorageJob()]);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|