Files
calendar-lite/tools/check_i18n.py
T
XoséandClaude Opus 5.5 f7428287eb GingerCal 1.4: bundled TLS 1.2, no bridge needed
CalDAV now goes through Minamp's own HTTP/TLS client (Bouncy Castle TLS 1.2, Mozilla
roots, keep-alive) instead of Apache HttpClient on Android 2.3's TLS 1.0, as the iPod
version does with BearSSL. The phone reaches the normal https:// Nextcloud address at
home and away; a pinned self-signed server or the old bridge still works.

- NetHttp transport; requests run on big-stack threads (the handshake overflows
  AsyncTask/IntentService stacks on Android 2.x)
- http:// -> https:// redirects on the same host are followed; other hosts still refused
- bridge wording replaced in Galician and English; README updated
- NetTest (TLS + CalDAV discovery through the new transport) replaces BridgeTest

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:14:06 +02:00

81 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Translation check (run by build.sh):
1. every L.t("...") / L.f("...") key in the code has a Galician entry in L.java,
2. English and Galician use the same placeholders in the same order,
3. no English text reaches the screen without going through L (setText/setTitle/... literals),
4. no unused entries.
usage: check_i18n.py <project dir>"""
import os
import re
import sys
root = sys.argv[1] if len(sys.argv) > 1 else "."
LIT = r'"((?:[^"\\]|\\.)*)"'
def unescape(s):
out, i = [], 0
while i < len(s):
c = s[i]
if c == "\\" and i + 1 < len(s):
n = s[i + 1]
if n == "u":
out.append(chr(int(s[i + 2:i + 6], 16)))
i += 6
continue
out.append({"n": "\n", "t": "\t", "r": "\r", '"': '"', "'": "'", "\\": "\\"}.get(n, n))
i += 2
continue
out.append(c)
i += 1
return "".join(out)
def placeholders(s):
return re.findall(r"%[sdn%]", s)
lsrc = open(os.path.join(root, "src/org/gingercal/L.java"), encoding="utf-8").read()
table = {}
for m in re.finditer(r"put\(\s*" + LIT + r"\s*,\s*" + LIT + r"\s*\);", lsrc, re.S):
en, gal = unescape(m.group(1)), unescape(m.group(2))
if en in table:
print("DUPLICATE key:", en[:60])
table[en] = gal
problems, used = [], set()
UI_CALL = re.compile(r"\b(setText|setTitle|setMessage|setHint|setPositiveButton|setNegativeButton|setNeutralButton|"
r"toast\([^,]+,|ProgressDialog\.show\([^,]+,[^,]+,|Rows\.add|rows\.add|Rows\.add|"
r"new DavException\([^,]+,|new IOException\(|label\(|field\([^,]+,)\s*\(?\s*" + LIT)
for d in ("src", "android"):
for dirpath, _, files in os.walk(os.path.join(root, d)):
if "/net" in dirpath: continue # bundled TLS client: internal messages, not UI text
for f in files:
if not f.endswith(".java") or f == "L.java":
continue
path = os.path.join(dirpath, f)
text = open(path, encoding="utf-8").read()
for m in re.finditer(r"\bL\.[tf]\(\s*" + LIT, text):
key = unescape(m.group(1))
used.add(key)
if key not in table:
problems.append("%s: no Galician for %r" % (f, key[:70]))
elif placeholders(key) != placeholders(table[key]):
problems.append("%s: placeholders differ for %r" % (f, key[:70]))
for line_no, line in enumerate(text.splitlines(), 1):
for m in UI_CALL.finditer(line):
s = unescape(m.group(2))
if re.search(r"[A-Za-z]{2,}", s) and s not in ("Galego", "English", "Lingua / Language"):
problems.append("%s:%d: untranslated UI text %r" % (f, line_no, s[:60]))
for m in re.finditer(r"\.add\(\s*" + LIT, line):
s = unescape(m.group(1))
if " " in s and re.search(r"[A-Za-z]{3,} [A-Za-z]{3,}", s):
problems.append("%s:%d: untranslated list text %r" % (f, line_no, s[:60]))
for key in sorted(set(table) - used):
problems.append("unused entry in L.java: %r" % key[:70])
for p in problems:
print("I18N:", p)
print("translations: %d entries, %d keys used, %s" % (len(table), len(used), "OK" if not problems else "PROBLEMS"))
sys.exit(1 if problems else 0)