console page: Home

Running game with Close, one storage card per drive, every job with its
owner (this console / a PC) and Cancel, quick actions Browse USB (hidden
until Files opens at a path), Rest mode and Restart. Every destructive
action goes through the shared ConfirmDialog (nav modal, focus on the safe
choice, ○ closes). useJobs polls /api/jobs every 1 s while a job runs,
every 5 s otherwise, stops on unmount and on 401. Sections get a context
(status, refreshStatus, openSection with params). Strings in 21 locales.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
This commit is contained in:
phantomptrandClaude Opus 5.5 committed 2026-10-17 05:49:08 -07:00
1 parent 8f7233827f
commit f91e0f9b67
31 files changed
+3592 -33

No files matched your search

+34 -8
View File
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { call, isOffline, onTokenExpired } from "./api";
import { focusElement, onNavAction, type InputMap } from "./nav";
import { SECTIONS } from "./sections";
import { SECTIONS, type SectionContext, type SectionParams } from "./sections";
import { HintBar } from "./shell/HintBar";
import { Rail } from "./shell/Rail";
import { ReloadBanner } from "./shell/ReloadBanner";
@@ -21,6 +21,16 @@ export function App({ inputMap }: { inputMap: InputMap }) {
const [expired, setExpired] = useState(false);
const [status, setStatus] = useState<ConsoleStatus | null>(null);
const [offline, setOffline] = useState(false);
/** Bumped to read /api/status again at once. */
const [statusTick, setStatusTick] = useState(0);
/** What the active section was opened with (Home's Browse USB → Files at a path). */
const [params, setParams] = useState<SectionParams | null>(null);
/** Switch section; params go only to a section opened through openSection. */
const show = useCallback((id: string, p: SectionParams | null = null) => {
setActive(id);
setParams(p);
}, []);
useEffect(() => onTokenExpired(() => setExpired(true)), []);
@@ -46,7 +56,7 @@ export function App({ inputMap }: { inputMap: InputMap }) {
live = false;
window.clearInterval(timer);
};
}, [expired]);
}, [expired, statusTick]);
// Start with focus on the rail, so the D-pad works at once.
useEffect(() => {
@@ -62,7 +72,7 @@ export function App({ inputMap }: { inputMap: InputMap }) {
if (action === "prevSection" || action === "nextSection") {
const step = action === "nextSection" ? 1 : SECTIONS.length - 1;
const next = SECTIONS[(i + step) % SECTIONS.length].id;
setActive(next);
show(next);
const el = railButton(next);
if (el) focusElement(el);
return true;
@@ -73,7 +83,7 @@ export function App({ inputMap }: { inputMap: InputMap }) {
const el = railButton(active);
if (el) focusElement(el);
} else if (active !== SECTIONS[0].id) {
setActive(SECTIONS[0].id);
show(SECTIONS[0].id);
const el = railButton(SECTIONS[0].id);
if (el) focusElement(el);
}
@@ -81,7 +91,23 @@ export function App({ inputMap }: { inputMap: InputMap }) {
}
return false;
}),
[active],
[active, show],
);
const ctx = useMemo<SectionContext>(
() => ({
status,
refreshStatus: () => setStatusTick((n) => n + 1),
openSection: (id, p) => {
if (!SECTIONS.some((s) => s.id === id)) return;
show(id, p ?? null);
const el = railButton(id);
if (el) focusElement(el);
},
canOpen: (id) => SECTIONS.some((s) => s.id === id && s.acceptsParams),
params,
}),
[status, params, show],
);
const section = SECTIONS.find((s) => s.id === active) ?? SECTIONS[0];
@@ -91,8 +117,8 @@ export function App({ inputMap }: { inputMap: InputMap }) {
<div className="cw-app">
<TopBar status={status} chipFallback={chipFallback} />
<div className="cw-body" aria-hidden={expired ? true : undefined}>
<Rail sections={SECTIONS} active={active} onSelect={setActive} />
<main className="cw-content">{section.render()}</main>
<Rail sections={SECTIONS} active={active} onSelect={(id) => show(id)} />
<main className="cw-content">{section.render(ctx)}</main>
</div>
<HintBar map={inputMap} />
{expired ? <ReloadBanner /> : null}
+233
View File
@@ -243,3 +243,236 @@ button {
width: 1.2rem;
height: 1.2rem;
}
.cw-button.is-danger {
background: var(--color-bad);
color: #fff;
}
.cw-button.is-danger:focus {
background: var(--color-bad);
box-shadow: 0 0 0 4px var(--color-bad-soft);
}
/* ---- shared text ---- */
.cw-h2 {
margin: 0 0 0.5rem;
font-size: 1.1rem;
font-weight: 600;
}
.cw-dim {
color: var(--color-muted);
}
.cw-mono {
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 0.8rem;
}
.cw-tag {
display: inline-block;
padding: 0.05rem 0.5rem;
margin-left: 0.6rem;
border-radius: var(--radius-control);
background: var(--color-pill);
color: var(--color-muted);
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
}
.cw-tag.is-pc {
background: var(--color-accent-soft);
color: var(--color-accent);
}
/* ---- progress bar ---- */
.cw-bar {
height: 0.4rem;
margin: 0.35rem 0;
border-radius: 0.2rem;
background: var(--color-surface-3);
overflow: hidden;
}
.cw-bar-fill {
height: 100%;
border-radius: 0.2rem;
background: var(--accent-fill);
}
.cw-bar-fill.is-warn {
background: var(--color-warn);
}
.cw-bar-fill.is-unknown {
opacity: 0.4;
}
/* ---- confirm dialog ---- */
.cw-scrim {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--overlay-scrim, rgba(0, 0, 0, 0.6));
z-index: 20;
}
.cw-dialog {
width: 32rem;
max-width: calc(100vw - 2rem);
padding: 1.2rem 1.4rem;
border-radius: var(--radius-panel);
background: var(--color-float);
border: 1px solid var(--color-border-strong);
box-shadow: var(--shadow-3);
}
.cw-dialog h2 {
margin: 0 0 0.4rem;
font-size: 1.2rem;
}
.cw-dialog p {
margin: 0 0 1rem;
color: var(--color-muted);
}
.cw-dialog-buttons {
display: flex;
justify-content: flex-end;
}
.cw-dialog-buttons .cw-button {
margin-left: 0.8rem;
}
/* ---- Home ---- */
.cw-msg {
margin: 0 0 0.8rem;
padding: 0.5rem 0.9rem;
border-radius: var(--radius-field);
background: var(--color-surface-2);
}
.cw-msg.is-error {
background: var(--color-bad-soft);
color: var(--color-bad);
}
.cw-msg.is-info {
background: var(--color-good-soft);
color: var(--color-good);
}
.cw-quick {
display: flex;
flex-wrap: wrap;
margin-bottom: 0.6rem;
}
.cw-quick .cw-button {
margin: 0 0.7rem 0.5rem 0;
}
.cw-card {
border-radius: var(--radius-card);
background: var(--color-surface);
border: 1px solid var(--color-border);
}
.cw-game {
display: flex;
align-items: center;
padding: 0.8rem 1rem;
margin-bottom: 1.2rem;
}
.cw-game-icon {
flex: none;
width: 2rem;
height: 2rem;
margin-right: 0.9rem;
color: var(--color-muted);
}
.cw-game-text {
flex: 1;
min-width: 0;
}
.cw-game-title {
font-size: 1.15rem;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.cw-home-section {
margin-bottom: 1.2rem;
}
.cw-home-section > p {
margin: 0;
}
.cw-tasks,
.cw-volumes {
list-style: none;
margin: 0;
padding: 0;
}
.cw-task {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
padding-right: 0.8rem;
border-radius: var(--radius-card);
background: var(--color-surface);
border: 1px solid var(--color-border);
}
.cw-task-main {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
padding: 0.6rem 0.9rem;
text-align: left;
}
.cw-task-icon {
flex: none;
width: 1.5rem;
height: 1.5rem;
margin-right: 0.8rem;
color: var(--color-muted);
}
.cw-task-text {
flex: 1;
min-width: 0;
}
.cw-task-title {
font-weight: 600;
}
.cw-task-sub {
font-size: 0.85rem;
}
.cw-task.is-failed .cw-task-sub {
color: var(--color-bad);
}
.cw-task.is-done .cw-task-sub {
color: var(--color-good);
}
.cw-task > .cw-button {
flex: none;
margin-left: 0.8rem;
}
.cw-volumes {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
grid-gap: 0.8rem;
gap: 0.8rem;
}
.cw-volume {
padding: 0.7rem 0.9rem;
border-radius: var(--radius-card);
background: var(--color-surface);
border: 1px solid var(--color-border);
}
.cw-volume-head {
display: flex;
align-items: center;
}
.cw-volume-head svg {
flex: none;
width: 1.3rem;
height: 1.3rem;
margin-right: 0.5rem;
color: var(--color-muted);
}
.cw-volume-name {
font-weight: 600;
}
.cw-volume-free {
font-size: 0.85rem;
}
+234
View File
@@ -0,0 +1,234 @@
import { isValidElement, type ReactElement, type ReactNode } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../api", async (importOriginal) => {
const real = await importOriginal<typeof import("../api")>();
return { ...real, call: vi.fn() };
});
import { ApiError, call } from "../api";
import { t } from "../strings";
import { ConfirmDialog, dialogNavHandler } from "../ui/ConfirmDialog";
import { errorText, firstUsbVolume, HomeView, runAsk, type Ask, type HomeViewProps, type Volume } from "./Home";
import { TaskRow } from "./TaskRow";
import { cancelJob, type Job } from "./useJobs";
const mockCall = vi.mocked(call);
const job = (over: Partial<Job> = {}): Job => ({
job: "0123456789abcdef0123456789abcdef",
kind: "copy",
state: "running",
files_done: 1,
files_total: 4,
bytes_done: 100,
bytes_total: 400,
mine: true,
...over,
});
const vol = (path: string, mount_from: string, over: Partial<Volume> = {}): Volume => ({
path,
mount_from,
fs_type: "ufs",
total_bytes: 1000,
free_bytes: 250,
writable: true,
...over,
});
/** Every element in `node`'s tree (function components are not expanded). */
function elements(node: ReactNode): ReactElement<Record<string, unknown>>[] {
const out: ReactElement<Record<string, unknown>>[] = [];
const walk = (n: ReactNode) => {
if (Array.isArray(n)) return n.forEach(walk);
if (!isValidElement(n)) return;
const el = n as ReactElement<Record<string, unknown>>;
out.push(el);
walk(el.props.children as ReactNode);
};
walk(node);
return out;
}
const byAction = (node: ReactNode, action: string) => {
const el = elements(node).find((e) => e.props["data-action"] === action);
if (!el) throw new Error(`no element with data-action=${action}`);
return el;
};
function viewProps(over: Partial<HomeViewProps> = {}): HomeViewProps {
return {
status: null,
volumes: [],
volumesError: false,
jobs: [],
jobsLoaded: true,
jobsError: false,
ask: null,
busy: false,
message: null,
onAsk: vi.fn(),
onConfirm: vi.fn(),
onDismiss: vi.fn(),
onBrowseUsb: null,
...over,
};
}
beforeEach(() => {
mockCall.mockReset();
mockCall.mockResolvedValue({});
});
describe("Home", () => {
it("shows_pc_job_as_from_pc", () => {
expect(t("cw_task_from_pc")).not.toBe("cw_task_from_pc");
const pc = renderToStaticMarkup(<TaskRow job={job({ mine: false })} onCancel={() => {}} />);
expect(pc).toContain(t("cw_task_from_pc"));
expect(pc).not.toContain(t("cw_task_from_console"));
const mine = renderToStaticMarkup(<TaskRow job={job({ mine: true })} onCancel={() => {}} />);
expect(mine).toContain(t("cw_task_from_console"));
});
it("cancel_asks_confirm_then_calls_api", async () => {
const running = job({ mine: false });
// Cancel on the row only asks.
const onCancel = vi.fn();
(byAction(TaskRow({ job: running, onCancel }), "cancel").props.onClick as () => void)();
expect(onCancel).toHaveBeenCalledWith(running);
expect(mockCall).not.toHaveBeenCalled();
// The ask opens the confirm dialog; focus starts on the safe choice.
const ask: Ask = { kind: "cancel", job: running };
const props = viewProps({ ask, jobs: [running] });
const html = renderToStaticMarkup(<HomeView {...props} />);
expect(html).toContain("data-nav-modal");
expect(html).toContain(t("cw_home_confirm_cancel_title"));
const dialog = elements(HomeView(props)).find((e) => e.type === ConfirmDialog);
expect(dialog).toBeDefined();
expect(mockCall).not.toHaveBeenCalled();
// "Keep going" (or ○) closes it with no call.
(dialog!.props.onCancel as () => void)();
expect(props.onDismiss).toHaveBeenCalled();
expect(mockCall).not.toHaveBeenCalled();
// Confirm runs the ask: POST /api/jobs/cancel {job}.
(dialog!.props.onConfirm as () => void)();
expect(props.onConfirm).toHaveBeenCalled();
await runAsk(ask, { cancel: cancelJob });
expect(mockCall).toHaveBeenCalledWith("/api/jobs/cancel", {
job: running.job,
});
});
it("a finished task has no Cancel", () => {
expect(() => byAction(TaskRow({ job: job({ state: "done" }), onCancel: () => {} }), "cancel")).toThrow();
});
it("close game asks, then posts the app id", async () => {
const props = viewProps({
status: {
version: null,
firmware: null,
model: null,
temp_c: null,
running: { title_id: "PPSA01234", app_id: 77, name: null },
},
});
(byAction(HomeView(props), "close-game").props.onClick as () => void)();
const ask: Ask = { kind: "close", appId: 77, titleId: "PPSA01234" };
expect(props.onAsk).toHaveBeenCalledWith(ask);
expect(mockCall).not.toHaveBeenCalled();
await runAsk(ask, { cancel: cancelJob });
expect(mockCall).toHaveBeenCalledWith("/api/app/close", { app_id: 77 });
});
it("rest mode and restart ask, then post the power action", async () => {
const props = viewProps();
(byAction(HomeView(props), "rest").props.onClick as () => void)();
(byAction(HomeView(props), "restart").props.onClick as () => void)();
expect(props.onAsk).toHaveBeenNthCalledWith(1, {
kind: "power",
action: "rest",
});
expect(props.onAsk).toHaveBeenNthCalledWith(2, {
kind: "power",
action: "reboot",
});
expect(mockCall).not.toHaveBeenCalled();
await runAsk({ kind: "power", action: "rest" }, { cancel: cancelJob });
expect(mockCall).toHaveBeenCalledWith("/api/power", { action: "rest" });
await runAsk({ kind: "power", action: "reboot" }, { cancel: cancelJob });
expect(mockCall).toHaveBeenCalledWith("/api/power", { action: "reboot" });
});
it("Browse USB shows only when there is somewhere to go", () => {
expect(() => byAction(HomeView(viewProps()), "browse-usb")).toThrow();
const go = vi.fn();
(byAction(HomeView(viewProps({ onBrowseUsb: go })), "browse-usb").props.onClick as () => void)();
expect(go).toHaveBeenCalled();
});
it("first USB volume: a /mnt/usbN stick or USB extended storage, never internal or M.2", () => {
expect(firstUsbVolume([vol("/data", "/dev/ssd0.user"), vol("/mnt/ext0", "/dev/nvme0p1")])).toBeNull();
expect(
firstUsbVolume([vol("/data", "x"), vol("/mnt/ext1", "/dev/da0"), vol("/mnt/usb0", "/dev/da1s1")])?.path,
).toBe("/mnt/ext1");
expect(firstUsbVolume([vol("/mnt/usb0", "/dev/da0s1")])?.path).toBe("/mnt/usb0");
});
it("one storage card per drive with free of total", () => {
const html = renderToStaticMarkup(
<HomeView
{...viewProps({
volumes: [vol("/data", "x"), vol("/mnt/usb0", "/dev/da0s1")],
})}
/>,
);
expect(html).toContain(t("cw_home_vol_internal"));
expect(html).toContain(t("cw_home_vol_usb"));
expect(html.split("data-volume=").length - 1).toBe(2);
});
it("errors become short translated messages", () => {
expect(errorText(new ApiError(0, "offline"))).toBe(t("cw_home_err_offline"));
expect(errorText(new ApiError(409, "busy"))).toBe(t("cw_home_err_busy"));
expect(errorText(new ApiError(404, "unknown_job"))).toBe(t("cw_home_err_gone"));
expect(errorText(new ApiError(500, "io_error"))).toBe(t("cw_home_err_failed", { code: "io_error" }));
expect(errorText(new ApiError(401, "token"))).toBeNull(); // the reload banner says it
expect(errorText(new Error("x"))).toBe(t("cw_home_err_failed", { code: "x" }));
});
it("the shown message renders as an alert", () => {
const html = renderToStaticMarkup(<HomeView {...viewProps({ message: { tone: "error", text: "Nope" } })} />);
expect(html).toContain('role="alert"');
expect(html).toContain("Nope");
});
});
describe("ConfirmDialog", () => {
it("is a nav modal whose safe choice is the default focus", () => {
const html = renderToStaticMarkup(
<ConfirmDialog title="T" confirmLabel="Do it" onConfirm={() => {}} onCancel={() => {}} />,
);
expect(html).toContain("data-nav-modal");
expect(html).toContain('role="alertdialog"');
// The cancel button comes first and carries data-nav-default.
const safe = html.indexOf("data-nav-default");
expect(safe).toBeGreaterThan(-1);
expect(safe).toBeLessThan(html.indexOf("Do it"));
expect(html).toContain(t("cw_dialog_cancel"));
});
it("○ cancels; other actions are swallowed while it is open", () => {
const onCancel = vi.fn();
const handle = dialogNavHandler(onCancel);
expect(handle("back")).toBe(true);
expect(onCancel).toHaveBeenCalledTimes(1);
expect(handle("nextSection")).toBe(true);
expect(onCancel).toHaveBeenCalledTimes(1);
});
});
+399
View File
@@ -0,0 +1,399 @@
import { useCallback, useEffect, useState } from "react";
import { FolderOpen, Gamepad2, HardDrive, Moon, RotateCw, Usb, X } from "lucide-react";
import { formatBytes } from "../../lib/format";
import { ApiError, call, isOffline } from "../api";
import type { SectionContext } from "../sections";
import type { ConsoleStatus } from "../shell/TopBar";
import { t } from "../strings";
import { ConfirmDialog } from "../ui/ConfirmDialog";
import { TaskRow } from "./TaskRow";
import { useJobs, type Job } from "./useJobs";
/** Free space moves with every write: re-read this often while Home is open. */
const VOLUMES_POLL_MS = 15_000;
/** An action's result line clears itself after this long. */
const MESSAGE_MS = 8_000;
/** One /api/volumes entry (payload/src/runtime.c handle_fs_list_volumes). */
export interface Volume {
path: string;
mount_from: string;
fs_type: string;
total_bytes: number;
free_bytes: number;
writable: boolean;
source_image?: string;
}
/** Something Home asks about before doing it. */
export type Ask =
| { kind: "cancel"; job: Job }
| { kind: "close"; appId: number; titleId: string }
| { kind: "power"; action: "rest" | "reboot" };
export interface Message {
tone: "error" | "info";
text: string;
}
/* ---- volumes ---- */
const isUsbStick = (v: Volume) => /^\/mnt\/usb\d+$/.test(v.path);
const isExt = (v: Volume) => /^\/mnt\/ext\d+$/.test(v.path);
/** A USB drive formatted as PS5 extended storage (M.2 is /dev/nvme*). */
const isUsbExt = (v: Volume) => isExt(v) && v.mount_from.startsWith("/dev/da");
/** The console's drives: internal, extended/M.2 and USB. Mounted disk images are left out. */
export function isDrive(v: Volume): boolean {
return v.path === "/data" || isExt(v) || isUsbStick(v);
}
/** The first USB drive in the helper's order (a stick or USB extended storage), or null. */
export function firstUsbVolume(volumes: Volume[]): Volume | null {
return volumes.find((v) => isUsbStick(v) || isUsbExt(v)) ?? null;
}
export function volumeLabel(v: Volume): string {
if (v.path === "/data") return t("cw_home_vol_internal");
if (isUsbStick(v)) return t("cw_home_vol_usb");
if (isUsbExt(v)) return t("cw_home_vol_usb_ext");
if (v.mount_from.startsWith("/dev/nvme")) return t("cw_home_vol_m2");
return t("cw_home_vol_ext");
}
/* ---- actions ---- */
/** A failed call as one short translated line; null for a 401 (the reload banner says it). */
export function errorText(err: unknown): string | null {
if (err instanceof ApiError) {
if (err.status === 401) return null;
if (isOffline(err)) return t("cw_home_err_offline");
if (err.code === "busy") return t("cw_home_err_busy");
if (err.code === "unknown_job") return t("cw_home_err_gone");
return t("cw_home_err_failed", { code: err.code });
}
return t("cw_home_err_failed", {
code: err instanceof Error ? err.message : String(err),
});
}
/** Do what was confirmed. Resolves to a line to show (the power actions), throws the ApiError. */
export async function runAsk(ask: Ask, deps: { cancel: (id: string) => Promise<void> }): Promise<string | null> {
switch (ask.kind) {
case "cancel":
await deps.cancel(ask.job.job);
return null;
case "close":
await call("/api/app/close", { app_id: ask.appId });
return null;
case "power":
await call("/api/power", { action: ask.action });
return t(ask.action === "rest" ? "cw_home_rest_sent" : "cw_home_restart_sent");
}
}
function askCopy(ask: Ask): {
title: string;
body: string;
confirm: string;
cancel?: string;
danger: boolean;
} {
switch (ask.kind) {
case "cancel":
return {
title: t("cw_home_confirm_cancel_title"),
body: t(ask.job.mine ? "cw_home_confirm_cancel_body" : "cw_home_confirm_cancel_body_pc"),
confirm: t("cw_home_cancel_task_confirm"),
cancel: t("cw_home_keep_task"),
danger: true,
};
case "close":
return {
title: t("cw_home_confirm_close_title", { name: ask.titleId }),
body: t("cw_home_confirm_close_body"),
confirm: t("cw_home_close_game"),
danger: true,
};
case "power":
return ask.action === "rest"
? {
title: t("cw_home_confirm_rest_title"),
body: t("cw_home_confirm_rest_body"),
confirm: t("cw_home_rest"),
danger: false,
}
: {
title: t("cw_home_confirm_restart_title"),
body: t("cw_home_confirm_restart_body"),
confirm: t("cw_home_restart"),
danger: true,
};
}
}
/* ---- view ---- */
export interface HomeViewProps {
status: ConsoleStatus | null;
/** null until the first answer. */
volumes: Volume[] | null;
volumesError: boolean;
jobs: Job[];
jobsLoaded: boolean;
jobsError: boolean;
ask: Ask | null;
busy: boolean;
message: Message | null;
onAsk: (ask: Ask) => void;
onConfirm: () => void;
onDismiss: () => void;
/** Browse USB, or null to hide it (no USB drive, or Files cannot open a path yet). */
onBrowseUsb: (() => void) | null;
}
function VolumeCard({ v }: { v: Volume }) {
const used = v.total_bytes > 0 ? Math.max(0, Math.min(100, 100 - (v.free_bytes / v.total_bytes) * 100)) : 0;
const Icon = isUsbStick(v) || isUsbExt(v) ? Usb : HardDrive;
return (
<li className="cw-focusable cw-volume" tabIndex={0} data-volume={v.path}>
<div className="cw-volume-head">
<Icon aria-hidden />
<span className="cw-volume-name">{volumeLabel(v)}</span>
{!v.writable ? <span className="cw-tag">{t("cw_home_vol_readonly")}</span> : null}
</div>
<div className="cw-dim cw-mono">{v.path}</div>
<div className="cw-bar" aria-hidden>
<div className={`cw-bar-fill${used >= 90 ? " is-warn" : ""}`} style={{ width: `${used}%` }} />
</div>
<div className="cw-volume-free">
{t("cw_home_free_of", {
free: formatBytes(v.free_bytes),
total: formatBytes(v.total_bytes),
})}
</div>
</li>
);
}
/** Home without state: everything comes in through props (tests render it directly). */
export function HomeView(p: HomeViewProps) {
const running = p.status?.running ?? null;
const tasks = p.jobs.filter((j) => j.state === "running").concat(p.jobs.filter((j) => j.state !== "running"));
const drives = (p.volumes ?? []).filter(isDrive);
const copy = p.ask ? askCopy(p.ask) : null;
let tasksNote: string | null = null;
if (p.jobsError && !tasks.length) tasksNote = t("cw_home_err_load_tasks");
else if (!p.jobsLoaded) tasksNote = t("cw_home_loading");
else if (!tasks.length) tasksNote = t("cw_home_no_tasks");
let drivesNote: string | null = null;
if (p.volumesError && !drives.length) drivesNote = t("cw_home_err_load_storage");
else if (p.volumes == null) drivesNote = t("cw_home_loading");
return (
<div className="cw-home">
<h1 className="cw-h1">{t("cw_nav_home")}</h1>
{p.message ? (
<div className={`cw-msg is-${p.message.tone}`} role={p.message.tone === "error" ? "alert" : "status"}>
{p.message.text}
</div>
) : null}
<div className="cw-quick" role="group" aria-label={t("cw_home_quick")}>
{p.onBrowseUsb ? (
<button type="button" className="cw-focusable cw-button" data-action="browse-usb" onClick={p.onBrowseUsb}>
<FolderOpen aria-hidden />
<span>{t("cw_home_browse_usb")}</span>
</button>
) : null}
<button
type="button"
className="cw-focusable cw-button"
data-action="rest"
onClick={() => p.onAsk({ kind: "power", action: "rest" })}
>
<Moon aria-hidden />
<span>{t("cw_home_rest")}</span>
</button>
<button
type="button"
className="cw-focusable cw-button"
data-action="restart"
onClick={() => p.onAsk({ kind: "power", action: "reboot" })}
>
<RotateCw aria-hidden />
<span>{t("cw_home_restart")}</span>
</button>
</div>
<section className="cw-card cw-game" aria-label={t("cw_home_now_running")}>
<Gamepad2 className="cw-game-icon" aria-hidden />
{running ? (
<>
<div className="cw-game-text">
<div className="cw-dim">{t("cw_home_now_running")}</div>
<div className="cw-game-title">{running.name ?? running.title_id}</div>
{running.name ? <div className="cw-dim cw-mono">{running.title_id}</div> : null}
</div>
<button
type="button"
className="cw-focusable cw-button"
data-action="close-game"
onClick={() =>
p.onAsk({
kind: "close",
appId: running.app_id,
titleId: running.name ?? running.title_id,
})
}
>
<X aria-hidden />
<span>{t("cw_home_close_game")}</span>
</button>
</>
) : (
<div className="cw-game-text cw-dim">{p.status ? t("cw_home_no_game") : t("cw_home_loading")}</div>
)}
</section>
<section className="cw-home-section">
<h2 className="cw-h2">{t("cw_home_tasks")}</h2>
{p.jobsError && tasks.length ? <div className="cw-dim">{t("cw_home_err_load_tasks")}</div> : null}
{tasksNote ? (
<p className="cw-dim">{tasksNote}</p>
) : (
<ul className="cw-tasks">
{tasks.map((j) => (
<TaskRow key={j.job} job={j} onCancel={(job) => p.onAsk({ kind: "cancel", job })} />
))}
</ul>
)}
</section>
<section className="cw-home-section">
<h2 className="cw-h2">{t("cw_home_storage")}</h2>
{drivesNote ? (
<p className="cw-dim">{drivesNote}</p>
) : (
<ul className="cw-volumes">
{drives.map((v) => (
<VolumeCard key={v.path} v={v} />
))}
</ul>
)}
</section>
{p.ask && copy ? (
<ConfirmDialog
title={copy.title}
body={copy.body}
confirmLabel={copy.confirm}
cancelLabel={copy.cancel}
danger={copy.danger}
busy={p.busy}
onConfirm={p.onConfirm}
onCancel={p.onDismiss}
/>
) : null}
</div>
);
}
/* ---- the section ---- */
function useVolumes(): { volumes: Volume[] | null; error: boolean } {
const [volumes, setVolumes] = useState<Volume[] | null>(null);
const [error, setError] = useState(false);
useEffect(() => {
let live = true;
let timer: ReturnType<typeof setTimeout> | undefined;
const load = () => {
call<{ volumes?: unknown }>("/api/volumes").then(
(r) => {
if (!live) return;
setVolumes(Array.isArray(r?.volumes) ? (r.volumes as Volume[]) : []);
setError(false);
timer = setTimeout(load, VOLUMES_POLL_MS);
},
(e: unknown) => {
if (!live) return;
setError(true);
// The token is gone: every read would 401 until the page reloads.
if (!(e instanceof ApiError && e.status === 401)) timer = setTimeout(load, VOLUMES_POLL_MS);
},
);
};
load();
return () => {
live = false;
if (timer !== undefined) clearTimeout(timer);
};
}, []);
return { volumes, error };
}
/** The Home section: the running game, tasks, storage and quick actions. */
export function Home({ ctx }: { ctx: SectionContext }) {
const { jobs, loaded, error: jobsError, cancel } = useJobs();
const { volumes, error: volumesError } = useVolumes();
const [ask, setAsk] = useState<Ask | null>(null);
const [busy, setBusy] = useState(false);
const [message, setMessage] = useState<Message | null>(null);
const { refreshStatus, openSection, canOpen } = ctx;
useEffect(() => {
if (!message) return;
const timer = setTimeout(() => setMessage(null), MESSAGE_MS);
return () => clearTimeout(timer);
}, [message]);
const onConfirm = useCallback(() => {
if (!ask || busy) return;
setBusy(true);
runAsk(ask, { cancel })
.then(
(notice) => {
setMessage(notice ? { tone: "info", text: notice } : null);
if (ask.kind === "close") refreshStatus();
},
(e: unknown) => {
const text = errorText(e);
setMessage(text ? { tone: "error", text } : null);
},
)
.finally(() => {
setBusy(false);
setAsk(null);
});
}, [ask, busy, cancel, refreshStatus]);
const onDismiss = useCallback(() => {
if (!busy) setAsk(null);
}, [busy]);
const usb = volumes ? firstUsbVolume(volumes) : null;
const onBrowseUsb = usb && canOpen("files") ? () => openSection("files", { path: usb.path }) : null;
return (
<HomeView
status={ctx.status}
volumes={volumes}
volumesError={volumesError}
jobs={jobs}
jobsLoaded={loaded}
jobsError={jobsError != null}
ask={ask}
busy={busy}
message={message}
onAsk={(a) => {
setMessage(null);
setAsk(a);
}}
onConfirm={onConfirm}
onDismiss={onDismiss}
onBrowseUsb={onBrowseUsb}
/>
);
}
+88
View File
@@ -0,0 +1,88 @@
import { CircleX, Copy, Download, FolderCog, ListTodo, Upload, type LucideIcon } from "lucide-react";
import { formatBytes } from "../../lib/format";
import { t } from "../strings";
import type { Job } from "./useJobs";
const KIND_ICON: Record<Job["kind"], LucideIcon> = {
upload: Upload,
download: Download,
copy: Copy,
op: FolderCog,
other: ListTodo,
};
export function kindLabel(kind: Job["kind"]): string {
return t(`cw_home_kind_${KIND_ICON[kind] ? kind : "other"}`);
}
/** 0–100 from bytes, else from files; null when the job reports neither. */
export function jobPercent(j: Job): number | null {
const [done, total] = j.bytes_total > 0 ? [j.bytes_done, j.bytes_total] : [j.files_done, j.files_total];
if (!(total > 0)) return null;
return Math.max(0, Math.min(100, Math.floor((done / total) * 100)));
}
function progressText(j: Job): string {
const parts: string[] = [];
if (j.bytes_total > 0)
parts.push(
t("cw_home_progress_bytes", {
done: formatBytes(j.bytes_done),
total: formatBytes(j.bytes_total),
}),
);
if (j.files_total > 0) parts.push(t("cw_home_progress_files", { done: j.files_done, total: j.files_total }));
return parts.join(" · ");
}
/**
* One task on Home: what it is, who started it, how far it is. The row itself
* is focusable so the D-pad reaches (and scrolls to) every task; a running one
* has a Cancel button, which only asks (Home shows the confirm).
*/
export function TaskRow({ job, onCancel }: { job: Job; onCancel: (job: Job) => void }) {
const Icon = KIND_ICON[job.kind] ?? ListTodo;
const pct = jobPercent(job);
const running = job.state === "running";
const progress = progressText(job);
return (
<li className={`cw-task is-${job.state}`}>
<div className="cw-focusable cw-task-main" tabIndex={0}>
<Icon className="cw-task-icon" aria-hidden />
<div className="cw-task-text">
<div className="cw-task-title">
<span>{kindLabel(job.kind)}</span>
<span className={`cw-tag${job.mine ? "" : " is-pc"}`}>
{t(job.mine ? "cw_task_from_console" : "cw_task_from_pc")}
</span>
</div>
{running ? (
<div
className="cw-bar"
role="progressbar"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={pct ?? undefined}
>
<div className={`cw-bar-fill${pct == null ? " is-unknown" : ""}`} style={{ width: `${pct ?? 100}%` }} />
</div>
) : null}
<div className="cw-task-sub">
{running
? pct != null
? t("cw_home_percent", { n: pct })
: t("cw_home_state_running")
: t(job.state === "done" ? "cw_home_state_done" : "cw_home_state_failed")}
{progress ? <span className="cw-dim">{` · ${progress}`}</span> : null}
</div>
</div>
</div>
{running ? (
<button type="button" className="cw-focusable cw-button" data-action="cancel" onClick={() => onCancel(job)}>
<CircleX aria-hidden />
<span>{t("cw_home_cancel_task")}</span>
</button>
) : null}
</li>
);
}
+121
View File
@@ -0,0 +1,121 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../api", async (importOriginal) => {
const real = await importOriginal<typeof import("../api")>();
return { ...real, call: vi.fn() };
});
import { ApiError, call } from "../api";
import { cancelJob, createJobsPoller, JOBS_FAST_MS, JOBS_SLOW_MS, nextJobsDelay, type Job } from "./useJobs";
const mockCall = vi.mocked(call);
const job = (state: Job["state"], over: Partial<Job> = {}): Job => ({
job: "0123456789abcdef0123456789abcdef",
kind: "copy",
state,
files_done: 1,
files_total: 4,
bytes_done: 100,
bytes_total: 400,
mine: true,
...over,
});
const jobsCalls = () => mockCall.mock.calls.filter((c) => c[0] === "/api/jobs").length;
describe("useJobs poller", () => {
beforeEach(() => {
vi.useFakeTimers();
mockCall.mockReset();
});
afterEach(() => {
vi.useRealTimers();
});
it("polls_fast_while_running_then_slow", async () => {
const replies = [[job("running")], [job("done")], [job("done")]];
mockCall.mockImplementation(async () => ({ jobs: replies.shift() ?? [] }));
const seen: Job[][] = [];
const p = createJobsPoller({
onJobs: (j) => seen.push(j),
onError: () => {},
});
p.start();
await vi.advanceTimersByTimeAsync(0);
expect(jobsCalls()).toBe(1);
expect(seen[0][0].state).toBe("running");
// something runs: the next read comes after JOBS_FAST_MS
await vi.advanceTimersByTimeAsync(JOBS_FAST_MS - 1);
expect(jobsCalls()).toBe(1);
await vi.advanceTimersByTimeAsync(1);
expect(jobsCalls()).toBe(2);
// nothing runs any more: the poller backs off to JOBS_SLOW_MS
await vi.advanceTimersByTimeAsync(JOBS_FAST_MS);
expect(jobsCalls()).toBe(2);
await vi.advanceTimersByTimeAsync(JOBS_SLOW_MS - JOBS_FAST_MS);
expect(jobsCalls()).toBe(3);
p.stop();
});
it("stops polling once stopped (the section unmounted)", async () => {
mockCall.mockResolvedValue({ jobs: [job("running")] });
const p = createJobsPoller({ onJobs: () => {}, onError: () => {} });
p.start();
await vi.advanceTimersByTimeAsync(0);
p.stop();
await vi.advanceTimersByTimeAsync(JOBS_SLOW_MS * 3);
expect(jobsCalls()).toBe(1);
});
it("reports errors and backs off; stops for good on 401", async () => {
mockCall.mockRejectedValueOnce(new ApiError(0, "offline")).mockRejectedValueOnce(new ApiError(401, "token"));
const errors: unknown[] = [];
const p = createJobsPoller({
onJobs: () => {},
onError: (e) => errors.push(e),
});
p.start();
await vi.advanceTimersByTimeAsync(0);
expect(errors).toHaveLength(1);
await vi.advanceTimersByTimeAsync(JOBS_FAST_MS);
expect(jobsCalls()).toBe(1); // an error backs off, it does not hammer
await vi.advanceTimersByTimeAsync(JOBS_SLOW_MS);
expect(jobsCalls()).toBe(2);
expect(errors).toHaveLength(2);
await vi.advanceTimersByTimeAsync(JOBS_SLOW_MS * 3);
expect(jobsCalls()).toBe(2); // the token is gone: every read would 401
});
it("refresh reads at once without overlapping a read in flight", async () => {
let release: (v: unknown) => void = () => {};
mockCall.mockImplementationOnce(() => new Promise((r) => (release = r)));
mockCall.mockResolvedValue({ jobs: [] });
const p = createJobsPoller({ onJobs: () => {}, onError: () => {} });
p.start();
await vi.advanceTimersByTimeAsync(0);
p.refresh();
await vi.advanceTimersByTimeAsync(0);
expect(jobsCalls()).toBe(1); // still waiting for the first answer
release({ jobs: [] });
await vi.advanceTimersByTimeAsync(0);
expect(jobsCalls()).toBe(2); // the refresh ran right after it
p.stop();
});
it("delay: fast only while some job is running", () => {
expect(nextJobsDelay([])).toBe(JOBS_SLOW_MS);
expect(nextJobsDelay([job("done"), job("failed")])).toBe(JOBS_SLOW_MS);
expect(nextJobsDelay([job("done"), job("running")])).toBe(JOBS_FAST_MS);
expect(JOBS_FAST_MS).toBe(1000);
expect(JOBS_SLOW_MS).toBe(5000);
});
it("cancelJob posts the job id", async () => {
mockCall.mockResolvedValue({});
await cancelJob("abc");
expect(mockCall).toHaveBeenCalledWith("/api/jobs/cancel", { job: "abc" });
});
});
+152
View File
@@ -0,0 +1,152 @@
/**
* The helper's jobs, every owner's (the PC app's uploads and copies as well as
* the page's own), read from /api/jobs: about once a second while one runs,
* every five seconds otherwise. The poller stops when the section unmounts and
* for good on a 401 (the token is gone; the reload banner takes over).
*/
import { useCallback, useEffect, useRef, useState } from "react";
import { ApiError, call } from "../api";
/** One /api/jobs entry (payload/src/webui_codec.c webui_jobs_to_json). */
export interface Job {
/** 32 lowercase hex chars. */
job: string;
kind: "upload" | "download" | "copy" | "op" | "other";
state: "running" | "done" | "failed";
files_done: number;
files_total: number;
bytes_done: number;
bytes_total: number;
/** Started from this page (false: a paired PC's job). */
mine: boolean;
}
export const JOBS_FAST_MS = 1000;
export const JOBS_SLOW_MS = 5000;
/** How long until the next read after `jobs`. */
export function nextJobsDelay(jobs: Job[]): number {
return jobs.some((j) => j.state === "running") ? JOBS_FAST_MS : JOBS_SLOW_MS;
}
async function fetchJobs(): Promise<Job[]> {
const r = await call<{ jobs?: unknown }>("/api/jobs");
return Array.isArray(r?.jobs) ? (r.jobs as Job[]) : [];
}
/** Ask the helper to stop job `id` (any owner's). */
export async function cancelJob(id: string): Promise<void> {
await call("/api/jobs/cancel", { job: id });
}
export interface JobsPoller {
start(): void;
stop(): void;
/** Read now (after a cancel), never two reads at once. */
refresh(): void;
}
/** The polling loop behind useJobs, with no React in it. */
export function createJobsPoller(opts: { onJobs: (jobs: Job[]) => void; onError: (err: unknown) => void }): JobsPoller {
let timer: ReturnType<typeof setTimeout> | undefined;
let stopped = false;
let inFlight = false;
let again = false;
const schedule = (ms: number) => {
if (stopped) return;
if (timer !== undefined) clearTimeout(timer);
timer = setTimeout(tick, ms);
};
async function tick(): Promise<void> {
timer = undefined;
if (stopped) return;
if (inFlight) {
again = true;
return;
}
inFlight = true;
let delay = JOBS_SLOW_MS;
try {
const jobs = await fetchJobs();
if (stopped) return;
opts.onJobs(jobs);
delay = nextJobsDelay(jobs);
} catch (e) {
if (stopped) return;
opts.onError(e);
if (e instanceof ApiError && e.status === 401) {
stopped = true;
return;
}
} finally {
inFlight = false;
}
if (again) {
again = false;
delay = 0;
}
schedule(delay);
}
return {
start() {
schedule(0);
},
stop() {
stopped = true;
if (timer !== undefined) clearTimeout(timer);
timer = undefined;
},
refresh() {
if (stopped) return;
if (inFlight) again = true;
else schedule(0);
},
};
}
export interface JobsState {
jobs: Job[];
/** The first answer has come in. */
loaded: boolean;
/** The last read failed (null after a good one). */
error: unknown;
/** Stop job `id`; throws the ApiError when the helper refuses. Re-reads either way. */
cancel(id: string): Promise<void>;
}
export function useJobs(): JobsState {
const [jobs, setJobs] = useState<Job[]>([]);
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState<unknown>(null);
const poller = useRef<JobsPoller | null>(null);
useEffect(() => {
const p = createJobsPoller({
onJobs: (j) => {
setJobs(j);
setLoaded(true);
setError(null);
},
onError: setError,
});
poller.current = p;
p.start();
return () => {
p.stop();
if (poller.current === p) poller.current = null;
};
}, []);
const cancel = useCallback(async (id: string) => {
try {
await cancelJob(id);
} finally {
poller.current?.refresh();
}
}, []);
return { jobs, loaded, error, cancel };
}
+37 -4
View File
@@ -1,13 +1,34 @@
import type { ReactNode } from "react";
import { Folder, House, Settings, type LucideIcon } from "lucide-react";
import { Home } from "./home/Home";
import type { ConsoleStatus } from "./shell/TopBar";
import { t } from "./strings";
/** What a section is opened with, e.g. Files at {path: "/mnt/usb0"}. */
export type SectionParams = Record<string, string>;
/** What App hands every section. */
export interface SectionContext {
/** The last /api/status answer (App polls it for the console chip). */
status: ConsoleStatus | null;
/** Read /api/status again now (after closing a game). */
refreshStatus: () => void;
/** Switch to section `id`, opened with `params`. */
openSection: (id: string, params?: SectionParams) => void;
/** Whether section `id` exists and reads params (e.g. Files at a path). */
canOpen: (id: string) => boolean;
/** The params this section was opened with, or null. */
params: SectionParams | null;
}
/** One entry of the Rail. Only implemented sections are listed. */
export interface Section {
id: string;
icon: LucideIcon;
labelKey: string;
render: () => ReactNode;
render: (ctx: SectionContext) => ReactNode;
/** It reads ctx.params, so other sections may open it at a place (Home's Browse USB → Files). */
acceptsParams?: boolean;
}
/** A section whose screen is not built yet: its heading only. */
@@ -15,10 +36,22 @@ function Placeholder({ labelKey }: { labelKey: string }) {
return <h1 className="cw-h1">{t(labelKey)}</h1>;
}
// Tasks 8–10 replace these renders with the Home, Files and Settings screens.
// Tasks 9–10 replace these renders with the Files and Settings screens. Files
// sets acceptsParams once it opens at ctx.params.path: until then Home hides
// Browse USB.
export const SECTIONS: Section[] = [
{ id: "home", icon: House, labelKey: "cw_nav_home", render: () => <Placeholder labelKey="cw_nav_home" /> },
{ id: "files", icon: Folder, labelKey: "cw_nav_files", render: () => <Placeholder labelKey="cw_nav_files" /> },
{
id: "home",
icon: House,
labelKey: "cw_nav_home",
render: (ctx) => <Home ctx={ctx} />,
},
{
id: "files",
icon: Folder,
labelKey: "cw_nav_files",
render: () => <Placeholder labelKey="cw_nav_files" />,
},
{
id: "settings",
icon: Settings,
File diff suppressed because it is too large. Load diff
+89
View File
@@ -0,0 +1,89 @@
import { useEffect, useRef } from "react";
import { focusElement, onNavAction, type NavAction } from "../nav";
import { t } from "../strings";
/**
* The dialog's controller handling: ○ is "no". Every other action is swallowed
* while it is open (the page behind never sees them anyway: nav.ts scopes
* handlers to the open modal).
*/
export function dialogNavHandler(onCancel: () => void): (action: NavAction) => boolean {
return (action) => {
if (action === "back") onCancel();
return true;
};
}
/**
* "Are you sure?" for anything that cannot be undone: close a game, cancel a
* task, rest mode, restart, delete. A nav modal: the D-pad stays inside it,
* focus starts on the safe choice (listed first), ○ closes it, and focus goes
* back where it was when it closes.
*/
export function ConfirmDialog({
title,
body,
confirmLabel,
cancelLabel,
danger,
busy,
onConfirm,
onCancel,
}: {
title: string;
body?: string;
confirmLabel: string;
/** Default: "Cancel". */
cancelLabel?: string;
/** The confirm button in the warning colour. */
danger?: boolean;
/** The action is running: a second ✕ does nothing. */
busy?: boolean;
onConfirm: () => void;
onCancel: () => void;
}) {
const root = useRef<HTMLDivElement>(null);
const safe = useRef<HTMLButtonElement>(null);
useEffect(() => {
const before = document.activeElement as HTMLElement | null;
if (safe.current) focusElement(safe.current);
return () => {
if (before && before !== document.body && before.isConnected) focusElement(before);
};
}, []);
useEffect(() => onNavAction(dialogNavHandler(onCancel), () => root.current), [onCancel]);
return (
<div className="cw-scrim">
<div
ref={root}
className="cw-dialog"
role="alertdialog"
aria-modal="true"
aria-labelledby="cw-dialog-title"
aria-describedby={body ? "cw-dialog-body" : undefined}
aria-busy={busy ? true : undefined}
data-nav-modal
>
<h2 id="cw-dialog-title">{title}</h2>
{body ? <p id="cw-dialog-body">{body}</p> : null}
<div className="cw-dialog-buttons">
<button ref={safe} type="button" className="cw-focusable cw-button" data-nav-default onClick={onCancel}>
<span>{cancelLabel ?? t("cw_dialog_cancel")}</span>
</button>
<button
type="button"
className={`cw-focusable cw-button ${danger ? "is-danger" : "is-primary"}`}
onClick={() => {
if (!busy) onConfirm();
}}
>
<span>{confirmLabel}</span>
</button>
</div>
</div>
</div>
);
}
+52
View File
@@ -4046,6 +4046,58 @@ c2c_test_deep_bad: "لم يصل الإرسال التجريبي بحجم 4 KiB:
"cw_status_connecting": "جارٍ الاتصال…",
"cw_status_offline": "غير متصل",
"cw_hints_label": "أزرار وحدة التحكم",
"cw_task_from_console": "من هذا الجهاز",
"cw_task_from_pc": "من حاسوب",
"cw_dialog_cancel": "إلغاء",
"cw_home_kind_upload": "رفع",
"cw_home_kind_download": "تنزيل",
"cw_home_kind_copy": "نسخ",
"cw_home_kind_op": "عملية ملفات",
"cw_home_kind_other": "مهمة",
"cw_home_progress_bytes": "{done} من {total}",
"cw_home_progress_files": "{done} من {total} ملفات",
"cw_home_percent": "{n}%",
"cw_home_state_running": "قيد التشغيل",
"cw_home_state_done": "اكتملت",
"cw_home_state_failed": "فشلت",
"cw_home_cancel_task": "إلغاء",
"cw_home_vol_internal": "التخزين الداخلي",
"cw_home_vol_usb": "محرك USB",
"cw_home_vol_usb_ext": "تخزين موسّع عبر USB",
"cw_home_vol_m2": "تخزين M.2",
"cw_home_vol_ext": "تخزين موسّع",
"cw_home_vol_readonly": "للقراءة فقط",
"cw_home_free_of": "{free} متاحة من {total}",
"cw_home_err_offline": "لم يستجب الجهاز. حاول مرة أخرى.",
"cw_home_err_busy": "الجهاز مشغول. حاول مرة أخرى بعد قليل.",
"cw_home_err_gone": "انتهت هذه المهمة بالفعل.",
"cw_home_err_failed": "لم ينجح ذلك ({code}).",
"cw_home_err_load_tasks": "تعذّرت قراءة قائمة المهام.",
"cw_home_err_load_storage": "تعذّرت قراءة المحركات.",
"cw_home_rest_sent": "ينتقل الجهاز إلى وضع الراحة.",
"cw_home_restart_sent": "يُعاد تشغيل الجهاز. أعد تحميل هذه الصفحة عند عودته.",
"cw_home_confirm_cancel_title": "إلغاء هذه المهمة؟",
"cw_home_confirm_cancel_body": "ستتوقف الآن. لا يمكن التراجع عن ذلك.",
"cw_home_confirm_cancel_body_pc": "بدأ حاسوب هذه المهمة. ستتوقف على الحاسوب أيضًا.",
"cw_home_cancel_task_confirm": "إلغاء المهمة",
"cw_home_keep_task": "متابعة",
"cw_home_confirm_close_title": "إغلاق {name}؟",
"cw_home_confirm_close_body": "سيضيع التقدم غير المحفوظ في اللعبة.",
"cw_home_close_game": "إغلاق اللعبة",
"cw_home_confirm_rest_title": "وضع الجهاز في وضع الراحة؟",
"cw_home_confirm_rest_body": "تنقطع هذه الصفحة حتى يستيقظ الجهاز.",
"cw_home_rest": "وضع الراحة",
"cw_home_confirm_restart_title": "إعادة تشغيل الجهاز؟",
"cw_home_confirm_restart_body": "تتوقف الألعاب والمهام الجارية. تنقطع هذه الصفحة حتى يعود الجهاز.",
"cw_home_restart": "إعادة التشغيل",
"cw_home_loading": "جارٍ التحميل…",
"cw_home_no_tasks": "لا توجد مهام حاليًا.",
"cw_home_quick": "إجراءات سريعة",
"cw_home_browse_usb": "تصفح USB",
"cw_home_now_running": "قيد التشغيل الآن",
"cw_home_no_game": "لا توجد لعبة قيد التشغيل.",
"cw_home_tasks": "المهام",
"cw_home_storage": "التخزين",
};
export default ar;
+52
View File
@@ -4046,6 +4046,58 @@ c2c_test_deep_bad: "4 KiB-এর পরীক্ষামূলক পাঠা
"cw_status_connecting": "সংযোগ হচ্ছে…",
"cw_status_offline": "সংযুক্ত নয়",
"cw_hints_label": "কন্ট্রোলারের বোতাম",
"cw_task_from_console": "এই কনসোল থেকে",
"cw_task_from_pc": "PC থেকে",
"cw_dialog_cancel": "বাতিল",
"cw_home_kind_upload": "আপলোড",
"cw_home_kind_download": "ডাউনলোড",
"cw_home_kind_copy": "কপি",
"cw_home_kind_op": "ফাইল অপারেশন",
"cw_home_kind_other": "কাজ",
"cw_home_progress_bytes": "{total}-এর মধ্যে {done}",
"cw_home_progress_files": "{total}টির মধ্যে {done}টি ফাইল",
"cw_home_percent": "{n}%",
"cw_home_state_running": "চলছে",
"cw_home_state_done": "সম্পন্ন",
"cw_home_state_failed": "ব্যর্থ",
"cw_home_cancel_task": "বাতিল",
"cw_home_vol_internal": "অভ্যন্তরীণ স্টোরেজ",
"cw_home_vol_usb": "USB ড্রাইভ",
"cw_home_vol_usb_ext": "USB এক্সটেন্ডেড স্টোরেজ",
"cw_home_vol_m2": "M.2 স্টোরেজ",
"cw_home_vol_ext": "এক্সটেন্ডেড স্টোরেজ",
"cw_home_vol_readonly": "শুধু পড়া যায়",
"cw_home_free_of": "{total}-এর মধ্যে {free} খালি",
"cw_home_err_offline": "কনসোল সাড়া দেয়নি। আবার চেষ্টা করুন।",
"cw_home_err_busy": "কনসোল ব্যস্ত। একটু পরে আবার চেষ্টা করুন।",
"cw_home_err_gone": "সেই কাজটি আগেই শেষ হয়েছে।",
"cw_home_err_failed": "এটি কাজ করেনি ({code})।",
"cw_home_err_load_tasks": "কাজের তালিকা পড়া যায়নি।",
"cw_home_err_load_storage": "ড্রাইভগুলো পড়া যায়নি।",
"cw_home_rest_sent": "কনসোল রেস্ট মোডে যাচ্ছে।",
"cw_home_restart_sent": "কনসোল রিস্টার্ট হচ্ছে। ফিরে এলে এই পেজটি আবার লোড করুন।",
"cw_home_confirm_cancel_title": "এই কাজটি বাতিল করবেন?",
"cw_home_confirm_cancel_body": "এটি এখনই থেমে যাবে। এটি পূর্বাবস্থায় ফেরানো যাবে না।",
"cw_home_confirm_cancel_body_pc": "একটি PC এই কাজটি শুরু করেছিল। এটি PC-তেও থেমে যাবে।",
"cw_home_cancel_task_confirm": "কাজ বাতিল করুন",
"cw_home_keep_task": "চলতে দিন",
"cw_home_confirm_close_title": "{name} বন্ধ করবেন?",
"cw_home_confirm_close_body": "গেমের সেভ না করা অগ্রগতি হারিয়ে যাবে।",
"cw_home_close_game": "গেম বন্ধ করুন",
"cw_home_confirm_rest_title": "কনসোলকে রেস্ট মোডে রাখবেন?",
"cw_home_confirm_rest_body": "কনসোল জেগে না ওঠা পর্যন্ত এই পেজ বিচ্ছিন্ন থাকবে।",
"cw_home_rest": "রেস্ট মোড",
"cw_home_confirm_restart_title": "কনসোল রিস্টার্ট করবেন?",
"cw_home_confirm_restart_body": "চলমান গেম ও কাজ থেমে যাবে। কনসোল ফিরে না আসা পর্যন্ত এই পেজ বিচ্ছিন্ন থাকবে।",
"cw_home_restart": "রিস্টার্ট",
"cw_home_loading": "লোড হচ্ছে…",
"cw_home_no_tasks": "এখন কোনো কাজ নেই।",
"cw_home_quick": "দ্রুত কাজ",
"cw_home_browse_usb": "USB ব্রাউজ করুন",
"cw_home_now_running": "এখন চলছে",
"cw_home_no_game": "কোনো গেম চলছে না।",
"cw_home_tasks": "কাজ",
"cw_home_storage": "স্টোরেজ",
};
export default bn;
+52
View File
@@ -4054,6 +4054,58 @@ c2c_test_deep_bad: "Die 4-KiB-Testsendung ist nicht angekommen: {detail}",
"cw_status_connecting": "Verbinde…",
"cw_status_offline": "Nicht verbunden",
"cw_hints_label": "Controller-Tasten",
"cw_task_from_console": "Von dieser Konsole",
"cw_task_from_pc": "Von einem PC",
"cw_dialog_cancel": "Abbrechen",
"cw_home_kind_upload": "Hochladen",
"cw_home_kind_download": "Herunterladen",
"cw_home_kind_copy": "Kopieren",
"cw_home_kind_op": "Dateivorgang",
"cw_home_kind_other": "Aufgabe",
"cw_home_progress_bytes": "{done} von {total}",
"cw_home_progress_files": "{done} von {total} Dateien",
"cw_home_percent": "{n} %",
"cw_home_state_running": "Läuft",
"cw_home_state_done": "Fertig",
"cw_home_state_failed": "Fehlgeschlagen",
"cw_home_cancel_task": "Abbrechen",
"cw_home_vol_internal": "Interner Speicher",
"cw_home_vol_usb": "USB-Laufwerk",
"cw_home_vol_usb_ext": "USB-Erweiterungsspeicher",
"cw_home_vol_m2": "M.2-Speicher",
"cw_home_vol_ext": "Erweiterungsspeicher",
"cw_home_vol_readonly": "Schreibgeschützt",
"cw_home_free_of": "{free} frei von {total}",
"cw_home_err_offline": "Die Konsole hat nicht geantwortet. Versuche es erneut.",
"cw_home_err_busy": "Die Konsole ist beschäftigt. Versuche es gleich noch einmal.",
"cw_home_err_gone": "Diese Aufgabe ist bereits beendet.",
"cw_home_err_failed": "Das hat nicht funktioniert ({code}).",
"cw_home_err_load_tasks": "Die Aufgabenliste konnte nicht gelesen werden.",
"cw_home_err_load_storage": "Die Laufwerke konnten nicht gelesen werden.",
"cw_home_rest_sent": "Die Konsole wechselt in den Ruhemodus.",
"cw_home_restart_sent": "Die Konsole startet neu. Lade diese Seite neu, sobald sie wieder da ist.",
"cw_home_confirm_cancel_title": "Diese Aufgabe abbrechen?",
"cw_home_confirm_cancel_body": "Sie stoppt sofort. Das lässt sich nicht rückgängig machen.",
"cw_home_confirm_cancel_body_pc": "Ein PC hat diese Aufgabe gestartet. Sie stoppt auch auf dem PC.",
"cw_home_cancel_task_confirm": "Aufgabe abbrechen",
"cw_home_keep_task": "Weiterlaufen lassen",
"cw_home_confirm_close_title": "{name} schließen?",
"cw_home_confirm_close_body": "Nicht gespeicherter Spielfortschritt geht verloren.",
"cw_home_close_game": "Spiel schließen",
"cw_home_confirm_rest_title": "Konsole in den Ruhemodus versetzen?",
"cw_home_confirm_rest_body": "Diese Seite ist getrennt, bis die Konsole aufwacht.",
"cw_home_rest": "Ruhemodus",
"cw_home_confirm_restart_title": "Konsole neu starten?",
"cw_home_confirm_restart_body": "Laufende Spiele und Aufgaben werden beendet. Diese Seite ist getrennt, bis die Konsole wieder da ist.",
"cw_home_restart": "Neu starten",
"cw_home_loading": "Wird geladen…",
"cw_home_no_tasks": "Gerade keine Aufgaben.",
"cw_home_quick": "Schnellaktionen",
"cw_home_browse_usb": "USB durchsuchen",
"cw_home_now_running": "Läuft gerade",
"cw_home_no_game": "Es läuft kein Spiel.",
"cw_home_tasks": "Aufgaben",
"cw_home_storage": "Speicher",
};
export default de;
+52
View File
@@ -4369,6 +4369,58 @@ c2c_test_deep_bad: "The 4 KiB test send did not arrive: {detail}",
"cw_status_connecting": "Connecting…",
"cw_status_offline": "Not connected",
"cw_hints_label": "Controller buttons",
"cw_task_from_console": "From this console",
"cw_task_from_pc": "From a PC",
"cw_dialog_cancel": "Cancel",
"cw_home_kind_upload": "Upload",
"cw_home_kind_download": "Download",
"cw_home_kind_copy": "Copy",
"cw_home_kind_op": "File operation",
"cw_home_kind_other": "Task",
"cw_home_progress_bytes": "{done} of {total}",
"cw_home_progress_files": "{done} of {total} files",
"cw_home_percent": "{n}%",
"cw_home_state_running": "Running",
"cw_home_state_done": "Done",
"cw_home_state_failed": "Failed",
"cw_home_cancel_task": "Cancel",
"cw_home_vol_internal": "Internal storage",
"cw_home_vol_usb": "USB drive",
"cw_home_vol_usb_ext": "USB extended storage",
"cw_home_vol_m2": "M.2 storage",
"cw_home_vol_ext": "Extended storage",
"cw_home_vol_readonly": "Read-only",
"cw_home_free_of": "{free} free of {total}",
"cw_home_err_offline": "The console did not answer. Try again.",
"cw_home_err_busy": "The console is busy. Try again in a moment.",
"cw_home_err_gone": "That task has already ended.",
"cw_home_err_failed": "That did not work ({code}).",
"cw_home_err_load_tasks": "Could not read the task list.",
"cw_home_err_load_storage": "Could not read the drives.",
"cw_home_rest_sent": "The console is going into rest mode.",
"cw_home_restart_sent": "The console is restarting. Reload this page when it is back.",
"cw_home_confirm_cancel_title": "Cancel this task?",
"cw_home_confirm_cancel_body": "It stops now. This cannot be undone.",
"cw_home_confirm_cancel_body_pc": "A PC started this task. It stops on the PC too.",
"cw_home_cancel_task_confirm": "Cancel task",
"cw_home_keep_task": "Keep going",
"cw_home_confirm_close_title": "Close {name}?",
"cw_home_confirm_close_body": "Progress you have not saved in the game is lost.",
"cw_home_close_game": "Close game",
"cw_home_confirm_rest_title": "Put the console in rest mode?",
"cw_home_confirm_rest_body": "This page disconnects until the console wakes up.",
"cw_home_rest": "Rest mode",
"cw_home_confirm_restart_title": "Restart the console?",
"cw_home_confirm_restart_body": "Running games and tasks stop. This page disconnects until the console is back.",
"cw_home_restart": "Restart",
"cw_home_loading": "Loading…",
"cw_home_no_tasks": "No tasks right now.",
"cw_home_quick": "Quick actions",
"cw_home_browse_usb": "Browse USB",
"cw_home_now_running": "Now running",
"cw_home_no_game": "No game is running.",
"cw_home_tasks": "Tasks",
"cw_home_storage": "Storage",
};
export default en;
+52
View File
@@ -3996,6 +3996,58 @@ c2c_test_deep_bad: "El envío de prueba de 4 KiB no llegó: {detail}",
"cw_status_connecting": "Conectando…",
"cw_status_offline": "Sin conexión",
"cw_hints_label": "Botones del mando",
"cw_task_from_console": "Desde esta consola",
"cw_task_from_pc": "Desde un PC",
"cw_dialog_cancel": "Cancelar",
"cw_home_kind_upload": "Subida",
"cw_home_kind_download": "Descarga",
"cw_home_kind_copy": "Copia",
"cw_home_kind_op": "Operación de archivos",
"cw_home_kind_other": "Tarea",
"cw_home_progress_bytes": "{done} de {total}",
"cw_home_progress_files": "{done} de {total} archivos",
"cw_home_percent": "{n} %",
"cw_home_state_running": "En curso",
"cw_home_state_done": "Hecho",
"cw_home_state_failed": "Error",
"cw_home_cancel_task": "Cancelar",
"cw_home_vol_internal": "Almacenamiento interno",
"cw_home_vol_usb": "Unidad USB",
"cw_home_vol_usb_ext": "Almacenamiento ampliado USB",
"cw_home_vol_m2": "Almacenamiento M.2",
"cw_home_vol_ext": "Almacenamiento ampliado",
"cw_home_vol_readonly": "Solo lectura",
"cw_home_free_of": "{free} libres de {total}",
"cw_home_err_offline": "La consola no respondió. Inténtalo de nuevo.",
"cw_home_err_busy": "La consola está ocupada. Inténtalo de nuevo en un momento.",
"cw_home_err_gone": "Esa tarea ya ha terminado.",
"cw_home_err_failed": "No ha funcionado ({code}).",
"cw_home_err_load_tasks": "No se pudo leer la lista de tareas.",
"cw_home_err_load_storage": "No se pudieron leer las unidades.",
"cw_home_rest_sent": "La consola está entrando en modo de reposo.",
"cw_home_restart_sent": "La consola se está reiniciando. Recarga esta página cuando vuelva.",
"cw_home_confirm_cancel_title": "¿Cancelar esta tarea?",
"cw_home_confirm_cancel_body": "Se detiene ahora. No se puede deshacer.",
"cw_home_confirm_cancel_body_pc": "Un PC inició esta tarea. También se detiene en el PC.",
"cw_home_cancel_task_confirm": "Cancelar tarea",
"cw_home_keep_task": "Continuar",
"cw_home_confirm_close_title": "¿Cerrar {name}?",
"cw_home_confirm_close_body": "Se perderá el progreso no guardado del juego.",
"cw_home_close_game": "Cerrar juego",
"cw_home_confirm_rest_title": "¿Poner la consola en modo de reposo?",
"cw_home_confirm_rest_body": "Esta página se desconecta hasta que la consola se active.",
"cw_home_rest": "Modo de reposo",
"cw_home_confirm_restart_title": "¿Reiniciar la consola?",
"cw_home_confirm_restart_body": "Los juegos y tareas en curso se detienen. Esta página se desconecta hasta que la consola vuelva.",
"cw_home_restart": "Reiniciar",
"cw_home_loading": "Cargando…",
"cw_home_no_tasks": "No hay tareas ahora mismo.",
"cw_home_quick": "Acciones rápidas",
"cw_home_browse_usb": "Explorar USB",
"cw_home_now_running": "En ejecución",
"cw_home_no_game": "No hay ningún juego en ejecución.",
"cw_home_tasks": "Tareas",
"cw_home_storage": "Almacenamiento",
};
export default es;
+52
View File
@@ -4347,6 +4347,58 @@ c2c_test_deep_bad: "ارسال آزمایشی ۴ KiB نرسید: {detail}",
"cw_status_connecting": "در حال اتصال…",
"cw_status_offline": "متصل نیست",
"cw_hints_label": "دکمه‌های دسته",
"cw_task_from_console": "از این کنسول",
"cw_task_from_pc": "از رایانه",
"cw_dialog_cancel": "لغو",
"cw_home_kind_upload": "بارگذاری",
"cw_home_kind_download": "بارگیری",
"cw_home_kind_copy": "کپی",
"cw_home_kind_op": "عملیات فایل",
"cw_home_kind_other": "کار",
"cw_home_progress_bytes": "{done} از {total}",
"cw_home_progress_files": "{done} از {total} فایل",
"cw_home_percent": "{n}٪",
"cw_home_state_running": "در حال اجرا",
"cw_home_state_done": "انجام شد",
"cw_home_state_failed": "ناموفق",
"cw_home_cancel_task": "لغو",
"cw_home_vol_internal": "حافظه داخلی",
"cw_home_vol_usb": "درایو USB",
"cw_home_vol_usb_ext": "حافظه توسعه‌یافته USB",
"cw_home_vol_m2": "حافظه M.2",
"cw_home_vol_ext": "حافظه توسعه‌یافته",
"cw_home_vol_readonly": "فقط‌خواندنی",
"cw_home_free_of": "{free} آزاد از {total}",
"cw_home_err_offline": "کنسول پاسخ نداد. دوباره تلاش کنید.",
"cw_home_err_busy": "کنسول مشغول است. کمی بعد دوباره تلاش کنید.",
"cw_home_err_gone": "این کار قبلاً تمام شده است.",
"cw_home_err_failed": "انجام نشد ({code}).",
"cw_home_err_load_tasks": "فهرست کارها خوانده نشد.",
"cw_home_err_load_storage": "درایوها خوانده نشدند.",
"cw_home_rest_sent": "کنسول به حالت استراحت می‌رود.",
"cw_home_restart_sent": "کنسول در حال راه‌اندازی مجدد است. وقتی برگشت این صفحه را دوباره بارگذاری کنید.",
"cw_home_confirm_cancel_title": "این کار لغو شود؟",
"cw_home_confirm_cancel_body": "همین حالا متوقف می‌شود. این کار برگشت‌پذیر نیست.",
"cw_home_confirm_cancel_body_pc": "این کار را یک رایانه شروع کرده است. روی رایانه هم متوقف می‌شود.",
"cw_home_cancel_task_confirm": "لغو کار",
"cw_home_keep_task": "ادامه بده",
"cw_home_confirm_close_title": "{name} بسته شود؟",
"cw_home_confirm_close_body": "پیشرفت ذخیره‌نشده در بازی از بین می‌رود.",
"cw_home_close_game": "بستن بازی",
"cw_home_confirm_rest_title": "کنسول به حالت استراحت برود؟",
"cw_home_confirm_rest_body": "این صفحه تا بیدار شدن کنسول قطع می‌شود.",
"cw_home_rest": "حالت استراحت",
"cw_home_confirm_restart_title": "کنسول دوباره راه‌اندازی شود؟",
"cw_home_confirm_restart_body": "بازی‌ها و کارهای در حال اجرا متوقف می‌شوند. این صفحه تا برگشتن کنسول قطع می‌شود.",
"cw_home_restart": "راه‌اندازی مجدد",
"cw_home_loading": "در حال بارگذاری…",
"cw_home_no_tasks": "در حال حاضر کاری نیست.",
"cw_home_quick": "اقدامات سریع",
"cw_home_browse_usb": "مرور USB",
"cw_home_now_running": "در حال اجرا",
"cw_home_no_game": "هیچ بازی‌ای در حال اجرا نیست.",
"cw_home_tasks": "کارها",
"cw_home_storage": "حافظه",
};
export default fa;
+52
View File
@@ -4064,6 +4064,58 @@ c2c_test_deep_bad: "L’envoi test de 4 Kio n’est pas arrivé : {detail}",
"cw_status_connecting": "Connexion…",
"cw_status_offline": "Non connecté",
"cw_hints_label": "Boutons de la manette",
"cw_task_from_console": "Depuis cette console",
"cw_task_from_pc": "Depuis un PC",
"cw_dialog_cancel": "Annuler",
"cw_home_kind_upload": "Envoi",
"cw_home_kind_download": "Téléchargement",
"cw_home_kind_copy": "Copie",
"cw_home_kind_op": "Opération sur les fichiers",
"cw_home_kind_other": "Tâche",
"cw_home_progress_bytes": "{done} sur {total}",
"cw_home_progress_files": "{done} fichiers sur {total}",
"cw_home_percent": "{n} %",
"cw_home_state_running": "En cours",
"cw_home_state_done": "Terminé",
"cw_home_state_failed": "Échec",
"cw_home_cancel_task": "Annuler",
"cw_home_vol_internal": "Stockage interne",
"cw_home_vol_usb": "Clé USB",
"cw_home_vol_usb_ext": "Stockage étendu USB",
"cw_home_vol_m2": "Stockage M.2",
"cw_home_vol_ext": "Stockage étendu",
"cw_home_vol_readonly": "Lecture seule",
"cw_home_free_of": "{free} libres sur {total}",
"cw_home_err_offline": "La console n'a pas répondu. Réessayez.",
"cw_home_err_busy": "La console est occupée. Réessayez dans un instant.",
"cw_home_err_gone": "Cette tâche est déjà terminée.",
"cw_home_err_failed": "Cela n'a pas fonctionné ({code}).",
"cw_home_err_load_tasks": "Impossible de lire la liste des tâches.",
"cw_home_err_load_storage": "Impossible de lire les disques.",
"cw_home_rest_sent": "La console passe en mode repos.",
"cw_home_restart_sent": "La console redémarre. Rechargez cette page quand elle sera revenue.",
"cw_home_confirm_cancel_title": "Annuler cette tâche ?",
"cw_home_confirm_cancel_body": "Elle s'arrête maintenant. Cette action est irréversible.",
"cw_home_confirm_cancel_body_pc": "Un PC a lancé cette tâche. Elle s'arrête aussi sur le PC.",
"cw_home_cancel_task_confirm": "Annuler la tâche",
"cw_home_keep_task": "Continuer",
"cw_home_confirm_close_title": "Fermer {name} ?",
"cw_home_confirm_close_body": "La progression non sauvegardée du jeu sera perdue.",
"cw_home_close_game": "Fermer le jeu",
"cw_home_confirm_rest_title": "Mettre la console en mode repos ?",
"cw_home_confirm_rest_body": "Cette page est déconnectée jusqu'au réveil de la console.",
"cw_home_rest": "Mode repos",
"cw_home_confirm_restart_title": "Redémarrer la console ?",
"cw_home_confirm_restart_body": "Les jeux et tâches en cours s'arrêtent. Cette page est déconnectée jusqu'au retour de la console.",
"cw_home_restart": "Redémarrer",
"cw_home_loading": "Chargement…",
"cw_home_no_tasks": "Aucune tâche pour le moment.",
"cw_home_quick": "Actions rapides",
"cw_home_browse_usb": "Parcourir l'USB",
"cw_home_now_running": "En cours d'exécution",
"cw_home_no_game": "Aucun jeu en cours.",
"cw_home_tasks": "Tâches",
"cw_home_storage": "Stockage",
};
export default fr;
+52
View File
@@ -4062,6 +4062,58 @@ c2c_test_deep_bad: "4 KiB का परीक्षण नहीं पहुँ
"cw_status_connecting": "कनेक्ट हो रहा है…",
"cw_status_offline": "कनेक्ट नहीं है",
"cw_hints_label": "कंट्रोलर के बटन",
"cw_task_from_console": "इस कंसोल से",
"cw_task_from_pc": "PC से",
"cw_dialog_cancel": "रद्द करें",
"cw_home_kind_upload": "अपलोड",
"cw_home_kind_download": "डाउनलोड",
"cw_home_kind_copy": "कॉपी",
"cw_home_kind_op": "फ़ाइल कार्रवाई",
"cw_home_kind_other": "कार्य",
"cw_home_progress_bytes": "{total} में से {done}",
"cw_home_progress_files": "{total} में से {done} फ़ाइलें",
"cw_home_percent": "{n}%",
"cw_home_state_running": "चल रहा है",
"cw_home_state_done": "पूरा हुआ",
"cw_home_state_failed": "विफल",
"cw_home_cancel_task": "रद्द करें",
"cw_home_vol_internal": "आंतरिक स्टोरेज",
"cw_home_vol_usb": "USB ड्राइव",
"cw_home_vol_usb_ext": "USB विस्तारित स्टोरेज",
"cw_home_vol_m2": "M.2 स्टोरेज",
"cw_home_vol_ext": "विस्तारित स्टोरेज",
"cw_home_vol_readonly": "केवल पढ़ने योग्य",
"cw_home_free_of": "{total} में से {free} खाली",
"cw_home_err_offline": "कंसोल ने जवाब नहीं दिया। फिर से कोशिश करें।",
"cw_home_err_busy": "कंसोल व्यस्त है। थोड़ी देर बाद फिर से कोशिश करें।",
"cw_home_err_gone": "वह कार्य पहले ही खत्म हो चुका है।",
"cw_home_err_failed": "यह काम नहीं किया ({code})।",
"cw_home_err_load_tasks": "कार्य सूची पढ़ी नहीं जा सकी।",
"cw_home_err_load_storage": "ड्राइव पढ़ी नहीं जा सकीं।",
"cw_home_rest_sent": "कंसोल रेस्ट मोड में जा रहा है।",
"cw_home_restart_sent": "कंसोल फिर से शुरू हो रहा है। वापस आने पर यह पेज फिर से लोड करें।",
"cw_home_confirm_cancel_title": "यह कार्य रद्द करें?",
"cw_home_confirm_cancel_body": "यह अभी रुक जाएगा। इसे पूर्ववत नहीं किया जा सकता।",
"cw_home_confirm_cancel_body_pc": "यह कार्य एक PC ने शुरू किया था। यह PC पर भी रुक जाएगा।",
"cw_home_cancel_task_confirm": "कार्य रद्द करें",
"cw_home_keep_task": "चलने दें",
"cw_home_confirm_close_title": "{name} बंद करें?",
"cw_home_confirm_close_body": "गेम की बिना सहेजी प्रगति खो जाएगी।",
"cw_home_close_game": "गेम बंद करें",
"cw_home_confirm_rest_title": "कंसोल को रेस्ट मोड में डालें?",
"cw_home_confirm_rest_body": "कंसोल के जागने तक यह पेज डिस्कनेक्ट रहेगा।",
"cw_home_rest": "रेस्ट मोड",
"cw_home_confirm_restart_title": "कंसोल फिर से शुरू करें?",
"cw_home_confirm_restart_body": "चल रहे गेम और कार्य रुक जाएंगे। कंसोल के वापस आने तक यह पेज डिस्कनेक्ट रहेगा।",
"cw_home_restart": "फिर से शुरू करें",
"cw_home_loading": "लोड हो रहा है…",
"cw_home_no_tasks": "अभी कोई कार्य नहीं है।",
"cw_home_quick": "त्वरित कार्रवाइयाँ",
"cw_home_browse_usb": "USB ब्राउज़ करें",
"cw_home_now_running": "अभी चल रहा है",
"cw_home_no_game": "कोई गेम नहीं चल रहा है।",
"cw_home_tasks": "कार्य",
"cw_home_storage": "स्टोरेज",
};
export default hi;
+52
View File
@@ -4361,6 +4361,58 @@ c2c_test_deep_bad: "A 4 KiB-os tesztküldés nem érkezett meg: {detail}",
"cw_status_connecting": "Kapcsolódás…",
"cw_status_offline": "Nincs kapcsolat",
"cw_hints_label": "Kontroller gombjai",
"cw_task_from_console": "Erről a konzolról",
"cw_task_from_pc": "Egy PC-ről",
"cw_dialog_cancel": "Mégse",
"cw_home_kind_upload": "Feltöltés",
"cw_home_kind_download": "Letöltés",
"cw_home_kind_copy": "Másolás",
"cw_home_kind_op": "Fájlművelet",
"cw_home_kind_other": "Feladat",
"cw_home_progress_bytes": "{done} / {total}",
"cw_home_progress_files": "{done} / {total} fájl",
"cw_home_percent": "{n}%",
"cw_home_state_running": "Folyamatban",
"cw_home_state_done": "Kész",
"cw_home_state_failed": "Sikertelen",
"cw_home_cancel_task": "Megszakítás",
"cw_home_vol_internal": "Belső tárhely",
"cw_home_vol_usb": "USB-meghajtó",
"cw_home_vol_usb_ext": "USB bővített tárhely",
"cw_home_vol_m2": "M.2 tárhely",
"cw_home_vol_ext": "Bővített tárhely",
"cw_home_vol_readonly": "Csak olvasható",
"cw_home_free_of": "{free} szabad / {total}",
"cw_home_err_offline": "A konzol nem válaszolt. Próbáld újra.",
"cw_home_err_busy": "A konzol foglalt. Próbáld újra egy pillanat múlva.",
"cw_home_err_gone": "Ez a feladat már befejeződött.",
"cw_home_err_failed": "Ez nem sikerült ({code}).",
"cw_home_err_load_tasks": "Nem sikerült beolvasni a feladatlistát.",
"cw_home_err_load_storage": "Nem sikerült beolvasni a meghajtókat.",
"cw_home_rest_sent": "A konzol nyugalmi módba lép.",
"cw_home_restart_sent": "A konzol újraindul. Töltsd újra ezt az oldalt, ha újra elérhető.",
"cw_home_confirm_cancel_title": "Megszakítod ezt a feladatot?",
"cw_home_confirm_cancel_body": "Azonnal leáll. Ez nem vonható vissza.",
"cw_home_confirm_cancel_body_pc": "Ezt a feladatot egy PC indította. A PC-n is leáll.",
"cw_home_cancel_task_confirm": "Feladat megszakítása",
"cw_home_keep_task": "Folytatás",
"cw_home_confirm_close_title": "Bezárod: {name}?",
"cw_home_confirm_close_body": "A játékban el nem mentett haladás elvész.",
"cw_home_close_game": "Játék bezárása",
"cw_home_confirm_rest_title": "Nyugalmi módba teszed a konzolt?",
"cw_home_confirm_rest_body": "Ez az oldal a konzol felébredéséig nem kapcsolódik.",
"cw_home_rest": "Nyugalmi mód",
"cw_home_confirm_restart_title": "Újraindítod a konzolt?",
"cw_home_confirm_restart_body": "A futó játékok és feladatok leállnak. Ez az oldal a konzol visszatéréséig nem kapcsolódik.",
"cw_home_restart": "Újraindítás",
"cw_home_loading": "Betöltés…",
"cw_home_no_tasks": "Most nincs feladat.",
"cw_home_quick": "Gyorsműveletek",
"cw_home_browse_usb": "USB tallózása",
"cw_home_now_running": "Most fut",
"cw_home_no_game": "Nem fut játék.",
"cw_home_tasks": "Feladatok",
"cw_home_storage": "Tárhely",
};
export default hu;
+52
View File
@@ -4055,6 +4055,58 @@ c2c_test_deep_bad: "Kiriman uji 4 KiB tidak tiba: {detail}",
"cw_status_connecting": "Menghubungkan…",
"cw_status_offline": "Tidak terhubung",
"cw_hints_label": "Tombol kontroler",
"cw_task_from_console": "Dari konsol ini",
"cw_task_from_pc": "Dari PC",
"cw_dialog_cancel": "Batal",
"cw_home_kind_upload": "Unggah",
"cw_home_kind_download": "Unduh",
"cw_home_kind_copy": "Salin",
"cw_home_kind_op": "Operasi file",
"cw_home_kind_other": "Tugas",
"cw_home_progress_bytes": "{done} dari {total}",
"cw_home_progress_files": "{done} dari {total} file",
"cw_home_percent": "{n}%",
"cw_home_state_running": "Berjalan",
"cw_home_state_done": "Selesai",
"cw_home_state_failed": "Gagal",
"cw_home_cancel_task": "Batalkan",
"cw_home_vol_internal": "Penyimpanan internal",
"cw_home_vol_usb": "Drive USB",
"cw_home_vol_usb_ext": "Penyimpanan tambahan USB",
"cw_home_vol_m2": "Penyimpanan M.2",
"cw_home_vol_ext": "Penyimpanan tambahan",
"cw_home_vol_readonly": "Hanya baca",
"cw_home_free_of": "{free} kosong dari {total}",
"cw_home_err_offline": "Konsol tidak menjawab. Coba lagi.",
"cw_home_err_busy": "Konsol sedang sibuk. Coba lagi sebentar lagi.",
"cw_home_err_gone": "Tugas itu sudah berakhir.",
"cw_home_err_failed": "Itu tidak berhasil ({code}).",
"cw_home_err_load_tasks": "Tidak dapat membaca daftar tugas.",
"cw_home_err_load_storage": "Tidak dapat membaca drive.",
"cw_home_rest_sent": "Konsol masuk ke mode istirahat.",
"cw_home_restart_sent": "Konsol sedang memulai ulang. Muat ulang halaman ini setelah konsol kembali.",
"cw_home_confirm_cancel_title": "Batalkan tugas ini?",
"cw_home_confirm_cancel_body": "Tugas berhenti sekarang. Ini tidak bisa dibatalkan.",
"cw_home_confirm_cancel_body_pc": "Tugas ini dimulai dari PC. Tugas juga berhenti di PC.",
"cw_home_cancel_task_confirm": "Batalkan tugas",
"cw_home_keep_task": "Lanjutkan",
"cw_home_confirm_close_title": "Tutup {name}?",
"cw_home_confirm_close_body": "Progres game yang belum disimpan akan hilang.",
"cw_home_close_game": "Tutup game",
"cw_home_confirm_rest_title": "Masukkan konsol ke mode istirahat?",
"cw_home_confirm_rest_body": "Halaman ini terputus sampai konsol bangun.",
"cw_home_rest": "Mode istirahat",
"cw_home_confirm_restart_title": "Mulai ulang konsol?",
"cw_home_confirm_restart_body": "Game dan tugas yang berjalan akan berhenti. Halaman ini terputus sampai konsol kembali.",
"cw_home_restart": "Mulai ulang",
"cw_home_loading": "Memuat…",
"cw_home_no_tasks": "Tidak ada tugas saat ini.",
"cw_home_quick": "Tindakan cepat",
"cw_home_browse_usb": "Jelajahi USB",
"cw_home_now_running": "Sedang berjalan",
"cw_home_no_game": "Tidak ada game yang berjalan.",
"cw_home_tasks": "Tugas",
"cw_home_storage": "Penyimpanan",
};
export default id;
+52
View File
@@ -3996,6 +3996,58 @@ c2c_test_deep_bad: "L’invio di prova di 4 KiB non è arrivato: {detail}",
"cw_status_connecting": "Connessione…",
"cw_status_offline": "Non connesso",
"cw_hints_label": "Tasti del controller",
"cw_task_from_console": "Da questa console",
"cw_task_from_pc": "Da un PC",
"cw_dialog_cancel": "Annulla",
"cw_home_kind_upload": "Caricamento",
"cw_home_kind_download": "Download",
"cw_home_kind_copy": "Copia",
"cw_home_kind_op": "Operazione sui file",
"cw_home_kind_other": "Attività",
"cw_home_progress_bytes": "{done} di {total}",
"cw_home_progress_files": "{done} di {total} file",
"cw_home_percent": "{n}%",
"cw_home_state_running": "In corso",
"cw_home_state_done": "Completata",
"cw_home_state_failed": "Non riuscita",
"cw_home_cancel_task": "Annulla",
"cw_home_vol_internal": "Memoria interna",
"cw_home_vol_usb": "Unità USB",
"cw_home_vol_usb_ext": "Memoria estesa USB",
"cw_home_vol_m2": "Memoria M.2",
"cw_home_vol_ext": "Memoria estesa",
"cw_home_vol_readonly": "Sola lettura",
"cw_home_free_of": "{free} liberi su {total}",
"cw_home_err_offline": "La console non ha risposto. Riprova.",
"cw_home_err_busy": "La console è occupata. Riprova tra un momento.",
"cw_home_err_gone": "Quell'attività è già terminata.",
"cw_home_err_failed": "Non ha funzionato ({code}).",
"cw_home_err_load_tasks": "Impossibile leggere l'elenco delle attività.",
"cw_home_err_load_storage": "Impossibile leggere le unità.",
"cw_home_rest_sent": "La console sta entrando in modalità riposo.",
"cw_home_restart_sent": "La console si sta riavviando. Ricarica questa pagina quando torna disponibile.",
"cw_home_confirm_cancel_title": "Annullare questa attività?",
"cw_home_confirm_cancel_body": "Si interrompe subito. L'operazione non può essere annullata.",
"cw_home_confirm_cancel_body_pc": "Questa attività è stata avviata da un PC. Si interrompe anche sul PC.",
"cw_home_cancel_task_confirm": "Annulla attività",
"cw_home_keep_task": "Continua",
"cw_home_confirm_close_title": "Chiudere {name}?",
"cw_home_confirm_close_body": "I progressi di gioco non salvati andranno persi.",
"cw_home_close_game": "Chiudi gioco",
"cw_home_confirm_rest_title": "Mettere la console in modalità riposo?",
"cw_home_confirm_rest_body": "Questa pagina resta disconnessa finché la console non si riattiva.",
"cw_home_rest": "Modalità riposo",
"cw_home_confirm_restart_title": "Riavviare la console?",
"cw_home_confirm_restart_body": "Giochi e attività in corso si interrompono. Questa pagina resta disconnessa finché la console non torna disponibile.",
"cw_home_restart": "Riavvia",
"cw_home_loading": "Caricamento…",
"cw_home_no_tasks": "Nessuna attività al momento.",
"cw_home_quick": "Azioni rapide",
"cw_home_browse_usb": "Sfoglia USB",
"cw_home_now_running": "In esecuzione",
"cw_home_no_game": "Nessun gioco in esecuzione.",
"cw_home_tasks": "Attività",
"cw_home_storage": "Archiviazione",
};
export default it;
+52
View File
@@ -4067,6 +4067,58 @@ c2c_test_deep_bad: "4 KiB のテスト送信が届きませんでした: {detail
"cw_status_connecting": "接続中…",
"cw_status_offline": "未接続",
"cw_hints_label": "コントローラーのボタン",
"cw_task_from_console": "このコンソールから",
"cw_task_from_pc": "PCから",
"cw_dialog_cancel": "キャンセル",
"cw_home_kind_upload": "アップロード",
"cw_home_kind_download": "ダウンロード",
"cw_home_kind_copy": "コピー",
"cw_home_kind_op": "ファイル操作",
"cw_home_kind_other": "タスク",
"cw_home_progress_bytes": "{done} / {total}",
"cw_home_progress_files": "{done} / {total} ファイル",
"cw_home_percent": "{n}%",
"cw_home_state_running": "実行中",
"cw_home_state_done": "完了",
"cw_home_state_failed": "失敗",
"cw_home_cancel_task": "キャンセル",
"cw_home_vol_internal": "本体ストレージ",
"cw_home_vol_usb": "USBドライブ",
"cw_home_vol_usb_ext": "USB拡張ストレージ",
"cw_home_vol_m2": "M.2ストレージ",
"cw_home_vol_ext": "拡張ストレージ",
"cw_home_vol_readonly": "読み取り専用",
"cw_home_free_of": "空き {free} / {total}",
"cw_home_err_offline": "コンソールが応答しませんでした。もう一度お試しください。",
"cw_home_err_busy": "コンソールが処理中です。少ししてからもう一度お試しください。",
"cw_home_err_gone": "そのタスクはすでに終了しています。",
"cw_home_err_failed": "うまくいきませんでした({code})。",
"cw_home_err_load_tasks": "タスク一覧を読み込めませんでした。",
"cw_home_err_load_storage": "ドライブを読み込めませんでした。",
"cw_home_rest_sent": "コンソールがレストモードに入ります。",
"cw_home_restart_sent": "コンソールを再起動しています。復帰したらこのページを再読み込みしてください。",
"cw_home_confirm_cancel_title": "このタスクをキャンセルしますか?",
"cw_home_confirm_cancel_body": "すぐに停止します。元に戻せません。",
"cw_home_confirm_cancel_body_pc": "このタスクはPCから開始されました。PC側でも停止します。",
"cw_home_cancel_task_confirm": "タスクをキャンセル",
"cw_home_keep_task": "続ける",
"cw_home_confirm_close_title": "{name} を終了しますか?",
"cw_home_confirm_close_body": "ゲーム内の保存していない進行状況は失われます。",
"cw_home_close_game": "ゲームを終了",
"cw_home_confirm_rest_title": "コンソールをレストモードにしますか?",
"cw_home_confirm_rest_body": "コンソールが復帰するまで、このページは切断されます。",
"cw_home_rest": "レストモード",
"cw_home_confirm_restart_title": "コンソールを再起動しますか?",
"cw_home_confirm_restart_body": "実行中のゲームとタスクは停止します。コンソールが戻るまで、このページは切断されます。",
"cw_home_restart": "再起動",
"cw_home_loading": "読み込み中…",
"cw_home_no_tasks": "現在タスクはありません。",
"cw_home_quick": "クイックアクション",
"cw_home_browse_usb": "USBを参照",
"cw_home_now_running": "実行中",
"cw_home_no_game": "実行中のゲームはありません。",
"cw_home_tasks": "タスク",
"cw_home_storage": "ストレージ",
};
export default ja;
+52
View File
@@ -4065,6 +4065,58 @@ c2c_test_deep_bad: "4 KiB 테스트 전송이 도착하지 않았습니다: {det
"cw_status_connecting": "연결 중…",
"cw_status_offline": "연결되지 않음",
"cw_hints_label": "컨트롤러 버튼",
"cw_task_from_console": "이 콘솔에서",
"cw_task_from_pc": "PC에서",
"cw_dialog_cancel": "취소",
"cw_home_kind_upload": "업로드",
"cw_home_kind_download": "다운로드",
"cw_home_kind_copy": "복사",
"cw_home_kind_op": "파일 작업",
"cw_home_kind_other": "작업",
"cw_home_progress_bytes": "{done} / {total}",
"cw_home_progress_files": "{done} / {total}개 파일",
"cw_home_percent": "{n}%",
"cw_home_state_running": "진행 중",
"cw_home_state_done": "완료",
"cw_home_state_failed": "실패",
"cw_home_cancel_task": "취소",
"cw_home_vol_internal": "내부 저장소",
"cw_home_vol_usb": "USB 드라이브",
"cw_home_vol_usb_ext": "USB 확장 저장소",
"cw_home_vol_m2": "M.2 저장소",
"cw_home_vol_ext": "확장 저장소",
"cw_home_vol_readonly": "읽기 전용",
"cw_home_free_of": "{total} 중 {free} 남음",
"cw_home_err_offline": "콘솔이 응답하지 않았습니다. 다시 시도하세요.",
"cw_home_err_busy": "콘솔이 사용 중입니다. 잠시 후 다시 시도하세요.",
"cw_home_err_gone": "이 작업은 이미 끝났습니다.",
"cw_home_err_failed": "실패했습니다({code}).",
"cw_home_err_load_tasks": "작업 목록을 읽을 수 없습니다.",
"cw_home_err_load_storage": "드라이브를 읽을 수 없습니다.",
"cw_home_rest_sent": "콘솔이 휴식 모드로 전환됩니다.",
"cw_home_restart_sent": "콘솔을 다시 시작하는 중입니다. 다시 켜지면 이 페이지를 새로 고치세요.",
"cw_home_confirm_cancel_title": "이 작업을 취소할까요?",
"cw_home_confirm_cancel_body": "지금 바로 멈춥니다. 되돌릴 수 없습니다.",
"cw_home_confirm_cancel_body_pc": "PC에서 시작한 작업입니다. PC에서도 멈춥니다.",
"cw_home_cancel_task_confirm": "작업 취소",
"cw_home_keep_task": "계속 진행",
"cw_home_confirm_close_title": "{name}을(를) 닫을까요?",
"cw_home_confirm_close_body": "게임에서 저장하지 않은 진행 상황은 사라집니다.",
"cw_home_close_game": "게임 닫기",
"cw_home_confirm_rest_title": "콘솔을 휴식 모드로 전환할까요?",
"cw_home_confirm_rest_body": "콘솔이 깨어날 때까지 이 페이지는 연결이 끊깁니다.",
"cw_home_rest": "휴식 모드",
"cw_home_confirm_restart_title": "콘솔을 다시 시작할까요?",
"cw_home_confirm_restart_body": "실행 중인 게임과 작업이 멈춥니다. 콘솔이 다시 켜질 때까지 이 페이지는 연결이 끊깁니다.",
"cw_home_restart": "다시 시작",
"cw_home_loading": "불러오는 중…",
"cw_home_no_tasks": "현재 작업이 없습니다.",
"cw_home_quick": "빠른 작업",
"cw_home_browse_usb": "USB 탐색",
"cw_home_now_running": "실행 중",
"cw_home_no_game": "실행 중인 게임이 없습니다.",
"cw_home_tasks": "작업",
"cw_home_storage": "저장소",
};
export default ko;
+52
View File
@@ -4301,6 +4301,58 @@ c2c_test_deep_bad: "Testowa wysyłka 4 KiB nie dotarła: {detail}",
"cw_status_connecting": "Łączenie…",
"cw_status_offline": "Brak połączenia",
"cw_hints_label": "Przyciski kontrolera",
"cw_task_from_console": "Z tej konsoli",
"cw_task_from_pc": "Z komputera",
"cw_dialog_cancel": "Anuluj",
"cw_home_kind_upload": "Wysyłanie",
"cw_home_kind_download": "Pobieranie",
"cw_home_kind_copy": "Kopiowanie",
"cw_home_kind_op": "Operacja na plikach",
"cw_home_kind_other": "Zadanie",
"cw_home_progress_bytes": "{done} z {total}",
"cw_home_progress_files": "{done} z {total} plików",
"cw_home_percent": "{n}%",
"cw_home_state_running": "W toku",
"cw_home_state_done": "Gotowe",
"cw_home_state_failed": "Niepowodzenie",
"cw_home_cancel_task": "Anuluj",
"cw_home_vol_internal": "Pamięć wewnętrzna",
"cw_home_vol_usb": "Dysk USB",
"cw_home_vol_usb_ext": "Rozszerzona pamięć USB",
"cw_home_vol_m2": "Pamięć M.2",
"cw_home_vol_ext": "Rozszerzona pamięć",
"cw_home_vol_readonly": "Tylko do odczytu",
"cw_home_free_of": "{free} wolne z {total}",
"cw_home_err_offline": "Konsola nie odpowiedziała. Spróbuj ponownie.",
"cw_home_err_busy": "Konsola jest zajęta. Spróbuj ponownie za chwilę.",
"cw_home_err_gone": "To zadanie już się zakończyło.",
"cw_home_err_failed": "Nie udało się ({code}).",
"cw_home_err_load_tasks": "Nie udało się odczytać listy zadań.",
"cw_home_err_load_storage": "Nie udało się odczytać dysków.",
"cw_home_rest_sent": "Konsola przechodzi w tryb odpoczynku.",
"cw_home_restart_sent": "Konsola uruchamia się ponownie. Przeładuj tę stronę, gdy wróci.",
"cw_home_confirm_cancel_title": "Anulować to zadanie?",
"cw_home_confirm_cancel_body": "Zatrzyma się teraz. Tego nie można cofnąć.",
"cw_home_confirm_cancel_body_pc": "To zadanie uruchomił komputer. Zatrzyma się także na komputerze.",
"cw_home_cancel_task_confirm": "Anuluj zadanie",
"cw_home_keep_task": "Kontynuuj",
"cw_home_confirm_close_title": "Zamknąć {name}?",
"cw_home_confirm_close_body": "Niezapisane postępy w grze zostaną utracone.",
"cw_home_close_game": "Zamknij grę",
"cw_home_confirm_rest_title": "Przełączyć konsolę w tryb odpoczynku?",
"cw_home_confirm_rest_body": "Ta strona będzie rozłączona, dopóki konsola się nie wybudzi.",
"cw_home_rest": "Tryb odpoczynku",
"cw_home_confirm_restart_title": "Uruchomić konsolę ponownie?",
"cw_home_confirm_restart_body": "Uruchomione gry i zadania zostaną zatrzymane. Ta strona będzie rozłączona, dopóki konsola nie wróci.",
"cw_home_restart": "Uruchom ponownie",
"cw_home_loading": "Wczytywanie…",
"cw_home_no_tasks": "Brak zadań.",
"cw_home_quick": "Szybkie akcje",
"cw_home_browse_usb": "Przeglądaj USB",
"cw_home_now_running": "Teraz uruchomione",
"cw_home_no_game": "Żadna gra nie jest uruchomiona.",
"cw_home_tasks": "Zadania",
"cw_home_storage": "Pamięć",
};
export default pl;
+52
View File
@@ -4063,6 +4063,58 @@ c2c_test_deep_bad: "O envio de teste de 4 KiB não chegou: {detail}",
"cw_status_connecting": "Conectando…",
"cw_status_offline": "Não conectado",
"cw_hints_label": "Botões do controle",
"cw_task_from_console": "Deste console",
"cw_task_from_pc": "De um PC",
"cw_dialog_cancel": "Cancelar",
"cw_home_kind_upload": "Envio",
"cw_home_kind_download": "Download",
"cw_home_kind_copy": "Cópia",
"cw_home_kind_op": "Operação de arquivos",
"cw_home_kind_other": "Tarefa",
"cw_home_progress_bytes": "{done} de {total}",
"cw_home_progress_files": "{done} de {total} arquivos",
"cw_home_percent": "{n}%",
"cw_home_state_running": "Em andamento",
"cw_home_state_done": "Concluída",
"cw_home_state_failed": "Falhou",
"cw_home_cancel_task": "Cancelar",
"cw_home_vol_internal": "Armazenamento interno",
"cw_home_vol_usb": "Unidade USB",
"cw_home_vol_usb_ext": "Armazenamento estendido USB",
"cw_home_vol_m2": "Armazenamento M.2",
"cw_home_vol_ext": "Armazenamento estendido",
"cw_home_vol_readonly": "Somente leitura",
"cw_home_free_of": "{free} livres de {total}",
"cw_home_err_offline": "O console não respondeu. Tente novamente.",
"cw_home_err_busy": "O console está ocupado. Tente novamente em instantes.",
"cw_home_err_gone": "Essa tarefa já terminou.",
"cw_home_err_failed": "Não deu certo ({code}).",
"cw_home_err_load_tasks": "Não foi possível ler a lista de tarefas.",
"cw_home_err_load_storage": "Não foi possível ler as unidades.",
"cw_home_rest_sent": "O console está entrando no modo de descanso.",
"cw_home_restart_sent": "O console está reiniciando. Recarregue esta página quando ele voltar.",
"cw_home_confirm_cancel_title": "Cancelar esta tarefa?",
"cw_home_confirm_cancel_body": "Ela para agora. Isso não pode ser desfeito.",
"cw_home_confirm_cancel_body_pc": "Um PC iniciou esta tarefa. Ela também para no PC.",
"cw_home_cancel_task_confirm": "Cancelar tarefa",
"cw_home_keep_task": "Continuar",
"cw_home_confirm_close_title": "Fechar {name}?",
"cw_home_confirm_close_body": "O progresso não salvo no jogo será perdido.",
"cw_home_close_game": "Fechar jogo",
"cw_home_confirm_rest_title": "Colocar o console no modo de descanso?",
"cw_home_confirm_rest_body": "Esta página fica desconectada até o console despertar.",
"cw_home_rest": "Modo de descanso",
"cw_home_confirm_restart_title": "Reiniciar o console?",
"cw_home_confirm_restart_body": "Jogos e tarefas em andamento param. Esta página fica desconectada até o console voltar.",
"cw_home_restart": "Reiniciar",
"cw_home_loading": "Carregando…",
"cw_home_no_tasks": "Nenhuma tarefa no momento.",
"cw_home_quick": "Ações rápidas",
"cw_home_browse_usb": "Navegar no USB",
"cw_home_now_running": "Em execução",
"cw_home_no_game": "Nenhum jogo em execução.",
"cw_home_tasks": "Tarefas",
"cw_home_storage": "Armazenamento",
};
export default pt_BR;
+52
View File
@@ -3996,6 +3996,58 @@ c2c_test_deep_bad: "Тестовая отправка 4 КиБ не дошла:
"cw_status_connecting": "Подключение…",
"cw_status_offline": "Нет подключения",
"cw_hints_label": "Кнопки геймпада",
"cw_task_from_console": "С этой консоли",
"cw_task_from_pc": "С ПК",
"cw_dialog_cancel": "Отмена",
"cw_home_kind_upload": "Загрузка на консоль",
"cw_home_kind_download": "Скачивание",
"cw_home_kind_copy": "Копирование",
"cw_home_kind_op": "Операция с файлами",
"cw_home_kind_other": "Задача",
"cw_home_progress_bytes": "{done} из {total}",
"cw_home_progress_files": "{done} из {total} файлов",
"cw_home_percent": "{n}%",
"cw_home_state_running": "Выполняется",
"cw_home_state_done": "Готово",
"cw_home_state_failed": "Ошибка",
"cw_home_cancel_task": "Отменить",
"cw_home_vol_internal": "Внутреннее хранилище",
"cw_home_vol_usb": "USB-накопитель",
"cw_home_vol_usb_ext": "Расширенное хранилище USB",
"cw_home_vol_m2": "Хранилище M.2",
"cw_home_vol_ext": "Расширенное хранилище",
"cw_home_vol_readonly": "Только чтение",
"cw_home_free_of": "Свободно {free} из {total}",
"cw_home_err_offline": "Консоль не ответила. Попробуйте ещё раз.",
"cw_home_err_busy": "Консоль занята. Попробуйте чуть позже.",
"cw_home_err_gone": "Эта задача уже завершилась.",
"cw_home_err_failed": "Не получилось ({code}).",
"cw_home_err_load_tasks": "Не удалось прочитать список задач.",
"cw_home_err_load_storage": "Не удалось прочитать накопители.",
"cw_home_rest_sent": "Консоль переходит в режим покоя.",
"cw_home_restart_sent": "Консоль перезагружается. Обновите эту страницу, когда она снова будет доступна.",
"cw_home_confirm_cancel_title": "Отменить эту задачу?",
"cw_home_confirm_cancel_body": "Она остановится сейчас. Это нельзя отменить.",
"cw_home_confirm_cancel_body_pc": "Эту задачу запустил ПК. На ПК она тоже остановится.",
"cw_home_cancel_task_confirm": "Отменить задачу",
"cw_home_keep_task": "Продолжить",
"cw_home_confirm_close_title": "Закрыть {name}?",
"cw_home_confirm_close_body": "Несохранённый прогресс в игре будет потерян.",
"cw_home_close_game": "Закрыть игру",
"cw_home_confirm_rest_title": "Перевести консоль в режим покоя?",
"cw_home_confirm_rest_body": "Страница будет отключена, пока консоль не проснётся.",
"cw_home_rest": "Режим покоя",
"cw_home_confirm_restart_title": "Перезагрузить консоль?",
"cw_home_confirm_restart_body": "Запущенные игры и задачи остановятся. Страница будет отключена, пока консоль не вернётся.",
"cw_home_restart": "Перезагрузить",
"cw_home_loading": "Загрузка…",
"cw_home_no_tasks": "Сейчас задач нет.",
"cw_home_quick": "Быстрые действия",
"cw_home_browse_usb": "Открыть USB",
"cw_home_now_running": "Сейчас запущено",
"cw_home_no_game": "Игра не запущена.",
"cw_home_tasks": "Задачи",
"cw_home_storage": "Хранилище",
};
export default ru;
+52
View File
@@ -4055,6 +4055,58 @@ c2c_test_deep_bad: "การส่งทดสอบ 4 KiB ไปไม่ถ
"cw_status_connecting": "กำลังเชื่อมต่อ…",
"cw_status_offline": "ไม่ได้เชื่อมต่อ",
"cw_hints_label": "ปุ่มคอนโทรลเลอร์",
"cw_task_from_console": "จากคอนโซลนี้",
"cw_task_from_pc": "จาก PC",
"cw_dialog_cancel": "ยกเลิก",
"cw_home_kind_upload": "อัปโหลด",
"cw_home_kind_download": "ดาวน์โหลด",
"cw_home_kind_copy": "คัดลอก",
"cw_home_kind_op": "การจัดการไฟล์",
"cw_home_kind_other": "งาน",
"cw_home_progress_bytes": "{done} จาก {total}",
"cw_home_progress_files": "{done} จาก {total} ไฟล์",
"cw_home_percent": "{n}%",
"cw_home_state_running": "กำลังทำงาน",
"cw_home_state_done": "เสร็จแล้ว",
"cw_home_state_failed": "ล้มเหลว",
"cw_home_cancel_task": "ยกเลิก",
"cw_home_vol_internal": "ที่เก็บข้อมูลภายใน",
"cw_home_vol_usb": "ไดรฟ์ USB",
"cw_home_vol_usb_ext": "ที่เก็บข้อมูลเสริม USB",
"cw_home_vol_m2": "ที่เก็บข้อมูล M.2",
"cw_home_vol_ext": "ที่เก็บข้อมูลเสริม",
"cw_home_vol_readonly": "อ่านอย่างเดียว",
"cw_home_free_of": "ว่าง {free} จาก {total}",
"cw_home_err_offline": "คอนโซลไม่ตอบสนอง ลองอีกครั้ง",
"cw_home_err_busy": "คอนโซลไม่ว่าง ลองอีกครั้งในอีกสักครู่",
"cw_home_err_gone": "งานนั้นสิ้นสุดไปแล้ว",
"cw_home_err_failed": "ไม่สำเร็จ ({code})",
"cw_home_err_load_tasks": "อ่านรายการงานไม่ได้",
"cw_home_err_load_storage": "อ่านไดรฟ์ไม่ได้",
"cw_home_rest_sent": "คอนโซลกำลังเข้าสู่โหมดพัก",
"cw_home_restart_sent": "คอนโซลกำลังรีสตาร์ท โหลดหน้านี้ใหม่เมื่อกลับมาแล้ว",
"cw_home_confirm_cancel_title": "ยกเลิกงานนี้ไหม",
"cw_home_confirm_cancel_body": "งานจะหยุดทันทีและย้อนกลับไม่ได้",
"cw_home_confirm_cancel_body_pc": "งานนี้เริ่มจาก PC และจะหยุดบน PC ด้วย",
"cw_home_cancel_task_confirm": "ยกเลิกงาน",
"cw_home_keep_task": "ทำงานต่อ",
"cw_home_confirm_close_title": "ปิด {name} ไหม",
"cw_home_confirm_close_body": "ความคืบหน้าในเกมที่ยังไม่ได้บันทึกจะหายไป",
"cw_home_close_game": "ปิดเกม",
"cw_home_confirm_rest_title": "ให้คอนโซลเข้าสู่โหมดพักไหม",
"cw_home_confirm_rest_body": "หน้านี้จะขาดการเชื่อมต่อจนกว่าคอนโซลจะตื่น",
"cw_home_rest": "โหมดพัก",
"cw_home_confirm_restart_title": "รีสตาร์ทคอนโซลไหม",
"cw_home_confirm_restart_body": "เกมและงานที่กำลังทำงานจะหยุด หน้านี้จะขาดการเชื่อมต่อจนกว่าคอนโซลจะกลับมา",
"cw_home_restart": "รีสตาร์ท",
"cw_home_loading": "กำลังโหลด…",
"cw_home_no_tasks": "ตอนนี้ไม่มีงาน",
"cw_home_quick": "การทำงานด่วน",
"cw_home_browse_usb": "เรียกดู USB",
"cw_home_now_running": "กำลังทำงานอยู่",
"cw_home_no_game": "ไม่มีเกมที่กำลังทำงาน",
"cw_home_tasks": "งาน",
"cw_home_storage": "ที่เก็บข้อมูล",
};
export default th;
+52
View File
@@ -4053,6 +4053,58 @@ c2c_test_deep_bad: "4 KiB'lik test gönderimi ulaşmadı: {detail}",
"cw_status_connecting": "Bağlanıyor…",
"cw_status_offline": "Bağlı değil",
"cw_hints_label": "Kumanda düğmeleri",
"cw_task_from_console": "Bu konsoldan",
"cw_task_from_pc": "Bir PC'den",
"cw_dialog_cancel": "İptal",
"cw_home_kind_upload": "Yükleme",
"cw_home_kind_download": "İndirme",
"cw_home_kind_copy": "Kopyalama",
"cw_home_kind_op": "Dosya işlemi",
"cw_home_kind_other": "Görev",
"cw_home_progress_bytes": "{done} / {total}",
"cw_home_progress_files": "{done} / {total} dosya",
"cw_home_percent": "%{n}",
"cw_home_state_running": "Çalışıyor",
"cw_home_state_done": "Bitti",
"cw_home_state_failed": "Başarısız",
"cw_home_cancel_task": "İptal et",
"cw_home_vol_internal": "Dahili depolama",
"cw_home_vol_usb": "USB sürücü",
"cw_home_vol_usb_ext": "USB genişletilmiş depolama",
"cw_home_vol_m2": "M.2 depolama",
"cw_home_vol_ext": "Genişletilmiş depolama",
"cw_home_vol_readonly": "Salt okunur",
"cw_home_free_of": "{total} alanın {free} kadarı boş",
"cw_home_err_offline": "Konsol yanıt vermedi. Tekrar dene.",
"cw_home_err_busy": "Konsol meşgul. Birazdan tekrar dene.",
"cw_home_err_gone": "Bu görev zaten bitti.",
"cw_home_err_failed": "Bu işe yaramadı ({code}).",
"cw_home_err_load_tasks": "Görev listesi okunamadı.",
"cw_home_err_load_storage": "Sürücüler okunamadı.",
"cw_home_rest_sent": "Konsol dinlenme moduna geçiyor.",
"cw_home_restart_sent": "Konsol yeniden başlıyor. Geri geldiğinde bu sayfayı yenile.",
"cw_home_confirm_cancel_title": "Bu görev iptal edilsin mi?",
"cw_home_confirm_cancel_body": "Hemen durur. Bu geri alınamaz.",
"cw_home_confirm_cancel_body_pc": "Bu görevi bir PC başlattı. PC'de de durur.",
"cw_home_cancel_task_confirm": "Görevi iptal et",
"cw_home_keep_task": "Devam etsin",
"cw_home_confirm_close_title": "{name} kapatılsın mı?",
"cw_home_confirm_close_body": "Oyunda kaydedilmemiş ilerleme kaybolur.",
"cw_home_close_game": "Oyunu kapat",
"cw_home_confirm_rest_title": "Konsol dinlenme moduna alınsın mı?",
"cw_home_confirm_rest_body": "Bu sayfanın bağlantısı konsol uyanana kadar kesilir.",
"cw_home_rest": "Dinlenme modu",
"cw_home_confirm_restart_title": "Konsol yeniden başlatılsın mı?",
"cw_home_confirm_restart_body": "Çalışan oyunlar ve görevler durur. Bu sayfanın bağlantısı konsol geri gelene kadar kesilir.",
"cw_home_restart": "Yeniden başlat",
"cw_home_loading": "Yükleniyor…",
"cw_home_no_tasks": "Şu anda görev yok.",
"cw_home_quick": "Hızlı işlemler",
"cw_home_browse_usb": "USB'ye göz at",
"cw_home_now_running": "Şu anda çalışıyor",
"cw_home_no_game": "Çalışan oyun yok.",
"cw_home_tasks": "Görevler",
"cw_home_storage": "Depolama",
};
export default tr;
+52
View File
@@ -4048,6 +4048,58 @@ c2c_test_deep_bad: "Lượt gửi thử 4 KiB không tới: {detail}",
"cw_status_connecting": "Đang kết nối…",
"cw_status_offline": "Chưa kết nối",
"cw_hints_label": "Nút tay cầm",
"cw_task_from_console": "Từ máy này",
"cw_task_from_pc": "Từ PC",
"cw_dialog_cancel": "Hủy",
"cw_home_kind_upload": "Tải lên",
"cw_home_kind_download": "Tải xuống",
"cw_home_kind_copy": "Sao chép",
"cw_home_kind_op": "Thao tác tệp",
"cw_home_kind_other": "Tác vụ",
"cw_home_progress_bytes": "{done} / {total}",
"cw_home_progress_files": "{done} / {total} tệp",
"cw_home_percent": "{n}%",
"cw_home_state_running": "Đang chạy",
"cw_home_state_done": "Xong",
"cw_home_state_failed": "Thất bại",
"cw_home_cancel_task": "Hủy",
"cw_home_vol_internal": "Bộ nhớ trong",
"cw_home_vol_usb": "Ổ USB",
"cw_home_vol_usb_ext": "Bộ nhớ mở rộng USB",
"cw_home_vol_m2": "Bộ nhớ M.2",
"cw_home_vol_ext": "Bộ nhớ mở rộng",
"cw_home_vol_readonly": "Chỉ đọc",
"cw_home_free_of": "Còn trống {free} / {total}",
"cw_home_err_offline": "Máy không phản hồi. Hãy thử lại.",
"cw_home_err_busy": "Máy đang bận. Hãy thử lại sau giây lát.",
"cw_home_err_gone": "Tác vụ đó đã kết thúc.",
"cw_home_err_failed": "Không thành công ({code}).",
"cw_home_err_load_tasks": "Không đọc được danh sách tác vụ.",
"cw_home_err_load_storage": "Không đọc được các ổ đĩa.",
"cw_home_rest_sent": "Máy đang chuyển sang chế độ nghỉ.",
"cw_home_restart_sent": "Máy đang khởi động lại. Hãy tải lại trang này khi máy hoạt động trở lại.",
"cw_home_confirm_cancel_title": "Hủy tác vụ này?",
"cw_home_confirm_cancel_body": "Tác vụ dừng ngay. Không thể hoàn tác.",
"cw_home_confirm_cancel_body_pc": "Một PC đã bắt đầu tác vụ này. Tác vụ cũng dừng trên PC.",
"cw_home_cancel_task_confirm": "Hủy tác vụ",
"cw_home_keep_task": "Tiếp tục",
"cw_home_confirm_close_title": "Đóng {name}?",
"cw_home_confirm_close_body": "Tiến trình chưa lưu trong game sẽ mất.",
"cw_home_close_game": "Đóng game",
"cw_home_confirm_rest_title": "Đưa máy vào chế độ nghỉ?",
"cw_home_confirm_rest_body": "Trang này mất kết nối cho đến khi máy thức dậy.",
"cw_home_rest": "Chế độ nghỉ",
"cw_home_confirm_restart_title": "Khởi động lại máy?",
"cw_home_confirm_restart_body": "Các game và tác vụ đang chạy sẽ dừng. Trang này mất kết nối cho đến khi máy hoạt động trở lại.",
"cw_home_restart": "Khởi động lại",
"cw_home_loading": "Đang tải…",
"cw_home_no_tasks": "Hiện không có tác vụ nào.",
"cw_home_quick": "Thao tác nhanh",
"cw_home_browse_usb": "Duyệt USB",
"cw_home_now_running": "Đang chạy",
"cw_home_no_game": "Không có game nào đang chạy.",
"cw_home_tasks": "Tác vụ",
"cw_home_storage": "Bộ nhớ",
};
export default vi;
+52
View File
@@ -4002,6 +4002,58 @@ c2c_test_deep_bad: "4 KiB 测试发送未到达:{detail}",
"cw_status_connecting": "正在连接…",
"cw_status_offline": "未连接",
"cw_hints_label": "手柄按键",
"cw_task_from_console": "来自本主机",
"cw_task_from_pc": "来自电脑",
"cw_dialog_cancel": "取消",
"cw_home_kind_upload": "上传",
"cw_home_kind_download": "下载",
"cw_home_kind_copy": "复制",
"cw_home_kind_op": "文件操作",
"cw_home_kind_other": "任务",
"cw_home_progress_bytes": "{done} / {total}",
"cw_home_progress_files": "{done} / {total} 个文件",
"cw_home_percent": "{n}%",
"cw_home_state_running": "进行中",
"cw_home_state_done": "已完成",
"cw_home_state_failed": "失败",
"cw_home_cancel_task": "取消",
"cw_home_vol_internal": "内置存储",
"cw_home_vol_usb": "USB 驱动器",
"cw_home_vol_usb_ext": "USB 扩展存储",
"cw_home_vol_m2": "M.2 存储",
"cw_home_vol_ext": "扩展存储",
"cw_home_vol_readonly": "只读",
"cw_home_free_of": "可用 {free},共 {total}",
"cw_home_err_offline": "主机没有响应,请重试。",
"cw_home_err_busy": "主机正忙,请稍后重试。",
"cw_home_err_gone": "该任务已经结束。",
"cw_home_err_failed": "操作未成功({code})。",
"cw_home_err_load_tasks": "无法读取任务列表。",
"cw_home_err_load_storage": "无法读取驱动器。",
"cw_home_rest_sent": "主机正在进入待机模式。",
"cw_home_restart_sent": "主机正在重启。恢复后请重新加载此页面。",
"cw_home_confirm_cancel_title": "取消此任务?",
"cw_home_confirm_cancel_body": "任务会立即停止,且无法撤销。",
"cw_home_confirm_cancel_body_pc": "此任务由电脑发起,电脑端也会停止。",
"cw_home_cancel_task_confirm": "取消任务",
"cw_home_keep_task": "继续运行",
"cw_home_confirm_close_title": "关闭 {name}?",
"cw_home_confirm_close_body": "游戏中未保存的进度将丢失。",
"cw_home_close_game": "关闭游戏",
"cw_home_confirm_rest_title": "让主机进入待机模式?",
"cw_home_confirm_rest_body": "主机唤醒前,此页面将断开连接。",
"cw_home_rest": "待机模式",
"cw_home_confirm_restart_title": "重启主机?",
"cw_home_confirm_restart_body": "正在运行的游戏和任务会停止。主机恢复前,此页面将断开连接。",
"cw_home_restart": "重启",
"cw_home_loading": "正在加载…",
"cw_home_no_tasks": "当前没有任务。",
"cw_home_quick": "快捷操作",
"cw_home_browse_usb": "浏览 USB",
"cw_home_now_running": "正在运行",
"cw_home_no_game": "没有正在运行的游戏。",
"cw_home_tasks": "任务",
"cw_home_storage": "存储",
};
export default zh_CN;
+52
View File
@@ -3996,6 +3996,58 @@ c2c_test_deep_bad: "4 KiB 測試傳送未抵達:{detail}",
"cw_status_connecting": "正在連線…",
"cw_status_offline": "未連線",
"cw_hints_label": "控制器按鍵",
"cw_task_from_console": "來自本主機",
"cw_task_from_pc": "來自電腦",
"cw_dialog_cancel": "取消",
"cw_home_kind_upload": "上傳",
"cw_home_kind_download": "下載",
"cw_home_kind_copy": "複製",
"cw_home_kind_op": "檔案操作",
"cw_home_kind_other": "工作",
"cw_home_progress_bytes": "{done} / {total}",
"cw_home_progress_files": "{done} / {total} 個檔案",
"cw_home_percent": "{n}%",
"cw_home_state_running": "進行中",
"cw_home_state_done": "已完成",
"cw_home_state_failed": "失敗",
"cw_home_cancel_task": "取消",
"cw_home_vol_internal": "內建儲存空間",
"cw_home_vol_usb": "USB 磁碟機",
"cw_home_vol_usb_ext": "USB 延伸儲存空間",
"cw_home_vol_m2": "M.2 儲存空間",
"cw_home_vol_ext": "延伸儲存空間",
"cw_home_vol_readonly": "唯讀",
"cw_home_free_of": "可用 {free},共 {total}",
"cw_home_err_offline": "主機沒有回應,請再試一次。",
"cw_home_err_busy": "主機忙碌中,請稍後再試。",
"cw_home_err_gone": "這項工作已經結束。",
"cw_home_err_failed": "操作未成功({code})。",
"cw_home_err_load_tasks": "無法讀取工作清單。",
"cw_home_err_load_storage": "無法讀取磁碟機。",
"cw_home_rest_sent": "主機正在進入休眠模式。",
"cw_home_restart_sent": "主機正在重新啟動。恢復後請重新載入此頁面。",
"cw_home_confirm_cancel_title": "要取消這項工作嗎?",
"cw_home_confirm_cancel_body": "工作會立即停止,且無法復原。",
"cw_home_confirm_cancel_body_pc": "這項工作由電腦啟動,電腦端也會停止。",
"cw_home_cancel_task_confirm": "取消工作",
"cw_home_keep_task": "繼續執行",
"cw_home_confirm_close_title": "要關閉 {name} 嗎?",
"cw_home_confirm_close_body": "遊戲中未儲存的進度將會遺失。",
"cw_home_close_game": "關閉遊戲",
"cw_home_confirm_rest_title": "要讓主機進入休眠模式嗎?",
"cw_home_confirm_rest_body": "主機喚醒前,此頁面會中斷連線。",
"cw_home_rest": "休眠模式",
"cw_home_confirm_restart_title": "要重新啟動主機嗎?",
"cw_home_confirm_restart_body": "執行中的遊戲和工作會停止。主機恢復前,此頁面會中斷連線。",
"cw_home_restart": "重新啟動",
"cw_home_loading": "載入中…",
"cw_home_no_tasks": "目前沒有工作。",
"cw_home_quick": "快速動作",
"cw_home_browse_usb": "瀏覽 USB",
"cw_home_now_running": "執行中",
"cw_home_no_game": "沒有執行中的遊戲。",
"cw_home_tasks": "工作",
"cw_home_storage": "儲存空間",
};
export default zh_TW;