Files
felhom.eu/documentation/audits/app-guides-2026-10-15/tools/labelcheck.py
T

110 lines
4.4 KiB
Python

#!/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))