How to run the full OVOS Localize pipeline locally without deploying to GitHub.
uv pip install -e ".[dev]"Zero external dependencies — everything uses Python stdlib.
python scripts/generate_data.pyThis clones every repo in skills.txt into repos/ (shallow clone), scans locale directories, runs AST analysis and validation, then writes JSON to data/.
First run takes several minutes (cloning ~60 repos). Subsequent runs are faster (git pull only).
To test with fewer skills, create a temporary skills.txt:
# Back up the full list
cp skills.txt skills.txt.bak
# Test with just 2 skills
cat > skills.txt << 'EOF'
OpenVoiceOS/ovos-skill-hello-world
OpenVoiceOS/ovos-skill-weather
EOF
python scripts/generate_data.py
# Restore
mv skills.txt.bak skills.txtAfter running, data/ contains:
data/
├── repos.json # Skill index (all skills)
├── coverage.json # Language × skill coverage matrix
├── validation.json # Aggregated validation results
└── skills/
├── ovos-skill-hello-world.json
├── ovos-skill-weather.json
└── ...
python -m http.server 8000Open http://localhost:8000 in a browser.
| URL | What you see |
|---|---|
http://localhost:8000/#/ |
Dashboard — coverage heatmap, 53 skills × 40 languages |
http://localhost:8000/#/skill/ovos-skill-weather |
Skill detail — file list with per-language validation badges |
http://localhost:8000/#/skill/ovos-skill-weather/weather.intent/de-de |
Translation editor — source panel, editable textarea, context card |
The editor view has a "Submit as PR" button. The workflow:
- Click Sign in in the header
- Paste a GitHub Personal Access Token with
public_reposcope - Navigate to a file and language (e.g.
#/skill/ovos-skill-weather/weather.intent/de-de) - Edit the translation in the center textarea
- Click Submit as PR
What happens behind the scenes:
- Forks the skill repo to your GitHub account (idempotent)
- Creates a
translate/{lang}/{file}branch on your fork - Commits the edited file
- Opens a PR from your fork to the upstream
devbranch - Opens the PR in a new tab
The token is stored in localStorage only — never sent anywhere except the GitHub API.
Dashboard shows nothing / fetch errors in console:
- Verify
data/repos.jsonexists and is valid JSON:python -m json.tool data/repos.json > /dev/null - The server must run from the repo root (where
index.htmlanddata/both live)
"Submit as PR" fails with 404:
- Your token may lack
public_reposcope — regenerate it - The fork may not be ready yet — wait a few seconds and retry
"Edit on GitHub" links point to wrong branch:
- Links follow the branch the scan resolved for that repo, recorded as
branchindata/repos.json. Regenerate the data if a repo changed its default branch. devis used only as a last resort, when no branch was resolved.
No data generation needed — the CLI works standalone:
# Text output
ovos-localize-cli --repo /path/to/ovos-skill-weather
# GitHub Actions annotation format (for CI)
ovos-localize-cli --repo /path/to/ovos-skill-weather --report-format github
# JSON output
ovos-localize-cli --repo /path/to/ovos-skill-weather --report-format jsonExit code: 0 = no errors, 1 = validation errors found.
uv run pytest test/ -vWith coverage:
uv run pytest test/ -v --cov=ovos_localize --cov-report=term-missingfrom ovos_localize.sync.github import RepoScanner
from ovos_localize.analyzers.context_builder import build_context_card
scanner = RepoScanner("./repos")
scan = scanner.full_sync("OpenVoiceOS", "ovos-skill-weather")
print(f"Skill: {scan.skill_class_name}")
print(f"Languages: {scan.languages}")
print(f"Files: {len(scan.locale_files)}")
for f in scan.locale_files:
if f.lang == "en-us":
card = build_context_card(f, scan.skill_analysis, scan.locale_files)
print(f"\n{f.base_name}.{f.file_type.value}: {card.file_type_label}")
if card.handler_method:
print(f" Handler: {card.handler_method}() at :{card.handler_line}")
if card.tips:
print(f" Tips: {card.tips[0]}")The SPA ships with the structural accessibility baseline:
- a skip-to-content link (visually hidden until focused) targeting
<main id="app">; - semantic landmarks (
header/nav[aria-label="Primary"]/main/footer); - focus management on hash-route change — after each view renders, focus moves
to its heading (or
<main>) so screen-reader users are told the page changed; - a polite live region (
#toast-container) so toast/status messages are announced; - visible keyboard focus rings (
:focus-visible) on all interactive elements.
Not yet automated (follow-ups): an axe-core CI gate, a per-view
single-h1 audit, and a colour-contrast pass on the dark
theme. Run a manual keyboard-only + screen-reader pass on the editor before
release — automated tooling catches only ~30–40% of real issues.
Playwright smoke tests for the single-file SPA live in e2e/ (kept out of the
unit test/ directory so they never gate the fast unit suite). They serve the
repo over HTTP and drive a headless browser; the SPA tolerates missing data, so
no fixtures are needed.
pip install -e ".[e2e]"
python -m playwright install chromium
pytest e2e/CI runs them in a dedicated E2E workflow that installs the browser. This is
the harness that makes further SPA changes (RTL, accessibility, batch editing)
safe to evolve.
The editor supports right-to-left languages. lang_utils.is_rtl(code) is the
single source of truth for direction, keyed on the BCP-47 primary subtag
(ar, he, fa, ur, ps, sd, ug, dv, yi, ckb, arc). Direction
is a property of the language, never inferred from the script — Kabyle (kab)
uses Latin script and Tifinagh is itself LTR, so neither is RTL.
The SPA mirrors this set as RTL_LANGS / isRtlLang() in index.html (keep
the two in sync). The translation textarea receives dir="rtl" for RTL
languages; structured JSON fields use dir="auto"; and slot placeholders such
as {location} are wrapped in <bdi> so their Latin names do not scramble the
surrounding RTL text.
Bidi visual correctness (caret behaviour, punctuation order) is not fully
verifiable in CI — QA an RTL language such as fa-IR manually in a browser.