83 lines
3.0 KiB
Python
83 lines
3.0 KiB
Python
# Hardcoded string scanner for ObsiGate frontend JS files
|
|
# Finds user-visible strings that should be i18n'd but aren't.
|
|
# Also checks EN locale for French contamination.
|
|
#
|
|
# Usage:
|
|
# cd /c/dev/git/python/obsigate
|
|
# python3 << 'PYEOF' ... PYEOF
|
|
|
|
import re, os, json
|
|
|
|
js_dir = 'frontend/js'
|
|
|
|
# ── Part 1: Find hardcoded user-visible strings in JS ──
|
|
noise_patterns = [
|
|
r'\.css', r'data-', r'console\.', r'getElementById', r'querySelector',
|
|
r'\.json', r'/api/', r'CSS\.escape', r'encodeURI', r'font-size',
|
|
r'background:', r'padding:', r'border', r'SSE parse', r'\[Offline',
|
|
r'Failed to', r'Error ', r'Soft vault', r'\[Forge\]', r'CodeMirror',
|
|
r'\[PWA\]', r'\[SW\]', r'DragDrop', r'Dashboard:',
|
|
]
|
|
|
|
french_chars = re.compile(r'[àâäéèêëîïôöùûüçÀÂÄÉÈÊËÎÏÔÖÙÛÜÇ]')
|
|
ui_words = ['loading', 'error', 'search', 'save', 'delete', 'create',
|
|
'cancel', 'download', 'upload', 'share', 'copy', 'rename',
|
|
'open', 'close', 'file', 'folder', 'vault']
|
|
|
|
for fname in sorted(os.listdir(js_dir)):
|
|
if not fname.endswith('.js') or fname == 'i18n.js':
|
|
continue
|
|
fpath = os.path.join(js_dir, fname)
|
|
with open(fpath, 'r', encoding='utf-8') as f:
|
|
lines = f.readlines()
|
|
|
|
hardcoded = []
|
|
for i, line in enumerate(lines, 1):
|
|
strings = re.findall(r"'([^']+)'|\"([^\"]+)\"|`([^`]+)`", line)
|
|
for s_tuple in strings:
|
|
s = s_tuple[0] or s_tuple[1] or s_tuple[2]
|
|
if len(s) < 4: continue
|
|
if not re.search(r'[A-Za-zÀ-ÿ]', s): continue
|
|
if any(re.search(p, s) for p in noise_patterns): continue
|
|
if s.startswith('#') or s.startswith('.') or s.startswith('http'): continue
|
|
|
|
context = ''.join(lines[max(0,i-2):i+1])
|
|
if 't(' in context or 'data-i18n' in context or 'tr(' in context:
|
|
continue
|
|
|
|
has_fr = bool(french_chars.search(s))
|
|
is_ui = has_fr or any(w in s.lower() for w in ui_words)
|
|
if is_ui:
|
|
hardcoded.append((i, s.strip()[:120]))
|
|
|
|
if hardcoded:
|
|
print(f"\n=== {fname} ({len(hardcoded)}) ===")
|
|
for ln, s in hardcoded:
|
|
print(f" L{ln}: {s}")
|
|
|
|
# ── Part 2: Check EN locale for French contamination ──
|
|
with open('frontend/locales/fr.json', encoding='utf-8') as f:
|
|
fr = json.load(f)
|
|
with open('frontend/locales/en.json', encoding='utf-8') as f:
|
|
en = json.load(f)
|
|
|
|
print(f"\n=== Locale alignment: FR={len(fr)} EN={len(en)} ===")
|
|
|
|
only_fr = set(fr.keys()) - set(en.keys())
|
|
only_en = set(en.keys()) - set(fr.keys())
|
|
if only_fr: print(f"Keys in FR only: {sorted(only_fr)}")
|
|
if only_en: print(f"Keys in EN only: {sorted(only_en)}")
|
|
|
|
# EN values with French chars
|
|
fr_in_en = []
|
|
for k in sorted(fr.keys()):
|
|
if k in en and isinstance(en[k], str):
|
|
if french_chars.search(en[k]):
|
|
fr_in_en.append(k)
|
|
|
|
if fr_in_en:
|
|
print(f"\n=== EN keys with French text ({len(fr_in_en)}) ===")
|
|
for k in fr_in_en:
|
|
if not k.startswith('auto.') and not k.startswith('help.desc_'):
|
|
print(f" {k}: {en[k][:80]}")
|