chore(i18n): point src/i18n/config.ts to config/i18n.json and deprecate legacy scripts

src/i18n/config.ts is now a thin adapter that reads config/i18n.json
(canonical locale list) and exports the same shape — LOCALES, LANGUAGES,
RTL_LOCALES, DEFAULT_LOCALE, Locale type, LOCALE_COOKIE — that
LanguageSelector, layout.tsx, request.ts, and the rest of the codebase
already consume. No call-site changes were required. Adds
DOCS_TARGET_LOCALES and getLanguage() helpers for new code.

The legacy scripts now print a deprecation warning on stderr at startup:
  - scripts/i18n/i18n_autotranslate.py    (banner + module-level warn)
  - scripts/i18n/generate-multilang.mjs   (banner + console.warn)

They keep working for messages and readme modes (UI strings / root README
variants), which are still outside the new pipeline's scope. Both will be
removed in v3.10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
diegosouzapw
2026-05-13 16:57:35 -03:00
parent 58b70de654
commit dfe9a3e288
3 changed files with 90 additions and 96 deletions

View File

@@ -1,8 +1,22 @@
#!/usr/bin/env node
/**
* DEPRECATED 2026-05-13. Use `npm run i18n:run`
* (scripts/i18n/run-translation.mjs) for docs translation.
*
* This Google-Translate-backed generator is kept for the `messages` and
* `readme` modes which target `src/i18n/messages/*.json` and root README
* variants — those are not yet handled by the new LLM pipeline. The `docs`
* mode is superseded and will be removed in v3.10.
*/
import { promises as fs } from "node:fs";
import path from "node:path";
console.warn(
"[generate-multilang] DEPRECATED: prefer `npm run i18n:run` for docs (this script will be removed in v3.10)."
);
const ROOT = process.cwd();
const MESSAGES_DIR = path.join(ROOT, "src", "i18n", "messages");
const DOCS_DIR = path.join(ROOT, "docs");

View File

@@ -1,17 +1,27 @@
#!/usr/bin/env python3
"""
OmniRoute i18n Auto-Translator
This script scans all docs/i18n directory markdown files and uses an LLM
API (like OmniRoute itself) to translate any English paragraphs into the
target language.
DEPRECATED 2026-05-13. Use `npm run i18n:run`
(scripts/i18n/run-translation.mjs) instead. This Python script will be
removed in v3.10.
Usage:
python3 scripts/i18n/i18n_autotranslate.py --api-url http://localhost:20128/v1 --api-key sk-your-omniroute-key --model cx/gpt-5.4
The Node-based translator is hash-incremental (only retranslates files
whose source SHA-256 changed), runs the OmniRoute Cloud LLM through
OMNIROUTE_TRANSLATION_* env vars, and is wired into `npm run i18n:run`
and `npm run i18n:check`.
Historical purpose:
Scanned `docs/i18n/` markdown files and translated English paragraphs
into the target language by paragraph through an OpenAI-compatible LLM.
"""
import sys
print(
"WARN: scripts/i18n/i18n_autotranslate.py is deprecated. Use 'npm run i18n:run' instead.",
file=sys.stderr,
)
import os
import re
import sys
import glob
import json
import urllib.request

View File

