fix(i18n): every counted string is a plural pair, so one reads singular

"1 seasons", "Synced 1 subscriptions", "1 video(s) were skipped": 21 English
strings carried {{count}} in one plain key and read plural at one. Each is a
_one/_other pair now (Hungarian keeps one form for both), and so is every other
counted string, so the next wording change is a plural too. A contract test
holds the rule for every locale file.
This commit is contained in:
2026-10-02 02:13:17 +02:00
parent e4c80fb7bb
commit 98d0ca6eeb
31 changed files with 177 additions and 72 deletions
+2 -1
View File
@@ -6,7 +6,8 @@
"clear": "Clear audit log",
"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",
"cleared_one": "Cleared {{count}} audit entry",
"cleared_other": "Cleared {{count}} audit entries",
"clearFailed": "Couldn't clear the audit log",
"truncated": "Showing the {{returned}} most recent of {{total}} entries.",
"on": "On",
+6 -3
View File
@@ -94,7 +94,8 @@
"fullHistory": "Full history",
"fullHistoryHint": "Channels whose entire back-catalog is fetched, out of the ones you've requested full history for.",
"left": "left",
"leftHint": "Rough estimate to finish full-history backfill of {{count}} pending channel(s), based on the shared daily quota.",
"leftHint_one": "Rough estimate to finish full-history backfill of {{count}} pending channel, based on the shared daily quota.",
"leftHint_other": "Rough estimate to finish full-history backfill of {{count}} pending channels, based on the shared daily quota.",
"myVideos": "My videos",
"myVideosHint": "Total videos available across your channels.",
"quotaLeft": "Quota left",
@@ -125,11 +126,13 @@
"noTagsYet": "No tags yet — make one with a row’s tag “+”"
},
"notify": {
"synced": "Synced {{count}} subscriptions",
"synced_one": "Synced {{count}} subscription",
"synced_other": "Synced {{count}} subscriptions",
"syncFailed": "Couldn't sync subscriptions",
"unsubscribed": "Unsubscribed from {{name}} on YouTube",
"unsubscribeFailed": "Couldn't unsubscribe",
"fullHistoryRequested": "Full history requested for {{count}} channels",
"fullHistoryRequested_one": "Full history requested for {{count}} channel",
"fullHistoryRequested_other": "Full history requested for {{count}} channels",
"fullHistoryFailed": "Couldn't request full history",
"needYouTube": "Connect your YouTube account to do this",
"connect": "Connect",
+4 -2
View File
@@ -14,7 +14,8 @@
"itemCount_one": "{{formattedCount}} item",
"itemCount_other": "{{formattedCount}} items",
"accessRequestsTitle": "Access requests",
"accessRequestsMessage": "{{count}} pending — review them on the Users page.",
"accessRequestsMessage_one": "{{count}} pending — review it on the Users page.",
"accessRequestsMessage_other": "{{count}} pending — review them on the Users page.",
"accessRequestsReview": "Review",
"demoTitle": "Demo account",
"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.",
@@ -33,7 +34,8 @@
"lower": "Lower priority",
"edit": "Click to type a priority (negatives allowed)"
},
"moreRefine": "{{count}} more — refine with search",
"moreRefine_one": "{{count}} more — refine with search",
"moreRefine_other": "{{count}} more — refine with search",
"matchMode": "Match",
"match": {
"any": "Any",
+2 -1
View File
@@ -18,7 +18,8 @@
"willReencode": "Will re-encode",
"willCopy": "Instant (stream copy)",
"create": "Create clip",
"createN": "Create {{count}} clips",
"createN_one": "Create {{count}} clip",
"createN_other": "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",
+6 -3
View File
@@ -40,12 +40,15 @@
"total": "total",
"countTooltip": "Your subscriptions vs. the whole shared catalog.",
"paused": "paused",
"syncing": "{{count}} syncing",
"syncing_one": "{{count}} syncing",
"syncing_other": "{{count}} syncing",
"backfillingHistory": "fetching history",
"working": "syncing…",
"recentQueued": "{{count}} queued",
"recentQueued_one": "{{count}} queued",
"recentQueued_other": "{{count}} queued",
"allSynced": "all synced",
"withoutFullHistory": "{{count}} without full history",
"withoutFullHistory_one": "{{count}} without full history",
"withoutFullHistory_other": "{{count}} without full history",
"fullHistoryTooltip": "Recent uploads only — click to fetch the rest.",
"pause": "Pause sync",
"resume": "Resume sync"
+2 -1
View File
@@ -11,7 +11,8 @@
"menu": "Notifications actions",
"sectionSystem": "System",
"sectionActivity": "Activity",
"andMore": "and {{count}} more",
"andMore_one": "and {{count}} more",
"andMore_other": "and {{count}} more",
"openScheduler": "Open Scheduler",
"openDownloads": "Open Downloads",
"revertFailed": "Couldn't undo that — please try again",
+2 -1
View File
@@ -15,7 +15,8 @@
"title": "You're connected",
"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)",
"feedReadyChannels_one": " ({{count}} channel)",
"feedReadyChannels_other": " ({{count}} channels)",
"rationale": "<0>unsubscribe</0> 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.",
"retryImport": "Retry import",
"enableUnsubscribe": "Enable unsubscribe",
+2 -2
View File
@@ -2,10 +2,10 @@
"title": "Jump to…",
"placeholder": "Search modules, channels, playlists…",
"empty": "Nothing matches.",
"results": "{{count}} result",
"results_one": "{{count}} result",
"results_other": "{{count}} results",
"theme": "Theme: {{name}}",
"videoCount": "{{count}} video",
"videoCount_one": "{{count}} video",
"videoCount_other": "{{count}} videos",
"groups": {
"modules": "Modules",
+8 -4
View File
@@ -4,7 +4,8 @@
"listGroup": "Your playlists",
"watchLater": "Watch later",
"syncYoutube": "Sync from YouTube",
"syncedToast": "Synced {{count}} playlists from YouTube",
"syncedToast_one": "Synced {{count}} playlist from YouTube",
"syncedToast_other": "Synced {{count}} playlists from YouTube",
"syncFailed": "Couldn't sync from YouTube",
"noneYet": "No playlists yet",
"newPlaylist": "New playlist…",
@@ -34,7 +35,8 @@
"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.)",
"pushPlanCreate_one": "Create a new YouTube playlist with {{count}} video? (~{{units}} quota units; {{left}} left today.)",
"pushPlanCreate_other": "Create a new YouTube playlist with {{count}} videos? (~{{units}} quota units; {{left}} left today.)",
"mergeAddLocal_one": "{{count}} video added to Siftlode from YouTube.",
"mergeAddLocal_other": "{{count}} videos added to Siftlode from YouTube.",
"mergeRemoveLocal_one": "{{count}} video removed from Siftlode.",
@@ -53,7 +55,8 @@
"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}} video(s) were skipped",
"pushPartial_one": "Synced, but {{count}} video was skipped",
"pushPartial_other": "Synced, but {{count}} videos were skipped",
"pushFailed": "Couldn't sync to YouTube",
"deleteHereOnly": "Delete here only",
"deleteYtFailed": "Couldn't delete on YouTube; deleted here only",
@@ -103,7 +106,8 @@
"dragBlockedFilter": "Reordering by hand is off while a filter is applied.",
"clearView": "Reset view",
"saveOrder": "Save this order",
"filteredCount": "{{count}} shown",
"filteredCount_one": "{{count}} shown",
"filteredCount_other": "{{count}} shown",
"noneMatchFilter": "No videos match the filter.",
"loadFailed": "Couldn't load this playlist",
"reorderFailed": "Couldn't save the new order",
+16 -8
View File
@@ -128,7 +128,8 @@
"seasonTitle": "Season {{n}}",
"specials": "Specials",
"watchedFailed": "Couldn't update the watched state — please try again",
"seasons": "{{count}} seasons",
"seasons_one": "{{count}} season",
"seasons_other": "{{count}} seasons",
"unified": {
"titles": "Titles",
"episodes": "Episodes"
@@ -136,7 +137,8 @@
"series": {
"seasons": "Seasons",
"backToShow": "Back to show",
"episodeCount": "{{count}} episodes",
"episodeCount_one": "{{count}} episode",
"episodeCount_other": "{{count}} episodes",
"showFromStart": "Show from the start",
"seasonFromStart": "Season from the start",
"related": "Related shows",
@@ -154,11 +156,13 @@
"create": "Create",
"search": "Filter collections…",
"none": "No editable collections yet. Create one above.",
"count": "{{count}} items",
"count_one": "{{count}} item",
"count_other": "{{count}} items",
"delete": "Delete",
"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_one": "Manage {{count}} existing collection",
"others_other": "Manage {{count}} existing collections",
"makeEditable": "Manage"
},
"player": {
@@ -298,7 +302,8 @@
"newPlaceholder": "New playlist name…",
"create": "Create",
"none": "No playlists yet. Create one above.",
"count": "{{count}} items",
"count_one": "{{count}} item",
"count_other": "{{count}} items",
"groupProgress": "{{in}} / {{size}}",
"addFailed": "Couldn't add it to the playlist",
"removeFailed": "Couldn't remove it from the playlist",
@@ -319,7 +324,8 @@
"layoutTree": "Tree view",
"removeShow": "Remove whole show",
"removeSeason": "Remove whole season",
"episodes": "{{count}} episodes",
"episodes_one": "{{count}} episode",
"episodes_other": "{{count}} episodes",
"seasonUnknown": "Episodes",
"addShow": "Add whole show to a playlist",
"addSeason": "Add season to a playlist",
@@ -345,8 +351,10 @@
"person": {
"credits": "{{movies}} movies · {{shows}} shows",
"filterBy": "Filter library by {{name}}",
"movies": "Movies ({{count}})",
"shows": "Shows ({{count}})",
"movies_one": "Movies ({{count}})",
"movies_other": "Movies ({{count}})",
"shows_one": "Shows ({{count}})",
"shows_other": "Shows ({{count}})",
"empty": "No titles found for this person.",
"born": "Born",
"died": "Died",
+4 -2
View File
@@ -14,7 +14,8 @@
"jobsTitle": "Jobs",
"queueTitle": "Work queued",
"quotaTitle": "Shared API quota",
"everyMin": "every {{count}} min",
"everyMin_one": "every {{count}} min",
"everyMin_other": "every {{count}} min",
"runningNow": "Running now…",
"lastRun": "Last run {{time}}",
"neverRun": "Hasn't run yet this session",
@@ -115,7 +116,8 @@
"purgeDiscovery": "Purge discovery",
"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": "Deleted {{count}} discovery items",
"purgedDiscovery_one": "Deleted {{count}} discovery item",
"purgedDiscovery_other": "Deleted {{count}} discovery items",
"result": {
"new": "new videos",
"failed": "failed",
+6 -3
View File
@@ -147,7 +147,8 @@
"resetHint": "Wipe the demo account's playlists, hidden/watched/saved videos and settings, then re-seed a few sample playlists.",
"resetTitle": "Reset demo state?",
"resetConfirm": "This clears everything the demo account has accumulated — playlists, hidden/watched/saved videos and settings — and re-seeds a few sample playlists. It can't be undone.",
"resetDone": "Demo reset — {{count}} sample playlist(s) seeded",
"resetDone_one": "Demo reset — {{count}} sample playlist seeded",
"resetDone_other": "Demo reset — {{count}} sample playlists seeded",
"resetFailed": "Couldn't reset the demo account",
"removed": "Removed from the demo whitelist",
"removeTitle": "Remove demo access?",
@@ -158,7 +159,8 @@
"noPending": "No pending requests.",
"addPlaceholder": "Add an email directly…",
"add": "Add",
"decided": "{{count}} decided",
"decided_one": "{{count}} decided",
"decided_other": "{{count}} decided",
"approved": "Approved {{email}} — they can sign in now",
"approveFailed": "Couldn't approve the request",
"denyFailed": "Couldn't deny the request",
@@ -243,7 +245,8 @@
},
"count_one": "{{count}} event · newest first",
"count_other": "{{count}} events · newest first",
"capped": "showing the newest {{shown}} of {{count}}",
"capped_one": "showing the newest {{shown}} of {{count}}",
"capped_other": "showing the newest {{shown}} of {{count}}",
"copy": "Copy",
"copied_one": "Copied {{count}} event",
"copied_other": "Copied {{count}} events",
+2 -1
View File
@@ -1,6 +1,7 @@
{
"filters": "Filters",
"activeCount": "{{count}} active",
"activeCount_one": "{{count}} active",
"activeCount_other": "{{count}} active",
"clearAll": "Clear all",
"resetDefaults": "Reset to defaults",
"customize": "Customize sidebar",
+6 -3
View File
@@ -24,7 +24,8 @@
"fullHistory": "Full history",
"fullHistoryHint": "Channels whose entire back-catalog has been fetched, out of the ones you've requested full history for (opt in per channel on the Channels page).",
"fullHistoryEta": "Full history ETA",
"fullHistoryEtaHint": "Rough estimate to finish full-history backfill of {{count}} pending channel(s), based on the shared daily quota.",
"fullHistoryEtaHint_one": "Rough estimate to finish full-history backfill of {{count}} pending channel, based on the shared daily quota.",
"fullHistoryEtaHint_other": "Rough estimate to finish full-history backfill of {{count}} pending channels, based on the shared daily quota.",
"myVideos": "My videos",
"myVideosHint": "Total videos available to you across your subscribed channels.",
"quotaLeft": "Quota left today",
@@ -43,9 +44,11 @@
"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",
"synced_one": "Synced {{count}} subscription",
"synced_other": "Synced {{count}} subscriptions",
"syncFailed": "Couldn't sync subscriptions",
"fullHistoryRequested": "Full history requested for {{count}} channels",
"fullHistoryRequested_one": "Full history requested for {{count}} channel",
"fullHistoryRequested_other": "Full history requested for {{count}} channels",
"fullHistoryFailed": "Couldn't request full history",
"admin": "Admin",
"adminPauseHint": "Pause or resume the background sync for the whole app (all users).",
+2 -1
View File
@@ -1,6 +1,7 @@
{
"title": "Manage tags",
"channels": "{{count}} ch.",
"channels_one": "{{count}} ch.",
"channels_other": "{{count}} ch.",
"onChannels": "Tagged channels",
"delete": "Delete",
"deleted": "Tag “{{name}}” deleted",
+2 -1
View File
@@ -6,7 +6,8 @@
"clear": "Eseménynapló ürítése",
"clearTitle": "Üríted az eseménynapló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",
"cleared_one": "{{count}} naplóbejegyzés törölve",
"cleared_other": "{{count}} naplóbejegyzés törölve",
"clearFailed": "Nem sikerült üríteni az eseménynaplót",
"truncated": "A legutóbbi {{returned}} bejegyzés látszik a(z) {{total}}-ból.",
"on": "Be",
+6 -3
View File
@@ -94,7 +94,8 @@
"fullHistory": "Teljes előzmény",
"fullHistoryHint": "Csatornák, amelyek teljes archívuma le van töltve, azok közül, amelyekhez teljes előzményt kértél.",
"left": "hátra",
"leftHint": "Hozzávetőleges becslés {{count}} függőben lévő csatorna teljes előzményének behúzására, a megosztott napi kvóta alapján.",
"leftHint_one": "Hozzávetőleges becslés {{count}} függőben lévő csatorna teljes előzményének behúzására, a megosztott napi kvóta alapján.",
"leftHint_other": "Hozzávetőleges becslés {{count}} függőben lévő csatorna teljes előzményének behúzására, a megosztott napi kvóta alapján.",
"myVideos": "Videóim",
"myVideosHint": "A csatornáidon elérhető videók teljes száma.",
"quotaLeft": "Maradék kvóta",
@@ -125,11 +126,13 @@
"noTagsYet": "Még nincs címkéd — egy sor címke-„+” gombjával hozhatsz létre"
},
"notify": {
"synced": "{{count}} feliratkozás szinkronizálva",
"synced_one": "{{count}} feliratkozás szinkronizálva",
"synced_other": "{{count}} feliratkozás szinkronizálva",
"syncFailed": "Nem sikerült szinkronizálni a feliratkozásokat",
"unsubscribed": "Leiratkozva a(z) {{name}} csatornáról a YouTube-on",
"unsubscribeFailed": "Nem sikerült leiratkozni",
"fullHistoryRequested": "Teljes előzmény kérve {{count}} csatornához",
"fullHistoryRequested_one": "Teljes előzmény kérve {{count}} csatornához",
"fullHistoryRequested_other": "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",
"connect": "Csatlakoztatás",
+4 -2
View File
@@ -14,7 +14,8 @@
"itemCount_one": "{{formattedCount}} elem",
"itemCount_other": "{{formattedCount}} elem",
"accessRequestsTitle": "Hozzáférési kérelmek",
"accessRequestsMessage": "{{count}} függőben — nézd át a Felhasználók oldalon.",
"accessRequestsMessage_one": "{{count}} függőben — nézd át a Felhasználók oldalon.",
"accessRequestsMessage_other": "{{count}} függőben — nézd át a Felhasználók oldalon.",
"accessRequestsReview": "Áttekintés",
"demoTitle": "Demó fiók",
"demoSharedNotice": "Egy közös demó 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 demó látogatók egyszerre módosíthatják.",
@@ -33,7 +34,8 @@
"lower": "Prioritás csökkentése",
"edit": "Kattints a prioritás beírásához (negatív is lehet)"
},
"moreRefine": "még {{count}} — finomíts kereséssel",
"moreRefine_one": "még {{count}} — finomíts kereséssel",
"moreRefine_other": "még {{count}} — finomíts kereséssel",
"matchMode": "Egyezés",
"match": {
"any": "Bármelyik",
+2 -1
View File
@@ -18,7 +18,8 @@
"willReencode": "Újrakódolás lesz",
"willCopy": "Azonnali (másolás)",
"create": "Klip létrehozása",
"createN": "{{count}} klip létrehozása",
"createN_one": "{{count}} klip létrehozása",
"createN_other": "{{count}} klip létrehozása",
"created_one": "{{count}} klip a letöltésekhez adva",
"created_other": "{{count}} klip a letöltésekhez adva",
"failed": "Nem sikerült létrehozni a klipet",
+6 -3
View File
@@ -40,12 +40,15 @@
"total": "összes",
"countTooltip": "A feliratkozásaid a teljes megosztott katalógushoz képest.",
"paused": "szüneteltetve",
"syncing": "{{count}} szinkronizálás alatt",
"syncing_one": "{{count}} szinkronizálás alatt",
"syncing_other": "{{count}} szinkronizálás alatt",
"backfillingHistory": "előzmény behúzása",
"working": "szinkronizálás…",
"recentQueued": "{{count}} sorban",
"recentQueued_one": "{{count}} sorban",
"recentQueued_other": "{{count}} sorban",
"allSynced": "minden szinkronban",
"withoutFullHistory": "{{count}} teljes előzmény nélkül",
"withoutFullHistory_one": "{{count}} teljes előzmény nélkül",
"withoutFullHistory_other": "{{count}} teljes előzmény nélkül",
"fullHistoryTooltip": "Csak a legutóbbi feltöltések — kattints a többiért.",
"pause": "Szinkron szüneteltetése",
"resume": "Szinkron folytatása"
+2 -1
View File
@@ -11,7 +11,8 @@
"menu": "Értesítések műveletei",
"sectionSystem": "Rendszer",
"sectionActivity": "Tevékenység",
"andMore": "és még {{count}}",
"andMore_one": "és még {{count}}",
"andMore_other": "és még {{count}}",
"openScheduler": "Ütemező megnyitása",
"openDownloads": "Letöltések megnyitása",
"revertFailed": "Nem sikerült visszavonni — próbáld újra",
+2 -1
View File
@@ -15,7 +15,8 @@
"title": "Csatlakoztatva",
"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)",
"feedReadyChannels_one": " ({{count}} csatorna)",
"feedReadyChannels_other": " ({{count}} csatorna)",
"rationale": "<0>leiratkozzon</0> 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.",
"retryImport": "Importálás újra",
"enableUnsubscribe": "Leiratkozás engedélyezése",
+2 -2
View File
@@ -2,10 +2,10 @@
"title": "Ugrás…",
"placeholder": "Modul, csatorna, lista keresése…",
"empty": "Nincs találat.",
"results": "{{count}} találat",
"results_one": "{{count}} találat",
"results_other": "{{count}} találat",
"theme": "Téma: {{name}}",
"videoCount": "{{count}} videó",
"videoCount_one": "{{count}} videó",
"videoCount_other": "{{count}} videó",
"groups": {
"modules": "Modulok",
+8 -4
View File
@@ -4,7 +4,8 @@
"listGroup": "Listáid",
"watchLater": "Megnézem később",
"syncYoutube": "Szinkronizálás a YouTube-ról",
"syncedToast": "{{count}} lista szinkronizálva a YouTube-ról",
"syncedToast_one": "{{count}} lista szinkronizálva a YouTube-ról",
"syncedToast_other": "{{count}} lista szinkronizálva a YouTube-ról",
"syncFailed": "Nem sikerült szinkronizálni a YouTube-ról",
"noneYet": "Még nincs lejátszási lista",
"newPlaylist": "Új lista…",
@@ -34,7 +35,8 @@
"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.)",
"pushPlanCreate_one": "Létrehozol egy új YouTube-listát {{count}} videóval? (~{{units}} kvótaegység; ma még {{left}} maradt.)",
"pushPlanCreate_other": "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.",
"mergeRemoveLocal_one": "{{count}} videó törlődik a Siftlode-listádból.",
@@ -53,7 +55,8 @@
"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}} videó kimaradt",
"pushPartial_one": "Szinkronizálva, de {{count}} videó kimaradt",
"pushPartial_other": "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 törölve",
@@ -103,7 +106,8 @@
"dragBlockedFilter": "A kézi átrendezés ki van kapcsolva, amíg szűrő van érvényben.",
"clearView": "Nézet visszaállítása",
"saveOrder": "Sorrend mentése",
"filteredCount": "{{count}} látszik",
"filteredCount_one": "{{count}} látszik",
"filteredCount_other": "{{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",
+16 -8
View File
@@ -128,7 +128,8 @@
"seasonTitle": "{{n}}. évad",
"specials": "Különkiadások",
"watchedFailed": "Nem sikerült frissíteni a megnézett állapotot — próbáld újra",
"seasons": "{{count}} évad",
"seasons_one": "{{count}} évad",
"seasons_other": "{{count}} évad",
"unified": {
"titles": "Címek",
"episodes": "Epizódok"
@@ -136,7 +137,8 @@
"series": {
"seasons": "Évadok",
"backToShow": "Vissza a sorozathoz",
"episodeCount": "{{count}} rész",
"episodeCount_one": "{{count}} rész",
"episodeCount_other": "{{count}} rész",
"showFromStart": "Sorozat elejéről",
"seasonFromStart": "Évad elejéről",
"related": "Kapcsolódó sorozatok",
@@ -154,11 +156,13 @@
"create": "Létrehozás",
"search": "Kollekciók szűrése…",
"none": "Még nincs szerkeszthető kollekció. Hozz létre egyet fent.",
"count": "{{count}} elem",
"count_one": "{{count}} elem",
"count_other": "{{count}} elem",
"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_one": "{{count}} meglévő kollekció kezelése",
"others_other": "{{count}} meglévő kollekció kezelése",
"makeEditable": "Kezelem"
},
"player": {
@@ -298,7 +302,8 @@
"newPlaceholder": "Új lista neve…",
"create": "Létrehozás",
"none": "Még nincs lista. Hozz létre egyet fent.",
"count": "{{count}} elem",
"count_one": "{{count}} elem",
"count_other": "{{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",
@@ -319,7 +324,8 @@
"layoutTree": "Fa nézet",
"removeShow": "Egész sorozat eltávolítása",
"removeSeason": "Egész évad eltávolítása",
"episodes": "{{count}} epizód",
"episodes_one": "{{count}} epizód",
"episodes_other": "{{count}} epizód",
"seasonUnknown": "Epizódok",
"addShow": "Egész sorozat listához adása",
"addSeason": "Évad listához adása",
@@ -345,8 +351,10 @@
"person": {
"credits": "{{movies}} film · {{shows}} sorozat",
"filterBy": "Szűrés erre: {{name}}",
"movies": "Filmek ({{count}})",
"shows": "Sorozatok ({{count}})",
"movies_one": "Filmek ({{count}})",
"movies_other": "Filmek ({{count}})",
"shows_one": "Sorozatok ({{count}})",
"shows_other": "Sorozatok ({{count}})",
"empty": "Nincs találat ehhez a személyhez.",
"born": "Született",
"died": "Elhunyt",
+4 -2
View File
@@ -14,7 +14,8 @@
"jobsTitle": "Feladatok",
"queueTitle": "Sorban álló munka",
"quotaTitle": "Közös API-kvóta",
"everyMin": "{{count}} percenként",
"everyMin_one": "{{count}} percenként",
"everyMin_other": "{{count}} percenként",
"runningNow": "Most fut…",
"lastRun": "Utolsó futás {{time}}",
"neverRun": "Ebben a munkamenetben még nem futott",
@@ -115,7 +116,8 @@
"purgeDiscovery": "Felfedezés ürítése",
"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 törölve",
"purgedDiscovery_one": "{{count}} felfedezés-elem törölve",
"purgedDiscovery_other": "{{count}} felfedezés-elem törölve",
"result": {
"new": "új videó",
"failed": "sikertelen",
+6 -3
View File
@@ -147,7 +147,8 @@
"resetHint": "Törli a demó fiók lejátszási listáit, elrejtett/megnézett/mentett videóit és beállításait, majd újra létrehoz pár minta lejátszási listát.",
"resetTitle": "Visszaállítod a demó állapotot?",
"resetConfirm": "Ez törli mindazt, amit a demó fiók összegyűjtött — lejátszási listák, elrejtett/megnézett/mentett videók és beállítások —, és újra létrehoz pár minta lejátszási listát. Nem vonható vissza.",
"resetDone": "Demó visszaállítva — {{count}} minta lejátszási lista létrehozva",
"resetDone_one": "Demó visszaállítva — {{count}} minta lejátszási lista létrehozva",
"resetDone_other": "Demó visszaállítva — {{count}} minta lejátszási lista létrehozva",
"resetFailed": "Nem sikerült visszaállítani a demó fiókot",
"removed": "Eltávolítva a demó fehérlistáról",
"removeTitle": "Visszavonod a demó hozzáférést?",
@@ -158,7 +159,8 @@
"noPending": "Nincs függőben lévő kérelem.",
"addPlaceholder": "E-mail közvetlen hozzáadása…",
"add": "Hozzáadás",
"decided": "{{count}} eldöntve",
"decided_one": "{{count}} eldöntve",
"decided_other": "{{count}} eldöntve",
"approved": "Jóváhagyva: {{email}} — most már be tud jelentkezni",
"approveFailed": "Nem sikerült jóváhagyni a kérelmet",
"denyFailed": "Nem sikerült elutasítani a kérelmet",
@@ -243,7 +245,8 @@
},
"count_one": "{{count}} esemény · a legújabb felül",
"count_other": "{{count}} esemény · a legújabb felül",
"capped": "a legújabb {{shown}} látszik a(z) {{count}} közül",
"capped_one": "a legújabb {{shown}} látszik a(z) {{count}} közül",
"capped_other": "a legújabb {{shown}} látszik a(z) {{count}} közül",
"copy": "Másolás",
"copied_one": "Kimásolva: {{count}} esemény",
"copied_other": "Kimásolva: {{count}} esemény",
+2 -1
View File
@@ -1,6 +1,7 @@
{
"filters": "Szűrők",
"activeCount": "{{count}} aktív",
"activeCount_one": "{{count}} aktív",
"activeCount_other": "{{count}} aktív",
"clearAll": "Összes törlése",
"resetDefaults": "Visszaállítás alapértelmezettre",
"customize": "Oldalsáv testreszabása",
+6 -3
View File
@@ -24,7 +24,8 @@
"fullHistory": "Teljes előzmény",
"fullHistoryHint": "Azok a csatornák, amelyek teljes archívuma be van húzva, azokból, amelyekhez teljes előzményt kértél (csatornánként választható a Csatornák oldalon).",
"fullHistoryEta": "Teljes előzmény becsült ideje",
"fullHistoryEtaHint": "Hozzávetőleges becslés {{count}} függőben lévő csatorna teljes előzményének behúzásához, a megosztott napi kvóta alapján.",
"fullHistoryEtaHint_one": "Hozzávetőleges becslés {{count}} függőben lévő csatorna teljes előzményének behúzásához, a megosztott napi kvóta alapján.",
"fullHistoryEtaHint_other": "Hozzávetőleges becslés {{count}} függőben lévő csatorna teljes előzményének behúzásához, a megosztott napi kvóta alapján.",
"myVideos": "Saját videók",
"myVideosHint": "A számodra elérhető videók összesen a feliratkozott csatornáidon.",
"quotaLeft": "Ma maradt kvóta",
@@ -43,9 +44,11 @@
"syncSubscriptionsHint": "Újraimportálja a YouTube-feliratkozásaidat és behúzza az újonnan követett csatornákat.",
"backfillEverything": "Minden videó behúzása",
"backfillEverythingHint": "A teljes archívum behúzásának 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",
"synced_one": "{{count}} feliratkozás szinkronizálva",
"synced_other": "{{count}} feliratkozás szinkronizálva",
"syncFailed": "Nem sikerült szinkronizálni a feliratkozásokat",
"fullHistoryRequested": "Teljes előzmény kérve {{count}} csatornához",
"fullHistoryRequested_one": "Teljes előzmény kérve {{count}} csatornához",
"fullHistoryRequested_other": "Teljes előzmény kérve {{count}} csatornához",
"fullHistoryFailed": "Nem sikerült teljes előzményt kérni",
"admin": "Adminisztrátor",
"adminPauseHint": "A háttér-szinkronizálás szüneteltetése vagy folytatása az egész alkalmazásra (minden felhasználóra).",
+2 -1
View File
@@ -1,6 +1,7 @@
{
"title": "Címkék kezelése",
"channels": "{{count}} csat.",
"channels_one": "{{count}} csat.",
"channels_other": "{{count}} csat.",
"onChannels": "Címkézett csatornák",
"delete": "Törlés",
"deleted": "„{{name}}” címke törölve",
+37
View File
@@ -6,6 +6,20 @@ import { describe, expect, it } from "vitest";
import i18n from "./index";
const mods = import.meta.glob("./locales/*/*.json", { eager: true }) as Record<
string,
{ default: Record<string, unknown> }
>;
// Every leaf string as [dotted key, value].
function leaves(obj: Record<string, unknown>, prefix = ""): [string, string][] {
return Object.entries(obj).flatMap(([k, v]) => {
const key = prefix ? `${prefix}.${k}` : k;
if (v && typeof v === "object") return leaves(v as Record<string, unknown>, key);
return typeof v === "string" ? [[key, v] as [string, string]] : [];
});
}
describe("counted copy", () => {
it("the share link's expiry is singular for one day", async () => {
await i18n.changeLanguage("en");
@@ -22,3 +36,26 @@ describe("counted copy", () => {
expect(i18n.t("downloads.share.views", { count: 0 })).toBe("0 views");
});
});
// E31: "1 seasons", "Synced 1 subscriptions" — a counted string written as one plain key reads plural at
// one. So the rule is held for every string, not key by key: a value that carries {{count}} is a plural
// pair (`_one` / `_other`), even where the two read the same today, so the next wording is a plural too.
describe("every counted string is a plural pair", () => {
it("no {{count}} in a key without a plural suffix, in any language", () => {
const bare = Object.entries(mods).flatMap(([path, mod]) =>
leaves(mod.default)
.filter(([k, v]) => v.includes("{{count}}") && !/_(zero|one|two|few|many|other)$/.test(k))
.map(([k]) => `${path.replace(/^.*locales\//, "")}: ${k}`),
);
expect(bare).toEqual([]);
});
it("reads singular at one", async () => {
await i18n.changeLanguage("en");
expect(i18n.t("plex.seasons", { count: 1 })).toBe("1 season");
expect(i18n.t("plex.seasons", { count: 3 })).toBe("3 seasons");
expect(i18n.t("channels.notify.synced", { count: 1 })).toBe("Synced 1 subscription");
expect(i18n.t("playlists.pushPartial", { count: 1 })).toBe("Synced, but 1 video was skipped");
expect(i18n.t("playlists.pushPartial", { count: 2 })).toBe("Synced, but 2 videos were skipped");
});
});