Files
OmniRoute/scripts/i18n/lib/language-bar.mjs
diegosouzapw c1ea96e03f feat(i18n): add 9 European locales (el hr sr lt et lv sl mt ga)
Batch 1 of the locale-expansion plan: Greek, Croatian, Serbian, Lithuanian,
Estonian, Latvian, Slovenian, Maltese and Irish across every surface —
dashboard catalog, docs mirrors, CLI catalog, README, locale index and the
marketing site. OmniRoute now ships all 24 official EU languages (51 locales).

Also fixes two defects the batch exposed:

- The placeholder-parity gate matched every "{…}" pair, so an ICU plural branch
  body (other {s}) counted as an argument named "s" and any correct plural
  translation was reported as drift. The scanner now follows the ICU grammar.
  Three translations that invented a {count} argument the English source never
  defines were corrected, as was one Irish string that translated the argument
  name itself.
- Language bars linked to mirrors that do not exist: docs/guides/I18N.md is
  English-only by design yet keeps legacy mirrors, so every new locale got a
  dead link. Bars now skip locales without a mirror on disk.
2026-09-08 09:15:01 -03:00

86 lines
4.2 KiB
JavaScript
Raw 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.
/**
* Shared builders for the `🌐 **Languages:** …` bars that head every English
* doc and every translated mirror. `config` is the parsed `config/i18n.json`
* (`locales[]` with `code`, `flag`, `native`); all paths are repo-relative POSIX
* (`docs/guides/USER_GUIDE.md`, `README.md`) and the links are relative to the
* file that carries the bar.
*
* buildMirrorBar(rel, locale, config) → docs/i18n/<locale>/… format:
* 🌐 **Languages:** 🇺🇸 [English](../../../README.md) · 🇸🇦 [ar](../ar/README.md) · …
* buildSourceBar(rel, config) → English source format:
* 🌐 **Languages:** 🇺🇸 [English](./USER_GUIDE.md) | 🇧🇷 [Português (Brasil)](../i18n/pt-BR/docs/guides/USER_GUIDE.md) | …
* replaceLanguageBar(markdown, bar) → swaps EVERY language-bar line (any
* `🌐 **<label>:** …[…](…)`, translated labels
* included), `null` when there is none.
*/
import path from "node:path";
const DOCS_I18N = "docs/i18n";
function mirrorPath(relSource, locale) {
return path.posix.join(DOCS_I18N, locale, relSource);
}
/**
* `hasMirror(relSource, locale)` — optional existence predicate. A bar must not
* link to a mirror that is not on disk: `docs/guides/I18N.md` is deliberately
* English-only (see `DOCS_EXCLUDED_NAMES` in run-translation.mjs) yet carries
* legacy mirrors for the older locales, so every newly added locale would get a
* dead link. Omit it and every configured locale is listed, as before.
*/
export function buildMirrorBar(relSource, locale, config, { hasMirror } = {}) {
const targetDir = path.posix.dirname(mirrorPath(relSource, locale));
const parts = [`🇺🇸 [English](${path.posix.relative(targetDir, relSource)})`];
for (const entry of config.locales) {
if (entry.code === "en" || entry.code === locale) continue;
if (hasMirror && !hasMirror(relSource, entry.code)) continue;
const peer = path.posix.relative(targetDir, mirrorPath(relSource, entry.code));
parts.push(`${entry.flag} [${entry.code}](${peer})`);
}
return `🌐 **Languages:** ${parts.join(" · ")}`;
}
export function buildSourceBar(relSource, config, { hasMirror } = {}) {
const sourceDir = path.posix.dirname(relSource);
const parts = [`🇺🇸 [English](./${path.posix.basename(relSource)})`];
for (const entry of config.locales) {
if (entry.code === "en") continue;
if (hasMirror && !hasMirror(relSource, entry.code)) continue;
const peer = path.posix.relative(sourceDir, mirrorPath(relSource, entry.code));
parts.push(`${entry.flag} [${entry.native ?? entry.name}](${peer})`);
}
return `🌐 **Languages:** ${parts.join(" | ")}`;
}
/**
* A language-bar line has BOTH halves of the shape `🌐 **<label>:** …[…](…)`:
*
* 1. a bold label of 140 non-`*` characters closed by a colon (ASCII `:` or the
* fullwidth `` the CJK mirrors use), followed by a space, and
* 2. at least one markdown link — a bar without links is not a bar.
*
* The label is NOT pinned to the canonical English "Languages": older mirrors carry
* whatever the translation backend produced (`🌐 **Idiomas:**`, `🌐 **語言:**`,
* `🌐 **Available in:**`, …) and those bars are exactly the ones that still listed
* retired locales, so syncLanguageBars must normalize them too. Both halves are
* required so a future `🌐 **Website**` or `🌐 **Note:** we support many languages`
* line is never silently overwritten. The label class is bounded and anchored, so
* the regex cannot backtrack on the ~6 KB bar lines.
*/
const LANGUAGE_BAR_LABEL = /^🌐 \*\*[^*]{1,40}[:]\*\* /;
const isLanguageBar = (line) => LANGUAGE_BAR_LABEL.test(line) && line.includes("](");
export function replaceLanguageBar(markdown, bar) {
const lines = markdown.split("\n");
let found = false;
for (let index = 0; index < lines.length; index += 1) {
if (!isLanguageBar(lines[index])) continue;
lines[index] = bar;
found = true;
}
// Every bar in the file describes the same file, so they all get the same
// (correct) list: a stale second bar left in a translated body would otherwise
// keep pointing at locales that no longer exist.
return found ? lines.join("\n") : null;
}