Files
Alejandro Veras 828a81f3a5 Add Spanish and Arabic versions, a contact form captcha and a multilingual sitemap
- English, Spanish (/es) and Arabic (/ar, right to left) versions of every page, with a language switch
- Self-hosted "I'm not a robot" check on the contact form; the message field is optional
- Google Tag Manager, GA4 and Search Console verification in the shared layout
- Sitemap, robots.txt and canonical links point at aigrowthtechnologies.com
2026-10-13 11:53:18 -04:00

131 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Translation helpers for the generated content pages.
python3 -E scripts/i18n.py extract <out_dir>
Write one flat {path: text} JSON per English page with only the strings to translate.
python3 -E scripts/i18n.py apply <locale> <dir>
Read translated flat files from <dir> and write src/content/pages/<locale>/<slug>.json,
keeping the English structure. Fails if a translation is missing or extra.
python3 -E scripts/i18n.py index
Regenerate src/content/pages/index.ts. A locale without a page falls back to English.
"""
from __future__ import annotations
import copy
import json
import pathlib
import re
import sys
ROOT = pathlib.Path(__file__).resolve().parent.parent
PAGES = ROOT / "src" / "content" / "pages"
LOCALES = ["en", "es", "ar"]
SKIP_KEYS = {"slug", "path", "form", "level", "kind", "num"}
def keep_english(value: str, section: dict | None, key: str) -> bool:
"""Office addresses and stat figures stay as they are in every language."""
if "Phone:" in value:
return True
if section and section.get("kind") == "stats" and key == "title" and re.match(r"^[\d.,]+", value):
return True
return False
def walk(node, path=(), section=None):
if isinstance(node, dict):
if "kind" in node:
section = node
for k, v in node.items():
if k in SKIP_KEYS:
continue
yield from walk(v, path + (k,), section)
elif isinstance(node, list):
for i, v in enumerate(node):
yield from walk(v, path + (str(i),), section)
elif isinstance(node, str) and node.strip():
if not keep_english(node, section, path[-1]):
yield ".".join(path), node
def set_path(node, path: str, value: str):
parts = path.split(".")
for p in parts[:-1]:
node = node[int(p)] if isinstance(node, list) else node[p]
last = parts[-1]
if isinstance(node, list):
node[int(last)] = value
else:
node[last] = value
def english_pages():
return sorted((PAGES / "en").glob("*.json"))
def extract(out_dir: str):
out = pathlib.Path(out_dir)
out.mkdir(parents=True, exist_ok=True)
for f in english_pages():
flat = dict(walk(json.loads(f.read_text())))
(out / f.name).write_text(json.dumps(flat, ensure_ascii=False, indent=1) + "\n")
print(f"{f.name}: {len(flat)} strings")
def apply(locale: str, src_dir: str):
src = pathlib.Path(src_dir)
dest = PAGES / locale
dest.mkdir(parents=True, exist_ok=True)
problems = 0
for f in english_pages():
tf = src / f.name
if not tf.exists():
print(f"skip {f.name}: no translation yet")
continue
page = json.loads(f.read_text())
wanted = dict(walk(page))
got = json.loads(tf.read_text())
missing = [k for k in wanted if k not in got]
extra = [k for k in got if k not in wanted]
if missing or extra:
problems += 1
print(f"{f.name}: missing {missing[:5]} extra {extra[:5]}")
continue
out = copy.deepcopy(page)
for k, v in got.items():
set_path(out, k, v)
(dest / f.name).write_text(json.dumps(out, ensure_ascii=False, indent=2) + "\n")
print(f"{locale}/{f.name}: ok")
if problems:
sys.exit(1)
def ident(locale: str, name: str) -> str:
return locale + "_" + re.sub(r"\W", "_", name)
def index():
names = [f.stem for f in english_pages()]
lines = ["// Generated by scripts/i18n.py index — do not edit by hand.", ""]
table = []
for locale in LOCALES:
entries = []
for n in names:
if (PAGES / locale / f"{n}.json").exists():
lines.append(f'import {ident(locale, n)} from "./{locale}/{n}.json";')
entries.append(f' "{n}": {ident(locale, n)},')
else:
entries.append(f' "{n}": {ident("en", n)},')
table.append(f" {locale}: {{\n" + "\n".join(entries) + "\n },")
lines += ["", "export const content = {", *table, "};", ""]
(PAGES / "index.ts").write_text("\n".join(lines))
print(f"index.ts: {len(names)} pages × {len(LOCALES)} locales")
if __name__ == "__main__":
cmd, *args = sys.argv[1:]
{"extract": extract, "apply": apply, "index": index}[cmd](*args)