diff --git a/frontend/src/i18n/locales/en/audit.json b/frontend/src/i18n/locales/en/audit.json index f4513bd8..454f58ff 100644 --- a/frontend/src/i18n/locales/en/audit.json +++ b/frontend/src/i18n/locales/en/audit.json @@ -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", diff --git a/frontend/src/i18n/locales/en/channels.json b/frontend/src/i18n/locales/en/channels.json index 5aee7e43..3d9b956b 100644 --- a/frontend/src/i18n/locales/en/channels.json +++ b/frontend/src/i18n/locales/en/channels.json @@ -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", diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json index ea7acbc9..c1ca9c55 100644 --- a/frontend/src/i18n/locales/en/common.json +++ b/frontend/src/i18n/locales/en/common.json @@ -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", diff --git a/frontend/src/i18n/locales/en/editor.json b/frontend/src/i18n/locales/en/editor.json index 45fd89f9..8ded648f 100644 --- a/frontend/src/i18n/locales/en/editor.json +++ b/frontend/src/i18n/locales/en/editor.json @@ -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", diff --git a/frontend/src/i18n/locales/en/header.json b/frontend/src/i18n/locales/en/header.json index 5b841765..c03b081f 100644 --- a/frontend/src/i18n/locales/en/header.json +++ b/frontend/src/i18n/locales/en/header.json @@ -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" diff --git a/frontend/src/i18n/locales/en/inbox.json b/frontend/src/i18n/locales/en/inbox.json index edff4fac..c4eb9bf9 100644 --- a/frontend/src/i18n/locales/en/inbox.json +++ b/frontend/src/i18n/locales/en/inbox.json @@ -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", diff --git a/frontend/src/i18n/locales/en/onboarding.json b/frontend/src/i18n/locales/en/onboarding.json index 5259b23e..48ca1ee3 100644 --- a/frontend/src/i18n/locales/en/onboarding.json +++ b/frontend/src/i18n/locales/en/onboarding.json @@ -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 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", diff --git a/frontend/src/i18n/locales/en/palette.json b/frontend/src/i18n/locales/en/palette.json index 930393d2..0c4403fb 100644 --- a/frontend/src/i18n/locales/en/palette.json +++ b/frontend/src/i18n/locales/en/palette.json @@ -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", diff --git a/frontend/src/i18n/locales/en/playlists.json b/frontend/src/i18n/locales/en/playlists.json index e6adfa49..10cd22da 100644 --- a/frontend/src/i18n/locales/en/playlists.json +++ b/frontend/src/i18n/locales/en/playlists.json @@ -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", diff --git a/frontend/src/i18n/locales/en/plex.json b/frontend/src/i18n/locales/en/plex.json index 7b0781a8..6df97c52 100644 --- a/frontend/src/i18n/locales/en/plex.json +++ b/frontend/src/i18n/locales/en/plex.json @@ -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", diff --git a/frontend/src/i18n/locales/en/scheduler.json b/frontend/src/i18n/locales/en/scheduler.json index c6b82cc5..1f81ed2a 100644 --- a/frontend/src/i18n/locales/en/scheduler.json +++ b/frontend/src/i18n/locales/en/scheduler.json @@ -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", diff --git a/frontend/src/i18n/locales/en/settings.json b/frontend/src/i18n/locales/en/settings.json index 49619177..89a5ec43 100644 --- a/frontend/src/i18n/locales/en/settings.json +++ b/frontend/src/i18n/locales/en/settings.json @@ -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", diff --git a/frontend/src/i18n/locales/en/sidebar.json b/frontend/src/i18n/locales/en/sidebar.json index b25e3437..c5508f32 100644 --- a/frontend/src/i18n/locales/en/sidebar.json +++ b/frontend/src/i18n/locales/en/sidebar.json @@ -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", diff --git a/frontend/src/i18n/locales/en/stats.json b/frontend/src/i18n/locales/en/stats.json index 0f660043..ae3935e9 100644 --- a/frontend/src/i18n/locales/en/stats.json +++ b/frontend/src/i18n/locales/en/stats.json @@ -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).", diff --git a/frontend/src/i18n/locales/en/tagManager.json b/frontend/src/i18n/locales/en/tagManager.json index 10e50c74..ce8ebf90 100644 --- a/frontend/src/i18n/locales/en/tagManager.json +++ b/frontend/src/i18n/locales/en/tagManager.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/audit.json b/frontend/src/i18n/locales/hu/audit.json index 294ce05e..60b56a83 100644 --- a/frontend/src/i18n/locales/hu/audit.json +++ b/frontend/src/i18n/locales/hu/audit.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/channels.json b/frontend/src/i18n/locales/hu/channels.json index 3c6e61bb..f73a6dd6 100644 --- a/frontend/src/i18n/locales/hu/channels.json +++ b/frontend/src/i18n/locales/hu/channels.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/common.json b/frontend/src/i18n/locales/hu/common.json index b91fa02c..93c2ef68 100644 --- a/frontend/src/i18n/locales/hu/common.json +++ b/frontend/src/i18n/locales/hu/common.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/editor.json b/frontend/src/i18n/locales/hu/editor.json index 5203cde7..d8bf9275 100644 --- a/frontend/src/i18n/locales/hu/editor.json +++ b/frontend/src/i18n/locales/hu/editor.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/header.json b/frontend/src/i18n/locales/hu/header.json index 9049a448..ed782b2e 100644 --- a/frontend/src/i18n/locales/hu/header.json +++ b/frontend/src/i18n/locales/hu/header.json @@ -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" diff --git a/frontend/src/i18n/locales/hu/inbox.json b/frontend/src/i18n/locales/hu/inbox.json index 477214bf..8f16fdba 100644 --- a/frontend/src/i18n/locales/hu/inbox.json +++ b/frontend/src/i18n/locales/hu/inbox.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/onboarding.json b/frontend/src/i18n/locales/hu/onboarding.json index dd4fcdc8..38b122a7 100644 --- a/frontend/src/i18n/locales/hu/onboarding.json +++ b/frontend/src/i18n/locales/hu/onboarding.json @@ -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 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", diff --git a/frontend/src/i18n/locales/hu/palette.json b/frontend/src/i18n/locales/hu/palette.json index 39ee5fd9..551ca07b 100644 --- a/frontend/src/i18n/locales/hu/palette.json +++ b/frontend/src/i18n/locales/hu/palette.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/playlists.json b/frontend/src/i18n/locales/hu/playlists.json index ce7cec69..45e57c91 100644 --- a/frontend/src/i18n/locales/hu/playlists.json +++ b/frontend/src/i18n/locales/hu/playlists.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/plex.json b/frontend/src/i18n/locales/hu/plex.json index 844c82d3..20165d44 100644 --- a/frontend/src/i18n/locales/hu/plex.json +++ b/frontend/src/i18n/locales/hu/plex.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/scheduler.json b/frontend/src/i18n/locales/hu/scheduler.json index f1011665..c6f79124 100644 --- a/frontend/src/i18n/locales/hu/scheduler.json +++ b/frontend/src/i18n/locales/hu/scheduler.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/settings.json b/frontend/src/i18n/locales/hu/settings.json index 5585ae23..c641fba3 100644 --- a/frontend/src/i18n/locales/hu/settings.json +++ b/frontend/src/i18n/locales/hu/settings.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/sidebar.json b/frontend/src/i18n/locales/hu/sidebar.json index 70d70934..87626359 100644 --- a/frontend/src/i18n/locales/hu/sidebar.json +++ b/frontend/src/i18n/locales/hu/sidebar.json @@ -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", diff --git a/frontend/src/i18n/locales/hu/stats.json b/frontend/src/i18n/locales/hu/stats.json index 3a325ebf..eee941bf 100644 --- a/frontend/src/i18n/locales/hu/stats.json +++ b/frontend/src/i18n/locales/hu/stats.json @@ -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).", diff --git a/frontend/src/i18n/locales/hu/tagManager.json b/frontend/src/i18n/locales/hu/tagManager.json index 76a6d766..c962f5a6 100644 --- a/frontend/src/i18n/locales/hu/tagManager.json +++ b/frontend/src/i18n/locales/hu/tagManager.json @@ -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", diff --git a/frontend/src/i18n/plurals.test.ts b/frontend/src/i18n/plurals.test.ts index 24c3adc2..5b858929 100644 --- a/frontend/src/i18n/plurals.test.ts +++ b/frontend/src/i18n/plurals.test.ts @@ -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 } +>; + +// Every leaf string as [dotted key, value]. +function leaves(obj: Record, 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, 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"); + }); +});