mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 21:02:50 +03:00
feat(i18n): language bars generated from config (sync-language-bars)
This commit is contained in:
50
scripts/i18n/lib/language-bar.mjs
Normal file
50
scripts/i18n/lib/language-bar.mjs
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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 the first bar line, `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);
|
||||
}
|
||||
|
||||
export function buildMirrorBar(relSource, locale, config) {
|
||||
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;
|
||||
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) {
|
||||
const sourceDir = path.posix.dirname(relSource);
|
||||
const parts = [`🇺🇸 [English](./${path.posix.basename(relSource)})`];
|
||||
for (const entry of config.locales) {
|
||||
if (entry.code === "en") continue;
|
||||
const peer = path.posix.relative(sourceDir, mirrorPath(relSource, entry.code));
|
||||
parts.push(`${entry.flag} [${entry.native ?? entry.name}](${peer})`);
|
||||
}
|
||||
return `🌐 **Languages:** ${parts.join(" | ")}`;
|
||||
}
|
||||
|
||||
export function replaceLanguageBar(markdown, bar) {
|
||||
const lines = markdown.split("\n");
|
||||
const index = lines.findIndex((line) => line.startsWith("🌐 **Languages:**"));
|
||||
if (index === -1) return null;
|
||||
lines[index] = bar;
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import crypto from "node:crypto";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { normalizeLocaleText } from "./glossary-normalize.mjs";
|
||||
import { buildMirrorBar } from "./lib/language-bar.mjs";
|
||||
|
||||
// ----- .env loader --------------------------------------------------------
|
||||
// Loads variables from a local `.env` (gitignored) into process.env without
|
||||
@@ -268,36 +269,6 @@ function targetPathFor(relSource, locale) {
|
||||
return path.join(DOCS_I18N_DIR, locale, relSource);
|
||||
}
|
||||
|
||||
function relativeBackToRoot(targetAbsPath) {
|
||||
// From the target file's directory back to the repo root, used to build the
|
||||
// "🇺🇸 English" link in the language bar.
|
||||
const targetDir = path.dirname(targetAbsPath);
|
||||
const rel = path.relative(targetDir, ROOT);
|
||||
return rel === "" ? "." : rel;
|
||||
}
|
||||
|
||||
function buildLanguageBar(relSource, locale, config) {
|
||||
const targetAbs = targetPathFor(relSource, locale);
|
||||
const targetDir = path.dirname(targetAbs);
|
||||
const rootRel = relativeBackToRoot(targetAbs);
|
||||
|
||||
const parts = [];
|
||||
// English link → source file relative to target dir.
|
||||
const enRel = path.relative(targetDir, path.join(ROOT, relSource));
|
||||
parts.push(`🇺🇸 [English](${enRel.split(path.sep).join("/")})`);
|
||||
|
||||
for (const entry of config.locales) {
|
||||
if (entry.code === "en" || entry.code === locale) continue;
|
||||
const peerAbs = targetPathFor(relSource, entry.code);
|
||||
const peerRel = path.relative(targetDir, peerAbs).split(path.sep).join("/");
|
||||
parts.push(`${entry.flag} [${entry.code}](${peerRel})`);
|
||||
}
|
||||
|
||||
return `🌐 **Languages:** ${parts.join(" · ")}`;
|
||||
// Quiet the unused linter warning for rootRel — kept here for future expansion.
|
||||
void rootRel;
|
||||
}
|
||||
|
||||
function extractTopHeading(markdown) {
|
||||
const m = markdown.match(/^# (.+)\r?\n/);
|
||||
return m ? m[1].trim() : null;
|
||||
@@ -586,7 +557,7 @@ async function main() {
|
||||
const heading = topHeading
|
||||
? `# ${topHeading} (${localeEntry.native})`
|
||||
: `# ${path.basename(task.rel, ".md")} (${localeEntry.native})`;
|
||||
const langBar = buildLanguageBar(task.rel, task.locale, config);
|
||||
const langBar = buildMirrorBar(task.rel, task.locale, config);
|
||||
const rawContent = `${heading}\n\n${langBar}\n\n---\n\n${translatedBody.trim()}\n`;
|
||||
// Pre-format with Prettier (markdown parser) so the on-disk content
|
||||
// matches what `lint-staged` would produce. This keeps `target_hash`
|
||||
|
||||
67
scripts/i18n/sync-language-bars.mjs
Normal file
67
scripts/i18n/sync-language-bars.mjs
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Rewrites every `🌐 **Languages:** …` bar from config/i18n.json:
|
||||
* - English sources (root MDs + docs/**.md that already carry a bar) → buildSourceBar
|
||||
* - mirrors under docs/i18n/<locale>/ → buildMirrorBar
|
||||
* Never inserts a bar where there is none. Idempotent. `--dry-run` lists files.
|
||||
*/
|
||||
import { promises as fs, existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { buildMirrorBar, buildSourceBar, replaceLanguageBar } from "./lib/language-bar.mjs";
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
|
||||
async function walkMd(dir, out = []) {
|
||||
for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
|
||||
const abs = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) await walkMd(abs, out);
|
||||
else if (entry.name.endsWith(".md") || entry.name === "llm.txt") out.push(abs);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function syncLanguageBars({ root = ROOT, dryRun = false } = {}) {
|
||||
const config = JSON.parse(await fs.readFile(path.join(root, "config", "i18n.json"), "utf8"));
|
||||
const changed = [];
|
||||
// 1. English sources under docs/ (excluding docs/i18n).
|
||||
for (const abs of await walkMd(path.join(root, "docs"))) {
|
||||
const rel = path.relative(root, abs).split(path.sep).join("/");
|
||||
if (rel.startsWith("docs/i18n/")) continue;
|
||||
const text = await fs.readFile(abs, "utf8");
|
||||
const next = replaceLanguageBar(text, buildSourceBar(rel, config));
|
||||
if (next && next !== text) {
|
||||
changed.push(rel);
|
||||
if (!dryRun) await fs.writeFile(abs, next, "utf8");
|
||||
}
|
||||
}
|
||||
// 2. Mirrors.
|
||||
for (const entry of config.locales) {
|
||||
const dir = path.join(root, "docs", "i18n", entry.code);
|
||||
if (!existsSync(dir)) continue;
|
||||
for (const abs of await walkMd(dir)) {
|
||||
const relInMirror = path.relative(dir, abs).split(path.sep).join("/");
|
||||
const text = await fs.readFile(abs, "utf8");
|
||||
const next = replaceLanguageBar(text, buildMirrorBar(relInMirror, entry.code, config));
|
||||
if (next && next !== text) {
|
||||
changed.push(path.relative(root, abs).split(path.sep).join("/"));
|
||||
if (!dryRun) await fs.writeFile(abs, next, "utf8");
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
||||
if (isDirectRun) {
|
||||
const dryRun = process.argv.includes("--dry-run");
|
||||
syncLanguageBars({ dryRun })
|
||||
.then((changed) =>
|
||||
console.log(`[i18n-bars] ${dryRun ? "would update" : "updated"} ${changed.length} file(s)`)
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error("[i18n-bars] ERROR", err?.stack || err?.message || String(err));
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user