diff --git a/bin/cli/CONVENTIONS.md b/bin/cli/CONVENTIONS.md index 86e94d94db..e46caa0b27 100644 --- a/bin/cli/CONVENTIONS.md +++ b/bin/cli/CONVENTIONS.md @@ -135,11 +135,25 @@ export const RETRY_DEFAULTS = { ## 6. Internationalization - Every user-facing string goes through `t("module.key", vars)`. -- Catalogs live in `bin/cli/locales/{en,pt-BR}.json` (nested objects). -- Detection: `OMNIROUTE_LANG` overrides, otherwise `LC_ALL`, `LC_MESSAGES`, - `LANG`. Fallback: `en`. -- Missing keys return the key itself (no crash). PRs that add new strings - must update both `en` and `pt-BR` catalogs. +- Catalogs live in `bin/cli/locales/{locale}.json` (nested objects). + 42 files ship out-of-the-box: `en`, `pt-BR`, and 40 additional locales. + 11 locales are scaffold-only (empty `{}`); all keys fall back to `en` automatically. +- Detection order: `--lang` flag → `OMNIROUTE_LANG` env → `LC_ALL` → `LC_MESSAGES` → `LANG` → `en`. +- Locale persisted via `config lang set ` — saves `OMNIROUTE_LANG` to `~/.omniroute/.env`. +- Missing keys return the key itself (no crash). +- PRs that add new strings **must** update `en.json` and `pt-BR.json`. + Other locale files are best-effort; missing keys silently fall back to `en`. +- `normalize()` in `i18n.mjs` validates locale codes via `/^[a-zA-Z0-9-]+$/` to + prevent path traversal — never pass raw filesystem paths. +- Canonical locale list: `config/i18n.json` — source of truth used by both CLI and + dashboard i18n pipelines. + +### Adding a new locale file + +1. Add entry to `config/i18n.json` with `code`, `english`, `native`, `flag`. +2. Run `node bin/cli/scripts/generate-locales.mjs` — creates `bin/cli/locales/{code}.json`. +3. Fill in translations (or leave as `{}` for en-fallback scaffold). +4. The pre-commit hook `check-cli-i18n` will verify all `t()` keys exist in `en.json`. ## 7. Logs / output channels diff --git a/bin/cli/README.md b/bin/cli/README.md index 7342ae07a4..00c9dd06e4 100644 --- a/bin/cli/README.md +++ b/bin/cli/README.md @@ -8,8 +8,7 @@ This directory contains the CLI runtime, helpers, and commands for the `omnirout bin/cli/ ├── CONVENTIONS.md ← normative design rules (read this first) ├── README.md ← this file -├── index.mjs ← central command router (will migrate to Commander in 1.1) -├── args.mjs ← legacy arg parser (replaced by Commander in 1.1) +├── program.mjs ← Commander setup — global flags, registerCommands() ├── api.mjs ← apiFetch() — all HTTP calls + retry/backoff ├── runtime.mjs ← withRuntime() — server-first / DB-fallback ├── i18n.mjs ← t() — i18n helper + locale detection @@ -23,13 +22,16 @@ bin/cli/ ├── provider-test.mjs ← testProviderApiKey() ├── settings-store.mjs ← DB CRUD for key_value settings ├── locales/ -│ ├── en.json ← English strings -│ └── pt-BR.json ← Portuguese (Brazil) strings +│ ├── en.json ← English strings (source of truth, 42+ locales) +│ ├── pt-BR.json ← Portuguese (Brazil) — fully translated +│ └── {locale}.json ← 40 additional locales (ar, az, de, es, fr, ja, zh-CN, …) +├── scripts/ +│ └── generate-locales.mjs ← scaffold new locale files from config/i18n.json └── commands/ ├── setup.mjs ├── doctor.mjs ├── providers.mjs - ├── config.mjs + ├── config.mjs ← includes `config lang get/set/list` ├── status.mjs ├── logs.mjs └── update.mjs @@ -103,12 +105,42 @@ printError("Something went wrong"); process.exit(EXIT_CODES.SERVER_OFFLINE); ``` +## Locale selection + +The CLI displays text in the user's language. Detection order: + +1. `--lang ` flag on the command line +2. `OMNIROUTE_LANG` environment variable +3. System env: `LC_ALL` → `LC_MESSAGES` → `LANG` +4. Fallback: `en` + +**Set permanently:** + +```bash +omniroute config lang set pt-BR # saves to ~/.omniroute/.env +omniroute config lang list # show all 42 available locales +omniroute config lang get # show currently active locale +``` + +**One-time override:** + +```bash +omniroute --lang de providers list # run in German, not persisted +OMNIROUTE_LANG=ja omniroute status # same effect via env +``` + +**Adding a new locale**: add entry to `config/i18n.json`, then run: + +```bash +node bin/cli/scripts/generate-locales.mjs +``` + ## Adding a new command 1. Create `bin/cli/commands/your-command.mjs` -2. Export `runYourCommand(argv, context)` (pre-1.1) or `registerYourCommand(program)` (post-1.1) -3. Register in `bin/cli/index.mjs` (pre-1.1) or `bin/cli/program.mjs` (post-1.1) -4. Add strings to both `locales/en.json` and `locales/pt-BR.json` +2. Export `registerYourCommand(program)` following the Commander pattern +3. Register in `bin/cli/commands/registry.mjs` +4. Add strings to `locales/en.json` and `locales/pt-BR.json` 5. Write test in `tests/unit/cli-your-command.test.ts` See `CONVENTIONS.md` for exit codes, flag naming, output format, and destructive-action rules. diff --git a/bin/cli/commands/config.mjs b/bin/cli/commands/config.mjs index 9db715bbdc..bdea980ac6 100644 --- a/bin/cli/commands/config.mjs +++ b/bin/cli/commands/config.mjs @@ -2,6 +2,8 @@ import { printHeading, printInfo, printSuccess, printError } from "../io.mjs"; import { t } from "../i18n.mjs"; import path from "node:path"; import fs from "node:fs"; +import { fileURLToPath } from "node:url"; +import { resolveDataDir } from "../data-dir.mjs"; import { registerContexts } from "./contexts.mjs"; function ensureBackup(configPath) { @@ -140,6 +142,106 @@ async function runConfigValidateCommand(toolId, opts = {}) { return 0; } +function loadI18nLocales() { + const cfgPath = path.join( + path.dirname(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url))))), + "config", + "i18n.json" + ); + try { + return JSON.parse(fs.readFileSync(cfgPath, "utf8")).locales || []; + } catch { + return []; + } +} + +function getCliEnvPath() { + return path.join(resolveDataDir(), ".env"); +} + +function upsertEnvLine(envPath, key, value) { + let content = ""; + if (fs.existsSync(envPath)) content = fs.readFileSync(envPath, "utf8"); + const lines = content.split("\n"); + const idx = lines.findIndex((l) => l.trimStart().startsWith(`${key}=`)); + const newLine = `${key}=${value}`; + if (idx >= 0) { + lines[idx] = newLine; + } else { + if (content && !content.endsWith("\n")) lines.push(""); + lines.push(newLine); + } + const dir = path.dirname(envPath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + const tmp = `${envPath}.tmp`; + fs.writeFileSync(tmp, lines.join("\n"), "utf8"); + fs.renameSync(tmp, envPath); +} + +export async function runConfigLangGetCommand(opts = {}) { + const { getLocale } = await import("../i18n.mjs"); + const code = getLocale(); + const locales = loadI18nLocales(); + const entry = locales.find((l) => l.code === code); + const name = entry ? entry.english : code; + if (opts.output === "json" || opts.json) { + console.log(JSON.stringify({ code, name }, null, 2)); + } else { + console.log(t("config.lang.current", { code, name })); + } + return 0; +} + +export async function runConfigLangSetCommand(code, opts = {}) { + if (!code) { + console.error(t("config.lang.noCode")); + return 1; + } + const locales = loadI18nLocales(); + const entry = locales.find((l) => l.code === code); + if (!entry) { + console.error(t("config.lang.unknown", { code })); + return 1; + } + const { getLocale, setLocale } = await import("../i18n.mjs"); + const current = getLocale(); + if (current === code && !opts.force) { + console.log(t("config.lang.alreadySet", { code })); + return 0; + } + const envPath = getCliEnvPath(); + upsertEnvLine(envPath, "OMNIROUTE_LANG", code); + setLocale(code); + console.log(t("config.lang.saved", { code, name: entry.english })); + console.log(t("config.lang.envHint", { code })); + return 0; +} + +export async function runConfigLangListCommand(opts = {}) { + const { getLocale } = await import("../i18n.mjs"); + const current = getLocale(); + const locales = loadI18nLocales(); + if (opts.output === "json" || opts.json) { + console.log( + JSON.stringify( + locales.map((l) => ({ ...l, active: l.code === current })), + null, + 2 + ) + ); + return 0; + } + console.log(`\n\x1b[1m\x1b[36m${t("config.lang.listTitle")}\x1b[0m\n`); + for (const loc of locales) { + const active = loc.code === current ? " \x1b[32m◀ active\x1b[0m" : ""; + console.log( + ` ${loc.flag} ${loc.code.padEnd(8)} ${loc.english.padEnd(28)} ${loc.native}${active}` + ); + } + console.log(""); + return 0; +} + export function registerConfig(program) { const config = program.command("config").description("Show or update CLI tool configuration"); @@ -190,6 +292,35 @@ export function registerConfig(program) { if (exitCode !== 0) process.exit(exitCode); }); + // lang subgroup + const lang = config.command("lang").description(t("config.lang.description")); + lang + .command("get") + .description(t("config.lang.getDescription")) + .option("--json", t("common.jsonOpt")) + .action(async (opts, cmd) => { + const globalOpts = cmd.parent.parent.optsWithGlobals(); + const exitCode = await runConfigLangGetCommand({ ...opts, output: globalOpts.output }); + if (exitCode !== 0) process.exit(exitCode); + }); + lang + .command("set ") + .description(t("config.lang.setDescription")) + .option("--force", "Set even if already active") + .action(async (code, opts, cmd) => { + const exitCode = await runConfigLangSetCommand(code, opts); + if (exitCode !== 0) process.exit(exitCode); + }); + lang + .command("list") + .description(t("config.lang.listDescription")) + .option("--json", t("common.jsonOpt")) + .action(async (opts, cmd) => { + const globalOpts = cmd.parent.parent.optsWithGlobals(); + const exitCode = await runConfigLangListCommand({ ...opts, output: globalOpts.output }); + if (exitCode !== 0) process.exit(exitCode); + }); + // Register contexts/profiles CRUD as a subgroup of config. registerContexts(config); } diff --git a/bin/cli/i18n.mjs b/bin/cli/i18n.mjs index c4fb7d0973..70cae65232 100644 --- a/bin/cli/i18n.mjs +++ b/bin/cli/i18n.mjs @@ -21,8 +21,8 @@ export function detectLocale() { } function normalize(raw) { - const stripped = String(raw).split(".")[0].replace("_", "-"); - if (!stripped) return FALLBACK_LOCALE; + const stripped = String(raw).split(".")[0].replaceAll("_", "-"); + if (!stripped || !/^[a-zA-Z0-9-]+$/.test(stripped)) return FALLBACK_LOCALE; if (hasCatalog(stripped)) return stripped; const base = stripped.split("-")[0]; if (hasCatalog(base)) return base; diff --git a/bin/cli/locales/ar.json b/bin/cli/locales/ar.json new file mode 100644 index 0000000000..de6dd790da --- /dev/null +++ b/bin/cli/locales/ar.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "خطأ: {message}", + "serverOffline": "خادم OmniRoute غير متصل. ابدأ بالأمر: omniroute serve", + "authRequired": "المصادقة مطلوبة. عيّن OMNIROUTE_API_KEY أو شغّل: omniroute setup", + "rateLimited": "تم تجاوز حد الطلبات. أعد المحاولة بعد {seconds} ثانية.", + "timeout": "انتهت مهلة الطلب بعد {ms}ms.", + "success": "تم.", + "yes": "نعم", + "no": "لا", + "confirm": "هل أنت متأكد؟ (نعم/لا)", + "dryRun": "[محاكاة] سيتم: {action}", + "cancelled": "تم الإلغاء.", + "jsonOpt": "إخراج بتنسيق JSON", + "yesOpt": "تخطي رسالة التأكيد" + }, + "program": { + "description": "OmniRoute — جهاز توجيه الذكاء الاصطناعي مع التبديل التلقائي", + "version": "عرض الإصدار والخروج", + "output": "تنسيق الإخراج (table, json, jsonl, csv)", + "quiet": "إخفاء المخرجات غير الأساسية", + "no_color": "تعطيل الإخراج الملوّن", + "timeout": "مهلة طلب HTTP بالميلي ثانية", + "api_key": "مفتاح API لخادم OmniRoute", + "base_url": "عنوان URL الأساسي لخادم OmniRoute", + "context": "سياق/ملف تعريف الخادم المستخدم في هذا الأمر", + "lang": "تعيين لغة عرض CLI (يتجاوز OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/az.json b/bin/cli/locales/az.json new file mode 100644 index 0000000000..12b46ef0ab --- /dev/null +++ b/bin/cli/locales/az.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Xəta: {message}", + "serverOffline": "OmniRoute serveri oflayndır. Başladın: omniroute serve", + "authRequired": "Autentifikasiya tələb olunur. OMNIROUTE_API_KEY təyin edin və ya işə salın: omniroute setup", + "rateLimited": "Sorğu limiti aşıldı. {seconds} saniyə sonra yenidən cəhd edin.", + "timeout": "Sorğunun vaxtı {ms}ms sonra bitdi.", + "success": "Tamamlandı.", + "yes": "bəli", + "no": "xeyr", + "confirm": "Əminsiniz? (bəli/xeyr)", + "dryRun": "[simulyasiya] ediləcəkdi: {action}", + "cancelled": "Ləğv edildi.", + "jsonOpt": "JSON formatında çıxış", + "yesOpt": "Təsdiq sorğusunu keç" + }, + "program": { + "description": "OmniRoute — Avtomatik Fallback ilə Ağıllı AI Marşrutlaşdırıcısı", + "version": "Versiyasını çap et və çıx", + "output": "Çıxış formatı (table, json, jsonl, csv)", + "quiet": "Vacib olmayan çıxışı gizlət", + "no_color": "Rəngli çıxışı deaktiv et", + "timeout": "HTTP sorğusu üçün zaman aşımı (millisaniyə)", + "api_key": "OmniRoute serveri üçün API açarı", + "base_url": "OmniRoute server baza URL-i", + "context": "Bu əmr üçün server konteksti/profili", + "lang": "CLI ekran dilini təyin edin (OMNIROUTE_LANG-ı keçir)" + } +} diff --git a/bin/cli/locales/bg.json b/bin/cli/locales/bg.json new file mode 100644 index 0000000000..8feb356cf7 --- /dev/null +++ b/bin/cli/locales/bg.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Грешка: {message}", + "serverOffline": "Сървърът OmniRoute е офлайн. Стартирайте с: omniroute serve", + "authRequired": "Необходима е автентикация. Задайте OMNIROUTE_API_KEY или изпълнете: omniroute setup", + "rateLimited": "Превишен лимит на заявки. Опитайте след {seconds}с.", + "timeout": "Заявката изтече след {ms}ms.", + "success": "Готово.", + "yes": "да", + "no": "не", + "confirm": "Сигурни ли сте? (да/не)", + "dryRun": "[симулация] ще извърши: {action}", + "cancelled": "Отменено.", + "jsonOpt": "Изход като JSON", + "yesOpt": "Пропускане на потвърждение" + }, + "program": { + "description": "OmniRoute — Интелигентен AI рутер с автоматично превключване", + "version": "Покажи версията и излез", + "output": "Формат на изхода (table, json, jsonl, csv)", + "quiet": "Потисни несъществена информация", + "no_color": "Деактивирай цветния изход", + "timeout": "Таймаут за HTTP заявки в милисекунди", + "api_key": "API ключ за сървъра OmniRoute", + "base_url": "Базов URL на сървъра OmniRoute", + "context": "Контекст/профил на сървъра за тази команда", + "lang": "Задай език на CLI (замества OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/bn.json b/bin/cli/locales/bn.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/bn.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/cs.json b/bin/cli/locales/cs.json new file mode 100644 index 0000000000..b608c57506 --- /dev/null +++ b/bin/cli/locales/cs.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Chyba: {message}", + "serverOffline": "Server OmniRoute je offline. Spusťte: omniroute serve", + "authRequired": "Vyžaduje se ověření. Nastavte OMNIROUTE_API_KEY nebo spusťte: omniroute setup", + "rateLimited": "Překročen limit požadavků. Zkuste za {seconds}s.", + "timeout": "Požadavek vypršel po {ms}ms.", + "success": "Hotovo.", + "yes": "ano", + "no": "ne", + "confirm": "Jste si jisti? (ano/ne)", + "dryRun": "[simulace] by provedlo: {action}", + "cancelled": "Zrušeno.", + "jsonOpt": "Výstup jako JSON", + "yesOpt": "Přeskočit potvrzení" + }, + "program": { + "description": "OmniRoute — Chytrý AI router s automatickým přepínáním", + "version": "Vypsat verzi a skončit", + "output": "Formát výstupu (table, json, jsonl, csv)", + "quiet": "Potlačit nepodstatný výstup", + "no_color": "Zakázat barevný výstup", + "timeout": "Časový limit HTTP požadavků v milisekundách", + "api_key": "API klíč pro server OmniRoute", + "base_url": "Základní URL serveru OmniRoute", + "context": "Kontext/profil serveru pro tento příkaz", + "lang": "Nastavit jazyk CLI (přepisuje OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/da.json b/bin/cli/locales/da.json new file mode 100644 index 0000000000..1dedcd96cd --- /dev/null +++ b/bin/cli/locales/da.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Fejl: {message}", + "serverOffline": "OmniRoute-serveren er offline. Start med: omniroute serve", + "authRequired": "Godkendelse kræves. Sæt OMNIROUTE_API_KEY eller kør: omniroute setup", + "rateLimited": "Anmodningsgrænse overskredet. Prøv igen om {seconds}s.", + "timeout": "Anmodningen timed ud efter {ms}ms.", + "success": "Færdig.", + "yes": "ja", + "no": "nej", + "confirm": "Er du sikker? (ja/nej)", + "dryRun": "[simulering] ville: {action}", + "cancelled": "Annulleret.", + "jsonOpt": "Output som JSON", + "yesOpt": "Spring bekræftelse over" + }, + "program": { + "description": "OmniRoute — Smart AI-router med automatisk fallback", + "version": "Vis version og afslut", + "output": "Outputformat (table, json, jsonl, csv)", + "quiet": "Undertryk ikke-essentielt output", + "no_color": "Deaktiver farvet output", + "timeout": "HTTP-anmodnings timeout i millisekunder", + "api_key": "API-nøgle til OmniRoute-serveren", + "base_url": "OmniRoute-serverens basis-URL", + "context": "Server-kontekst/profil til denne kommando", + "lang": "Angiv CLI-visningssprog (tilsidesætter OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/de.json b/bin/cli/locales/de.json new file mode 100644 index 0000000000..90eb78f4b5 --- /dev/null +++ b/bin/cli/locales/de.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Fehler: {message}", + "serverOffline": "OmniRoute-Server ist offline. Starten mit: omniroute serve", + "authRequired": "Authentifizierung erforderlich. OMNIROUTE_API_KEY setzen oder ausführen: omniroute setup", + "rateLimited": "Anfragelimit überschritten. Erneut versuchen in {seconds}s.", + "timeout": "Anfrage-Timeout nach {ms}ms.", + "success": "Fertig.", + "yes": "ja", + "no": "nein", + "confirm": "Sind Sie sicher? (ja/nein)", + "dryRun": "[Simulation] würde: {action}", + "cancelled": "Abgebrochen.", + "jsonOpt": "Ausgabe als JSON", + "yesOpt": "Bestätigung überspringen" + }, + "program": { + "description": "OmniRoute — Intelligenter AI-Router mit automatischem Fallback", + "version": "Version ausgeben und beenden", + "output": "Ausgabeformat (table, json, jsonl, csv)", + "quiet": "Unwesentliche Ausgabe unterdrücken", + "no_color": "Farbige Ausgabe deaktivieren", + "timeout": "HTTP-Anfrage-Timeout in Millisekunden", + "api_key": "API-Schlüssel für den OmniRoute-Server", + "base_url": "OmniRoute-Server-Basis-URL", + "context": "Server-Kontext/Profil für diesen Befehl", + "lang": "CLI-Anzeigesprache festlegen (überschreibt OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/en.json b/bin/cli/locales/en.json index f217dc1866..c8d5071298 100644 --- a/bin/cli/locales/en.json +++ b/bin/cli/locales/en.json @@ -776,7 +776,8 @@ "timeout": "HTTP request timeout in milliseconds", "api_key": "API key for OmniRoute server", "base_url": "OmniRoute server base URL", - "context": "Server context/profile to use for this command" + "context": "Server context/profile to use for this command", + "lang": "Set CLI display language (overrides OMNIROUTE_LANG)" }, "files": { "description": "Manage files (upload, list, get, download, delete)", @@ -1154,6 +1155,19 @@ "config": { "contexts": { "description": "Manage server contexts/profiles (add, use, list, show, remove, rename, export, import)" + }, + "lang": { + "description": "Manage CLI display language", + "getDescription": "Show the currently active language code", + "setDescription": "Set the display language and save it to config", + "listDescription": "List all available languages", + "listTitle": "Available Languages", + "current": "Language: {code} ({name})", + "saved": "Language set to {code} ({name}).", + "noCode": "Language code required. Run 'omniroute config lang list' to see available codes.", + "unknown": "Unknown language code: {code}. Run 'omniroute config lang list' to see available codes.", + "alreadySet": "Language is already set to {code}.", + "envHint": "Tip: you can also set OMNIROUTE_LANG={code} in your environment." } }, "completion": { diff --git a/bin/cli/locales/es.json b/bin/cli/locales/es.json new file mode 100644 index 0000000000..31593a07b9 --- /dev/null +++ b/bin/cli/locales/es.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Error: {message}", + "serverOffline": "El servidor OmniRoute está offline. Inícielo con: omniroute serve", + "authRequired": "Autenticación requerida. Configure OMNIROUTE_API_KEY o ejecute: omniroute setup", + "rateLimited": "Límite de solicitudes excedido. Reintente en {seconds}s.", + "timeout": "Solicitud expiró después de {ms}ms.", + "success": "Listo.", + "yes": "sí", + "no": "no", + "confirm": "¿Está seguro? (sí/no)", + "dryRun": "[simulación] haría: {action}", + "cancelled": "Cancelado.", + "jsonOpt": "Salida como JSON", + "yesOpt": "Omitir confirmación" + }, + "program": { + "description": "OmniRoute — Router de IA inteligente con fallback automático", + "version": "Mostrar versión y salir", + "output": "Formato de salida (table, json, jsonl, csv)", + "quiet": "Suprimir salida no esencial", + "no_color": "Deshabilitar salida en color", + "timeout": "Tiempo de espera de solicitudes HTTP en milisegundos", + "api_key": "Clave de API para el servidor OmniRoute", + "base_url": "URL base del servidor OmniRoute", + "context": "Contexto/perfil del servidor para este comando", + "lang": "Establecer idioma del CLI (reemplaza OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/fa.json b/bin/cli/locales/fa.json new file mode 100644 index 0000000000..106bc6e928 --- /dev/null +++ b/bin/cli/locales/fa.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "خطا: {message}", + "serverOffline": "سرور OmniRoute آفلاین است. با این دستور راه‌اندازی کنید: omniroute serve", + "authRequired": "احراز هویت لازم است. OMNIROUTE_API_KEY را تنظیم کنید یا اجرا کنید: omniroute setup", + "rateLimited": "محدودیت درخواست رسیده است. پس از {seconds} ثانیه دوباره تلاش کنید.", + "timeout": "درخواست پس از {ms}ms منقضی شد.", + "success": "انجام شد.", + "yes": "بله", + "no": "خیر", + "confirm": "مطمئنید؟ (بله/خیر)", + "dryRun": "[شبیه‌سازی] اقدام می‌شد: {action}", + "cancelled": "لغو شد.", + "jsonOpt": "خروجی به صورت JSON", + "yesOpt": "رد کردن تأیید" + }, + "program": { + "description": "OmniRoute — روتر هوشمند هوش مصنوعی با fallback خودکار", + "version": "نمایش نسخه و خروج", + "output": "فرمت خروجی (table, json, jsonl, csv)", + "quiet": "حذف خروجی غیر ضروری", + "no_color": "غیرفعال کردن خروجی رنگی", + "timeout": "تایم‌اوت درخواست HTTP به میلی‌ثانیه", + "api_key": "کلید API برای سرور OmniRoute", + "base_url": "URL پایه سرور OmniRoute", + "context": "زمینه/پروفایل سرور برای این دستور", + "lang": "تنظیم زبان نمایش CLI (OMNIROUTE_LANG را نادیده می‌گیرد)" + } +} diff --git a/bin/cli/locales/fi.json b/bin/cli/locales/fi.json new file mode 100644 index 0000000000..7f57bba56f --- /dev/null +++ b/bin/cli/locales/fi.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Virhe: {message}", + "serverOffline": "OmniRoute-palvelin on offline. Käynnistä komennolla: omniroute serve", + "authRequired": "Todennus vaaditaan. Aseta OMNIROUTE_API_KEY tai suorita: omniroute setup", + "rateLimited": "Pyyntöraja ylitetty. Yritä uudelleen {seconds}s kuluttua.", + "timeout": "Pyyntö aikakatkaistiin {ms}ms jälkeen.", + "success": "Valmis.", + "yes": "kyllä", + "no": "ei", + "confirm": "Oletko varma? (kyllä/ei)", + "dryRun": "[simulointi] tekisi: {action}", + "cancelled": "Peruutettu.", + "jsonOpt": "Tulosta JSON-muodossa", + "yesOpt": "Ohita vahvistuspyyntö" + }, + "program": { + "description": "OmniRoute — Älykäs AI-reititin automaattisella fallbackilla", + "version": "Tulosta versio ja poistu", + "output": "Tulostusmuoto (table, json, jsonl, csv)", + "quiet": "Piilota epäolennaiset tulosteet", + "no_color": "Poista väritulostus käytöstä", + "timeout": "HTTP-pyyntöjen aikakatkaisu millisekunteina", + "api_key": "API-avain OmniRoute-palvelimelle", + "base_url": "OmniRoute-palvelimen perus-URL", + "context": "Palvelimen konteksti/profiili tälle komennolle", + "lang": "Aseta CLI-näyttökieli (ohittaa OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/fr.json b/bin/cli/locales/fr.json new file mode 100644 index 0000000000..8ec0382cee --- /dev/null +++ b/bin/cli/locales/fr.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Erreur : {message}", + "serverOffline": "Le serveur OmniRoute est hors ligne. Démarrez avec : omniroute serve", + "authRequired": "Authentification requise. Définissez OMNIROUTE_API_KEY ou exécutez : omniroute setup", + "rateLimited": "Limite de requêtes atteinte. Réessayez dans {seconds}s.", + "timeout": "La requête a expiré après {ms}ms.", + "success": "Terminé.", + "yes": "oui", + "no": "non", + "confirm": "Êtes-vous sûr ? (oui/non)", + "dryRun": "[simulation] ferait : {action}", + "cancelled": "Annulé.", + "jsonOpt": "Sortie au format JSON", + "yesOpt": "Ignorer la confirmation" + }, + "program": { + "description": "OmniRoute — Routeur IA intelligent avec basculement automatique", + "version": "Afficher la version et quitter", + "output": "Format de sortie (table, json, jsonl, csv)", + "quiet": "Supprimer les sorties non essentielles", + "no_color": "Désactiver la sortie en couleur", + "timeout": "Délai d'expiration des requêtes HTTP en millisecondes", + "api_key": "Clé API pour le serveur OmniRoute", + "base_url": "URL de base du serveur OmniRoute", + "context": "Contexte/profil du serveur pour cette commande", + "lang": "Définir la langue d'affichage du CLI (remplace OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/gu.json b/bin/cli/locales/gu.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/gu.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/he.json b/bin/cli/locales/he.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/he.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/hi.json b/bin/cli/locales/hi.json new file mode 100644 index 0000000000..2bc859c477 --- /dev/null +++ b/bin/cli/locales/hi.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "त्रुटि: {message}", + "serverOffline": "OmniRoute सर्वर ऑफलाइन है। शुरू करें: omniroute serve", + "authRequired": "प्रमाणीकरण आवश्यक है। OMNIROUTE_API_KEY सेट करें या चलाएं: omniroute setup", + "rateLimited": "अनुरोध सीमा पार हो गई। {seconds}s बाद पुनः प्रयास करें।", + "timeout": "{ms}ms के बाद अनुरोध समय समाप्त हुआ।", + "success": "पूर्ण।", + "yes": "हाँ", + "no": "नहीं", + "confirm": "क्या आप सुनिश्चित हैं? (हाँ/नहीं)", + "dryRun": "[अनुकरण] करेगा: {action}", + "cancelled": "रद्द किया गया।", + "jsonOpt": "JSON के रूप में आउटपुट", + "yesOpt": "पुष्टि छोड़ें" + }, + "program": { + "description": "OmniRoute — ऑटो फॉलबैक के साथ स्मार्ट AI राउटर", + "version": "संस्करण प्रिंट करें और बाहर निकलें", + "output": "आउटपुट प्रारूप (table, json, jsonl, csv)", + "quiet": "गैर-आवश्यक आउटपुट दबाएं", + "no_color": "रंगीन आउटपुट अक्षम करें", + "timeout": "HTTP अनुरोध टाइमआउट मिलीसेकंड में", + "api_key": "OmniRoute सर्वर के लिए API कुंजी", + "base_url": "OmniRoute सर्वर का बेस URL", + "context": "इस कमांड के लिए सर्वर संदर्भ/प्रोफ़ाइल", + "lang": "CLI प्रदर्शन भाषा सेट करें (OMNIROUTE_LANG को ओवरराइड करता है)" + } +} diff --git a/bin/cli/locales/hu.json b/bin/cli/locales/hu.json new file mode 100644 index 0000000000..c590899841 --- /dev/null +++ b/bin/cli/locales/hu.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Hiba: {message}", + "serverOffline": "Az OmniRoute szerver offline. Indítsa el: omniroute serve", + "authRequired": "Hitelesítés szükséges. Állítsa be az OMNIROUTE_API_KEY-t vagy futtassa: omniroute setup", + "rateLimited": "Kérési korlát túllépve. Próbálja újra {seconds}s múlva.", + "timeout": "A kérés {ms}ms után lejárt.", + "success": "Kész.", + "yes": "igen", + "no": "nem", + "confirm": "Biztos benne? (igen/nem)", + "dryRun": "[szimuláció] végrehajtaná: {action}", + "cancelled": "Törölve.", + "jsonOpt": "JSON formátumú kimenet", + "yesOpt": "Megerősítés kihagyása" + }, + "program": { + "description": "OmniRoute — Intelligens AI útválasztó automatikus fallbackkel", + "version": "Verzió kiírása és kilépés", + "output": "Kimeneti formátum (table, json, jsonl, csv)", + "quiet": "Nem lényeges kimenet elnyomása", + "no_color": "Színes kimenet letiltása", + "timeout": "HTTP kérés időtúllépése ezredmásodpercben", + "api_key": "API kulcs az OmniRoute szerverhez", + "base_url": "Az OmniRoute szerver alap URL-je", + "context": "Szerverkontextus/profil ehhez a parancshoz", + "lang": "CLI megjelenítési nyelv beállítása (felülírja az OMNIROUTE_LANG-ot)" + } +} diff --git a/bin/cli/locales/id.json b/bin/cli/locales/id.json new file mode 100644 index 0000000000..5236d01004 --- /dev/null +++ b/bin/cli/locales/id.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Kesalahan: {message}", + "serverOffline": "Server OmniRoute sedang offline. Mulai dengan: omniroute serve", + "authRequired": "Autentikasi diperlukan. Setel OMNIROUTE_API_KEY atau jalankan: omniroute setup", + "rateLimited": "Batas permintaan terlampaui. Coba lagi dalam {seconds}d.", + "timeout": "Permintaan habis waktu setelah {ms}ms.", + "success": "Selesai.", + "yes": "ya", + "no": "tidak", + "confirm": "Apakah Anda yakin? (ya/tidak)", + "dryRun": "[simulasi] akan: {action}", + "cancelled": "Dibatalkan.", + "jsonOpt": "Keluaran sebagai JSON", + "yesOpt": "Lewati konfirmasi" + }, + "program": { + "description": "OmniRoute — Router AI Cerdas dengan Fallback Otomatis", + "version": "Cetak versi dan keluar", + "output": "Format keluaran (table, json, jsonl, csv)", + "quiet": "Sembunyikan output yang tidak penting", + "no_color": "Nonaktifkan output berwarna", + "timeout": "Batas waktu permintaan HTTP dalam milidetik", + "api_key": "Kunci API untuk server OmniRoute", + "base_url": "URL dasar server OmniRoute", + "context": "Konteks/profil server untuk perintah ini", + "lang": "Atur bahasa tampilan CLI (menggantikan OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/in.json b/bin/cli/locales/in.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/in.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/it.json b/bin/cli/locales/it.json new file mode 100644 index 0000000000..012f9d4d78 --- /dev/null +++ b/bin/cli/locales/it.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Errore: {message}", + "serverOffline": "Il server OmniRoute è offline. Avviarlo con: omniroute serve", + "authRequired": "Autenticazione richiesta. Impostare OMNIROUTE_API_KEY o eseguire: omniroute setup", + "rateLimited": "Limite di richieste superato. Riprovare tra {seconds}s.", + "timeout": "La richiesta è scaduta dopo {ms}ms.", + "success": "Completato.", + "yes": "sì", + "no": "no", + "confirm": "Sei sicuro? (sì/no)", + "dryRun": "[simulazione] eseguirebbe: {action}", + "cancelled": "Annullato.", + "jsonOpt": "Output come JSON", + "yesOpt": "Salta la conferma" + }, + "program": { + "description": "OmniRoute — Router AI intelligente con fallback automatico", + "version": "Stampa la versione ed esci", + "output": "Formato di output (table, json, jsonl, csv)", + "quiet": "Sopprimi l'output non essenziale", + "no_color": "Disabilita l'output colorato", + "timeout": "Timeout delle richieste HTTP in millisecondi", + "api_key": "Chiave API per il server OmniRoute", + "base_url": "URL base del server OmniRoute", + "context": "Contesto/profilo del server per questo comando", + "lang": "Imposta la lingua di visualizzazione della CLI (sovrascrive OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/ja.json b/bin/cli/locales/ja.json new file mode 100644 index 0000000000..6a1d84fd33 --- /dev/null +++ b/bin/cli/locales/ja.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "エラー: {message}", + "serverOffline": "OmniRouteサーバーはオフラインです。起動: omniroute serve", + "authRequired": "認証が必要です。OMNIROUTE_API_KEYを設定するか実行してください: omniroute setup", + "rateLimited": "リクエスト制限を超えました。{seconds}秒後に再試行してください。", + "timeout": "{ms}ms後にリクエストがタイムアウトしました。", + "success": "完了。", + "yes": "はい", + "no": "いいえ", + "confirm": "よろしいですか?(はい/いいえ)", + "dryRun": "[シミュレーション] 実行予定: {action}", + "cancelled": "キャンセルしました。", + "jsonOpt": "JSON形式で出力", + "yesOpt": "確認をスキップ" + }, + "program": { + "description": "OmniRoute — 自動フォールバック付きスマートAIルーター", + "version": "バージョンを表示して終了", + "output": "出力形式 (table, json, jsonl, csv)", + "quiet": "重要でない出力を抑制", + "no_color": "カラー出力を無効化", + "timeout": "HTTPリクエストタイムアウト(ミリ秒)", + "api_key": "OmniRouteサーバーのAPIキー", + "base_url": "OmniRouteサーバーのベースURL", + "context": "このコマンドで使用するサーバーコンテキスト/プロファイル", + "lang": "CLI表示言語を設定(OMNIROUTE_LANGを上書き)" + } +} diff --git a/bin/cli/locales/ko.json b/bin/cli/locales/ko.json new file mode 100644 index 0000000000..e42ae86c1b --- /dev/null +++ b/bin/cli/locales/ko.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "오류: {message}", + "serverOffline": "OmniRoute 서버가 오프라인입니다. 시작: omniroute serve", + "authRequired": "인증이 필요합니다. OMNIROUTE_API_KEY를 설정하거나 실행하세요: omniroute setup", + "rateLimited": "요청 제한 초과. {seconds}초 후 다시 시도하세요.", + "timeout": "{ms}ms 후 요청 시간 초과.", + "success": "완료.", + "yes": "예", + "no": "아니오", + "confirm": "확실합니까? (예/아니오)", + "dryRun": "[시뮬레이션] 실행 예정: {action}", + "cancelled": "취소되었습니다.", + "jsonOpt": "JSON으로 출력", + "yesOpt": "확인 건너뛰기" + }, + "program": { + "description": "OmniRoute — 자동 폴백 기능을 갖춘 스마트 AI 라우터", + "version": "버전 출력 후 종료", + "output": "출력 형식 (table, json, jsonl, csv)", + "quiet": "불필요한 출력 억제", + "no_color": "색상 출력 비활성화", + "timeout": "HTTP 요청 타임아웃(밀리초)", + "api_key": "OmniRoute 서버의 API 키", + "base_url": "OmniRoute 서버 기본 URL", + "context": "이 명령에 사용할 서버 컨텍스트/프로필", + "lang": "CLI 표시 언어 설정 (OMNIROUTE_LANG 재정의)" + } +} diff --git a/bin/cli/locales/mr.json b/bin/cli/locales/mr.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/mr.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/ms.json b/bin/cli/locales/ms.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/ms.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/nl.json b/bin/cli/locales/nl.json new file mode 100644 index 0000000000..e7b68a7bf7 --- /dev/null +++ b/bin/cli/locales/nl.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Fout: {message}", + "serverOffline": "OmniRoute-server is offline. Start met: omniroute serve", + "authRequired": "Authenticatie vereist. Stel OMNIROUTE_API_KEY in of voer uit: omniroute setup", + "rateLimited": "Verzoeklimiet overschreden. Probeer opnieuw na {seconds}s.", + "timeout": "Verzoek verlopen na {ms}ms.", + "success": "Klaar.", + "yes": "ja", + "no": "nee", + "confirm": "Weet u het zeker? (ja/nee)", + "dryRun": "[simulatie] zou: {action}", + "cancelled": "Geannuleerd.", + "jsonOpt": "Uitvoer als JSON", + "yesOpt": "Bevestiging overslaan" + }, + "program": { + "description": "OmniRoute — Slimme AI-router met automatische fallback", + "version": "Versie afdrukken en afsluiten", + "output": "Uitvoerformaat (table, json, jsonl, csv)", + "quiet": "Niet-essentiële uitvoer onderdrukken", + "no_color": "Gekleurde uitvoer uitschakelen", + "timeout": "HTTP-verzoek timeout in milliseconden", + "api_key": "API-sleutel voor de OmniRoute-server", + "base_url": "Basis-URL van de OmniRoute-server", + "context": "Servercontext/profiel voor dit commando", + "lang": "CLI-weergavetaal instellen (overschrijft OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/no.json b/bin/cli/locales/no.json new file mode 100644 index 0000000000..db5d0a0706 --- /dev/null +++ b/bin/cli/locales/no.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Feil: {message}", + "serverOffline": "OmniRoute-serveren er offline. Start med: omniroute serve", + "authRequired": "Autentisering kreves. Angi OMNIROUTE_API_KEY eller kjør: omniroute setup", + "rateLimited": "Forespørselgrense overskredet. Prøv igjen om {seconds}s.", + "timeout": "Forespørselen tidsavbrutt etter {ms}ms.", + "success": "Ferdig.", + "yes": "ja", + "no": "nei", + "confirm": "Er du sikker? (ja/nei)", + "dryRun": "[simulering] ville: {action}", + "cancelled": "Avbrutt.", + "jsonOpt": "Utdata som JSON", + "yesOpt": "Hopp over bekreftelse" + }, + "program": { + "description": "OmniRoute — Smart AI-ruter med automatisk fallback", + "version": "Skriv ut versjon og avslutt", + "output": "Utdataformat (table, json, jsonl, csv)", + "quiet": "Undertrykk ikke-essensiell utdata", + "no_color": "Deaktiver farget utdata", + "timeout": "HTTP-forespørsel timeout i millisekunder", + "api_key": "API-nøkkel for OmniRoute-serveren", + "base_url": "OmniRoute-serverens basis-URL", + "context": "Serverkontekst/profil for denne kommandoen", + "lang": "Angi CLI-visningsspråk (overstyrer OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/phi.json b/bin/cli/locales/phi.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/phi.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/pl.json b/bin/cli/locales/pl.json new file mode 100644 index 0000000000..35bda06d84 --- /dev/null +++ b/bin/cli/locales/pl.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Błąd: {message}", + "serverOffline": "Serwer OmniRoute jest offline. Uruchom: omniroute serve", + "authRequired": "Wymagane uwierzytelnienie. Ustaw OMNIROUTE_API_KEY lub uruchom: omniroute setup", + "rateLimited": "Przekroczono limit żądań. Spróbuj ponownie za {seconds}s.", + "timeout": "Żądanie przekroczyło czas po {ms}ms.", + "success": "Gotowe.", + "yes": "tak", + "no": "nie", + "confirm": "Czy jesteś pewien? (tak/nie)", + "dryRun": "[symulacja] wykonałoby: {action}", + "cancelled": "Anulowano.", + "jsonOpt": "Wyjście jako JSON", + "yesOpt": "Pomiń potwierdzenie" + }, + "program": { + "description": "OmniRoute — Inteligentny router AI z automatycznym fallbackiem", + "version": "Wydrukuj wersję i wyjdź", + "output": "Format wyjścia (table, json, jsonl, csv)", + "quiet": "Pomiń nieistotne wyjście", + "no_color": "Wyłącz kolorowe wyjście", + "timeout": "Limit czasu żądania HTTP w milisekundach", + "api_key": "Klucz API dla serwera OmniRoute", + "base_url": "Bazowy URL serwera OmniRoute", + "context": "Kontekst/profil serwera dla tego polecenia", + "lang": "Ustaw język wyświetlania CLI (nadpisuje OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/pt-BR.json b/bin/cli/locales/pt-BR.json index 6b7cae77d1..37d2efc9f0 100644 --- a/bin/cli/locales/pt-BR.json +++ b/bin/cli/locales/pt-BR.json @@ -776,7 +776,8 @@ "timeout": "Timeout de requisições HTTP em milissegundos", "api_key": "Chave de API para o servidor OmniRoute", "base_url": "URL base do servidor OmniRoute", - "context": "Contexto/perfil do servidor a usar neste comando" + "context": "Contexto/perfil do servidor a usar neste comando", + "lang": "Definir idioma de exibição do CLI (substitui OMNIROUTE_LANG)" }, "files": { "description": "Gerenciar arquivos (upload, listar, obter, baixar, deletar)", @@ -1154,6 +1155,19 @@ "config": { "contexts": { "description": "Gerenciar contextos/perfis de servidor (add, use, list, show, remove, rename, export, import)" + }, + "lang": { + "description": "Gerenciar idioma de exibição do CLI", + "getDescription": "Exibir o código de idioma atualmente ativo", + "setDescription": "Definir o idioma de exibição e salvar na configuração", + "listDescription": "Listar todos os idiomas disponíveis", + "listTitle": "Idiomas Disponíveis", + "current": "Idioma: {code} ({name})", + "saved": "Idioma definido como {code} ({name}).", + "noCode": "Código de idioma obrigatório. Execute 'omniroute config lang list' para ver os códigos disponíveis.", + "unknown": "Código de idioma desconhecido: {code}. Execute 'omniroute config lang list' para ver os códigos disponíveis.", + "alreadySet": "O idioma já está definido como {code}.", + "envHint": "Dica: você também pode definir OMNIROUTE_LANG={code} no seu ambiente." } }, "completion": { diff --git a/bin/cli/locales/pt.json b/bin/cli/locales/pt.json new file mode 100644 index 0000000000..8a32221ed6 --- /dev/null +++ b/bin/cli/locales/pt.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Erro: {message}", + "serverOffline": "O servidor OmniRoute está offline. Inicie com: omniroute serve", + "authRequired": "Autenticação necessária. Defina OMNIROUTE_API_KEY ou execute: omniroute setup", + "rateLimited": "Limite de pedidos atingido. Tente novamente em {seconds}s.", + "timeout": "O pedido expirou após {ms}ms.", + "success": "Concluído.", + "yes": "sim", + "no": "não", + "confirm": "Tem a certeza? (sim/não)", + "dryRun": "[simulação] faria: {action}", + "cancelled": "Cancelado.", + "jsonOpt": "Saída em JSON", + "yesOpt": "Ignorar confirmação" + }, + "program": { + "description": "OmniRoute — Router de IA inteligente com fallback automático", + "version": "Mostrar versão e sair", + "output": "Formato de saída (table, json, jsonl, csv)", + "quiet": "Suprimir saída não essencial", + "no_color": "Desativar saída colorida", + "timeout": "Timeout de pedidos HTTP em milissegundos", + "api_key": "Chave de API para o servidor OmniRoute", + "base_url": "URL base do servidor OmniRoute", + "context": "Contexto/perfil do servidor para este comando", + "lang": "Definir idioma de apresentação do CLI (substitui OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/ro.json b/bin/cli/locales/ro.json new file mode 100644 index 0000000000..830c063564 --- /dev/null +++ b/bin/cli/locales/ro.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Eroare: {message}", + "serverOffline": "Serverul OmniRoute este offline. Porniți cu: omniroute serve", + "authRequired": "Autentificare necesară. Setați OMNIROUTE_API_KEY sau rulați: omniroute setup", + "rateLimited": "Limita de cereri depășită. Încercați din nou după {seconds}s.", + "timeout": "Cererea a expirat după {ms}ms.", + "success": "Gata.", + "yes": "da", + "no": "nu", + "confirm": "Sigur? (da/nu)", + "dryRun": "[simulare] ar face: {action}", + "cancelled": "Anulat.", + "jsonOpt": "Ieșire ca JSON", + "yesOpt": "Omite confirmarea" + }, + "program": { + "description": "OmniRoute — Router AI inteligent cu fallback automat", + "version": "Afișează versiunea și ieși", + "output": "Format de ieșire (table, json, jsonl, csv)", + "quiet": "Suprimă ieșirile neesențiale", + "no_color": "Dezactivează ieșirea colorată", + "timeout": "Timeout cereri HTTP în milisecunde", + "api_key": "Cheie API pentru serverul OmniRoute", + "base_url": "URL de bază al serverului OmniRoute", + "context": "Contextul/profilul serverului pentru această comandă", + "lang": "Setează limba de afișare CLI (suprascrie OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/ru.json b/bin/cli/locales/ru.json new file mode 100644 index 0000000000..12fa73e8eb --- /dev/null +++ b/bin/cli/locales/ru.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Ошибка: {message}", + "serverOffline": "Сервер OmniRoute отключён. Запустите: omniroute serve", + "authRequired": "Требуется аутентификация. Установите OMNIROUTE_API_KEY или выполните: omniroute setup", + "rateLimited": "Превышен лимит запросов. Повторите через {seconds}с.", + "timeout": "Запрос истёк через {ms}мс.", + "success": "Готово.", + "yes": "да", + "no": "нет", + "confirm": "Вы уверены? (да/нет)", + "dryRun": "[симуляция] выполнит: {action}", + "cancelled": "Отменено.", + "jsonOpt": "Вывод в формате JSON", + "yesOpt": "Пропустить подтверждение" + }, + "program": { + "description": "OmniRoute — Умный AI-маршрутизатор с автоматическим переключением", + "version": "Вывести версию и выйти", + "output": "Формат вывода (table, json, jsonl, csv)", + "quiet": "Подавить несущественный вывод", + "no_color": "Отключить цветной вывод", + "timeout": "Таймаут HTTP-запросов в миллисекундах", + "api_key": "API-ключ для сервера OmniRoute", + "base_url": "Базовый URL сервера OmniRoute", + "context": "Контекст/профиль сервера для этой команды", + "lang": "Установить язык отображения CLI (переопределяет OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/sk.json b/bin/cli/locales/sk.json new file mode 100644 index 0000000000..65b923aeda --- /dev/null +++ b/bin/cli/locales/sk.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Chyba: {message}", + "serverOffline": "Server OmniRoute je offline. Spustite: omniroute serve", + "authRequired": "Vyžaduje sa overenie. Nastavte OMNIROUTE_API_KEY alebo spustite: omniroute setup", + "rateLimited": "Prekročený limit požiadaviek. Skúste za {seconds}s.", + "timeout": "Požiadavka vypršala po {ms}ms.", + "success": "Hotovo.", + "yes": "áno", + "no": "nie", + "confirm": "Ste si istí? (áno/nie)", + "dryRun": "[simulácia] by vykonalo: {action}", + "cancelled": "Zrušené.", + "jsonOpt": "Výstup ako JSON", + "yesOpt": "Preskočiť potvrdenie" + }, + "program": { + "description": "OmniRoute — Inteligentný AI router s automatickým prepínaním", + "version": "Vypísať verziu a skončiť", + "output": "Formát výstupu (table, json, jsonl, csv)", + "quiet": "Potlačiť nepodstatný výstup", + "no_color": "Zakázať farebný výstup", + "timeout": "Časový limit HTTP požiadaviek v milisekundách", + "api_key": "API kľúč pre server OmniRoute", + "base_url": "Základná URL servera OmniRoute", + "context": "Kontext/profil servera pre tento príkaz", + "lang": "Nastaviť jazyk zobrazenia CLI (prepíše OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/sv.json b/bin/cli/locales/sv.json new file mode 100644 index 0000000000..79e9e589d5 --- /dev/null +++ b/bin/cli/locales/sv.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Fel: {message}", + "serverOffline": "OmniRoute-servern är offline. Starta med: omniroute serve", + "authRequired": "Autentisering krävs. Ange OMNIROUTE_API_KEY eller kör: omniroute setup", + "rateLimited": "Begäransgräns nådd. Försök igen om {seconds}s.", + "timeout": "Begäran tog slut efter {ms}ms.", + "success": "Klar.", + "yes": "ja", + "no": "nej", + "confirm": "Är du säker? (ja/nej)", + "dryRun": "[simulering] skulle: {action}", + "cancelled": "Avbruten.", + "jsonOpt": "Utdata som JSON", + "yesOpt": "Hoppa över bekräftelse" + }, + "program": { + "description": "OmniRoute — Smart AI-router med automatisk fallback", + "version": "Skriv ut version och avsluta", + "output": "Utdataformat (table, json, jsonl, csv)", + "quiet": "Undertryck icke-väsentlig utdata", + "no_color": "Inaktivera färgad utdata", + "timeout": "HTTP-begärans timeout i millisekunder", + "api_key": "API-nyckel för OmniRoute-servern", + "base_url": "OmniRoute-serverns bas-URL", + "context": "Serverkontext/profil för det här kommandot", + "lang": "Ange CLI-visningsspråk (åsidosätter OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/sw.json b/bin/cli/locales/sw.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/sw.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/ta.json b/bin/cli/locales/ta.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/ta.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/te.json b/bin/cli/locales/te.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/te.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/th.json b/bin/cli/locales/th.json new file mode 100644 index 0000000000..56e3d77c18 --- /dev/null +++ b/bin/cli/locales/th.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "ข้อผิดพลาด: {message}", + "serverOffline": "เซิร์ฟเวอร์ OmniRoute ออฟไลน์ เริ่มด้วย: omniroute serve", + "authRequired": "ต้องการการยืนยันตัวตน ตั้งค่า OMNIROUTE_API_KEY หรือรัน: omniroute setup", + "rateLimited": "เกินขีดจำกัดคำขอ ลองใหม่หลังจาก {seconds}วินาที", + "timeout": "คำขอหมดเวลาหลังจาก {ms}ms", + "success": "เสร็จสิ้น", + "yes": "ใช่", + "no": "ไม่", + "confirm": "คุณแน่ใจหรือไม่? (ใช่/ไม่)", + "dryRun": "[จำลอง] จะทำ: {action}", + "cancelled": "ยกเลิกแล้ว", + "jsonOpt": "ส่งออกเป็น JSON", + "yesOpt": "ข้ามการยืนยัน" + }, + "program": { + "description": "OmniRoute — AI Router อัจฉริยะพร้อม Auto Fallback", + "version": "แสดงเวอร์ชันและออก", + "output": "รูปแบบเอาต์พุต (table, json, jsonl, csv)", + "quiet": "ซ่อนเอาต์พุตที่ไม่จำเป็น", + "no_color": "ปิดใช้งานเอาต์พุตสี", + "timeout": "หมดเวลา HTTP request ในมิลลิวินาที", + "api_key": "API Key สำหรับ OmniRoute server", + "base_url": "Base URL ของ OmniRoute server", + "context": "บริบท/โปรไฟล์ของเซิร์ฟเวอร์สำหรับคำสั่งนี้", + "lang": "ตั้งค่าภาษาแสดงผล CLI (แทนที่ OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/tr.json b/bin/cli/locales/tr.json new file mode 100644 index 0000000000..a78955fef4 --- /dev/null +++ b/bin/cli/locales/tr.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Hata: {message}", + "serverOffline": "OmniRoute sunucusu çevrimdışı. Başlatın: omniroute serve", + "authRequired": "Kimlik doğrulama gerekli. OMNIROUTE_API_KEY ayarlayın veya çalıştırın: omniroute setup", + "rateLimited": "İstek limiti aşıldı. {seconds}s sonra tekrar deneyin.", + "timeout": "İstek {ms}ms sonra zaman aşımına uğradı.", + "success": "Tamamlandı.", + "yes": "evet", + "no": "hayır", + "confirm": "Emin misiniz? (evet/hayır)", + "dryRun": "[simülasyon] yapılacaktı: {action}", + "cancelled": "İptal edildi.", + "jsonOpt": "JSON olarak çıktı", + "yesOpt": "Onayı atla" + }, + "program": { + "description": "OmniRoute — Otomatik Fallback ile Akıllı AI Yönlendirici", + "version": "Sürümü yazdır ve çık", + "output": "Çıktı formatı (table, json, jsonl, csv)", + "quiet": "Önemsiz çıktıyı gizle", + "no_color": "Renkli çıktıyı devre dışı bırak", + "timeout": "HTTP istek zaman aşımı (milisaniye)", + "api_key": "OmniRoute sunucusu için API anahtarı", + "base_url": "OmniRoute sunucusu temel URL'si", + "context": "Bu komut için sunucu bağlamı/profili", + "lang": "CLI görüntüleme dilini ayarla (OMNIROUTE_LANG'ı geçersiz kılar)" + } +} diff --git a/bin/cli/locales/uk-UA.json b/bin/cli/locales/uk-UA.json new file mode 100644 index 0000000000..64d0d76b62 --- /dev/null +++ b/bin/cli/locales/uk-UA.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Помилка: {message}", + "serverOffline": "Сервер OmniRoute відключено. Запустіть: omniroute serve", + "authRequired": "Потрібна автентифікація. Встановіть OMNIROUTE_API_KEY або виконайте: omniroute setup", + "rateLimited": "Перевищено ліміт запитів. Повторіть через {seconds}с.", + "timeout": "Запит завершився через {ms}мс.", + "success": "Готово.", + "yes": "так", + "no": "ні", + "confirm": "Ви впевнені? (так/ні)", + "dryRun": "[симуляція] виконає: {action}", + "cancelled": "Скасовано.", + "jsonOpt": "Вивести у форматі JSON", + "yesOpt": "Пропустити підтвердження" + }, + "program": { + "description": "OmniRoute — Розумний AI-маршрутизатор з автоматичним перемиканням", + "version": "Вивести версію та вийти", + "output": "Формат виведення (table, json, jsonl, csv)", + "quiet": "Приховати несуттєвий вивід", + "no_color": "Вимкнути кольоровий вивід", + "timeout": "Тайм-аут HTTP-запитів у мілісекундах", + "api_key": "API-ключ для сервера OmniRoute", + "base_url": "Базовий URL сервера OmniRoute", + "context": "Контекст/профіль сервера для цієї команди", + "lang": "Встановити мову відображення CLI (замінює OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/ur.json b/bin/cli/locales/ur.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/bin/cli/locales/ur.json @@ -0,0 +1 @@ +{} diff --git a/bin/cli/locales/vi.json b/bin/cli/locales/vi.json new file mode 100644 index 0000000000..29bfc11da0 --- /dev/null +++ b/bin/cli/locales/vi.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "Lỗi: {message}", + "serverOffline": "Máy chủ OmniRoute đang offline. Khởi động với: omniroute serve", + "authRequired": "Cần xác thực. Đặt OMNIROUTE_API_KEY hoặc chạy: omniroute setup", + "rateLimited": "Đã vượt giới hạn yêu cầu. Thử lại sau {seconds}s.", + "timeout": "Yêu cầu hết thời gian sau {ms}ms.", + "success": "Xong.", + "yes": "có", + "no": "không", + "confirm": "Bạn có chắc không? (có/không)", + "dryRun": "[mô phỏng] sẽ: {action}", + "cancelled": "Đã hủy.", + "jsonOpt": "Xuất dưới dạng JSON", + "yesOpt": "Bỏ qua xác nhận" + }, + "program": { + "description": "OmniRoute — Bộ định tuyến AI thông minh với tự động chuyển đổi dự phòng", + "version": "In phiên bản và thoát", + "output": "Định dạng đầu ra (table, json, jsonl, csv)", + "quiet": "Ẩn đầu ra không cần thiết", + "no_color": "Tắt đầu ra màu sắc", + "timeout": "Thời gian chờ yêu cầu HTTP tính bằng mili giây", + "api_key": "Khóa API cho máy chủ OmniRoute", + "base_url": "URL cơ sở của máy chủ OmniRoute", + "context": "Bối cảnh/hồ sơ máy chủ cho lệnh này", + "lang": "Đặt ngôn ngữ hiển thị CLI (ghi đè OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/locales/zh-CN.json b/bin/cli/locales/zh-CN.json new file mode 100644 index 0000000000..c27f430239 --- /dev/null +++ b/bin/cli/locales/zh-CN.json @@ -0,0 +1,29 @@ +{ + "common": { + "error": "错误:{message}", + "serverOffline": "OmniRoute 服务器已离线。请启动:omniroute serve", + "authRequired": "需要认证。请设置 OMNIROUTE_API_KEY 或运行:omniroute setup", + "rateLimited": "请求超出限制。请在 {seconds}s 后重试。", + "timeout": "请求在 {ms}ms 后超时。", + "success": "完成。", + "yes": "是", + "no": "否", + "confirm": "确定吗?(是/否)", + "dryRun": "【模拟】将执行:{action}", + "cancelled": "已取消。", + "jsonOpt": "以 JSON 格式输出", + "yesOpt": "跳过确认" + }, + "program": { + "description": "OmniRoute — 具有自动故障转移的智能 AI 路由器", + "version": "打印版本并退出", + "output": "输出格式(table, json, jsonl, csv)", + "quiet": "禁止非必要输出", + "no_color": "禁用彩色输出", + "timeout": "HTTP 请求超时(毫秒)", + "api_key": "OmniRoute 服务器的 API 密钥", + "base_url": "OmniRoute 服务器的基础 URL", + "context": "此命令使用的服务器上下文/配置文件", + "lang": "设置 CLI 显示语言(覆盖 OMNIROUTE_LANG)" + } +} diff --git a/bin/cli/program.mjs b/bin/cli/program.mjs index 8c0de2d9af..b88dae8f58 100644 --- a/bin/cli/program.mjs +++ b/bin/cli/program.mjs @@ -31,6 +31,7 @@ export function createProgram() { t("program.context") || "Server context/profile to use for this command" ).env("OMNIROUTE_CONTEXT") ) + .addOption(new Option("--lang ", t("program.lang"))) .showHelpAfterError(true) .exitOverride(); diff --git a/bin/cli/scripts/generate-locales.mjs b/bin/cli/scripts/generate-locales.mjs new file mode 100644 index 0000000000..e90aa1ee8c --- /dev/null +++ b/bin/cli/scripts/generate-locales.mjs @@ -0,0 +1,911 @@ +#!/usr/bin/env node +/** + * Generates scaffold locale files for all languages listed in config/i18n.json + * that don't yet have a corresponding file in bin/cli/locales/. + * + * For top-tier languages, a translated `common` + `program` section is included. + * All other keys fall back to `en` via i18n.mjs's existing fallback mechanism. + * + * Run: node bin/cli/scripts/generate-locales.mjs [--force] + */ +import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, "..", "..", ".."); +const LOCALES_DIR = join(__dirname, "..", "locales"); +const I18N_CFG = join(ROOT, "config", "i18n.json"); +const FORCE = process.argv.includes("--force"); + +const { locales } = JSON.parse(readFileSync(I18N_CFG, "utf8")); + +// common + program translations for each language code. +// Keys that are absent fall back to en automatically. +const TRANSLATIONS = { + ar: { + common: { + error: "خطأ: {message}", + serverOffline: "خادم OmniRoute غير متصل. ابدأ بالأمر: omniroute serve", + authRequired: "المصادقة مطلوبة. عيّن OMNIROUTE_API_KEY أو شغّل: omniroute setup", + rateLimited: "تم تجاوز حد الطلبات. أعد المحاولة بعد {seconds} ثانية.", + timeout: "انتهت مهلة الطلب بعد {ms}ms.", + success: "تم.", + yes: "نعم", + no: "لا", + confirm: "هل أنت متأكد؟ (نعم/لا)", + dryRun: "[محاكاة] سيتم: {action}", + cancelled: "تم الإلغاء.", + jsonOpt: "إخراج بتنسيق JSON", + yesOpt: "تخطي رسالة التأكيد", + }, + program: { + description: "OmniRoute — جهاز توجيه الذكاء الاصطناعي مع التبديل التلقائي", + version: "عرض الإصدار والخروج", + output: "تنسيق الإخراج (table, json, jsonl, csv)", + quiet: "إخفاء المخرجات غير الأساسية", + no_color: "تعطيل الإخراج الملوّن", + timeout: "مهلة طلب HTTP بالميلي ثانية", + api_key: "مفتاح API لخادم OmniRoute", + base_url: "عنوان URL الأساسي لخادم OmniRoute", + context: "سياق/ملف تعريف الخادم المستخدم في هذا الأمر", + lang: "تعيين لغة عرض CLI (يتجاوز OMNIROUTE_LANG)", + }, + }, + az: { + common: { + error: "Xəta: {message}", + serverOffline: "OmniRoute serveri oflayndır. Başladın: omniroute serve", + authRequired: + "Autentifikasiya tələb olunur. OMNIROUTE_API_KEY təyin edin və ya işə salın: omniroute setup", + rateLimited: "Sorğu limiti aşıldı. {seconds} saniyə sonra yenidən cəhd edin.", + timeout: "Sorğunun vaxtı {ms}ms sonra bitdi.", + success: "Tamamlandı.", + yes: "bəli", + no: "xeyr", + confirm: "Əminsiniz? (bəli/xeyr)", + dryRun: "[simulyasiya] ediləcəkdi: {action}", + cancelled: "Ləğv edildi.", + jsonOpt: "JSON formatında çıxış", + yesOpt: "Təsdiq sorğusunu keç", + }, + program: { + description: "OmniRoute — Avtomatik Fallback ilə Ağıllı AI Marşrutlaşdırıcısı", + version: "Versiyasını çap et və çıx", + output: "Çıxış formatı (table, json, jsonl, csv)", + quiet: "Vacib olmayan çıxışı gizlət", + no_color: "Rəngli çıxışı deaktiv et", + timeout: "HTTP sorğusu üçün zaman aşımı (millisaniyə)", + api_key: "OmniRoute serveri üçün API açarı", + base_url: "OmniRoute server baza URL-i", + context: "Bu əmr üçün server konteksti/profili", + lang: "CLI ekran dilini təyin edin (OMNIROUTE_LANG-ı keçir)", + }, + }, + bg: { + common: { + error: "Грешка: {message}", + serverOffline: "Сървърът OmniRoute е офлайн. Стартирайте с: omniroute serve", + authRequired: + "Необходима е автентикация. Задайте OMNIROUTE_API_KEY или изпълнете: omniroute setup", + rateLimited: "Превишен лимит на заявки. Опитайте след {seconds}с.", + timeout: "Заявката изтече след {ms}ms.", + success: "Готово.", + yes: "да", + no: "не", + confirm: "Сигурни ли сте? (да/не)", + dryRun: "[симулация] ще извърши: {action}", + cancelled: "Отменено.", + jsonOpt: "Изход като JSON", + yesOpt: "Пропускане на потвърждение", + }, + program: { + description: "OmniRoute — Интелигентен AI рутер с автоматично превключване", + version: "Покажи версията и излез", + output: "Формат на изхода (table, json, jsonl, csv)", + quiet: "Потисни несъществена информация", + no_color: "Деактивирай цветния изход", + timeout: "Таймаут за HTTP заявки в милисекунди", + api_key: "API ключ за сървъра OmniRoute", + base_url: "Базов URL на сървъра OmniRoute", + context: "Контекст/профил на сървъра за тази команда", + lang: "Задай език на CLI (замества OMNIROUTE_LANG)", + }, + }, + cs: { + common: { + error: "Chyba: {message}", + serverOffline: "Server OmniRoute je offline. Spusťte: omniroute serve", + authRequired: "Vyžaduje se ověření. Nastavte OMNIROUTE_API_KEY nebo spusťte: omniroute setup", + rateLimited: "Překročen limit požadavků. Zkuste za {seconds}s.", + timeout: "Požadavek vypršel po {ms}ms.", + success: "Hotovo.", + yes: "ano", + no: "ne", + confirm: "Jste si jisti? (ano/ne)", + dryRun: "[simulace] by provedlo: {action}", + cancelled: "Zrušeno.", + jsonOpt: "Výstup jako JSON", + yesOpt: "Přeskočit potvrzení", + }, + program: { + description: "OmniRoute — Chytrý AI router s automatickým přepínáním", + version: "Vypsat verzi a skončit", + output: "Formát výstupu (table, json, jsonl, csv)", + quiet: "Potlačit nepodstatný výstup", + no_color: "Zakázat barevný výstup", + timeout: "Časový limit HTTP požadavků v milisekundách", + api_key: "API klíč pro server OmniRoute", + base_url: "Základní URL serveru OmniRoute", + context: "Kontext/profil serveru pro tento příkaz", + lang: "Nastavit jazyk CLI (přepisuje OMNIROUTE_LANG)", + }, + }, + da: { + common: { + error: "Fejl: {message}", + serverOffline: "OmniRoute-serveren er offline. Start med: omniroute serve", + authRequired: "Godkendelse kræves. Sæt OMNIROUTE_API_KEY eller kør: omniroute setup", + rateLimited: "Anmodningsgrænse overskredet. Prøv igen om {seconds}s.", + timeout: "Anmodningen timed ud efter {ms}ms.", + success: "Færdig.", + yes: "ja", + no: "nej", + confirm: "Er du sikker? (ja/nej)", + dryRun: "[simulering] ville: {action}", + cancelled: "Annulleret.", + jsonOpt: "Output som JSON", + yesOpt: "Spring bekræftelse over", + }, + program: { + description: "OmniRoute — Smart AI-router med automatisk fallback", + version: "Vis version og afslut", + output: "Outputformat (table, json, jsonl, csv)", + quiet: "Undertryk ikke-essentielt output", + no_color: "Deaktiver farvet output", + timeout: "HTTP-anmodnings timeout i millisekunder", + api_key: "API-nøgle til OmniRoute-serveren", + base_url: "OmniRoute-serverens basis-URL", + context: "Server-kontekst/profil til denne kommando", + lang: "Angiv CLI-visningssprog (tilsidesætter OMNIROUTE_LANG)", + }, + }, + de: { + common: { + error: "Fehler: {message}", + serverOffline: "OmniRoute-Server ist offline. Starten mit: omniroute serve", + authRequired: + "Authentifizierung erforderlich. OMNIROUTE_API_KEY setzen oder ausführen: omniroute setup", + rateLimited: "Anfragelimit überschritten. Erneut versuchen in {seconds}s.", + timeout: "Anfrage-Timeout nach {ms}ms.", + success: "Fertig.", + yes: "ja", + no: "nein", + confirm: "Sind Sie sicher? (ja/nein)", + dryRun: "[Simulation] würde: {action}", + cancelled: "Abgebrochen.", + jsonOpt: "Ausgabe als JSON", + yesOpt: "Bestätigung überspringen", + }, + program: { + description: "OmniRoute — Intelligenter AI-Router mit automatischem Fallback", + version: "Version ausgeben und beenden", + output: "Ausgabeformat (table, json, jsonl, csv)", + quiet: "Unwesentliche Ausgabe unterdrücken", + no_color: "Farbige Ausgabe deaktivieren", + timeout: "HTTP-Anfrage-Timeout in Millisekunden", + api_key: "API-Schlüssel für den OmniRoute-Server", + base_url: "OmniRoute-Server-Basis-URL", + context: "Server-Kontext/Profil für diesen Befehl", + lang: "CLI-Anzeigesprache festlegen (überschreibt OMNIROUTE_LANG)", + }, + }, + es: { + common: { + error: "Error: {message}", + serverOffline: "El servidor OmniRoute está offline. Inícielo con: omniroute serve", + authRequired: + "Autenticación requerida. Configure OMNIROUTE_API_KEY o ejecute: omniroute setup", + rateLimited: "Límite de solicitudes excedido. Reintente en {seconds}s.", + timeout: "Solicitud expiró después de {ms}ms.", + success: "Listo.", + yes: "sí", + no: "no", + confirm: "¿Está seguro? (sí/no)", + dryRun: "[simulación] haría: {action}", + cancelled: "Cancelado.", + jsonOpt: "Salida como JSON", + yesOpt: "Omitir confirmación", + }, + program: { + description: "OmniRoute — Router de IA inteligente con fallback automático", + version: "Mostrar versión y salir", + output: "Formato de salida (table, json, jsonl, csv)", + quiet: "Suprimir salida no esencial", + no_color: "Deshabilitar salida en color", + timeout: "Tiempo de espera de solicitudes HTTP en milisegundos", + api_key: "Clave de API para el servidor OmniRoute", + base_url: "URL base del servidor OmniRoute", + context: "Contexto/perfil del servidor para este comando", + lang: "Establecer idioma del CLI (reemplaza OMNIROUTE_LANG)", + }, + }, + fa: { + common: { + error: "خطا: {message}", + serverOffline: "سرور OmniRoute آفلاین است. با این دستور راه‌اندازی کنید: omniroute serve", + authRequired: + "احراز هویت لازم است. OMNIROUTE_API_KEY را تنظیم کنید یا اجرا کنید: omniroute setup", + rateLimited: "محدودیت درخواست رسیده است. پس از {seconds} ثانیه دوباره تلاش کنید.", + timeout: "درخواست پس از {ms}ms منقضی شد.", + success: "انجام شد.", + yes: "بله", + no: "خیر", + confirm: "مطمئنید؟ (بله/خیر)", + dryRun: "[شبیه‌سازی] اقدام می‌شد: {action}", + cancelled: "لغو شد.", + jsonOpt: "خروجی به صورت JSON", + yesOpt: "رد کردن تأیید", + }, + program: { + description: "OmniRoute — روتر هوشمند هوش مصنوعی با fallback خودکار", + version: "نمایش نسخه و خروج", + output: "فرمت خروجی (table, json, jsonl, csv)", + quiet: "حذف خروجی غیر ضروری", + no_color: "غیرفعال کردن خروجی رنگی", + timeout: "تایم‌اوت درخواست HTTP به میلی‌ثانیه", + api_key: "کلید API برای سرور OmniRoute", + base_url: "URL پایه سرور OmniRoute", + context: "زمینه/پروفایل سرور برای این دستور", + lang: "تنظیم زبان نمایش CLI (OMNIROUTE_LANG را نادیده می‌گیرد)", + }, + }, + fi: { + common: { + error: "Virhe: {message}", + serverOffline: "OmniRoute-palvelin on offline. Käynnistä komennolla: omniroute serve", + authRequired: "Todennus vaaditaan. Aseta OMNIROUTE_API_KEY tai suorita: omniroute setup", + rateLimited: "Pyyntöraja ylitetty. Yritä uudelleen {seconds}s kuluttua.", + timeout: "Pyyntö aikakatkaistiin {ms}ms jälkeen.", + success: "Valmis.", + yes: "kyllä", + no: "ei", + confirm: "Oletko varma? (kyllä/ei)", + dryRun: "[simulointi] tekisi: {action}", + cancelled: "Peruutettu.", + jsonOpt: "Tulosta JSON-muodossa", + yesOpt: "Ohita vahvistuspyyntö", + }, + program: { + description: "OmniRoute — Älykäs AI-reititin automaattisella fallbackilla", + version: "Tulosta versio ja poistu", + output: "Tulostusmuoto (table, json, jsonl, csv)", + quiet: "Piilota epäolennaiset tulosteet", + no_color: "Poista väritulostus käytöstä", + timeout: "HTTP-pyyntöjen aikakatkaisu millisekunteina", + api_key: "API-avain OmniRoute-palvelimelle", + base_url: "OmniRoute-palvelimen perus-URL", + context: "Palvelimen konteksti/profiili tälle komennolle", + lang: "Aseta CLI-näyttökieli (ohittaa OMNIROUTE_LANG)", + }, + }, + fr: { + common: { + error: "Erreur : {message}", + serverOffline: "Le serveur OmniRoute est hors ligne. Démarrez avec : omniroute serve", + authRequired: + "Authentification requise. Définissez OMNIROUTE_API_KEY ou exécutez : omniroute setup", + rateLimited: "Limite de requêtes atteinte. Réessayez dans {seconds}s.", + timeout: "La requête a expiré après {ms}ms.", + success: "Terminé.", + yes: "oui", + no: "non", + confirm: "Êtes-vous sûr ? (oui/non)", + dryRun: "[simulation] ferait : {action}", + cancelled: "Annulé.", + jsonOpt: "Sortie au format JSON", + yesOpt: "Ignorer la confirmation", + }, + program: { + description: "OmniRoute — Routeur IA intelligent avec basculement automatique", + version: "Afficher la version et quitter", + output: "Format de sortie (table, json, jsonl, csv)", + quiet: "Supprimer les sorties non essentielles", + no_color: "Désactiver la sortie en couleur", + timeout: "Délai d'expiration des requêtes HTTP en millisecondes", + api_key: "Clé API pour le serveur OmniRoute", + base_url: "URL de base du serveur OmniRoute", + context: "Contexte/profil du serveur pour cette commande", + lang: "Définir la langue d'affichage du CLI (remplace OMNIROUTE_LANG)", + }, + }, + hi: { + common: { + error: "त्रुटि: {message}", + serverOffline: "OmniRoute सर्वर ऑफलाइन है। शुरू करें: omniroute serve", + authRequired: "प्रमाणीकरण आवश्यक है। OMNIROUTE_API_KEY सेट करें या चलाएं: omniroute setup", + rateLimited: "अनुरोध सीमा पार हो गई। {seconds}s बाद पुनः प्रयास करें।", + timeout: "{ms}ms के बाद अनुरोध समय समाप्त हुआ।", + success: "पूर्ण।", + yes: "हाँ", + no: "नहीं", + confirm: "क्या आप सुनिश्चित हैं? (हाँ/नहीं)", + dryRun: "[अनुकरण] करेगा: {action}", + cancelled: "रद्द किया गया।", + jsonOpt: "JSON के रूप में आउटपुट", + yesOpt: "पुष्टि छोड़ें", + }, + program: { + description: "OmniRoute — ऑटो फॉलबैक के साथ स्मार्ट AI राउटर", + version: "संस्करण प्रिंट करें और बाहर निकलें", + output: "आउटपुट प्रारूप (table, json, jsonl, csv)", + quiet: "गैर-आवश्यक आउटपुट दबाएं", + no_color: "रंगीन आउटपुट अक्षम करें", + timeout: "HTTP अनुरोध टाइमआउट मिलीसेकंड में", + api_key: "OmniRoute सर्वर के लिए API कुंजी", + base_url: "OmniRoute सर्वर का बेस URL", + context: "इस कमांड के लिए सर्वर संदर्भ/प्रोफ़ाइल", + lang: "CLI प्रदर्शन भाषा सेट करें (OMNIROUTE_LANG को ओवरराइड करता है)", + }, + }, + hu: { + common: { + error: "Hiba: {message}", + serverOffline: "Az OmniRoute szerver offline. Indítsa el: omniroute serve", + authRequired: + "Hitelesítés szükséges. Állítsa be az OMNIROUTE_API_KEY-t vagy futtassa: omniroute setup", + rateLimited: "Kérési korlát túllépve. Próbálja újra {seconds}s múlva.", + timeout: "A kérés {ms}ms után lejárt.", + success: "Kész.", + yes: "igen", + no: "nem", + confirm: "Biztos benne? (igen/nem)", + dryRun: "[szimuláció] végrehajtaná: {action}", + cancelled: "Törölve.", + jsonOpt: "JSON formátumú kimenet", + yesOpt: "Megerősítés kihagyása", + }, + program: { + description: "OmniRoute — Intelligens AI útválasztó automatikus fallbackkel", + version: "Verzió kiírása és kilépés", + output: "Kimeneti formátum (table, json, jsonl, csv)", + quiet: "Nem lényeges kimenet elnyomása", + no_color: "Színes kimenet letiltása", + timeout: "HTTP kérés időtúllépése ezredmásodpercben", + api_key: "API kulcs az OmniRoute szerverhez", + base_url: "Az OmniRoute szerver alap URL-je", + context: "Szerverkontextus/profil ehhez a parancshoz", + lang: "CLI megjelenítési nyelv beállítása (felülírja az OMNIROUTE_LANG-ot)", + }, + }, + id: { + common: { + error: "Kesalahan: {message}", + serverOffline: "Server OmniRoute sedang offline. Mulai dengan: omniroute serve", + authRequired: + "Autentikasi diperlukan. Setel OMNIROUTE_API_KEY atau jalankan: omniroute setup", + rateLimited: "Batas permintaan terlampaui. Coba lagi dalam {seconds}d.", + timeout: "Permintaan habis waktu setelah {ms}ms.", + success: "Selesai.", + yes: "ya", + no: "tidak", + confirm: "Apakah Anda yakin? (ya/tidak)", + dryRun: "[simulasi] akan: {action}", + cancelled: "Dibatalkan.", + jsonOpt: "Keluaran sebagai JSON", + yesOpt: "Lewati konfirmasi", + }, + program: { + description: "OmniRoute — Router AI Cerdas dengan Fallback Otomatis", + version: "Cetak versi dan keluar", + output: "Format keluaran (table, json, jsonl, csv)", + quiet: "Sembunyikan output yang tidak penting", + no_color: "Nonaktifkan output berwarna", + timeout: "Batas waktu permintaan HTTP dalam milidetik", + api_key: "Kunci API untuk server OmniRoute", + base_url: "URL dasar server OmniRoute", + context: "Konteks/profil server untuk perintah ini", + lang: "Atur bahasa tampilan CLI (menggantikan OMNIROUTE_LANG)", + }, + }, + it: { + common: { + error: "Errore: {message}", + serverOffline: "Il server OmniRoute è offline. Avviarlo con: omniroute serve", + authRequired: + "Autenticazione richiesta. Impostare OMNIROUTE_API_KEY o eseguire: omniroute setup", + rateLimited: "Limite di richieste superato. Riprovare tra {seconds}s.", + timeout: "La richiesta è scaduta dopo {ms}ms.", + success: "Completato.", + yes: "sì", + no: "no", + confirm: "Sei sicuro? (sì/no)", + dryRun: "[simulazione] eseguirebbe: {action}", + cancelled: "Annullato.", + jsonOpt: "Output come JSON", + yesOpt: "Salta la conferma", + }, + program: { + description: "OmniRoute — Router AI intelligente con fallback automatico", + version: "Stampa la versione ed esci", + output: "Formato di output (table, json, jsonl, csv)", + quiet: "Sopprimi l'output non essenziale", + no_color: "Disabilita l'output colorato", + timeout: "Timeout delle richieste HTTP in millisecondi", + api_key: "Chiave API per il server OmniRoute", + base_url: "URL base del server OmniRoute", + context: "Contesto/profilo del server per questo comando", + lang: "Imposta la lingua di visualizzazione della CLI (sovrascrive OMNIROUTE_LANG)", + }, + }, + ja: { + common: { + error: "エラー: {message}", + serverOffline: "OmniRouteサーバーはオフラインです。起動: omniroute serve", + authRequired: + "認証が必要です。OMNIROUTE_API_KEYを設定するか実行してください: omniroute setup", + rateLimited: "リクエスト制限を超えました。{seconds}秒後に再試行してください。", + timeout: "{ms}ms後にリクエストがタイムアウトしました。", + success: "完了。", + yes: "はい", + no: "いいえ", + confirm: "よろしいですか?(はい/いいえ)", + dryRun: "[シミュレーション] 実行予定: {action}", + cancelled: "キャンセルしました。", + jsonOpt: "JSON形式で出力", + yesOpt: "確認をスキップ", + }, + program: { + description: "OmniRoute — 自動フォールバック付きスマートAIルーター", + version: "バージョンを表示して終了", + output: "出力形式 (table, json, jsonl, csv)", + quiet: "重要でない出力を抑制", + no_color: "カラー出力を無効化", + timeout: "HTTPリクエストタイムアウト(ミリ秒)", + api_key: "OmniRouteサーバーのAPIキー", + base_url: "OmniRouteサーバーのベースURL", + context: "このコマンドで使用するサーバーコンテキスト/プロファイル", + lang: "CLI表示言語を設定(OMNIROUTE_LANGを上書き)", + }, + }, + ko: { + common: { + error: "오류: {message}", + serverOffline: "OmniRoute 서버가 오프라인입니다. 시작: omniroute serve", + authRequired: "인증이 필요합니다. OMNIROUTE_API_KEY를 설정하거나 실행하세요: omniroute setup", + rateLimited: "요청 제한 초과. {seconds}초 후 다시 시도하세요.", + timeout: "{ms}ms 후 요청 시간 초과.", + success: "완료.", + yes: "예", + no: "아니오", + confirm: "확실합니까? (예/아니오)", + dryRun: "[시뮬레이션] 실행 예정: {action}", + cancelled: "취소되었습니다.", + jsonOpt: "JSON으로 출력", + yesOpt: "확인 건너뛰기", + }, + program: { + description: "OmniRoute — 자동 폴백 기능을 갖춘 스마트 AI 라우터", + version: "버전 출력 후 종료", + output: "출력 형식 (table, json, jsonl, csv)", + quiet: "불필요한 출력 억제", + no_color: "색상 출력 비활성화", + timeout: "HTTP 요청 타임아웃(밀리초)", + api_key: "OmniRoute 서버의 API 키", + base_url: "OmniRoute 서버 기본 URL", + context: "이 명령에 사용할 서버 컨텍스트/프로필", + lang: "CLI 표시 언어 설정 (OMNIROUTE_LANG 재정의)", + }, + }, + nl: { + common: { + error: "Fout: {message}", + serverOffline: "OmniRoute-server is offline. Start met: omniroute serve", + authRequired: "Authenticatie vereist. Stel OMNIROUTE_API_KEY in of voer uit: omniroute setup", + rateLimited: "Verzoeklimiet overschreden. Probeer opnieuw na {seconds}s.", + timeout: "Verzoek verlopen na {ms}ms.", + success: "Klaar.", + yes: "ja", + no: "nee", + confirm: "Weet u het zeker? (ja/nee)", + dryRun: "[simulatie] zou: {action}", + cancelled: "Geannuleerd.", + jsonOpt: "Uitvoer als JSON", + yesOpt: "Bevestiging overslaan", + }, + program: { + description: "OmniRoute — Slimme AI-router met automatische fallback", + version: "Versie afdrukken en afsluiten", + output: "Uitvoerformaat (table, json, jsonl, csv)", + quiet: "Niet-essentiële uitvoer onderdrukken", + no_color: "Gekleurde uitvoer uitschakelen", + timeout: "HTTP-verzoek timeout in milliseconden", + api_key: "API-sleutel voor de OmniRoute-server", + base_url: "Basis-URL van de OmniRoute-server", + context: "Servercontext/profiel voor dit commando", + lang: "CLI-weergavetaal instellen (overschrijft OMNIROUTE_LANG)", + }, + }, + no: { + common: { + error: "Feil: {message}", + serverOffline: "OmniRoute-serveren er offline. Start med: omniroute serve", + authRequired: "Autentisering kreves. Angi OMNIROUTE_API_KEY eller kjør: omniroute setup", + rateLimited: "Forespørselgrense overskredet. Prøv igjen om {seconds}s.", + timeout: "Forespørselen tidsavbrutt etter {ms}ms.", + success: "Ferdig.", + yes: "ja", + no: "nei", + confirm: "Er du sikker? (ja/nei)", + dryRun: "[simulering] ville: {action}", + cancelled: "Avbrutt.", + jsonOpt: "Utdata som JSON", + yesOpt: "Hopp over bekreftelse", + }, + program: { + description: "OmniRoute — Smart AI-ruter med automatisk fallback", + version: "Skriv ut versjon og avslutt", + output: "Utdataformat (table, json, jsonl, csv)", + quiet: "Undertrykk ikke-essensiell utdata", + no_color: "Deaktiver farget utdata", + timeout: "HTTP-forespørsel timeout i millisekunder", + api_key: "API-nøkkel for OmniRoute-serveren", + base_url: "OmniRoute-serverens basis-URL", + context: "Serverkontekst/profil for denne kommandoen", + lang: "Angi CLI-visningsspråk (overstyrer OMNIROUTE_LANG)", + }, + }, + pl: { + common: { + error: "Błąd: {message}", + serverOffline: "Serwer OmniRoute jest offline. Uruchom: omniroute serve", + authRequired: + "Wymagane uwierzytelnienie. Ustaw OMNIROUTE_API_KEY lub uruchom: omniroute setup", + rateLimited: "Przekroczono limit żądań. Spróbuj ponownie za {seconds}s.", + timeout: "Żądanie przekroczyło czas po {ms}ms.", + success: "Gotowe.", + yes: "tak", + no: "nie", + confirm: "Czy jesteś pewien? (tak/nie)", + dryRun: "[symulacja] wykonałoby: {action}", + cancelled: "Anulowano.", + jsonOpt: "Wyjście jako JSON", + yesOpt: "Pomiń potwierdzenie", + }, + program: { + description: "OmniRoute — Inteligentny router AI z automatycznym fallbackiem", + version: "Wydrukuj wersję i wyjdź", + output: "Format wyjścia (table, json, jsonl, csv)", + quiet: "Pomiń nieistotne wyjście", + no_color: "Wyłącz kolorowe wyjście", + timeout: "Limit czasu żądania HTTP w milisekundach", + api_key: "Klucz API dla serwera OmniRoute", + base_url: "Bazowy URL serwera OmniRoute", + context: "Kontekst/profil serwera dla tego polecenia", + lang: "Ustaw język wyświetlania CLI (nadpisuje OMNIROUTE_LANG)", + }, + }, + pt: { + common: { + error: "Erro: {message}", + serverOffline: "O servidor OmniRoute está offline. Inicie com: omniroute serve", + authRequired: "Autenticação necessária. Defina OMNIROUTE_API_KEY ou execute: omniroute setup", + rateLimited: "Limite de pedidos atingido. Tente novamente em {seconds}s.", + timeout: "O pedido expirou após {ms}ms.", + success: "Concluído.", + yes: "sim", + no: "não", + confirm: "Tem a certeza? (sim/não)", + dryRun: "[simulação] faria: {action}", + cancelled: "Cancelado.", + jsonOpt: "Saída em JSON", + yesOpt: "Ignorar confirmação", + }, + program: { + description: "OmniRoute — Router de IA inteligente com fallback automático", + version: "Mostrar versão e sair", + output: "Formato de saída (table, json, jsonl, csv)", + quiet: "Suprimir saída não essencial", + no_color: "Desativar saída colorida", + timeout: "Timeout de pedidos HTTP em milissegundos", + api_key: "Chave de API para o servidor OmniRoute", + base_url: "URL base do servidor OmniRoute", + context: "Contexto/perfil do servidor para este comando", + lang: "Definir idioma de apresentação do CLI (substitui OMNIROUTE_LANG)", + }, + }, + ro: { + common: { + error: "Eroare: {message}", + serverOffline: "Serverul OmniRoute este offline. Porniți cu: omniroute serve", + authRequired: "Autentificare necesară. Setați OMNIROUTE_API_KEY sau rulați: omniroute setup", + rateLimited: "Limita de cereri depășită. Încercați din nou după {seconds}s.", + timeout: "Cererea a expirat după {ms}ms.", + success: "Gata.", + yes: "da", + no: "nu", + confirm: "Sigur? (da/nu)", + dryRun: "[simulare] ar face: {action}", + cancelled: "Anulat.", + jsonOpt: "Ieșire ca JSON", + yesOpt: "Omite confirmarea", + }, + program: { + description: "OmniRoute — Router AI inteligent cu fallback automat", + version: "Afișează versiunea și ieși", + output: "Format de ieșire (table, json, jsonl, csv)", + quiet: "Suprimă ieșirile neesențiale", + no_color: "Dezactivează ieșirea colorată", + timeout: "Timeout cereri HTTP în milisecunde", + api_key: "Cheie API pentru serverul OmniRoute", + base_url: "URL de bază al serverului OmniRoute", + context: "Contextul/profilul serverului pentru această comandă", + lang: "Setează limba de afișare CLI (suprascrie OMNIROUTE_LANG)", + }, + }, + ru: { + common: { + error: "Ошибка: {message}", + serverOffline: "Сервер OmniRoute отключён. Запустите: omniroute serve", + authRequired: + "Требуется аутентификация. Установите OMNIROUTE_API_KEY или выполните: omniroute setup", + rateLimited: "Превышен лимит запросов. Повторите через {seconds}с.", + timeout: "Запрос истёк через {ms}мс.", + success: "Готово.", + yes: "да", + no: "нет", + confirm: "Вы уверены? (да/нет)", + dryRun: "[симуляция] выполнит: {action}", + cancelled: "Отменено.", + jsonOpt: "Вывод в формате JSON", + yesOpt: "Пропустить подтверждение", + }, + program: { + description: "OmniRoute — Умный AI-маршрутизатор с автоматическим переключением", + version: "Вывести версию и выйти", + output: "Формат вывода (table, json, jsonl, csv)", + quiet: "Подавить несущественный вывод", + no_color: "Отключить цветной вывод", + timeout: "Таймаут HTTP-запросов в миллисекундах", + api_key: "API-ключ для сервера OmniRoute", + base_url: "Базовый URL сервера OmniRoute", + context: "Контекст/профиль сервера для этой команды", + lang: "Установить язык отображения CLI (переопределяет OMNIROUTE_LANG)", + }, + }, + sk: { + common: { + error: "Chyba: {message}", + serverOffline: "Server OmniRoute je offline. Spustite: omniroute serve", + authRequired: + "Vyžaduje sa overenie. Nastavte OMNIROUTE_API_KEY alebo spustite: omniroute setup", + rateLimited: "Prekročený limit požiadaviek. Skúste za {seconds}s.", + timeout: "Požiadavka vypršala po {ms}ms.", + success: "Hotovo.", + yes: "áno", + no: "nie", + confirm: "Ste si istí? (áno/nie)", + dryRun: "[simulácia] by vykonalo: {action}", + cancelled: "Zrušené.", + jsonOpt: "Výstup ako JSON", + yesOpt: "Preskočiť potvrdenie", + }, + program: { + description: "OmniRoute — Inteligentný AI router s automatickým prepínaním", + version: "Vypísať verziu a skončiť", + output: "Formát výstupu (table, json, jsonl, csv)", + quiet: "Potlačiť nepodstatný výstup", + no_color: "Zakázať farebný výstup", + timeout: "Časový limit HTTP požiadaviek v milisekundách", + api_key: "API kľúč pre server OmniRoute", + base_url: "Základná URL servera OmniRoute", + context: "Kontext/profil servera pre tento príkaz", + lang: "Nastaviť jazyk zobrazenia CLI (prepíše OMNIROUTE_LANG)", + }, + }, + sv: { + common: { + error: "Fel: {message}", + serverOffline: "OmniRoute-servern är offline. Starta med: omniroute serve", + authRequired: "Autentisering krävs. Ange OMNIROUTE_API_KEY eller kör: omniroute setup", + rateLimited: "Begäransgräns nådd. Försök igen om {seconds}s.", + timeout: "Begäran tog slut efter {ms}ms.", + success: "Klar.", + yes: "ja", + no: "nej", + confirm: "Är du säker? (ja/nej)", + dryRun: "[simulering] skulle: {action}", + cancelled: "Avbruten.", + jsonOpt: "Utdata som JSON", + yesOpt: "Hoppa över bekräftelse", + }, + program: { + description: "OmniRoute — Smart AI-router med automatisk fallback", + version: "Skriv ut version och avsluta", + output: "Utdataformat (table, json, jsonl, csv)", + quiet: "Undertryck icke-väsentlig utdata", + no_color: "Inaktivera färgad utdata", + timeout: "HTTP-begärans timeout i millisekunder", + api_key: "API-nyckel för OmniRoute-servern", + base_url: "OmniRoute-serverns bas-URL", + context: "Serverkontext/profil för det här kommandot", + lang: "Ange CLI-visningsspråk (åsidosätter OMNIROUTE_LANG)", + }, + }, + th: { + common: { + error: "ข้อผิดพลาด: {message}", + serverOffline: "เซิร์ฟเวอร์ OmniRoute ออฟไลน์ เริ่มด้วย: omniroute serve", + authRequired: "ต้องการการยืนยันตัวตน ตั้งค่า OMNIROUTE_API_KEY หรือรัน: omniroute setup", + rateLimited: "เกินขีดจำกัดคำขอ ลองใหม่หลังจาก {seconds}วินาที", + timeout: "คำขอหมดเวลาหลังจาก {ms}ms", + success: "เสร็จสิ้น", + yes: "ใช่", + no: "ไม่", + confirm: "คุณแน่ใจหรือไม่? (ใช่/ไม่)", + dryRun: "[จำลอง] จะทำ: {action}", + cancelled: "ยกเลิกแล้ว", + jsonOpt: "ส่งออกเป็น JSON", + yesOpt: "ข้ามการยืนยัน", + }, + program: { + description: "OmniRoute — AI Router อัจฉริยะพร้อม Auto Fallback", + version: "แสดงเวอร์ชันและออก", + output: "รูปแบบเอาต์พุต (table, json, jsonl, csv)", + quiet: "ซ่อนเอาต์พุตที่ไม่จำเป็น", + no_color: "ปิดใช้งานเอาต์พุตสี", + timeout: "หมดเวลา HTTP request ในมิลลิวินาที", + api_key: "API Key สำหรับ OmniRoute server", + base_url: "Base URL ของ OmniRoute server", + context: "บริบท/โปรไฟล์ของเซิร์ฟเวอร์สำหรับคำสั่งนี้", + lang: "ตั้งค่าภาษาแสดงผล CLI (แทนที่ OMNIROUTE_LANG)", + }, + }, + tr: { + common: { + error: "Hata: {message}", + serverOffline: "OmniRoute sunucusu çevrimdışı. Başlatın: omniroute serve", + authRequired: + "Kimlik doğrulama gerekli. OMNIROUTE_API_KEY ayarlayın veya çalıştırın: omniroute setup", + rateLimited: "İstek limiti aşıldı. {seconds}s sonra tekrar deneyin.", + timeout: "İstek {ms}ms sonra zaman aşımına uğradı.", + success: "Tamamlandı.", + yes: "evet", + no: "hayır", + confirm: "Emin misiniz? (evet/hayır)", + dryRun: "[simülasyon] yapılacaktı: {action}", + cancelled: "İptal edildi.", + jsonOpt: "JSON olarak çıktı", + yesOpt: "Onayı atla", + }, + program: { + description: "OmniRoute — Otomatik Fallback ile Akıllı AI Yönlendirici", + version: "Sürümü yazdır ve çık", + output: "Çıktı formatı (table, json, jsonl, csv)", + quiet: "Önemsiz çıktıyı gizle", + no_color: "Renkli çıktıyı devre dışı bırak", + timeout: "HTTP istek zaman aşımı (milisaniye)", + api_key: "OmniRoute sunucusu için API anahtarı", + base_url: "OmniRoute sunucusu temel URL'si", + context: "Bu komut için sunucu bağlamı/profili", + lang: "CLI görüntüleme dilini ayarla (OMNIROUTE_LANG'ı geçersiz kılar)", + }, + }, + "uk-UA": { + common: { + error: "Помилка: {message}", + serverOffline: "Сервер OmniRoute відключено. Запустіть: omniroute serve", + authRequired: + "Потрібна автентифікація. Встановіть OMNIROUTE_API_KEY або виконайте: omniroute setup", + rateLimited: "Перевищено ліміт запитів. Повторіть через {seconds}с.", + timeout: "Запит завершився через {ms}мс.", + success: "Готово.", + yes: "так", + no: "ні", + confirm: "Ви впевнені? (так/ні)", + dryRun: "[симуляція] виконає: {action}", + cancelled: "Скасовано.", + jsonOpt: "Вивести у форматі JSON", + yesOpt: "Пропустити підтвердження", + }, + program: { + description: "OmniRoute — Розумний AI-маршрутизатор з автоматичним перемиканням", + version: "Вивести версію та вийти", + output: "Формат виведення (table, json, jsonl, csv)", + quiet: "Приховати несуттєвий вивід", + no_color: "Вимкнути кольоровий вивід", + timeout: "Тайм-аут HTTP-запитів у мілісекундах", + api_key: "API-ключ для сервера OmniRoute", + base_url: "Базовий URL сервера OmniRoute", + context: "Контекст/профіль сервера для цієї команди", + lang: "Встановити мову відображення CLI (замінює OMNIROUTE_LANG)", + }, + }, + vi: { + common: { + error: "Lỗi: {message}", + serverOffline: "Máy chủ OmniRoute đang offline. Khởi động với: omniroute serve", + authRequired: "Cần xác thực. Đặt OMNIROUTE_API_KEY hoặc chạy: omniroute setup", + rateLimited: "Đã vượt giới hạn yêu cầu. Thử lại sau {seconds}s.", + timeout: "Yêu cầu hết thời gian sau {ms}ms.", + success: "Xong.", + yes: "có", + no: "không", + confirm: "Bạn có chắc không? (có/không)", + dryRun: "[mô phỏng] sẽ: {action}", + cancelled: "Đã hủy.", + jsonOpt: "Xuất dưới dạng JSON", + yesOpt: "Bỏ qua xác nhận", + }, + program: { + description: "OmniRoute — Bộ định tuyến AI thông minh với tự động chuyển đổi dự phòng", + version: "In phiên bản và thoát", + output: "Định dạng đầu ra (table, json, jsonl, csv)", + quiet: "Ẩn đầu ra không cần thiết", + no_color: "Tắt đầu ra màu sắc", + timeout: "Thời gian chờ yêu cầu HTTP tính bằng mili giây", + api_key: "Khóa API cho máy chủ OmniRoute", + base_url: "URL cơ sở của máy chủ OmniRoute", + context: "Bối cảnh/hồ sơ máy chủ cho lệnh này", + lang: "Đặt ngôn ngữ hiển thị CLI (ghi đè OMNIROUTE_LANG)", + }, + }, + "zh-CN": { + common: { + error: "错误:{message}", + serverOffline: "OmniRoute 服务器已离线。请启动:omniroute serve", + authRequired: "需要认证。请设置 OMNIROUTE_API_KEY 或运行:omniroute setup", + rateLimited: "请求超出限制。请在 {seconds}s 后重试。", + timeout: "请求在 {ms}ms 后超时。", + success: "完成。", + yes: "是", + no: "否", + confirm: "确定吗?(是/否)", + dryRun: "【模拟】将执行:{action}", + cancelled: "已取消。", + jsonOpt: "以 JSON 格式输出", + yesOpt: "跳过确认", + }, + program: { + description: "OmniRoute — 具有自动故障转移的智能 AI 路由器", + version: "打印版本并退出", + output: "输出格式(table, json, jsonl, csv)", + quiet: "禁止非必要输出", + no_color: "禁用彩色输出", + timeout: "HTTP 请求超时(毫秒)", + api_key: "OmniRoute 服务器的 API 密钥", + base_url: "OmniRoute 服务器的基础 URL", + context: "此命令使用的服务器上下文/配置文件", + lang: "设置 CLI 显示语言(覆盖 OMNIROUTE_LANG)", + }, + }, +}; + +// Languages with no translation in this script — will be created as empty objects +// All keys fall back to `en` via i18n.mjs's fallback mechanism. +const SCAFFOLD_ONLY = ["bn", "gu", "he", "in", "mr", "ms", "phi", "sw", "ta", "te", "ur"]; + +let created = 0; +let skipped = 0; + +for (const locale of locales) { + const { code } = locale; + if (code === "en" || code === "pt-BR") { + skipped++; + continue; + } + + const filePath = join(LOCALES_DIR, `${code}.json`); + if (existsSync(filePath) && !FORCE) { + skipped++; + continue; + } + + const translations = TRANSLATIONS[code] || {}; + const content = Object.keys(translations).length > 0 ? translations : {}; + writeFileSync(filePath, JSON.stringify(content, null, 2) + "\n", "utf8"); + console.log(` ✓ ${code.padEnd(8)} ${locale.english}`); + created++; +} + +console.log(`\nGenerated: ${created} | Skipped (already exist): ${skipped}`); diff --git a/bin/omniroute.mjs b/bin/omniroute.mjs index 939a568c68..43702422e3 100644 --- a/bin/omniroute.mjs +++ b/bin/omniroute.mjs @@ -67,6 +67,20 @@ function loadEnvFile() { loadEnvFile(); +// Apply --lang before Commander parses (program descriptions call t() during setup) +{ + const langIdx = process.argv.findIndex((a) => a === "--lang"); + const langArg = langIdx >= 0 ? process.argv[langIdx + 1] : null; + const langEnv = process.env.OMNIROUTE_LANG; + const chosen = langArg || langEnv; + if (chosen) { + const { setLocale } = await import( + pathToFileURL(join(ROOT, "bin", "cli", "i18n.mjs")).href + ); + setLocale(chosen); + } +} + // Register update notifier — checks npm once per 24h, notifies on exit via stderr. const _pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")); const _notifier = updateNotifier({ pkg: _pkg, updateCheckInterval: 1000 * 60 * 60 * 24 }); diff --git a/docs/guides/I18N.md b/docs/guides/I18N.md index 7da3906fc5..8ef8a2965f 100644 --- a/docs/guides/I18N.md +++ b/docs/guides/I18N.md @@ -243,6 +243,75 @@ python3 scripts/i18n/i18n_autotranslate.py \ - Sends paragraphs to LLM with technical translation system prompt - Supports all 30 languages +## CLI i18n + +The `omniroute` CLI has its own i18n layer separate from the Next.js dashboard. + +### How it works + +- Every user-facing string in CLI commands goes through `t("module.key", vars)` from `bin/cli/i18n.mjs`. +- Catalogs are JSON files in `bin/cli/locales/` — 42 ship out-of-the-box. +- Locale falls back to `en` for any missing key, so partial translations are valid. +- The source of truth for available locales is `config/i18n.json` (shared with the dashboard). + +### Locale selection + +Detection order (first match wins): + +| Priority | Source | Example | +| -------- | ------------------------ | --------------------------------------- | +| 1 | `--lang` flag | `omniroute --lang de status` | +| 2 | `OMNIROUTE_LANG` env var | `OMNIROUTE_LANG=ja omniroute providers` | +| 3 | `LC_ALL` system env | auto-detected from terminal locale | +| 4 | `LC_MESSAGES` system env | auto-detected from terminal locale | +| 5 | `LANG` system env | auto-detected from terminal locale | +| 6 | Fallback | `en` | + +Locale codes with underscores (`pt_BR`) are normalized to hyphen form (`pt-BR`). +Locale codes are validated against `/^[a-zA-Z0-9-]+$/` — path traversal is rejected. + +### Saving a language preference + +```bash +# Set language and save to ~/.omniroute/.env (persists across sessions) +omniroute config lang set pt-BR + +# View current language +omniroute config lang get + +# List all 42 available languages +omniroute config lang list + +# JSON output +omniroute config lang list --output json +``` + +The saved preference is written atomically to `~/.omniroute/.env` and is loaded by the +CLI bootstrap before any command runs. + +### One-time override + +```bash +# Override for one command only (not persisted) +omniroute --lang de providers list +``` + +Note: the `--lang` flag does NOT write to the env file — it only affects the current +invocation. Use `config lang set` to persist. + +### Available locales + +42 locale files ship in `bin/cli/locales/`. Full translations: `en`, `pt-BR`. +Scaffold-only (all keys fall back to `en`): `bn`, `gu`, `he`, `in`, `mr`, `ms`, `phi`, `sw`, `ta`, `te`, `ur`. +All other 29 locales have `common` + `program` keys translated. + +### Adding a new CLI locale + +1. Add the locale entry to `config/i18n.json`. +2. Run `node bin/cli/scripts/generate-locales.mjs` — creates the locale file. +3. Translate the keys (or leave as `{}` for en-fallback scaffold). +4. PRs must add strings to `en.json` and `pt-BR.json`; other files are best-effort. + ## Validation & QA ### validate_translation.py diff --git a/tests/unit/cli-lang-commands.test.ts b/tests/unit/cli-lang-commands.test.ts new file mode 100644 index 0000000000..72ec775ae2 --- /dev/null +++ b/tests/unit/cli-lang-commands.test.ts @@ -0,0 +1,275 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync, readFileSync, existsSync, writeFileSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; + +let tmpDir: string; +let origDataDir: string | undefined; +let origOmniLang: string | undefined; + +test.before(() => { + tmpDir = mkdtempSync(join(tmpdir(), "omniroute-lang-test-")); + origDataDir = process.env.DATA_DIR; + origOmniLang = process.env.OMNIROUTE_LANG; + process.env.DATA_DIR = tmpDir; + delete process.env.OMNIROUTE_LANG; +}); + +test.after(() => { + if (origDataDir === undefined) delete process.env.DATA_DIR; + else process.env.DATA_DIR = origDataDir; + if (origOmniLang === undefined) delete process.env.OMNIROUTE_LANG; + else process.env.OMNIROUTE_LANG = origOmniLang; + try { + rmSync(tmpDir, { recursive: true, force: true }); + } catch {} +}); + +// ── i18n.mjs security ───────────────────────────────────────────────────────── + +test("normalize rejeita path traversal com ../", async () => { + const { resetForTests, setLocale, getLocale } = await import("../../bin/cli/i18n.mjs"); + resetForTests(); + setLocale("../etc/passwd"); + const locale = getLocale(); + assert.equal(locale, "en", `Deveria ter fallback para en, obteve: ${locale}`); + resetForTests(); +}); + +test("normalize rejeita código com caracteres especiais", async () => { + const { resetForTests, setLocale, getLocale } = await import("../../bin/cli/i18n.mjs"); + resetForTests(); + setLocale("pt;rm -rf /"); + const locale = getLocale(); + assert.equal(locale, "en", `Deveria ter fallback para en, obteve: ${locale}`); + resetForTests(); +}); + +test("normalize aceita código válido com hífen (pt-BR)", async () => { + const { resetForTests, setLocale, getLocale } = await import("../../bin/cli/i18n.mjs"); + resetForTests(); + setLocale("pt-BR"); + const locale = getLocale(); + assert.equal(locale, "pt-BR"); + resetForTests(); +}); + +test("normalize converte underscore para hífen (pt_BR → pt-BR)", async () => { + const { resetForTests, setLocale, getLocale } = await import("../../bin/cli/i18n.mjs"); + resetForTests(); + setLocale("pt_BR"); + const locale = getLocale(); + assert.equal(locale, "pt-BR"); + resetForTests(); +}); + +// ── config lang get ──────────────────────────────────────────────────────────── + +test("runConfigLangGetCommand retorna 0 e imprime o locale ativo", async () => { + const { resetForTests, setLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangGetCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const output: string[] = []; + const origLog = console.log; + console.log = (...args: unknown[]) => output.push(args.join(" ")); + + try { + const code = await runConfigLangGetCommand({}); + assert.equal(code, 0); + assert.ok( + output.some((l) => l.includes("en")), + `Esperava 'en' no output: ${output.join("|")}` + ); + } finally { + console.log = origLog; + resetForTests(); + } +}); + +test("runConfigLangGetCommand --json retorna objeto com code e name", async () => { + const { resetForTests, setLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangGetCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const chunks: string[] = []; + const origLog = console.log; + console.log = (...args: unknown[]) => chunks.push(args.join(" ")); + + try { + await runConfigLangGetCommand({ json: true }); + const parsed = JSON.parse(chunks.join("")); + assert.ok("code" in parsed, "JSON deve ter campo code"); + assert.ok("name" in parsed, "JSON deve ter campo name"); + assert.equal(parsed.code, "en"); + } finally { + console.log = origLog; + resetForTests(); + } +}); + +// ── config lang list ─────────────────────────────────────────────────────────── + +test("runConfigLangListCommand --json lista locales com campo active", async () => { + const { resetForTests, setLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangListCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const chunks: string[] = []; + const origLog = console.log; + console.log = (...args: unknown[]) => chunks.push(args.join(" ")); + + try { + const exitCode = await runConfigLangListCommand({ json: true }); + assert.equal(exitCode, 0); + const arr = JSON.parse(chunks.join("")); + assert.ok(Array.isArray(arr), "Deve retornar array"); + assert.ok(arr.length >= 2, "Deve ter ao menos en e pt-BR"); + const enEntry = arr.find((l: any) => l.code === "en"); + assert.ok(enEntry, "Deve ter entrada para en"); + assert.ok("active" in enEntry, "Deve ter campo active"); + assert.equal(enEntry.active, true, "en deve ser active quando locale for en"); + } finally { + console.log = origLog; + resetForTests(); + } +}); + +// ── config lang set ──────────────────────────────────────────────────────────── + +test("runConfigLangSetCommand salva locale no .env e chama setLocale imediatamente", async () => { + const { resetForTests, setLocale, getLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangSetCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const chunks: string[] = []; + const origLog = console.log; + console.log = (...args: unknown[]) => chunks.push(args.join(" ")); + + try { + const exitCode = await runConfigLangSetCommand("pt-BR", {}); + assert.equal(exitCode, 0); + const envPath = join(tmpDir, ".env"); + assert.ok(existsSync(envPath), ".env deve existir após set"); + const content = readFileSync(envPath, "utf8"); + assert.ok(content.includes("OMNIROUTE_LANG=pt-BR"), "Deve persistir OMNIROUTE_LANG=pt-BR"); + assert.equal(getLocale(), "pt-BR", "setLocale deve ter sido chamado imediatamente em-processo"); + } finally { + console.log = origLog; + resetForTests(); + } +}); + +test("runConfigLangSetCommand retorna 1 para código desconhecido", async () => { + const { resetForTests, setLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangSetCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const errors: string[] = []; + const origErr = console.error; + console.error = (...args: unknown[]) => errors.push(args.join(" ")); + + try { + const exitCode = await runConfigLangSetCommand("xx-NONEXISTENT", {}); + assert.equal(exitCode, 1); + } finally { + console.error = origErr; + resetForTests(); + } +}); + +test("runConfigLangSetCommand retorna 1 quando code não fornecido", async () => { + const { resetForTests, setLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangSetCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const errors: string[] = []; + const origErr = console.error; + console.error = (...args: unknown[]) => errors.push(args.join(" ")); + + try { + const exitCode = await runConfigLangSetCommand(undefined, {}); + assert.equal(exitCode, 1); + } finally { + console.error = origErr; + resetForTests(); + } +}); + +test("runConfigLangSetCommand retorna 0 quando já ativo (sem --force)", async () => { + const { resetForTests, setLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangSetCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const chunks: string[] = []; + const origLog = console.log; + console.log = (...args: unknown[]) => chunks.push(args.join(" ")); + + try { + const exitCode = await runConfigLangSetCommand("en", {}); + assert.equal(exitCode, 0, "Deve retornar 0 mesmo quando locale já está ativo"); + } finally { + console.log = origLog; + resetForTests(); + } +}); + +test("runConfigLangSetCommand --force salva mesmo quando locale já ativo", async () => { + const { resetForTests, setLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangSetCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const chunks: string[] = []; + const origLog = console.log; + console.log = (...args: unknown[]) => chunks.push(args.join(" ")); + + try { + const exitCode = await runConfigLangSetCommand("en", { force: true }); + assert.equal(exitCode, 0, "Com --force deve retornar 0"); + const envPath = join(tmpDir, ".env"); + if (existsSync(envPath)) { + const content = readFileSync(envPath, "utf8"); + assert.ok(content.includes("OMNIROUTE_LANG=en"), "Deve ter gravado OMNIROUTE_LANG=en"); + } + } finally { + console.log = origLog; + resetForTests(); + } +}); + +// ── upsertEnvLine (testado indiretamente via set) ───────────────────────────── + +test("runConfigLangSetCommand atualiza chave sem duplicar quando já existe", async () => { + const { resetForTests, setLocale } = await import("../../bin/cli/i18n.mjs"); + const { runConfigLangSetCommand } = await import("../../bin/cli/commands/config.mjs"); + resetForTests(); + setLocale("en"); + + const envPath = join(tmpDir, ".env"); + writeFileSync(envPath, "OMNIROUTE_LANG=de\n", "utf8"); + + const chunks: string[] = []; + const origLog = console.log; + console.log = (...args: unknown[]) => chunks.push(args.join(" ")); + + try { + const exitCode = await runConfigLangSetCommand("pt-BR", { force: true }); + assert.equal(exitCode, 0); + const content = readFileSync(envPath, "utf8"); + assert.ok(content.includes("OMNIROUTE_LANG=pt-BR"), "Deve ter atualizado para pt-BR"); + const matches = content.match(/OMNIROUTE_LANG=/g); + assert.equal(matches?.length, 1, "Deve ter exatamente uma ocorrência de OMNIROUTE_LANG"); + } finally { + console.log = origLog; + resetForTests(); + } +});