app guides night: R-1085 (the floor can still offer spoiled tag 0.316.3), R-1086 (qBittorrent password advice breaks the media wiring); pilot evidence and tools
gates / gates (push) Successful in 6m38s

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-15 22:37:08 +02:00
parent a3bf208242
commit bfd9b2a7ea
21 changed files with 1192 additions and 2 deletions
@@ -0,0 +1,49 @@
== taken: 2026-10-15T20:29:41Z guest 9201 (demo-hp), read-only
== docker ps (before = after: nothing installed, removed or restarted)
adventurelog ghcr.io/seanmorley15/adventurelog-backend:v0.13.0 Up 18 hours (healthy)
adventurelog-frontend ghcr.io/seanmorley15/adventurelog-frontend:v0.13.0 Up 18 hours (healthy)
adventurelog-postgres postgis/postgis:16-3.5-alpine Up 18 hours (healthy)
bentopdf ghcr.io/alam00000/bentopdf:v2.8.6 Up 18 hours (healthy)
bookstack lscr.io/linuxserver/bookstack:26.09.1 Up 18 hours (healthy)
bookstack-db mariadb:12.3 Up 18 hours (healthy)
calibre-web crocodilestick/calibre-web-automated:v4.0.8 Up 18 hours (healthy)
cloudflared cloudflare/cloudflared:2026.9.3 Up 29 hours (healthy)
docmost docmost/docmost:0.96.0 Up 18 hours (healthy)
docmost-postgres postgres:18-alpine Up 18 hours (healthy)
docmost-redis redis:7-alpine Up 18 hours (healthy)
felhom-controller gitea.dooplex.hu/admin/felhom-controller:0.317.0 Up 56 minutes (healthy)
filebrowser gtstef/filebrowser:1.5.6-stable Up 6 hours (healthy)
home-assistant ghcr.io/home-assistant/home-assistant:2026.9.4 Up 18 hours (healthy)
opengist ghcr.io/thomiceli/opengist:1.15 Up 18 hours (healthy)
paperless-postgres postgres:18-alpine Up 18 hours (healthy)
paperless-redis redis:7-alpine Up 18 hours (healthy)
paperless-webserver ghcr.io/paperless-ngx/paperless-ngx:2.20.15 Up 18 hours (healthy)
plex plexinc/pms-docker:1.41.4.9463-630c9f557 Up 12 hours (healthy)
privatebin privatebin/pdo:2.0.6 Up 18 hours (healthy)
traefik traefik:v3.7.13 Up 18 hours
== plex image
plexinc/pms-docker:1.41.4.9463-630c9f557
== plex mounts (FAQ: Plex only READS the drive)
/config rw=true
/media rw=false
/transcode rw=true
/mnt/felhom-drives/hdd_1/userdata/media rw=false
== plex env KEYS (FAQ: the box set the public address) - names only
ADVERTISE_IP
TZ
PLEX_CLAIM
== /identity (claimed flag)
claimed="1"
== controller log: media wiring lines for plex (positive observable: the box added folders / found nothing to add)
== VERDICT (read-only box check, Part A.3 under night rule 2)
- „Plex only reads the drive" (FAQ 2): PASS — /media and the per-drive media bind are rw=false.
- „The box set Plex up to answer on your own address" (FAQ 5): PASS — ADVERTISE_IP is in the container env (value not printed).
- Claimed state the guide assumes (sections 1 and 4, FAQ 6): PASS — /identity claimed="1".
- „The box adds a new drive's folders to your existing libraries and starts a scan" (section 2): NOT RE-READ TONIGHT.
The wiring runs on a media app's start; nothing started since the controller came up at 19:33 UTC, so the log holds
no wiring line, and an absent line is not evidence either way. Reading the libraries needs Plex's own token, which a
read-only night check does not take. The claim rests on the media build's live proof (G2 on Tester 1: a drive wired into every app, Plex included; G7 on this box: the live catalog)
— felhom.eu documentation/audits/media-build-2026-10-15/G and G7 — and on felhom-controller mediawire.go plex().
- Nothing installed, removed, restarted or changed (docker ps above; no POST sent).
@@ -0,0 +1,136 @@
{
"app": "plex",
"checked_version": "1.41.4.9463-630c9f557",
"hu": {
"sections": [
{
"title": "Nézés tévén vagy telefonon",
"intro": "A Plex appok a Plex fiókodon keresztül találják meg a szerveredet.",
"steps": [
"Nyisd meg a Plex appot, és jelentkezz be ugyanazzal a Plex fiókkal, amellyel a kódot kérted.",
"A „Kezdőképernyő” oldalon megjelennek a könyvtáraid.",
"Válassz egy filmet, és kattints a „Lejátszás” gombra."
]
},
{
"title": "Új filmek és új meghajtó",
"intro": "Amit a meghajtód media mappájába teszel, azt a Plex magától észreveszi.",
"steps": [
"A Radarr a media/movies, a Sonarr a media/tv mappába tesz mindent, a Plex innen olvas.",
"Új meghajtónál a doboz hozzáadja a meghajtó mappáit a meglévő film- és sorozatkönyvtáraidhoz, és elindít egy beolvasást.",
"Ha egy új fájl mégsem jelenik meg, a könyvtár menüjében kattints a „Könyvtárfájlok beolvasása” pontra."
]
},
{
"title": "Rossz cím vagy borító javítása",
"intro": "A Plex a fájl és a mappa nevéből találja ki, melyik film ez.",
"steps": [
"Nyisd meg a filmet, és kattints a „Több” menüre.",
"Válaszd a „Találatok pontosítása” pontot, és keresd meg a helyes címet.",
"Legközelebb segít, ha a mappa neve a film címe és éve, például: Nosferatu (1922)."
]
},
{
"title": "Megosztás a családdal",
"intro": "Minden családtag a saját Plex fiókjával nézhet.",
"steps": [
"A „Beállítások” oldalon nyisd meg a „Könyvtár hozzáférés kezelése” részt.",
"Válaszd ki, kivel és mely könyvtárakat osztod meg.",
"A családtag a saját Plex fiókjával lép be az appba."
]
}
],
"faq": [
{
"q": "Miért nem látom a filmet?",
"a": "Nézd meg, hogy a fájl a media/movies mappában van-e, és hogy a mappa neve a film címe és éve. Utána kattints a „Könyvtárfájlok beolvasása” pontra."
},
{
"q": "Törölhetek filmet a Plexből?",
"a": "Nem: a Plex csak olvassa a meghajtót. Törölni a fájlböngészőben, a Radarrban vagy a Sonarrban tudsz."
},
{
"q": "Kell Plex Pass?",
"a": "Otthoni nézéshez nem. Néhány funkcióhoz igen, például a bevezető átugrásához; ezt a Plex dönti el, nem a doboz."
},
{
"q": "Akadozik a lejátszás. Mit tehetek?",
"a": "A Plex ilyenkor átalakítja a videót, és ez a dobozon lassú lehet. Az appban a „Videó minőség” beállításnál válaszd az „Eredeti” értéket."
},
{
"q": "Elérem otthonon kívül?",
"a": "A doboz beállította, hogy a Plex a saját címeden is elérhető legyen. Otthonon kívüli nézéshez a Plex előfizetést kérhet; ezt a Plex szabja meg."
},
{
"q": "Lejárt a kód. Hol kérek újat?",
"a": "Ugyanott, ahol az elsőt: a címet az első lépések között találod. Az új kódot a vezérlőpulton, a Plex „Beállítások” fülén add meg."
}
]
},
"en": {
"sections": [
{
"title": "Watch on a TV or a phone",
"intro": "The Plex apps find your server through your Plex account.",
"steps": [
"Open the Plex app and sign in with the same Plex account you got the code with.",
"Your libraries appear on the „Home” page.",
"Choose a film and select „Play”."
]
},
{
"title": "New films and a new drive",
"intro": "Whatever you put into your drive's media folder, Plex notices by itself.",
"steps": [
"Radarr puts everything into media/movies and Sonarr into media/tv; Plex reads from there.",
"With a new drive, the box adds its folders to your existing film and series libraries and starts a scan.",
"If a new file still does not show up, select „Scan Library Files” in the library's menu."
]
},
{
"title": "Fix a wrong title or cover",
"intro": "Plex works out which film it is from the names of the file and the folder.",
"steps": [
"Open the film and select the „More” menu.",
"Choose „Fix Match” and search for the right title.",
"Next time it helps to name the folder after the film's title and year, for example: Nosferatu (1922)."
]
},
{
"title": "Share with the family",
"intro": "Every family member can watch with their own Plex account.",
"steps": [
"On the „Settings” page, open „Manage Library Access”.",
"Choose who gets which libraries.",
"The family member signs in to the app with their own Plex account."
]
}
],
"faq": [
{
"q": "Why can I not see the film?",
"a": "Check that the file is in the media/movies folder and that the folder is named after the film's title and year. Then select „Scan Library Files”."
},
{
"q": "Can I delete a film from Plex?",
"a": "No: Plex only reads the drive. You delete in the file browser, in Radarr or in Sonarr."
},
{
"q": "Do I need Plex Pass?",
"a": "Not for watching at home. Some features need it, for example skipping intros; Plex decides that, not the box."
},
{
"q": "Playback stutters. What can I do?",
"a": "Plex is converting the video, and that can be slow on the box. In the app, set „Video Quality” to „Original”."
},
{
"q": "Can I reach it away from home?",
"a": "The box set Plex up to answer on your own address. To watch away from home, Plex may ask for a subscription; Plex sets that rule."
},
{
"q": "The code ran out. Where do I get a new one?",
"a": "In the same place as the first one: its address is in the first steps. Enter the new code on the dashboard, on Plex's „Settings” tab."
}
]
}
}
@@ -0,0 +1,22 @@
app: plex checked against: 1.41.4.9463-630c9f557
corpora: plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json felhom-controller@123d2d5:controller/internal/i18n/locales/hu.json felhom-controller@123d2d5:controller/internal/i18n/locales/en.json
labels: 18 (Hungarian and English guide strings, every „…” occurrence)
OK [hu] „Kezdőképernyő” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:920
OK [hu] „Lejátszás” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1407
OK [hu] „Könyvtárfájlok beolvasása” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1693
OK [hu] „Több” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1172
OK [hu] „Találatok pontosítása” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:817
OK [hu] „Beállítások” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1355
OK [hu] „Könyvtár hozzáférés kezelése” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1111
OK [hu] „Videó minőség” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:2360
OK [hu] „Eredeti” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1359
OK [en] „Home” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:920
OK [en] „Play” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1407
OK [en] „Scan Library Files” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1693
OK [en] „More” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1172
OK [en] „Fix Match” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:817
OK [en] „Settings” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1774
OK [en] „Manage Library Access” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1111
OK [en] „Video Quality” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:2360
OK [en] „Original” plexinc/pms-docker:1.41.4.9463-630c9f557 image, Plex Web 4.143.0 (WebClient.bundle/Contents/Resources, copied from demo-hp 9201's running container):translations/hu.json:1359
RESULT: ALL FOUND — 18 label(s), 0 missing
@@ -0,0 +1,2 @@
BOX CHECK OWED — prowlarr was not running on demo-hp 9201 at 2026-10-15T20:29Z (docker ps in plex-box.txt), and night rule 2
allows no install there tonight. Label check only (§8 row „all found / not run"); checked_version set.
@@ -0,0 +1,92 @@
{
"app": "prowlarr",
"checked_version": "version-2.6.5.5623",
"hu": {
"sections": [
{"title": "Első forrás hozzáadása",
"intro": "A forrásokat (indexereket) csak itt adod meg, egyszer.",
"steps": [
"Az „Indexers” (Indexerek) oldalon kattints az „Add Indexer” (indexer hozzáadása) gombra.",
"Keresd meg a listában azt a forrást, amelyhez hozzáférésed van, és kattints rá.",
"Töltsd ki, amit a forrás kér, például a saját felhasználónevedet vagy kulcsodat.",
"Kattints a „Test” (próba) gombra, és ha sikeres, a „Save” (mentés) gombra."
]},
{"title": "A Sonarr és a Radarr megkapja",
"intro": "Ezt a doboz bekötötte, neked nincs vele teendőd.",
"steps": [
"A „Settings” (Beállítások) menü „Apps” (alkalmazások) oldalán látod a Sonarrt és a Radarrt.",
"Egy új forrás magától átkerül hozzájuk, mert a kapcsolat „Full Sync” (teljes szinkron) módban van.",
"Ha mégsem jelenik meg, az „Indexers” oldalon kattints a „Sync App Indexers” (indexerek átküldése) gombra."
]},
{"title": "Egy forrás kipróbálása",
"intro": "Letöltés nélkül is megnézheted, ad-e találatot egy forrás.",
"steps": [
"A „Search” (Keresés) oldalon írj be egy címet, és nézd meg a találatokat.",
"Az „Indexers” oldalon a „Test All Indexers” (összes kipróbálása) gomb minden forrást egyszerre ellenőriz."
]},
{"title": "Ha egy forrás hibás",
"intro": "A Prowlarr egy ideig kihagyja azt a forrást, amelyik többször hibázott, majd újra próbálja.",
"steps": [
"Nézd meg a „System” (Rendszer) menü „Status” (Állapot) lapját: a „Health” (állapot) rész sorolja a hibás forrásokat.",
"A „History” (Előzmények) oldalon látod, melyik keresés mit hozott.",
"Ha egy forrás véglegesen elromlott, nyisd meg az „Indexers” oldalon, és javítsd vagy töröld."
]}
],
"faq": [
{"q": "Melyik forrást válasszam?",
"a": "Ezt te döntöd el. A Felhom nem ad, nem ajánl és nem keres forrást. Csak olyat használj, amelyhez jogod van."},
{"q": "Kell a Sonarrban vagy a Radarrban is felvennem a forrásokat?",
"a": "Nem. A Prowlarr átküldi nekik, és ha itt módosítasz egy forrást, ott is módosul."},
{"q": "Mi a különbség a Prowlarr és a Sonarr között?",
"a": "A Prowlarr csak a forrásokat tartja nyilván. A Sonarr és a Radarr dönti el, mit keres és mit tölt le."},
{"q": "Mi történik, ha a Sonarrt később telepítem?",
"a": "A doboz akkor is összeköti őket: amikor a Sonarr vagy a Radarr elindul, megkapja a forrásokat, gombnyomás nélkül."},
{"q": "Mit mutat a „Stats” oldal?",
"a": "Forrásonként mutatja, hány keresésre és hány találatra jutott, és mennyi idő alatt válaszolt. Így látod, melyik forrás lassú."}
]
},
"en": {
"sections": [
{"title": "Add your first source",
"intro": "You add your sources (indexers) here only, and only once.",
"steps": [
"On the „Indexers” page, select „Add Indexer”.",
"Find the source you have access to in the list and select it.",
"Fill in what the source asks for, for example your own user name or key.",
"Select „Test”, and if it succeeds, select „Save”."
]},
{"title": "Sonarr and Radarr get them",
"intro": "The box connected this; there is nothing for you to do.",
"steps": [
"The „Apps” page of the „Settings” menu shows Sonarr and Radarr.",
"A new source reaches them by itself, because the link is set to „Full Sync”.",
"If one does not show up, select „Sync App Indexers” on the „Indexers” page."
]},
{"title": "Try a source",
"intro": "You can see whether a source finds anything without downloading.",
"steps": [
"On the „Search” page, type a title and look at the results.",
"On the „Indexers” page, „Test All Indexers” checks every source at once."
]},
{"title": "When a source fails",
"intro": "Prowlarr skips a source that failed several times for a while, then tries it again.",
"steps": [
"Look at the „Status” tab of the „System” menu: the „Health” part lists the failing sources.",
"The „History” page shows what each search brought back.",
"If a source is broken for good, open it on the „Indexers” page and fix it or delete it."
]}
],
"faq": [
{"q": "Which source should I choose?",
"a": "That is your decision. Felhom neither provides, suggests nor searches for a source. Use only one you have the right to use."},
{"q": "Do I also have to add the sources in Sonarr or Radarr?",
"a": "No. Prowlarr sends them over, and when you change a source here, it changes there too."},
{"q": "What is the difference between Prowlarr and Sonarr?",
"a": "Prowlarr only keeps the list of sources. Sonarr and Radarr decide what to search for and what to download."},
{"q": "What happens if I install Sonarr later?",
"a": "The box still connects them: when Sonarr or Radarr starts, it gets the sources, with no button to press."},
{"q": "What does the „Stats” page show?",
"a": "For each source: how many searches and results it had, and how long it took to answer. That shows you which source is slow."}
]
}
}
@@ -0,0 +1,34 @@
app: prowlarr checked against: version-2.6.5.5623
corpora: Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json Prowlarr/Prowlarr@v2.6.5.5623(12c3278):frontend/src felhom-controller@123d2d5:controller/internal/i18n/locales/hu.json felhom-controller@123d2d5:controller/internal/i18n/locales/en.json
labels: 30 (Hungarian and English guide strings, every „…” occurrence)
OK [hu] „Indexers” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:456
OK [hu] „Add Indexer” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:17
OK [hu] „Test” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:731
OK [hu] „Save” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:653
OK [hu] „Settings” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:682
OK [hu] „Apps” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:75
OK [hu] „Full Sync” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:306
OK [hu] „Sync App Indexers” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:714
OK [hu] „Search” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:659
OK [hu] „Test All Indexers” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:735
OK [hu] „System” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:720
OK [hu] „Status” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:710
OK [hu] „Health” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:317
OK [hu] „History” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:320
OK [hu] „Stats” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:709
OK [en] „Indexers” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:456
OK [en] „Add Indexer” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:17
OK [en] „Test” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:731
OK [en] „Save” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:653
OK [en] „Apps” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:75
OK [en] „Settings” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:682
OK [en] „Full Sync” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:306
OK [en] „Sync App Indexers” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:714
OK [en] „Search” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:659
OK [en] „Test All Indexers” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:735
OK [en] „Status” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:710
OK [en] „System” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:720
OK [en] „Health” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:317
OK [en] „History” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:320
OK [en] „Stats” Prowlarr/Prowlarr@v2.6.5.5623(12c3278):src/NzbDrone.Core/Localization/Core/en.json:709
RESULT: ALL FOUND — 30 label(s), 0 missing
@@ -0,0 +1,2 @@
BOX CHECK OWED — qbittorrent was not running on demo-hp 9201 at 2026-10-15T20:29Z (docker ps in plex-box.txt), and night rule 2
allows no install there tonight. Label check only (§8 row „all found / not run"); checked_version set.
@@ -0,0 +1,94 @@
{
"app": "qbittorrent",
"checked_version": "5.2.4-1",
"hu": {
"sections": [
{"title": "Torrent hozzáadása",
"intro": "Csak olyan torrentet adj hozzá, amelynek a tartalmához jogod van, például egy Linux telepítőét a kiadó saját oldaláról.",
"steps": [
"Kattints a felső sávban az „Add Torrent Link...” (torrent hozzáadása linkkel) gombra.",
"Illeszd be a torrent vagy a magnet linkjét, és kattints a „Download” (Letöltés) gombra.",
"Ha torrentfájlod van, használd az „Add Torrent File...” (torrentfájl hozzáadása) gombot."
]},
{"title": "Leállítás és törlés",
"intro": "Egy torrentet bármikor megállíthatsz vagy eltávolíthatsz.",
"steps": [
"Jelöld ki a torrentet, és kattints a „Stop” (leállítás) gombra. A „Start” (indítás) folytatja.",
"Eltávolításhoz kattints a „Remove” (eltávolítás) gombra.",
"Ha a letöltött fájlokat is törölnéd, jelöld be az „Also remove the content files” (a fájlok törlése is) jelölőt."
]},
{"title": "Lassabb letöltés napközben",
"intro": "Beállíthatod, hogy napközben kevésbé terhelje a netedet.",
"steps": [
"Kattints az „Options” (beállítások) gombra, majd a „Speed” (sebesség) lapra.",
"Az „Alternative Rate Limits” (másodlagos korlát) részben adj meg alacsonyabb értékeket.",
"Jelöld be a „Schedule the use of alternative rate limits” (ütemezés) jelölőt, és add meg, mikor legyen érvényes.",
"Mentsd el a beállításokat a lap alján."
]},
{"title": "Meddig ossza tovább?",
"intro": "A kész letöltést a qBittorrent tovább küldi másoknak. Ezt korlátozhatod.",
"steps": [
"Az „Options” (beállítások) „BitTorrent” lapján keresd a „Seeding Limits” (megosztási korlát) részt.",
"A „When ratio reaches” (ha az arány eléri) jelölővel megadhatod, hányszoros feltöltés után álljon le.",
"Mentsd el a beállításokat a lap alján."
]}
],
"faq": [
{"q": "Hová kerülnek a letöltések?",
"a": "A meghajtódra, a media/torrents mappába. A fájlböngészőben is látod őket."},
{"q": "Be kell állítanom a Sonarr és a Radarr kategóriáit?",
"a": "Nem. A doboz minden meghajtóhoz létrehozta a tv- és a movies- kezdetű kategóriákat, és bekapcsolta az automatikus kezelést. Ezeket ne töröld és ne nevezd át."},
{"q": "Ha eltávolítom a torrentet, eltűnik a film a könyvtárból is?",
"a": "Nem. A Sonarr és a Radarr ugyanazt a fájlt egy második néven tette a könyvtárba, ez a torrent eltávolítása után is megmarad."},
{"q": "Mit jelent a „Seeding” állapot?",
"a": "A letöltés kész, és a qBittorrent most másoknak küldi tovább. Ezt a „Stop” gombbal leállíthatod."},
{"q": "Megváltoztathatom, hová mentsen?",
"a": "Ne tedd. A qBittorrent csak a media/torrents mappát látja a meghajtódon, máshová mentett fájl nem a meghajtóra kerül, és a Sonarr meg a Radarr sem találja."}
]
},
"en": {
"sections": [
{"title": "Add a torrent",
"intro": "Add only a torrent whose content you have the right to, for example a Linux installer from its publisher's own site.",
"steps": [
"In the bar at the top, select „Add Torrent Link...”.",
"Paste the torrent or magnet link and select „Download”.",
"If you have a torrent file, use „Add Torrent File...”."
]},
{"title": "Stop and remove",
"intro": "You can stop or remove a torrent at any time.",
"steps": [
"Select the torrent and select „Stop”. „Start” carries on.",
"To remove it, select „Remove”.",
"To delete the downloaded files as well, tick „Also remove the content files”."
]},
{"title": "Slower downloads by day",
"intro": "You can make it use less of your internet line during the day.",
"steps": [
"Select „Options”, then the „Speed” tab.",
"Under „Alternative Rate Limits”, enter lower values.",
"Tick „Schedule the use of alternative rate limits” and set when it applies.",
"Save the settings at the bottom of the page."
]},
{"title": "How long does it share?",
"intro": "qBittorrent keeps sending a finished download to others. You can limit that.",
"steps": [
"On the „BitTorrent” tab of „Options”, find „Seeding Limits”.",
"With „When ratio reaches”, set after how many times the size it stops uploading.",
"Save the settings at the bottom of the page."
]}
],
"faq": [
{"q": "Where do the downloads go?",
"a": "To your drive, into the media/torrents folder. You see them in the file browser too."},
{"q": "Do I have to set up the categories for Sonarr and Radarr?",
"a": "No. For every drive, the box created the categories that start with tv- and movies-, and switched on automatic management. Do not delete or rename them."},
{"q": "If I remove the torrent, does the film leave the library too?",
"a": "No. Sonarr and Radarr put the same file into the library under a second name, and that stays after the torrent is removed."},
{"q": "What does „Seeding” mean?",
"a": "The download is finished and qBittorrent is now sending it on to others. „Stop” ends that."},
{"q": "Can I change where it saves?",
"a": "Do not. qBittorrent sees only the media/torrents folder on your drive; a file saved anywhere else is not on the drive, and Sonarr and Radarr cannot find it."}
]
}
}
@@ -0,0 +1,34 @@
app: qbittorrent checked against: 5.2.4-1
corpora: qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/public felhom-controller@123d2d5:controller/internal/i18n/locales/hu.json felhom-controller@123d2d5:controller/internal/i18n/locales/en.json
labels: 30 (Hungarian and English guide strings, every „…” occurrence)
OK [hu] „Add Torrent Link...” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/index.html:70
OK [hu] „Download” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/addtorrent.html:401
OK [hu] „Add Torrent File...” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/index.html:64
OK [hu] „Stop” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/shareratio.html:201
OK [hu] „Start” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/index.html:78
OK [hu] „Remove” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/shareratio.html:202
OK [hu] „Also remove the content files” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/confirmdeletion.html:11
OK [hu] „Options” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/index.html:140
OK [hu] „Speed” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferencesToolbar.html:14
OK [hu] „Alternative Rate Limits” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferences.html:643
OK [hu] „Schedule the use of alternative rate limits” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferences.html:664
OK [hu] „BitTorrent” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferencesToolbar.html:17
OK [hu] „Seeding Limits” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferences.html:811
OK [hu] „When ratio reaches” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferences.html:817
OK [hu] „Seeding” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/filters.html:8
OK [en] „Add Torrent Link...” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/index.html:70
OK [en] „Download” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/addtorrent.html:401
OK [en] „Add Torrent File...” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/index.html:64
OK [en] „Stop” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/shareratio.html:201
OK [en] „Start” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/index.html:78
OK [en] „Remove” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/shareratio.html:202
OK [en] „Also remove the content files” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/confirmdeletion.html:11
OK [en] „Options” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/index.html:140
OK [en] „Speed” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferencesToolbar.html:14
OK [en] „Alternative Rate Limits” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferences.html:643
OK [en] „Schedule the use of alternative rate limits” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferences.html:664
OK [en] „BitTorrent” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferencesToolbar.html:17
OK [en] „Seeding Limits” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferences.html:811
OK [en] „When ratio reaches” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/preferences.html:817
OK [en] „Seeding” qbittorrent/qBittorrent@release-5.2.4(a1e4649):src/webui/www/private/views/filters.html:8
RESULT: ALL FOUND — 30 label(s), 0 missing
@@ -0,0 +1,2 @@
BOX CHECK OWED — radarr was not running on demo-hp 9201 at 2026-10-15T20:29Z (docker ps in plex-box.txt), and night rule 2
allows no install there tonight. Label check only (§8 row „all found / not run"); checked_version set.
@@ -0,0 +1,100 @@
{
"app": "radarr",
"checked_version": "6.4.4",
"hu": {
"sections": [
{"title": "Első film hozzáadása",
"intro": "A Radarr megkeresi és letölti a filmet, amint elérhető.",
"steps": [
"A bal oldali menüben a „Movies” (Filmek) alatt kattints az „Add New” (Új hozzáadása) pontra.",
"Írd be a film címét, és kattints a megfelelő találatra.",
"A „Root Folder” (gyökérmappa) mezőben válaszd annak a meghajtónak a media/movies mappáját, ahová a filmet szeretnéd.",
"A „Tags” (címkék) mezőbe írd ugyanannak a meghajtónak a címkéjét, például hdd-1. Címke nélkül a film nem kap letöltőt.",
"Hagyd bejelölve a „Start search for missing movie” (film keresése) jelölőt, és kattints az „Add Movie” (Film hozzáadása) gombra."
]},
{"title": "Mikor keresi a filmet?",
"intro": "A Radarr csak akkor keres, amikor a film már elérhető lehet.",
"steps": [
"A film hozzáadásakor a „Minimum Availability” (legkorábbi elérhetőség) mezőben állítod, mikor kezdjen keresni.",
"Az „Announced” (bejelentve) és az „In Cinemas” (moziban) korán keres, gyakran eredmény nélkül.",
"A „Released” (megjelent) a legbiztosabb: akkor keres, amikor a filmet már kiadták otthoni nézésre."
]},
{"title": "A letöltés követése",
"intro": "A letöltés a háttérben fut, az oldalt be is zárhatod.",
"steps": [
"Az „Activity” (Tevékenység) menü „Queue” (Sor) lapján látod, mi töltődik éppen.",
"Ha a film letöltődött, a Radarr magától átteszi a film mappájába. A Plex és a Jellyfin innen látja.",
"A „Calendar” (Naptár) mutatja, mikor jelenik meg egy figyelt film."
]},
{"title": "Ha valami nem működik",
"intro": "A Radarr maga jelzi, ha valami hibás.",
"steps": [
"Nézd meg a „System” (Rendszer) menü „Status” (Állapot) lapját: a „Health” (állapot) rész sorolja a hibákat.",
"A „Settings” (Beállítások) menü „Download Clients” (letöltő kliensek) oldalán látod a letöltőket, amelyeket a doboz bekötött.",
"Ha nincs találat, nézd meg a Prowlarr „Indexers” (indexerek) oldalát: legalább egy forrás kell."
]}
],
"faq": [
{"q": "Honnan tölt le a Radarr?",
"a": "Azokról a forrásokról (indexerekről), amelyeket te adsz meg a Prowlarrban. A doboz összeköti a kettőt, de forrást nem ad és nem ajánl: ez a te döntésed."},
{"q": "Be kell állítanom a letöltő klienst és a mappákat?",
"a": "Nem. A doboz minden meghajtódhoz bekötötte a telepített letöltőket (qBittorrent, Transmission), a gyökérmappát és a címkét. Neked csak a filmet kell hozzáadnod."},
{"q": "Miért van ugyanaz a fájl a torrents és a movies mappában is?",
"a": "Nem foglal dupla helyet: ugyanaz a fájl két néven (hardlink). A letöltő tovább oszthatja, a könyvtárad pedig rendezett marad."},
{"q": "Letöltötte, de nem került a könyvtárba. Miért?",
"a": "Ha a meghajtón kevesebb mint 10 GB maradna, a Radarr nem importál. Ezt a doboz állította be: „Settings”, „Media Management”, „Minimum Free Space” (legkevesebb szabad hely). Szabadíts fel helyet, és a következő ellenőrzéskor folytatja."},
{"q": "Ajánl filmeket?",
"a": "Igen: a „Discover” (Felfedezés) oldal a meglévő filmjeid alapján mutat ötleteket, és onnan egy kattintással hozzáadhatod őket."},
{"q": "Mit tölthetek le?",
"a": "Csak olyan tartalmat, amihez jogod van, például közkincs (public domain) filmeket."}
]
},
"en": {
"sections": [
{"title": "Add your first film",
"intro": "Radarr finds and downloads a film as soon as it is available.",
"steps": [
"In the menu on the left, under „Movies”, select „Add New”.",
"Type the title of the film and select the right result.",
"In „Root Folder”, choose the media/movies folder of the drive you want the film on.",
"In „Tags”, type the tag of that same drive, for example hdd-1. Without a tag the film gets no download client.",
"Leave „Start search for missing movie” ticked and select „Add Movie”."
]},
{"title": "When does it search?",
"intro": "Radarr searches only once a film may be available.",
"steps": [
"When you add a film, „Minimum Availability” sets when the search starts.",
"„Announced” and „In Cinemas” search early, and often find nothing.",
"„Released” is the safest: it searches once the film is out for home viewing."
]},
{"title": "Follow a download",
"intro": "Downloads run in the background, so you can close the page.",
"steps": [
"The „Queue” tab of the „Activity” menu shows what is downloading now.",
"When a film is done, Radarr moves it into the film's folder by itself. Plex and Jellyfin see it there.",
"The „Calendar” shows when a film you watch for comes out."
]},
{"title": "When something does not work",
"intro": "Radarr tells you itself when something is wrong.",
"steps": [
"Look at the „Status” tab of the „System” menu: the „Health” part lists the problems.",
"The „Download Clients” page of the „Settings” menu shows the download clients the box connected.",
"If nothing is found, look at the „Indexers” page in Prowlarr: you need at least one source."
]}
],
"faq": [
{"q": "Where does Radarr download from?",
"a": "From the sources (indexers) you add in Prowlarr. The box connects the two, but it neither provides nor suggests a source: that is your choice."},
{"q": "Do I have to set up the download client and the folders?",
"a": "No. For every drive, the box connected the installed download clients (qBittorrent, Transmission), the root folder and the tag. You only add the film."},
{"q": "Why is the same file in both the torrents and the movies folder?",
"a": "It does not take double space: it is one file with two names (a hardlink). The download client can keep sharing it, and your library stays tidy."},
{"q": "It downloaded, but it is not in the library. Why?",
"a": "Radarr does not import if less than 10 GB would be left on the drive. The box set this: „Settings”, „Media Management”, „Minimum Free Space”. Free up some space and it goes on at the next check."},
{"q": "Does it suggest films?",
"a": "Yes: the „Discover” page shows ideas based on the films you have, and you can add one from there with a click."},
{"q": "What may I download?",
"a": "Only content you have the right to, for example films in the public domain."}
]
}
}
@@ -0,0 +1,48 @@
app: radarr checked against: 6.4.4
corpora: Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json Radarr/Radarr@v6.4.4.10685(a96bf7c):frontend/src felhom-controller@123d2d5:controller/internal/i18n/locales/hu.json felhom-controller@123d2d5:controller/internal/i18n/locales/en.json
labels: 44 (Hungarian and English guide strings, every „…” occurrence)
OK [hu] „Movies” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1188
OK [hu] „Add New” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:35
OK [hu] „Root Folder” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:132
OK [hu] „Tags” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1902
OK [hu] „Start search for missing movie” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1868
OK [hu] „Add Movie” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:33
OK [hu] „Minimum Availability” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1092
OK [hu] „Announced” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:78
OK [hu] „In Cinemas” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:867
OK [hu] „Released” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1613
OK [hu] „Activity” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:4
OK [hu] „Queue” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1559
OK [hu] „Calendar” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:181
OK [hu] „System” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1886
OK [hu] „Status” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:133
OK [hu] „Health” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:748
OK [hu] „Settings” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1786
OK [hu] „Download Clients” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:570
OK [hu] „Indexers” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:942
OK [hu] „Media Management” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1062
OK [hu] „Minimum Free Space” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1097
OK [hu] „Discover” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:410
OK [en] „Movies” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1188
OK [en] „Add New” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:35
OK [en] „Root Folder” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:132
OK [en] „Tags” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1902
OK [en] „Start search for missing movie” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1868
OK [en] „Add Movie” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:33
OK [en] „Minimum Availability” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1092
OK [en] „Announced” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:78
OK [en] „In Cinemas” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:867
OK [en] „Released” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1613
OK [en] „Queue” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1559
OK [en] „Activity” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:4
OK [en] „Calendar” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:181
OK [en] „Status” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:133
OK [en] „System” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1886
OK [en] „Health” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:748
OK [en] „Download Clients” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:570
OK [en] „Settings” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1786
OK [en] „Indexers” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:942
OK [en] „Media Management” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1062
OK [en] „Minimum Free Space” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:1097
OK [en] „Discover” Radarr/Radarr@v6.4.4.10685(a96bf7c):src/NzbDrone.Core/Localization/Core/en.json:410
RESULT: ALL FOUND — 44 label(s), 0 missing
@@ -0,0 +1,2 @@
BOX CHECK OWED — sonarr was not running on demo-hp 9201 at 2026-10-15T20:29Z (docker ps in plex-box.txt), and night rule 2
allows no install there tonight. Label check only (§8 row „all found / not run"); checked_version set.
@@ -0,0 +1,100 @@
{
"app": "sonarr",
"checked_version": "4.0.20",
"hu": {
"sections": [
{"title": "Első sorozat hozzáadása",
"intro": "A Sonarr figyeli a sorozatot, és letölti az új részeket, amint megjelennek.",
"steps": [
"A bal oldali menüben a „Series” (Sorozatok) alatt kattints az „Add New” (Új hozzáadása) pontra.",
"Írd be a sorozat címét, és kattints a megfelelő találatra.",
"A „Root Folder” (gyökérmappa) mezőben válaszd annak a meghajtónak a media/tv mappáját, ahová a sorozatot szeretnéd.",
"A „Tags” (címkék) mezőbe írd ugyanannak a meghajtónak a címkéjét, például hdd-1. Címke nélkül a sorozat nem kap letöltőt.",
"Hagyd bejelölve a „Start search for missing episodes” (hiányzó részek keresése) jelölőt, és add hozzá a sorozatot."
]},
{"title": "A letöltés követése",
"intro": "A letöltés a háttérben fut, az oldalt be is zárhatod.",
"steps": [
"Az „Activity” (Tevékenység) menü „Queue” (Sor) lapján látod, mi töltődik éppen.",
"Ha egy rész letöltődött, a Sonarr magától átteszi a sorozat mappájába. A Plex és a Jellyfin innen látja.",
"A „Calendar” (Naptár) mutatja, mikor jön a következő rész."
]},
{"title": "Hiányzó részek keresése",
"intro": "Ha egy régebbi évad hiányzik, egyszerre kerestetheted.",
"steps": [
"Nyisd meg a „Wanted” (Keresett) menü „Missing” (Hiányzó) lapját.",
"Jelöld be a kívánt részeket, és kattints a „Search Selected” (kijelöltek keresése) gombra.",
"Ha mindet keresnéd, használd a „Search All” (összes keresése) gombot."
]},
{"title": "Ha valami nem működik",
"intro": "A Sonarr maga jelzi, ha valami hibás.",
"steps": [
"Nézd meg a „System” (Rendszer) menü „Status” (Állapot) lapját: a „Health” (állapot) rész sorolja a hibákat.",
"A „Settings” (Beállítások) menü „Download Clients” (letöltő kliensek) oldalán látod a letöltőket, amelyeket a doboz bekötött.",
"Ha nincs találat, nézd meg a Prowlarr „Indexers” (indexerek) oldalát: legalább egy forrás kell."
]}
],
"faq": [
{"q": "Honnan tölt le a Sonarr?",
"a": "Azokról a forrásokról (indexerekről), amelyeket te adsz meg a Prowlarrban. A doboz összeköti a kettőt, de forrást nem ad és nem ajánl: ez a te döntésed."},
{"q": "Be kell állítanom a letöltő klienst és a mappákat?",
"a": "Nem. A doboz minden meghajtódhoz bekötötte a telepített letöltőket (qBittorrent, Transmission), a gyökérmappát és a címkét. Neked csak a sorozatot kell hozzáadnod."},
{"q": "Miért van ugyanaz a fájl a torrents és a tv mappában is?",
"a": "Nem foglal dupla helyet: ugyanaz a fájl két néven (hardlink). A letöltő tovább oszthatja, a könyvtárad pedig rendezett marad."},
{"q": "Letöltötte, de nem került a könyvtárba. Miért?",
"a": "Ha a meghajtón kevesebb mint 10 GB maradna, a Sonarr nem importál. Ezt a doboz állította be: „Settings”, „Media Management”, „Minimum Free Space” (legkevesebb szabad hely). Szabadíts fel helyet, és a következő ellenőrzéskor folytatja."},
{"q": "Hol nézhetem meg a sorozatokat?",
"a": "A Plexben vagy a Jellyfinben. A Sonarr csak letölt és rendez, lejátszani nem tud."},
{"q": "Mit tölthetek le?",
"a": "Csak olyan tartalmat, amihez jogod van, például szabad licencű műsorokat."}
]
},
"en": {
"sections": [
{"title": "Add your first series",
"intro": "Sonarr watches a series and downloads each new episode as it comes out.",
"steps": [
"In the menu on the left, under „Series”, select „Add New”.",
"Type the title of the series and select the right result.",
"In „Root Folder”, choose the media/tv folder of the drive you want the series on.",
"In „Tags”, type the tag of that same drive, for example hdd-1. Without a tag the series gets no download client.",
"Leave „Start search for missing episodes” ticked and add the series."
]},
{"title": "Follow a download",
"intro": "Downloads run in the background, so you can close the page.",
"steps": [
"The „Queue” tab of the „Activity” menu shows what is downloading now.",
"When an episode is done, Sonarr moves it into the series folder by itself. Plex and Jellyfin see it there.",
"The „Calendar” shows when the next episode is due."
]},
{"title": "Search for missing episodes",
"intro": "If an older season is missing, you can search for it in one go.",
"steps": [
"Open the „Missing” tab of the „Wanted” menu.",
"Tick the episodes you want and select „Search Selected”.",
"To search for all of them, use „Search All”."
]},
{"title": "When something does not work",
"intro": "Sonarr tells you itself when something is wrong.",
"steps": [
"Look at the „Status” tab of the „System” menu: the „Health” part lists the problems.",
"The „Download Clients” page of the „Settings” menu shows the download clients the box connected.",
"If nothing is found, look at the „Indexers” page in Prowlarr: you need at least one source."
]}
],
"faq": [
{"q": "Where does Sonarr download from?",
"a": "From the sources (indexers) you add in Prowlarr. The box connects the two, but it neither provides nor suggests a source: that is your choice."},
{"q": "Do I have to set up the download client and the folders?",
"a": "No. For every drive, the box connected the installed download clients (qBittorrent, Transmission), the root folder and the tag. You only add the series."},
{"q": "Why is the same file in both the torrents and the tv folder?",
"a": "It does not take double space: it is one file with two names (a hardlink). The download client can keep sharing it, and your library stays tidy."},
{"q": "It downloaded, but it is not in the library. Why?",
"a": "Sonarr does not import if less than 10 GB would be left on the drive. The box set this: „Settings”, „Media Management”, „Minimum Free Space”. Free up some space and it goes on at the next check."},
{"q": "Where do I watch the series?",
"a": "In Plex or Jellyfin. Sonarr only downloads and files; it cannot play anything."},
{"q": "What may I download?",
"a": "Only content you have the right to, for example shows under a free licence."}
]
}
}
@@ -0,0 +1,44 @@
app: sonarr checked against: 4.0.20
corpora: Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json Sonarr/Sonarr@v4.0.20.3014(cab419a):frontend/src felhom-controller@123d2d5:controller/internal/i18n/locales/hu.json felhom-controller@123d2d5:controller/internal/i18n/locales/en.json
labels: 40 (Hungarian and English guide strings, every „…” occurrence)
OK [hu] „Series” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1860
OK [hu] „Add New” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:38
OK [hu] „Root Folder” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:139
OK [hu] „Tags” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1998
OK [hu] „Start search for missing episodes” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:45
OK [hu] „Activity” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:7
OK [hu] „Queue” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1631
OK [hu] „Calendar” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:184
OK [hu] „Wanted” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:2139
OK [hu] „Missing” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1207
OK [hu] „Search Selected” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1819
OK [hu] „Search All” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1806
OK [hu] „System” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1984
OK [hu] „Status” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:141
OK [hu] „Health” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:781
OK [hu] „Settings” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1901
OK [hu] „Download Clients” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:571
OK [hu] „Indexers” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1043
OK [hu] „Media Management” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1159
OK [hu] „Minimum Free Space” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1201
OK [en] „Series” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1860
OK [en] „Add New” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:38
OK [en] „Root Folder” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:139
OK [en] „Tags” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1998
OK [en] „Start search for missing episodes” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:45
OK [en] „Queue” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1631
OK [en] „Activity” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:7
OK [en] „Calendar” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:184
OK [en] „Missing” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1207
OK [en] „Wanted” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:2139
OK [en] „Search Selected” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1819
OK [en] „Search All” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1806
OK [en] „Status” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:141
OK [en] „System” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1984
OK [en] „Health” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:781
OK [en] „Download Clients” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:571
OK [en] „Settings” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1901
OK [en] „Indexers” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1043
OK [en] „Media Management” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1159
OK [en] „Minimum Free Space” Sonarr/Sonarr@v4.0.20.3014(cab419a):src/NzbDrone.Core/Localization/Core/en.json:1201
RESULT: ALL FOUND — 40 label(s), 0 missing
@@ -0,0 +1,36 @@
import io, os, sys, subprocess, shutil
ROOT = "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu"
sys.path.insert(0, os.path.join(ROOT, "scripts"))
import test_gate_decoys as T
CG = os.path.join(ROOT, "scripts", "check-copy-i18n.py")
GV = os.path.join(ROOT, "scripts", "check-guide-version.py")
P = [
("R1 guide strings counted as copy", CG, ' g = ai.get("guide") if isinstance(ai.get("guide"), dict) else {}', ' g = {}'),
("R2 overlay accepts guide", CG, '"prerequisites",\n "guide"):', '"prerequisites"):'),
("R3 checked_version refused in overlay", CG, ' if k == "checked_version":', ' if False:'),
("R4 empty steps refused", CG, ' if not isinstance(steps, list) or not steps:', ' if not isinstance(steps, list):'),
("R5 faq answer required", CG, ' if not (isinstance(qa.get(k), str) and qa.get(k).strip()):', ' if not isinstance(qa.get(k), (str, type(None))):'),
("R6 quoted version", CG, ' if cv is not None and not isinstance(cv, str):', ' if False:'),
("R7 section/faq counts", CG, ' if len(lst) != len(hu_lst):', ' if len(lst) < len(hu_lst):'),
("R8 step counts", CG, ' if len(st) != hu_n:', ' if len(st) > hu_n + 99:'),
("R9 inert English guide", CG, ' if not isinstance(hu_g, dict):\n errs.append', ' if False:\n errs.append'),
("R10 source deny-list", CG, ' for name in SOURCE_DENYLIST:', ' for name in []:'),
("R11 WARN on a moved image", GV, ' if cv in cands:', ' if cv:'),
("R12 the app service, not a sidecar", GV, ' cands = [tags[s] for s in main_svcs] or list(tags.values())', ' cands = list(tags.values())'),
]
for name, path, old, new in P:
src = io.open(path, encoding="utf-8").read()
assert src.count(old) == 1, name
io.open(path, "w", encoding="utf-8").write(src.replace(old, new))
try:
T.fails[:] = []
clone = T.make_clone()
import contextlib
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
T.guide_cases(clone)
shutil.rmtree(clone, ignore_errors=True)
names = [f.split(":")[0] + ":" + f.split(":")[1] for f in T.fails]
print("%-42s -> %d case(s) FAIL: %s" % (name, len(T.fails), "; ".join(n.strip() for n in names)))
finally:
io.open(path, "w", encoding="utf-8").write(src)
@@ -0,0 +1,12 @@
R1 guide strings counted as copy -> 3 case(s) FAIL: GENUINE: a full Hungarian guide with its English overlay; FACT: guide strings that are not in the freeze; FACT: a tracker named in a Hungarian answer (folded, any case)
R2 overlay accepts guide -> 3 case(s) FAIL: GENUINE: a full Hungarian guide with its English overlay; DECOY: a tracker named only in a YAML comment; DECOY: a source-like word only in docs_url (configuration)
R3 checked_version refused in overlay -> 1 case(s) FAIL: FACT: checked_version in the English block
R4 empty steps refused -> 1 case(s) FAIL: FACT: a section with an empty steps list
R5 faq answer required -> 1 case(s) FAIL: FACT: an FAQ entry with a question and no answer
R6 quoted version -> 1 case(s) FAIL: FACT: an unquoted checked_version (YAML reads 4.10 as 4.1)
R7 section/faq counts -> 1 case(s) FAIL: FACT: more English sections than Hungarian
R8 step counts -> 1 case(s) FAIL: FACT: an English section with a different step count
R9 inert English guide -> 1 case(s) FAIL: FACT: an English guide with no Hungarian guide
R10 source deny-list -> 2 case(s) FAIL: FACT: a tracker named in an English step; FACT: a tracker named in a Hungarian answer (folded, any case)
R11 WARN on a moved image -> 2 case(s) FAIL: FACT: the app's image moved off the checked tag (4.0.20 -> 4.0.21); DECOY: the checked tag survives only on a SIDECAR
R12 the app service, not a sidecar -> 1 case(s) FAIL: DECOY: the checked tag survives only on a SIDECAR
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""apply_guide.py — write one draft guide into templates/<app>/.felhom.yml, by TEXT, and prove the parse.
python3 apply_guide.py <catalog-root> <draft.json> [--hidden]
The draft is {"app", "checked_version", "hu": {"sections", "faq"}, "en": {"sections", "faq"}}. The Hungarian guide
goes at the END of the top-level `app_info:` block (two spaces), the English one at the end of `i18n.en.app_info`
(six spaces). An existing guide block is replaced, so a morning format change is one more run. Every string is
written JSON-quoted (valid YAML double-quoted: Hungarian quotes, apostrophes and colons cannot break it — the
„Hungarian-quote YAML kill" trap).
`--hidden` writes `checked_version: ""` (a label was not found: the box does not show the guide).
PROOF, after writing: the file parses; `app_info.guide` and `i18n.en.app_info.guide` equal the draft; and the
WHOLE document minus those two keys equals the document before. Any difference restores the old file and exits 1.
"""
import copy
import io
import json
import sys
import yaml
def q(s):
return json.dumps(s, ensure_ascii=False)
def block(g, ind, cv=None):
p = " " * ind
out = [p + "guide:"]
if cv is not None:
out.append(p + " checked_version: " + q(cv))
out.append(p + " sections:")
for se in g["sections"]:
out.append(p + " - title: " + q(se["title"]))
if se.get("intro"):
out.append(p + " intro: " + q(se["intro"]))
out.append(p + " steps:")
for st in se["steps"]:
out.append(p + " - " + q(st))
if g.get("faq"):
out.append(p + " faq:")
for qa in g["faq"]:
out.append(p + " - q: " + q(qa["q"]))
out.append(p + " a: " + q(qa["a"]))
return out
def indent(line):
return len(line) - len(line.lstrip(" "))
def is_content(line):
s = line.strip()
return s != "" and not s.startswith("#")
def span(lines, start, ind):
"""Index just past the last CONTENT line belonging to the block whose header is lines[start] (children are
indented deeper than `ind`). Trailing blank and comment lines stay outside, with whatever follows."""
end = start + 1
last = start
i = start + 1
while i < len(lines):
if is_content(lines[i]):
if indent(lines[i]) <= ind:
break
last = i
i += 1
return last + 1
def remove_guide(lines, parent, ind):
"""Remove a `guide:` child (at indent `ind`) of the block headed at lines[parent]."""
end = span(lines, parent, ind - 2)
for i in range(parent + 1, end):
if lines[i] == " " * ind + "guide:":
j = span(lines, i, ind)
del lines[i:j]
return True
return False
def find_line(lines, text, after=0, before=None):
for i in range(after, before if before is not None else len(lines)):
if lines[i] == text:
return i
return -1
def strip_guides(doc):
d = copy.deepcopy(doc)
(d.get("app_info") or {}).pop("guide", None)
en = ((d.get("i18n") or {}).get("en") or {})
(en.get("app_info") or {}).pop("guide", None)
return d
def main(argv):
root, draft_path = argv[1], argv[2]
hidden = "--hidden" in argv
d = json.load(io.open(draft_path, encoding="utf-8"))
app = d["app"]
path = "%s/templates/%s/.felhom.yml" % (root, app)
old_text = io.open(path, encoding="utf-8").read()
old_doc = yaml.safe_load(old_text)
lines = old_text.split("\n")
# Hungarian: top-level app_info
ai = find_line(lines, "app_info:")
if ai < 0:
print("FAIL %s: no top-level app_info:" % app)
return 1
remove_guide(lines, ai, 2)
end = span(lines, ai, 0)
cv = "" if hidden else d["checked_version"]
lines[end:end] = block(d["hu"], 2, cv)
# English: i18n -> en -> app_info
i18 = find_line(lines, "i18n:")
en = find_line(lines, " en:", i18 + 1) if i18 >= 0 else -1
if en < 0:
print("FAIL %s: no i18n.en block — every app is translated; refusing to invent one" % app)
return 1
en_end = span(lines, en, 2)
eai = find_line(lines, " app_info:", en + 1, en_end)
if eai < 0:
lines[en_end:en_end] = [" app_info:"]
eai = en_end
remove_guide(lines, eai, 6)
e_end = span(lines, eai, 4)
lines[e_end:e_end] = block(d["en"], 6)
new_text = "\n".join(lines)
new_doc = yaml.safe_load(new_text)
want_hu = {"checked_version": cv, "sections": d["hu"]["sections"], "faq": d["hu"]["faq"]}
want_en = {"sections": d["en"]["sections"], "faq": d["en"]["faq"]}
def norm(g): # the writer omits an empty intro; compare like for like
g = copy.deepcopy(g)
for se in g.get("sections") or []:
if not se.get("intro"):
se.pop("intro", None)
return g
problems = []
if norm(new_doc["app_info"].get("guide")) != norm(want_hu):
problems.append("Hungarian guide does not read back as the draft")
if norm(new_doc["i18n"]["en"]["app_info"].get("guide")) != norm(want_en):
problems.append("English guide does not read back as the draft")
if strip_guides(new_doc) != strip_guides(old_doc):
problems.append("the rest of the document CHANGED")
if problems:
print("FAIL %s: %s — file left as it was" % (app, "; ".join(problems)))
return 1
with io.open(path, "w", encoding="utf-8") as fh:
fh.write(new_text)
print("OK %s: guide written (%d sections, %d faq; checked_version %r)"
% (app, len(d["hu"]["sections"]), len(d["hu"]["faq"]), cv))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""labelcheck.py — every quoted label in a guide draft, looked up in the app's source AT THE PINNED TAG.
python3 labelcheck.py <draft.json> <out.txt> <corpus-dir-or-file> [<corpus> ...]
A label is the text between „ and ” (or “ and ”) in any guide string, Hungarian or English. It is FOUND
when it occurs DELIMITED in a corpus file: as a whole JSON key or value, inside QBT_TR(…)QBT_TR, between
quotes, or between > and <. A bare substring is not enough ("Add" is inside every file). The first hit's
file:line is recorded. Exit 0 = every label found, 1 = at least one MISSING (the guide stays hidden).
The corpus is the app's upstream source at the image's tag (or the files the image itself ships), plus the
controller's own locale files for the Felhom labels a guide points at (a tab name, „Kész, beállítottam").
"""
import io
import json
import os
import re
import sys
LABEL = re.compile(r"[„“]([^”„“]+)”")
TEXT_EXT = (".json", ".js", ".jsx", ".ts", ".tsx", ".html", ".htm", ".vue", ".svelte", ".py", ".go", ".php",
".twig", ".blade.php", ".po", ".pot", ".yml", ".yaml", ".xml", ".ts.xml", ".properties", ".dart",
".kt", ".java", ".cs", ".rb", ".erb", ".hbs", ".mjs", ".cjs", ".svg", ".md", ".txt", ".ini", ".toml",
".strings", ".resx", ".arb", ".csv", ".tmpl", ".gohtml", ".j2", ".jinja", ".lua", ".rs", ".ex", ".exs",
".heex", ".eex", ".elm", ".swift", ".c", ".cpp", ".h", ".ui")
def labels_of(draft):
out = []
for lang in ("hu", "en"):
g = draft.get(lang) or {}
strings = []
for se in g.get("sections") or []:
strings += [se.get("title") or "", se.get("intro") or ""] + list(se.get("steps") or [])
for qa in g.get("faq") or []:
strings += [qa.get("q") or "", qa.get("a") or ""]
for s in strings:
for m in LABEL.finditer(s):
lab = m.group(1).strip()
if (lang, lab) not in out:
out.append((lang, lab))
return out
def files_of(corpus):
if os.path.isfile(corpus):
yield corpus
return
for dp, dn, fn in os.walk(corpus):
dn[:] = [d for d in dn if d not in (".git", "node_modules", "test", "tests", "__tests__")]
for f in fn:
if f.endswith(TEXT_EXT):
yield os.path.join(dp, f)
def find(label, corpora, cache):
e = re.escape(label)
pats = [re.compile(p) for p in (
r'QBT_TR\(%s\)QBT_TR' % e,
r'"%s"' % e, r"'%s'" % e, r'`%s`' % e,
r'>\s*%s\s*<' % e,
r'msgstr "%s"' % e, r'<translation>%s</translation>' % e, r'<source>%s</source>' % e,
r'(?m)^\s*[\w.\-]+\s*=\s*%s\s*$' % e, # .properties / .ini value
r'(?m)^\s*"?[\w.\-]+"?\s*:\s*%s\s*$' % e, # YAML value, unquoted
)]
for c in corpora:
for path in files_of(c):
if path not in cache:
try:
cache[path] = io.open(path, encoding="utf-8", errors="replace").read()
except OSError:
cache[path] = ""
text = cache[path]
if label not in text:
continue
for p in pats:
m = p.search(text)
if m:
line = text.count("\n", 0, m.start()) + 1
return "%s:%d" % (path, line)
return None
def main(argv):
draft = json.load(io.open(argv[1], encoding="utf-8"))
out_path, corpora = argv[2], argv[3:]
cache, lines, missing = {}, [], 0
labs = labels_of(draft)
lines.append("app: %s checked against: %s" % (draft.get("app"), draft.get("checked_version")))
lines.append("corpora: %s" % " ".join(corpora))
lines.append("labels: %d (Hungarian and English guide strings, every „…” occurrence)" % len(labs))
for lang, lab in labs:
hit = find(lab, corpora, cache)
if hit:
lines.append("OK [%s] „%s” %s" % (lang, lab, hit))
else:
missing += 1
lines.append("MISSING [%s] „%s”" % (lang, lab))
lines.append("RESULT: %s — %d label(s), %d missing" % ("ALL FOUND" if not missing else "NOT ALL FOUND",
len(labs), missing))
with io.open(out_path, "w", encoding="utf-8") as fh:
fh.write("\n".join(lines) + "\n")
print(lines[-1])
return 1 if missing else 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""validate_draft.py — would this draft pass the catalog's copy gate? Checked on a THROWAWAY copy, never the repo.
python3 validate_draft.py <catalog-root> <draft.json>
Copies `templates/<app>/`, `scripts/` and the freeze into a temp directory, writes the guide there with
apply_guide.py, admits the app to the temp freeze, and runs check-copy-i18n.py --root <temp> <app>. Also checks the
night's format: 3-5 sections, each with a title, a one-sentence intro and 2-5 steps; 4-6 FAQ entries; no URL, no
markdown, no bare DOMAIN outside `<label>.DOMAIN`. Prints PASS or the findings; exit 0 / 1. The repo is only read.
"""
import io
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
def fmt_problems(d):
p = []
for lang in ("hu", "en"):
g = d.get(lang) or {}
secs, faq = g.get("sections") or [], g.get("faq") or []
if not 3 <= len(secs) <= 5:
p.append("%s: %d sections (format: 3-5)" % (lang, len(secs)))
if not 4 <= len(faq) <= 6:
p.append("%s: %d FAQ entries (format: 4-6)" % (lang, len(faq)))
for i, se in enumerate(secs):
if not se.get("intro"):
p.append("%s: sections[%d] has no intro (format: one sentence each)" % (lang, i))
if not 2 <= len(se.get("steps") or []) <= 5:
p.append("%s: sections[%d] has %d steps (format: 2-5)" % (lang, i, len(se.get("steps") or [])))
strings = []
for se in secs:
strings += [se.get("title", ""), se.get("intro", "")] + list(se.get("steps") or [])
for qa in faq:
strings += [qa.get("q", ""), qa.get("a", "")]
for s in strings:
if re.search(r"https?://|www\.", s):
p.append("%s: a URL in guide text (not allowed): %r" % (lang, s))
if re.search(r"\*\*|__|`|<[a-zA-Z/][^>]*>|^#|\[[^\]]+\]\(", s):
p.append("%s: markdown or HTML in guide text (rendered as plain text): %r" % (lang, s))
for m in re.finditer(r"\bDOMAIN\b", s):
if not re.search(r"[a-z0-9-]+\.DOMAIN\b", s[max(0, m.start() - 40):m.end()]):
p.append("%s: bare DOMAIN (the box fills it with the household's domain): %r" % (lang, s))
if "—" in s and lang == "hu":
p.append("hu: em dash (R-932): %r" % s)
if re.search(r"[\U0001F300-\U0001FAFF☀-➿]", s):
p.append("%s: emoji: %r" % (lang, s))
return p
def main(argv):
root, draft = os.path.abspath(argv[1]), os.path.abspath(argv[2])
d = json.load(io.open(draft, encoding="utf-8"))
app = d["app"]
probs = fmt_problems(d)
tmp = tempfile.mkdtemp(prefix="guide-validate-")
try:
shutil.copytree(os.path.join(root, "scripts"), os.path.join(tmp, "scripts"),
ignore=shutil.ignore_patterns("__pycache__"))
os.makedirs(os.path.join(tmp, "templates"))
for a in os.listdir(os.path.join(root, "templates")):
src = os.path.join(root, "templates", a)
if os.path.isdir(src):
shutil.copytree(src, os.path.join(tmp, "templates", a))
r = subprocess.run([sys.executable, os.path.join(HERE, "apply_guide.py"), tmp, draft], capture_output=True,
text=True)
if r.returncode != 0:
probs.append("apply: " + (r.stdout + r.stderr).strip())
else:
subprocess.run([sys.executable, os.path.join(tmp, "scripts", "check-copy-i18n.py"), "--root", tmp,
"--add-app", app, "--reason", "validate_draft"], capture_output=True, text=True)
# The ratchet is about the whole catalog; measure it on the temp copy and hand it back.
probe = subprocess.run([sys.executable, os.path.join(tmp, "scripts", "check-copy-i18n.py"), "--root", tmp,
"--expect-missing", "-1"], capture_output=True, text=True)
m = re.search(r"(\d+) strings have no English", probe.stdout)
miss = m.group(1) if m else "0"
r = subprocess.run([sys.executable, os.path.join(tmp, "scripts", "check-copy-i18n.py"), "--root", tmp,
"--expect-missing", miss, app], capture_output=True, text=True)
if r.returncode != 0:
probs += ["copy gate: " + l.strip() for l in r.stdout.splitlines() if l.strip().startswith("- ")
and app in l]
if not any(app in l for l in r.stdout.splitlines() if l.strip().startswith("- ")):
probs.append("copy gate failed:\n" + r.stdout[-1500:])
if miss != "0":
probs.append("English coverage: %s copy string(s) of the catalog would have no English" % miss)
finally:
shutil.rmtree(tmp, ignore_errors=True)
if probs:
print("FAIL %s" % app)
for x in probs:
print(" - " + x)
return 1
print("PASS %s — format and copy gate clean" % app)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))