-
{t("plex.collection.newCollection")}
+ {t("plex.collection.newCollectionTitle")}
diff --git a/frontend/src/components/plexCards.dom.test.tsx b/frontend/src/components/plexCards.dom.test.tsx
index 2bc1f7d5..4f751674 100644
--- a/frontend/src/components/plexCards.dom.test.tsx
+++ b/frontend/src/components/plexCards.dom.test.tsx
@@ -85,7 +85,7 @@ describe("PlexPosterCard a11y", () => {
);
expect(screen.getByText("2023")).toBeTruthy();
expect(screen.getByText("7.5")).toBeTruthy(); // rating.toFixed(1)
- expect(screen.getByText("1h 53m")).toBeTruthy();
+ expect(screen.getByText("1 hr 53 min")).toBeTruthy();
expect(screen.getByRole("button", { name: movie().title }).getAttribute("title")).toBeNull();
});
diff --git a/frontend/src/components/ui/README.md b/frontend/src/components/ui/README.md
index 51eb7c3a..bc54aa7c 100644
--- a/frontend/src/components/ui/README.md
+++ b/frontend/src/components/ui/README.md
@@ -43,7 +43,7 @@ domain-free.
B5 glass roles; a bare glass class is a lint error), `Badge` (a static label), `LinkButton` / `IconLink` (a
link wearing the button face) over `buttonStyles`, `Skeleton` (B8 first loads, through `LoadingState shape`), `NavRail` (B7), `form`, `DraftSaveBar`, `Combobox`, `FilterSelect`, `SegmentedToggle`, `SortControl`, `ColumnSortControl`, `PriorityStepper`, `UndoToolbar` |
-| data display | `DataTable`, `tableHeader` + `tableAlign`, `Pager` + `pagerContent`, `VirtualGrid` + `gridColumns` (the column/width maths, incl. the ultrawide `maxCols` cap), `readAhead`, `Carousel`, `MediaCard` + `mediaCardStyles` (the one media card, docs/DESIGN.md "Media card") + `MediaPreview` / `useMediaPreview` (its mouse-rest preview), `NavGrid` (a plain grid, list or table body the keyboard walks — `DataTable`, `VirtualGrid` and `Carousel` take `keyboardNav`), `Avatar`, `CountBadge`, `ItemCount`, `FilterChipGroup` |
+| data display | `DataTable`, `tableHeader` + `tableAlign`, `Pager` + `pagerContent`, `VirtualGrid` + `gridColumns` (the column/width maths, incl. the ultrawide `maxCols` cap), `readAhead`, `Carousel`, `MediaCard` + `mediaCardStyles` (the one media card, docs/DESIGN.md "Media card") + `MediaPreview` / `useMediaPreview` (its mouse-rest preview), `NavGrid` (a plain grid, list or table body the keyboard walks — `DataTable`, `VirtualGrid` and `Carousel` take `keyboardNav`), `Avatar`, `CountBadge`, `ItemCount`, `FilterChipGroup`, `RelativeTime` ("3d ago" with the exact minute on hover, docs/DESIGN.md "Copy") |
| page & panel structure | `PageShell` + `pageToolbarSlot` + `pageHeading`, `PageScroller`, `Headings` (`PageHeading`, `SectionLabel`), `SkipLink` + `landmarks`, `FilterFold` (B4), `PanelGroup`, `PanelGroups`, `Tabs`, `BackToTop` |
| state & messaging | `QueryState`, `Banner`, `InlineNotice` |
diff --git a/frontend/src/components/ui/RelativeTime.dom.test.tsx b/frontend/src/components/ui/RelativeTime.dom.test.tsx
new file mode 100644
index 00000000..08d6cdb6
--- /dev/null
+++ b/frontend/src/components/ui/RelativeTime.dom.test.tsx
@@ -0,0 +1,40 @@
+// @vitest-environment jsdom
+//
+// docs/DESIGN.md "Copy": a relative time in a list carries the exact moment on hover.
+import { afterEach, describe, expect, it } from "vitest";
+import { cleanup, render } from "@testing-library/react";
+import { formatDateTime, formatDateTimeSec, relativeTime } from "../../lib/format";
+import { RelativeTime } from "./RelativeTime";
+
+afterEach(cleanup);
+
+const THREE_DAYS_AGO = new Date(Date.now() - 3 * 86400_000).toISOString();
+
+describe("RelativeTime", () => {
+ it("says how long ago, and names the exact minute on hover", () => {
+ const { container } = render( );
+ const el = container.querySelector("time")!;
+ expect(el.textContent).toBe(relativeTime(THREE_DAYS_AGO));
+ expect(el.getAttribute("title")).toBe(formatDateTime(THREE_DAYS_AGO));
+ expect(el.getAttribute("dateTime")).toBe(THREE_DAYS_AGO);
+ });
+
+ it("takes epoch millis, and the log form with seconds", () => {
+ const ms = Date.parse(THREE_DAYS_AGO);
+ const { container } = render( );
+ expect(container.querySelector("time")!.getAttribute("title")).toBe(
+ formatDateTimeSec(THREE_DAYS_AGO),
+ );
+ });
+
+ it("shows the placeholder for a missing or unreadable moment, never a bare title", () => {
+ const { container } = render(
+ <>
+
+
+ >,
+ );
+ expect(container.textContent).toBe("—");
+ expect(container.querySelector("time")).toBeNull();
+ });
+});
diff --git a/frontend/src/components/ui/RelativeTime.tsx b/frontend/src/components/ui/RelativeTime.tsx
new file mode 100644
index 00000000..9cafec27
--- /dev/null
+++ b/frontend/src/components/ui/RelativeTime.tsx
@@ -0,0 +1,32 @@
+import { formatDateTime, formatDateTimeSec, relativeFromMs } from "../../lib/format";
+
+// docs/DESIGN.md "Copy": a relative time ("3d ago") says how recent something is in a list, and the
+// exact moment is one hover away — so the pair travels together here instead of every list
+// remembering (or forgetting) its own `title`. `seconds` is the log form (the audit). An unreadable
+// or missing moment renders `empty` ("—" in a table cell) or nothing.
+export function RelativeTime({
+ at,
+ seconds = false,
+ empty = "",
+ className,
+}: {
+ /** An ISO timestamp, or epoch millis (a client-side notification). */
+ at: string | number | null | undefined;
+ seconds?: boolean;
+ empty?: string;
+ className?: string;
+}) {
+ const ms = typeof at === "number" ? at : at ? new Date(at).getTime() : NaN;
+ const text = Number.isFinite(ms) ? relativeFromMs(ms) : "";
+ if (!text) return empty ? {empty} : null;
+ const iso = new Date(ms).toISOString();
+ return (
+
+ {text}
+
+ );
+}
diff --git a/frontend/src/i18n/copyRule.test.ts b/frontend/src/i18n/copyRule.test.ts
new file mode 100644
index 00000000..1ab5bb39
--- /dev/null
+++ b/frontend/src/i18n/copyRule.test.ts
@@ -0,0 +1,45 @@
+import { describe, expect, it } from "vitest";
+
+// docs/DESIGN.md "Copy" (E28 S3g): the parts of the copy rule a machine can read, held over every
+// value in both languages. The rest (one name per feature, one verb per meaning, "…" when a command
+// asks first) needs a reader and is checked by the DESIGN conformance step.
+
+const files = import.meta.glob("./locales/*/*.json", { eager: true, import: "default" }) as Record<
+ string,
+ unknown
+>;
+
+function values(): [string, string][] {
+ const out: [string, string][] = [];
+ const walk = (o: unknown, path: string) => {
+ if (typeof o === "string") out.push([path, o]);
+ else if (o && typeof o === "object")
+ for (const [k, v] of Object.entries(o)) walk(v, path ? `${path}.${k}` : k);
+ };
+ for (const [file, json] of Object.entries(files))
+ walk(json, file.replace("./locales/", "").replace(".json", ":"));
+ return out;
+}
+
+const offenders = (test: (v: string) => boolean) =>
+ values()
+ .filter(([, v]) => test(v))
+ .map(([k, v]) => `${k} = ${v}`);
+
+describe("the copy rule", () => {
+ it("quotes a name with curly quotes, never straight ones", () => {
+ expect(offenders((v) => v.includes('"'))).toEqual([]);
+ });
+
+ it("writes an ellipsis as one character", () => {
+ expect(offenders((v) => v.includes("..."))).toEqual([]);
+ });
+
+ it("ends no message with a tick", () => {
+ expect(offenders((v) => v.includes("✓"))).toEqual([]);
+ });
+
+ it("spells no unit as a bare letter after a number ('90m', '24h', '{{count}}d', '10 s')", () => {
+ expect(offenders((v) => /(\d|\}\})\s?(m|h|d|s)(?![\p{L}\d/])/u.test(v))).toEqual([]);
+ });
+});
diff --git a/frontend/src/i18n/locales/en/about.json b/frontend/src/i18n/locales/en/about.json
index 6cd37069..0fe37407 100644
--- a/frontend/src/i18n/locales/en/about.json
+++ b/frontend/src/i18n/locales/en/about.json
@@ -11,5 +11,5 @@
"fixes": "Fixes",
"olderVersions_one": "{{count}} older version",
"olderVersions_other": "{{count}} older versions",
- "notesError": "The release notes could not be loaded."
+ "notesError": "Couldn't load the release notes"
}
diff --git a/frontend/src/i18n/locales/en/adminLogs.json b/frontend/src/i18n/locales/en/adminLogs.json
index f121848a..519ee9d8 100644
--- a/frontend/src/i18n/locales/en/adminLogs.json
+++ b/frontend/src/i18n/locales/en/adminLogs.json
@@ -26,9 +26,9 @@
"showRequest": "Only this request's lines",
"showJob": "Only this job's lines",
"empty": "No lines match the filters.",
- "stale": "The last refresh failed; what you see may be out of date.",
- "olderFailed": "Couldn't load the older lines.",
- "countsFailed": "The filter counts didn't load.",
+ "stale": "Couldn't refresh — what you see may be out of date",
+ "olderFailed": "Couldn't load the older lines",
+ "countsFailed": "Couldn't load the filter counts",
"count_one": "{{count}} line loaded · newest first",
"count_other": "{{count}} lines loaded · newest first",
"older": "Load older",
diff --git a/frontend/src/i18n/locales/en/audit.json b/frontend/src/i18n/locales/en/audit.json
index cad77e88..1d599c19 100644
--- a/frontend/src/i18n/locales/en/audit.json
+++ b/frontend/src/i18n/locales/en/audit.json
@@ -7,7 +7,7 @@
"clearTitle": "Clear the audit log?",
"clearConfirm": "Permanently delete all audit entries? This can't be undone. (One entry recording that you cleared it is kept.)",
"cleared": "Cleared {{count}} audit entries",
- "clearFailed": "Couldn't clear the audit log.",
+ "clearFailed": "Couldn't clear the audit log",
"truncated": "Showing the {{returned}} most recent of {{total}} entries.",
"cols": {
"when": "When",
diff --git a/frontend/src/i18n/locales/en/auditActions.json b/frontend/src/i18n/locales/en/auditActions.json
index ac74f709..24004a99 100644
--- a/frontend/src/i18n/locales/en/auditActions.json
+++ b/frontend/src/i18n/locales/en/auditActions.json
@@ -22,7 +22,7 @@
"job_run": "Scheduler job run",
"audit_clear": "Audit log cleared",
"download_credential_set": "Download cookies set",
- "download_credential_delete": "Download cookies removed",
+ "download_credential_delete": "Download cookies deleted",
"invite_delete": "Whitelist entry deleted",
"invite_send": "Invitation sent",
"issue_create": "Bug report sent"
diff --git a/frontend/src/i18n/locales/en/channel.json b/frontend/src/i18n/locales/en/channel.json
index f50d2ca4..6055ddfd 100644
--- a/frontend/src/i18n/locales/en/channel.json
+++ b/frontend/src/i18n/locales/en/channel.json
@@ -36,5 +36,5 @@
"fullHistoryProgress": "Fetching history… {{stored}} / {{total}}",
"fullHistoryLoadingHint": "Fetching the full back-catalogue — videos appear as they're found; a very large channel's tail is finished in the background.",
"fullHistoryQueued": "Fetching this channel's full history — new videos will appear as they're found.",
- "fullHistoryFailed": "Couldn't start the full-history fetch."
+ "fullHistoryFailed": "Couldn't start the full-history fetch"
}
diff --git a/frontend/src/i18n/locales/en/channels.json b/frontend/src/i18n/locales/en/channels.json
index b25ee7f5..5aee7e43 100644
--- a/frontend/src/i18n/locales/en/channels.json
+++ b/frontend/src/i18n/locales/en/channels.json
@@ -1,6 +1,6 @@
{
"actionsLabel": "Catalog actions",
- "syncSubscriptions": "Read subscriptions from YouTube",
+ "syncSubscriptions": "Sync subscriptions from YouTube",
"syncSubscriptionsHint": "Re-import your subscription list from YouTube — adds channels you've newly followed and drops ones you've unfollowed. The videos themselves keep syncing automatically in the background; this does not re-fetch them.",
"backfillEverything": "Backfill everything",
"backfillEverythingHint": "Request full back-catalog backfill for every channel you're subscribed to. Older videos and search become complete as the shared daily quota allows — this can take a while.",
@@ -31,8 +31,8 @@
"needWriteHint": "Enable playlist editing in Settings to subscribe on YouTube.",
"subscribedTitle": "Subscribed on YouTube",
"subscribedBody": "You're now following {{name}} — its new uploads will start arriving in your Feed.",
- "subscribeFailed": "Subscribe failed",
- "confirmSubscribe": "Subscribe to \"{{name}}\" on YouTube? This changes your real YouTube account and spends a little API quota.",
+ "subscribeFailed": "Couldn't subscribe",
+ "confirmSubscribe": "Subscribe to “{{name}}” on YouTube? This changes your real YouTube account and spends a little API quota.",
"cols": {
"totalVideos": "Videos",
"inPlaylists": "In playlists",
@@ -54,8 +54,8 @@
"noneYet": "No tags yet — create one below.",
"create": "Create tag",
"createPlaceholder": "New tag…",
- "createFailed": "Could not create the tag.",
- "updateFailed": "Could not update the tag.",
+ "createFailed": "Couldn't create the tag",
+ "updateFailed": "Couldn't update the tag",
"noTag": "No tag",
"moreCount_one": "{{count}} more tag",
"moreCount_other": "{{count}} more tags",
@@ -102,7 +102,7 @@
},
"row": {
"openOnYouTube": "Open on YouTube",
- "editTags": "Edit tags",
+ "editTags": "Edit tags…",
"filterFeedByTag": "Filter the Feed by “{{name}}”",
"priorityHint": "Your ranking for this channel. Sort the Feed by “Channel priority” to bring higher-priority channels to the top.",
"fullHint": "Full history fetched.",
@@ -126,20 +126,20 @@
},
"notify": {
"synced": "Synced {{count}} subscriptions",
- "syncFailed": "Subscription sync failed",
+ "syncFailed": "Couldn't sync subscriptions",
"unsubscribed": "Unsubscribed from {{name}} on YouTube",
- "unsubscribeFailed": "Unsubscribe failed",
+ "unsubscribeFailed": "Couldn't unsubscribe",
"fullHistoryRequested": "Full history requested for {{count}} channels",
"fullHistoryFailed": "Couldn't request full history",
- "needYouTube": "Connect your YouTube account to do this.",
+ "needYouTube": "Connect your YouTube account to do this",
"connect": "Connect",
"filteredByTag": "Feed filtered by tag: {{name}}",
"resetDone": "{{name}} reset — re-fetching now",
"resetFailed": "Couldn't reset this channel",
"hidden": "“{{name}}” hidden from your Feed"
},
- "confirmUnsubscribe": "Unsubscribe from \"{{name}}\" on YouTube? This changes your real YouTube account. To just remove it from your Feed, hide it instead.",
- "confirmReset": "Re-fetch \"{{name}}\" from scratch? This re-runs its backfill (recent + full history) and spends some API quota.",
+ "confirmUnsubscribe": "Unsubscribe from “{{name}}” on YouTube? This changes your real YouTube account. To just remove it from your Feed, hide it instead.",
+ "confirmReset": "Re-fetch “{{name}}” from scratch? This re-runs its backfill (recent + full history) and spends some API quota.",
"searchPlaceholder": "Search channels…",
"blocked": {
"title": "Blocked channels",
diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json
index 33e77cdc..bbf438c6 100644
--- a/frontend/src/i18n/locales/en/common.json
+++ b/frontend/src/i18n/locales/en/common.json
@@ -1,6 +1,6 @@
{
- "privacyPolicy": "Privacy Policy",
- "termsOfService": "Terms of Service",
+ "privacyPolicy": "Privacy policy",
+ "termsOfService": "Terms of service",
"sourceCode": "Source code",
"close": "Close",
"cancel": "Cancel",
@@ -10,7 +10,7 @@
"undo": "Undo",
"redo": "Redo",
"loading": "Loading…",
- "somethingWrong": "Something went wrong.",
+ "somethingWrong": "Something went wrong",
"itemCount_one": "{{formattedCount}} item",
"itemCount_other": "{{formattedCount}} items",
"accessRequestsTitle": "Access requests",
@@ -20,7 +20,7 @@
"demoSharedNotice": "You're in the shared demo account. Everything you do here — playlists, hidden videos, settings — is communal and may be changed by other demo visitors at the same time.",
"backToTop": "Back to top",
"retry": "Retry",
- "loadError": "Couldn't load this — check your connection and try again.",
+ "loadError": "Couldn't load this — check your connection and try again",
"seeAll": "See all",
"scrollLeft": "Scroll left",
"scrollRight": "Scroll right",
diff --git a/frontend/src/i18n/locales/en/config.json b/frontend/src/i18n/locales/en/config.json
index e329babc..de1831a5 100644
--- a/frontend/src/i18n/locales/en/config.json
+++ b/frontend/src/i18n/locales/en/config.json
@@ -54,7 +54,7 @@
},
"search_source": {
"label": "Live search source",
- "hint": "Where live YouTube search results come from. \"Website\" reads YouTube's own search endpoint and spends no API quota (recommended). \"Official API\" uses search.list (100 units/page) — switch to it if the website source ever breaks or is blocked."
+ "hint": "Where live YouTube search results come from. “Website” reads YouTube's own search endpoint and spends no API quota (recommended). “Official API” uses search.list (100 units/page) — switch to it if the website source ever breaks or is blocked."
},
"backfill_recent_max_videos": {
"label": "Recent backfill — max videos",
@@ -134,7 +134,7 @@
},
"download_upload_max_bytes": {
"label": "Upload size cap per file (bytes)",
- "hint": "Largest single file a user may upload to the download center. At least 1, so the cap is always on; the user's total storage is still bounded by their quota. Default 500 MiB."
+ "hint": "Largest single file a user may upload to Downloads. At least 1, so the cap is always on; the user's total storage is still bounded by their quota. Default 500 MiB."
},
"download_retention_days": {
"label": "Retention (days)",
@@ -146,7 +146,7 @@
},
"download_layout": {
"label": "On-disk layout",
- "hint": "\"Plex\" = a Channel/Season YYYY/… tree with .nfo + poster (Plex-indexable); \"Flat\" = a single folder."
+ "hint": "“Plex” = a Channel/Season YYYY/… tree with .nfo + poster (Plex-indexable); “Flat” = a single folder."
},
"download_worker_concurrency": {
"label": "Worker concurrency",
@@ -166,7 +166,7 @@
},
"log_retention_days": {
"label": "Log retention (days)",
- "hint": "How long stored log events are kept before the log cleanup job deletes them. They include account-linked request history, so there is no \"keep forever\". Default 7."
+ "hint": "How long stored log events are kept before the log cleanup job deletes them. They include account-linked request history, so there is no “keep forever”. Default 7."
},
"plex_enabled": {
"label": "Enable Plex",
@@ -292,22 +292,22 @@
"testEmail": "Send test email",
"testEmailHint": "Send a test email to your own address using the settings above, to confirm they work.",
"testSent": "Test email sent to {{to}}",
- "testFailed": "Test email failed — check the settings",
+ "testFailed": "Couldn't send the test email — check the settings",
"plexTest": "Test connection",
"plexTestHint": "Verify the Plex server URL + token and load the library list. Save any edits first.",
"testDirty": "Save your changes before testing them.",
"plexTestOk": "Connected to {{name}}",
- "plexTestFailed": "Plex connection failed — check the URL and token",
+ "plexTestFailed": "Couldn't connect to Plex — check the URL and token",
"plexConnected": "Connected to {{name}} {{version}}",
"plexMediaOk": "Media mount OK — a sample file was found on disk.",
"plexMediaMissing": "Media not readable at {{path}} — check that the Plex media is bind-mounted into the container and that the path map is correct. Playback will fail until this is fixed.",
"plexPickLibraries": "Libraries to expose:",
"plexPickHint": "Unchecking all is the same as exposing every library. Remember to Save.",
"plexMovies": "Movies",
- "plexShows": "TV Shows",
+ "plexShows": "TV shows",
"emailOff": "Email sending is not configured — Siftlode silently skips every message: verification links, password resets and access-request notices. Fill in the fields above, then send a test.",
"issuesTest": "Check the connection",
"issuesTestHint": "Checks that the repository answers, is private and takes issues. Save your changes first.",
"issuesTestOk": "Works: {{repo}} (private)",
- "issuesTestFailed": "The issue tracker check failed"
+ "issuesTestFailed": "Couldn't check the issue tracker"
}
diff --git a/frontend/src/i18n/locales/en/downloads.json b/frontend/src/i18n/locales/en/downloads.json
index 0e330505..59e51318 100644
--- a/frontend/src/i18n/locales/en/downloads.json
+++ b/frontend/src/i18n/locales/en/downloads.json
@@ -13,14 +13,14 @@
"submit": "Add to downloads",
"added": "Added to downloads",
"view": "View",
- "manage": "Manage formats"
+ "manage": "Manage formats…"
},
"page": {
"addUrl": "Paste a video link (YouTube, Facebook, …) or a YouTube video id…",
"add": "Add",
"paste": "Paste link from clipboard",
"upload": "Upload",
- "manage": "Manage formats"
+ "manage": "Manage formats…"
},
"upload": {
"title": "Upload a file",
@@ -33,7 +33,7 @@
"uploading": "Uploading… {{pct}}%",
"uploadProgress": "Upload progress",
"added": "File uploaded",
- "failed": "The upload failed."
+ "failed": "Couldn't upload the file"
},
"tabs": {
"queue": "Queue",
@@ -65,15 +65,15 @@
"pause": "Pause",
"resume": "Resume",
"cancel": "Cancel",
- "delete": "Remove",
+ "delete": "Delete",
"retry": "Retry",
"download": "Save to my device",
"play": "Play",
"rename": "Rename",
- "editDetails": "Edit details",
+ "editDetails": "Edit details…",
"share": "Share (copy link)",
- "shareManage": "Manage sharing",
- "edit": "Edit / trim",
+ "shareManage": "Manage sharing…",
+ "edit": "Edit / trim…",
"redownload": "Re-download (rebuild file)",
"removeShared": "Remove from my list"
},
@@ -88,7 +88,7 @@
},
"usage": {
"title": "Your storage",
- "items": "{{used}} / {{max}} items",
+ "items": "{{used}} / {{max}} downloads",
"unlimited": "Unlimited"
},
"source": {
@@ -108,7 +108,7 @@
"share": {
"title": "Share download",
"done": "Shared with {{email}}",
- "failed": "Couldn’t share.",
+ "failed": "Couldn’t share",
"userSection": "Share with a user",
"userHint": "They’ll find it under “Shared with me”.",
"pickPlaceholder": "Search a user…",
@@ -127,7 +127,7 @@
"days": "{{n}} days",
"password": "Password (optional)",
"copy": "Copy link",
- "copyFailed": "Couldn’t copy.",
+ "copyFailed": "Couldn’t copy",
"revoke": "Revoke",
"revokeConfirm": "This link will stop working immediately.",
"views": "{{n}} views",
@@ -138,15 +138,15 @@
"changePassword": "Change password",
"newPassword": "New password",
"savePassword": "Save",
- "removePassword": "Remove",
+ "removePassword": "Delete",
"removePasswordConfirm": "This makes the link public — anyone with the URL can watch it without a password."
},
"confirm": {
"cancelTitle": "Cancel download?",
"cancelBody": "This stops the download and removes it from your queue.",
"cancelAction": "Stop download",
- "deleteTitle": "Remove download?",
- "deleteBody": "This removes it from your list. The file may stay cached for other users until it expires.",
+ "deleteTitle": "Delete download?",
+ "deleteBody": "This deletes it from your downloads. The file may stay cached for other users until it expires.",
"removeSharedTitle": "Remove from your list?",
"removeSharedBody": "This removes it from your shared list only — it won't delete the owner's file.",
"redownloadTitle": "Re-download this file?",
@@ -198,28 +198,28 @@
"errors": {
"quota": {
"max_bytes": "You've used your download storage quota ({{size}} GB). Free up space first.",
- "max_jobs": "You've reached your download limit ({{limit}} items). Remove some first.",
- "generic": "Download quota exceeded."
+ "max_jobs": "You've reached your download limit ({{limit}} downloads). Delete some first.",
+ "generic": "Download quota exceeded"
},
"edit": {
- "empty_edit": "Choose a trim range or crop area first.",
- "source_not_ready": "The source download isn't ready to edit.",
- "generic": "That edit couldn't be created."
+ "empty_edit": "Choose a trim range or crop area first",
+ "source_not_ready": "The source download isn't ready to edit",
+ "generic": "Couldn't create that edit"
},
"upload": {
- "tooLarge": "That file is too large (max {{size}}).",
- "empty": "That file is empty."
+ "tooLarge": "That file is too large (max {{size}})",
+ "empty": "That file is empty"
},
"urlBlocked": {
- "scheme": "Only http:// and https:// links can be downloaded.",
- "no_host": "That link doesn't contain a website address.",
+ "scheme": "Only http:// and https:// links can be downloaded",
+ "no_host": "That link doesn't contain a website address",
"unresolvable": "That address couldn't be looked up. Check the link and try again.",
- "private": "That address is on the server's own network, so it can't be downloaded from.",
- "generic": "That address can't be downloaded from.",
- "malformed": "That link isn't a valid web address."
+ "private": "That address is on the server's own network, so it can't be downloaded from",
+ "generic": "That address can't be downloaded from",
+ "malformed": "That link isn't a valid web address"
},
"fail": {
- "title": "Download failed",
+ "title": "Couldn't download",
"detailsLink": "details",
"detailsToggle": "Technical details",
"cat": {
@@ -248,7 +248,7 @@
"body": "This video is DRM-protected and cannot be downloaded."
},
"postprocess": {
- "short": "Processing failed",
+ "short": "Couldn't process the video",
"body": "The video downloaded but a processing step (merge or convert) failed. A retry often fixes this."
},
"source_missing": {
@@ -260,8 +260,8 @@
"body": "A network error interrupted the download. A retry usually fixes this."
},
"unknown": {
- "short": "Download failed",
- "body": "Something went wrong while downloading this item. The technical details below may help."
+ "short": "Couldn't download",
+ "body": "Something went wrong with this download. The technical details below may help."
},
"quota_exceeded": {
"short": "Storage full",
@@ -300,9 +300,9 @@
}
},
"cookies": {
- "empty": "Paste your cookies first.",
- "format": "That doesn't look like a cookies.txt file (Netscape format).",
- "generic": "Those cookies couldn't be saved."
+ "empty": "Paste your cookies first",
+ "format": "That doesn't look like a cookies.txt file (Netscape format)",
+ "generic": "Couldn't save those cookies"
}
}
}
diff --git a/frontend/src/i18n/locales/en/editor.json b/frontend/src/i18n/locales/en/editor.json
index de64e34e..45fd89f9 100644
--- a/frontend/src/i18n/locales/en/editor.json
+++ b/frontend/src/i18n/locales/en/editor.json
@@ -12,7 +12,7 @@
},
"fast": {
"label": "Fast cut",
- "hint": "Instant, but cuts snap to keyframes (±1–2s)."
+ "hint": "Instant, but cuts snap to keyframes (±1–2 sec)."
},
"nameLabel": "Clip name (optional)",
"willReencode": "Will re-encode",
@@ -21,7 +21,7 @@
"createN": "Create {{count}} clips",
"created_one": "{{count}} clip added to your downloads",
"created_other": "{{count}} clips added to your downloads",
- "failed": "Couldn’t create the clip.",
+ "failed": "Couldn’t create the clip",
"partlyCreated": "Only {{done}} of {{total}} clips were added — the rest are still selected, so creating again adds just those.",
"splitHere": "Split here",
"output": {
diff --git a/frontend/src/i18n/locales/en/errors.json b/frontend/src/i18n/locales/en/errors.json
index afdf28b7..6c5f37fa 100644
--- a/frontend/src/i18n/locales/en/errors.json
+++ b/frontend/src/i18n/locales/en/errors.json
@@ -1,57 +1,57 @@
{
"title": "Couldn't complete that",
"generic": "The server couldn't carry out that action. Please try again.",
- "demo": "This isn't available in the demo account.",
+ "demo": "This isn't available in the demo account",
"validation": "Some of the values you entered weren't valid. Please check them and try again.",
- "tooLarge": "That was too large for the server to accept.",
+ "tooLarge": "That was too large for the server to accept",
"server": "Server error",
"syncCooldown": "You've synced a few times just now — give it a few minutes. The YouTube budget is shared with everyone on this instance, and background syncing keeps running either way.",
"notFound": {
- "generic": "This item doesn't exist any more.",
- "view": "This view doesn't exist any more.",
- "tag": "This tag doesn't exist any more.",
- "pin": "This isn't on your comfort shelf any more.",
- "playlist": "This playlist doesn't exist any more.",
- "move_target": "The playlist you're moving it to doesn't exist any more.",
- "video": "This video isn't in the library any more.",
- "invite": "This invite doesn't exist any more.",
- "user": "This user doesn't exist any more.",
- "channel": "This channel isn't in the library any more.",
- "subscription": "You're not subscribed to this channel any more.",
- "recipient": "There's no user with that email address.",
- "job": "This job doesn't exist any more.",
- "download": "This download doesn't exist any more.",
- "debug_session": "This debug session doesn't exist any more.",
- "screenshot": "This screenshot doesn't exist any more.",
- "link": "This share link doesn't exist any more.",
- "notification": "This notification doesn't exist any more.",
- "account": "That account no longer exists.",
- "collection": "This collection doesn't exist any more.",
- "config_key": "There's no such setting.",
- "file": "The file is no longer available.",
- "filmstrip": "There's no filmstrip for this.",
- "host": "This host isn't on the list any more.",
- "image": "There's no image for this.",
- "item": "This item doesn't exist any more.",
- "library": "This library doesn't exist any more.",
- "plex_item": "This Plex item isn't in the library any more.",
- "poster": "There's no poster for this.",
- "profile": "This download profile doesn't exist any more.",
- "season": "This season isn't in the library any more.",
- "show": "This show isn't in the library any more."
+ "generic": "This item doesn't exist any more",
+ "view": "This view doesn't exist any more",
+ "tag": "This tag doesn't exist any more",
+ "pin": "This isn't on your comfort shelf any more",
+ "playlist": "This playlist doesn't exist any more",
+ "move_target": "The playlist you're moving it to doesn't exist any more",
+ "video": "This video isn't in the library any more",
+ "invite": "This invite doesn't exist any more",
+ "user": "This user doesn't exist any more",
+ "channel": "This channel isn't in the library any more",
+ "subscription": "You're not subscribed to this channel any more",
+ "recipient": "There's no user with that email address",
+ "job": "This job doesn't exist any more",
+ "download": "This download doesn't exist any more",
+ "debug_session": "This debug session doesn't exist any more",
+ "screenshot": "This screenshot doesn't exist any more",
+ "link": "This share link doesn't exist any more",
+ "notification": "This notification doesn't exist any more",
+ "account": "That account no longer exists",
+ "collection": "This collection doesn't exist any more",
+ "config_key": "There's no such setting",
+ "file": "The file is no longer available",
+ "filmstrip": "There's no filmstrip for this",
+ "host": "This host isn't on the list any more",
+ "image": "There's no image for this",
+ "item": "This item doesn't exist any more",
+ "library": "This library doesn't exist any more",
+ "plex_item": "This Plex item isn't in the library any more",
+ "poster": "There's no poster for this",
+ "profile": "This download profile doesn't exist any more",
+ "season": "This season isn't in the library any more",
+ "show": "This show isn't in the library any more"
},
"admin": {
"not_allowed": "Their address isn't approved any more. Add it to the whitelist first.",
- "not_applicable": "This account has nothing left to confirm.",
- "not_approved": "Only an approved address can be invited.",
+ "not_applicable": "This account has nothing left to confirm",
+ "not_approved": "Only an approved address can be invited",
"email_off": "No mail was sent: this instance has no working SMTP setup. Check Configuration → Email / SMTP.",
"send_failed": "The mail couldn't be sent: the mail server was unreachable, or it refused the login or the message. Check Configuration → Email / SMTP, then try again.",
- "demo": "The demo account is shared, so it can't get a set-password link."
+ "demo": "The demo account is shared, so it can't get a set-password link"
},
"reason": {
"unreachable": "The server couldn't be reached. Try again.",
- "forbidden": "You don't have permission to do that.",
- "rateLimited": "Too many requests at once — wait a moment and try again.",
+ "forbidden": "You don't have permission to do that",
+ "rateLimited": "Too many requests at once — wait a moment and try again",
"unexpected": "Something went wrong in the app. Try again."
},
"writeFailed": {
@@ -70,12 +70,12 @@
"delete": "Couldn't delete the download",
"redownload": "Couldn't start the download again"
},
- "removeShared": "Couldn't remove the shared item",
+ "removeShared": "Couldn't remove the shared download",
"downloadDetails": "Couldn't save the download's details",
"enqueueDownload": "Couldn't queue the download",
"downloadProfileSave": "Couldn't save the download profile",
"downloadProfileDelete": "Couldn't delete the download profile",
- "downloadCookiesRemove": "Couldn't remove those cookies",
+ "downloadCookiesRemove": "Couldn't delete those cookies",
"notifRead": "Couldn't mark the notification as read",
"notifReadAll": "Couldn't mark the notifications as read",
"notifDismiss": "Couldn't dismiss the notification",
@@ -106,8 +106,8 @@
"body": "Couldn't reach the server — it may be restarting. This notice will clear once the connection is back."
},
"boundary": {
- "title": "Something went wrong.",
- "subtitle": "The app hit an unexpected error.",
+ "title": "Something went wrong",
+ "subtitle": "The app hit an unexpected error",
"reload": "Reload",
"notifTitle": "Something broke",
"notifMessage": "Unexpected error"
@@ -118,111 +118,111 @@
"unavailable": "YouTube didn't accept that just now. Please try again."
},
"refused": {
- "account_no_access": "That account no longer has access.",
- "account_signed_out": "That account was signed out elsewhere — sign in again.",
- "account_suspended": "That account is suspended.",
- "address_has_account": "That address already has an account — delete the account instead.",
- "admin_first": "Create the admin account first.",
- "admin_only": "Only an admin can do that.",
- "already_set_up": "This instance is already set up.",
- "already_subscribed": "You're already subscribed to this channel.",
- "channel_blocked": "You've blocked this channel.",
- "collection_fields_required": "A title and an item are both needed.",
- "collection_pending_sync": "The collection changed in Plex, but refreshing the local copy failed — it catches up on the next sync.",
- "collection_read_only": "This collection is read-only.",
- "collection_smart": "Smart and auto-list collections can't be made editable.",
- "demo_activate": "The demo account can't be activated.",
- "demo_delete": "The demo account can't be deleted.",
- "demo_role": "The demo account's role can't be changed.",
- "demo_suspend": "The demo account can't be suspended.",
- "download_not_ready": "This download isn't ready.",
- "edit_unfinished": "Only a finished download can be edited.",
- "filters_not_object": "The view's filters are malformed.",
+ "account_no_access": "That account no longer has access",
+ "account_signed_out": "That account was signed out elsewhere — sign in again",
+ "account_suspended": "That account is suspended",
+ "address_has_account": "That address already has an account — delete the account instead",
+ "admin_first": "Create the admin account first",
+ "admin_only": "Only an admin can do that",
+ "already_set_up": "This instance is already set up",
+ "already_subscribed": "You're already subscribed to this channel",
+ "channel_blocked": "You've blocked this channel",
+ "collection_fields_required": "A title and an item are both needed",
+ "collection_pending_sync": "The collection changed in Plex, but refreshing the local copy failed — it catches up on the next sync",
+ "collection_read_only": "This collection is read-only",
+ "collection_smart": "Smart and auto-list collections can't be made editable",
+ "demo_activate": "The demo account can't be activated",
+ "demo_delete": "The demo account can't be deleted",
+ "demo_role": "The demo account's role can't be changed",
+ "demo_suspend": "The demo account can't be suspended",
+ "download_not_ready": "This download isn't ready",
+ "edit_unfinished": "Only a finished download can be edited",
+ "filters_not_object": "The view's filters are malformed",
"google_no_userinfo": "Google didn't send back the account details. Try signing in again.",
- "image_fetch_failed": "The image couldn't be loaded.",
- "image_host": "Images from that address aren't loaded.",
- "invalid_cursor": "The list position is out of date — reload the page.",
- "invalid_email": "Enter a valid email address.",
- "invalid_interval": "The interval must be between {{min}} and {{max}} minutes.",
- "invalid_link": "That doesn't look like a valid video link.",
- "invalid_role": "Unknown role.",
- "invalid_status": "Unknown status.",
- "invite_decided": "This request has already been decided.",
- "job_running": "This job is already running.",
- "last_active_admin": "This is the only active admin — make someone else admin first.",
- "last_admin": "This is the only admin — make someone else admin first.",
- "link_gone": "This link is no longer available.",
- "link_required": "A video link or id is required.",
- "media_missing": "The media file isn't on disk.",
- "message_empty": "The message is empty.",
- "message_rate": "Too many messages — slow down a moment.",
- "message_self": "You can't message yourself.",
- "message_too_long": "The message is too long.",
- "messaging_set_up": "Secure messaging is already set up.",
- "name_required": "A name is required.",
- "no_encryption_key": "Set TOKEN_ENCRYPTION_KEY to store secrets in the database.",
- "no_messaging": "That member hasn't set up messaging yet.",
- "no_profile": "There's no download profile to use.",
- "no_subscription_id": "There's no YouTube subscription on record yet — sync your subscriptions first.",
- "not_authenticated": "You're not signed in.",
- "not_ready": "Not available yet.",
- "not_redownloadable": "This item can't be downloaded again.",
- "not_your_account": "That's not an account you've signed into here.",
- "only_admin_delete": "You're the only admin — make someone else admin before deleting your account.",
- "own_delete": "Delete your own account in Settings → Account.",
- "own_role": "You can't change your own role.",
- "own_suspend": "You can't suspend your own account.",
- "plex_disabled": "The Plex module is turned off.",
- "plex_not_configured": "Plex isn't set up — add the server address and token in Configuration → Plex.",
+ "image_fetch_failed": "Couldn't load the image",
+ "image_host": "Images from that address aren't loaded",
+ "invalid_cursor": "The list position is out of date — reload the page",
+ "invalid_email": "Enter a valid email address",
+ "invalid_interval": "The interval must be between {{min}} and {{max}} minutes",
+ "invalid_link": "That doesn't look like a valid video link",
+ "invalid_role": "Unknown role",
+ "invalid_status": "Unknown status",
+ "invite_decided": "This request has already been decided",
+ "job_running": "This job is already running",
+ "last_active_admin": "This is the only active admin — make someone else admin first",
+ "last_admin": "This is the only admin — make someone else admin first",
+ "link_gone": "This link is no longer available",
+ "link_required": "A video link or id is required",
+ "media_missing": "The media file isn't on disk",
+ "message_empty": "The message is empty",
+ "message_rate": "Too many messages — slow down a moment",
+ "message_self": "You can't message yourself",
+ "message_too_long": "The message is too long",
+ "messaging_set_up": "Secure messaging is already set up",
+ "name_required": "A name is required",
+ "no_encryption_key": "Set TOKEN_ENCRYPTION_KEY to store secrets in the database",
+ "no_messaging": "That member hasn't set up messaging yet",
+ "no_profile": "There's no download profile to use",
+ "no_subscription_id": "There's no YouTube subscription on record yet — sync your subscriptions first",
+ "not_authenticated": "You're not signed in",
+ "not_ready": "Not available yet",
+ "not_redownloadable": "This download can't be downloaded again",
+ "not_your_account": "That's not an account you've signed into here",
+ "only_admin_delete": "You're the only admin — make someone else admin before deleting your account",
+ "own_delete": "Delete your own account in Settings → Account",
+ "own_role": "You can't change your own role",
+ "own_suspend": "You can't suspend your own account",
+ "plex_disabled": "The Plex module is turned off",
+ "plex_not_configured": "Plex isn't set up — add the server address and token in Configuration → Plex",
"plex_unreachable": "The Plex server didn't answer. Check the address and the token, then try again.",
"plex_write_failed": "Plex didn't accept the change. Try again.",
- "profile_name_required": "A profile name is required.",
- "quota_reserve": "Today's shared YouTube budget is down to its reserve — try again later.",
+ "profile_name_required": "A profile name is required",
+ "quota_reserve": "Today's shared YouTube budget is down to its reserve — try again later",
"quota_spent": "Today's shared YouTube budget is spent. Try again tomorrow.",
- "recipient_required": "A recipient email is required.",
- "registration_closed": "Registration is currently closed.",
+ "recipient_required": "A recipient email is required",
+ "registration_closed": "Registration is currently closed",
"search_daily_limit": "You've reached today's YouTube search limit ({{limit}}). Try again tomorrow.",
- "search_term_required": "Enter something to search for.",
- "session_gone": "The playback session ended.",
- "setting_not_a_choice": "Pick one of: {{choices}}.",
- "setting_not_a_number": "Enter a whole number.",
- "setting_too_high": "The value can be at most {{max}}.",
- "setting_too_low": "The value must be at least {{min}}.",
- "setup_token": "The setup token is missing or wrong.",
- "share_own": "That's already your download.",
- "share_password_required": "This link needs a password.",
- "share_unfinished": "Only a finished download can be shared.",
- "smtp_failed": "The email couldn't be sent — check the SMTP settings.",
- "smtp_off": "SMTP isn't configured.",
- "spec_not_object": "The profile's settings are malformed.",
- "still_downloading": "This download is still in progress.",
- "tag_name_taken": "You already have a tag named “{{name}}”.",
- "title_required": "A title is required.",
+ "search_term_required": "Enter something to search for",
+ "session_gone": "The playback session ended",
+ "setting_not_a_choice": "Pick one of: {{choices}}",
+ "setting_not_a_number": "Enter a whole number",
+ "setting_too_high": "The value can be at most {{max}}",
+ "setting_too_low": "The value must be at least {{min}}",
+ "setup_token": "The setup token is missing or wrong",
+ "share_own": "That's already your download",
+ "share_password_required": "This link needs a password",
+ "share_unfinished": "Only a finished download can be shared",
+ "smtp_failed": "The email couldn't be sent — check the SMTP settings",
+ "smtp_off": "SMTP isn't configured",
+ "spec_not_object": "The profile's settings are malformed",
+ "still_downloading": "This download is still in progress",
+ "tag_name_taken": "You already have a tag named “{{name}}”",
+ "title_required": "A title is required",
"too_many_attempts": "Too many attempts. Try again shortly.",
"unknown_setting": "Unknown setting: {{key}}",
- "value_missing": "A value is required.",
- "video_gone": "This video is no longer available.",
- "view_name_taken": "You already have a view named “{{name}}”.",
- "watch_sync_off": "Turn on Plex watch-sync first.",
- "weak_password": "The password must be at least {{min}} characters.",
- "wrong_password": "The current password is incorrect.",
- "wrong_share_password": "Wrong password.",
- "youtube_editing_off": "Turn on YouTube editing in Settings first.",
- "youtube_read_scope": "Connect your YouTube account (read access) first.",
+ "value_missing": "A value is required",
+ "video_gone": "This video is no longer available",
+ "view_name_taken": "You already have a view named “{{name}}”",
+ "watch_sync_off": "Turn on Plex watch-sync first",
+ "weak_password": "The password must be at least {{min}} characters",
+ "wrong_password": "The current password is incorrect",
+ "wrong_share_password": "Wrong password",
+ "youtube_editing_off": "Turn on YouTube editing in Settings first",
+ "youtube_read_scope": "Connect your YouTube account (read access) first",
"debug_session_conflict": "A recording just started in another tab. Reload the page.",
- "issue_already_sent": "This report was already sent; its draft can no longer change.",
- "issue_title_required": "Give the report a title before you send it.",
- "screenshot_already_sent": "The report was already sent; this session takes no more images.",
- "screenshot_too_many": "A session can have at most {{limit}} screenshots.",
- "screenshot_empty": "The image is empty.",
- "screenshot_not_an_image": "This is not a PNG, JPEG or WebP image.",
+ "issue_already_sent": "This report was already sent; its draft can no longer change",
+ "issue_title_required": "Give the report a title before you send it",
+ "screenshot_already_sent": "The report was already sent; this session takes no more images",
+ "screenshot_too_many": "A session can have at most {{limit}} screenshots",
+ "screenshot_empty": "The image is empty",
+ "screenshot_not_an_image": "This is not a PNG, JPEG or WebP image",
"issues_not_configured": "No issue tracker is set up. Download the report as a zip instead.",
"issues_unreachable": "The issue tracker isn't answering. Try again later.",
- "issues_bad_token": "The issue tracker refused the token, or it lacks the permission.",
- "issues_repo_not_found": "The configured issue repository was not found.",
+ "issues_bad_token": "The issue tracker refused the token, or it lacks the permission",
+ "issues_repo_not_found": "The configured issue repository was not found",
"issues_public_repo": "The issue repository is public. Because of the log, reports go only to a private one.",
- "issues_disabled": "The issue repository has its issue tracker switched off.",
- "issues_rejected": "The issue tracker refused the request.",
+ "issues_disabled": "The issue repository has its issue tracker switched off",
+ "issues_rejected": "The issue tracker refused the request",
"issues_rate_limited": "You can send at most {{limit}} reports an hour. Try again later.",
"debug_session_rate_limited": "You can start at most {{limit}} new recordings an hour. Try again later.",
"export_rate_limited": "Too many downloads in a row. Wait a minute.",
diff --git a/frontend/src/i18n/locales/en/feed.json b/frontend/src/i18n/locales/en/feed.json
index b6081059..80f14326 100644
--- a/frontend/src/i18n/locales/en/feed.json
+++ b/frontend/src/i18n/locales/en/feed.json
@@ -12,16 +12,16 @@
"maxAge_other": "Last {{count}} days"
},
"loading": "Loading Feed…",
- "loadError": "Couldn't load the Feed.",
+ "loadError": "Couldn't load the Feed",
"emptyTitle": "Your Feed is empty",
"emptyBody": "Connect your YouTube account to import your subscriptions and build your Feed.",
"setUp": "Set up my Feed",
- "browseShared": "Or just browse the shared library",
+ "browseShared": "Or just browse the shared Library",
"demoEmptyTitle": "Nothing here in the demo",
- "demoEmptyBody": "The demo account has no subscriptions of its own — but the whole shared library is yours to explore, filter and sort.",
- "browseLibrary": "Browse the library",
+ "demoEmptyBody": "The demo account has no subscriptions of its own — but the whole shared Library is yours to explore, filter and sort.",
+ "browseLibrary": "Browse the Library",
"noMatches": "No videos match these filters.",
- "channelScopeHint": "This channel isn't in your Feed — it may be hidden. Its videos are in the shared library.",
+ "channelScopeHint": "This channel isn't in your Feed — it may be hidden. Its videos are in the shared Library.",
"viewInLibrary": "View in Library",
"sortLabel": "Sort",
"viewLabel": "View",
@@ -70,7 +70,7 @@
"back": "Back to Feed",
"searching": "Searching YouTube…",
"noResults": "No YouTube videos found.",
- "error": "YouTube search failed.",
+ "error": "Couldn't search YouTube",
"loadMore": "Load more (uses quota)",
"loadMoreFree": "Load more",
"show": "Show",
@@ -78,6 +78,6 @@
"clearNow": "Clear now",
"cleared_one": "Cleared {{count}} result",
"cleared_other": "Cleared {{count}} results",
- "showHint": "How many results to fetch at a time — Load more pulls another batch of this size."
+ "showHint": "How many results to fetch at a time — “Load more” pulls another batch of this size."
}
}
diff --git a/frontend/src/i18n/locales/en/header.json b/frontend/src/i18n/locales/en/header.json
index e682bf25..5b841765 100644
--- a/frontend/src/i18n/locales/en/header.json
+++ b/frontend/src/i18n/locales/en/header.json
@@ -5,7 +5,7 @@
"searchYoutubeTip": "Search live on YouTube for this term (uses shared API quota). You can also press Enter.",
"go": "Go",
"goTip": "Search (or press Enter)",
- "channelManager": "Channel manager",
+ "channelManager": "Channels",
"usageStats": "Usage & stats",
"scheduler": "Scheduler",
"configuration": "Configuration",
@@ -15,7 +15,7 @@
"my": "Mine",
"myTip": "Only videos from your own subscriptions",
"all": "Library",
- "allTip": "Every video in the shared catalog"
+ "allTip": "Every video in the shared Library"
},
"account": {
"admin": "Admin",
@@ -51,14 +51,14 @@
"resume": "Resume sync"
},
"banner": {
- "updated": "Updated to v{{version}} — see what's changed.",
+ "updated": "Updated to v{{version}} — see what's changed",
"releaseNotes": "Release notes",
"dismiss": "Dismiss"
},
"verify": {
- "confirmed": "Email address confirmed.",
- "confirmedNamed": "Email address confirmed — {{email}}.",
- "invalid": "That verification link is no longer valid — it has expired or was already used.",
+ "confirmed": "Email address confirmed",
+ "confirmedNamed": "Email address confirmed — {{email}}",
+ "invalid": "That verification link is no longer valid — it has expired or was already used",
"unreachable": "Couldn't reach the server to confirm that link, so it hasn't been used up. Try opening it again in a moment.",
"resetSignedOut": "Password-reset links open in a signed-out window. Sign out here, or open the link in a private window, and it will pick up where it left off.",
"reset": "The password this account registered with was cleared for security; set a new one before signing in."
diff --git a/frontend/src/i18n/locales/en/home.json b/frontend/src/i18n/locales/en/home.json
index 81685c8f..37203b9c 100644
--- a/frontend/src/i18n/locales/en/home.json
+++ b/frontend/src/i18n/locales/en/home.json
@@ -1,8 +1,8 @@
{
"navLabel": "Home",
"title": "What should I watch?",
- "error": "Could not load your suggestions.",
- "actionFailed": "Could not save that.",
+ "error": "Couldn't load your suggestions",
+ "actionFailed": "Couldn't save that",
"budget": {
"ask": "How much time do you have?",
"short": "10 minutes",
@@ -38,7 +38,7 @@
"moveLeft": "Left",
"moveRight": "Right",
"moveLast": "To the end",
- "moveFailed": "Could not reorder the shelf.",
+ "moveFailed": "Couldn't reorder the shelf",
"moveSaving": "Saving the new order…",
"openPlaylist": "Open the playlist"
},
@@ -46,9 +46,9 @@
"menu": "Comfort shelf",
"add": "Pin to comfort shelf",
"remove": "Remove from shelf",
- "added": "{{title}} is on your comfort shelf.",
- "removed": "{{title}} is off your comfort shelf.",
- "addFailed": "Could not pin it to the shelf.",
- "removeFailed": "Could not remove it from the shelf."
+ "added": "{{title}} is on your comfort shelf",
+ "removed": "{{title}} is off your comfort shelf",
+ "addFailed": "Couldn't pin it to the shelf",
+ "removeFailed": "Couldn't remove it from the shelf"
}
}
diff --git a/frontend/src/i18n/locales/en/inbox.json b/frontend/src/i18n/locales/en/inbox.json
index 72c67745..c6f8e71a 100644
--- a/frontend/src/i18n/locales/en/inbox.json
+++ b/frontend/src/i18n/locales/en/inbox.json
@@ -8,13 +8,13 @@
"markRead": "Mark read",
"dismiss": "Dismiss",
"rowMenu": "Notification actions",
- "menu": "Notification center actions",
+ "menu": "Notifications actions",
"sectionSystem": "System",
"sectionActivity": "Activity",
"andMore": "and {{count}} more",
"openScheduler": "Open Scheduler",
"openDownloads": "Open Downloads",
- "revertFailed": "Couldn't undo that — please try again.",
+ "revertFailed": "Couldn't undo that — please try again",
"downloadDone": {
"title": "Download ready"
},
@@ -28,11 +28,11 @@
},
"jobDone": {
"titleOk": "{{job}} finished",
- "titleError": "{{job}} failed"
+ "titleError": "Couldn't run {{job}}"
},
"emailUndelivered": {
"title": "Email is not going out",
"notConfigured": "SMTP is not configured, so nothing this instance sends is being delivered — verification links, password resets and access requests alike. One of them was “{{subject}}”. Set it up under Configuration → Email / SMTP.",
- "sendFailed": "Sending failed, so messages are not being delivered. One of them was “{{subject}}”. Check Configuration → Email / SMTP and send a test; the server log has the reason."
+ "sendFailed": "Couldn't send email, so messages are not being delivered. One of them was “{{subject}}”. Check Configuration → Email / SMTP and send a test; the server log has the reason."
}
}
diff --git a/frontend/src/i18n/locales/en/messages.json b/frontend/src/i18n/locales/en/messages.json
index dde9f393..5bb48d8b 100644
--- a/frontend/src/i18n/locales/en/messages.json
+++ b/frontend/src/i18n/locales/en/messages.json
@@ -5,7 +5,7 @@
"minimize": "Minimize",
"close": "Close",
"conversations": "Conversations",
- "new": "New message",
+ "new": "New message…",
"newTitle": "New message",
"empty": "No conversations yet. Start one with the New message button.",
"searchPeople": "Search people…",
@@ -27,9 +27,9 @@
"unlock": "Unlock",
"passphrase": "Passphrase",
"passphraseConfirm": "Confirm passphrase",
- "passTooShort": "Use at least 6 characters.",
- "passMismatch": "The passphrases don't match.",
- "wrongPass": "Wrong passphrase.",
+ "passTooShort": "Use at least 6 characters",
+ "passMismatch": "The passphrases don't match",
+ "wrongPass": "Wrong passphrase",
"setupFailed": "Couldn't set up secure messaging. Please try again.",
"forgotPass": "Forgot your passphrase?",
"resetTitle": "Reset secure messaging",
diff --git a/frontend/src/i18n/locales/en/nav.json b/frontend/src/i18n/locales/en/nav.json
index 3b63fa9e..f847f2bd 100644
--- a/frontend/src/i18n/locales/en/nav.json
+++ b/frontend/src/i18n/locales/en/nav.json
@@ -12,7 +12,7 @@
"channels": "Channels",
"playlists": "Playlists",
"plex": "Plex",
- "notifications": "Alerts",
+ "notifications": "Notifications",
"messages": "Messages",
"downloads": "Downloads",
"stats": "Stats",
diff --git a/frontend/src/i18n/locales/en/notifications.json b/frontend/src/i18n/locales/en/notifications.json
index 6dce3d40..7f3267df 100644
--- a/frontend/src/i18n/locales/en/notifications.json
+++ b/frontend/src/i18n/locales/en/notifications.json
@@ -4,6 +4,6 @@
"findInFeed": "Find in Feed",
"unhide": "Unhide",
"unwatch": "Unwatch",
- "openInManager": "Channel manager",
+ "openInManager": "Open in Channels",
"openOnYouTube": "Open on YouTube"
}
diff --git a/frontend/src/i18n/locales/en/onboarding.json b/frontend/src/i18n/locales/en/onboarding.json
index d39ff0ec..5259b23e 100644
--- a/frontend/src/i18n/locales/en/onboarding.json
+++ b/frontend/src/i18n/locales/en/onboarding.json
@@ -1,6 +1,6 @@
{
"close": "Close",
- "consent": "Google will show a <0>\"Google hasn't verified this app\"0> screen — that's expected, because Siftlode hasn't gone through Google's full review. It's safe to continue: click <1>Advanced1> → <2>Go to Siftlode2>. You can revoke access anytime from your <3>Google Account3>.",
+ "consent": "Google will show a <0>“Google hasn't verified this app”0> screen — that's expected, because Siftlode hasn't gone through Google's full review. It's safe to continue: click <1>Advanced1> → <2>Go to Siftlode2>. You can revoke access anytime from your <3>Google Account3>.",
"read": {
"title": "Connect your YouTube subscriptions",
"body": "You're signed in. To build your Feed, Siftlode needs <0>read-only0> access to the channels you're subscribed to on YouTube. It never posts, deletes, or changes anything with this.",
@@ -13,7 +13,7 @@
},
"write": {
"title": "You're connected",
- "importFailed": "Your YouTube account is connected, but importing subscriptions failed — you can retry from Settings → Sync. ",
+ "importFailed": "Your YouTube account is connected, but we couldn't import your subscriptions — you can retry from Settings → Sync. ",
"feedReady": "Your Feed is ready{{channels}}. Optionally, let Siftlode ",
"feedReadyChannels": " ({{count}} channels)",
"rationale": "<0>unsubscribe0> from channels on YouTube for you — this needs an extra write permission. Skip it to stay read-only; you can always enable it later in Settings.",
diff --git a/frontend/src/i18n/locales/en/playlists.json b/frontend/src/i18n/locales/en/playlists.json
index 2d1f3a51..8c5205a3 100644
--- a/frontend/src/i18n/locales/en/playlists.json
+++ b/frontend/src/i18n/locales/en/playlists.json
@@ -19,7 +19,8 @@
"delete": "Delete",
"rename": "Rename",
"confirmDelete": "Delete the playlist “{{name}}”? This can't be undone.",
- "addToPlaylist": "Add to playlist",
+ "addToPlaylist": "Add to playlist…",
+ "addToPlaylistTitle": "Add to playlist",
"exportToYoutube": "Export to YouTube",
"pushToYoutube": "Sync to YouTube",
"unsynced": "Unsynced",
@@ -29,8 +30,8 @@
"revertTitle": "Discard local changes?",
"revertMsg": "This reloads the playlist from YouTube and discards your unsynced local changes (order, additions, removals, rename). Continue?",
"revertConfirm": "Discard & reload",
- "revertDone": "“{{name}}” reloaded from YouTube ✓",
- "revertFailed": "Couldn't reload from YouTube.",
+ "revertDone": "“{{name}}” synced from YouTube",
+ "revertFailed": "Couldn't sync from YouTube",
"pushTitle": "Sync to YouTube",
"pushConfirm": "Sync",
"pushPlanCreate": "Create a new YouTube playlist with {{count}} videos? (~{{units}} quota units; {{left}} left today.)",
@@ -50,19 +51,19 @@
"mergeQuota": " (~{{units}} quota units; {{left}} left today.)",
"mergeQuotaFree": " (~0 quota units.)",
"pushNoQuota": "Not enough YouTube quota left today ({{left}}) for this sync (~{{units}} needed). Try again tomorrow.",
- "pushUpToDate": "“{{name}}” is already in sync with YouTube.",
- "pushDone": "“{{name}}” synced to YouTube ✓",
- "pushPartial": "Synced, but {{count}} item(s) were skipped.",
- "pushFailed": "Couldn't sync to YouTube.",
+ "pushUpToDate": "“{{name}}” is already in sync with YouTube",
+ "pushDone": "“{{name}}” synced to YouTube",
+ "pushPartial": "Synced, but {{count}} video(s) were skipped",
+ "pushFailed": "Couldn't sync to YouTube",
"deleteHereOnly": "Delete here only",
- "deleteYtFailed": "Couldn't delete on YouTube; removed here only.",
+ "deleteYtFailed": "Couldn't delete on YouTube; deleted here only",
"sortLabel": "Sort",
"dirAsc": "Ascending",
"dirDesc": "Descending",
"groupByChannel": "Group by channel",
"railSortCustom": "Custom order",
"railSortName": "Name",
- "railSortCount": "Item count",
+ "railSortCount": "Video count",
"railSortDuration": "Total length",
"railSortRecent": "Recently changed",
"dirtyFirst": "Unsynced first",
@@ -72,27 +73,27 @@
"rowMenu": "Video actions",
"moveTo": "Move to another list",
"moveToRecent": "Move to “{{name}}”",
- "moveNoTargets": "There is no other playlist to move it to yet.",
- "movedToast": "“{{title}}” moved to “{{name}}” ✓",
- "moveFailed": "Couldn't move the video.",
+ "moveNoTargets": "There is no other playlist to move it to yet",
+ "movedToast": "“{{title}}” moved to “{{name}}”",
+ "moveFailed": "Couldn't move the video",
"removedToast": "“{{title}}” removed from the playlist",
- "undoRemoveFailed": "Couldn't put the video back.",
- "removeFailed": "Couldn't remove the video.",
+ "undoRemoveFailed": "Couldn't put the video back",
+ "removeFailed": "Couldn't remove the video",
"errors": {
- "name_required": "Give the playlist a name.",
- "youtube_read_scope": "Connect YouTube (read access) to sync your playlists.",
- "not_on_youtube": "This playlist isn't on YouTube.",
- "watch_later_not_pushable": "The Watch later list can't be sent to YouTube.",
- "watch_later_undeletable": "The Watch later list can't be deleted.",
- "youtube_editing_off": "Enable YouTube editing in Settings first.",
- "quota_exceeded": "Not enough YouTube quota left today for this sync."
+ "name_required": "Give the playlist a name",
+ "youtube_read_scope": "Connect YouTube (read access) to sync your playlists",
+ "not_on_youtube": "This playlist isn't on YouTube",
+ "watch_later_not_pushable": "The Watch later list can't be sent to YouTube",
+ "watch_later_undeletable": "The Watch later list can't be deleted",
+ "youtube_editing_off": "Enable YouTube editing in Settings first",
+ "quota_exceeded": "Not enough YouTube quota left today for this sync"
},
"moveErrors": {
- "not_in_playlist": "That video isn't in this playlist any more — it changed somewhere else.",
- "same_playlist": "That video is already in this playlist."
+ "not_in_playlist": "That video isn't in this playlist any more — it changed somewhere else",
+ "same_playlist": "That video is already in this playlist"
},
"railMenu": "Playlist actions",
- "builtinLocked": "The built-in {{name}} list can’t be renamed or deleted.",
+ "builtinLocked": "The built-in “{{name}}” list can’t be renamed or deleted.",
"removeItem": "Remove",
"removeItemOf": "Remove “{{title}}” from the playlist",
"dragHandleOf": "Reorder “{{title}}”",
@@ -102,10 +103,10 @@
"saveOrder": "Save this order",
"filteredCount": "{{count}} shown",
"noneMatchFilter": "No videos match the filter.",
- "loadFailed": "Couldn't load this playlist.",
- "reorderFailed": "Couldn't save the new order.",
- "renameFailed": "Couldn't rename the playlist.",
- "deleteFailed": "Couldn't delete the playlist.",
+ "loadFailed": "Couldn't load this playlist",
+ "reorderFailed": "Couldn't save the new order",
+ "renameFailed": "Couldn't rename the playlist",
+ "deleteFailed": "Couldn't delete the playlist",
"confirmDeleteLinked": "Delete the playlist “{{name}}”? It is synced with YouTube — choose whether to delete it there as well. This can't be undone.",
"dragBlockedBuiltin": "This list's order is fixed — it can't be reordered by hand.",
"deleteBoth": "Delete here and on YouTube",
@@ -114,7 +115,7 @@
"viewDiffers": "Temporary view ({{view}}) — the playlist's saved order is unchanged. Save it to keep this order.",
"viewMatches": "This view ({{view}}) already matches the playlist's saved order, so there is nothing to save.",
"viewFiltered": "Filtered view — {{shown}} of {{total}} shown. The order can't be saved while a filter is on.",
- "addFailed": "Couldn't add it to the playlist.",
+ "addFailed": "Couldn't add it to the playlist",
"limits": {
"hint": "Missing a list?",
"why": "Why?",
diff --git a/frontend/src/i18n/locales/en/plex.json b/frontend/src/i18n/locales/en/plex.json
index 3bcb1989..ad5882d8 100644
--- a/frontend/src/i18n/locales/en/plex.json
+++ b/frontend/src/i18n/locales/en/plex.json
@@ -5,11 +5,11 @@
"playAll": "Play all",
"shuffle": "Shuffle",
"rename": "Rename",
- "addItem": "Add title",
+ "addItem": "Add title…",
"delete": "Delete",
"enableEdit": "Enable editing",
"removeItem": "Remove from collection",
- "removeConfirm": "Remove \"{{title}}\" from this collection? This affects all users on Plex.",
+ "removeConfirm": "Remove “{{title}}” from this collection? This affects all users on Plex.",
"nameMaybeTaken": "A collection with this name may already exist in another library — you can still create it here.",
"readOnly": "Read-only",
"empty": "This collection has no items.",
@@ -20,16 +20,17 @@
"addNoResults": "No matches.",
"add": "Add",
"added": "Added",
- "deleteConfirm": "Delete the collection \"{{title}}\"? This removes it from Plex too.",
- "editFailed": "Collection edit failed",
- "playFailed": "Couldn't build the play queue — try again.",
- "playEmpty": "Nothing to play in this collection yet.",
- "newCollection": "New collection",
+ "deleteConfirm": "Delete the collection “{{title}}”? This removes it from Plex too.",
+ "editFailed": "Couldn't edit the collection",
+ "playFailed": "Couldn't build the play queue — try again",
+ "playEmpty": "Nothing to play in this collection yet",
+ "newCollection": "New collection…",
+ "newCollectionTitle": "New collection",
"namePlaceholder": "Collection name",
- "nameTaken": "A collection with this name already exists.",
+ "nameTaken": "A collection with this name already exists",
"newHint": "Pick the first title to add — a collection needs at least one.",
"createWith": "Create",
- "deleteFailed": "Couldn't delete the collection."
+ "deleteFailed": "Couldn't delete the collection"
},
"searchPlaceholder": "Search Plex…",
"searchField": {
@@ -78,13 +79,13 @@
"duration": "Length",
"durationUnit": "min",
"durationOpt": {
- "short": "Under 90m",
- "mid": "90–120m",
- "long": "Over 2h"
+ "short": "Under 90 min",
+ "mid": "90–120 min",
+ "long": "Over 2 hr"
},
"added": "Added to Plex",
"addedOpt": {
- "24h": "24h",
+ "24h": "24 hours",
"1w": "1 week",
"1m": "1 month",
"6m": "6 months",
@@ -128,7 +129,7 @@
"markNextWatched": "Mark S{{season}}·E{{episode}} watched",
"markNextUnwatched": "Mark S{{season}}·E{{episode}} unwatched",
"nextUp": "Next: S{{season}}·E{{episode}}",
- "watchedFailed": "Couldn't update the watched state — please try again.",
+ "watchedFailed": "Couldn't update the watched state — please try again",
"seasons": "{{count}} seasons",
"unified": {
"titles": "Titles",
@@ -157,9 +158,9 @@
"none": "No editable collections yet. Create one above.",
"count": "{{count}} items",
"delete": "Delete",
- "deleteConfirm": "Delete the collection \"{{title}}\" from Plex? This affects all users.",
+ "deleteConfirm": "Delete the collection “{{title}}” from Plex? This affects all users.",
"note": "Changes are written to Plex and shared across all clients and users.",
- "others": "Manage {{count}} existing collections…",
+ "others": "Manage {{count}} existing collections",
"makeEditable": "Manage"
},
"player": {
@@ -174,8 +175,8 @@
"restart": "Restart from beginning",
"prev": "Previous episode",
"next": "Next episode",
- "tapBack": "−{{n}} s",
- "tapForward": "+{{n}} s",
+ "tapBack": "−{{d}}",
+ "tapForward": "+{{d}}",
"mute": "Mute (m)",
"volume": "Volume",
"fullscreen": "Fullscreen (f)",
@@ -222,9 +223,9 @@
"subStyle": "Subtitles",
"subPreparing": "Preparing subtitles: {{label}}…",
"subUnavailable": {
- "bitmap": "This subtitle is image-based and can't be shown as text.",
- "missing": "This subtitle track isn't available.",
- "failed": "This subtitle couldn't be loaded."
+ "bitmap": "This subtitle is image-based and can't be shown as text",
+ "missing": "This subtitle track isn't available",
+ "failed": "Couldn't load this subtitle"
},
"subSize": "Text size",
"subPos": "Position",
@@ -274,7 +275,7 @@
"audience": "Audience",
"keywords": "Keywords",
"crew": "Crew",
- "filterValue": "Filter by \"{{name}}\"",
+ "filterValue": "Filter by “{{name}}”",
"facts": {
"title": "Details",
"release": "Released",
@@ -294,23 +295,23 @@
"transcode_sw": "Transcoded in software (heaviest)"
},
"playlistAdd": {
- "manage": "Add to playlist",
+ "manage": "Add to playlist…",
"title": "Add to playlist — {{title}}",
"newPlaceholder": "New playlist name…",
"create": "Create",
"none": "No playlists yet. Create one above.",
"count": "{{count}} items",
"groupProgress": "{{in}} / {{size}}",
- "addFailed": "Couldn't add it to the playlist.",
- "removeFailed": "Couldn't remove it from the playlist.",
- "createFailed": "Couldn't create the playlist."
+ "addFailed": "Couldn't add it to the playlist",
+ "removeFailed": "Couldn't remove it from the playlist",
+ "createFailed": "Couldn't create the playlist"
},
"playlist": {
"section": "Playlists",
"pickerPlaceholder": "Find or create a playlist…",
- "createNamed": "Create \"{{name}}\"",
+ "createNamed": "Create “{{name}}”",
"none": "No playlists yet.",
- "deleteConfirm": "Delete the playlist \"{{title}}\"?",
+ "deleteConfirm": "Delete the playlist “{{title}}”?",
"rename": "Rename",
"playAll": "Play all",
"delete": "Delete playlist",
@@ -326,10 +327,10 @@
"addShow": "Add whole show to a playlist",
"addSeason": "Add season to a playlist",
"addEpisode": "Add episode to a playlist",
- "removeFailed": "Couldn't remove it from the playlist.",
- "renameFailed": "Couldn't rename the playlist.",
- "deleteFailed": "Couldn't delete the playlist.",
- "reorderFailed": "Couldn't save the new order.",
+ "removeFailed": "Couldn't remove it from the playlist",
+ "renameFailed": "Couldn't rename the playlist",
+ "deleteFailed": "Couldn't delete the playlist",
+ "reorderFailed": "Couldn't save the new order",
"reorderItem": "Move “{{title}}”",
"rowMenu": "Item actions",
"menu": "Playlist actions",
@@ -337,7 +338,7 @@
"playSeason": "Play the season",
"removeFromPlaylist": "Remove from the playlist",
"removedToast": "“{{title}}” removed from the playlist",
- "undoRemoveFailed": "Couldn't put it back in the playlist."
+ "undoRemoveFailed": "Couldn't put it back in the playlist"
},
"people": {
"count_one": "{{count}} title",
diff --git a/frontend/src/i18n/locales/en/quotaActions.json b/frontend/src/i18n/locales/en/quotaActions.json
index 7bec3cd4..73aa3b7e 100644
--- a/frontend/src/i18n/locales/en/quotaActions.json
+++ b/frontend/src/i18n/locales/en/quotaActions.json
@@ -1,7 +1,7 @@
{
- "subscriptions_pull": "Read subscriptions",
+ "subscriptions_pull": "Sync subscriptions",
"subscriptions_resync": "Auto subscription resync",
- "playlists_pull": "Read playlists",
+ "playlists_pull": "Sync playlists",
"playlists_push": "Push playlists",
"playlists_delete": "Delete playlist",
"videos_backfill_recent": "Recent backfill",
diff --git a/frontend/src/i18n/locales/en/recorder.json b/frontend/src/i18n/locales/en/recorder.json
index 9f214303..4201151f 100644
--- a/frontend/src/i18n/locales/en/recorder.json
+++ b/frontend/src/i18n/locales/en/recorder.json
@@ -29,8 +29,8 @@
"shotFailed": "Couldn't save the screenshot",
"stopFailed": "Couldn't stop recording",
"stopped": "Recording stopped",
- "expired_one": "The recording reached its {{count}}-minute limit and stopped.",
- "expired_other": "The recording reached its {{count}}-minute limit and stopped."
+ "expired_one": "The recording reached its {{count}}-minute limit and stopped",
+ "expired_other": "The recording reached its {{count}}-minute limit and stopped"
},
"settings": {
"title": "Debug session",
@@ -46,7 +46,7 @@
},
"composer": {
"title": "Bug report",
- "meta": "{{start}} · {{minutes}} min · {{level}}",
+ "meta": "{{start}} · {{minutes}} · {{level}}",
"fieldTitle": "Title",
"titlePlaceholder": "What went wrong?",
"description": "Description",
@@ -61,7 +61,7 @@
"stepPlaceholder": "What did you do?",
"withShot": "image",
"addStep": "Add a step",
- "removeStep": "Remove step {{n}}",
+ "removeStep": "Delete step {{n}}",
"environment": "Environment — automatic",
"attachments": "Attachments — what is sent, each one removable",
"clientLog": "Browser log",
@@ -75,7 +75,7 @@
"reviewDone": "Done",
"keepRow": "Keep line: {{event}} {{msg}}",
"screenshot": "Screenshot {{n}}",
- "removeShot": "Remove screenshot {{n}}",
+ "removeShot": "Delete screenshot {{n}}",
"drop": "Paste an image with Ctrl+V, drop it here, or",
"pick": "choose a file",
"close": "Close",
@@ -85,7 +85,7 @@
"saveFailed": "Couldn't save the draft",
"loadFailed": "Couldn't load the session",
"truncated": "The logs carry the first {{kept}} of {{total}} lines.",
- "sent": "Sent as an issue.",
+ "sent": "Sent as an issue",
"openIssue": "Open the issue",
"env": {
"version": "Version",
@@ -99,7 +99,7 @@
"sentOk": "Issue created",
"sendFailed": "Couldn't send the report",
"target": "To: {{repo}} · private",
- "partial": "The issue was created, but not every attachment went: {{files}}.",
+ "partial": "The issue was created, but not every attachment went: {{files}}",
"retry": "Send the missing ones",
"edit": "Edit",
"preview": "Preview",
@@ -107,8 +107,8 @@
"previewTitle": "Preview of the report",
"previewLocal": "Without an issue tracker, this is the zip's issue.md.",
"shotsFailed": "Couldn't load the screenshots",
- "removeShotFailed": "Couldn't remove the screenshot",
- "partialImages": "The issue was created, but its text does not show the screenshots yet.",
+ "removeShotFailed": "Couldn't delete the screenshot",
+ "partialImages": "The issue was created, but its text does not show the screenshots yet",
"retryImages": "Show them in the issue",
"sendingNow": "The report is being sent…",
"exportFailed": "Couldn't build the zip"
diff --git a/frontend/src/i18n/locales/en/scheduler.json b/frontend/src/i18n/locales/en/scheduler.json
index 79a99790..9ffa94f2 100644
--- a/frontend/src/i18n/locales/en/scheduler.json
+++ b/frontend/src/i18n/locales/en/scheduler.json
@@ -24,7 +24,7 @@
"quotaRemaining": "{{formatted}} left",
"quotaNote": "Shared YouTube Data API budget; resets at midnight US Pacific. Backfill yields to leave headroom for interactive use.",
"minutes": "min",
- "range": "Must be between {{min}} and {{max}}.",
+ "range": "Must be between {{min}} and {{max}}",
"editInterval": "Click to change how often this runs",
"intervalFailed": "Couldn't change the interval",
"runNow": "Run now",
@@ -85,8 +85,8 @@
"backfill": "Backfill (recent + full history)",
"autotag": "Auto-tagging",
"shorts": "Shorts classification",
- "subscriptions": "Subscription resync",
- "playlist_sync": "YouTube playlist sync",
+ "subscriptions": "Subscription sync from YouTube",
+ "playlist_sync": "Playlist sync from YouTube",
"maintenance": "Maintenance + validation",
"demo_reset": "Demo account reset",
"explore_cleanup": "Explored-channel cleanup",
@@ -111,7 +111,7 @@
"liveRefreshHint": "Live/upcoming videos (and just-ended streams without a duration yet) re-checked until they settle."
},
"purgeDiscovery": "Purge discovery",
- "purgeDiscoveryHint": "Remove all un-kept discovery content now (search results nobody watched/saved, and explored-but-unsubscribed channels) — ignoring the grace period.",
+ "purgeDiscoveryHint": "Delete all un-kept discovery content now (search results nobody watched/saved, and explored-but-unsubscribed channels) — ignoring the grace period.",
"purgeConfirmTitle": "Purge discovery content?",
- "purgedDiscovery": "Removed {{count}} discovery items"
+ "purgedDiscovery": "Deleted {{count}} discovery items"
}
diff --git a/frontend/src/i18n/locales/en/settings.json b/frontend/src/i18n/locales/en/settings.json
index 0f448759..17b5c5e1 100644
--- a/frontend/src/i18n/locales/en/settings.json
+++ b/frontend/src/i18n/locales/en/settings.json
@@ -42,7 +42,7 @@
},
"notifications": {
"title": "Notifications",
- "sound": "Sound on alerts",
+ "sound": "Sound on notifications",
"soundHint": "Play a short tone when a notification needs your attention (e.g. errors, prompts).",
"autoDismiss": "Auto-dismiss toasts",
"autoDismissHint": "When off, pop-up toasts stay until you close them. When on, they fade after the set time; one with a button (Undo) stays at least 8 seconds.",
@@ -65,7 +65,7 @@
"writeEnableHint": "Lets Siftlode unsubscribe from channels on YouTube for you. Optional — skip it to stay read-only.",
"granted": "Granted",
"enable": "Enable",
- "enableHint": "Redirects to Google to grant the permission. Google shows an 'unverified app' notice — that's expected; click Advanced → Go to Siftlode.",
+ "enableHint": "Redirects to Google to grant the permission. Google shows an “unverified app” notice — that's expected; click Advanced → Go to Siftlode.",
"walkMeThrough": "Walk me through setup",
"dangerZone": "Danger zone",
"deleteHint": "Permanently delete your account and all your data — subscriptions, tags, watch history, playlists and settings. This can't be undone.",
@@ -73,7 +73,7 @@
"deleteTitle": "Delete your account?",
"deleteConfirm": "This permanently erases your account and all your data (subscriptions, tags, watch/saved/hidden state, playlists, settings). It can't be undone.",
"deleteConfirmButton": "Delete everything",
- "demoNotice": "This is the shared demo account. It has no YouTube connection — you can browse the whole shared library, filter and sort it, build playlists and try things out, but nothing touches a real YouTube account.",
+ "demoNotice": "This is the shared demo account. It has no YouTube connection — you can browse the whole shared Library, filter and sort it, build playlists and try things out, but nothing touches a real YouTube account.",
"signInMethods": "Sign-in methods",
"googleLink": {
"title": "Google account",
@@ -81,13 +81,13 @@
"connectedHint": "Your Google account is linked. You can sign in with Google and grant YouTube access below.",
"connectHint": "Link a Google account to sign in with Google and to enable YouTube access for your Feed.",
"connect": "Connect Google",
- "linked": "Google account linked.",
- "conflict": "That Google account is already linked to a different Siftlode account.",
+ "linked": "Google account linked",
+ "conflict": "That Google account is already linked to a different Siftlode account",
"mismatch": "That's a different Google account than the one already linked here. Sign in with the linked one.",
"error": "Couldn't link the Google account. Please try again.",
"signedInAs": "Signed in with Google as"
},
- "googleCancelled": "Google sign-in was cancelled.",
+ "googleCancelled": "Google sign-in was cancelled",
"password": {
"title": "Password",
"setHint": "A password is set. You can sign in with your email and password.",
@@ -98,8 +98,8 @@
"new": "New password (min. 10 characters)",
"save": "Save password",
"saving": "Saving…",
- "saved": "Password updated for {{email}}.",
- "failed": "Couldn't update the password."
+ "saved": "Password updated for {{email}}",
+ "failed": "Couldn't update the password"
},
"sessions": {
"title": "Active sessions",
@@ -107,7 +107,7 @@
"action": "Log out other sessions",
"confirmTitle": "Log out other sessions?",
"confirmBody": "This signs you out of every other browser and device. You'll stay signed in here.",
- "done": "Signed out of all other sessions.",
+ "done": "Signed out of all other sessions",
"failed": "Couldn't sign out other sessions. Please try again."
}
},
@@ -116,21 +116,21 @@
"intro": "Some sites (e.g. a private Facebook group you belong to) only let logged-in members view a video. Paste your browser cookies for a site here and your downloads from it will use your login. Cookies are encrypted, kept private to your account, and only ever sent to the matching site.",
"configured": "Set · updated {{when}}",
"notSet": "Not set",
- "set": "Set cookies",
- "update": "Update",
- "remove": "Remove",
+ "set": "Set cookies…",
+ "update": "Update…",
+ "remove": "Delete",
"save": "Save",
"cancel": "Cancel",
"saving": "Saving…",
"placeholder": "Paste the contents of your cookies.txt (Netscape format) here",
"help": "Export a cookies.txt for this site with a browser extension, then paste it here. It's write-only — we never show it back.",
- "saved": "Cookies saved.",
- "removed": "Cookies removed.",
- "failed": "Couldn't save those cookies.",
- "removeConfirmTitle": "Remove cookies?",
+ "saved": "Cookies saved",
+ "removed": "Cookies deleted",
+ "failed": "Couldn't save those cookies",
+ "removeConfirmTitle": "Delete cookies?",
"removeConfirmBody": "Downloads from this site will stop using your login.",
- "removeConfirmAction": "Remove",
- "loadError": "Couldn't load your download authentication settings."
+ "removeConfirmAction": "Delete",
+ "loadError": "Couldn't load your download authentication settings"
},
"demo": {
"title": "Demo access",
@@ -160,14 +160,14 @@
"add": "Add",
"decided": "{{count}} decided",
"approved": "Approved {{email}} — they can sign in now",
- "approveFailed": "Approve failed",
- "denyFailed": "Deny failed",
+ "approveFailed": "Couldn't approve the request",
+ "denyFailed": "Couldn't deny the request",
"addedToWhitelist": "{{email}} added to the whitelist",
"inviteHint": "Send them an invitation. Adding an address never mails anyone — this does.",
"invitedAt": "Invited {{time}}. Sending again will mail them a second time.",
"inviteLabel": "Send an invitation to {{email}}",
- "invited": "Invitation sent to {{email}}.",
- "inviteFailed": "Couldn't send that invitation.",
+ "invited": "Invitation sent to {{email}}",
+ "inviteFailed": "Couldn't send that invitation",
"inviteTitle": "Send invitation?",
"inviteConfirm": "Email {{email}} to say their address is whitelisted and Siftlode is open for them.",
"inviteConfirmAgain": "You already invited {{email}} on {{time}}. Sending now writes to them a second time.",
@@ -188,11 +188,11 @@
"deleteTitle": "Delete whitelist entry?",
"deleteConfirm": "Forget {{email}}? Siftlode drops the record, so anyone at that address would have to ask again.",
"deleteHasAccount": "Someone has registered on this address — manage it under Users & roles, where deleting the account removes this entry too.",
- "deleted": "Whitelist entry removed.",
- "deleteFailed": "Could not remove the entry.",
- "emailOff": "Email sending is not configured, so nobody gets a verification link and no request reaches you by email. Watch this list and your inbox instead, and fix it under Configuration → Email / SMTP.",
- "deletedStillAllowed": "Entry removed, but this address is still allow-listed by the server's configuration, so it keeps access. Remove it from ALLOWED_EMAILS / ADMIN_EMAILS to change that.",
- "deleteConfirmStillAllowed": "Forget {{email}}? Careful: this address is also allow-listed in the server's configuration (ALLOWED_EMAILS / ADMIN_EMAILS), so removing this row will NOT take its access away — and an ADMIN_EMAILS address is made an admin again on its next Google sign-in."
+ "deleted": "Whitelist entry deleted",
+ "deleteFailed": "Couldn't delete the entry",
+ "emailOff": "Email sending is not configured, so nobody gets a verification link and no request reaches you by email. Watch this list and your Notifications instead, and fix it under Configuration → Email / SMTP.",
+ "deletedStillAllowed": "Entry deleted, but this address is still allow-listed by the server's configuration, so it keeps access. Remove it from ALLOWED_EMAILS / ADMIN_EMAILS to change that.",
+ "deleteConfirmStillAllowed": "Forget {{email}}? Careful: this address is also allow-listed in the server's configuration (ALLOWED_EMAILS / ADMIN_EMAILS), so deleting this row will NOT take its access away — and an ADMIN_EMAILS address is made an admin again on its next Google sign-in."
},
"diagnostics": {
"title": "Logging level",
@@ -215,15 +215,13 @@
"server_one": "The Siftlode server receives this too and keeps it for {{count}} day, so the problem can be tracked down.",
"server_other": "The Siftlode server receives this too and keeps it for {{count}} days, so the problem can be tracked down.",
"never": "Passwords, tokens, email addresses and the content of your messages are never included.",
- "lapse": "After 24 hours it goes back to Basic by itself.",
+ "lapse": "After 24 hours it goes back to “Basic” by itself.",
"policy": "Privacy policy",
"cancel": "Cancel",
"confirm": "Turn on for 24 hours"
},
- "status": "{{level}} logging · back to Basic in {{time}}",
- "hoursMinutes": "{{h}} h {{m}} min",
- "minutes": "{{m}} min",
- "revert": "Back to Basic now",
+ "status": "{{level}} logging · back to “Basic” in {{time}}",
+ "revert": "Back to “Basic” now",
"demo": "The demo account is shared by several people, so detailed logging can't be turned on here.",
"failed": "Couldn't change the logging level"
},
diff --git a/frontend/src/i18n/locales/en/setup.json b/frontend/src/i18n/locales/en/setup.json
index 7272d0b3..7d70bc8c 100644
--- a/frontend/src/i18n/locales/en/setup.json
+++ b/frontend/src/i18n/locales/en/setup.json
@@ -8,15 +8,15 @@
"desc": "This is the first account and an administrator. You'll sign in with this email and password once setup is done.",
"email": "Email address",
"password": "Password (min. {{n}} characters)",
- "invalidEmail": "Enter a valid email address.",
- "shortPassword": "Password must be at least {{n}} characters."
+ "invalidEmail": "Enter a valid email address",
+ "shortPassword": "Password must be at least {{n}} characters"
},
"google": {
"title": "Sign in with Google (optional)",
"desc": "Paste your Google OAuth client ID and secret to enable Google sign-in and YouTube access. Leave both empty to skip — email+password works without it.",
"clientId": "Google client ID",
"clientSecret": "Google client secret",
- "bothRequired": "Enter both the client ID and secret, or leave both empty to skip.",
+ "bothRequired": "Enter both the client ID and secret, or leave both empty to skip",
"skipHint": "Leave empty to set this up later from the admin Configuration page."
},
"smtp": {
@@ -29,8 +29,8 @@
"password": "SMTP password",
"testTo": "Send a test email to…",
"test": "Send test",
- "testOk": "Test email sent — check the inbox.",
- "testFail": "Couldn't send — check the settings.",
+ "testOk": "Test email sent — check the inbox",
+ "testFail": "Couldn't send — check the settings",
"skipHint": "Leave empty to set this up later from the admin Configuration page."
},
"finish": {
@@ -46,5 +46,5 @@
"title": "Setup link needed",
"body": "Open the setup wizard using the link printed in the container logs at first start (it looks like …/setup?token=…). It's required so only the operator can configure this instance."
},
- "genericError": "Something went wrong — please try again."
+ "genericError": "Something went wrong — please try again"
}
diff --git a/frontend/src/i18n/locales/en/sidebar.json b/frontend/src/i18n/locales/en/sidebar.json
index 28e3db2a..57dc5ad8 100644
--- a/frontend/src/i18n/locales/en/sidebar.json
+++ b/frontend/src/i18n/locales/en/sidebar.json
@@ -36,7 +36,7 @@
"year": "Year"
},
"year": {
- "more": "More…",
+ "more": "More",
"moreLabel": "All years",
"none": "No years yet",
"sortBy": "Sort",
@@ -54,11 +54,11 @@
"content": {
"normal": "Normal",
"shorts": "Shorts",
- "live": "Live / Upcoming"
+ "live": "Live / upcoming"
},
"datePresetLabel": "Upload date",
"datePreset": {
- "24h": "24h",
+ "24h": "24 hours",
"1week": "1 week",
"2weeks": "2 weeks",
"1month": "1 month",
diff --git a/frontend/src/i18n/locales/en/stats.json b/frontend/src/i18n/locales/en/stats.json
index c47f36b4..0f660043 100644
--- a/frontend/src/i18n/locales/en/stats.json
+++ b/frontend/src/i18n/locales/en/stats.json
@@ -4,14 +4,14 @@
"system": "System"
},
"title": "API quota usage",
- "rangeDays": "{{count}}d",
+ "rangeDays": "{{d}}",
"rangeLabel": "Time range",
"introBefore": "YouTube Data API units attributed by who triggered the spend. ",
"introSystem": "System",
"introAfter": " is shared background work (scheduled backfill, enrichment, resync) that isn't tied to one person.",
"loading": "Loading…",
"noData": "No data.",
- "dailyTotal": "Daily total ({{count}}d · {{units}} units)",
+ "dailyTotal": "Daily total ({{d}} · {{units}} units)",
"noUsageInRange": "No usage in this range.",
"dailyTooltip": "{{day}}: {{units}} units",
"system": "System (background)",
@@ -36,15 +36,15 @@
"last7d": "Last 7 days",
"last30d": "Last 30 days",
"allTime": "All time",
- "byAction": "By action (30d)",
+ "byAction": "By action (30 days)",
"noUsage": "No usage yet.",
"actions": "Actions",
- "syncSubscriptions": "Sync subscriptions",
+ "syncSubscriptions": "Sync subscriptions from YouTube",
"syncSubscriptionsHint": "Re-import your YouTube subscriptions and pull in any newly-followed channels.",
"backfillEverything": "Backfill everything",
"backfillEverythingHint": "Request full back-catalog backfill for every channel you're subscribed to. Older videos + complete search fill in as the shared daily quota allows.",
"synced": "Synced {{count}} subscriptions",
- "syncFailed": "Sync failed",
+ "syncFailed": "Couldn't sync subscriptions",
"fullHistoryRequested": "Full history requested for {{count}} channels",
"fullHistoryFailed": "Couldn't request full history",
"admin": "Admin",
diff --git a/frontend/src/i18n/locales/en/tagManager.json b/frontend/src/i18n/locales/en/tagManager.json
index d6f2709f..10e50c74 100644
--- a/frontend/src/i18n/locales/en/tagManager.json
+++ b/frontend/src/i18n/locales/en/tagManager.json
@@ -9,6 +9,6 @@
"empty": "No tags yet — add one below.",
"newPlaceholder": "New tag name…",
"add": "Add",
- "renameFailed": "Couldn't rename the tag.",
- "deleteFailed": "Couldn't delete the tag."
+ "renameFailed": "Couldn't rename the tag",
+ "deleteFailed": "Couldn't delete the tag"
}
diff --git a/frontend/src/i18n/locales/en/users.json b/frontend/src/i18n/locales/en/users.json
index da41c7fb..f07a73bc 100644
--- a/frontend/src/i18n/locales/en/users.json
+++ b/frontend/src/i18n/locales/en/users.json
@@ -13,12 +13,12 @@
"inactive": "waiting for activation",
"activate": "Activate",
"activateHint": "Let this account in. Their address is already approved — this only finishes it.",
- "activated": "{{email}} can sign in now.",
- "activateFailed": "Couldn't activate that account.",
+ "activated": "{{email}} can sign in now",
+ "activateFailed": "Couldn't activate that account",
"resend": "Send the confirmation link again",
"resendHint": "Send the confirmation link again, to the address on the account.",
- "resent": "Confirmation link sent to {{email}}.",
- "resendFailed": "Couldn't send the confirmation link."
+ "resent": "Confirmation link sent to {{email}}",
+ "resendFailed": "Couldn't send the confirmation link"
},
"roles": {
"title": "Users & roles",
@@ -42,7 +42,7 @@
"demoteTitle": "Remove admin?",
"promoteConfirm": "Give {{email}} full admin access (settings, scheduler, user management)?",
"demoteConfirm": "Remove admin access from {{email}}? They'll become a regular user.",
- "failed": "Couldn't change the role."
+ "failed": "Couldn't change the role"
},
"suspend": {
"action": "Suspend",
@@ -55,7 +55,7 @@
"undone": "{{email}} unsuspended",
"demoLocked": "The demo account can't be suspended.",
"selfLocked": "You can't suspend your own account.",
- "failed": "Couldn't change the suspension."
+ "failed": "Couldn't change the suspension"
},
"delete": {
"action": "Delete",
@@ -63,7 +63,7 @@
"title": "Delete account?",
"confirm": "Permanently delete {{email}} and all their data — subscriptions, tags, watch history, playlists and settings? This can't be undone.",
"done": "{{email}} deleted",
- "failed": "Couldn't delete the account."
+ "failed": "Couldn't delete the account"
},
"account": {
"access": "Access",
@@ -75,7 +75,7 @@
"approvalWaitingOnYou": "waiting on you",
"copied": "Copied",
"copy": "Copy",
- "copyFailed": "Couldn't copy — select the link and copy it by hand.",
+ "copyFailed": "Couldn't copy — select the link and copy it by hand",
"email": "Email",
"emailConfirmed": "confirmed",
"emailUnconfirmed": "not confirmed",
@@ -97,7 +97,7 @@
"passwordSet": "set",
"recovery": "Recovery",
"resend": "Send a confirmation link",
- "resendSent": "Confirmation link sent.",
+ "resendSent": "Confirmation link sent",
"setPassword": "Create a set-password link",
"setPasswordAgain": "Create another link",
"linkFailed": "Couldn't create the link. Please try again."
diff --git a/frontend/src/i18n/locales/en/views.json b/frontend/src/i18n/locales/en/views.json
index b4ff9e26..37c65058 100644
--- a/frontend/src/i18n/locales/en/views.json
+++ b/frontend/src/i18n/locales/en/views.json
@@ -7,8 +7,8 @@
"delete": "Delete",
"deleteConfirm": "Delete the saved view “{{name}}”?",
"reorder": "Drag to reorder",
- "actionFailed": "Couldn't update your saved views — please try again.",
- "reorderFailed": "Couldn't save the new order — please try again.",
+ "actionFailed": "Couldn't update your saved views — please try again",
+ "reorderFailed": "Couldn't save the new order — please try again",
"saved": "Saved views",
"manage": "Manage views…",
"manageTitle": "Personal views",
diff --git a/frontend/src/i18n/locales/en/welcome.json b/frontend/src/i18n/locales/en/welcome.json
index 14b413c8..3fa1dee0 100644
--- a/frontend/src/i18n/locales/en/welcome.json
+++ b/frontend/src/i18n/locales/en/welcome.json
@@ -11,10 +11,10 @@
},
"preview": {
"feed": "The Feed — filter by channel, language, topic, length and date.",
- "channels": "Channel manager",
+ "channels": "Channels",
"playlists": "Playlists",
"player": "In-app player",
- "downloads": "Download Center",
+ "downloads": "Downloads",
"search": "Search results",
"plex": "Your Plex library"
},
@@ -41,7 +41,7 @@
"body": "A built-in player picks each video up where you left off, with keyboard and scroll controls."
},
"downloads": {
- "title": "Download Center",
+ "title": "Downloads",
"body": "Save videos to your server in a Plex-friendly layout, trim or join them, then share a link."
},
"plex": {
@@ -61,8 +61,8 @@
"email": "Email",
"password": "Password",
"newPassword": "New password (min {{min}} characters)",
- "invalidEmail": "Enter a valid email address.",
- "weakPassword": "Password must be at least {{min}} characters.",
+ "invalidEmail": "Enter a valid email address",
+ "weakPassword": "Password must be at least {{min}} characters",
"genericError": "Something went wrong. Please try again.",
"working": "Please wait…",
"signinButton": "Sign in",
@@ -84,17 +84,17 @@
"demoDenied": "That email isn't enabled for the demo. Ask the admin to add it.",
"registerDone": "Almost there — check your email for a confirmation link, and your spam folder if it isn't there in a minute. After you confirm it, an admin approves your account before you can sign in.",
"forgotDone": "If that email has an account, we've sent a reset link. Check your inbox, and your spam folder if it hasn't arrived in a minute.",
- "resetDone": "Your password has been set — you can sign in now.",
+ "resetDone": "Your password has been set — you can sign in now",
"deadLink": {
"title": "This link is no longer valid",
"body": "A password reset link lasts one hour and works once. This one has expired, has already been used, or belongs to an account that no longer exists — so there is nothing to set a password on. Ask for a fresh one and we'll email it.",
- "short": "This reset link is no longer valid.",
+ "short": "This reset link is no longer valid",
"cta": "Request a new link"
},
"verifyOk": "Email confirmed. Once an admin approves your account, you can sign in.",
"verifyOkReset": "Email confirmed. The password this account registered with had been cleared for security — choose a new one to sign in.",
"verifyOkResetCta": "Set a new password",
- "verifyInvalid": "That verification link is invalid or has expired.",
+ "verifyInvalid": "That verification link is invalid or has expired",
"verifyUnreachable": "Couldn't reach the server to confirm that link, so it hasn't been used up. Try opening it again in a moment.",
"resendCta": "Send a new link",
"resendDone": "If that email has an unconfirmed account, we've sent a new confirmation link. Check your inbox, and your spam folder if it hasn't arrived in a minute.",
@@ -116,26 +116,26 @@
"email_unverified": {
"title": "Confirm your email address",
"body": "Your password is correct — we just can't let you in until the address on this account is confirmed. Open the link we emailed you, or ask for a new one.",
- "short": "Confirm your email address first — check your inbox."
+ "short": "Confirm your email address first — check your inbox"
},
"pending_approval": {
"title": "Waiting for approval",
"body": "Your password is correct and your address is confirmed. An admin still has to let you in — we'll email you as soon as they decide.",
- "short": "Your account is waiting for admin approval."
+ "short": "Your account is waiting for admin approval"
},
"suspended": {
"title": "Account suspended",
"body": "This account has been suspended, so it can't sign in by any method.",
- "short": "This account has been suspended."
+ "short": "This account has been suspended"
},
"invalid": {
- "short": "Invalid email or password."
+ "short": "Invalid email or password"
},
"rate_limited": {
"short": "Too many attempts. Try again shortly."
}
},
- "accessDenied": "That account isn't approved for this instance yet — register below or ask the admin for access.",
+ "accessDenied": "That account isn't approved for this instance yet — register below or ask the admin for access",
"suspended": "This account has been suspended and can't sign in. If you think this is a mistake, contact the operator (check your email for details).",
"deleted": "Your account and all its data were permanently deleted. We've emailed you a confirmation."
}
diff --git a/frontend/src/i18n/locales/hu/about.json b/frontend/src/i18n/locales/hu/about.json
index 8d0f2949..a1b257ea 100644
--- a/frontend/src/i18n/locales/hu/about.json
+++ b/frontend/src/i18n/locales/hu/about.json
@@ -11,5 +11,5 @@
"fixes": "Javítások",
"olderVersions_one": "{{count}} korábbi verzió",
"olderVersions_other": "{{count}} korábbi verzió",
- "notesError": "A verziójegyzéket nem sikerült betölteni."
+ "notesError": "Nem sikerült betölteni a verziójegyzéket"
}
diff --git a/frontend/src/i18n/locales/hu/adminLogs.json b/frontend/src/i18n/locales/hu/adminLogs.json
index e8ef112e..eb657236 100644
--- a/frontend/src/i18n/locales/hu/adminLogs.json
+++ b/frontend/src/i18n/locales/hu/adminLogs.json
@@ -26,9 +26,9 @@
"showRequest": "Csak ennek a kérésnek a sorai",
"showJob": "Csak ennek a jobnak a sorai",
"empty": "Nincs a szűrésnek megfelelő sor.",
- "stale": "Az utolsó frissítés nem sikerült, amit látsz, elavult lehet.",
- "olderFailed": "Nem sikerült betölteni a régebbi sorokat.",
- "countsFailed": "A szűrők számai nem töltődtek be.",
+ "stale": "Nem sikerült frissíteni — amit látsz, elavult lehet",
+ "olderFailed": "Nem sikerült betölteni a régebbi sorokat",
+ "countsFailed": "Nem sikerült betölteni a szűrők számait",
"count_one": "{{count}} sor betöltve · a legújabb felül",
"count_other": "{{count}} sor betöltve · a legújabb felül",
"older": "Régebbiek betöltése",
diff --git a/frontend/src/i18n/locales/hu/audit.json b/frontend/src/i18n/locales/hu/audit.json
index e7793d49..18d00352 100644
--- a/frontend/src/i18n/locales/hu/audit.json
+++ b/frontend/src/i18n/locales/hu/audit.json
@@ -7,7 +7,7 @@
"clearTitle": "Üríted a naplót?",
"clearConfirm": "Véglegesen törlöd az összes naplóbejegyzést? Ez nem vonható vissza. (Egy bejegyzés arról, hogy te ürítetted, megmarad.)",
"cleared": "{{count}} naplóbejegyzés törölve",
- "clearFailed": "A naplót nem sikerült üríteni.",
+ "clearFailed": "Nem sikerült üríteni a naplót",
"truncated": "A legutóbbi {{returned}} bejegyzés látszik a(z) {{total}}-ból.",
"cols": {
"when": "Mikor",
diff --git a/frontend/src/i18n/locales/hu/auditActions.json b/frontend/src/i18n/locales/hu/auditActions.json
index 2e16f6b4..30401aeb 100644
--- a/frontend/src/i18n/locales/hu/auditActions.json
+++ b/frontend/src/i18n/locales/hu/auditActions.json
@@ -22,7 +22,7 @@
"job_run": "Ütemező job lefutott",
"audit_clear": "Napló ürítve",
"download_credential_set": "Letöltési cookie-k beállítva",
- "download_credential_delete": "Letöltési cookie-k eltávolítva",
+ "download_credential_delete": "Letöltési cookie-k törölve",
"invite_delete": "Whitelista-bejegyzés törölve",
"invite_send": "Meghívó elküldve",
"issue_create": "Hibajegy elküldve"
diff --git a/frontend/src/i18n/locales/hu/card.json b/frontend/src/i18n/locales/hu/card.json
index 69f7061a..c94c7979 100644
--- a/frontend/src/i18n/locales/hu/card.json
+++ b/frontend/src/i18n/locales/hu/card.json
@@ -22,7 +22,7 @@
"menu": {
"label": "Videó",
"unmarkWatched": "Megnézés visszavonása",
- "unsave": "Törlés a mentettek közül",
+ "unsave": "Eltávolítás a mentettek közül",
"more": "További műveletek",
"openChannel": "Csatorna megnyitása"
}
diff --git a/frontend/src/i18n/locales/hu/channel.json b/frontend/src/i18n/locales/hu/channel.json
index 947aad80..f5a153ee 100644
--- a/frontend/src/i18n/locales/hu/channel.json
+++ b/frontend/src/i18n/locales/hu/channel.json
@@ -36,5 +36,5 @@
"fullHistoryProgress": "Előzmény letöltése… {{stored}} / {{total}}",
"fullHistoryLoadingHint": "A teljes előzmény letöltése folyamatban — a videók feltűnnek, ahogy előkerülnek; egy nagyon nagy csatorna maradékát a háttérben fejezi be.",
"fullHistoryQueued": "A csatorna teljes előzményének letöltése elindult — az új videók feltűnnek, ahogy előkerülnek.",
- "fullHistoryFailed": "Nem sikerült elindítani a teljes előzmény letöltését."
+ "fullHistoryFailed": "Nem sikerült elindítani a teljes előzmény letöltését"
}
diff --git a/frontend/src/i18n/locales/hu/channels.json b/frontend/src/i18n/locales/hu/channels.json
index 416413e4..3b0e2951 100644
--- a/frontend/src/i18n/locales/hu/channels.json
+++ b/frontend/src/i18n/locales/hu/channels.json
@@ -1,6 +1,6 @@
{
"actionsLabel": "Katalógus-műveletek",
- "syncSubscriptions": "Feliratkozások beolvasása a YouTube-ról",
+ "syncSubscriptions": "Feliratkozások szinkronizálása a YouTube-ról",
"syncSubscriptionsHint": "Újraimportálja a feliratkozási listádat a YouTube-ról — hozzáadja az újonnan követett csatornákat, és eltávolítja azokat, amelyekről leiratkoztál. Maguk a videók a háttérben automatikusan tovább szinkronizálódnak; ez nem tölti le őket újra.",
"backfillEverything": "Minden letöltése",
"backfillEverythingHint": "Teljes archívum letöltését kéri minden csatornához, amelyre feliratkoztál. A régebbi videók és a keresés a megosztott napi kvóta függvényében válik teljessé — ez eltarthat egy ideig.",
@@ -31,7 +31,7 @@
"needWriteHint": "Engedélyezd a lejátszási listák szerkesztését a Beállításokban a feliratkozáshoz.",
"subscribedTitle": "Feliratkozva a YouTube-on",
"subscribedBody": "Mostantól követed: {{name}} — az új feltöltései megjelennek a Feededben.",
- "subscribeFailed": "A feliratkozás sikertelen",
+ "subscribeFailed": "Nem sikerült feliratkozni",
"confirmSubscribe": "Feliratkozol erre a YouTube-on: „{{name}}”? Ez módosítja a valódi YouTube-fiókodat és kevés API-kvótát használ.",
"cols": {
"totalVideos": "Videók",
@@ -54,15 +54,15 @@
"noneYet": "Még nincs címke — hozz létre egyet lent.",
"create": "Címke létrehozása",
"createPlaceholder": "Új címke…",
- "createFailed": "A címke létrehozása nem sikerült.",
- "updateFailed": "A címke frissítése nem sikerült.",
+ "createFailed": "Nem sikerült létrehozni a címkét",
+ "updateFailed": "Nem sikerült frissíteni a címkét",
"noTag": "Nincs címke",
"moreCount_one": "még {{count}} címke",
"moreCount_other": "még {{count}} címke",
"suggestedHint": "A címkézési mintád alapján javasolt ({{pct}}% egyezés) — kattints a hozzáadáshoz",
"acceptSuggestion": "{{name}} javasolt címke hozzáadása",
- "dismissSuggestion": "Javaslat elvetése",
- "suggestionsHint": "A címkézetlen csatornáidhoz javasolt címkék, abból tanulva, hogyan címkézted a hasonlókat. Kattints egy szellem-chipre a hozzáadáshoz, vagy ×-re az elvetéshez.",
+ "dismissSuggestion": "Javaslat elrejtése",
+ "suggestionsHint": "A címkézetlen csatornáidhoz javasolt címkék, abból tanulva, hogyan címkézted a hasonlókat. Kattints egy szellem-chipre a hozzáadáshoz, vagy ×-re az elrejtéshez.",
"suggestionsCount_one": "{{count}} javaslat",
"suggestionsCount_other": "{{count}} javaslat",
"acceptAllHigh_one": "{{count}} biztos elfogadása",
@@ -102,7 +102,7 @@
},
"row": {
"openOnYouTube": "Megnyitás a YouTube-on",
- "editTags": "Címkék szerkesztése",
+ "editTags": "Címkék szerkesztése…",
"filterFeedByTag": "Feed szűrése: „{{name}}”",
"priorityHint": "A te rangsorod ehhez a csatornához. Rendezd a Feedet „Csatorna prioritás” szerint, hogy a magasabb prioritású csatornák felülre kerüljenek.",
"fullHint": "Teljes előzmény letöltve.",
@@ -126,12 +126,12 @@
},
"notify": {
"synced": "{{count}} feliratkozás szinkronizálva",
- "syncFailed": "A feliratkozások szinkronizálása sikertelen",
+ "syncFailed": "Nem sikerült szinkronizálni a feliratkozásokat",
"unsubscribed": "Leiratkozva a(z) {{name}} csatornáról a YouTube-on",
- "unsubscribeFailed": "A leiratkozás sikertelen",
+ "unsubscribeFailed": "Nem sikerült leiratkozni",
"fullHistoryRequested": "Teljes előzmény kérve {{count}} csatornához",
"fullHistoryFailed": "Nem sikerült teljes előzményt kérni",
- "needYouTube": "Ehhez csatlakoztasd a YouTube-fiókod.",
+ "needYouTube": "Ehhez csatlakoztasd a YouTube-fiókod",
"connect": "Csatlakoztatás",
"filteredByTag": "Feed szűrve címkére: {{name}}",
"resetDone": "{{name}} resetelve — újraletöltés folyamatban",
@@ -143,12 +143,12 @@
"searchPlaceholder": "Csatornák keresése…",
"blocked": {
"title": "Tiltott csatornák",
- "hint": "A videóik el vannak rejtve a Feededből, a Library-ből és a YouTube-keresésből.",
+ "hint": "A videóik el vannak rejtve a Feededből, a Könyvtárból és a YouTube-keresésből.",
"empty": "Nincs tiltott csatorna. Egy csatorna oldaláról tilthatsz le egyet, hogy minden videóját elrejtsd leiratkozás nélkül."
},
"hidden": {
"title": "Elrejtett csatornák",
- "hint": "A videóik kimaradnak a személyes Feededből, de a megosztott Library-ben továbbra is megjelennek.",
+ "hint": "A videóik kimaradnak a személyes Feededből, de a megosztott Könyvtárban továbbra is megjelennek.",
"empty": "Nincs elrejtett csatorna. Egy feliratkozást a sorában (a szem ikonnal) rejthetsz el, hogy kimaradjon a személyes Feededből leiratkozás nélkül.",
"unhide": "Felfedés"
}
diff --git a/frontend/src/i18n/locales/hu/common.json b/frontend/src/i18n/locales/hu/common.json
index c496c97e..3619dd93 100644
--- a/frontend/src/i18n/locales/hu/common.json
+++ b/frontend/src/i18n/locales/hu/common.json
@@ -10,7 +10,7 @@
"undo": "Visszavonás",
"redo": "Újra",
"loading": "Betöltés…",
- "somethingWrong": "Hiba történt.",
+ "somethingWrong": "Hiba történt",
"itemCount_one": "{{formattedCount}} elem",
"itemCount_other": "{{formattedCount}} elem",
"accessRequestsTitle": "Hozzáférési kérések",
@@ -20,7 +20,7 @@
"demoSharedNotice": "Egy közös demo fiókban vagy. Minden, amit itt csinálsz — lejátszási listák, elrejtett videók, beállítások — közös, és más demo látogatók egyszerre módosíthatják.",
"backToTop": "Vissza a tetejére",
"retry": "Újra",
- "loadError": "Nem sikerült betölteni — ellenőrizd a kapcsolatot, és próbáld újra.",
+ "loadError": "Nem sikerült betölteni — ellenőrizd a kapcsolatot, és próbáld újra",
"seeAll": "Összes",
"scrollLeft": "Görgetés balra",
"scrollRight": "Görgetés jobbra",
diff --git a/frontend/src/i18n/locales/hu/config.json b/frontend/src/i18n/locales/hu/config.json
index 7b550d3d..cd03a09c 100644
--- a/frontend/src/i18n/locales/hu/config.json
+++ b/frontend/src/i18n/locales/hu/config.json
@@ -134,7 +134,7 @@
},
"download_upload_max_bytes": {
"label": "Feltöltési méretkorlát fájlonként (bájt)",
- "hint": "A letöltési központba feltölthető legnagyobb egyedi fájl. Legalább 1, így a korlát mindig él; a felhasználó teljes tárhelyét továbbra is a kvótája korlátozza. Alapérték 500 MiB."
+ "hint": "A Letöltésekbe feltölthető legnagyobb egyedi fájl. Legalább 1, így a korlát mindig él; a felhasználó teljes tárhelyét továbbra is a kvótája korlátozza. Alapérték 500 MiB."
},
"download_retention_days": {
"label": "Megőrzés (nap)",
@@ -292,12 +292,12 @@
"testEmail": "Teszt-email küldése",
"testEmailHint": "Teszt-email küldése a saját címedre a fenti beállításokkal, hogy ellenőrizd, működnek-e.",
"testSent": "Teszt-email elküldve ide: {{to}}",
- "testFailed": "A teszt-email sikertelen — ellenőrizd a beállításokat",
+ "testFailed": "Nem sikerült elküldeni a teszt-emailt — ellenőrizd a beállításokat",
"plexTest": "Kapcsolat tesztelése",
"plexTestHint": "Ellenőrzi a Plex-szerver URL-t és tokent, és betölti a könyvtárlistát. Előbb mentsd a módosításokat.",
"testDirty": "Mentsd a változásokat a tesztelés előtt.",
"plexTestOk": "Kapcsolódva: {{name}}",
- "plexTestFailed": "A Plex-kapcsolat sikertelen — ellenőrizd az URL-t és a tokent",
+ "plexTestFailed": "Nem sikerült csatlakozni a Plexhez — ellenőrizd az URL-t és a tokent",
"plexConnected": "Kapcsolódva: {{name}} {{version}}",
"plexMediaOk": "A media-mount rendben — egy mintafájl megtalálható a lemezen.",
"plexMediaMissing": "A média nem olvasható itt: {{path}} — ellenőrizd, hogy a Plex média be van-e mountolva a konténerbe, és hogy helyes-e az útvonal-leképezés. A lejátszás addig nem fog működni, amíg ez nincs javítva.",
@@ -309,5 +309,5 @@
"issuesTest": "Kapcsolat ellenőrzése",
"issuesTestHint": "Megnézi, hogy a tárhely elérhető, privát, és fogad hibajegyet. Előbb mentsd a módosításokat.",
"issuesTestOk": "Rendben: {{repo}} (privát)",
- "issuesTestFailed": "A hibajegy-kezelő ellenőrzése nem sikerült"
+ "issuesTestFailed": "Nem sikerült ellenőrizni a hibajegy-kezelőt"
}
diff --git a/frontend/src/i18n/locales/hu/downloads.json b/frontend/src/i18n/locales/hu/downloads.json
index 736a9287..9871919a 100644
--- a/frontend/src/i18n/locales/hu/downloads.json
+++ b/frontend/src/i18n/locales/hu/downloads.json
@@ -13,14 +13,14 @@
"submit": "Hozzáadás a letöltésekhez",
"added": "Hozzáadva a letöltésekhez",
"view": "Megnyitás",
- "manage": "Formátumok kezelése"
+ "manage": "Formátumok kezelése…"
},
"page": {
"addUrl": "Illessz be egy videólinket (YouTube, Facebook, …) vagy YouTube-videóazonosítót…",
"add": "Hozzáad",
"paste": "Link beillesztése a vágólapról",
"upload": "Feltöltés",
- "manage": "Formátumok kezelése"
+ "manage": "Formátumok kezelése…"
},
"upload": {
"title": "Fájl feltöltése",
@@ -33,7 +33,7 @@
"uploading": "Feltöltés… {{pct}}%",
"uploadProgress": "Feltöltés folyamata",
"added": "Fájl feltöltve",
- "failed": "A feltöltés nem sikerült."
+ "failed": "Nem sikerült feltölteni a fájlt"
},
"tabs": {
"queue": "Sor",
@@ -65,15 +65,15 @@
"pause": "Szünet",
"resume": "Folytatás",
"cancel": "Megszakítás",
- "delete": "Eltávolítás",
+ "delete": "Törlés",
"retry": "Újra",
"download": "Mentés a gépemre",
"play": "Lejátszás",
"rename": "Átnevezés",
- "editDetails": "Adatok szerkesztése",
+ "editDetails": "Adatok szerkesztése…",
"share": "Megosztás (link másolása)",
- "shareManage": "Megosztás kezelése",
- "edit": "Szerkesztés / vágás",
+ "shareManage": "Megosztás kezelése…",
+ "edit": "Szerkesztés / vágás…",
"redownload": "Újraletöltés (fájl újraépítése)",
"removeShared": "Eltávolítás a listámból"
},
@@ -88,7 +88,7 @@
},
"usage": {
"title": "Tárhelyed",
- "items": "{{used}} / {{max}} elem",
+ "items": "{{used}} / {{max}} letöltés",
"unlimited": "Korlátlan"
},
"source": {
@@ -108,9 +108,9 @@
"share": {
"title": "Letöltés megosztása",
"done": "Megosztva vele: {{email}}",
- "failed": "A megosztás nem sikerült.",
+ "failed": "Nem sikerült megosztani",
"userSection": "Megosztás felhasználóval",
- "userHint": "A „Velem megosztott” fülön találja meg.",
+ "userHint": "A címzett a „Velem megosztva” fülön találja meg.",
"pickPlaceholder": "Keress egy felhasználót…",
"noUsers": "Nincs más felhasználó.",
"linkSection": "Megosztás linkkel",
@@ -127,7 +127,7 @@
"days": "{{n}} nap",
"password": "Jelszó (opcionális)",
"copy": "Link másolása",
- "copyFailed": "Nem sikerült a másolás.",
+ "copyFailed": "Nem sikerült másolni",
"revoke": "Visszavonás",
"revokeConfirm": "A link azonnal használhatatlanná válik.",
"views": "{{n}} megtekintés",
@@ -145,8 +145,8 @@
"cancelTitle": "Megszakítod a letöltést?",
"cancelBody": "Ez leállítja a letöltést és eltávolítja a sorból.",
"cancelAction": "Letöltés leállítása",
- "deleteTitle": "Eltávolítod a letöltést?",
- "deleteBody": "Ez eltávolítja a listádról. A fájl a lejáratáig még a gyorsítótárban maradhat mások számára.",
+ "deleteTitle": "Törlöd a letöltést?",
+ "deleteBody": "Ez törli a letöltéseid közül. A fájl a lejáratáig még a gyorsítótárban maradhat mások számára.",
"removeSharedTitle": "Eltávolítod a listádból?",
"removeSharedBody": "Csak a te megosztott listádból tűnik el — a tulajdonos fájlját nem törli.",
"redownloadTitle": "Újraletöltöd ezt a fájlt?",
@@ -198,28 +198,28 @@
"errors": {
"quota": {
"max_bytes": "Elfogyott a letöltési tárhelykereted ({{size}} GB). Előbb szabadíts fel helyet.",
- "max_jobs": "Elérted a letöltési korlátodat ({{limit}} elem). Előbb távolíts el néhányat.",
- "generic": "Túllépted a letöltési kvótát."
+ "max_jobs": "Elérted a letöltési korlátodat ({{limit}} letöltés). Előbb törölj néhányat.",
+ "generic": "Túllépted a letöltési kvótát"
},
"edit": {
- "empty_edit": "Előbb válassz vágási tartományt vagy körbevágási területet.",
- "source_not_ready": "A forrásletöltés még nem szerkeszthető.",
- "generic": "Ezt a vágást nem sikerült létrehozni."
+ "empty_edit": "Előbb válassz vágási tartományt vagy körbevágási területet",
+ "source_not_ready": "A forrásletöltés még nem szerkeszthető",
+ "generic": "Nem sikerült létrehozni a vágást"
},
"upload": {
- "tooLarge": "Ez a fájl túl nagy (legfeljebb {{size}}).",
- "empty": "Ez a fájl üres."
+ "tooLarge": "Ez a fájl túl nagy (legfeljebb {{size}})",
+ "empty": "Ez a fájl üres"
},
"urlBlocked": {
- "scheme": "Csak http:// és https:// hivatkozás tölthető le.",
- "no_host": "Ez a hivatkozás nem tartalmaz webcímet.",
+ "scheme": "Csak http:// és https:// hivatkozás tölthető le",
+ "no_host": "Ez a hivatkozás nem tartalmaz webcímet",
"unresolvable": "Ezt a címet nem sikerült feloldani. Ellenőrizd a hivatkozást, és próbáld újra.",
- "private": "Ez a cím a szerver saját hálózatán van, ezért nem tölthető le róla.",
- "generic": "Erről a címről nem lehet letölteni.",
- "malformed": "Ez a hivatkozás nem érvényes webcím."
+ "private": "Ez a cím a szerver saját hálózatán van, ezért nem tölthető le róla",
+ "generic": "Erről a címről nem lehet letölteni",
+ "malformed": "Ez a hivatkozás nem érvényes webcím"
},
"fail": {
- "title": "A letöltés nem sikerült",
+ "title": "Nem sikerült letölteni",
"detailsLink": "részletek",
"detailsToggle": "Technikai részletek",
"cat": {
@@ -248,7 +248,7 @@
"body": "Ez a videó DRM-védett, ezért nem tölthető le."
},
"postprocess": {
- "short": "A feldolgozás megszakadt",
+ "short": "Nem sikerült feldolgozni a videót",
"body": "A videó letöltődött, de egy feldolgozási lépés (összefűzés vagy konvertálás) meghiúsult. Újrapróbálásra gyakran megjavul."
},
"source_missing": {
@@ -260,8 +260,8 @@
"body": "Hálózati hiba szakította meg a letöltést. Újrapróbálásra általában megjavul."
},
"unknown": {
- "short": "A letöltés nem sikerült",
- "body": "Valami hiba történt az elem letöltése közben. Az alábbi technikai részletek segíthetnek."
+ "short": "Nem sikerült letölteni",
+ "body": "Valami hiba történt ennél a letöltésnél. Az alábbi technikai részletek segíthetnek."
},
"quota_exceeded": {
"short": "Betelt a tárhely",
@@ -300,9 +300,9 @@
}
},
"cookies": {
- "empty": "Előbb illeszd be a cookie-kat.",
- "format": "Ez nem tűnik cookies.txt fájlnak (Netscape formátum).",
- "generic": "A cookie-kat nem sikerült menteni."
+ "empty": "Előbb illeszd be a cookie-kat",
+ "format": "Ez nem tűnik cookies.txt fájlnak (Netscape formátum)",
+ "generic": "Nem sikerült menteni a cookie-kat"
}
}
}
diff --git a/frontend/src/i18n/locales/hu/editor.json b/frontend/src/i18n/locales/hu/editor.json
index e9ae6bf8..5203cde7 100644
--- a/frontend/src/i18n/locales/hu/editor.json
+++ b/frontend/src/i18n/locales/hu/editor.json
@@ -21,7 +21,7 @@
"createN": "{{count}} klip létrehozása",
"created_one": "{{count}} klip a letöltésekhez adva",
"created_other": "{{count}} klip a letöltésekhez adva",
- "failed": "A klip létrehozása nem sikerült.",
+ "failed": "Nem sikerült létrehozni a klipet",
"partlyCreated": "{{total}} klipből csak {{done}} került a letöltésekhez — a többi kijelölve maradt, így az újbóli létrehozás csak azokat adja hozzá.",
"splitHere": "Vágás itt",
"output": {
diff --git a/frontend/src/i18n/locales/hu/errors.json b/frontend/src/i18n/locales/hu/errors.json
index a4a86166..f40bcd51 100644
--- a/frontend/src/i18n/locales/hu/errors.json
+++ b/frontend/src/i18n/locales/hu/errors.json
@@ -1,57 +1,57 @@
{
"title": "Nem sikerült",
"generic": "A szerver nem tudta végrehajtani a műveletet. Próbáld újra.",
- "demo": "Ez a bemutató fiókban nem érhető el.",
+ "demo": "Ez a bemutató fiókban nem érhető el",
"validation": "Néhány megadott érték érvénytelen volt. Ellenőrizd, majd próbáld újra.",
- "tooLarge": "Ez túl nagy, a szerver nem fogadta el.",
+ "tooLarge": "Ez túl nagy, a szerver nem fogadta el",
"server": "Szerverhiba",
"syncCooldown": "Az imént többször is szinkronizáltál — várj pár percet. A YouTube-keret közös az instance összes felhasználójával, a háttérszinkron pedig így is fut tovább.",
"notFound": {
- "generic": "Ez az elem már nem létezik.",
- "view": "Ez a nézet már nem létezik.",
- "tag": "Ez a címke már nem létezik.",
- "pin": "Ez már nincs a komfort polcodon.",
- "playlist": "Ez a lejátszási lista már nem létezik.",
- "move_target": "A lejátszási lista, ahová áthelyeznéd, már nem létezik.",
- "video": "Ez a videó már nincs a könyvtárban.",
- "invite": "Ez a meghívó már nem létezik.",
- "user": "Ez a felhasználó már nem létezik.",
- "channel": "Ez a csatorna már nincs a könyvtárban.",
- "subscription": "Már nem vagy feliratkozva erre a csatornára.",
- "recipient": "Nincs felhasználó ezzel az e-mail címmel.",
- "job": "Ez a feladat már nem létezik.",
- "download": "Ez a letöltés már nem létezik.",
- "debug_session": "Ez a hibakereső munkamenet már nem létezik.",
- "screenshot": "Ez a képernyőkép már nem létezik.",
- "link": "Ez a megosztási link már nem létezik.",
- "notification": "Ez az értesítés már nem létezik.",
- "account": "Ez a fiók már nem létezik.",
- "collection": "Ez a gyűjtemény már nem létezik.",
- "config_key": "Nincs ilyen beállítás.",
- "file": "A fájl már nem érhető el.",
- "filmstrip": "Ehhez nincs képsor.",
- "host": "Ez a kiszolgáló már nincs a listán.",
- "image": "Ehhez nincs kép.",
- "item": "Ez az elem már nem létezik.",
- "library": "Ez a könyvtár már nem létezik.",
- "plex_item": "Ez a Plex-elem már nincs a könyvtárban.",
- "poster": "Ehhez nincs poszter.",
- "profile": "Ez a letöltési profil már nem létezik.",
- "season": "Ez az évad már nincs a könyvtárban.",
- "show": "Ez a sorozat már nincs a könyvtárban."
+ "generic": "Ez az elem már nem létezik",
+ "view": "Ez a nézet már nem létezik",
+ "tag": "Ez a címke már nem létezik",
+ "pin": "Ez már nincs a komfort polcodon",
+ "playlist": "Ez a lejátszási lista már nem létezik",
+ "move_target": "A lejátszási lista, ahová áthelyeznéd, már nem létezik",
+ "video": "Ez a videó már nincs a könyvtárban",
+ "invite": "Ez a meghívó már nem létezik",
+ "user": "Ez a felhasználó már nem létezik",
+ "channel": "Ez a csatorna már nincs a könyvtárban",
+ "subscription": "Már nem vagy feliratkozva erre a csatornára",
+ "recipient": "Nincs felhasználó ezzel az e-mail címmel",
+ "job": "Ez a feladat már nem létezik",
+ "download": "Ez a letöltés már nem létezik",
+ "debug_session": "Ez a hibakereső munkamenet már nem létezik",
+ "screenshot": "Ez a képernyőkép már nem létezik",
+ "link": "Ez a megosztási link már nem létezik",
+ "notification": "Ez az értesítés már nem létezik",
+ "account": "Ez a fiók már nem létezik",
+ "collection": "Ez a gyűjtemény már nem létezik",
+ "config_key": "Nincs ilyen beállítás",
+ "file": "A fájl már nem érhető el",
+ "filmstrip": "Ehhez nincs képsor",
+ "host": "Ez a kiszolgáló már nincs a listán",
+ "image": "Ehhez nincs kép",
+ "item": "Ez az elem már nem létezik",
+ "library": "Ez a könyvtár már nem létezik",
+ "plex_item": "Ez a Plex-elem már nincs a könyvtárban",
+ "poster": "Ehhez nincs poszter",
+ "profile": "Ez a letöltési profil már nem létezik",
+ "season": "Ez az évad már nincs a könyvtárban",
+ "show": "Ez a sorozat már nincs a könyvtárban"
},
"admin": {
"not_allowed": "A címe már nincs jóváhagyva. Előbb vedd fel a fehérlistára.",
- "not_applicable": "Ezen a fiókon már nincs mit megerősíteni.",
- "not_approved": "Csak jóváhagyott címre küldhető meghívó.",
+ "not_applicable": "Ezen a fiókon már nincs mit megerősíteni",
+ "not_approved": "Csak jóváhagyott címre küldhető meghívó",
"email_off": "Nem ment ki levél: ezen a példányon nincs működő SMTP. Nézd meg a Konfiguráció → E-mail / SMTP fület.",
"send_failed": "Nem sikerült elküldeni a levelet: a levelezőszerver nem volt elérhető, vagy elutasította a belépést vagy a levelet. Nézd meg a Konfiguráció → E-mail / SMTP fület, majd próbáld újra.",
- "demo": "A bemutató fiók közös, ezért nem kaphat jelszóbeállító linket."
+ "demo": "A bemutató fiók közös, ezért nem kaphat jelszóbeállító linket"
},
"reason": {
"unreachable": "A szerver nem volt elérhető. Próbáld újra.",
- "forbidden": "Ehhez nincs jogosultságod.",
- "rateLimited": "Túl sok kérés egyszerre — várj egy kicsit, és próbáld újra.",
+ "forbidden": "Ehhez nincs jogosultságod",
+ "rateLimited": "Túl sok kérés egyszerre — várj egy kicsit, és próbáld újra",
"unexpected": "Váratlan hiba történt az alkalmazásban. Próbáld újra."
},
"writeFailed": {
@@ -70,15 +70,15 @@
"delete": "Nem sikerült törölni a letöltést",
"redownload": "Nem sikerült újraindítani a letöltést"
},
- "removeShared": "Nem sikerült eltávolítani a megosztott elemet",
+ "removeShared": "Nem sikerült eltávolítani a megosztott letöltést",
"downloadDetails": "Nem sikerült menteni a letöltés adatait",
"enqueueDownload": "Nem sikerült sorba állítani a letöltést",
"downloadProfileSave": "Nem sikerült menteni a letöltési profilt",
"downloadProfileDelete": "Nem sikerült törölni a letöltési profilt",
- "downloadCookiesRemove": "Nem sikerült eltávolítani ezeket a sütiket",
+ "downloadCookiesRemove": "Nem sikerült törölni ezeket a cookie-kat",
"notifRead": "Nem sikerült olvasottnak jelölni az értesítést",
"notifReadAll": "Nem sikerült olvasottnak jelölni az értesítéseket",
- "notifDismiss": "Nem sikerült elvetni az értesítést",
+ "notifDismiss": "Nem sikerült elrejteni az értesítést",
"notifClear": "Nem sikerült törölni az értesítéseket",
"createPlaylist": "Nem sikerült létrehozni a lejátszási listát",
"schedulerRun": "Nem sikerült elindítani a feladatot",
@@ -106,8 +106,8 @@
"body": "Nem sikerült elérni a szervert — lehet, hogy épp újraindul. Ez az üzenet eltűnik, amint helyreáll a kapcsolat."
},
"boundary": {
- "title": "Hiba történt.",
- "subtitle": "Az alkalmazás váratlan hibába ütközött.",
+ "title": "Hiba történt",
+ "subtitle": "Az alkalmazás váratlan hibába ütközött",
"reload": "Újratöltés",
"notifTitle": "Valami elromlott",
"notifMessage": "Váratlan hiba"
@@ -118,111 +118,111 @@
"unavailable": "A YouTube most nem fogadta el. Próbáld újra."
},
"refused": {
- "account_no_access": "Ennek a fióknak már nincs hozzáférése.",
- "account_signed_out": "Ezt a fiókot máshol kijelentkeztették — lépj be újra.",
- "account_suspended": "Ez a fiók fel van függesztve.",
- "address_has_account": "Ehhez a címhez már tartozik fiók — azt töröld helyette.",
- "admin_first": "Előbb hozd létre az admin fiókot.",
- "admin_only": "Ezt csak admin teheti meg.",
- "already_set_up": "Ez a példány már be van állítva.",
- "already_subscribed": "Erre a csatornára már feliratkoztál.",
- "channel_blocked": "Ezt a csatornát letiltottad.",
- "collection_fields_required": "A címet és egy elemet is meg kell adni.",
- "collection_pending_sync": "A gyűjtemény a Plexben megváltozott, de a helyi másolat frissítése nem sikerült — a következő szinkron pótolja.",
- "collection_read_only": "Ez a gyűjtemény csak olvasható.",
- "collection_smart": "Az okos és az automatikus listás gyűjtemények nem tehetők szerkeszthetővé.",
- "demo_activate": "A bemutató fiók nem aktiválható.",
- "demo_delete": "A bemutató fiók nem törölhető.",
- "demo_role": "A bemutató fiók szerepköre nem módosítható.",
- "demo_suspend": "A bemutató fiók nem függeszthető fel.",
- "download_not_ready": "Ez a letöltés még nincs kész.",
- "edit_unfinished": "Csak befejezett letöltés szerkeszthető.",
- "filters_not_object": "A nézet szűrői hibás formátumúak.",
+ "account_no_access": "Ennek a fióknak már nincs hozzáférése",
+ "account_signed_out": "Ezt a fiókot máshol kijelentkeztették — lépj be újra",
+ "account_suspended": "Ez a fiók fel van függesztve",
+ "address_has_account": "Ehhez a címhez már tartozik fiók — azt töröld helyette",
+ "admin_first": "Előbb hozd létre az admin fiókot",
+ "admin_only": "Ezt csak admin teheti meg",
+ "already_set_up": "Ez a példány már be van állítva",
+ "already_subscribed": "Erre a csatornára már feliratkoztál",
+ "channel_blocked": "Ezt a csatornát letiltottad",
+ "collection_fields_required": "A címet és egy elemet is meg kell adni",
+ "collection_pending_sync": "A gyűjtemény a Plexben megváltozott, de a helyi másolat frissítése nem sikerült — a következő szinkron pótolja",
+ "collection_read_only": "Ez a gyűjtemény csak olvasható",
+ "collection_smart": "Az okos és az automatikus listás gyűjtemények nem tehetők szerkeszthetővé",
+ "demo_activate": "A bemutató fiók nem aktiválható",
+ "demo_delete": "A bemutató fiók nem törölhető",
+ "demo_role": "A bemutató fiók szerepköre nem módosítható",
+ "demo_suspend": "A bemutató fiók nem függeszthető fel",
+ "download_not_ready": "Ez a letöltés még nincs kész",
+ "edit_unfinished": "Csak befejezett letöltés szerkeszthető",
+ "filters_not_object": "A nézet szűrői hibás formátumúak",
"google_no_userinfo": "A Google nem küldte vissza a fiók adatait. Próbálj újra belépni.",
- "image_fetch_failed": "A képet nem sikerült betölteni.",
- "image_host": "Erről a címről nem töltünk be képet.",
- "invalid_cursor": "A lista pozíciója elavult — töltsd újra az oldalt.",
- "invalid_email": "Adj meg egy érvényes e-mail címet.",
- "invalid_interval": "Az intervallumnak {{min}} és {{max}} perc között kell lennie.",
- "invalid_link": "Ez nem tűnik érvényes videólinknek.",
- "invalid_role": "Ismeretlen szerepkör.",
- "invalid_status": "Ismeretlen állapot.",
- "invite_decided": "Erről a kérésről már döntöttek.",
- "job_running": "Ez a feladat már fut.",
- "last_active_admin": "Ez az egyetlen aktív admin — előbb tegyél meg valaki mást adminnak.",
- "last_admin": "Ez az egyetlen admin — előbb tegyél meg valaki mást adminnak.",
- "link_gone": "Ez a link már nem érhető el.",
- "link_required": "Adj meg egy videólinket vagy -azonosítót.",
- "media_missing": "A médiafájl nincs a lemezen.",
- "message_empty": "Az üzenet üres.",
- "message_rate": "Túl sok üzenet — várj egy kicsit.",
- "message_self": "Magadnak nem küldhetsz üzenetet.",
- "message_too_long": "Az üzenet túl hosszú.",
- "messaging_set_up": "A biztonságos üzenetküldés már be van állítva.",
- "name_required": "Adj meg egy nevet.",
- "no_encryption_key": "Titkos értékek tárolásához állítsd be a TOKEN_ENCRYPTION_KEY-t.",
- "no_messaging": "Ez a tag még nem állította be az üzenetküldést.",
- "no_profile": "Nincs használható letöltési profil.",
- "no_subscription_id": "Még nincs rögzített YouTube-feliratkozás — előbb szinkronizáld a feliratkozásaidat.",
- "not_authenticated": "Nem vagy bejelentkezve.",
- "not_ready": "Még nem érhető el.",
- "not_redownloadable": "Ez az elem nem tölthető le újra.",
- "not_your_account": "Ebbe a fiókba itt nem léptél be.",
- "only_admin_delete": "Te vagy az egyetlen admin — a fiókod törlése előtt tegyél meg valaki mást adminnak.",
- "own_delete": "A saját fiókodat a Beállítások → Fiók alatt törölheted.",
- "own_role": "A saját szerepkörödet nem módosíthatod.",
- "own_suspend": "A saját fiókodat nem függesztheted fel.",
- "plex_disabled": "A Plex modul ki van kapcsolva.",
- "plex_not_configured": "A Plex nincs beállítva — add meg a szerver címét és a tokent a Konfiguráció → Plex alatt.",
+ "image_fetch_failed": "Nem sikerült betölteni a képet",
+ "image_host": "Erről a címről nem töltünk be képet",
+ "invalid_cursor": "A lista pozíciója elavult — töltsd újra az oldalt",
+ "invalid_email": "Adj meg egy érvényes e-mail címet",
+ "invalid_interval": "Az intervallumnak {{min}} és {{max}} perc között kell lennie",
+ "invalid_link": "Ez nem tűnik érvényes videólinknek",
+ "invalid_role": "Ismeretlen szerepkör",
+ "invalid_status": "Ismeretlen állapot",
+ "invite_decided": "Erről a kérésről már döntöttek",
+ "job_running": "Ez a feladat már fut",
+ "last_active_admin": "Ez az egyetlen aktív admin — előbb tegyél meg valaki mást adminnak",
+ "last_admin": "Ez az egyetlen admin — előbb tegyél meg valaki mást adminnak",
+ "link_gone": "Ez a link már nem érhető el",
+ "link_required": "Adj meg egy videólinket vagy -azonosítót",
+ "media_missing": "A médiafájl nincs a lemezen",
+ "message_empty": "Az üzenet üres",
+ "message_rate": "Túl sok üzenet — várj egy kicsit",
+ "message_self": "Magadnak nem küldhetsz üzenetet",
+ "message_too_long": "Az üzenet túl hosszú",
+ "messaging_set_up": "A biztonságos üzenetküldés már be van állítva",
+ "name_required": "Adj meg egy nevet",
+ "no_encryption_key": "Titkos értékek tárolásához állítsd be a TOKEN_ENCRYPTION_KEY-t",
+ "no_messaging": "Ez a tag még nem állította be az üzenetküldést",
+ "no_profile": "Nincs használható letöltési profil",
+ "no_subscription_id": "Még nincs rögzített YouTube-feliratkozás — előbb szinkronizáld a feliratkozásaidat",
+ "not_authenticated": "Nem vagy bejelentkezve",
+ "not_ready": "Még nem érhető el",
+ "not_redownloadable": "Ez a letöltés nem tölthető le újra",
+ "not_your_account": "Ebbe a fiókba itt nem léptél be",
+ "only_admin_delete": "Te vagy az egyetlen admin — a fiókod törlése előtt tegyél meg valaki mást adminnak",
+ "own_delete": "A saját fiókodat a Beállítások → Fiók alatt törölheted",
+ "own_role": "A saját szerepkörödet nem módosíthatod",
+ "own_suspend": "A saját fiókodat nem függesztheted fel",
+ "plex_disabled": "A Plex modul ki van kapcsolva",
+ "plex_not_configured": "A Plex nincs beállítva — add meg a szerver címét és a tokent a Konfiguráció → Plex alatt",
"plex_unreachable": "A Plex-szerver nem válaszolt. Ellenőrizd a címet és a tokent, majd próbáld újra.",
"plex_write_failed": "A Plex nem fogadta el a módosítást. Próbáld újra.",
- "profile_name_required": "Adj meg egy profilnevet.",
- "quota_reserve": "A mai közös YouTube-keret a tartalékánál tart — próbáld később.",
+ "profile_name_required": "Adj meg egy profilnevet",
+ "quota_reserve": "A mai közös YouTube-keret a tartalékánál tart — próbáld később",
"quota_spent": "A mai közös YouTube-keret elfogyott. Próbáld holnap.",
- "recipient_required": "Adj meg egy címzett e-mail címet.",
- "registration_closed": "A regisztráció jelenleg zárva van.",
+ "recipient_required": "Adj meg egy címzett e-mail címet",
+ "registration_closed": "A regisztráció jelenleg zárva van",
"search_daily_limit": "Elérted a mai YouTube-keresési korlátot ({{limit}}). Próbáld holnap.",
- "search_term_required": "Írj be egy keresőkifejezést.",
- "session_gone": "A lejátszási munkamenet véget ért.",
- "setting_not_a_choice": "Válassz ezek közül: {{choices}}.",
- "setting_not_a_number": "Adj meg egy egész számot.",
- "setting_too_high": "Az érték nem lehet nagyobb, mint {{max}}.",
- "setting_too_low": "Az érték nem lehet kisebb, mint {{min}}.",
- "setup_token": "A beállítási token hiányzik vagy hibás.",
- "share_own": "Ez már a te letöltésed.",
- "share_password_required": "Ehhez a linkhez jelszó kell.",
- "share_unfinished": "Csak befejezett letöltés osztható meg.",
- "smtp_failed": "Nem sikerült elküldeni a levelet — ellenőrizd az SMTP-beállításokat.",
- "smtp_off": "Az SMTP nincs beállítva.",
- "spec_not_object": "A profil beállításai hibás formátumúak.",
- "still_downloading": "Ez a letöltés még folyamatban van.",
- "tag_name_taken": "Már van „{{name}}” nevű címkéd.",
- "title_required": "Adj meg egy címet.",
+ "search_term_required": "Írj be egy keresőkifejezést",
+ "session_gone": "A lejátszási munkamenet véget ért",
+ "setting_not_a_choice": "Válassz ezek közül: {{choices}}",
+ "setting_not_a_number": "Adj meg egy egész számot",
+ "setting_too_high": "Az érték nem lehet nagyobb, mint {{max}}",
+ "setting_too_low": "Az érték nem lehet kisebb, mint {{min}}",
+ "setup_token": "A beállítási token hiányzik vagy hibás",
+ "share_own": "Ez már a te letöltésed",
+ "share_password_required": "Ehhez a linkhez jelszó kell",
+ "share_unfinished": "Csak befejezett letöltés osztható meg",
+ "smtp_failed": "Nem sikerült elküldeni a levelet — ellenőrizd az SMTP-beállításokat",
+ "smtp_off": "Az SMTP nincs beállítva",
+ "spec_not_object": "A profil beállításai hibás formátumúak",
+ "still_downloading": "Ez a letöltés még folyamatban van",
+ "tag_name_taken": "Már van „{{name}}” nevű címkéd",
+ "title_required": "Adj meg egy címet",
"too_many_attempts": "Túl sok próbálkozás. Próbáld kicsit később.",
"unknown_setting": "Ismeretlen beállítás: {{key}}",
- "value_missing": "Adj meg egy értéket.",
- "video_gone": "Ez a videó már nem érhető el.",
- "view_name_taken": "Már van „{{name}}” nevű nézeted.",
- "watch_sync_off": "Előbb kapcsold be a Plex megtekintés-szinkront.",
- "weak_password": "A jelszónak legalább {{min}} karakterből kell állnia.",
- "wrong_password": "A jelenlegi jelszó helytelen.",
- "wrong_share_password": "Hibás jelszó.",
- "youtube_editing_off": "Előbb kapcsold be a YouTube-szerkesztést a Beállításokban.",
- "youtube_read_scope": "Előbb kapcsold össze a YouTube-fiókodat (olvasási hozzáférés).",
+ "value_missing": "Adj meg egy értéket",
+ "video_gone": "Ez a videó már nem érhető el",
+ "view_name_taken": "Már van „{{name}}” nevű nézeted",
+ "watch_sync_off": "Előbb kapcsold be a Plex megtekintés-szinkront",
+ "weak_password": "A jelszónak legalább {{min}} karakterből kell állnia",
+ "wrong_password": "A jelenlegi jelszó helytelen",
+ "wrong_share_password": "Hibás jelszó",
+ "youtube_editing_off": "Előbb kapcsold be a YouTube-szerkesztést a Beállításokban",
+ "youtube_read_scope": "Előbb kapcsold össze a YouTube-fiókodat (olvasási hozzáférés)",
"debug_session_conflict": "Egy másik lapon épp most indult rögzítés. Frissítsd az oldalt.",
- "issue_already_sent": "Ezt a hibajegyet már elküldted, a vázlata nem változtatható.",
- "issue_title_required": "Adj címet a hibajegynek, mielőtt elküldöd.",
- "screenshot_already_sent": "A hibajegy már elment, ehhez a munkamenethez nem adhatsz több képet.",
- "screenshot_too_many": "Egy munkamenethez legfeljebb {{limit}} képernyőkép tartozhat.",
- "screenshot_empty": "A kép üres.",
- "screenshot_not_an_image": "Ez nem PNG, JPEG vagy WebP kép.",
+ "issue_already_sent": "Ezt a hibajegyet már elküldted, a vázlata nem változtatható",
+ "issue_title_required": "Adj címet a hibajegynek, mielőtt elküldöd",
+ "screenshot_already_sent": "A hibajegy már elment, ehhez a munkamenethez nem adhatsz több képet",
+ "screenshot_too_many": "Egy munkamenethez legfeljebb {{limit}} képernyőkép tartozhat",
+ "screenshot_empty": "A kép üres",
+ "screenshot_not_an_image": "Ez nem PNG, JPEG vagy WebP kép",
"issues_not_configured": "Nincs beállítva hibajegy-kezelő. Töltsd le a hibajegyet zipként.",
"issues_unreachable": "A hibajegy-kezelő nem válaszol. Próbáld újra később.",
- "issues_bad_token": "A hibajegy-kezelő nem fogadta el a tokent, vagy hiányzik a jogosultsága.",
- "issues_repo_not_found": "A beállított hibajegy-tárhely nem található.",
+ "issues_bad_token": "A hibajegy-kezelő nem fogadta el a tokent, vagy hiányzik a jogosultsága",
+ "issues_repo_not_found": "A beállított hibajegy-tárhely nem található",
"issues_public_repo": "A hibajegy-tárhely nyilvános. A napló miatt csak privát tárhelyre küldünk.",
- "issues_disabled": "A hibajegy-tárhelyen ki van kapcsolva a hibajegy-kezelés.",
- "issues_rejected": "A hibajegy-kezelő visszautasította a kérést.",
+ "issues_disabled": "A hibajegy-tárhelyen ki van kapcsolva a hibajegy-kezelés",
+ "issues_rejected": "A hibajegy-kezelő visszautasította a kérést",
"issues_rate_limited": "Óránként legfeljebb {{limit}} hibajegyet küldhetsz. Próbáld újra később.",
"debug_session_rate_limited": "Óránként legfeljebb {{limit}} új rögzítést indíthatsz. Próbáld újra később.",
"export_rate_limited": "Túl sok letöltés egymás után. Várj egy percet.",
diff --git a/frontend/src/i18n/locales/hu/feed.json b/frontend/src/i18n/locales/hu/feed.json
index 0be0bc03..2d640d4b 100644
--- a/frontend/src/i18n/locales/hu/feed.json
+++ b/frontend/src/i18n/locales/hu/feed.json
@@ -12,16 +12,16 @@
"maxAge_other": "Utolsó {{count}} nap"
},
"loading": "Feed betöltése…",
- "loadError": "Nem sikerült betölteni a Feedet.",
+ "loadError": "Nem sikerült betölteni a Feedet",
"emptyTitle": "A Feeded üres",
"emptyBody": "Kösd össze a YouTube-fiókodat, hogy importáld a feliratkozásaidat, és felépítsd a Feededet.",
"setUp": "Feed beállítása",
- "browseShared": "Vagy csak böngészd a közös könyvtárat",
+ "browseShared": "Vagy csak böngészd a közös Könyvtárat",
"demoEmptyTitle": "Itt a demóban nincs semmi",
- "demoEmptyBody": "A demo fióknak nincsenek saját feliratkozásai — de a teljes közös könyvtár a tiéd: böngészd, szűrd és rendezd kedvedre.",
+ "demoEmptyBody": "A demo fióknak nincsenek saját feliratkozásai — de a teljes közös Könyvtár a tiéd: böngészd, szűrd és rendezd kedvedre.",
"browseLibrary": "Könyvtár böngészése",
"noMatches": "Egy videó sem felel meg ezeknek a szűrőknek.",
- "channelScopeHint": "Ez a csatorna nincs a Feededben – lehet, hogy el van rejtve. A videói a közös könyvtárban vannak.",
+ "channelScopeHint": "Ez a csatorna nincs a Feededben – lehet, hogy el van rejtve. A videói a közös Könyvtárban vannak.",
"viewInLibrary": "Megnézés a Könyvtárban",
"sortLabel": "Rendezés",
"viewLabel": "Nézet",
@@ -70,7 +70,7 @@
"back": "Vissza a Feedhez",
"searching": "Keresés a YouTube-on…",
"noResults": "Nincs YouTube-találat.",
- "error": "A YouTube-keresés nem sikerült.",
+ "error": "Nem sikerült keresni a YouTube-on",
"loadMore": "Továbbiak betöltése (kvótát fogyaszt)",
"loadMoreFree": "Továbbiak betöltése",
"show": "Mutat",
@@ -78,6 +78,6 @@
"clearNow": "Törlés most",
"cleared_one": "{{count}} találat törölve",
"cleared_other": "{{count}} találat törölve",
- "showHint": "Hány találatot szerezzen egyszerre — a Több betöltése ennyivel tölt tovább."
+ "showHint": "Hány találatot szerezzen egyszerre — a „Továbbiak betöltése” ennyivel tölt tovább."
}
}
diff --git a/frontend/src/i18n/locales/hu/header.json b/frontend/src/i18n/locales/hu/header.json
index 4c09b5da..c5273494 100644
--- a/frontend/src/i18n/locales/hu/header.json
+++ b/frontend/src/i18n/locales/hu/header.json
@@ -5,7 +5,7 @@
"searchYoutubeTip": "Élő keresés a YouTube-on erre a kifejezésre (a közös API-kvótát fogyasztja). Entert is nyomhatsz.",
"go": "Mehet",
"goTip": "Keresés (vagy Enter)",
- "channelManager": "Csatornakezelő",
+ "channelManager": "Csatornák",
"usageStats": "Használat és statisztika",
"scheduler": "Ütemező",
"configuration": "Konfiguráció",
@@ -15,7 +15,7 @@
"my": "Sajátom",
"myTip": "Csak a saját feliratkozásaid videói",
"all": "Könyvtár",
- "allTip": "A közös könyvtár összes videója"
+ "allTip": "A közös Könyvtár összes videója"
},
"account": {
"admin": "Adminisztrátor",
@@ -51,14 +51,14 @@
"resume": "Szinkron folytatása"
},
"banner": {
- "updated": "Frissítve a(z) v{{version}} verzióra — nézd meg, mi változott.",
+ "updated": "Frissítve a(z) v{{version}} verzióra — nézd meg, mi változott",
"releaseNotes": "Verziójegyzék",
- "dismiss": "Elvetés"
+ "dismiss": "Elrejtés"
},
"verify": {
- "confirmed": "Megerősítettük az e-mail-címedet.",
- "confirmedNamed": "Megerősítettük az e-mail-címedet — {{email}}.",
- "invalid": "Ez a megerősítő link már nem érvényes — lejárt, vagy már felhasználták.",
+ "confirmed": "Megerősítettük az e-mail-címedet",
+ "confirmedNamed": "Megerősítettük az e-mail-címedet — {{email}}",
+ "invalid": "Ez a megerősítő link már nem érvényes — lejárt, vagy már felhasználták",
"unreachable": "Nem sikerült elérni a szervert a link megerősítéséhez, így az nem is használódott el. Próbáld megnyitni újra egy pillanat múlva.",
"resetSignedOut": "A jelszó-visszaállító linkek kijelentkezett ablakban nyílnak meg. Jelentkezz ki itt, vagy nyisd meg a linket privát ablakban, és onnan folytatódik.",
"reset": "A fiók regisztrációkori jelszavát biztonsági okból töröltük; belépés előtt állíts be újat."
diff --git a/frontend/src/i18n/locales/hu/home.json b/frontend/src/i18n/locales/hu/home.json
index 41585b24..bda520cc 100644
--- a/frontend/src/i18n/locales/hu/home.json
+++ b/frontend/src/i18n/locales/hu/home.json
@@ -1,8 +1,8 @@
{
"navLabel": "Kezdőlap",
"title": "Mit nézzek most?",
- "error": "Nem sikerült betölteni az ajánlatokat.",
- "actionFailed": "Nem sikerült elmenteni.",
+ "error": "Nem sikerült betölteni az ajánlatokat",
+ "actionFailed": "Nem sikerült elmenteni",
"budget": {
"ask": "Mennyi időd van?",
"short": "10 perc",
@@ -38,17 +38,17 @@
"moveLeft": "Balra",
"moveRight": "Jobbra",
"moveLast": "A polc végére",
- "moveFailed": "Nem sikerült átrendezni a polcot.",
+ "moveFailed": "Nem sikerült átrendezni a polcot",
"moveSaving": "Az új sorrend mentése…",
"openPlaylist": "Lejátszási lista megnyitása"
},
"pin": {
"menu": "Komfort polc",
"add": "Komfort polcra",
- "remove": "Levétel a polcról",
- "added": "{{title}} felkerült a komfort polcra.",
- "removed": "{{title}} lekerült a komfort polcról.",
- "addFailed": "Nem sikerült a polcra tűzni.",
- "removeFailed": "Nem sikerült levenni a polcról."
+ "remove": "Eltávolítás a polcról",
+ "added": "{{title}} felkerült a komfort polcra",
+ "removed": "{{title}} lekerült a komfort polcról",
+ "addFailed": "Nem sikerült a polcra tűzni",
+ "removeFailed": "Nem sikerült eltávolítani a polcról"
}
}
diff --git a/frontend/src/i18n/locales/hu/inbox.json b/frontend/src/i18n/locales/hu/inbox.json
index a796e5d6..d8017756 100644
--- a/frontend/src/i18n/locales/hu/inbox.json
+++ b/frontend/src/i18n/locales/hu/inbox.json
@@ -6,15 +6,15 @@
"clearConfirmTitle": "Törlöd az összes értesítést?",
"clearConfirmBody": "Minden itteni értesítés eltűnik a listából, olvasott és olvasatlan is. Nem hozhatók vissza.",
"markRead": "Olvasottnak jelöl",
- "dismiss": "Elvetés",
+ "dismiss": "Elrejtés",
"rowMenu": "Értesítés műveletei",
- "menu": "Értesítésközpont műveletei",
+ "menu": "Értesítések műveletei",
"sectionSystem": "Rendszer",
"sectionActivity": "Tevékenység",
"andMore": "és még {{count}}",
"openScheduler": "Ütemező megnyitása",
"openDownloads": "Letöltések megnyitása",
- "revertFailed": "Nem sikerült visszavonni — próbáld újra.",
+ "revertFailed": "Nem sikerült visszavonni — próbáld újra",
"downloadDone": {
"title": "Letöltés kész"
},
@@ -28,11 +28,11 @@
},
"jobDone": {
"titleOk": "{{job}} kész",
- "titleError": "{{job}} hibázott"
+ "titleError": "Nem sikerült lefuttatni: {{job}}"
},
"emailUndelivered": {
"title": "Nem megy ki e-mail",
"notConfigured": "Az SMTP nincs beállítva, így semmi nem kézbesíthető, amit ez a példány küldene — megerősítő linkek, jelszó-visszaállítások és hozzáférés-kérelmek egyaránt. Az egyik ilyen: „{{subject}}”. Állítsd be a Konfiguráció → E-mail / SMTP fülön.",
- "sendFailed": "A küldés nem sikerült, így az üzenetek nem kézbesíthetők. Az egyik ilyen: „{{subject}}”. Nézd meg a Konfiguráció → E-mail / SMTP fület, és küldj tesztet; az okot a szerver naplója tartalmazza."
+ "sendFailed": "Nem sikerült e-mailt küldeni, így az üzenetek nem kézbesíthetők. Az egyik ilyen: „{{subject}}”. Nézd meg a Konfiguráció → E-mail / SMTP fület, és küldj tesztet; az okot a szerver naplója tartalmazza."
}
}
diff --git a/frontend/src/i18n/locales/hu/keys.json b/frontend/src/i18n/locales/hu/keys.json
index 47f6e278..1986af78 100644
--- a/frontend/src/i18n/locales/hu/keys.json
+++ b/frontend/src/i18n/locales/hu/keys.json
@@ -15,7 +15,7 @@
"audio": "Hangsáv váltása",
"subtitle": "Felirat váltása",
"help": "Súgó megjelenítése",
- "home": "Ugrás a kezdőlapra (mit nézzek?)",
+ "home": "Ugrás: Kezdőlap (mit nézzek?)",
"info": "Média infó",
"stats": "Statisztika (nerdöknek)",
"back": "Leállítás és vissza",
diff --git a/frontend/src/i18n/locales/hu/messages.json b/frontend/src/i18n/locales/hu/messages.json
index c531c20e..f17cad86 100644
--- a/frontend/src/i18n/locales/hu/messages.json
+++ b/frontend/src/i18n/locales/hu/messages.json
@@ -5,7 +5,7 @@
"minimize": "Összecsukás",
"close": "Bezárás",
"conversations": "Beszélgetések",
- "new": "Új üzenet",
+ "new": "Új üzenet…",
"newTitle": "Új üzenet",
"empty": "Még nincs beszélgetés. Indíts egyet az Új üzenet gombbal.",
"searchPeople": "Emberek keresése…",
@@ -27,15 +27,15 @@
"unlock": "Feloldás",
"passphrase": "Jelszó",
"passphraseConfirm": "Jelszó megerősítése",
- "passTooShort": "Legalább 6 karakter legyen.",
- "passMismatch": "A jelszavak nem egyeznek.",
- "wrongPass": "Hibás jelszó.",
- "setupFailed": "A biztonságos üzenetküldés beállítása nem sikerült. Próbáld újra.",
+ "passTooShort": "Legalább 6 karakter legyen",
+ "passMismatch": "A jelszavak nem egyeznek",
+ "wrongPass": "Hibás jelszó",
+ "setupFailed": "Nem sikerült beállítani a biztonságos üzenetküldést. Próbáld újra.",
"forgotPass": "Elfelejtetted a jelszavad?",
"resetTitle": "Biztonságos üzenetküldés visszaállítása",
"resetWarn": "Ez véglegesen törli a teljes üzenet-előzményedet — minden beszélgetést, nálad és a partnereidnél is —, mert az többé nem visszafejthető. Utána új jelszót állíthatsz be és tiszta lappal indulhatsz. Ez nem visszavonható.",
"resetCurrentPass": "Jelenlegi fiók-jelszó",
"resetNeedPass": "Add meg a jelenlegi fiók-jelszavad a megerősítéshez.",
"resetButton": "Visszaállítás és előzmény törlése",
- "resetFailed": "A biztonságos üzenetküldés visszaállítása nem sikerült. Próbáld újra."
+ "resetFailed": "Nem sikerült visszaállítani a biztonságos üzenetküldést. Próbáld újra."
}
diff --git a/frontend/src/i18n/locales/hu/nav.json b/frontend/src/i18n/locales/hu/nav.json
index 41090a5b..2acc8919 100644
--- a/frontend/src/i18n/locales/hu/nav.json
+++ b/frontend/src/i18n/locales/hu/nav.json
@@ -12,13 +12,13 @@
"channels": "Csatornák",
"playlists": "Listák",
"plex": "Plex",
- "notifications": "Értesítés",
+ "notifications": "Értesítések",
"messages": "Üzenetek",
"downloads": "Letöltések",
"stats": "Statisztika",
"scheduler": "Ütemező",
"config": "Konfig",
- "users": "Fiókok",
+ "users": "Felhasználók",
"audit": "Audit",
"logs": "Napló",
"settings": "Beállítások"
diff --git a/frontend/src/i18n/locales/hu/notifications.json b/frontend/src/i18n/locales/hu/notifications.json
index 567be2d7..8f06bdfa 100644
--- a/frontend/src/i18n/locales/hu/notifications.json
+++ b/frontend/src/i18n/locales/hu/notifications.json
@@ -1,9 +1,9 @@
{
"actionNeeded": "Teendő",
- "dismiss": "Elvetés",
+ "dismiss": "Elrejtés",
"findInFeed": "Keresés a Feedben",
"unhide": "Megjelenítés",
"unwatch": "Megtekintés visszavonása",
- "openInManager": "Csatornakezelő",
+ "openInManager": "Megnyitás a Csatornákban",
"openOnYouTube": "Megnyitás a YouTube-on"
}
diff --git a/frontend/src/i18n/locales/hu/onboarding.json b/frontend/src/i18n/locales/hu/onboarding.json
index 85fe9613..dd4fcdc8 100644
--- a/frontend/src/i18n/locales/hu/onboarding.json
+++ b/frontend/src/i18n/locales/hu/onboarding.json
@@ -13,7 +13,7 @@
},
"write": {
"title": "Csatlakoztatva",
- "importFailed": "A YouTube-fiókod csatlakoztatva, de a feliratkozások importálása sikertelen volt — újrapróbálhatod a Beállítások → Szinkronizálás menüben. ",
+ "importFailed": "A YouTube-fiókod csatlakoztatva, de nem sikerült importálni a feliratkozásaidat — újrapróbálhatod a Beállítások → Szinkronizálás menüben. ",
"feedReady": "A Feeded készen áll{{channels}}. Választhatóan engedélyezheted, hogy a Siftlode ",
"feedReadyChannels": " ({{count}} csatorna)",
"rationale": "<0>leiratkozzon0> helyetted a YouTube-csatornákról — ehhez egy plusz írási engedély szükséges. Hagyd ki, ha csak olvasható módban szeretnél maradni; ezt később bármikor engedélyezheted a Beállításokban.",
diff --git a/frontend/src/i18n/locales/hu/playlists.json b/frontend/src/i18n/locales/hu/playlists.json
index 55fff3bd..0f01ee8b 100644
--- a/frontend/src/i18n/locales/hu/playlists.json
+++ b/frontend/src/i18n/locales/hu/playlists.json
@@ -3,9 +3,9 @@
"manageGroup": "Kezelés",
"listGroup": "Listáid",
"watchLater": "Megnézem később",
- "syncYoutube": "Szinkron YouTube-ról",
+ "syncYoutube": "Szinkronizálás a YouTube-ról",
"syncedToast": "{{count}} lista szinkronizálva a YouTube-ról",
- "syncFailed": "Nem sikerült a YouTube-szinkron",
+ "syncFailed": "Nem sikerült szinkronizálni a YouTube-ról",
"noneYet": "Még nincs lejátszási lista",
"newPlaylist": "Új lista…",
"searchPlaceholder": "Listák keresése…",
@@ -19,20 +19,21 @@
"delete": "Törlés",
"rename": "Átnevezés",
"confirmDelete": "Törlöd a(z) „{{name}}” listát? Nem vonható vissza.",
- "addToPlaylist": "Hozzáadás listához",
+ "addToPlaylist": "Hozzáadás listához…",
+ "addToPlaylistTitle": "Hozzáadás listához",
"exportToYoutube": "Exportálás YouTube-ra",
- "pushToYoutube": "Szinkron YouTube-ra",
+ "pushToYoutube": "Szinkronizálás a YouTube-ra",
"unsynced": "Nincs szinkronizálva",
"unsyncedHint": "Vannak helyi módosításaid, amelyek még nincsenek a YouTube-ra szinkronizálva.",
"openOnYoutube": "Megnyitás YouTube-on",
"revert": "Visszaállítás YouTube-ról",
- "revertTitle": "Eldobod a helyi módosításokat?",
- "revertMsg": "Ez újratölti a listát a YouTube-ról, és eldobja a nem szinkronizált helyi módosításaidat (sorrend, hozzáadás, eltávolítás, átnevezés). Folytatod?",
- "revertConfirm": "Eldobás és újratöltés",
- "revertDone": "„{{name}}” újratöltve a YouTube-ról ✓",
- "revertFailed": "Nem sikerült újratölteni a YouTube-ról.",
- "pushTitle": "Szinkron YouTube-ra",
- "pushConfirm": "Szinkron",
+ "revertTitle": "Elveted a helyi módosításokat?",
+ "revertMsg": "Ez újratölti a listát a YouTube-ról, és elveti a nem szinkronizált helyi módosításaidat (sorrend, hozzáadás, eltávolítás, átnevezés). Folytatod?",
+ "revertConfirm": "Elvetés és újratöltés",
+ "revertDone": "„{{name}}” szinkronizálva a YouTube-ról",
+ "revertFailed": "Nem sikerült szinkronizálni a YouTube-ról",
+ "pushTitle": "Szinkronizálás a YouTube-ra",
+ "pushConfirm": "Szinkronizálás",
"pushPlanCreate": "Létrehozol egy új YouTube-listát {{count}} videóval? (~{{units}} kvótaegység; ma még {{left}} maradt.)",
"mergeAddLocal_one": "{{count}} videó bekerül a Siftlode-listádba a YouTube-ról.",
"mergeAddLocal_other": "{{count}} videó bekerül a Siftlode-listádba a YouTube-ról.",
@@ -50,19 +51,19 @@
"mergeQuota": " (~{{units}} kvótaegység; ma még {{left}} maradt.)",
"mergeQuotaFree": " (~0 kvótaegység.)",
"pushNoQuota": "Nincs elég YouTube-kvóta mára ({{left}}) ehhez a szinkronhoz (~{{units}} kell). Próbáld újra holnap.",
- "pushUpToDate": "A(z) „{{name}}” már szinkronban van a YouTube-bal.",
- "pushDone": "„{{name}}” szinkronizálva a YouTube-ra ✓",
- "pushPartial": "Szinkronizálva, de {{count}} elem kimaradt.",
- "pushFailed": "Nem sikerült a YouTube-ra szinkronizálás.",
+ "pushUpToDate": "A(z) „{{name}}” már szinkronban van a YouTube-bal",
+ "pushDone": "„{{name}}” szinkronizálva a YouTube-ra",
+ "pushPartial": "Szinkronizálva, de {{count}} videó kimaradt",
+ "pushFailed": "Nem sikerült szinkronizálni a YouTube-ra",
"deleteHereOnly": "Törlés csak itt",
- "deleteYtFailed": "Nem sikerült törölni a YouTube-on; csak itt lett eltávolítva.",
+ "deleteYtFailed": "Nem sikerült törölni a YouTube-on; csak itt törölve",
"sortLabel": "Rendezés",
"dirAsc": "Növekvő",
"dirDesc": "Csökkenő",
"groupByChannel": "Csoportosítás csatorna szerint",
"railSortCustom": "Egyéni sorrend",
"railSortName": "Név",
- "railSortCount": "Elemszám",
+ "railSortCount": "Videók száma",
"railSortDuration": "Összhossz",
"railSortRecent": "Utoljára módosított",
"dirtyFirst": "Nem szinkronizáltak elöl",
@@ -72,24 +73,24 @@
"rowMenu": "Videó műveletek",
"moveTo": "Áthelyezés másik listába",
"moveToRecent": "Áthelyezés ide: „{{name}}”",
- "moveNoTargets": "Még nincs másik lejátszási lista, ahová áthelyezhetnéd.",
- "movedToast": "„{{title}}” áthelyezve ide: „{{name}}” ✓",
- "moveFailed": "Nem sikerült áthelyezni a videót.",
+ "moveNoTargets": "Még nincs másik lejátszási lista, ahová áthelyezhetnéd",
+ "movedToast": "„{{title}}” áthelyezve ide: „{{name}}”",
+ "moveFailed": "Nem sikerült áthelyezni a videót",
"removedToast": "„{{title}}” eltávolítva a listáról",
- "undoRemoveFailed": "Nem sikerült visszatenni a videót.",
- "removeFailed": "Nem sikerült eltávolítani a videót.",
+ "undoRemoveFailed": "Nem sikerült visszatenni a videót",
+ "removeFailed": "Nem sikerült eltávolítani a videót",
"errors": {
- "name_required": "Adj nevet a lejátszási listának.",
- "youtube_read_scope": "A listák szinkronizálásához kapcsold össze a YouTube-ot (olvasási hozzáférés).",
- "not_on_youtube": "Ez a lejátszási lista nincs fent a YouTube-on.",
- "watch_later_not_pushable": "A „Megnézem később” listát nem lehet a YouTube-ra küldeni.",
- "watch_later_undeletable": "A „Megnézem később” lista nem törölhető.",
- "youtube_editing_off": "Előbb engedélyezd a YouTube-szerkesztést a Beállításokban.",
- "quota_exceeded": "Mára nem maradt elég YouTube-keret ehhez a szinkronhoz."
+ "name_required": "Adj nevet a lejátszási listának",
+ "youtube_read_scope": "A listák szinkronizálásához kapcsold össze a YouTube-ot (olvasási hozzáférés)",
+ "not_on_youtube": "Ez a lejátszási lista nincs fent a YouTube-on",
+ "watch_later_not_pushable": "A „Megnézem később” listát nem lehet a YouTube-ra küldeni",
+ "watch_later_undeletable": "A „Megnézem később” lista nem törölhető",
+ "youtube_editing_off": "Előbb engedélyezd a YouTube-szerkesztést a Beállításokban",
+ "quota_exceeded": "Mára nem maradt elég YouTube-keret ehhez a szinkronhoz"
},
"moveErrors": {
- "not_in_playlist": "Ez a videó már nincs ebben a listában — időközben máshol megváltozott.",
- "same_playlist": "Ez a videó már ebben a listában van."
+ "not_in_playlist": "Ez a videó már nincs ebben a listában — időközben máshol megváltozott",
+ "same_playlist": "Ez a videó már ebben a listában van"
},
"railMenu": "Lejátszási lista műveletei",
"builtinLocked": "A beépített „{{name}}” lista nem nevezhető át és nem törölhető.",
@@ -102,10 +103,10 @@
"saveOrder": "Sorrend mentése",
"filteredCount": "{{count}} látszik",
"noneMatchFilter": "Egy videó sem felel meg a szűrőnek.",
- "loadFailed": "Nem sikerült betölteni ezt a listát.",
- "reorderFailed": "Nem sikerült elmenteni az új sorrendet.",
- "renameFailed": "Nem sikerült átnevezni a listát.",
- "deleteFailed": "Nem sikerült törölni a listát.",
+ "loadFailed": "Nem sikerült betölteni ezt a listát",
+ "reorderFailed": "Nem sikerült elmenteni az új sorrendet",
+ "renameFailed": "Nem sikerült átnevezni a listát",
+ "deleteFailed": "Nem sikerült törölni a listát",
"confirmDeleteLinked": "Törlöd a(z) „{{name}}” listát? A YouTube-bal szinkronban van — válaszd ki, hogy ott is törlődjön-e. Nem vonható vissza.",
"dragBlockedBuiltin": "Ennek a listának kötött a sorrendje — kézzel nem rendezhető át.",
"deleteBoth": "Törlés itt és a YouTube-on",
@@ -114,7 +115,7 @@
"viewDiffers": "Ideiglenes nézet ({{view}}) — a lista mentett sorrendje változatlan. Mentsd, ha ez maradjon.",
"viewMatches": "Ez a nézet ({{view}}) megegyezik a lista mentett sorrendjével, így nincs mit menteni.",
"viewFiltered": "Szűrt nézet — {{total}} elemből {{shown}} látszik. Szűrés közben a sorrend nem menthető.",
- "addFailed": "Nem sikerült hozzáadni a lejátszási listához.",
+ "addFailed": "Nem sikerült hozzáadni a lejátszási listához",
"limits": {
"hint": "Hiányzik egy lista?",
"why": "Miért?",
diff --git a/frontend/src/i18n/locales/hu/plex.json b/frontend/src/i18n/locales/hu/plex.json
index 07a3bf4d..1144d0ad 100644
--- a/frontend/src/i18n/locales/hu/plex.json
+++ b/frontend/src/i18n/locales/hu/plex.json
@@ -5,7 +5,7 @@
"playAll": "Összes lejátszása",
"shuffle": "Keverés",
"rename": "Átnevezés",
- "addItem": "Cím hozzáadása",
+ "addItem": "Cím hozzáadása…",
"delete": "Törlés",
"enableEdit": "Szerkesztés engedélyezése",
"removeItem": "Eltávolítás a kollekcióból",
@@ -21,15 +21,16 @@
"add": "Hozzáad",
"added": "Hozzáadva",
"deleteConfirm": "Törlöd a(z) „{{title}}” kollekciót? Ez a Plexből is eltávolítja.",
- "editFailed": "A kollekció szerkesztése sikertelen",
- "playFailed": "Nem sikerült összeállítani a lejátszási sort — próbáld újra.",
- "playEmpty": "Ebben a kollekcióban még nincs mit lejátszani.",
- "newCollection": "Új kollekció",
+ "editFailed": "Nem sikerült szerkeszteni a kollekciót",
+ "playFailed": "Nem sikerült összeállítani a lejátszási sort — próbáld újra",
+ "playEmpty": "Ebben a kollekcióban még nincs mit lejátszani",
+ "newCollection": "Új kollekció…",
+ "newCollectionTitle": "Új kollekció",
"namePlaceholder": "Kollekció neve",
- "nameTaken": "Már létezik ilyen nevű kollekció.",
+ "nameTaken": "Már létezik ilyen nevű kollekció",
"newHint": "Válaszd ki az első címet — egy kollekcióhoz legalább egy elem kell.",
"createWith": "Létrehozás",
- "deleteFailed": "Nem sikerült törölni a gyűjteményt."
+ "deleteFailed": "Nem sikerült törölni a gyűjteményt"
},
"searchPlaceholder": "Keresés a Plexben…",
"searchField": {
@@ -78,9 +79,9 @@
"duration": "Hossz",
"durationUnit": "perc",
"durationOpt": {
- "short": "90 perc alatt",
- "mid": "90–120 perc",
- "long": "2 óra felett"
+ "short": "90 p alatt",
+ "mid": "90–120 p",
+ "long": "2 ó felett"
},
"added": "Plexhez adva",
"addedOpt": {
@@ -128,7 +129,7 @@
"markNextWatched": "S{{season}}·E{{episode}} megnézettnek jelöl",
"markNextUnwatched": "S{{season}}·E{{episode}} nem-nézettnek jelöl",
"nextUp": "Következik: S{{season}}·E{{episode}}",
- "watchedFailed": "Nem sikerült frissíteni a megnézett állapotot — próbáld újra.",
+ "watchedFailed": "Nem sikerült frissíteni a megnézett állapotot — próbáld újra",
"seasons": "{{count}} évad",
"unified": {
"titles": "Címek",
@@ -159,7 +160,7 @@
"delete": "Törlés",
"deleteConfirm": "Törlöd a(z) „{{title}}” kollekciót a Plexből? Ez minden felhasználót érint.",
"note": "A változtatások a Plexbe íródnak, és minden kliensen/felhasználónál megjelennek.",
- "others": "{{count}} meglévő kollekció kezelése…",
+ "others": "{{count}} meglévő kollekció kezelése",
"makeEditable": "Kezelem"
},
"player": {
@@ -174,8 +175,8 @@
"restart": "Újra az elejéről",
"prev": "Előző rész",
"next": "Következő rész",
- "tapBack": "−{{n}} mp",
- "tapForward": "+{{n}} mp",
+ "tapBack": "−{{d}}",
+ "tapForward": "+{{d}}",
"mute": "Némítás (m)",
"volume": "Hangerő",
"fullscreen": "Teljes képernyő (f)",
@@ -186,7 +187,7 @@
"subOff": "Ki",
"errNotFound": "Ez a fájl nem játszható le — a média nem érhető el a szerveren (hiányzik a Plex media-mount, vagy hibás az útvonal-leképezés). Kérd meg az adminisztrátort, hogy ellenőrizze a Plex beállításait.",
"errTranscode": "Ennek a fájlnak a formátuma transzkódolást igényel, ami még nem támogatott.",
- "errGeneric": "A lejátszást nem sikerült elindítani. Próbáld újra.",
+ "errGeneric": "Nem sikerült elindítani a lejátszást. Próbáld újra.",
"help": "Billentyűparancsok",
"infoBtn": "Média infó",
"more": "Továbbiak",
@@ -222,9 +223,9 @@
"subStyle": "Feliratok",
"subPreparing": "Felirat előkészítése: {{label}}…",
"subUnavailable": {
- "bitmap": "Ez a felirat képalapú, szövegként nem jeleníthető meg.",
- "missing": "Ez a feliratsáv nem érhető el.",
- "failed": "Ezt a feliratot nem sikerült betölteni."
+ "bitmap": "Ez a felirat képalapú, szövegként nem jeleníthető meg",
+ "missing": "Ez a feliratsáv nem érhető el",
+ "failed": "Nem sikerült betölteni ezt a feliratot"
},
"subSize": "Szövegméret",
"subPos": "Pozíció",
@@ -294,16 +295,16 @@
"transcode_sw": "Szoftveresen átkódolva (legnehezebb)"
},
"playlistAdd": {
- "manage": "Listához adás",
+ "manage": "Listához adás…",
"title": "Listához adás — {{title}}",
"newPlaceholder": "Új lista neve…",
"create": "Létrehozás",
"none": "Még nincs lista. Hozz létre egyet fent.",
"count": "{{count}} elem",
"groupProgress": "{{in}} / {{size}}",
- "addFailed": "Nem sikerült hozzáadni a lejátszási listához.",
- "removeFailed": "Nem sikerült eltávolítani a lejátszási listáról.",
- "createFailed": "Nem sikerült létrehozni a lejátszási listát."
+ "addFailed": "Nem sikerült hozzáadni a lejátszási listához",
+ "removeFailed": "Nem sikerült eltávolítani a lejátszási listáról",
+ "createFailed": "Nem sikerült létrehozni a lejátszási listát"
},
"playlist": {
"section": "Lejátszási listák",
@@ -326,10 +327,10 @@
"addShow": "Egész sorozat listához adása",
"addSeason": "Évad listához adása",
"addEpisode": "Epizód listához adása",
- "removeFailed": "Nem sikerült eltávolítani a lejátszási listáról.",
- "renameFailed": "Nem sikerült átnevezni a lejátszási listát.",
- "deleteFailed": "Nem sikerült törölni a lejátszási listát.",
- "reorderFailed": "Nem sikerült menteni az új sorrendet.",
+ "removeFailed": "Nem sikerült eltávolítani a lejátszási listáról",
+ "renameFailed": "Nem sikerült átnevezni a lejátszási listát",
+ "deleteFailed": "Nem sikerült törölni a lejátszási listát",
+ "reorderFailed": "Nem sikerült menteni az új sorrendet",
"reorderItem": "„{{title}}” áthelyezése",
"rowMenu": "Elem műveletei",
"menu": "Lista műveletei",
@@ -337,7 +338,7 @@
"playSeason": "Évad lejátszása",
"removeFromPlaylist": "Eltávolítás a listáról",
"removedToast": "„{{title}}” eltávolítva a listáról",
- "undoRemoveFailed": "Nem sikerült visszatenni a listára."
+ "undoRemoveFailed": "Nem sikerült visszatenni a listára"
},
"people": {
"count_one": "{{count}} cím",
diff --git a/frontend/src/i18n/locales/hu/quotaActions.json b/frontend/src/i18n/locales/hu/quotaActions.json
index fb567bce..a4226012 100644
--- a/frontend/src/i18n/locales/hu/quotaActions.json
+++ b/frontend/src/i18n/locales/hu/quotaActions.json
@@ -1,7 +1,7 @@
{
- "subscriptions_pull": "Feliratkozások beolvasása",
+ "subscriptions_pull": "Feliratkozások szinkronizálása",
"subscriptions_resync": "Automatikus feliratkozás-újraszinkron",
- "playlists_pull": "Lejátszási listák beolvasása",
+ "playlists_pull": "Lejátszási listák szinkronizálása",
"playlists_push": "Lejátszási listák feltöltése",
"playlists_delete": "Lejátszási lista törlése",
"videos_backfill_recent": "Friss feltöltések betöltése",
diff --git a/frontend/src/i18n/locales/hu/recorder.json b/frontend/src/i18n/locales/hu/recorder.json
index 6783c30f..5300e21d 100644
--- a/frontend/src/i18n/locales/hu/recorder.json
+++ b/frontend/src/i18n/locales/hu/recorder.json
@@ -29,8 +29,8 @@
"shotFailed": "Nem sikerült elmenteni a képernyőképet",
"stopFailed": "Nem sikerült leállítani a rögzítést",
"stopped": "Rögzítés leállítva",
- "expired_one": "A rögzítés elérte a {{count}} perces határt, és leállt.",
- "expired_other": "A rögzítés elérte a {{count}} perces határt, és leállt."
+ "expired_one": "A rögzítés elérte a {{count}} perces határt, és leállt",
+ "expired_other": "A rögzítés elérte a {{count}} perces határt, és leállt"
},
"settings": {
"title": "Hibakereső munkamenet",
@@ -46,7 +46,7 @@
},
"composer": {
"title": "Hibajegy",
- "meta": "{{start}} · {{minutes}} perc · {{level}}",
+ "meta": "{{start}} · {{minutes}} · {{level}}",
"fieldTitle": "Cím",
"titlePlaceholder": "Mi romlott el?",
"description": "Leírás",
@@ -75,7 +75,7 @@
"reviewDone": "Kész",
"keepRow": "Sor megtartása: {{event}} {{msg}}",
"screenshot": "{{n}}. képernyőkép",
- "removeShot": "{{n}}. képernyőkép kivétele",
+ "removeShot": "{{n}}. képernyőkép törlése",
"drop": "Kép beillesztése: Ctrl+V, vagy húzd ide, vagy",
"pick": "válassz fájlt",
"close": "Bezárás",
@@ -85,7 +85,7 @@
"saveFailed": "Nem sikerült menteni a vázlatot",
"loadFailed": "Nem sikerült betölteni a munkamenetet",
"truncated": "A napló az első {{kept}} sort tartalmazza, összesen {{total}} volt.",
- "sent": "Elküldve hibajegyként.",
+ "sent": "Elküldve hibajegyként",
"openIssue": "Hibajegy megnyitása",
"env": {
"version": "Verzió",
@@ -99,7 +99,7 @@
"sentOk": "Hibajegy létrehozva",
"sendFailed": "Nem sikerült elküldeni a hibajegyet",
"target": "Cél: {{repo}} · privát",
- "partial": "A hibajegy létrejött, de nem ment el minden csatolmány: {{files}}.",
+ "partial": "A hibajegy létrejött, de nem ment el minden csatolmány: {{files}}",
"retry": "Hiányzók küldése",
"edit": "Szerkesztés",
"preview": "Előnézet",
@@ -108,7 +108,7 @@
"previewLocal": "Hibajegy-kezelő nélkül ez a zip issue.md fájlja.",
"shotsFailed": "Nem sikerült betölteni a képernyőképeket",
"removeShotFailed": "Nem sikerült törölni a képernyőképet",
- "partialImages": "A hibajegy létrejött, de a képernyőképek még nem látszanak a szövegében.",
+ "partialImages": "A hibajegy létrejött, de a képernyőképek még nem látszanak a szövegében",
"retryImages": "Megjelenítés a hibajegyben",
"sendingNow": "A hibajegy küldése folyamatban…",
"exportFailed": "Nem sikerült elkészíteni a zipet"
diff --git a/frontend/src/i18n/locales/hu/scheduler.json b/frontend/src/i18n/locales/hu/scheduler.json
index bd99bfc3..0ec76b9b 100644
--- a/frontend/src/i18n/locales/hu/scheduler.json
+++ b/frontend/src/i18n/locales/hu/scheduler.json
@@ -24,7 +24,7 @@
"quotaRemaining": "{{formatted}} maradt",
"quotaNote": "Közös YouTube Data API-keret; éjfélkor (US Pacific) nullázódik. A backfill hátrébb sorol, hogy maradjon hely az interaktív használatnak.",
"minutes": "perc",
- "range": "{{min}} és {{max}} között lehet.",
+ "range": "{{min}} és {{max}} között lehet",
"editInterval": "Kattints a gyakoriság módosításához",
"intervalFailed": "Nem sikerült módosítani a gyakoriságot",
"runNow": "Futtatás most",
@@ -85,8 +85,8 @@
"backfill": "Backfill (friss + teljes előzmény)",
"autotag": "Automatikus címkézés",
"shorts": "Shorts-besorolás",
- "subscriptions": "Feliratkozások újraszinkronja",
- "playlist_sync": "YouTube lejátszási listák szinkronja",
+ "subscriptions": "Feliratkozások szinkronizálása a YouTube-ról",
+ "playlist_sync": "Lejátszási listák szinkronizálása a YouTube-ról",
"maintenance": "Karbantartás + ellenőrzés",
"demo_reset": "Demo fiók visszaállítása",
"explore_cleanup": "Felfedezett csatornák takarítása",
@@ -111,7 +111,7 @@
"liveRefreshHint": "Élő/közelgő videók (és a frissen véget ért, hossz nélküli adások), amíg le nem ülepednek."
},
"purgeDiscovery": "Felfedezés ürítése",
- "purgeDiscoveryHint": "Most eltávolítja az összes meg nem tartott felfedezés-tartalmat (senki által meg nem nézett/mentett keresési találatok és felfedezett, de nem követett csatornák) — a türelmi időt figyelmen kívül hagyva.",
+ "purgeDiscoveryHint": "Most törli az összes meg nem tartott felfedezés-tartalmat (senki által meg nem nézett/mentett keresési találatok és felfedezett, de nem követett csatornák) — a türelmi időt figyelmen kívül hagyva.",
"purgeConfirmTitle": "Felfedezés-tartalom törlése?",
- "purgedDiscovery": "{{count}} felfedezés-elem eltávolítva"
+ "purgedDiscovery": "{{count}} felfedezés-elem törölve"
}
diff --git a/frontend/src/i18n/locales/hu/settings.json b/frontend/src/i18n/locales/hu/settings.json
index 6f9df609..cf7e648e 100644
--- a/frontend/src/i18n/locales/hu/settings.json
+++ b/frontend/src/i18n/locales/hu/settings.json
@@ -36,17 +36,17 @@
"railLabels": "Feliratok az oldalsó menüben",
"railLabelsHint": "Rövid név minden ikon alatt a bal oldali menüben. A menü ugyanolyan keskeny marad, csak magasabb lesz.",
"startup": "Indulás",
- "startOnHome": "Indítás a kezdőlapon",
+ "startOnHome": "Indítás a Kezdőlapon",
"startOnHomeHint": "Minden induláskor a „Mit nézzek most?” oldal nyílik meg. Kikapcsolva ott folytatod, ahol abbahagytad.",
"textSize": "Betűméret"
},
"notifications": {
"title": "Értesítések",
- "sound": "Hang riasztásoknál",
+ "sound": "Hang értesítéseknél",
"soundHint": "Rövid hangjelzés, ha egy értesítés a figyelmedet igényli (pl. hibák, kérdések).",
- "autoDismiss": "Buborékok automatikus eltüntetése",
+ "autoDismiss": "Buborékok automatikus elrejtése",
"autoDismissHint": "Kikapcsolva a felugró buborékok addig maradnak, amíg be nem zárod őket. Bekapcsolva a beállított idő után elhalványulnak; a gombos (Visszavonás) legalább 8 másodpercig marad.",
- "dismissAfter": "Eltüntetés ennyi után",
+ "dismissAfter": "Elrejtés ennyi után",
"sendTest": "Tesztértesítés küldése",
"testTitle": "Tesztértesítés",
"testMessage": "Így néz ki egy értesítés."
@@ -73,7 +73,7 @@
"deleteTitle": "Törlöd a fiókodat?",
"deleteConfirm": "Ez véglegesen törli a fiókodat és minden adatodat (feliratkozások, címkék, megnézett/mentett/elrejtett állapot, lejátszási listák, beállítások). Nem vonható vissza.",
"deleteConfirmButton": "Minden törlése",
- "demoNotice": "Ez a közös demo fiók. Nincs YouTube-kapcsolata — böngészheted a teljes közös könyvtárat, szűrheted és rendezheted, listákat építhetsz és kipróbálhatsz dolgokat, de semmi nem nyúl valódi YouTube-fiókhoz.",
+ "demoNotice": "Ez a közös demo fiók. Nincs YouTube-kapcsolata — böngészheted a teljes közös Könyvtárat, szűrheted és rendezheted, listákat építhetsz és kipróbálhatsz dolgokat, de semmi nem nyúl valódi YouTube-fiókhoz.",
"signInMethods": "Bejelentkezési módok",
"googleLink": {
"title": "Google-fiók",
@@ -81,13 +81,13 @@
"connectedHint": "A Google-fiókod össze van kapcsolva. Bejelentkezhetsz Google-lel, és alább engedélyezheted a YouTube-hozzáférést.",
"connectHint": "Kapcsolj össze egy Google-fiókot, hogy Google-lel jelentkezhess be, és engedélyezhesd a YouTube-hozzáférést a Feededhez.",
"connect": "Google összekapcsolása",
- "linked": "Google-fiók összekapcsolva.",
- "conflict": "Ez a Google-fiók már egy másik Siftlode-fiókhoz van kapcsolva.",
+ "linked": "Google-fiók összekapcsolva",
+ "conflict": "Ez a Google-fiók már egy másik Siftlode-fiókhoz van kapcsolva",
"mismatch": "Ez nem az a Google-fiók, amely ide már össze van kapcsolva. Az összekapcsolttal jelentkezz be.",
"error": "Nem sikerült összekapcsolni a Google-fiókot. Próbáld újra.",
"signedInAs": "Google-belépés ezzel a címmel:"
},
- "googleCancelled": "A Google-belépés megszakadt.",
+ "googleCancelled": "A Google-belépés megszakadt",
"password": {
"title": "Jelszó",
"setHint": "Van beállított jelszó. Bejelentkezhetsz e-mail-címmel és jelszóval.",
@@ -98,8 +98,8 @@
"new": "Új jelszó (min. 10 karakter)",
"save": "Jelszó mentése",
"saving": "Mentés…",
- "saved": "Jelszó frissítve ehhez: {{email}}.",
- "failed": "Nem sikerült frissíteni a jelszót."
+ "saved": "Jelszó frissítve ehhez: {{email}}",
+ "failed": "Nem sikerült frissíteni a jelszót"
},
"sessions": {
"title": "Aktív munkamenetek",
@@ -107,7 +107,7 @@
"action": "Kijelentkezés máshonnan",
"confirmTitle": "Kijelentkezés a többi munkamenetből?",
"confirmBody": "Ez kijelentkeztet minden más böngészőből és eszközről. Itt bejelentkezve maradsz.",
- "done": "Kijelentkeztetve minden más munkamenetből.",
+ "done": "Kijelentkeztetve minden más munkamenetből",
"failed": "Nem sikerült kijelentkeztetni a többi munkamenetet. Próbáld újra."
}
},
@@ -116,21 +116,21 @@
"intro": "Egyes oldalak (pl. egy zárt Facebook-csoport, aminek tagja vagy) csak bejelentkezett tagoknak mutatják a videót. Illeszd be ide a böngésződ cookie-jait egy oldalhoz, és az onnan induló letöltések a te bejelentkezésedet használják. A cookie-k titkosítva, csak a te fiókodhoz kötve tárolódnak, és kizárólag az adott oldalnak küldjük el őket.",
"configured": "Beállítva · frissítve: {{when}}",
"notSet": "Nincs beállítva",
- "set": "Cookie-k beállítása",
- "update": "Frissítés",
- "remove": "Eltávolítás",
+ "set": "Cookie-k beállítása…",
+ "update": "Frissítés…",
+ "remove": "Törlés",
"save": "Mentés",
"cancel": "Mégse",
"saving": "Mentés…",
"placeholder": "Illeszd be ide a cookies.txt tartalmát (Netscape formátum)",
"help": "Exportálj cookies.txt-t ehhez az oldalhoz egy böngészőbővítménnyel, majd illeszd be ide. Csak írható — sosem mutatjuk vissza.",
- "saved": "Cookie-k mentve.",
- "removed": "Cookie-k eltávolítva.",
- "failed": "A cookie-kat nem sikerült menteni.",
- "removeConfirmTitle": "Cookie-k eltávolítása?",
+ "saved": "Cookie-k mentve",
+ "removed": "Cookie-k törölve",
+ "failed": "Nem sikerült menteni a cookie-kat",
+ "removeConfirmTitle": "Törlöd a cookie-kat?",
"removeConfirmBody": "Az erről az oldalról induló letöltések nem használják tovább a bejelentkezésedet.",
- "removeConfirmAction": "Eltávolítás",
- "loadError": "Nem sikerült betölteni a letöltési hitelesítési beállításokat."
+ "removeConfirmAction": "Törlés",
+ "loadError": "Nem sikerült betölteni a letöltési hitelesítési beállításokat"
},
"demo": {
"title": "Demo hozzáférés",
@@ -160,14 +160,14 @@
"add": "Hozzáadás",
"decided": "{{count}} eldöntve",
"approved": "Jóváhagyva: {{email}} — most már be tud jelentkezni",
- "approveFailed": "A jóváhagyás sikertelen",
- "denyFailed": "Az elutasítás sikertelen",
+ "approveFailed": "Nem sikerült jóváhagyni a kérelmet",
+ "denyFailed": "Nem sikerült elutasítani a kérelmet",
"addedToWhitelist": "{{email}} hozzáadva a fehérlistához",
"inviteHint": "Küldj neki meghívót. A cím hozzáadása soha nem küld levelet — ez igen.",
"invitedAt": "Meghívva: {{time}}. Az újraküldés másodszor is levelet küld neki.",
"inviteLabel": "Meghívó küldése ide: {{email}}",
- "invited": "Meghívó elküldve ide: {{email}}.",
- "inviteFailed": "Nem sikerült elküldeni a meghívót.",
+ "invited": "Meghívó elküldve ide: {{email}}",
+ "inviteFailed": "Nem sikerült elküldeni a meghívót",
"inviteTitle": "Küldesz meghívót?",
"inviteConfirm": "Levél megy a(z) {{email}} címre arról, hogy a címe engedélyezve van, és nyitva áll előtte a Siftlode.",
"inviteConfirmAgain": "Ezt a címet már meghívtad ekkor: {{time}} ({{email}}). Ha most küldesz, másodszor is írsz neki.",
@@ -188,9 +188,9 @@
"deleteTitle": "Törlöd a whitelista-bejegyzést?",
"deleteConfirm": "Elfelejted {{email}} címet? A Siftlode eldobja a bejegyzést, így az adott címről újra kérelmezni kell.",
"deleteHasAccount": "Erre a címre már regisztrált valaki — a Felhasználók és szerepek fülön kezelhető, ahol a fiók törlése ezt a bejegyzést is eltávolítja.",
- "deleted": "Whitelista-bejegyzés eltávolítva.",
- "deleteFailed": "A bejegyzést nem sikerült eltávolítani.",
- "emailOff": "Az e-mail-küldés nincs beállítva, így senki nem kap megerősítő linket, és e-mailben egy kérelem sem jut el hozzád. Ezt a listát és az értesítéseidet figyeld helyette, a javítás pedig a Konfiguráció → E-mail / SMTP fülön van.",
+ "deleted": "Whitelista-bejegyzés törölve",
+ "deleteFailed": "Nem sikerült törölni a bejegyzést",
+ "emailOff": "Az e-mail-küldés nincs beállítva, így senki nem kap megerősítő linket, és e-mailben egy kérelem sem jut el hozzád. Ezt a listát és az Értesítéseket figyeld helyette, a javítás pedig a Konfiguráció → E-mail / SMTP fülön van.",
"deletedStillAllowed": "A bejegyzés törölve, de ezt a címet a szerver konfigurációja továbbra is engedélyezi, tehát megmarad a hozzáférése. Vedd ki az ALLOWED_EMAILS / ADMIN_EMAILS listából, ha ezen változtatnál.",
"deleteConfirmStillAllowed": "Elfelejted {{email}} címet? Figyelem: ezt a címet a szerver konfigurációja is engedélyezi (ALLOWED_EMAILS / ADMIN_EMAILS), így a bejegyzés törlése NEM veszi el a hozzáférését — sőt egy ADMIN_EMAILS cím a következő Google-belépéskor újra adminná válik."
},
@@ -215,14 +215,12 @@
"server_one": "Ezt a Siftlode szervere is megkapja, és {{count}} napig őrzi, hogy a hibát ki lehessen nyomozni.",
"server_other": "Ezt a Siftlode szervere is megkapja, és {{count}} napig őrzi, hogy a hibát ki lehessen nyomozni.",
"never": "Jelszó, token, e-mail-cím és az üzeneteid tartalma soha nem kerül bele.",
- "lapse": "24 óra múlva magától visszaáll Alapra.",
+ "lapse": "24 óra múlva magától visszaáll „Alap” szintre.",
"policy": "Adatvédelmi irányelvek",
"cancel": "Mégse",
"confirm": "Bekapcsolom 24 órára"
},
- "status": "{{level}} naplózás · visszaáll Alapra {{time}} múlva",
- "hoursMinutes": "{{h}} ó {{m}} p",
- "minutes": "{{m}} p",
+ "status": "{{level}} naplózás · {{time}} múlva visszaáll „Alap” szintre",
"revert": "Visszaállítás most",
"demo": "A demo fiókot többen használják, ezért itt nem kapcsolható be részletes naplózás.",
"failed": "Nem sikerült módosítani a naplózási szintet"
diff --git a/frontend/src/i18n/locales/hu/setup.json b/frontend/src/i18n/locales/hu/setup.json
index 22f50ddd..f2113f6e 100644
--- a/frontend/src/i18n/locales/hu/setup.json
+++ b/frontend/src/i18n/locales/hu/setup.json
@@ -8,16 +8,16 @@
"desc": "Ez az első fiók, egyben adminisztrátor. A telepítés után ezzel az e-maillel és jelszóval jelentkezel be.",
"email": "E-mail-cím",
"password": "Jelszó (min. {{n}} karakter)",
- "invalidEmail": "Adj meg érvényes e-mail-címet.",
- "shortPassword": "A jelszó legalább {{n}} karakter legyen."
+ "invalidEmail": "Adj meg érvényes e-mail-címet",
+ "shortPassword": "A jelszó legalább {{n}} karakter legyen"
},
"google": {
"title": "Google bejelentkezés (opcionális)",
"desc": "Illeszd be a Google OAuth kliens-azonosítót és -titkot a Google-bejelentkezés és a YouTube-hozzáférés engedélyezéséhez. Hagyd üresen mindkettőt a kihagyáshoz — e-mail+jelszó enélkül is működik.",
"clientId": "Google kliens-azonosító",
"clientSecret": "Google kliens-titok",
- "bothRequired": "Add meg a kliens-azonosítót és a -titkot is, vagy hagyd üresen mindkettőt a kihagyáshoz.",
- "skipHint": "Hagyd üresen, ha később, az admin Configuration oldalon állítanád be."
+ "bothRequired": "Add meg a kliens-azonosítót és a -titkot is, vagy hagyd üresen mindkettőt a kihagyáshoz",
+ "skipHint": "Hagyd üresen, ha később, az admin Konfiguráció oldalon állítanád be."
},
"smtp": {
"title": "E-mail / SMTP (opcionális)",
@@ -29,9 +29,9 @@
"password": "SMTP-jelszó",
"testTo": "Teszt-e-mail ide…",
"test": "Teszt küldése",
- "testOk": "Teszt-e-mail elküldve — nézd meg a postafiókot.",
- "testFail": "Nem sikerült elküldeni — ellenőrizd a beállításokat.",
- "skipHint": "Hagyd üresen, ha később, az admin Configuration oldalon állítanád be."
+ "testOk": "Teszt-e-mail elküldve — nézd meg a postafiókot",
+ "testFail": "Nem sikerült elküldeni — ellenőrizd a beállításokat",
+ "skipHint": "Hagyd üresen, ha később, az admin Konfiguráció oldalon állítanád be."
},
"finish": {
"title": "Minden kész!",
@@ -46,5 +46,5 @@
"title": "Setup-link szükséges",
"body": "Nyisd meg a varázslót a konténer logjaiba az első indításkor kiírt linkkel (így néz ki: …/setup?token=…). Ez azért kell, hogy csak az üzemeltető tudja beállítani a példányt."
},
- "genericError": "Valami hiba történt — próbáld újra."
+ "genericError": "Valami hiba történt — próbáld újra"
}
diff --git a/frontend/src/i18n/locales/hu/sidebar.json b/frontend/src/i18n/locales/hu/sidebar.json
index cfb3e83a..0e9b58f6 100644
--- a/frontend/src/i18n/locales/hu/sidebar.json
+++ b/frontend/src/i18n/locales/hu/sidebar.json
@@ -36,7 +36,7 @@
"year": "Év"
},
"year": {
- "more": "Több…",
+ "more": "Több",
"moreLabel": "Összes év",
"none": "Még nincs év",
"sortBy": "Rendezés",
@@ -54,7 +54,7 @@
"content": {
"normal": "Normál",
"shorts": "Shorts",
- "live": "Élő / Hamarosan"
+ "live": "Élő / hamarosan"
},
"datePresetLabel": "Feltöltés dátuma",
"datePreset": {
diff --git a/frontend/src/i18n/locales/hu/stats.json b/frontend/src/i18n/locales/hu/stats.json
index 54968316..fc559d08 100644
--- a/frontend/src/i18n/locales/hu/stats.json
+++ b/frontend/src/i18n/locales/hu/stats.json
@@ -4,14 +4,14 @@
"system": "Rendszer"
},
"title": "API-kvóta használat",
- "rangeDays": "{{count}} nap",
+ "rangeDays": "{{d}}",
"rangeLabel": "Időtartomány",
"introBefore": "A YouTube Data API-egységek aszerint, hogy ki váltotta ki a felhasználást. A ",
"introSystem": "Rendszer",
"introAfter": " a megosztott háttérmunka (ütemezett letöltés, gazdagítás, újraszinkronizálás), amely nem köthető egy személyhez.",
"loading": "Betöltés…",
"noData": "Nincs adat.",
- "dailyTotal": "Napi összesen ({{count}} nap · {{units}} egység)",
+ "dailyTotal": "Napi összesen ({{d}} · {{units}} egység)",
"noUsageInRange": "Nincs használat ebben az időszakban.",
"dailyTooltip": "{{day}}: {{units}} egység",
"system": "Rendszer (háttér)",
@@ -39,12 +39,12 @@
"byAction": "Művelet szerint (30 nap)",
"noUsage": "Még nincs használat.",
"actions": "Műveletek",
- "syncSubscriptions": "Feliratkozások szinkronizálása",
+ "syncSubscriptions": "Feliratkozások szinkronizálása a YouTube-ról",
"syncSubscriptionsHint": "Újraimportálja a YouTube-feliratkozásaidat és behúzza az újonnan követett csatornákat.",
"backfillEverything": "Minden letöltése",
"backfillEverythingHint": "Teljes archívum letöltésének kérése minden csatornához, amelyre fel vagy iratkozva. A régebbi videók és a teljes keresés annyira töltődnek fel, amennyit a megosztott napi kvóta enged.",
"synced": "{{count}} feliratkozás szinkronizálva",
- "syncFailed": "A szinkronizálás sikertelen",
+ "syncFailed": "Nem sikerült szinkronizálni a feliratkozásokat",
"fullHistoryRequested": "Teljes előzmény kérve {{count}} csatornához",
"fullHistoryFailed": "Nem sikerült teljes előzményt kérni",
"admin": "Adminisztrátor",
diff --git a/frontend/src/i18n/locales/hu/tagManager.json b/frontend/src/i18n/locales/hu/tagManager.json
index 543fc1cc..76a6d766 100644
--- a/frontend/src/i18n/locales/hu/tagManager.json
+++ b/frontend/src/i18n/locales/hu/tagManager.json
@@ -9,6 +9,6 @@
"empty": "Még nincs címke — adj hozzá lentebb.",
"newPlaceholder": "Új címke neve…",
"add": "Hozzáad",
- "renameFailed": "Nem sikerült átnevezni a címkét.",
- "deleteFailed": "Nem sikerült törölni a címkét."
+ "renameFailed": "Nem sikerült átnevezni a címkét",
+ "deleteFailed": "Nem sikerült törölni a címkét"
}
diff --git a/frontend/src/i18n/locales/hu/users.json b/frontend/src/i18n/locales/hu/users.json
index 3e41e878..71272888 100644
--- a/frontend/src/i18n/locales/hu/users.json
+++ b/frontend/src/i18n/locales/hu/users.json
@@ -13,12 +13,12 @@
"inactive": "aktiválásra vár",
"activate": "Aktiválás",
"activateHint": "Beengedi ezt a fiókot. A címe már jóvá van hagyva — ez csak befejezi.",
- "activated": "{{email}} mostantól be tud lépni.",
- "activateFailed": "Nem sikerült aktiválni a fiókot.",
+ "activated": "{{email}} mostantól be tud lépni",
+ "activateFailed": "Nem sikerült aktiválni a fiókot",
"resend": "Megerősítő link újraküldése",
"resendHint": "Újraküldi a megerősítő linket, a fiókon szereplő címre.",
- "resent": "Megerősítő link elküldve ide: {{email}}.",
- "resendFailed": "Nem sikerült elküldeni a megerősítő linket."
+ "resent": "Megerősítő link elküldve ide: {{email}}",
+ "resendFailed": "Nem sikerült elküldeni a megerősítő linket"
},
"roles": {
"title": "Felhasználók és szerepek",
@@ -42,7 +42,7 @@
"demoteTitle": "Elveszed az admin jogot?",
"promoteConfirm": "Teljes admin hozzáférést adsz neki: {{email}} (beállítások, ütemező, felhasználókezelés)?",
"demoteConfirm": "Elveszed az admin jogot tőle: {{email}}? Sima felhasználó lesz.",
- "failed": "Nem sikerült módosítani a szerepkört."
+ "failed": "Nem sikerült módosítani a szerepkört"
},
"suspend": {
"action": "Felfüggeszt",
@@ -55,7 +55,7 @@
"undone": "{{email}} felfüggesztése feloldva",
"demoLocked": "A demo fiók nem függeszthető fel.",
"selfLocked": "A saját fiókodat nem függesztheted fel.",
- "failed": "Nem sikerült módosítani a felfüggesztést."
+ "failed": "Nem sikerült módosítani a felfüggesztést"
},
"delete": {
"action": "Törlés",
@@ -63,7 +63,7 @@
"title": "Törlöd a fiókot?",
"confirm": "Véglegesen törlöd: {{email}} és minden adatát — feliratkozások, címkék, megtekintési előzmény, lejátszási listák és beállítások? Ez nem visszavonható.",
"done": "{{email}} törölve",
- "failed": "Nem sikerült törölni a fiókot."
+ "failed": "Nem sikerült törölni a fiókot"
},
"account": {
"access": "Hozzáférés",
@@ -75,7 +75,7 @@
"approvalWaitingOnYou": "rád vár",
"copied": "Másolva",
"copy": "Másolás",
- "copyFailed": "Nem sikerült másolni — jelöld ki a linket, és másold kézzel.",
+ "copyFailed": "Nem sikerült másolni — jelöld ki a linket, és másold kézzel",
"email": "E-mail",
"emailConfirmed": "megerősítve",
"emailUnconfirmed": "nincs megerősítve",
@@ -97,7 +97,7 @@
"passwordSet": "be van állítva",
"recovery": "Helyreállítás",
"resend": "Megerősítő link küldése",
- "resendSent": "Megerősítő link elküldve.",
+ "resendSent": "Megerősítő link elküldve",
"setPassword": "Jelszóbeállító link készítése",
"setPasswordAgain": "Új link készítése",
"linkFailed": "Nem sikerült létrehozni a linket. Próbáld újra."
diff --git a/frontend/src/i18n/locales/hu/views.json b/frontend/src/i18n/locales/hu/views.json
index 5ce4c553..b8429240 100644
--- a/frontend/src/i18n/locales/hu/views.json
+++ b/frontend/src/i18n/locales/hu/views.json
@@ -7,8 +7,8 @@
"delete": "Törlés",
"deleteConfirm": "Törlöd a(z) „{{name}}” mentett nézetet?",
"reorder": "Húzd az átrendezéshez",
- "actionFailed": "Nem sikerült frissíteni a mentett nézeteket — próbáld újra.",
- "reorderFailed": "Nem sikerült menteni az új sorrendet — próbáld újra.",
+ "actionFailed": "Nem sikerült frissíteni a mentett nézeteket — próbáld újra",
+ "reorderFailed": "Nem sikerült menteni az új sorrendet — próbáld újra",
"saved": "Mentett nézetek",
"manage": "Nézetek kezelése…",
"manageTitle": "Személyes nézetek",
diff --git a/frontend/src/i18n/locales/hu/welcome.json b/frontend/src/i18n/locales/hu/welcome.json
index 998a4694..4f6b3ca8 100644
--- a/frontend/src/i18n/locales/hu/welcome.json
+++ b/frontend/src/i18n/locales/hu/welcome.json
@@ -11,10 +11,10 @@
},
"preview": {
"feed": "A Feed — szűrés csatorna, nyelv, téma, hossz és dátum szerint.",
- "channels": "Csatorna-kezelő",
+ "channels": "Csatornák",
"playlists": "Lejátszási listák",
"player": "Beépített lejátszó",
- "downloads": "Letöltésközpont",
+ "downloads": "Letöltések",
"search": "Keresési találatok",
"plex": "A Plex-könyvtárad"
},
@@ -41,7 +41,7 @@
"body": "A beépített lejátszó onnan folytatja a videót, ahol abbahagytad — billentyű- és görgetés-vezérléssel."
},
"downloads": {
- "title": "Letöltésközpont",
+ "title": "Letöltések",
"body": "Ments videókat a szerveredre Plex-barát elrendezésben, vágd vagy fűzd össze őket, majd oszd meg egy linkkel."
},
"plex": {
@@ -61,8 +61,8 @@
"email": "E-mail",
"password": "Jelszó",
"newPassword": "Új jelszó (min. {{min}} karakter)",
- "invalidEmail": "Adj meg egy érvényes e-mail-címet.",
- "weakPassword": "A jelszónak legalább {{min}} karakteresnek kell lennie.",
+ "invalidEmail": "Adj meg egy érvényes e-mail-címet",
+ "weakPassword": "A jelszónak legalább {{min}} karakteresnek kell lennie",
"genericError": "Valami hiba történt. Próbáld újra.",
"working": "Egy pillanat…",
"signinButton": "Belépés",
@@ -84,17 +84,17 @@
"demoDenied": "Ez az e-mail nincs engedélyezve a demóhoz. Kérd az admint, hogy adja hozzá.",
"registerDone": "Majdnem kész — nézd meg a postafiókod a megerősítő linkért, és a Spam mappát is, ha egy percen belül nem jön meg. Miután megerősítetted, egy admin jóváhagyja a fiókodat, mielőtt beléphetnél.",
"forgotDone": "Ha ehhez az e-mailhez tartozik fiók, elküldtük a visszaállító linket. Nézd meg a postafiókod, és a Spam mappát is, ha egy percen belül nem érkezik meg.",
- "resetDone": "A jelszavad be van állítva — most már beléphetsz.",
+ "resetDone": "A jelszavad be van állítva — most már beléphetsz",
"deadLink": {
"title": "Ez a link már nem érvényes",
"body": "A jelszó-visszaállító link egy óráig él, és egyszer használható. Ez lejárt, már felhasználták, vagy olyan fiókhoz tartozik, ami már nem létezik — nincs mire jelszót állítani. Kérj újat, és elküldjük.",
- "short": "Ez a visszaállító link már nem érvényes.",
+ "short": "Ez a visszaállító link már nem érvényes",
"cta": "Új link kérése"
},
"verifyOk": "E-mail megerősítve. Amint egy admin jóváhagyja a fiókodat, beléphetsz.",
"verifyOkReset": "E-mail megerősítve. A fiók regisztrációkori jelszavát biztonsági okból korábban töröltük — válassz újat a belépéshez.",
"verifyOkResetCta": "Új jelszó beállítása",
- "verifyInvalid": "Ez a megerősítő link érvénytelen vagy lejárt.",
+ "verifyInvalid": "Ez a megerősítő link érvénytelen vagy lejárt",
"verifyUnreachable": "Nem sikerült elérni a szervert a link megerősítéséhez, így az nem is használódott el. Próbáld megnyitni újra egy pillanat múlva.",
"resendCta": "Új link küldése",
"resendDone": "Ha ehhez az e-mailhez tartozik megerősítetlen fiók, elküldtünk egy új megerősítő linket. Nézd meg a postafiókod, és a Spam mappát is, ha egy percen belül nem érkezik meg.",
@@ -116,26 +116,26 @@
"email_unverified": {
"title": "Erősítsd meg az e-mail-címed",
"body": "A jelszavad jó — csak addig nem engedhetünk be, amíg a fiókon lévő cím nincs megerősítve. Nyisd meg a kiküldött linket, vagy kérj újat.",
- "short": "Előbb erősítsd meg az e-mail-címed — nézd meg a postafiókod."
+ "short": "Előbb erősítsd meg az e-mail-címed — nézd meg a postafiókod"
},
"pending_approval": {
"title": "Jóváhagyásra vár",
"body": "A jelszavad jó, a címed megerősítve. Már csak arra várunk, hogy egy admin beengedjen — szólunk e-mailben, amint döntött.",
- "short": "A fiókod admin jóváhagyásra vár."
+ "short": "A fiókod admin jóváhagyásra vár"
},
"suspended": {
"title": "Felfüggesztett fiók",
"body": "Ez a fiók fel van függesztve, így semmilyen módon nem tud belépni.",
- "short": "Ez a fiók fel van függesztve."
+ "short": "Ez a fiók fel van függesztve"
},
"invalid": {
- "short": "Hibás e-mail-cím vagy jelszó."
+ "short": "Hibás e-mail-cím vagy jelszó"
},
"rate_limited": {
"short": "Túl sok próbálkozás. Próbáld újra kicsit később."
}
},
- "accessDenied": "Ez a fiók még nincs jóváhagyva ehhez a példányhoz — regisztrálj lent, vagy kérj hozzáférést az admintól.",
+ "accessDenied": "Ez a fiók még nincs jóváhagyva ehhez a példányhoz — regisztrálj lent, vagy kérj hozzáférést az admintól",
"suspended": "Ez a fiók fel van függesztve, nem tud belépni. Ha tévedésnek gondolod, keresd az üzemeltetőt (a részletek az e-mailedben).",
"deleted": "A fiókod és minden adata véglegesen törölve lett. A megerősítést e-mailben is elküldtük."
}
diff --git a/frontend/src/lib/dateFormat.contract.test.ts b/frontend/src/lib/dateFormat.contract.test.ts
new file mode 100644
index 00000000..266ab09b
--- /dev/null
+++ b/frontend/src/lib/dateFormat.contract.test.ts
@@ -0,0 +1,32 @@
+import { describe, expect, it } from "vitest";
+
+import { eslint } from "./lintProbe.testkit";
+
+// `local/date-format` (eslint.config.js): docs/DESIGN.md "Copy" — a date is shown in the forms
+// lib/format.ts builds, in the UI language. This pins that the rule fires on the ways a date used to
+// be formatted by hand, and where it stands down.
+const RULE = "local/date-format";
+
+async function hits(source: string, filePath = "src/components/__probe.tsx"): Promise {
+ const [result] = await eslint.lintText(source, { filePath });
+ return (result?.messages ?? []).filter((m) => m.ruleId === RULE).length;
+}
+
+describe("a hand-formatted date", () => {
+ it("is refused as toLocale*String or an Intl date formatter", async () => {
+ expect(await hits("export const a = (d: Date) => d.toLocaleString('en');\n")).toBe(1);
+ expect(await hits("export const b = (d: Date) => d.toLocaleDateString();\n")).toBe(1);
+ expect(await hits("export const c = (d: Date) => d.toLocaleTimeString();\n")).toBe(1);
+ expect(
+ await hits("export const e = new Intl.DateTimeFormat('en', { month: 'short' });\n"),
+ ).toBe(1);
+ expect(await hits("export const f = new Intl.RelativeTimeFormat('en');\n")).toBe(1);
+ });
+
+ it("is left alone in lib/format.ts, in a test and for a number format", async () => {
+ const src = "export const g = (d: Date) => d.toLocaleString('en');\n";
+ expect(await hits(src, "src/lib/format.ts")).toBe(0);
+ expect(await hits(src, "src/lib/format.test.ts")).toBe(0);
+ expect(await hits("export const h = new Intl.NumberFormat('en');\n")).toBe(0);
+ });
+});
diff --git a/frontend/src/lib/downloadFailure.test.ts b/frontend/src/lib/downloadFailure.test.ts
index 9c4039ff..607ec0a3 100644
--- a/frontend/src/lib/downloadFailure.test.ts
+++ b/frontend/src/lib/downloadFailure.test.ts
@@ -43,7 +43,7 @@ describe("failCopyKeys", () => {
).toBe("Sign-in required");
// A category this client has no copy for (a newer backend): the generic sentence, never a key.
expect(i18n.t(failCopyKeys({ error_code: "rate_limited", error_detail: null }, "short"))).toBe(
- "Download failed",
+ "Couldn't download",
);
});
});
diff --git a/frontend/src/lib/format.test.ts b/frontend/src/lib/format.test.ts
index e5193c59..bff43bc4 100644
--- a/frontend/src/lib/format.test.ts
+++ b/frontend/src/lib/format.test.ts
@@ -3,6 +3,7 @@ import {
RELATIVE_UNITS,
formatDate,
formatDateTime,
+ formatDateTimeSec,
formatNumber,
formatRuntime,
formatTotalHours,
@@ -161,7 +162,7 @@ describe("formatNumber", () => {
it("carries the UI language into the channel total-hours label", () => {
i18nStub.language = "hu";
- expect(formatTotalHours(12345 * 3600)).toBe(`${new Intl.NumberFormat("hu").format(12345)} h`);
+ expect(formatTotalHours(12345 * 3600)).toBe(`${new Intl.NumberFormat("hu").format(12345)} ó`);
});
});
@@ -170,26 +171,32 @@ describe("formatRuntime", () => {
[null, ""],
[undefined, ""],
[0, ""], // nothing to show — the call sites suppress their separator on the empty string
- [1, "1s"],
- [45, "45s"], // one Short: the regression case
- [45.7, "45s"], // truncates, so the seconds tier cannot overstate either
- [59, "59s"], // last second before the minute tier
- [60, "1m"],
- [90, "1m"], // floors, never rounds up — a total must not overstate
- [90.9, "1m"],
- [3599, "59m"],
- [3600, "1h 0m"], // the zero minutes stay: "1h" alone reads as an approximation
- [5025, "1h 23m"],
- [360000, "100h 0m"], // a large aggregate keeps counting hours rather than rolling to days
+ [1, "1 sec"],
+ [45, "45 sec"], // one Short: the regression case
+ [45.7, "45 sec"], // truncates, so the seconds tier cannot overstate either
+ [59, "59 sec"], // last second before the minute tier
+ [60, "1 min"],
+ [90, "1 min"], // floors, never rounds up — a total must not overstate
+ [90.9, "1 min"],
+ [3599, "59 min"],
+ [3600, "1 hr 0 min"], // the zero minutes stay: "1 hr" alone reads as an approximation
+ [5025, "1 hr 23 min"],
+ [360000, "100 hr 0 min"], // a large aggregate keeps counting hours rather than rolling to days
];
it.each(CASES)("formats %o as %o", (sec, expected) => {
expect(formatRuntime(sec)).toBe(expected);
});
+ it("speaks the UI language's units (DESIGN Copy: Intl short), not English letters", () => {
+ i18nStub.language = "hu";
+ expect(formatRuntime(5025)).toBe("1 ó 23 p");
+ i18nStub.language = "en";
+ });
+
it("never reports a non-zero duration as zero", () => {
for (let sec = 1; sec < 3600; sec++) {
- expect(formatRuntime(sec), `${sec}s`).not.toBe("0m");
+ expect(formatRuntime(sec), `${sec}s`).not.toBe("0 min");
}
});
});
@@ -212,12 +219,20 @@ describe("formatDateTime / formatDate", () => {
const asEnglish = formatDateTime(iso);
i18nStub.language = "hu";
const asHungarian = formatDateTime(iso);
- expect(asEnglish).toBe(new Date(iso).toLocaleString("en"));
- expect(asHungarian).toBe(new Date(iso).toLocaleString("hu"));
expect(asEnglish).not.toBe(asHungarian);
expect(formatDate(iso, "en")).not.toBe(formatDate(iso, "hu"));
});
+ it("names the month and stops at the minute; seconds only in the log form (DESIGN Copy)", () => {
+ process.env.TZ = "UTC";
+ const iso = "2026-09-17T08:30:14Z";
+ expect(formatDateTime(iso)).toBe("Sep 17, 2026, 08:30 AM");
+ expect(formatDateTimeSec(iso)).toBe("Sep 17, 2026, 08:30:14 AM");
+ i18nStub.language = "hu";
+ expect(formatDateTime(iso)).toBe("2026. szept. 17. 08:30");
+ expect(formatDate(iso, "hu")).toBe("2026. szept. 17.");
+ });
+
it("shows a date-only value as that calendar day in every time zone", () => {
process.env.TZ = "America/New_York";
expect(formatDate("2026-09-17", "en")).toBe("Sep 17, 2026");
diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts
index ede19328..5aed0a32 100644
--- a/frontend/src/lib/format.ts
+++ b/frontend/src/lib/format.ts
@@ -96,19 +96,34 @@ export function formatDuration(sec: number | null): string {
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
}
-/** Compact human runtime ("1h 30m", "45m", "45s") from seconds. Formats BOTH a single item's
- * length (Plex movies/episodes) and an aggregate total (playlist playtime, on the YouTube and the
- * Plex side alike) — it is the one renderer for a playlist total, so a total never appears in two
- * shapes on one screen. Empty string for a missing/zero duration (nothing to show).
+// A duration's unit in the UI language, in the browser's own short form (docs/DESIGN.md "Copy", E28
+// S3g): "90 min", "2 hr" / "90 p", "2 ó". Hand-typed "90m" / "2h" were English in both languages and
+// came in three spellings. One formatter per language × unit, built lazily (the grids format one per card).
+export type DurationUnit = "second" | "minute" | "hour" | "day";
+const unitFormatters = new Map();
+export function formatUnit(value: number, unit: DurationUnit): string {
+ const key = `${i18n.language}|${unit}`;
+ let f = unitFormatters.get(key);
+ if (!f) {
+ f = new Intl.NumberFormat(i18n.language, { style: "unit", unit, unitDisplay: "short" });
+ unitFormatters.set(key, f);
+ }
+ return f.format(value);
+}
+
+/** Compact human runtime ("1 hr 30 min", "45 min", "45 sec"; HU "1 ó 30 p") from seconds. Formats
+ * BOTH a single item's length (Plex movies/episodes) and an aggregate total (playlist playtime, on
+ * the YouTube and the Plex side alike) — it is the one renderer for a playlist total, so a total
+ * never appears in two shapes on one screen. Empty string for a missing/zero duration.
* The sub-minute tier is load-bearing, not decoration: an aggregate is the only input that can land
- * between 1 and 59 seconds (a playlist of Shorts), and flooring that to a flat "0m" made a
+ * between 1 and 59 seconds (a playlist of Shorts), and flooring that to a flat "0 min" made a
* non-empty playlist report no runtime at all. Keep it, or that case silently regresses. */
export function formatRuntime(sec?: number | null): string {
if (!sec) return "";
- if (sec < 60) return `${Math.floor(sec)}s`;
+ if (sec < 60) return formatUnit(Math.floor(sec), "second");
const h = Math.floor(sec / 3600);
const m = Math.floor((sec % 3600) / 60);
- return h ? `${h}h ${m}m` : `${m}m`;
+ return h ? `${formatUnit(h, "hour")} ${formatUnit(m, "minute")}` : formatUnit(m, "minute");
}
// Human label for a canonical quota-action key (see backend app.quota.QuotaAction). Resolved
@@ -128,11 +143,11 @@ export function formatEta(seconds: number): string {
return i18n.t("time.eta.days", { count: Math.round(hours / 24) });
}
-/** Compact whole-channel total length ("12 h", "45 m"), hours-scale so H:MM:SS isn't huge. */
+/** Compact whole-channel total length ("12 hr", "45 min"), hours-scale so H:MM:SS isn't huge. */
export function formatTotalHours(sec: number): string {
if (!sec) return "—";
const h = Math.round(sec / 3600);
- return h >= 1 ? `${formatNumber(h)} h` : `${Math.max(1, Math.round(sec / 60))} m`;
+ return h >= 1 ? formatUnit(h, "hour") : formatUnit(Math.max(1, Math.round(sec / 60)), "minute");
}
// One grouped-number formatter per language, built lazily — the same reason as the relative-time
@@ -169,10 +184,23 @@ export function formatCountOrDash(n: number | null | undefined): string {
return n != null ? formatViews(n) : "—";
}
-// Absolute date AND time in the UI language (e.g. "16 Sept 2026, 08:30:14"). A bare `toLocaleString()`
-// formats in the browser's locale instead, so an English UI showed a Hungarian-shaped date. Empty for null.
+// The app's date forms (docs/DESIGN.md "Copy", E28 S3g), all in the UI language — never a bare
+// `toLocaleString()`, which formats in the BROWSER's locale (an English UI showed a Hungarian-shaped
+// date) and, with no options, as numbers with seconds ("9/27/2026, 8:30:14 AM"):
+// relativeTime "3d ago" / "3 napja" — recency in a list; the exact form on hover
+// formatDate "Sep 27, 2026" / "2026. szept. 27."
+// formatDateTime "Sep 27, 2026, 08:30 AM" / "2026. szept. 27. 08:30"
+// formatDateTimeSec the same with seconds — only the server log, the audit and the recorder
+// lint (`no-restricted-syntax`) keeps date formatting in this file. Empty for null.
+const DAY: Intl.DateTimeFormatOptions = { year: "numeric", month: "short", day: "numeric" };
+const MINUTE: Intl.DateTimeFormatOptions = { ...DAY, hour: "2-digit", minute: "2-digit" };
+
export function formatDateTime(iso: string | null | undefined): string {
- return iso ? new Date(iso).toLocaleString(i18n.language) : "";
+ return iso ? new Date(iso).toLocaleString(i18n.language, MINUTE) : "";
+}
+
+export function formatDateTimeSec(iso: string | null | undefined): string {
+ return iso ? new Date(iso).toLocaleString(i18n.language, { ...MINUTE, second: "2-digit" }) : "";
}
// Absolute day-precision date (e.g. "12 Jan 2017"), shown alongside a relative time. Empty for null.
@@ -183,9 +211,7 @@ export function formatDate(iso: string | null | undefined, lang: string): string
if (!iso) return "";
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(iso);
return new Date(iso).toLocaleDateString(lang, {
- year: "numeric",
- month: "short",
- day: "numeric",
+ ...DAY,
...(dateOnly ? { timeZone: "UTC" } : {}),
});
}