From f2913f02b33d6329f32724ac082e75033a2ae536 Mon Sep 17 00:00:00 2001 From: Markus Hartung Date: Wed, 2 Sep 2026 13:28:11 -0300 Subject: [PATCH] docs(i18n): align every public locale-count claim with the 42-locale config; bumpCounts covers llm.txt file-count phrasings --- README.md | 4 +- bin/cli/README.md | 4 +- docs/architecture/MONITORING_SECTIONS.md | 2 +- docs/architecture/REPOSITORY_MAP.md | 6 +-- docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md | 2 +- docs/diagrams/comparison-table.svg | 4 +- docs/frameworks/RADAR.md | 2 +- docs/ops/RELEASE_CHECKLIST.md | 6 +-- scripts/i18n/lib/locale-scaffold.mjs | 9 +++- tests/unit/i18n-locale-scaffold.test.ts | 47 +++++++++++++++----- 10 files changed, 58 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 9d1596b334..60d5827d99 100644 --- a/README.md +++ b/README.md @@ -462,7 +462,7 @@ All **19** strategies β€” mix & match per combo step: -What sets OmniRoute apart β€” a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 355 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 43 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology. +What sets OmniRoute apart β€” a dated feature snapshot vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 355 providers, 150+ free tiers built in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA and 42 i18n UI locales. OmniRoute is MIT-licensed and self-hostable. Competitor capabilities and counts may change; see the linked methodology. πŸ“Š Full methodology & per-feature detail vs 9router, OpenRouter, CLIProxyAPI & LiteLLM β†’ [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md) @@ -1295,7 +1295,7 @@ MΓ©tricas canΓ΄nicas em 2026-08-24: **1.029 vΓ­deos ΓΊnicos** Β· **11.132.922 vi Branching & Release ModelWhere PRs target (release/*), what main and tags mean ChangelogFull per-version release history Security PolicyVulnerability reporting and security practices - i18n Guide43-language support, translation workflow, RTL + i18n Guide42-language support, translation workflow, RTL Release ChecklistPre-release validation steps Coverage PlanTest coverage strategy for 39,000+ static test declarations across 5,100+ tracked test files diff --git a/bin/cli/README.md b/bin/cli/README.md index c3b979da01..555e836edb 100644 --- a/bin/cli/README.md +++ b/bin/cli/README.md @@ -22,9 +22,9 @@ bin/cli/ β”œβ”€β”€ provider-test.mjs ← testProviderApiKey() β”œβ”€β”€ settings-store.mjs ← DB CRUD for key_value settings β”œβ”€β”€ locales/ -β”‚ β”œβ”€β”€ en.json ← English strings (source of truth, 43 locales) +β”‚ β”œβ”€β”€ en.json ← English strings (source of truth, 42 locales) β”‚ β”œβ”€β”€ pt-BR.json ← Portuguese (Brazil) β€” fully translated -β”‚ └── {locale}.json ← 42 additional locales (ar, az, de, es, fr, ja, zh-CN, …) +β”‚ └── {locale}.json ← 41 additional locales (ar, az, de, es, fr, ja, zh-CN, …) β”œβ”€β”€ scripts/ β”‚ └── generate-locales.mjs ← scaffold new locale files from config/i18n.json └── commands/ diff --git a/docs/architecture/MONITORING_SECTIONS.md b/docs/architecture/MONITORING_SECTIONS.md index 3fa9657caa..eb0aefd931 100644 --- a/docs/architecture/MONITORING_SECTIONS.md +++ b/docs/architecture/MONITORING_SECTIONS.md @@ -142,5 +142,5 @@ Namespaces added by Group B: | `sidebar.costsOverview` | Costs overview item | | `activity.*` | All Activity page strings (title, verbs, filters, empty state) | -Source-of-truth locales: `pt-BR` and `en`. All other 41 locales fall back to +Source-of-truth locales: `pt-BR` and `en`. All other 40 locales fall back to English via the `next-intl` fallback mechanism (configured in `src/i18n/config.ts`). diff --git a/docs/architecture/REPOSITORY_MAP.md b/docs/architecture/REPOSITORY_MAP.md index 8f991d5a72..76bdcc3394 100644 --- a/docs/architecture/REPOSITORY_MAP.md +++ b/docs/architecture/REPOSITORY_MAP.md @@ -121,7 +121,7 @@ src/ β”œβ”€β”€ domain/ # Pure domain logic (policy engine, fallback, cost, lockout, comboResolver, assessment) β”œβ”€β”€ server/ # Server-only modules (authz pipeline, cors, auth middleware) β€” cannot import from client β”œβ”€β”€ shared/ # Shared between server and client where safe (constants, types, validation, contracts, utils) -β”œβ”€β”€ i18n/ # next-intl config + per-locale message JSON (43 locales) +β”œβ”€β”€ i18n/ # next-intl config + per-locale message JSON (42 locales) β”œβ”€β”€ middleware/ # Next.js middleware (request enrichment, locale detection) β”œβ”€β”€ mitm/ # MITM proxy core: cert gen/install, handlers, targets, inspector, masks, passthrough β”‚ β”œβ”€β”€ handlers/ # 9 IDE-agent handler classes extending MitmHandlerBase (antigravity, kiro, copilot, codex, cursor, zed, claudeCode, openCode, trae) @@ -396,7 +396,7 @@ open-sse/ | `COVERAGE_PLAN.md` | Coverage goals and current state | | `FREE_TIERS.md` | Curated free-tier providers (48+ free + 11 OAuth) | | `CLI-TOOLS.md` | External CLI integrations + Internal OmniRoute CLI | -| `I18N.md` | i18n architecture, adding a language, 43 locales | +| `I18N.md` | i18n architecture, adding a language, 42 locales | | `UNINSTALL.md` | Clean uninstall steps | | `PROVIDER_REFERENCE.md` | **Auto-generated** catalog of 355 providers (regen: `npm run gen:provider-reference`) | @@ -448,7 +448,7 @@ open-sse/ | Subdir | Purpose | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `docs/i18n/` | Localized doc translations (42 locales) | +| `docs/i18n/` | Localized doc translations (41 locales) | | `docs/screenshots/` | Image assets for guides | | `_tasks/superpowers/` | Plans/specs from superpowers (`writing-plans`/`brainstorming`) + research β€” isolated, separately-versioned repo, gitignored by the main tree. See CLAUDE.md β†’ "Planning & Research Artifacts". | diff --git a/docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md b/docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md index addfc764c6..e4a749dd00 100644 --- a/docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md +++ b/docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md @@ -34,7 +34,7 @@ Objective feature comparison vs popular open-source AI routers. | **CLI with system tray (no Electron)** | βœ… | ❌ | n/a | n/a | | **CLI machine-ID auto-auth** | βœ… | ❌ | n/a | n/a | | **Dashboard** | Next.js 16 | basic | proprietary | proprietary | -| **i18n** | **43 locales** | ❌ | ❌ | ⚠ | +| **i18n** | **42 locales** | ❌ | ❌ | ⚠ | | **Public agent skills (SKILL.md)** | βœ… 45 | ❌ | ❌ | ❌ | | **Tunnel support (Cloudflared, Tailscale, Ngrok)** | βœ… | ❌ | n/a | n/a | | **License** | MIT | MIT | proprietary | proprietary | diff --git a/docs/diagrams/comparison-table.svg b/docs/diagrams/comparison-table.svg index 271cd367d6..3bc3895f25 100644 --- a/docs/diagrams/comparison-table.svg +++ b/docs/diagrams/comparison-table.svg @@ -1,4 +1,4 @@ - + Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses. @@ -117,7 +117,7 @@ i18n UI locales - 43 + 42 6 diff --git a/docs/frameworks/RADAR.md b/docs/frameworks/RADAR.md index 879f02b992..34bc602400 100644 --- a/docs/frameworks/RADAR.md +++ b/docs/frameworks/RADAR.md @@ -352,7 +352,7 @@ migration 142. The version floor above compares `version`, not either date. Two gaps remain, both deliberate: the dashboard still shows only `Last fetched`, so reading -the build date there needs a new label (and its 42 locale entries); and the offers and intel +the build date there needs a new label (and its 41 locale entries); and the offers and intel caches keep no build date at all, even though their feed schemas carry one β€” `GET /api/radar/status` therefore omits the field for those two rather than reporting a `null` that would read as "unknown". diff --git a/docs/ops/RELEASE_CHECKLIST.md b/docs/ops/RELEASE_CHECKLIST.md index 53fa6bb733..f6bf96790b 100644 --- a/docs/ops/RELEASE_CHECKLIST.md +++ b/docs/ops/RELEASE_CHECKLIST.md @@ -46,8 +46,8 @@ That is the bypass npm sanctions now that tokens which skip 2FA are being retire it restores the fully automatic flow the project had up to v3.8.48 while keeping the WS1.3 guarantee (a leaked token cannot publish alone β€” there is no token). -**One-time setup (owner):** npmjs.com β†’ package `omniroute` β†’ Settings β†’ *Trusted -Publisher* β†’ GitHub: owner `diegosouzapw`, repo `OmniRoute`, workflow `npm-publish.yml` +**One-time setup (owner):** npmjs.com β†’ package `omniroute` β†’ Settings β†’ _Trusted +Publisher_ β†’ GitHub: owner `diegosouzapw`, repo `OmniRoute`, workflow `npm-publish.yml` (environment: none). Until that exists, the automatic step fails with `ENEEDAUTH`: re-dispatch with `publish_mode=staged` (below) or `direct`. @@ -193,7 +193,7 @@ Breaking changes: add `BREAKING CHANGE:` footer or `!` after the scope (e.g. `fe - [ ] `npm run i18n:check` exits 0 β€” translation state (`.i18n-state.json`) in sync with source docs (no drifted sources in strict mode; warn-mode advisory is acceptable for last-minute doc touch-ups, but should be 0 before tagging) - [ ] `npm run i18n:check-ui-coverage` exits 0 β€” every UI locale at or above the 80% coverage floor -- [ ] `npm run i18n:sync-ui:dry` reports 0 missing keys across all 43 locales +- [ ] `npm run i18n:sync-ui:dry` reports 0 missing keys across all 42 locales - [ ] If source English docs changed, run `npm run i18n:run` (requires `OMNIROUTE_TRANSLATION_API_KEY` in `.env`) before tagging - [ ] Translation contributions can be deferred to next release if minor (track in CHANGELOG) diff --git a/scripts/i18n/lib/locale-scaffold.mjs b/scripts/i18n/lib/locale-scaffold.mjs index 6ff85829b0..8308396ad7 100644 --- a/scripts/i18n/lib/locale-scaffold.mjs +++ b/scripts/i18n/lib/locale-scaffold.mjs @@ -15,7 +15,10 @@ * insertReadmeFlagLink(readmeText, entry, total) README.md language block * insertDocsIndexRow(indexText, entry, total) docs/i18n/README.md * insertI18nGuideRow(guideText, entry, total, rtlCodes) docs/guides/I18N.md locale table - * bumpCounts(text, total) "N languages" / "N translated documentation sets" + * bumpCounts(text, total) llm.txt counts: "N languages", + * "N translated documentation sets", + * "N language JSON files", + * "N-language translated docs" * buildMirrorStub({ heading, native, bar, body }) docs/i18n//llm.txt, CHANGELOG.md * * Every insertion leaves the existing lines exactly as they are and places the @@ -164,7 +167,9 @@ export function insertI18nGuideRow(guideText, entry, total, rtlCodes = DEFAULT_R export function bumpCounts(text, total) { return text .replace(/\d+ languages/g, `${total} languages`) - .replace(/\d+ translated documentation sets/g, `${total - 1} translated documentation sets`); + .replace(/\d+ translated documentation sets/g, `${total - 1} translated documentation sets`) + .replace(/(\d+) language JSON files/g, `${total} language JSON files`) + .replace(/(\d+)-language translated docs/g, `${total - 1}-language translated docs`); } export function buildMirrorStub({ heading, native, bar, body }) { diff --git a/tests/unit/i18n-locale-scaffold.test.ts b/tests/unit/i18n-locale-scaffold.test.ts index 81d3a38a8c..68f3aa6e41 100644 --- a/tests/unit/i18n-locale-scaffold.test.ts +++ b/tests/unit/i18n-locale-scaffold.test.ts @@ -533,16 +533,34 @@ test("bumpCounts rewrites language and doc-set counts", () => { ); }); -test("bumpCounts touches only those two phrases", () => { - const text = - "352 providers, 43 languages for UI, 42 translated documentation sets, 43 locales, 7 language packs, in 42 locales, 43 langs"; +// llm.txt's repo-tree comments count the same two things in a different shape: +// `messages/ # N language JSON files` is the UI-locale total, `i18n/ # N-language +// translated docs` is the docs total (one less β€” English is the source, not a +// translation). Both were left behind by bumpCounts until this was added. +test("bumpCounts rewrites the llm.txt tree-comment count phrasings", () => { assert.equal( - bumpCounts(text, 44), - "352 providers, 44 languages for UI, 43 translated documentation sets, 43 locales, 7 language packs, in 42 locales, 43 langs" + bumpCounts("messages/ # 43 language JSON files\ni18n/ # 42-language translated docs", 44), + "messages/ # 44 language JSON files\ni18n/ # 43-language translated docs" ); }); -test("bumpCounts on the real llm.txt rewrites exactly the three count lines, each by one", () => { +test("bumpCounts touches only those four phrases", () => { + const text = + "352 providers, 43 languages for UI, 42 translated documentation sets, 43 language JSON files, 42-language translated docs, 43 locales, 7 language packs, in 42 locales, 43 langs"; + assert.equal( + bumpCounts(text, 44), + "352 providers, 44 languages for UI, 43 translated documentation sets, 44 language JSON files, 43-language translated docs, 43 locales, 7 language packs, in 42 locales, 43 langs" + ); +}); + +// The four phrases, in the shapes llm.txt actually uses. `-language translated +// docs` is hyphenated, so the separator class is `[ -]`, not a plain space. +const LLM_COUNT_PHRASE = + /(?:languages|translated documentation sets|language JSON files|language translated docs)/; +const LLM_COUNT_LINE = new RegExp(`\\d+[ -]${LLM_COUNT_PHRASE.source}`); +const LLM_COUNT_NUMBER = new RegExp(`\\d+(?=[ -]${LLM_COUNT_PHRASE.source})`); + +test("bumpCounts on the real llm.txt rewrites exactly the five count lines, each by one", () => { const total = realConfig().locales.length; const before = readRepo("llm.txt").split("\n"); const after = bumpCounts(before.join("\n"), total + 1).split("\n"); @@ -550,18 +568,25 @@ test("bumpCounts on the real llm.txt rewrites exactly the three count lines, eac const changed = before .map((line, index) => [line, after[index]]) .filter(([from, to]) => from !== to); - assert.equal(changed.length, 3); + assert.equal(changed.length, 5); for (const [from, to] of changed) { - assert.match(from, /\d+ (languages|translated documentation sets)/); + assert.match(from, LLM_COUNT_LINE); assert.equal( to, - from.replace(/\d+(?= (?:languages|translated documentation sets))/, (n) => - String(Number(n) + 1) - ) + from.replace(LLM_COUNT_NUMBER, (n) => String(Number(n) + 1)) ); } }); +// The counterpart of the test above: at the CURRENT locale total the file is +// already correct, so bumpCounts must be a no-op. This is what would have caught +// the two tree-comment phrasings drifting out of sync. +test("bumpCounts on the real llm.txt is a no-op at the current locale total", () => { + const total = realConfig().locales.length; + const text = readRepo("llm.txt"); + assert.equal(bumpCounts(text, total), text); +}); + // --------------------------------------------------------------------------- // buildMirrorStub // ---------------------------------------------------------------------------