test(i18n): every locale key must be read by the app; 75 dead keys removed

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.
This commit is contained in:
2026-09-19 17:15:18 +02:00
parent 4f72b193aa
commit cedcbb1434
33 changed files with 76 additions and 170 deletions
@@ -72,11 +72,6 @@
}, },
"loading": "Loading channels…", "loading": "Loading channels…",
"empty": "No channels.", "empty": "No channels.",
"pager": {
"prev": "Previous",
"next": "Next",
"page": "Page {{page}} of {{total}}"
},
"cols": { "cols": {
"priority": "Prio", "priority": "Prio",
"channel": "Channel", "channel": "Channel",
-1
View File
@@ -10,7 +10,6 @@
"undo": "Undo", "undo": "Undo",
"redo": "Redo", "redo": "Redo",
"loading": "Loading…", "loading": "Loading…",
"language": "Language",
"somethingWrong": "Something went wrong.", "somethingWrong": "Something went wrong.",
"itemCount_one": "{{formattedCount}} item", "itemCount_one": "{{formattedCount}} item",
"itemCount_other": "{{formattedCount}} items", "itemCount_other": "{{formattedCount}} items",
-1
View File
@@ -270,7 +270,6 @@
"willReset": "will reset to default on save", "willReset": "will reset to default on save",
"reset": "Reset", "reset": "Reset",
"resetHint": "Remove this override and fall back to the file/env default.", "resetHint": "Remove this override and fall back to the file/env default.",
"resetDone": "Reset to default",
"usingDefault": "using default", "usingDefault": "using default",
"overridden": "overridden in DB", "overridden": "overridden in DB",
"secretSet": "•••••••• stored", "secretSet": "•••••••• stored",
@@ -8,7 +8,6 @@
"pager": { "pager": {
"prev": "Previous", "prev": "Previous",
"next": "Next", "next": "Next",
"page": "Page {{page}} of {{total}}",
"pageLabel": "Page", "pageLabel": "Page",
"ofTotal": "of {{total}}" "ofTotal": "of {{total}}"
} }
@@ -112,9 +112,6 @@
}, },
"share": { "share": {
"title": "Share download", "title": "Share download",
"label": "Recipient email",
"placeholder": "user@example.com",
"submit": "Share",
"done": "Shared with {{email}}", "done": "Shared with {{email}}",
"failed": "Couldn’t share.", "failed": "Couldn’t share.",
"userSection": "Share with a user", "userSection": "Share with a user",
@@ -162,7 +159,6 @@
"redownloadAction": "Re-download" "redownloadAction": "Re-download"
}, },
"profiles": { "profiles": {
"manage": "Manage formats",
"title": "Download formats", "title": "Download formats",
"builtin": "Built-in", "builtin": "Built-in",
"yours": "Your formats", "yours": "Your formats",
@@ -186,14 +182,11 @@
"deleteConfirm": "Delete the format “{{name}}”?" "deleteConfirm": "Delete the format “{{name}}”?"
}, },
"admin": { "admin": {
"storageTitle": "Storage",
"readyFiles": "Ready files", "readyFiles": "Ready files",
"totalSize": "Total size", "totalSize": "Total size",
"cap": "Cache cap", "cap": "Cache cap",
"noCap": "no cap", "noCap": "no cap",
"perUser": "Per-user footprint", "perUser": "Per-user footprint",
"user": "User",
"footprint": "Footprint",
"editQuota": "Edit quota", "editQuota": "Edit quota",
"quotaTitle": "Download quota — {{email}}", "quotaTitle": "Download quota — {{email}}",
"maxBytes": "Storage limit (GB)", "maxBytes": "Storage limit (GB)",
@@ -202,20 +195,9 @@
"unlimited": "Unlimited (bypass storage limit)", "unlimited": "Unlimited (bypass storage limit)",
"reset": "Reset to default", "reset": "Reset to default",
"save": "Save", "save": "Save",
"custom": "custom",
"default": "default",
"quotaPickPlaceholder": "Set a user's quota…", "quotaPickPlaceholder": "Set a user's quota…",
"noFootprint": "No downloads yet." "noFootprint": "No downloads yet."
}, },
"cols": {
"title": "Title",
"channel": "Channel",
"format": "Format",
"size": "Size",
"status": "Status",
"added": "Added",
"user": "User"
},
"clipBadge": "Clip", "clipBadge": "Clip",
"errors": { "errors": {
"quota": { "quota": {
-2
View File
@@ -23,8 +23,6 @@
"noMatches": "No videos match these filters.", "noMatches": "No videos match these filters.",
"channelScopeHint": "This channel isn't in your Feed — it may be hidden. Its videos are in the shared library.", "channelScopeHint": "This channel isn't in your Feed — it may be hidden. Its videos are in the shared library.",
"viewInLibrary": "View in Library", "viewInLibrary": "View in Library",
"videoCount_one": "{{formattedCount}} video",
"videoCount_other": "{{formattedCount}} videos",
"sortLabel": "Sort", "sortLabel": "Sort",
"viewLabel": "View", "viewLabel": "View",
"showLabel": "Watch state", "showLabel": "Watch state",
-3
View File
@@ -1,13 +1,10 @@
{ {
"feed": "Feed",
"searchPlaceholder": "Search your subscriptions…", "searchPlaceholder": "Search your subscriptions…",
"clearSearch": "Clear search", "clearSearch": "Clear search",
"searchYoutube": "Search YouTube", "searchYoutube": "Search YouTube",
"searchYoutubeTip": "Search live on YouTube for this term (uses shared API quota). You can also press Enter.", "searchYoutubeTip": "Search live on YouTube for this term (uses shared API quota). You can also press Enter.",
"go": "Go", "go": "Go",
"goTip": "Search (or press Enter)", "goTip": "Search (or press Enter)",
"prevModule": "Previous module",
"nextModule": "Next module",
"channelManager": "Channel manager", "channelManager": "Channel manager",
"usageStats": "Usage & stats", "usageStats": "Usage & stats",
"scheduler": "Scheduler", "scheduler": "Scheduler",
-1
View File
@@ -2,7 +2,6 @@
"navLabel": "Notifications", "navLabel": "Notifications",
"title": "Notifications", "title": "Notifications",
"subtitle": "Updates from your library and the system.", "subtitle": "Updates from your library and the system.",
"tabInbox": "Notifications",
"empty": "You're all caught up.", "empty": "You're all caught up.",
"markAllRead": "Mark all read", "markAllRead": "Mark all read",
"clearAll": "Clear all", "clearAll": "Clear all",
+1 -12
View File
@@ -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."
}
@@ -1,5 +1,4 @@
{ {
"tab": "Messages",
"navLabel": "Messages", "navLabel": "Messages",
"popOut": "Open in pop-out", "popOut": "Open in pop-out",
"expand": "Expand", "expand": "Expand",
-3
View File
@@ -1,8 +1,5 @@
{ {
"primary": "Primary navigation", "primary": "Primary navigation",
"pin": "Pin sidebar open",
"unpin": "Unpin sidebar",
"adminSection": "Admin",
"role": { "role": {
"admin": "Admin", "admin": "Admin",
"user": "User", "user": "User",
@@ -1,15 +1,9 @@
{ {
"title": "Notifications",
"clearAll": "Clear all",
"clear": "Clear",
"empty": "No notifications yet.",
"actionNeeded": "Action needed", "actionNeeded": "Action needed",
"dismiss": "Dismiss", "dismiss": "Dismiss",
"findInFeed": "Find in Feed", "findInFeed": "Find in Feed",
"unhide": "Unhide", "unhide": "Unhide",
"unwatch": "Unwatch", "unwatch": "Unwatch",
"openInManager": "Channel manager", "openInManager": "Channel manager",
"openOnYouTube": "Open on YouTube", "openOnYouTube": "Open on YouTube"
"unhidden": "Unhidden “{{title}}”",
"unwatched": "Unwatched “{{title}}”"
} }
@@ -6,7 +6,6 @@
"syncYoutube": "Sync from YouTube", "syncYoutube": "Sync from YouTube",
"syncedToast": "Synced {{count}} playlists from YouTube", "syncedToast": "Synced {{count}} playlists from YouTube",
"syncFailed": "Couldn't sync from YouTube", "syncFailed": "Couldn't sync from YouTube",
"ytReadonly": "Synced from YouTube — edits sync back",
"noneYet": "No playlists yet", "noneYet": "No playlists yet",
"newPlaylist": "New playlist…", "newPlaylist": "New playlist…",
"searchPlaceholder": "Search playlists…", "searchPlaceholder": "Search playlists…",
-5
View File
@@ -1,6 +1,5 @@
{ {
"navLabel": "Plex", "navLabel": "Plex",
"backToFeed": "YouTube Feed",
"backToLibrary": "Back to library", "backToLibrary": "Back to library",
"collection": { "collection": {
"playAll": "Play all", "playAll": "Play all",
@@ -113,8 +112,6 @@
"transcode_sw": "Transcode · SW" "transcode_sw": "Transcode · SW"
} }
}, },
"count": "{{count}} titles",
"searchCount": "{{count}} matches",
"noMatches": "No matches.", "noMatches": "No matches.",
"noMatchesFiltered_one": "No matches — a filter is also narrowing the results.", "noMatchesFiltered_one": "No matches — a filter is also narrowing the results.",
"noMatchesFiltered_other": "No matches — {{count}} filters are also narrowing the results.", "noMatchesFiltered_other": "No matches — {{count}} filters are also narrowing the results.",
@@ -303,10 +300,8 @@
}, },
"playlist": { "playlist": {
"section": "Playlists", "section": "Playlists",
"newPlaceholder": "New playlist…",
"pickerPlaceholder": "Find or create a playlist…", "pickerPlaceholder": "Find or create a playlist…",
"createNamed": "Create \"{{name}}\"", "createNamed": "Create \"{{name}}\"",
"create": "Create playlist",
"none": "No playlists yet.", "none": "No playlists yet.",
"deleteConfirm": "Delete the playlist \"{{title}}\"?", "deleteConfirm": "Delete the playlist \"{{title}}\"?",
"rename": "Rename", "rename": "Rename",
+1 -20
View File
@@ -6,10 +6,6 @@
"customize": "Customize sidebar", "customize": "Customize sidebar",
"done": "Done", "done": "Done",
"editHint": "Drag to reorder · eye to show/hide", "editHint": "Drag to reorder · eye to show/hide",
"channel": "Channel",
"loading": "Loading…",
"channelCount": "{{count}} channel",
"channelCount_other": "{{count}} channels",
"dragToReorder": "Drag to reorder", "dragToReorder": "Drag to reorder",
"showWidget": "Show widget", "showWidget": "Show widget",
"hideWidget": "Hide widget", "hideWidget": "Hide widget",
@@ -19,8 +15,6 @@
"unpin": "Unpin panel", "unpin": "Unpin panel",
"any": "Any", "any": "Any",
"all": "All", "all": "All",
"tagModeTooltip": "Match any vs all selected tags",
"custom": "Custom",
"from": "From", "from": "From",
"to": "To", "to": "To",
"clearDates": "clear dates", "clearDates": "clear dates",
@@ -56,17 +50,6 @@
"saved": "Saved", "saved": "Saved",
"hidden": "Hidden" "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": { "content": {
"normal": "Normal", "normal": "Normal",
"shorts": "Shorts", "shorts": "Shorts",
@@ -91,7 +74,5 @@
"untilShort": "Until {{date}}" "untilShort": "Until {{date}}"
}, },
"channelSearch": "Search channels…", "channelSearch": "Search channels…",
"noChannelMatch": "No channel matches.", "noChannelMatch": "No channel matches."
"channelOrder": "Priority first, then A–Z",
"channelRemove": "Remove channel: {{name}}"
} }
-4
View File
@@ -1,12 +1,8 @@
{ {
"save": "Save current view", "save": "Save current view",
"namePlaceholder": "View name", "namePlaceholder": "View name",
"saveAction": "Save",
"cancel": "Cancel",
"empty": "No saved views yet. Apply some filters, then save them.", "empty": "No saved views yet. Apply some filters, then save them.",
"apply": "Apply this view", "apply": "Apply this view",
"makeDefault": "Set as default (loads on start)",
"isDefault": "Default view — loads on start",
"rename": "Rename", "rename": "Rename",
"delete": "Delete", "delete": "Delete",
"deleteConfirm": "Delete the saved view “{{name}}”?", "deleteConfirm": "Delete the saved view “{{name}}”?",
@@ -72,11 +72,6 @@
}, },
"loading": "Csatornák betöltése…", "loading": "Csatornák betöltése…",
"empty": "Nincsenek csatornák.", "empty": "Nincsenek csatornák.",
"pager": {
"prev": "Előző",
"next": "Következő",
"page": "{{page}}. oldal / {{total}}"
},
"cols": { "cols": {
"priority": "Prio", "priority": "Prio",
"channel": "Csatorna", "channel": "Csatorna",
-1
View File
@@ -10,7 +10,6 @@
"undo": "Visszavonás", "undo": "Visszavonás",
"redo": "Újra", "redo": "Újra",
"loading": "Betöltés…", "loading": "Betöltés…",
"language": "Nyelv",
"somethingWrong": "Hiba történt.", "somethingWrong": "Hiba történt.",
"itemCount_one": "{{formattedCount}} elem", "itemCount_one": "{{formattedCount}} elem",
"itemCount_other": "{{formattedCount}} elem", "itemCount_other": "{{formattedCount}} elem",
-1
View File
@@ -270,7 +270,6 @@
"willReset": "mentéskor visszaáll alapértékre", "willReset": "mentéskor visszaáll alapértékre",
"reset": "Visszaállítás", "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.", "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", "usingDefault": "alapérték használatban",
"overridden": "felülírva az adatbázisban", "overridden": "felülírva az adatbázisban",
"secretSet": "•••••••• tárolva", "secretSet": "•••••••• tárolva",
@@ -8,7 +8,6 @@
"pager": { "pager": {
"prev": "Előző", "prev": "Előző",
"next": "Következő", "next": "Következő",
"page": "{{page}}. oldal / {{total}}",
"pageLabel": "Oldal", "pageLabel": "Oldal",
"ofTotal": "/ {{total}}" "ofTotal": "/ {{total}}"
} }
@@ -112,9 +112,6 @@
}, },
"share": { "share": {
"title": "Letöltés megosztása", "title": "Letöltés megosztása",
"label": "Címzett e-mail címe",
"placeholder": "user@example.com",
"submit": "Megosztás",
"done": "Megosztva vele: {{email}}", "done": "Megosztva vele: {{email}}",
"failed": "A megosztás nem sikerült.", "failed": "A megosztás nem sikerült.",
"userSection": "Megosztás felhasználóval", "userSection": "Megosztás felhasználóval",
@@ -162,7 +159,6 @@
"redownloadAction": "Újraletöltés" "redownloadAction": "Újraletöltés"
}, },
"profiles": { "profiles": {
"manage": "Formátumok kezelése",
"title": "Letöltési formátumok", "title": "Letöltési formátumok",
"builtin": "Beépített", "builtin": "Beépített",
"yours": "Saját formátumaid", "yours": "Saját formátumaid",
@@ -186,14 +182,11 @@
"deleteConfirm": "Törlöd a(z) „{{name}}” formátumot?" "deleteConfirm": "Törlöd a(z) „{{name}}” formátumot?"
}, },
"admin": { "admin": {
"storageTitle": "Tárhely",
"readyFiles": "Kész fájlok", "readyFiles": "Kész fájlok",
"totalSize": "Teljes méret", "totalSize": "Teljes méret",
"cap": "Gyorsítótár-korlát", "cap": "Gyorsítótár-korlát",
"noCap": "nincs korlát", "noCap": "nincs korlát",
"perUser": "Felhasználónkénti tárhelyhasználat", "perUser": "Felhasználónkénti tárhelyhasználat",
"user": "Felhasználó",
"footprint": "Használat",
"editQuota": "Kvóta szerkesztése", "editQuota": "Kvóta szerkesztése",
"quotaTitle": "Letöltési kvóta — {{email}}", "quotaTitle": "Letöltési kvóta — {{email}}",
"maxBytes": "Tárhelykorlát (GB)", "maxBytes": "Tárhelykorlát (GB)",
@@ -202,20 +195,9 @@
"unlimited": "Korlátlan (tárhelykorlát kikapcsolása)", "unlimited": "Korlátlan (tárhelykorlát kikapcsolása)",
"reset": "Visszaállítás alapértékre", "reset": "Visszaállítás alapértékre",
"save": "Mentés", "save": "Mentés",
"custom": "egyedi",
"default": "alapértelmezett",
"quotaPickPlaceholder": "Kvóta beállítása egy felhasználónak…", "quotaPickPlaceholder": "Kvóta beállítása egy felhasználónak…",
"noFootprint": "Még nincs letöltés." "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", "clipBadge": "Klip",
"errors": { "errors": {
"quota": { "quota": {
-2
View File
@@ -23,8 +23,6 @@
"noMatches": "Egy videó sem felel meg ezeknek a szűrőknek.", "noMatches": "Egy videó sem felel meg ezeknek a szűrőknek.",
"channelScopeHint": "Ez a csatorna nincs a Feededben – lehet, hogy el van rejtve. A videói a közös könyvtárban vannak.", "channelScopeHint": "Ez a csatorna nincs a Feededben – lehet, hogy el van rejtve. A videói a közös könyvtárban vannak.",
"viewInLibrary": "Megnézés a Könyvtárban", "viewInLibrary": "Megnézés a Könyvtárban",
"videoCount_one": "{{formattedCount}} videó",
"videoCount_other": "{{formattedCount}} videó",
"sortLabel": "Rendezés", "sortLabel": "Rendezés",
"viewLabel": "Nézet", "viewLabel": "Nézet",
"showLabel": "Megtekintési állapot", "showLabel": "Megtekintési állapot",
-3
View File
@@ -1,13 +1,10 @@
{ {
"feed": "Feed",
"searchPlaceholder": "Keresés a feliratkozásaid között…", "searchPlaceholder": "Keresés a feliratkozásaid között…",
"clearSearch": "Keresés törlése", "clearSearch": "Keresés törlése",
"searchYoutube": "Keresés a YouTube-on", "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.", "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", "go": "Mehet",
"goTip": "Keresés (vagy Enter)", "goTip": "Keresés (vagy Enter)",
"prevModule": "Előző modul",
"nextModule": "Következő modul",
"channelManager": "Csatornakezelő", "channelManager": "Csatornakezelő",
"usageStats": "Használat és statisztika", "usageStats": "Használat és statisztika",
"scheduler": "Ütemező", "scheduler": "Ütemező",
-1
View File
@@ -2,7 +2,6 @@
"navLabel": "Értesítések", "navLabel": "Értesítések",
"title": "Értesítések", "title": "Értesítések",
"subtitle": "Frissítések a könyvtáradból és a rendszertől.", "subtitle": "Frissítések a könyvtáradból és a rendszertől.",
"tabInbox": "Értesítések",
"empty": "Nincs új értesítés.", "empty": "Nincs új értesítés.",
"markAllRead": "Összes olvasott", "markAllRead": "Összes olvasott",
"clearAll": "Összes törlése", "clearAll": "Összes törlése",
+1 -12
View File
@@ -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."
}
@@ -1,5 +1,4 @@
{ {
"tab": "Üzenetek",
"navLabel": "Üzenetek", "navLabel": "Üzenetek",
"popOut": "Megnyitás felugró ablakban", "popOut": "Megnyitás felugró ablakban",
"expand": "Kinyitás", "expand": "Kinyitás",
-3
View File
@@ -1,8 +1,5 @@
{ {
"primary": "Fő navigáció", "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": { "role": {
"admin": "Admin", "admin": "Admin",
"user": "Felhasználó", "user": "Felhasználó",
@@ -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ő", "actionNeeded": "Teendő",
"dismiss": "Elvetés", "dismiss": "Elvetés",
"findInFeed": "Keresés a Feedben", "findInFeed": "Keresés a Feedben",
"unhide": "Megjelenítés", "unhide": "Megjelenítés",
"unwatch": "Megtekintés visszavonása", "unwatch": "Megtekintés visszavonása",
"openInManager": "Csatornakezelő", "openInManager": "Csatornakezelő",
"openOnYouTube": "Megnyitás a YouTube-on", "openOnYouTube": "Megnyitás a YouTube-on"
"unhidden": "Megjelenítve: „{{title}}”",
"unwatched": "Megtekintés visszavonva: „{{title}}”"
} }
@@ -6,7 +6,6 @@
"syncYoutube": "Szinkron YouTube-ról", "syncYoutube": "Szinkron YouTube-ról",
"syncedToast": "{{count}} lista szinkronizálva a YouTube-ról", "syncedToast": "{{count}} lista szinkronizálva a YouTube-ról",
"syncFailed": "Nem sikerült a YouTube-szinkron", "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", "noneYet": "Még nincs lejátszási lista",
"newPlaylist": "Új lista…", "newPlaylist": "Új lista…",
"searchPlaceholder": "Listák keresése…", "searchPlaceholder": "Listák keresése…",
-5
View File
@@ -1,6 +1,5 @@
{ {
"navLabel": "Plex", "navLabel": "Plex",
"backToFeed": "YouTube Feed",
"backToLibrary": "Vissza a könyvtárhoz", "backToLibrary": "Vissza a könyvtárhoz",
"collection": { "collection": {
"playAll": "Összes lejátszása", "playAll": "Összes lejátszása",
@@ -113,8 +112,6 @@
"transcode_sw": "Átkódolás · SW" "transcode_sw": "Átkódolás · SW"
} }
}, },
"count": "{{count}} cím",
"searchCount": "{{count}} találat",
"noMatches": "Nincs találat.", "noMatches": "Nincs találat.",
"noMatchesFiltered_one": "Nincs találat — egy szűrő is szűkíti az eredményt.", "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.", "noMatchesFiltered_other": "Nincs találat — {{count}} szűrő is szűkíti az eredményt.",
@@ -303,10 +300,8 @@
}, },
"playlist": { "playlist": {
"section": "Lejátszási listák", "section": "Lejátszási listák",
"newPlaceholder": "Új lista…",
"pickerPlaceholder": "Lista keresése vagy létrehozása…", "pickerPlaceholder": "Lista keresése vagy létrehozása…",
"createNamed": "„{{name}}” létrehozása", "createNamed": "„{{name}}” létrehozása",
"create": "Lista létrehozása",
"none": "Még nincs lista.", "none": "Még nincs lista.",
"deleteConfirm": "Törlöd a(z) „{{title}}” listát?", "deleteConfirm": "Törlöd a(z) „{{title}}” listát?",
"rename": "Átnevezés", "rename": "Átnevezés",
+1 -20
View File
@@ -6,10 +6,6 @@
"customize": "Oldalsáv testreszabása", "customize": "Oldalsáv testreszabása",
"done": "Kész", "done": "Kész",
"editHint": "Húzd az átrendezéshez · szem a megjelenítéshez/elrejtéshez", "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", "dragToReorder": "Húzd az átrendezéshez",
"showWidget": "Modul megjelenítése", "showWidget": "Modul megjelenítése",
"hideWidget": "Modul elrejtése", "hideWidget": "Modul elrejtése",
@@ -19,8 +15,6 @@
"unpin": "Panel rögzítésének feloldása", "unpin": "Panel rögzítésének feloldása",
"any": "Bármelyik", "any": "Bármelyik",
"all": "Összes", "all": "Összes",
"tagModeTooltip": "Bármelyik vagy az összes kijelölt címke illeszkedjen",
"custom": "Egyéni",
"from": "Ettől", "from": "Ettől",
"to": "Eddig", "to": "Eddig",
"clearDates": "dátumok törlése", "clearDates": "dátumok törlése",
@@ -56,17 +50,6 @@
"saved": "Mentett", "saved": "Mentett",
"hidden": "Rejtett" "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": { "content": {
"normal": "Normál", "normal": "Normál",
"shorts": "Shorts", "shorts": "Shorts",
@@ -91,7 +74,5 @@
"untilShort": "{{date}}-ig" "untilShort": "{{date}}-ig"
}, },
"channelSearch": "Csatornák keresése…", "channelSearch": "Csatornák keresése…",
"noChannelMatch": "Nincs találat.", "noChannelMatch": "Nincs találat."
"channelOrder": "Prioritás szerint, majd A–Z",
"channelRemove": "Csatorna eltávolítása: {{name}}"
} }
-4
View File
@@ -1,12 +1,8 @@
{ {
"save": "Aktuális nézet mentése", "save": "Aktuális nézet mentése",
"namePlaceholder": "Nézet neve", "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.", "empty": "Még nincs mentett nézet. Állíts be szűrőket, majd mentsd el.",
"apply": "Nézet alkalmazása", "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", "rename": "Átnevezés",
"delete": "Törlés", "delete": "Törlés",
"deleteConfirm": "Törlöd a(z) „{{name}}” mentett nézetet?", "deleteConfirm": "Törlöd a(z) „{{name}}” mentett nézetet?",
@@ -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<string, string> = {};
const SRC = fileURLToPath(new URL("../", import.meta.url));
function flatten(obj: Record<string, unknown>, 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<string, unknown>, 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,
});
}
});
});