docs(i18n): align every public locale-count claim with the 42-locale config; bumpCounts covers llm.txt file-count phrasings

This commit is contained in:
Markus Hartung
2026-09-02 13:28:11 -03:00
parent df0297e43e
commit f2913f02b3
10 changed files with 58 additions and 28 deletions

View File

@@ -462,7 +462,7 @@ All **19** strategies — mix & match per combo step:
</div>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="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."/>
<img src="./docs/diagrams/comparison-table.svg" width="100%" alt="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."/>
<sub>📊 Full methodology &amp; per-feature detail vs 9router, OpenRouter, CLIProxyAPI &amp; LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md)</sub>
@@ -1295,7 +1295,7 @@ Métricas canônicas em 2026-08-24: **1.029 vídeos únicos** · **11.132.922 vi
<tr><td nowrap><b><a href="docs/ops/BRANCHING_MODEL.md">Branching & Release Model</a></b></td><td>Where PRs target (<code>release/*</code>), what <code>main</code> and tags mean</td></tr>
<tr><td nowrap><b><a href="CHANGELOG.md">Changelog</a></b></td><td>Full per-version release history</td></tr>
<tr><td nowrap><b><a href="SECURITY.md">Security Policy</a></b></td><td>Vulnerability reporting and security practices</td></tr>
<tr><td nowrap><b><a href="docs/guides/I18N.md">i18n Guide</a></b></td><td>43-language support, translation workflow, RTL</td></tr>
<tr><td nowrap><b><a href="docs/guides/I18N.md">i18n Guide</a></b></td><td>42-language support, translation workflow, RTL</td></tr>
<tr><td nowrap><b><a href="docs/ops/RELEASE_CHECKLIST.md">Release Checklist</a></b></td><td>Pre-release validation steps</td></tr>
<tr><td nowrap><b><a href="docs/ops/COVERAGE_PLAN.md">Coverage Plan</a></b></td><td>Test coverage strategy for 39,000+ static test declarations across 5,100+ tracked test files</td></tr>
</table>

View File

@@ -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/

View File

@@ -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`).

View File

@@ -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". |

View File

@@ -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 |

View File

@@ -1,4 +1,4 @@
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 355 providers, 150+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 355 providers, 150+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 42 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
<desc>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.</desc>
<defs>
<pattern id="gC" width="32" height="32" patternUnits="userSpaceOnUse"><path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.05" stroke-width="1"/></pattern>
@@ -117,7 +117,7 @@
<rect x="36" y="632" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="1.14s" fill="freeze"/>
<text x="44" y="658" font-size="14.5" fill="#c9d1d9">i18n UI locales</text>
<text x="440" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">43</text>
<text x="440" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">42</text>
<text x="604" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">6</text>
<use href="#no" x="760" y="653"/>
<use href="#no" x="916" y="653"/>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -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".

View File

@@ -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)

View File

@@ -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/<code>/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 }) {

View File

@@ -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
// ---------------------------------------------------------------------------