From dfe9a3e2885e31f8f56ff661166eae32e26b549a Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Wed, 13 May 2026 16:57:35 -0300 Subject: [PATCH] chore(i18n): point src/i18n/config.ts to config/i18n.json and deprecate legacy scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- scripts/i18n/generate-multilang.mjs | 14 +++ scripts/i18n/i18n_autotranslate.py | 24 +++-- src/i18n/config.ts | 148 +++++++++++----------------- 3 files changed, 90 insertions(+), 96 deletions(-) diff --git a/scripts/i18n/generate-multilang.mjs b/scripts/i18n/generate-multilang.mjs index b2479a50fc..ec4a6f3a61 100644 --- a/scripts/i18n/generate-multilang.mjs +++ b/scripts/i18n/generate-multilang.mjs @@ -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"); diff --git a/scripts/i18n/i18n_autotranslate.py b/scripts/i18n/i18n_autotranslate.py index 17c1d169ef..e05749a3ea 100755 --- a/scripts/i18n/i18n_autotranslate.py +++ b/scripts/i18n/i18n_autotranslate.py @@ -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 diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 0a180c714d..e4d5e3edc2 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -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)! + ); +}