From cedcbb143414c1f9b47b9dd71681d6c21676547e Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 19 Sep 2026 17:15:18 +0200 Subject: [PATCH] test(i18n): every locale key must be read by the app; 75 dead keys removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A contract test scans the source (tests excluded) for each key as a whole literal, or under a dynamic prefix like `plex.sort.${k}`, with plural forms counted through their base key. It found 75 keys that had outlived the code showing them (the old sidebar sort vocabulary, the old login page, a pager the DataTable replaced, …); they are gone from EN and HU. A key read in a way the scan cannot see goes in an explicit ALLOWED map with its reason. --- frontend/src/i18n/locales/en/channels.json | 5 -- frontend/src/i18n/locales/en/common.json | 1 - frontend/src/i18n/locales/en/config.json | 1 - frontend/src/i18n/locales/en/datatable.json | 1 - frontend/src/i18n/locales/en/downloads.json | 18 ----- frontend/src/i18n/locales/en/feed.json | 2 - frontend/src/i18n/locales/en/header.json | 3 - frontend/src/i18n/locales/en/inbox.json | 1 - frontend/src/i18n/locales/en/login.json | 13 +--- frontend/src/i18n/locales/en/messages.json | 1 - frontend/src/i18n/locales/en/nav.json | 3 - .../src/i18n/locales/en/notifications.json | 8 +-- frontend/src/i18n/locales/en/playlists.json | 1 - frontend/src/i18n/locales/en/plex.json | 5 -- frontend/src/i18n/locales/en/sidebar.json | 21 +----- frontend/src/i18n/locales/en/views.json | 4 -- frontend/src/i18n/locales/hu/channels.json | 5 -- frontend/src/i18n/locales/hu/common.json | 1 - frontend/src/i18n/locales/hu/config.json | 1 - frontend/src/i18n/locales/hu/datatable.json | 1 - frontend/src/i18n/locales/hu/downloads.json | 18 ----- frontend/src/i18n/locales/hu/feed.json | 2 - frontend/src/i18n/locales/hu/header.json | 3 - frontend/src/i18n/locales/hu/inbox.json | 1 - frontend/src/i18n/locales/hu/login.json | 13 +--- frontend/src/i18n/locales/hu/messages.json | 1 - frontend/src/i18n/locales/hu/nav.json | 3 - .../src/i18n/locales/hu/notifications.json | 8 +-- frontend/src/i18n/locales/hu/playlists.json | 1 - frontend/src/i18n/locales/hu/plex.json | 5 -- frontend/src/i18n/locales/hu/sidebar.json | 21 +----- frontend/src/i18n/locales/hu/views.json | 4 -- frontend/src/i18n/unusedKeys.contract.test.ts | 70 +++++++++++++++++++ 33 files changed, 76 insertions(+), 170 deletions(-) create mode 100644 frontend/src/i18n/unusedKeys.contract.test.ts diff --git a/frontend/src/i18n/locales/en/channels.json b/frontend/src/i18n/locales/en/channels.json index 4f88acf4..844dca8f 100644 --- a/frontend/src/i18n/locales/en/channels.json +++ b/frontend/src/i18n/locales/en/channels.json @@ -72,11 +72,6 @@ }, "loading": "Loading channels…", "empty": "No channels.", - "pager": { - "prev": "Previous", - "next": "Next", - "page": "Page {{page}} of {{total}}" - }, "cols": { "priority": "Prio", "channel": "Channel", diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json index 7be0f61c..9db81db3 100644 --- a/frontend/src/i18n/locales/en/common.json +++ b/frontend/src/i18n/locales/en/common.json @@ -10,7 +10,6 @@ "undo": "Undo", "redo": "Redo", "loading": "Loading…", - "language": "Language", "somethingWrong": "Something went wrong.", "itemCount_one": "{{formattedCount}} item", "itemCount_other": "{{formattedCount}} items", diff --git a/frontend/src/i18n/locales/en/config.json b/frontend/src/i18n/locales/en/config.json index a342b443..59e5fdb6 100644 --- a/frontend/src/i18n/locales/en/config.json +++ b/frontend/src/i18n/locales/en/config.json @@ -270,7 +270,6 @@ "willReset": "will reset to default on save", "reset": "Reset", "resetHint": "Remove this override and fall back to the file/env default.", - "resetDone": "Reset to default", "usingDefault": "using default", "overridden": "overridden in DB", "secretSet": "•••••••• stored", diff --git a/frontend/src/i18n/locales/en/datatable.json b/frontend/src/i18n/locales/en/datatable.json index 84c78e7d..561a6428 100644 --- a/frontend/src/i18n/locales/en/datatable.json +++ b/frontend/src/i18n/locales/en/datatable.json @@ -8,7 +8,6 @@ "pager": { "prev": "Previous", "next": "Next", - "page": "Page {{page}} of {{total}}", "pageLabel": "Page", "ofTotal": "of {{total}}" } diff --git a/frontend/src/i18n/locales/en/downloads.json b/frontend/src/i18n/locales/en/downloads.json index adc16396..4389fd06 100644 --- a/frontend/src/i18n/locales/en/downloads.json +++ b/frontend/src/i18n/locales/en/downloads.json @@ -112,9 +112,6 @@ }, "share": { "title": "Share download", - "label": "Recipient email", - "placeholder": "user@example.com", - "submit": "Share", "done": "Shared with {{email}}", "failed": "Couldn’t share.", "userSection": "Share with a user", @@ -162,7 +159,6 @@ "redownloadAction": "Re-download" }, "profiles": { - "manage": "Manage formats", "title": "Download formats", "builtin": "Built-in", "yours": "Your formats", @@ -186,14 +182,11 @@ "deleteConfirm": "Delete the format “{{name}}”?" }, "admin": { - "storageTitle": "Storage", "readyFiles": "Ready files", "totalSize": "Total size", "cap": "Cache cap", "noCap": "no cap", "perUser": "Per-user footprint", - "user": "User", - "footprint": "Footprint", "editQuota": "Edit quota", "quotaTitle": "Download quota — {{email}}", "maxBytes": "Storage limit (GB)", @@ -202,20 +195,9 @@ "unlimited": "Unlimited (bypass storage limit)", "reset": "Reset to default", "save": "Save", - "custom": "custom", - "default": "default", "quotaPickPlaceholder": "Set a user's quota…", "noFootprint": "No downloads yet." }, - "cols": { - "title": "Title", - "channel": "Channel", - "format": "Format", - "size": "Size", - "status": "Status", - "added": "Added", - "user": "User" - }, "clipBadge": "Clip", "errors": { "quota": { diff --git a/frontend/src/i18n/locales/en/feed.json b/frontend/src/i18n/locales/en/feed.json index 60d0cf9c..b6081059 100644 --- a/frontend/src/i18n/locales/en/feed.json +++ b/frontend/src/i18n/locales/en/feed.json @@ -23,8 +23,6 @@ "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.", "viewInLibrary": "View in Library", - "videoCount_one": "{{formattedCount}} video", - "videoCount_other": "{{formattedCount}} videos", "sortLabel": "Sort", "viewLabel": "View", "showLabel": "Watch state", diff --git a/frontend/src/i18n/locales/en/header.json b/frontend/src/i18n/locales/en/header.json index 19968222..e682bf25 100644 --- a/frontend/src/i18n/locales/en/header.json +++ b/frontend/src/i18n/locales/en/header.json @@ -1,13 +1,10 @@ { - "feed": "Feed", "searchPlaceholder": "Search your subscriptions…", "clearSearch": "Clear search", "searchYoutube": "Search YouTube", "searchYoutubeTip": "Search live on YouTube for this term (uses shared API quota). You can also press Enter.", "go": "Go", "goTip": "Search (or press Enter)", - "prevModule": "Previous module", - "nextModule": "Next module", "channelManager": "Channel manager", "usageStats": "Usage & stats", "scheduler": "Scheduler", diff --git a/frontend/src/i18n/locales/en/inbox.json b/frontend/src/i18n/locales/en/inbox.json index 56e97178..bb16c997 100644 --- a/frontend/src/i18n/locales/en/inbox.json +++ b/frontend/src/i18n/locales/en/inbox.json @@ -2,7 +2,6 @@ "navLabel": "Notifications", "title": "Notifications", "subtitle": "Updates from your library and the system.", - "tabInbox": "Notifications", "empty": "You're all caught up.", "markAllRead": "Mark all read", "clearAll": "Clear all", diff --git a/frontend/src/i18n/locales/en/login.json b/frontend/src/i18n/locales/en/login.json index 09bed1ff..0967ef42 100644 --- a/frontend/src/i18n/locales/en/login.json +++ b/frontend/src/i18n/locales/en/login.json @@ -1,12 +1 @@ -{ - "tagline": "A self-hosted, filterable Feed of your YouTube subscriptions — sort, tag, and tune out the noise. Sign in with Google to get started; YouTube access is an optional, separate step you control.", - "signIn": "Sign in with Google", - "approved": "You're already approved — just sign in with Google above.", - "requested": "Thanks — your request is in. We'll email you when it's approved.", - "noAccessYet": "No access yet?", - "denied": "That Google account isn't approved. Request access below.", - "emailPlaceholder": "you@gmail.com", - "sending": "Sending…", - "requestAccess": "Request access", - "submitError": "Couldn't submit — check the address and try again." -} +{} diff --git a/frontend/src/i18n/locales/en/messages.json b/frontend/src/i18n/locales/en/messages.json index 68c482b0..dde9f393 100644 --- a/frontend/src/i18n/locales/en/messages.json +++ b/frontend/src/i18n/locales/en/messages.json @@ -1,5 +1,4 @@ { - "tab": "Messages", "navLabel": "Messages", "popOut": "Open in pop-out", "expand": "Expand", diff --git a/frontend/src/i18n/locales/en/nav.json b/frontend/src/i18n/locales/en/nav.json index 78a70584..819a6dac 100644 --- a/frontend/src/i18n/locales/en/nav.json +++ b/frontend/src/i18n/locales/en/nav.json @@ -1,8 +1,5 @@ { "primary": "Primary navigation", - "pin": "Pin sidebar open", - "unpin": "Unpin sidebar", - "adminSection": "Admin", "role": { "admin": "Admin", "user": "User", diff --git a/frontend/src/i18n/locales/en/notifications.json b/frontend/src/i18n/locales/en/notifications.json index d35949af..6dce3d40 100644 --- a/frontend/src/i18n/locales/en/notifications.json +++ b/frontend/src/i18n/locales/en/notifications.json @@ -1,15 +1,9 @@ { - "title": "Notifications", - "clearAll": "Clear all", - "clear": "Clear", - "empty": "No notifications yet.", "actionNeeded": "Action needed", "dismiss": "Dismiss", "findInFeed": "Find in Feed", "unhide": "Unhide", "unwatch": "Unwatch", "openInManager": "Channel manager", - "openOnYouTube": "Open on YouTube", - "unhidden": "Unhidden “{{title}}”", - "unwatched": "Unwatched “{{title}}”" + "openOnYouTube": "Open on YouTube" } diff --git a/frontend/src/i18n/locales/en/playlists.json b/frontend/src/i18n/locales/en/playlists.json index f1a6dc46..78277d3c 100644 --- a/frontend/src/i18n/locales/en/playlists.json +++ b/frontend/src/i18n/locales/en/playlists.json @@ -6,7 +6,6 @@ "syncYoutube": "Sync from YouTube", "syncedToast": "Synced {{count}} playlists from YouTube", "syncFailed": "Couldn't sync from YouTube", - "ytReadonly": "Synced from YouTube — edits sync back", "noneYet": "No playlists yet", "newPlaylist": "New playlist…", "searchPlaceholder": "Search playlists…", diff --git a/frontend/src/i18n/locales/en/plex.json b/frontend/src/i18n/locales/en/plex.json index 6beaaa53..eec8da42 100644 --- a/frontend/src/i18n/locales/en/plex.json +++ b/frontend/src/i18n/locales/en/plex.json @@ -1,6 +1,5 @@ { "navLabel": "Plex", - "backToFeed": "YouTube Feed", "backToLibrary": "Back to library", "collection": { "playAll": "Play all", @@ -113,8 +112,6 @@ "transcode_sw": "Transcode · SW" } }, - "count": "{{count}} titles", - "searchCount": "{{count}} matches", "noMatches": "No matches.", "noMatchesFiltered_one": "No matches — a filter is also narrowing the results.", "noMatchesFiltered_other": "No matches — {{count}} filters are also narrowing the results.", @@ -303,10 +300,8 @@ }, "playlist": { "section": "Playlists", - "newPlaceholder": "New playlist…", "pickerPlaceholder": "Find or create a playlist…", "createNamed": "Create \"{{name}}\"", - "create": "Create playlist", "none": "No playlists yet.", "deleteConfirm": "Delete the playlist \"{{title}}\"?", "rename": "Rename", diff --git a/frontend/src/i18n/locales/en/sidebar.json b/frontend/src/i18n/locales/en/sidebar.json index 733ded12..5ec3dd79 100644 --- a/frontend/src/i18n/locales/en/sidebar.json +++ b/frontend/src/i18n/locales/en/sidebar.json @@ -6,10 +6,6 @@ "customize": "Customize sidebar", "done": "Done", "editHint": "Drag to reorder · eye to show/hide", - "channel": "Channel", - "loading": "Loading…", - "channelCount": "{{count}} channel", - "channelCount_other": "{{count}} channels", "dragToReorder": "Drag to reorder", "showWidget": "Show widget", "hideWidget": "Hide widget", @@ -19,8 +15,6 @@ "unpin": "Unpin panel", "any": "Any", "all": "All", - "tagModeTooltip": "Match any vs all selected tags", - "custom": "Custom", "from": "From", "to": "To", "clearDates": "clear dates", @@ -56,17 +50,6 @@ "saved": "Saved", "hidden": "Hidden" }, - "sort": { - "newest": "Newest", - "oldest": "Oldest", - "views": "Most viewed", - "duration_desc": "Longest", - "duration_asc": "Shortest", - "title": "Name (A–Z)", - "subscribers": "Channel subscribers", - "priority": "Channel priority", - "shuffle": "Surprise me" - }, "content": { "normal": "Normal", "shorts": "Shorts", @@ -91,7 +74,5 @@ "untilShort": "Until {{date}}" }, "channelSearch": "Search channels…", - "noChannelMatch": "No channel matches.", - "channelOrder": "Priority first, then A–Z", - "channelRemove": "Remove channel: {{name}}" + "noChannelMatch": "No channel matches." } diff --git a/frontend/src/i18n/locales/en/views.json b/frontend/src/i18n/locales/en/views.json index fa802349..b4ff9e26 100644 --- a/frontend/src/i18n/locales/en/views.json +++ b/frontend/src/i18n/locales/en/views.json @@ -1,12 +1,8 @@ { "save": "Save current view", "namePlaceholder": "View name", - "saveAction": "Save", - "cancel": "Cancel", "empty": "No saved views yet. Apply some filters, then save them.", "apply": "Apply this view", - "makeDefault": "Set as default (loads on start)", - "isDefault": "Default view — loads on start", "rename": "Rename", "delete": "Delete", "deleteConfirm": "Delete the saved view “{{name}}”?", diff --git a/frontend/src/i18n/locales/hu/channels.json b/frontend/src/i18n/locales/hu/channels.json index cae6384c..38d3b6a0 100644 --- a/frontend/src/i18n/locales/hu/channels.json +++ b/frontend/src/i18n/locales/hu/channels.json @@ -72,11 +72,6 @@ }, "loading": "Csatornák betöltése…", "empty": "Nincsenek csatornák.", - "pager": { - "prev": "Előző", - "next": "Következő", - "page": "{{page}}. oldal / {{total}}" - }, "cols": { "priority": "Prio", "channel": "Csatorna", diff --git a/frontend/src/i18n/locales/hu/common.json b/frontend/src/i18n/locales/hu/common.json index 0f59bd60..2c9978d5 100644 --- a/frontend/src/i18n/locales/hu/common.json +++ b/frontend/src/i18n/locales/hu/common.json @@ -10,7 +10,6 @@ "undo": "Visszavonás", "redo": "Újra", "loading": "Betöltés…", - "language": "Nyelv", "somethingWrong": "Hiba történt.", "itemCount_one": "{{formattedCount}} elem", "itemCount_other": "{{formattedCount}} elem", diff --git a/frontend/src/i18n/locales/hu/config.json b/frontend/src/i18n/locales/hu/config.json index 6318540e..99ec5456 100644 --- a/frontend/src/i18n/locales/hu/config.json +++ b/frontend/src/i18n/locales/hu/config.json @@ -270,7 +270,6 @@ "willReset": "mentéskor visszaáll alapértékre", "reset": "Visszaállítás", "resetHint": "Eltávolítja ezt a felülírást, és visszatér a fájl/env alapértékhez.", - "resetDone": "Visszaállítva az alapértékre", "usingDefault": "alapérték használatban", "overridden": "felülírva az adatbázisban", "secretSet": "•••••••• tárolva", diff --git a/frontend/src/i18n/locales/hu/datatable.json b/frontend/src/i18n/locales/hu/datatable.json index 269704c1..63f49085 100644 --- a/frontend/src/i18n/locales/hu/datatable.json +++ b/frontend/src/i18n/locales/hu/datatable.json @@ -8,7 +8,6 @@ "pager": { "prev": "Előző", "next": "Következő", - "page": "{{page}}. oldal / {{total}}", "pageLabel": "Oldal", "ofTotal": "/ {{total}}" } diff --git a/frontend/src/i18n/locales/hu/downloads.json b/frontend/src/i18n/locales/hu/downloads.json index 00362e97..53f354ea 100644 --- a/frontend/src/i18n/locales/hu/downloads.json +++ b/frontend/src/i18n/locales/hu/downloads.json @@ -112,9 +112,6 @@ }, "share": { "title": "Letöltés megosztása", - "label": "Címzett e-mail címe", - "placeholder": "user@example.com", - "submit": "Megosztás", "done": "Megosztva vele: {{email}}", "failed": "A megosztás nem sikerült.", "userSection": "Megosztás felhasználóval", @@ -162,7 +159,6 @@ "redownloadAction": "Újraletöltés" }, "profiles": { - "manage": "Formátumok kezelése", "title": "Letöltési formátumok", "builtin": "Beépített", "yours": "Saját formátumaid", @@ -186,14 +182,11 @@ "deleteConfirm": "Törlöd a(z) „{{name}}” formátumot?" }, "admin": { - "storageTitle": "Tárhely", "readyFiles": "Kész fájlok", "totalSize": "Teljes méret", "cap": "Gyorsítótár-korlát", "noCap": "nincs korlát", "perUser": "Felhasználónkénti tárhelyhasználat", - "user": "Felhasználó", - "footprint": "Használat", "editQuota": "Kvóta szerkesztése", "quotaTitle": "Letöltési kvóta — {{email}}", "maxBytes": "Tárhelykorlát (GB)", @@ -202,20 +195,9 @@ "unlimited": "Korlátlan (tárhelykorlát kikapcsolása)", "reset": "Visszaállítás alapértékre", "save": "Mentés", - "custom": "egyedi", - "default": "alapértelmezett", "quotaPickPlaceholder": "Kvóta beállítása egy felhasználónak…", "noFootprint": "Még nincs letöltés." }, - "cols": { - "title": "Cím", - "channel": "Csatorna", - "format": "Formátum", - "size": "Méret", - "status": "Állapot", - "added": "Hozzáadva", - "user": "Felhasználó" - }, "clipBadge": "Klip", "errors": { "quota": { diff --git a/frontend/src/i18n/locales/hu/feed.json b/frontend/src/i18n/locales/hu/feed.json index dd5bfed4..0be0bc03 100644 --- a/frontend/src/i18n/locales/hu/feed.json +++ b/frontend/src/i18n/locales/hu/feed.json @@ -23,8 +23,6 @@ "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.", "viewInLibrary": "Megnézés a Könyvtárban", - "videoCount_one": "{{formattedCount}} videó", - "videoCount_other": "{{formattedCount}} videó", "sortLabel": "Rendezés", "viewLabel": "Nézet", "showLabel": "Megtekintési állapot", diff --git a/frontend/src/i18n/locales/hu/header.json b/frontend/src/i18n/locales/hu/header.json index 7aa1d217..4c09b5da 100644 --- a/frontend/src/i18n/locales/hu/header.json +++ b/frontend/src/i18n/locales/hu/header.json @@ -1,13 +1,10 @@ { - "feed": "Feed", "searchPlaceholder": "Keresés a feliratkozásaid között…", "clearSearch": "Keresés törlése", "searchYoutube": "Keresés a YouTube-on", "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)", - "prevModule": "Előző modul", - "nextModule": "Következő modul", "channelManager": "Csatornakezelő", "usageStats": "Használat és statisztika", "scheduler": "Ütemező", diff --git a/frontend/src/i18n/locales/hu/inbox.json b/frontend/src/i18n/locales/hu/inbox.json index 6352b822..f8b434d1 100644 --- a/frontend/src/i18n/locales/hu/inbox.json +++ b/frontend/src/i18n/locales/hu/inbox.json @@ -2,7 +2,6 @@ "navLabel": "Értesítések", "title": "Értesítések", "subtitle": "Frissítések a könyvtáradból és a rendszertől.", - "tabInbox": "Értesítések", "empty": "Nincs új értesítés.", "markAllRead": "Összes olvasott", "clearAll": "Összes törlése", diff --git a/frontend/src/i18n/locales/hu/login.json b/frontend/src/i18n/locales/hu/login.json index a270290e..0967ef42 100644 --- a/frontend/src/i18n/locales/hu/login.json +++ b/frontend/src/i18n/locales/hu/login.json @@ -1,12 +1 @@ -{ - "tagline": "Saját üzemeltetésű, szűrhető Feed a YouTube-feliratkozásaidból — rendezd, címkézd, és szűrd ki a zajt. A kezdéshez jelentkezz be Google-fiókkal; a YouTube-hozzáférés külön, általad vezérelt lépés.", - "signIn": "Bejelentkezés Google-fiókkal", - "approved": "Már jóvá vagy hagyva — csak jelentkezz be a fenti Google-gombbal.", - "requested": "Köszönjük — a kérésedet rögzítettük. E-mailben értesítünk, ha jóváhagytuk.", - "noAccessYet": "Még nincs hozzáférésed?", - "denied": "Ez a Google-fiók nincs jóváhagyva. Lent kérhetsz hozzáférést.", - "emailPlaceholder": "te@gmail.com", - "sending": "Küldés…", - "requestAccess": "Hozzáférés kérése", - "submitError": "Nem sikerült elküldeni — ellenőrizd a címet, és próbáld újra." -} +{} diff --git a/frontend/src/i18n/locales/hu/messages.json b/frontend/src/i18n/locales/hu/messages.json index 8588a0c6..c531c20e 100644 --- a/frontend/src/i18n/locales/hu/messages.json +++ b/frontend/src/i18n/locales/hu/messages.json @@ -1,5 +1,4 @@ { - "tab": "Üzenetek", "navLabel": "Üzenetek", "popOut": "Megnyitás felugró ablakban", "expand": "Kinyitás", diff --git a/frontend/src/i18n/locales/hu/nav.json b/frontend/src/i18n/locales/hu/nav.json index 0cc3f12e..efdc344f 100644 --- a/frontend/src/i18n/locales/hu/nav.json +++ b/frontend/src/i18n/locales/hu/nav.json @@ -1,8 +1,5 @@ { "primary": "Fő navigáció", - "pin": "Oldalsáv rögzítése nyitva", - "unpin": "Oldalsáv rögzítésének feloldása", - "adminSection": "Admin", "role": { "admin": "Admin", "user": "Felhasználó", diff --git a/frontend/src/i18n/locales/hu/notifications.json b/frontend/src/i18n/locales/hu/notifications.json index 9a1344b4..567be2d7 100644 --- a/frontend/src/i18n/locales/hu/notifications.json +++ b/frontend/src/i18n/locales/hu/notifications.json @@ -1,15 +1,9 @@ { - "title": "Értesítések", - "clearAll": "Összes törlése", - "clear": "Törlés", - "empty": "Még nincs értesítés.", "actionNeeded": "Teendő", "dismiss": "Elvetés", "findInFeed": "Keresés a Feedben", "unhide": "Megjelenítés", "unwatch": "Megtekintés visszavonása", "openInManager": "Csatornakezelő", - "openOnYouTube": "Megnyitás a YouTube-on", - "unhidden": "Megjelenítve: „{{title}}”", - "unwatched": "Megtekintés visszavonva: „{{title}}”" + "openOnYouTube": "Megnyitás a YouTube-on" } diff --git a/frontend/src/i18n/locales/hu/playlists.json b/frontend/src/i18n/locales/hu/playlists.json index 8bd0725a..bd2812dc 100644 --- a/frontend/src/i18n/locales/hu/playlists.json +++ b/frontend/src/i18n/locales/hu/playlists.json @@ -6,7 +6,6 @@ "syncYoutube": "Szinkron YouTube-ról", "syncedToast": "{{count}} lista szinkronizálva a YouTube-ról", "syncFailed": "Nem sikerült a YouTube-szinkron", - "ytReadonly": "YouTube-ról szinkronizálva — a módosítások visszaszinkronizálhatók", "noneYet": "Még nincs lejátszási lista", "newPlaylist": "Új lista…", "searchPlaceholder": "Listák keresése…", diff --git a/frontend/src/i18n/locales/hu/plex.json b/frontend/src/i18n/locales/hu/plex.json index 3be0a0d0..d1e8420c 100644 --- a/frontend/src/i18n/locales/hu/plex.json +++ b/frontend/src/i18n/locales/hu/plex.json @@ -1,6 +1,5 @@ { "navLabel": "Plex", - "backToFeed": "YouTube Feed", "backToLibrary": "Vissza a könyvtárhoz", "collection": { "playAll": "Összes lejátszása", @@ -113,8 +112,6 @@ "transcode_sw": "Átkódolás · SW" } }, - "count": "{{count}} cím", - "searchCount": "{{count}} találat", "noMatches": "Nincs találat.", "noMatchesFiltered_one": "Nincs találat — egy szűrő is szűkíti az eredményt.", "noMatchesFiltered_other": "Nincs találat — {{count}} szűrő is szűkíti az eredményt.", @@ -303,10 +300,8 @@ }, "playlist": { "section": "Lejátszási listák", - "newPlaceholder": "Új lista…", "pickerPlaceholder": "Lista keresése vagy létrehozása…", "createNamed": "„{{name}}” létrehozása", - "create": "Lista létrehozása", "none": "Még nincs lista.", "deleteConfirm": "Törlöd a(z) „{{title}}” listát?", "rename": "Átnevezés", diff --git a/frontend/src/i18n/locales/hu/sidebar.json b/frontend/src/i18n/locales/hu/sidebar.json index ecb78cf5..97042155 100644 --- a/frontend/src/i18n/locales/hu/sidebar.json +++ b/frontend/src/i18n/locales/hu/sidebar.json @@ -6,10 +6,6 @@ "customize": "Oldalsáv testreszabása", "done": "Kész", "editHint": "Húzd az átrendezéshez · szem a megjelenítéshez/elrejtéshez", - "channel": "Csatorna", - "loading": "Betöltés…", - "channelCount": "{{count}} csatorna", - "channelCount_other": "{{count}} csatorna", "dragToReorder": "Húzd az átrendezéshez", "showWidget": "Modul megjelenítése", "hideWidget": "Modul elrejtése", @@ -19,8 +15,6 @@ "unpin": "Panel rögzítésének feloldása", "any": "Bármelyik", "all": "Összes", - "tagModeTooltip": "Bármelyik vagy az összes kijelölt címke illeszkedjen", - "custom": "Egyéni", "from": "Ettől", "to": "Eddig", "clearDates": "dátumok törlése", @@ -56,17 +50,6 @@ "saved": "Mentett", "hidden": "Rejtett" }, - "sort": { - "newest": "Legújabb", - "oldest": "Legrégebbi", - "views": "Legnézettebb", - "duration_desc": "Leghosszabb", - "duration_asc": "Legrövidebb", - "title": "Név (A–Z)", - "subscribers": "Csatorna feliratkozói", - "priority": "Csatorna prioritás", - "shuffle": "Lepj meg" - }, "content": { "normal": "Normál", "shorts": "Shorts", @@ -91,7 +74,5 @@ "untilShort": "{{date}}-ig" }, "channelSearch": "Csatornák keresése…", - "noChannelMatch": "Nincs találat.", - "channelOrder": "Prioritás szerint, majd A–Z", - "channelRemove": "Csatorna eltávolítása: {{name}}" + "noChannelMatch": "Nincs találat." } diff --git a/frontend/src/i18n/locales/hu/views.json b/frontend/src/i18n/locales/hu/views.json index 054aff56..5ce4c553 100644 --- a/frontend/src/i18n/locales/hu/views.json +++ b/frontend/src/i18n/locales/hu/views.json @@ -1,12 +1,8 @@ { "save": "Aktuális nézet mentése", "namePlaceholder": "Nézet neve", - "saveAction": "Mentés", - "cancel": "Mégse", "empty": "Még nincs mentett nézet. Állíts be szűrőket, majd mentsd el.", "apply": "Nézet alkalmazása", - "makeDefault": "Beállítás alapértelmezettként (indításkor betölt)", - "isDefault": "Alapértelmezett nézet — indításkor betölt", "rename": "Átnevezés", "delete": "Törlés", "deleteConfirm": "Törlöd a(z) „{{name}}” mentett nézetet?", diff --git a/frontend/src/i18n/unusedKeys.contract.test.ts b/frontend/src/i18n/unusedKeys.contract.test.ts new file mode 100644 index 00000000..ccb8cb38 --- /dev/null +++ b/frontend/src/i18n/unusedKeys.contract.test.ts @@ -0,0 +1,70 @@ +import { readdirSync, readFileSync, statSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +// Every locale key is used by the app. The parity test proves EN and HU agree; nothing proved either +// one is still READ — 75 keys had outlived the code that showed them (a whole sidebar sort vocabulary, +// the old login page, a pager the DataTable replaced), each a string a translator would keep in step +// for nobody, and each one making "is this key live?" a grep instead of a fact. +// +// A key counts as used when the source (tests excluded — a test naming a key does not show it to +// anyone) holds it as a whole string literal, or holds a string that ends in `.` right before an +// interpolation or a concatenation — a DYNAMIC prefix like `plex.sort.${k}` — which then covers every +// key under it. Plural forms count through their base key. A key read in a way this cannot see goes +// in ALLOWED with the reason, rather than the check being loosened for everyone. +const ALLOWED: Record = {}; + +const SRC = fileURLToPath(new URL("../", import.meta.url)); + +function flatten(obj: Record, prefix: string, out: string[]): string[] { + for (const [k, v] of Object.entries(obj)) { + const key = `${prefix}.${k}`; + if (v && typeof v === "object" && !Array.isArray(v)) + flatten(v as Record, key, out); + else out.push(key); + } + return out; +} + +function sources(dir: string, out: string[] = []): string[] { + for (const f of readdirSync(dir)) { + const p = join(dir, f); + if (statSync(p).isDirectory()) sources(p, out); + else if (/\.(ts|tsx)$/.test(f) && !/\.test\./.test(f)) out.push(readFileSync(p, "utf-8")); + } + return out; +} + +const locales = join(SRC, "i18n/locales/en"); +const keys = readdirSync(locales).flatMap((f) => + flatten(JSON.parse(readFileSync(join(locales, f), "utf-8")), f.replace(/\.json$/, ""), []), +); +const code = sources(SRC).join("\n"); +const literals = new Set([...code.matchAll(/["'`]([\w.-]+)["'`]/g)].map((m) => m[1]!)); +const prefixes = [...code.matchAll(/["'`]([\w.]+\.)(?:\$\{|["'`]\s*\+)/g)].map((m) => m[1]!); +const base = (k: string) => k.replace(/_(zero|one|two|few|many|other)$/, ""); +const used = (k: string) => + literals.has(k) || literals.has(base(k)) || prefixes.some((p) => k.startsWith(p)); + +describe("i18n keys", () => { + it("finds keys and code to check them against", () => { + expect(keys.length).toBeGreaterThan(1000); + expect(literals.has("home.hero.reroll")).toBe(true); + expect(prefixes).toContain("plex.sort."); + }); + + it("are all read somewhere in the app", () => { + expect(keys.filter((k) => !used(k) && !(k in ALLOWED))).toEqual([]); + }); + + it("allow only keys that exist and the scan really cannot see", () => { + for (const k of Object.keys(ALLOWED)) { + expect({ k, exists: keys.includes(k), scanSees: used(k) }).toEqual({ + k, + exists: true, + scanSees: false, + }); + } + }); +});