@@ -1,98 +1,68 @@
export const LOCALES = [
"ar",
"bg",
"bn",
"cs",
"da",
"de",
"en",
"es",
"fa",
"fi",
"fr",
"gu",
"he",
"hi",
"hu",
"id",
"in",
"it",
"ja",
"ko",
"mr",
"ms",
"nl",
"no",
"phi",
"pl",
"pt",
"pt-BR",
"ro",
"ru",
"sk",
"sv",
"sw",
"ta",
"te",
"th",
"tr",
"uk-UA",
"ur",
"vi",
"zh-CN",
] as const;
export type Locale = (typeof LOCALES)[number];
export const DEFAULT_LOCALE: Locale = "en";
// SOURCE OF TRUTH: `config/i18n.json` (also consumed by the docs translation
// pipeline in `scripts/i18n/run-translation.mjs`). Keep this file as a thin
// typed adapter — do NOT add hand-maintained locale lists here.
import i18nConfig from "../../config/i18n.json" with { type: "json" };
type RawLocaleEntry = {
code: string;
label: string;
name: string;
native?: string;
english?: string;
flag: string;
};
type RawI18nConfig = {
default: string;
rtl: readonly string[];
uiOnly?: readonly string[];
docsExcluded?: readonly string[];
locales: readonly RawLocaleEntry[];
};
const config = i18nConfig as RawI18nConfig;
export const LOCALES = config.locales.map((l) => l.code) as readonly string[];
export type Locale = (typeof LOCALES)[number];
export const DEFAULT_LOCALE: Locale = config.default as Locale;
/**
* Display metadata for every locale, kept in the same shape the codebase has
* historically consumed (`code`, `label`, `name`, `flag`). We additionally
* expose `native` and `english` as aliases for new call sites that want a
* stable field name regardless of the underlying display string.
*/
export const LANGUAGES: readonly {
code: Locale;
label: string;
name: string;
native: string;
english: string;
flag: string;
}[] = [
{ code: "ar", label: "AR", name: "العربية", flag: "🇸🇦" },
{ code: "bg", label: "BG", name: "Български", flag: "🇧🇬" },
{ code: "bn", label: "BN", name: "বাংলা", flag: "🇧🇩" },
{ code: "cs", label: "CS", name: "Čeština", flag: "🇨🇿" },
{ code: "da", label: "DA", name: "Dansk", flag: "🇩🇰" },
{ code: "de", label: "DE", name: "Deutsch", flag: "🇩🇪" },
{ code: "en", label: "EN", name: "English", flag: "🇺🇸" },
{ code: "es", label: "ES", name: "Español", flag: "🇪🇸" },
{ code: "fa", label: "FA", name: "فارسی", flag: "🇮🇷" },
{ code: "fi", label: "FI", name: "Suomi", flag: "🇫🇮" },
{ code: "fr", label: "FR", name: "Français", flag: "🇫🇷" },
{ code: "gu", label: "GU", name: "ગુજરાતી", flag: "🇮🇳" },
{ code: "he", label: "HE", name: "עברית", flag: "🇮🇱" },
{ code: "hi", label: "HI", name: "हिन्दी", flag: "🇮🇳" },
{ code: "hu", label: "HU", name: "Magyar", flag: "🇭🇺" },
{ code: "id", label: "ID", name: "Bahasa Indonesia", flag: "🇮🇩" },
{ code: "in", label: "IN", name: "Bahasa Indonesia (Alt)", flag: "🇮🇩" },
{ code: "it", label: "IT", name: "Italiano", flag: "🇮🇹" },
{ code: "ja", label: "JA", name: "日本語", flag: "🇯🇵" },
{ code: "ko", label: "KO", name: "한국어", flag: "🇰🇷" },
{ code: "mr", label: "MR", name: "मराठी", flag: "🇮🇳" },
{ code: "ms", label: "MS", name: "Bahasa Melayu", flag: "🇲🇾" },
{ code: "nl", label: "NL", name: "Nederlands", flag: "🇳🇱" },
{ code: "no", label: "NO", name: "Norsk", flag: "🇳🇴" },
{ code: "phi", label: "PHI", name: "Filipino", flag: "🇵🇭" },
{ code: "pl", label: "PL", name: "Polski", flag: "🇵🇱" },
{ code: "pt-BR", label: "PT-BR", name: "Português (Brasil)", flag: "🇧🇷" },
{ code: "pt", label: "PT", name: "Português (Portugal)", flag: "🇵🇹" },
{ code: "ro", label: "RO", name: "Română", flag: "🇷🇴" },
{ code: "ru", label: "RU", name: "Русский", flag: "🇷🇺" },
{ code: "sk", label: "SK", name: "Slovenčina", flag: "🇸🇰" },
{ code: "sv", label: "SV", name: "Svenska", flag: "🇸🇪" },
{ code: "sw", label: "SW", name: "Kiswahili", flag: "🇰🇪" },
{ code: "ta", label: "TA", name: "தமிழ்", flag: "🇮🇳" },
{ code: "te", label: "TE", name: "తెలుగు", flag: "🇮🇳" },
{ code: "th", label: "TH", name: "ไทย", flag: "🇹🇭" },
{ code: "tr", label: "TR", name: "Türkçe", flag: "🇹🇷" },
{ code: "uk-UA", label: "UK-UA", name: "Українська", flag: "🇺🇦" },
{ code: "ur", label: "UR", name: "اردو", flag: "🇵🇰" },
{ code: "vi", label: "VI", name: "Tiếng Việt", flag: "🇻🇳" },
{ code: "zh-CN", label: "ZH-CN", name: "中文 (简体)", flag: "🇨🇳" },
] as const;
}[] = config.locales.map((entry) => ({
code: entry.code as Locale,
label: entry.label,
name: entry.name,
native: entry.native ?? entry.name,
english: entry.english ?? entry.name,
flag: entry.flag,
}));
export const RTL_LOCALES = ["ar", "fa", "he", "ur"] as const;
export const RTL_LOCALES: readonly Locale[] = config.rtl as readonly Locale[];
export const LOCALE_COOKIE = "NEXT_LOCALE";
// Convenience helpers --------------------------------------------------------
/** Locales that the docs translation pipeline writes to (excludes the source). */
export const DOCS_TARGET_LOCALES: readonly Locale[] = LANGUAGES.map((l) => l.code).filter(
(code) => !(config.docsExcluded ?? []).includes(code)
) as readonly Locale[];
/** Lookup by code; falls back to the default locale entry if not found. */
export function getLanguage(code: string) {
return (
LANGUAGES.find((l) => l.code === code) ?? LANGUAGES.find((l) => l.code === DEFAULT_LOCALE)!
);
}