Compare commits

...

67 Commits

Author SHA1 Message Date
Diego Rodrigues de Sa e Souza 437cf9bab0 chore(release): v3.1.10 — OmniRoute v3.1.9 remaining bug fixes sprint (#720)
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-03-28 19:54:45 -03:00
Diego Rodrigues de Sa e Souza 9ffad1005e Merge pull request #713 from diegosouzapw/release/v3.1.9
Build Electron Desktop App / Validate version (push) Failing after 38s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
chore(release): v3.1.9 — schema coercion, tool sanitization, bug fixes
2026-03-28 17:37:08 -03:00
diegosouzapw 65edddd62e refactor(open-sse): remove unused imports from translator/index.ts
remove unused imports coerceToolSchemas and sanitizeToolDescriptions from translator/index.ts to satisfy lint and prevent unused import issues
2026-03-28 17:26:55 -03:00
diegosouzapw a7cdcd8b3a chore(release): v3.1.9 — schema coercion, tool sanitization, clearAllModels i18n, bug fixes #605 #709 #710 #711 2026-03-28 16:35:20 -03:00
diegosouzapw 3d6b85ed20 fix: update Windsurf test to match merged config notes 2026-03-28 16:31:46 -03:00
diegosouzapw 7abea2020c Merge feature-tests: schema coercion, tool sanitization, Codex auth export, enhanced test suite 2026-03-28 16:27:32 -03:00
diegosouzapw e16c34f0e3 Merge feat/clear-all-models-button: clearAllModels i18n translations for 30 languages 2026-03-28 16:19:46 -03:00
diegosouzapw 4bfda6a145 Merge fix/issue-605: strip proxy_ prefix in non-streaming Claude responses (#605, #592) 2026-03-28 16:17:06 -03:00
diegosouzapw 98470e8551 Merge fix/issue-711: provider max_tokens cap + upstream sync tasks (#711) 2026-03-28 16:08:12 -03:00
diegosouzapw df558ab8d6 Merge fix/issue-710: A2A TaskManager globalThis singleton + E2E auth (#710) 2026-03-28 16:07:34 -03:00
diegosouzapw c07372b58c fix: ensure output directory exists for system-info (#709) 2026-03-28 15:54:15 -03:00
diegosouzapw 00f59b95ae fix: protocol clients e2e dev mode singleton and auth (#710) 2026-03-28 15:52:29 -03:00
diegosouzapw 8915a7c2cd fix: add provider-specific max_tokens cap (#711) 2026-03-28 15:41:59 -03:00
diegosouzapw 8595964ab8 feat/fix: implement upstream sync tasks 1-7 2026-03-28 14:48:57 -03:00
diegosouzapw 922dae8546 feat: add Codex auth.json export and apply-local buttons for CLI integration
- Add codexAuthFile.ts utility: builds Codex auth.json payload from OAuth connection
  (id_token, access_token, refresh_token, account_id) with auto-refresh if expired
- Add POST /api/providers/[id]/codex-auth/export: downloads auth.json file
- Add POST /api/providers/[id]/codex-auth/apply-local: writes auth.json to local CLI path
- Add 'Apply auth' and 'Export auth' buttons to ConnectionRow (Codex provider only)
- Add i18n keys for en and pt-BR
2026-03-28 13:28:06 -03:00
diegosouzapw 69b3e23400 test(tests): introduce feature-tests suite and update coverage tooling
- add unit tests for API auth, display/error utilities, login bootstrap,
  model combo mappings, provider validation branches, and usage analytics
- add COVERAGE_PLAN.md and extend CONTRIBUTING.md with coverage notes and
  workflow guidance
- update package.json to adjust test:coverage thresholds and add coverage:report;
  include c8 as a devDependency
- introduce test scaffolding and ensure compatibility with existing test runners
- align tests with open-sse changes and improve overall test coverage planning
2026-03-28 12:58:31 -03:00
diegosouzapw 55325773dc feat(open-sse): add schema coercion and tool sanitization
- introduce open-sse/translator/helpers/schemaCoercion.ts to coerce
  numeric JSON Schema fields encoded as strings
- wire coerceToolSchemas and sanitizeToolDescriptions into translator
  pipeline; ensure tool descriptions are sanitized
- inject empty reasoning content for tool calls when target is OpenAI
  format
- update qwen base URL to DashScope-compatible endpoint
- extend antigravity static catalog with Gemini 3.1 pro preview models and
  update Gemini model specs with preview aliases
- implement call log max cap caching with TTL; expose invalidateCallLogsMaxCache
  and invalidate on settings PATCH
- add tests: call-log-cap.test.mjs and tool-request-sanitization.test.mjs;
  extend tests for Windsurf integration and gemini previews
- update CLI runtime and tools to include Windsurf as a guide-only tool
- add maxCallLogs to validation schemas (settings and updateSettings)
- add Czech README (README.cs.md) to repository
2026-03-28 12:33:13 -03:00
Diego Rodrigues de Sa e Souza cfb390936a Merge pull request #697 from diegosouzapw/fix/issue-667-opencode-zen-models
fix: add opencode-zen to PROVIDER_MODELS_CONFIG (#667)
2026-03-28 01:55:06 -03:00
diegosouzapw c5f344f333 fix: add opencode-zen to PROVIDER_MODELS_CONFIG (#667)
The 'Import from /models' button failed because opencode-zen was not
registered in PROVIDER_MODELS_CONFIG. The provider's API at
https://opencode.ai/zen/v1/models returns standard OpenAI-compatible
format and is now properly configured for model import.
2026-03-28 01:54:39 -03:00
diegosouzapw ba4b496306 Merge PR #666: Add Claude prompt cache logging and exclude cache reads
Includes fixes applied during review:
- Removed duplicate imports in chatCore.ts
- Fixed stray translatedBody argument (stream boolean bug)
- Fixed truncated test file
- Fixed usageExtractor cached_tokens fallback

Closes #688, Closes #640
2026-03-28 01:53:25 -03:00
diegosouzapw c48554589c fix: repair test failures from PR #666 changes
- Fix usageExtractor cached_tokens fallback for Responses API (use cache_read_input_tokens when input_tokens_details is absent)
- Fix truncated claude-native-passthrough-tools.test.mjs that caused parse error
2026-03-28 01:50:04 -03:00
Diego Rodrigues de Sa e Souza da0851e21d Merge pull request #690 from alper-han/feat/i18n-tr
Reviewed and approved via consolidated analysis. Turkish locale (31st language) follows existing i18n patterns perfectly. Registered in config.ts, generate-multilang.mjs, and full tr.json translation file.
2026-03-28 01:46:04 -03:00
Diego Rodrigues de Sa e Souza d2d05abac0 Merge pull request #693 from christopher-s/main
Reviewed and approved via consolidated analysis. GLM-5.1 addition and pricing corrections match official Z.AI pricing page. All 5 files follow existing patterns.
2026-03-28 01:45:52 -03:00
Diego Rodrigues de Sa e Souza de3e0423cc Merge pull request #696 from benjaminkitt/fix/input-stream-invalid-boolean
Reviewed and approved via consolidated analysis. Fix is surgical (1 line removed) with 122 lines of regression tests covering stream=true, stream=false and guard scenarios. Resolves #677.
2026-03-28 01:45:39 -03:00
Benjamin Kitt 8d742d7938 test: add regression tests for stream boolean in claude passthrough
Three tests covering the fixed bug where translateRequest received an
object instead of a boolean for the stream parameter:
- stream=true round-trip produces boolean true
- stream=false round-trip produces boolean false
- guard test documenting that passing an object as stream breaks typing

Co-Authored-By: Craft Agent <agents-noreply@craft.do>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 22:38:34 -05:00
Benjamin Kitt 682fd550fa fix(core): remove extra arg in claude passthrough translateRequest call
The second translateRequest call in the claude->openai->claude passthrough
path had an extra `translatedBody` argument before `stream`, shifting all
parameters by one. This caused the `stream` field in the upstream request
to be set to an object instead of a boolean, producing:
  "stream: Input should be a valid boolean"

Co-Authored-By: Craft Agent <agents-noreply@craft.do>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 22:30:02 -05:00
Chris Staley abcf836a0c feat: add GLM-5.1 to GLM Coding provider, update GLM-5 pricing
- Add glm-5.1 model to GLM Coding provider with fitness scores
- Update glm-5 pricing to match Z.AI API page ($1/$3.2/$0.2)
- Set glm-5.1 pricing to $1.2/$5/$0.3 per Z.AI
- Remove glm-4-32b (deprecated, returns empty from upstream)
- Rename Z.AI provider display name from "Z.AI (GLM-5)" to "Z.AI"
- Update zai pricing section to match glm pricing
2026-03-27 16:23:44 -06:00
diegosouzapw b123fb2cc7 chore(release): bump version to v3.1.8 and global i18n sync
Build Electron Desktop App / Validate version (push) Failing after 36s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
2026-03-27 18:08:16 -03:00
Diego Rodrigues de Sa e Souza 0da3621a68 Merge pull request #692 from diegosouzapw/fix/recent-bugs
fix: resolve issues 681, 684, 685
2026-03-27 18:04:03 -03:00
alper-han 8ed452d9ea feat: add Turkish translations 2026-03-27 22:28:21 +03:00
diegosouzapw f380d44697 fix(core): hidden models flag, antigravity streaming, and i18n translation sync (#681, #684, #685) 2026-03-27 16:17:28 -03:00
Chris 86d377a2f0 fix: remove id/type from tool_calls delta chunks in Responses API streaming (#683)
In OpenAI Chat Completions streaming format, the tool call id and type
should only appear on the first chunk (tool declaration). Subsequent
argument delta chunks should only include index and function.arguments.

Including id on every delta chunk caused openai-to-claude.ts to emit
a new content_block_start for each chunk, breaking Claude Code ACP
sessions with malformed Claude-format streams.

Fixes #682

Co-authored-by: Chris Staley <christopher.staley@protonmail.com>
2026-03-27 15:25:16 -03:00
diegosouzapw 508a6d99f5 chore(release): bump version to v3.1.7 and fix SSE parsing bug
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
2026-03-27 15:17:13 -03:00
Paijo 63e42047e3 fix: hasValuableContent explicit boolean returns for SSE streaming (#676)
The hasValuableContent() function in streamHelpers.ts returned undefined
instead of explicit false when checking empty delta chunks. This caused
JavaScript type coercion issues where undefined !== '' evaluated to true,
passing empty chunks through to clients.

Fix: Replace implicit returns with explicit boolean returns using
typeof checks and length comparisons for all content fields (content,
reasoning_content, tool_calls, text, thinking, partial_json).

Test: Added unit tests covering OpenAI, Claude, and Gemini format edge cases.

Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
2026-03-27 15:12:51 -03:00
diegosouzapw 13829de0d9 release: v3.1.6 — Claude tool name fix + Clear All Models alias cleanup
Build Electron Desktop App / Validate version (push) Failing after 35s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- fix: restore native Claude tool names in passthrough responses (PR #663 by @coobabm)
- fix: Clear All Models button now also removes aliases (PR #664 by @rdself)
- fix: completed truncated test from PR #663, added Claude-to-Claude passthrough test
- docs: update CHANGELOG and OpenAPI spec
2026-03-27 06:23:52 -03:00
Diego Rodrigues de Sa e Souza ad7f570be5 Merge pull request #664 from rdself/fix/clear-all-models-button
fix: Clear All Models button now also removes aliases
2026-03-27 06:14:16 -03:00
Diego Rodrigues de Sa e Souza 9ba4f966db Merge pull request #663 from coobabm/codex/claude-native-tool-fix-push
Fix Claude native tool names for Claude Code
2026-03-27 06:14:12 -03:00
cai kerui ae8d2ac2e1 Merge branch 'main' into codex/claude-cache-log-accounting 2026-03-27 17:25:38 +09:00
cai kerui 93beb068a3 Add Claude prompt cache logging and exclude cache reads 2026-03-27 15:14:54 +09:00
cai kerui e88d260acd Merge branch 'main' into codex/claude-native-tool-fix-push 2026-03-27 14:37:02 +09:00
R.D. 8121238872 fix: Clear All Models button now also removes associated aliases
The Clear All Models button was only deleting custom models from the
database but leaving their aliases intact, so the UI didn't reflect
the deletion. Now it also deletes all aliases belonging to the provider
and refreshes the alias state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 01:07:03 -04:00
cai kerui 161e377ec1 Fix Claude native tool names for Claude Code 2026-03-27 14:00:05 +09:00
diegosouzapw ad4bd800aa release: v3.1.5 — backoff auto-decay fix + Chinese i18n overhaul
Build Electron Desktop App / Validate version (push) Failing after 42s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- fix: auto-decay backoffLevel when rate limit window expires (PR #657 by @brendandebeasi)
- i18n: comprehensive Chinese translation rewrite (PR #658 by @only4copilot)
- docs: update CHANGELOG and OpenAPI spec
2026-03-27 01:27:01 -03:00
Diego Rodrigues de Sa e Souza 2fba6f65f4 Merge pull request #658 from only4copilot/main
Merged! Thank you for the comprehensive Chinese translation update.
2026-03-27 01:19:55 -03:00
Diego Rodrigues de Sa e Souza a754ab4f10 Merge pull request #657 from brendandebeasi/fix/backoff-level-auto-decay
Merged! Great catch on the backoff deadlock.
2026-03-27 01:19:53 -03:00
gmw 86cfc468bd feat: Improve the Chinese translation 2026-03-27 11:04:57 +08:00
Brendan DeBeasi 7df0c1607e fix: auto-decay backoffLevel when rate limit window has passed
High backoffLevel (up to 15) persisted permanently in the DB after a burst of 429s. The account health score dropped to zero (100 - 15*10 = -50), causing the account selector to never pick the account again. Only a successful request could reset backoffLevel via clearAccountError, but the account was never selected — creating a deadlock.

Now, during account selection, any non-terminal connection whose rateLimitedUntil has passed gets its backoffLevel reset to 0 and testStatus restored to active. The DB update is fire-and-forget to avoid blocking the hot path.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-26 20:00:32 -07:00
Diego Rodrigues de Sa e Souza 6acd36e374 Merge pull request #655 from oSoWoSo/dev
Build Electron Desktop App / Validate version (push) Failing after 34s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Merged! Thanks @zen0bit for polishing the Czech translations 🇨🇿
2026-03-26 23:50:54 -03:00
zenobit af51eecbac i18n: Improve some strings 2026-03-27 03:33:53 +01:00
diegosouzapw 3a23dc8b04 release: v3.1.3 — community i18n contributions (#652, #651)
Build Electron Desktop App / Validate version (push) Failing after 43s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- i18n: ~70 missing translation keys for en.json + 12 languages (PR #652 by @zen0bit)
- i18n: Czech documentation updates — CLI-TOOLS, API_REFERENCE, VM_DEPLOYMENT (PR #652)
- feat: translation validation scripts for CI/QA (PR #651 by @zen0bit)
- docs: update CHANGELOG and OpenAPI spec
2026-03-26 21:32:52 -03:00
Diego Rodrigues de Sa e Souza ba13e44720 Merge pull request #651 from oSoWoSo/main-scripts
Merged! 🎉 Thank you @zen0bit for the translation validation tooling.
2026-03-26 21:31:48 -03:00
Diego Rodrigues de Sa e Souza e80420f6db Merge pull request #652 from oSoWoSo/main-i18n-fixes
Merged! 🎉 Thank you @zen0bit for the comprehensive i18n contribution — ~70 missing keys + Czech docs updates.
2026-03-26 21:31:42 -03:00
zenobit 21ddcfc866 feat: make validate_translation.py support any language
- Add --lang / -l argument for target language
- Add TRANSLATION_LANG environment variable support
- Default to cs for backwards compatibility
- Validate language file exists before processing

Usage:
  python validate_translation.py -l de
  TRANSLATION_LANG=fr python validate_translation.py
  python validate_translation.py --lang cs quick

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 01:24:18 +01:00
zenobit 20f82cb22c Apply suggestions from code review
Co-authored-by: zenobit <zenobit@disroot.org>
2026-03-27 01:15:45 +01:00
zenobit 7ef75bab23 Apply suggestions from code review
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-27 01:13:13 +01:00
zenobit 7224e03590 Add check_translations and validate_translations scripts 2026-03-27 01:05:40 +01:00
zenobit cf4f2991a5 i18n: add missing translation keys, Czech docs, and validation scripts
- Added ~70 common keys and auth keys to en.json
- Added cliTools.toolDescriptions for CLI tools
- Updated Czech documentation (CLI-TOOLS.md, API_REFERENCE.md, VM_DEPLOYMENT_GUIDE.md)
- Added check_translations.py and validate-translation.sh scripts
- Refactored auth keys from full sentences to structured keys (auth.waitingForAuthorization)
- Fixed grammatical error (své cloud -> svůj cloud)
- Removed duplicate toolDescriptions from common namespace
- Improved error handling with specific exceptions
- OAuth services use hardcoded English strings as fallback (when i18n unavailable)
- Fixed capitalization: Antigravity, iFlow

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit 9eb3c23494 Improve 2026-03-27 00:37:21 +01:00
zenobit c80d8898cc Fix toolDescriptions and remaining auth keys
Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit bc74dd88e0 Add missing translation keys: common, auth, templates, toolDescriptions
- Added ~70 common keys (id, authorization, proxy, etc.)
- Added auth keys for OAuth waiting messages
- Added templateNames, templateDescriptions, templatePayloads
- Added toolDescriptions for CLI tools
- Added TOOL_ALLOWLIST, TOOL_DENYLIST
- Added pricing error messages

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit da87c461ef Add missing home.updateNow, updating, updateAvailableDesc, updateStarted
Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit bf2e694f2c cs.json: Add missing translations for notes and providers
- cliTools.guides.continue.notes
- cliTools.guides.opencode.notes (2 entries)
- cliTools.guides.kiro.notes
- providers.autoSync (7 new keys)

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
zenobit e5150487c4 Add missing notes to cliTools.guides for continue, opencode, kiro
- continue: 'Continue uses JSON config file.'
- opencode: 'OpenCode requires API key configuration.', 'Set the base URL to your OmniRoute endpoint.'
- kiro: 'Kiro requires Amazon account.'

Co-authored-by: openhands <openhands@all-hands.dev>
2026-03-27 00:37:21 +01:00
diegosouzapw 9ff6353b88 release: v3.1.2 — fix critical tool calling regression (#618)
Build Electron Desktop App / Validate version (push) Failing after 28s
Build Electron Desktop App / Build Electron (macos-arm64) (push) Has been skipped
Build Electron Desktop App / Build Electron (linux) (push) Has been skipped
Build Electron Desktop App / Build Electron (macos-intel) (push) Has been skipped
Build Electron Desktop App / Build Electron (windows) (push) Has been skipped
Build Electron Desktop App / Create Release (push) Has been skipped
Build Electron Desktop App / Publish to npm (push) Has been skipped
Changes:
- fix: disable proxy_ tool prefix for Claude passthrough (Bash → proxy_Bash)
- docs: document Kiro account ban as upstream AWS issue (#649)
- docs: update CHANGELOG and OpenAPI spec

Fixes #618, closes #649, closes #615
2026-03-26 19:49:45 -03:00
diegosouzapw 926fd8abf4 fix: disable proxy_ tool prefix for all Claude-target passthrough (#618)
The openai-to-claude translator was prefixing tool names with 'proxy_'
(e.g. Bash → proxy_Bash) even when routing Claude-format requests to
native Claude/Anthropic providers. Claude rejects unknown tool names,
causing 'No such tool available: proxy_Bash' errors.

Root cause: the _disableToolPrefix condition only disabled the prefix
for non-Claude providers, but it should be disabled for ALL providers
in the Claude passthrough path since tools are already in Claude format.

Fixes #618
2026-03-26 19:44:44 -03:00
diegosouzapw 7e90b8b7be i18n: add clearAllModels translations for all 30 languages 2026-03-26 08:43:52 -03:00
diegosouzapw ed146fcf07 fix: strip proxy_ prefix in non-streaming Claude responses & fix LongCat validation (#605, #592)
- fix(translator): pass toolNameMap to translateNonStreamingResponse so Claude
  OAuth proxy_ prefix is correctly stripped from tool_use block names in
  non-streaming responses (was only stripped in streaming path)
- fix(validation): add LongCat specialty validator that probes /chat/completions
  directly, bypassing the /v1/models endpoint that LongCat does not expose (#592)
2026-03-25 08:11:35 -03:00
140 changed files with 63587 additions and 831 deletions
+1
View File
@@ -112,6 +112,7 @@ app.log
# Backup directories
app.__qa_backup/
.app-build-backup-*/
# Production standalone build (created by scripts/prepublish.mjs)
# Conflicts with Next.js App Router detection in dev (root app/ shadows src/app/)
+114
View File
@@ -4,6 +4,120 @@
---
## [3.1.10] — 2026-03-28
### 🐛 Bug Fixes
- **Fix #706** — Fixed icon fallback rendering caused by Tailwind V4 `font-sans` override by applying `!important` to `.material-symbols-outlined`.
- **Fix #703** — Fixed GitHub Copilot broken streams by enabling `responses` to `openai` format translation for any custom models leveraging `apiFormat: "responses"`.
- **Fix #702** — Replaced flat-rate usage tracking with accurate DB pricing calculations for both streaming and non-streaming responses.
- **Fix #716** — Cleaned up Claude tool-call translation state, correctly parsing streaming arguments and preventing OpenAI `tool_calls` chunks from repeating the `id` field.
## [3.1.9] — 2026-03-28
### ✨ New Features
- **Schema Coercion** — Auto-coerce string-encoded numeric JSON Schema constraints (e.g. `"minimum": "1"`) to proper types, preventing 400 errors from Cursor, Cline, and other clients sending malformed tool schemas.
- **Tool Description Sanitization** — Ensure tool descriptions are always strings; converts `null`, `undefined`, or numeric descriptions to empty strings before sending to providers.
- **Clear All Models Button** — Added i18n translations for the "Clear All Models" provider action across all 30 languages.
- **Codex Auth Export** — Added Codex `auth.json` export and apply-local buttons for seamless CLI integration.
- **Windsurf BYOK Notes** — Added official limitation warnings to the Windsurf CLI tool card documenting BYOK constraints.
### 🐛 Bug Fixes
- **Fix #709** — `system-info.mjs` no longer crashes when the output directory doesn't exist (added `mkdirSync` with recursive flag).
- **Fix #710** — A2A `TaskManager` singleton now uses `globalThis` to prevent state leakage across Next.js API route recompilations in dev mode. E2E test suite updated to handle 401 gracefully.
- **Fix #711** — Added provider-specific `max_tokens` cap enforcement for upstream requests.
- **Fix #605 / #592** — Strip `proxy_` prefix from tool names in non-streaming Claude responses; fixed LongCat validation URL.
- **Call Logs Max Cap** — Upgraded `getMaxCallLogs()` with caching layer, env var support (`CALL_LOGS_MAX`), and DB settings integration.
### 🧪 Tests
- Test suite expanded from 964 → 1027 tests (63 new tests)
- Added `schema-coercion.test.mjs` — 9 tests for numeric field coercion and tool description sanitization
- Added `t40-opencode-cli-tools-integration.test.mjs` — OpenCode/Windsurf CLI integration tests
- Enhanced feature-tests branch with comprehensive coverage tooling
### 📁 New Files
| File | Purpose |
| -------------------------------------------------------- | ----------------------------------------------------------- |
| `open-sse/translator/helpers/schemaCoercion.ts` | Schema coercion and tool description sanitization utilities |
| `tests/unit/schema-coercion.test.mjs` | Unit tests for schema coercion |
| `tests/unit/t40-opencode-cli-tools-integration.test.mjs` | CLI tool integration tests |
| `COVERAGE_PLAN.md` | Test coverage planning document |
## [3.1.8] - 2026-03-27
### 🐛 Bug Fixes & Features
- **Platform Core:** Implemented global state handling for Hidden Models & Combos preventing them from cluttering the catalog or leaking into connected MCP agents (#681).
- **Stability:** Patched streaming crashes related to the native Antigravity provider integration failing due to unhandled undefined state arrays (#684).
- **Localization Sync:** Deployed a fully overhauled `i18n` synchronizer detecting missing nested JSON properties and retro-fitting 30 locales sequentially (#685).## [3.1.7] - 2026-03-27
### 🐛 Bug Fixes
- **Streaming Stability:** Fixed `hasValuableContent` returning `undefined` for empty chunks in SSE streams (#676).
- **Tool Calling:** Fixed an issue in `sseParser.ts` where non-streaming Claude responses with multiple tool calls dropped the `id` of subsequent tool calls due to incorrect index-based deduplication (#671).
---
## [3.1.6] — 2026-03-27
### 🐛 Bug Fixes
- **Claude Native Tool Name Restoration** — Tool names like `TodoWrite` are no longer prefixed with `proxy_` in Claude passthrough responses (both streaming and non-streaming). Includes unit test coverage (PR #663 by @coobabm)
- **Clear All Models Alias Cleanup** — "Clear All Models" button now also removes associated model aliases, preventing ghost models in the UI (PR #664 by @rdself)
---
## [3.1.5] — 2026-03-27
### 🐛 Bug Fixes
- **Backoff Auto-Decay** — Rate-limited accounts now auto-recover when their cooldown window expires, fixing a deadlock where high `backoffLevel` permanently deprioritized accounts (PR #657 by @brendandebeasi)
### 🌍 i18n
- **Chinese translation overhaul** — Comprehensive rewrite of `zh-CN.json` with improved accuracy (PR #658 by @only4copilot)
---
## [3.1.4] — 2026-03-27
### 🐛 Bug Fixes
- **Streaming Override Fix** — Explicit `stream: true` in request body now takes priority over `Accept: application/json` header. Clients sending both will correctly receive SSE streaming responses (#656)
### 🌍 i18n
- **Czech string improvements** — Refined terminology across `cs.json` (PR #655 by @zen0bit)
---
## [3.1.3] — 2026-03-26
### 🌍 i18n & Community
- **~70 missing translation keys** added to `en.json` and 12 languages (PR #652 by @zen0bit)
- **Czech documentation updated** — CLI-TOOLS, API_REFERENCE, VM_DEPLOYMENT guides (PR #652)
- **Translation validation scripts** — `check_translations.py` and `validate_translation.py` for CI/QA (PR #651 by @zen0bit)
---
## [3.1.2] — 2026-03-26
### 🐛 Bug Fixes
- **Critical: Tool Calling Regression** — Fixed `proxy_Bash` errors by disabling the `proxy_` tool name prefix in the Claude passthrough path. Tools like `Bash`, `Read`, `Write` were being renamed to `proxy_Bash`, `proxy_Read`, etc., causing Claude to reject them (#618)
- **Kiro Account Ban Documentation** — Documented as upstream AWS anti-fraud false positive, not an OmniRoute issue (#649)
### 🧪 Tests
- **936 tests, 0 failures**
---
## [3.1.1] — 2026-03-26
### ✨ New Features
+8 -1
View File
@@ -114,6 +114,7 @@ npm run test:fixes # Fix verification tests
# With coverage
npm run test:coverage
npm run coverage:report
# E2E tests (requires Playwright)
npm run test:e2e
@@ -123,7 +124,13 @@ npm run lint
npm run check
```
Current test status: **368+ unit tests** covering:
Coverage notes:
- `npm run test:coverage` measures source coverage for the main unit test suite, excludes `tests/**`, and includes `open-sse/**`
- `npm run coverage:report` prints the detailed file-by-file report from the latest coverage run
- `npm run test:coverage:legacy` preserves the older metric for historical comparison
Current test status: **968+ unit tests** covering:
- Provider translators and format conversion
- Rate limiting, circuit breaker, and resilience
+166
View File
@@ -0,0 +1,166 @@
# Test Coverage Plan
Last updated: 2026-03-28
## Baseline
There are multiple coverage numbers depending on how the report is computed. For planning, only one of them is useful.
| Metric | Scope | Statements / Lines | Branches | Functions | Notes |
| -------------------- | ----------------------------------------------------- | -----------------: | -------: | --------: | --------------------------------------------------- |
| Legacy | Old `npm run test:coverage` | 79.42% | 75.15% | 67.94% | Inflated: counts test files and excludes `open-sse` |
| Diagnostic | Source-only, excluding tests and excluding `open-sse` | 68.16% | 63.55% | 64.06% | Useful only to isolate `src/**` |
| Recommended baseline | Source-only, excluding tests and including `open-sse` | 56.95% | 66.05% | 57.80% | This is the project-wide baseline to improve |
The recommended baseline is the number to optimize against.
## Rules
- Coverage targets apply to source files, not to `tests/**`.
- `open-sse/**` is part of the product and must remain in scope.
- New code should not reduce coverage in touched areas.
- Prefer testing behavior and branch outcomes over implementation details.
- Prefer temp SQLite databases and small fixtures over broad mocks for `src/lib/db/**`.
## Current command set
- `npm run test:coverage`
- Main source coverage gate for the unit test suite
- Generates `text-summary`, `html`, `json-summary`, and `lcov`
- `npm run coverage:report`
- Detailed file-by-file report from the latest run
- `npm run test:coverage:legacy`
- Historical comparison only
## Milestones
| Phase | Target | Focus |
| ------- | ---------------------: | ------------------------------------------------- |
| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage |
| Phase 2 | 65% statements / lines | DB and route foundations |
| Phase 3 | 70% statements / lines | Provider validation and usage analytics |
| Phase 4 | 75% statements / lines | `open-sse` translators and helpers |
| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches |
| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites |
| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet |
Branches and functions should ratchet upward with each phase, but the primary hard target is statements / lines.
## Priority hotspots
These files or areas offer the best return for the next phases:
1. `open-sse/handlers`
- `chatCore.ts` at 7.57%
- Overall directory at 29.07%
2. `open-sse/translator/request`
- Overall directory at 36.39%
- Many translators are still near single-digit coverage
3. `open-sse/translator/response`
- Overall directory at 8.07%
4. `open-sse/executors`
- Overall directory at 36.62%
5. `src/lib/db`
- `models.ts` at 20.66%
- `registeredKeys.ts` at 34.46%
- `modelComboMappings.ts` at 36.25%
- `settings.ts` at 46.40%
- `webhooks.ts` at 33.33%
6. `src/lib/usage`
- `usageHistory.ts` at 21.12%
- `usageStats.ts` at 9.56%
- `costCalculator.ts` at 30.00%
7. `src/lib/providers`
- `validation.ts` at 41.16%
8. Low-risk utility and API files for early gains
- `src/shared/utils/upstreamError.ts`
- `src/shared/utils/apiAuth.ts`
- `src/lib/api/errorResponse.ts`
- `src/app/api/settings/require-login/route.ts`
- `src/app/api/providers/[id]/models/route.ts`
## Execution checklist
### Phase 1: 56.95% -> 60%
- [x] Fix coverage metric so it reflects source code instead of test files
- [x] Keep a legacy coverage script for comparison
- [x] Record the baseline and hotspots in-repo
- [ ] Add focused tests for low-risk utilities:
- `src/shared/utils/upstreamError.ts`
- `src/shared/utils/fetchTimeout.ts`
- `src/lib/api/errorResponse.ts`
- `src/shared/utils/apiAuth.ts`
- `src/lib/display/names.ts`
- [ ] Add route tests for:
- `src/app/api/settings/require-login/route.ts`
- `src/app/api/providers/[id]/models/route.ts`
### Phase 2: 60% -> 65%
- [ ] Add DB-backed tests for:
- `src/lib/db/modelComboMappings.ts`
- `src/lib/db/settings.ts`
- `src/lib/db/registeredKeys.ts`
- [ ] Cover branch behavior in:
- `src/lib/providers/validation.ts`
- `src/app/api/v1/embeddings/route.ts`
- `src/app/api/v1/moderations/route.ts`
### Phase 3: 65% -> 70%
- [ ] Add usage analytics tests for:
- `src/lib/usage/usageHistory.ts`
- `src/lib/usage/usageStats.ts`
- `src/lib/usage/costCalculator.ts`
- [ ] Expand route coverage for proxy management and settings branches
### Phase 4: 70% -> 75%
- [ ] Cover translator helpers and central translation paths:
- `open-sse/translator/index.ts`
- `open-sse/translator/helpers/*`
- `open-sse/translator/request/*`
- `open-sse/translator/response/*`
### Phase 5: 75% -> 80%
- [ ] Add handler-level tests for:
- `open-sse/handlers/chatCore.ts`
- `open-sse/handlers/responsesHandler.js`
- `open-sse/handlers/imageGeneration.js`
- `open-sse/handlers/embeddings.js`
- [ ] Add executor branch coverage for provider-specific auth, retries, and endpoint overrides
### Phase 6: 80% -> 85%
- [ ] Merge more edge-case suites into the main coverage path
- [ ] Increase function coverage for DB modules with weak constructor/helper coverage
- [ ] Close branch gaps in `settings.ts`, `registeredKeys.ts`, `validation.ts`, and translator helpers
### Phase 7: 85% -> 90%
- [ ] Treat the remaining low-coverage files as blockers
- [ ] Add regression tests for every uncovered production bug fixed during the push to 90%
- [ ] Raise the coverage gate in CI only after the local baseline is stable for at least two consecutive runs
## Ratchet policy
Update `npm run test:coverage` thresholds only after the project actually exceeds the next milestone with a comfortable buffer.
Recommended ratchet sequence:
1. 55/60/55
2. 60/62/58
3. 65/64/62
4. 70/66/66
5. 75/70/72
6. 80/75/78
7. 85/80/84
8. 90/85/88
Order is `statements-lines / branches / functions`.
## Known gap
The current coverage command measures the main Node unit suite and includes source reached from it, including `open-sse`. It does not yet merge Vitest coverage into a single unified report. That merge is worth doing later, but it is not a blocker for starting the 60% -> 80% climb.
+8 -1
View File
@@ -1,13 +1,17 @@
FROM node:22-bookworm-slim AS builder
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends libsecret-1-0 \
&& rm -rf /var/lib/apt/lists/*
COPY package*.json ./
COPY scripts/postinstall.mjs ./scripts/postinstall.mjs
COPY scripts/native-binary-compat.mjs ./scripts/native-binary-compat.mjs
RUN if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; else npm install --no-audit --no-fund; fi
COPY . ./
RUN mkdir -p /app/data && npm run build
RUN mkdir -p /app/data && npm run build -- --webpack
FROM node:22-bookworm-slim AS runner-base
WORKDIR /app
@@ -25,6 +29,9 @@ ENV NODE_OPTIONS="--max-old-space-size=256"
# Data directory inside Docker — must match the volume mount in docker-compose.yml
ENV DATA_DIR=/app/data
RUN apt-get update \
&& apt-get install -y --no-install-recommends libsecret-1-0 \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/data
COPY --from=builder /app/public ./public
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2081
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+1962
View File
File diff suppressed because it is too large Load Diff
+2073
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -876,6 +876,35 @@ docker compose --profile base up -d
docker compose --profile cli up -d
```
**Using Docker Compose with Caddy (HTTPS Auto-TLS):**
OmniRoute can be securely exposed using Caddy's automatic SSL provisioning. Ensure your domain's DNS A record points to your server's IP.
```yaml
services:
omniroute:
image: diegosouzapw/omniroute:latest
container_name: omniroute
restart: unless-stopped
volumes:
- omniroute-data:/app/data
environment:
- PORT=20128
- NEXT_PUBLIC_BASE_URL=https://your-domain.com
caddy:
image: caddy:latest
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
command: caddy reverse-proxy --from https://your-domain.com --to http://omniroute:20128
volumes:
omniroute-data:
```
| Image | Tag | Size | Description |
| ------------------------ | -------- | ------ | --------------------- |
| `diegosouzapw/omniroute` | `latest` | ~250MB | Latest stable release |
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2080
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2079
View File
File diff suppressed because it is too large Load Diff
+2074
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -48,6 +48,8 @@ Content-Type: application/json
| `X-OmniRoute-Idempotent` | Odpověď | `true` , pokud je odstraněna duplikace |
| `X-OmniRoute-Progress` | Odpověď | `enabled` pokud je zapnuto sledování průběhu |
> Poznámka Nginx: pokud spoléháte na hlavičky s podtržítkem (například `x_session_id`), povolte `underscores_in_headers on;`.
---
## Vložení
+125 -122
View File
@@ -1,60 +1,79 @@
# Průvodce nastavením nástrojů CLI — OmniRoute
Tato příručka vysvětluje, jak nainstalovat a nakonfigurovat všechny podporované nástroje CLI pro kódování umělé inteligence tak, aby **OmniRoute** fungoval jako jednotný backend, což vám umožní centralizovanou správu klíčů, sledování nákladů, přepínání modelů a protokolování požadavků napříč všemi nástroji.
Tato příručka vysvětluje, jak nainstalovat a nakonfigurovat všechny podporované nástroje CLI pro kódování umělé inteligence
tak, aby **OmniRoute** fungoval jako jednotný backend, což vám umožní centralizovanou správu klíčů,
sledování nákladů, přepínání modelů a protokolování požadavků napříč všemi nástroji.
---
## Jak to funguje
```
Claude / Codex / Gemini CLI / OpenCode / Cline / KiloCode / Continue / Kiro CLI
▼ (all point to OmniRoute)
http://YOUR_SERVER:20128/v1
▼ (OmniRoute routes to the right provider)
Anthropic / OpenAI / Gemini / DeepSeek / Groq / Mistral / ...
Claude / Codex / OpenCode / Cline / KiloCode / Continue / Kiro / Cursor / Copilot
▼ (všechny ukazují na OmniRoute)
http://VASE_SERVER:20128/v1
▼ (OmniRoute směruje ke správnému poskytovateli)
Anthropic / OpenAI / Gemini / DeepSeek / Groq / Mistral / ...
```
**Výhody:**
- Jeden klíč API pro správu všech nástrojů
- Sledování nákladů napříč všemi rozhraními příkazového řádku v dashboardu
- Jeden API klíč pro správu všech nástrojů
- Sledování nákladů napříč všemi CLI v dashboardu
- Přepínání modelů bez nutnosti překonfigurování každého nástroje
- Funguje lokálně i na vzdálených serverech (VPS)
---
## Podporované nástroje
## Podporované nástroje (Zdroj pravdy v dashboardu)
| Nástroj | Příkaz | Typ | Instalace |
| ---------------- | ------------------- | --------------- | -------------- |
| **Claude Code** | `claude` | CLI | npm |
| **OpenAI Codex** | `codex` | CLI | npm |
| **Gemini CLI** | `gemini` | CLI | npm |
| **OpenCode** | `opencode` | CLI | npm |
| **Cline** | `cline` | CLI + VS Code | npm |
| **KiloCode** | `kilocode` / `kilo` | CLI + VS Code | npm |
| **Continue** | průvodce | VS Code ext | VS kód |
| **Kiro CLI** | `kiro-cli` | CLI | curl instalace |
| **Kurzor** | `cursor` | Aplikace pro PC | Download |
| **Droid** | webový | Built-in agent | OmniRoute |
| **OpenClaw** | webový | Built-in agent | OmniRoute |
Karty dashboardu v `/dashboard/cli-tools` jsou generovány z `src/shared/constants/cliTools.ts`.
Aktuální seznam (v3.0.0-rc.16):
| Nástroj | ID | Příkaz | Režim nastavení | Metoda instalace |
| ------------------ | ------------- | ------------ | --------------- | ---------------- |
| **Claude Code** | `claude` | `claude` | env | npm |
| **OpenAI Codex** | `codex` | `codex` | custom | npm |
| **Factory Droid** | `droid` | `droid` | custom | bundled/CLI |
| **OpenClaw** | `openclaw` | `openclaw` | custom | bundled/CLI |
| **Cursor** | `cursor` | aplikace | guide | desktop app |
| **Cline** | `cline` | `cline` | custom | npm |
| **Kilo Code** | `kilo` | `kilocode` | custom | npm |
| **Continue** | `continue` | rozšíření | guide | VS Code |
| **Antigravity** | `antigravity` | interní | mitm | OmniRoute |
| **GitHub Copilot** | `copilot` | rozšíření | custom | VS Code |
| **OpenCode** | `opencode` | `opencode` | guide | npm |
| **Kiro AI** | `kiro` | aplikace/CLI | mitm | desktop/CLI |
### Synchronizace otisků CLI (Agenti + Nastavení)
`/dashboard/agents` a `Nastavení > CLI Otisk` používají `src/shared/constants/cliCompatProviders.ts`.
To udržuje ID poskytovatelů v souladu s kartami CLI a staršími ID.
| CLI ID | ID poskytovatele otisku |
| ---------------------------------------------------------------------------------------------------- | ----------------------- |
| `kilo` | `kilocode` |
| `copilot` | `github` |
| `claude` / `codex` / `antigravity` / `kiro` / `cursor` / `cline` / `opencode` / `droid` / `openclaw` | stejné ID |
Starší ID jsou stále přijímána pro kompatibilitu: `copilot`, `kimi-coding`, `qwen`.
---
## Krok 1 Získejte klíč OmniRoute API
## Krok 1 Získejte OmniRoute API klíč
1. Otevřete dashboard OmniRoute**Správce API** ( `/dashboard/api-manager` )
2. Klikněte na **Vytvořit klíč API**
3. Pojmenujte to (např. `cli-tools` ) a vyberte všechna oprávnění.
4. Zkopírujte klíč budete ho potřebovat pro každé níže uvedené rozhraní příkazového řádku.
1. Otevřete OmniRoute dashboard**Správce API** (`/dashboard/api-manager`)
2. Klikněte na **Vytvořit API klíč**
3. Dejte mu název (např. `cli-tools`) a vyberte všechna oprávnění
4. Zkopírujte klíč budete ho potřebovat pro každý CLI níže
> Váš klíč vypadá takto: `sk-xxxxxxxxxxxxxxxx-xxxxxxxxx`
---
## Krok 2 Instalace nástrojů CLI
## Krok 2 — Nainstalujte nástroje CLI
Všechny nástroje založené na npm vyžadují Node.js 18+:
@@ -65,9 +84,6 @@ npm install -g @anthropic-ai/claude-code
# OpenAI Codex
npm install -g @openai/codex
# Gemini CLI (Google)
npm install -g @google/gemini-cli
# OpenCode
npm install -g opencode-ai
@@ -75,47 +91,47 @@ npm install -g opencode-ai
npm install -g cline
# KiloCode
npm install -g kilecode
npm install -g kilocode
# Kiro CLI (Amazon — requires curl + unzip)
apt-get install -y unzip # on Debian/Ubuntu
# Kiro CLI (Amazon — vyžaduje curl + unzip)
apt-get install -y unzip # na Debian/Ubuntu
curl -fsSL https://cli.kiro.dev/install | bash
export PATH="$HOME/.local/bin:$PATH" # add to ~/.bashrc
export PATH="$HOME/.local/bin:$PATH" # přidat do ~/.bashrc
```
**Ověřit:**
**Ověření:**
```bash
claude --version # 2.x.x
codex --version # 0.x.x
gemini --version # 0.x.x
opencode --version # x.x.x
cline --version # 2.x.x
kilocode --version # x.x.x (or: kilo --version)
kilocode --version # x.x.x (nebo: kilo --version)
kiro-cli --version # 1.x.x
```
---
## Krok 3 Nastave globálních proměnných prostředí
## Krok 3 Nastavte globální proměnné prostředí
Přidejte do `~/.bashrc` (nebo `~/.zshrc` ) a poté spusťte `source ~/.bashrc` :
Přidejte do `~/.bashrc` (nebo `~/.zshrc`), pak spusťte `source ~/.bashrc`:
```bash
# OmniRoute Universal Endpoint
# OmniRoute Univerzální koncový bod
export OPENAI_BASE_URL="http://localhost:20128/v1"
export OPENAI_API_KEY="sk-your-omniroute-key"
export OPENAI_API_KEY="sk-vase-omniroute-klic"
export ANTHROPIC_BASE_URL="http://localhost:20128/v1"
export ANTHROPIC_API_KEY="sk-your-omniroute-key"
export ANTHROPIC_API_KEY="sk-vase-omniroute-klic"
export GEMINI_BASE_URL="http://localhost:20128/v1"
export GEMINI_API_KEY="sk-your-omniroute-key"
export GEMINI_API_KEY="sk-vase-omniroute-klic"
```
> Pro **vzdálený server** nahraďte `localhost:20128` IP adresou nebo doménou serveru, např. `http://192.168.0.15:20128` .
> Pro **vzdálený server** nahraďte `localhost:20128` IP adresou nebo doménou serveru,
> např. `http://192.168.0.15:20128`.
---
## Krok 4 Konfigurace jednotlivých nástrojů
## Krok 4 — Nakonfigurujte každý nástroj
### Claude Code
@@ -123,11 +139,11 @@ export GEMINI_API_KEY="sk-your-omniroute-key"
# Via CLI:
claude config set --global api-base-url http://localhost:20128/v1
# Or create ~/.claude/settings.json:
# Nebo vytvořte ~/.claude/settings.json:
mkdir -p ~/.claude && cat > ~/.claude/settings.json << EOF
{
"apiBaseUrl": "http://localhost:20128/v1",
"apiKey": "sk-your-omniroute-key"
"apiKey": "sk-vase-omniroute-klic"
}
EOF
```
@@ -141,7 +157,7 @@ EOF
```bash
mkdir -p ~/.codex && cat > ~/.codex/config.yaml << EOF
model: auto
apiKey: sk-your-omniroute-key
apiKey: sk-vase-omniroute-klic
apiBaseUrl: http://localhost:20128/v1
EOF
```
@@ -150,28 +166,13 @@ EOF
---
### Gemini CLI
```bash
mkdir -p ~/.gemini && cat > ~/.gemini/settings.json << EOF
{
"apiKey": "sk-your-omniroute-key",
"baseUrl": "http://localhost:20128/v1"
}
EOF
```
**Test:** `gemini "hello"`
---
### OpenCode
```bash
mkdir -p ~/.config/opencode && cat > ~/.config/opencode/config.toml << EOF
[provider.openai]
base_url = "http://localhost:20128/v1"
api_key = "sk-your-omniroute-key"
api_key = "sk-vase-omniroute-klic"
EOF
```
@@ -179,7 +180,7 @@ EOF
---
### Cline (CLI nebo VS kód)
### Cline (CLI nebo VS Code)
**Režim CLI:**
@@ -188,41 +189,42 @@ mkdir -p ~/.cline/data && cat > ~/.cline/data/globalState.json << EOF
{
"apiProvider": "openai",
"openAiBaseUrl": "http://localhost:20128/v1",
"openAiApiKey": "sk-your-omniroute-key"
"openAiApiKey": "sk-vase-omniroute-klic"
}
EOF
```
**Režim VS Code:** Nastavení rozšíření Cline → Poskytovatel API: `OpenAI Compatible` → Základní URL: `http://localhost:20128/v1`
**Režim VS Code:**
Nastavení rozšíření Cline → API Provider: `OpenAI Compatible` → Base URL: `http://localhost:20128/v1`
Nebo použijte dashboard OmniRoute**CLI Tools → Cline → Apply Config** .
Nebo použijte OmniRoute dashboard**CLI Nástroje → Cline → Použít konfiguraci**.
---
### KiloCode (CLI nebo VS kód)
### KiloCode (CLI nebo VS Code)
**Režim CLI:**
```bash
kilocode --api-base http://localhost:20128/v1 --api-key sk-your-omniroute-key
kilocode --api-base http://localhost:20128/v1 --api-key sk-vase-omniroute-klic
```
**Nastavení VS kódu:**
**Nastavení VS Code:**
```json
{
"kilo-code.openAiBaseUrl": "http://localhost:20128/v1",
"kilo-code.apiKey": "sk-your-omniroute-key"
"kilo-code.apiKey": "sk-vase-omniroute-klic"
}
```
Nebo použijte dashboard OmniRoute**CLI Tools → KiloCode → Apply Config** .
Nebo použijte OmniRoute dashboard**CLI Nástroje → KiloCode → Použít konfiguraci**.
---
### Continue (rozšíření kódu VS)
### Continue (Rozšíření VS Code)
Upravit `~/.continue/config.yaml` :
Upravte `~/.continue/config.yaml`:
```yaml
models:
@@ -230,7 +232,7 @@ models:
provider: openai
model: auto
apiBase: http://localhost:20128/v1
apiKey: sk-your-omniroute-key
apiKey: sk-vase-omniroute-klic
default: true
```
@@ -241,94 +243,95 @@ Po úpravě restartujte VS Code.
### Kiro CLI (Amazon)
```bash
# Login to your AWS/Kiro account:
# Přihlaste se ke svému AWS/Kiro účtu:
kiro-cli login
# The CLI uses its own auth — OmniRoute is not needed as backend for Kiro CLI itself.
# Use kiro-cli alongside OmniRoute for other tools.
# CLI používá vlastní autentifikaci — OmniRoute není potřeba jako backend pro samotný Kiro CLI.
# Používejte kiro-cli společně s OmniRoute pro ostatní nástroje.
kiro-cli status
```
---
### Kurzor (aplikace pro stolní počítače)
### Cursor (Desktop aplikace)
> **Poznámka:** Cursor směruje požadavky přes svůj cloud. Pro integraci OmniRoute povolte **cloudový koncový bod** v nastavení OmniRoute a použijte URL adresu vaší veřejné domény.
> **Poznámka:** Cursor směruje požadavky přes svůj cloud. Pro integraci s OmniRoute,
> povolte **Cloud Endpoint** v nastavení OmniRoute a použijte vaši veřejnou doménu.
Přes GUI: **Nastavení → Modely Klíč OpenAI API**
Via GUI: **Settings → Models → OpenAI API Key**
- Základní URL: `https://your-domain.com/v1`
- Klíč API: váš klíč OmniRoute
- Base URL: `https://vase-domena.com/v1`
- API Key: váš OmniRoute klíč
---
## Automatická konfigurace řídicího panelu
## Automatická konfigurace v dashboardu
Ovládací panel OmniRoute automatizuje konfiguraci většiny nástrojů:
OmniRoute dashboard automatizuje konfiguraci většiny nástrojů:
1. Přejděte na `http://localhost:20128/dashboard/cli-tools`
2. Rozbalit libovolnou kartu nástroje
3. Vyberte klíč API z rozbalovací nabídky
4. Klikněte **na Použít konfiguraci** (pokud je nástroj detekován jako nainstalovaný)
5. Nebo zkopírujte vygenerovaný konfigurační úryvek ručně
1. Jděte na `http://localhost:20128/dashboard/cli-tools`
2. Rozbalte libovolnou kartu nástroje
3. Vyberte svůj API klíč z rozbalovacího seznamu
4. Klikněte na **Použít konfiguraci** (pokud je nástroj detekován jako nainstalovaný)
5. Nebo ručně zkopírujte vygenerovaný konfigurační snippet
---
## Vestavění agenti: Droid a OpenClaw
## Vestavěný agenti: Droid & OpenClaw
**Droid** a **OpenClaw** jsou agenti umělé inteligence zabudovaní přímo do OmniRoute není nutná žádná instalace. Běží jako interní trasy a automaticky používají modelové směrování OmniRoute.
**Droid** a **OpenClaw** jsou AI agenti vestavění přímo do OmniRoute není potřeba žádná instalace.
Běží jako interní trasy a automaticky používají směrování modelů OmniRoute.
- Přístup: `http://localhost:20128/dashboard/agents`
- Konfigurace: stejné kombinace a poskytovatelé jako u všech ostatních nástrojů
- Není vyžadována instalace klíče API ani příkazového řádku
- Konfigurace: stejné kombinace a poskytovatelé jako všechny ostatní nástroje
- Není potřeba API klíč ani instalace CLI
---
## Dostupné koncové body API
## Dostupné API koncové body
| Koncový bod | Popis | Použití pro |
| -------------------------- | --------------------------------------- | ------------------------------------- |
| `/v1/chat/completions` | Standardní chat (všichni poskytovatelé) | Všechny moderní nástroje |
| `/v1/responses` | API pro odpovědi (formát OpenAI) | Kodex, agentické pracovní postupy |
| `/v1/completions` | Doplňování starších textů | Starší nástroje používající `prompt:` |
| `/v1/embeddings` | Vkládání textu | RAG, vyhledávání |
| `/v1/images/generations` | Generování obrázků | DALL-E, Flux atd. |
| `/v1/audio/speech` | Převod textu na řeč | ElevenLabs, OpenAI TTS |
| `/v1/audio/transcriptions` | Převod řeči na text | Deepgram, AssemblyAI |
| `/v1/responses` | Responses API (formát OpenAI) | Codex, agentní workflowy |
| `/v1/completions` | Legacy textové dokončení | Starší nástroje používající `prompt:` |
| `/v1/embeddings` | Textové vložení | RAG, vyhledávání |
| `/v1/images/generations` | Generování obrázků | DALL-E, Flux, atd. |
| `/v1/audio/speech` | Text-to-speech | ElevenLabs, OpenAI TTS |
| `/v1/audio/transcriptions` | Speech-to-text | Deepgram, AssemblyAI |
---
## Odstraňování problémů
## Řešení problémů
| Chyba | Příčina | Opravit |
| ---------------------------------- | ---------------------------------- | -------------------------------------------------------- |
| `Connection refused` | OmniRoute neběží | `pm2 start omniroute` |
| `401 Unauthorized` | Chybný klíč API | Zkontrolovat `/dashboard/api-manager` |
| `No combo configured` | Žádná aktivní routingová kombinace | Nastave v `/dashboard/combos` |
| `invalid model` | Model není v katalogu | Použijte `auto` nebo zkontrolujte `/dashboard/providers` |
| CLI zobrazuje „není nainstalováno“ | Binární soubor není v cestě PATH | Zkontrolujte, `which <command>` |
| `kiro-cli: not found` | Není v PATH | `export PATH="$HOME/.local/bin:$PATH"` |
| Chyba | Příčina | Oprava |
| ----------------------------- | ----------------------- | -------------------------------------------------------- |
| `Connection refused` | OmniRoute neběží | `pm2 start omniroute` |
| `401 Unauthorized` | Špatný API klíč | Zkontrolujte v `/dashboard/api-manager` |
| `No combo configured` | Žádná aktivní kombinace | Nastavte v `/dashboard/combos` |
| `invalid model` | Model není v katalogu | Použijte `auto` nebo zkontrolujte `/dashboard/providers` |
| CLI zobrazuje "not installed" | Binárka není v PATH | Zkontrolujte `which <příkaz>` |
| `kiro-cli: not found` | Není v PATH | `export PATH="$HOME/.local/bin:$PATH"` |
---
## Skript pro rychlé nastavení (jeden příkaz)
## Rychlý skript pro nastavení (jeden příkaz)
```bash
# Install all CLIs and configure for OmniRoute (replace with your key and server URL)
# Nainstalujte všechny CLI a nakonfigurujte pro OmniRoute (nahraďte svým klíčem a URL serveru)
OMNIROUTE_URL="http://localhost:20128/v1"
OMNIROUTE_KEY="sk-your-omniroute-key"
OMNIROUTE_KEY="sk-vase-omniroute-klic"
npm install -g @anthropic-ai/claude-code @openai/codex @google/gemini-cli opencode-ai cline kilecode
npm install -g @anthropic-ai/claude-code @openai/codex opencode-ai cline kilocode
# Kiro CLI
apt-get install -y unzip 2>/dev/null; curl -fsSL https://cli.kiro.dev/install | bash
# Write configs
mkdir -p ~/.claude ~/.codex ~/.gemini ~/.config/opencode ~/.continue
# Zápis konfigurací
mkdir -p ~/.claude ~/.codex ~/.config/opencode ~/.continue
cat > ~/.claude/settings.json <<< "{\"apiBaseUrl\":\"$OMNIROUTE_URL\",\"apiKey\":\"$OMNIROUTE_KEY\"}"
cat > ~/.codex/config.yaml <<< "model: auto\napiKey: $OMNIROUTE_KEY\napiBaseUrl: $OMNIROUTE_URL"
cat > ~/.gemini/settings.json <<< "{\"apiKey\":\"$OMNIROUTE_KEY\",\"baseUrl\":\"$OMNIROUTE_URL\"}"
cat >> ~/.bashrc << EOF
export OPENAI_BASE_URL="$OMNIROUTE_URL"
export OPENAI_API_KEY="$OMNIROUTE_KEY"
@@ -337,5 +340,5 @@ export ANTHROPIC_API_KEY="$OMNIROUTE_KEY"
EOF
source ~/.bashrc
echo "✅ All CLIs installed and configured for OmniRoute"
echo "✅ Všechny CLI nainstalovány a nakonfigurovány pro OmniRoute"
```
-1
View File
@@ -1590,7 +1590,6 @@ opencode
### 🔐 OAuth na vzdáleném serveru
<a name="oauth-on-a-remote-server"></a>
<a name="oauth-em-servidor-remoto"></a>
> **⚠️ Důležité pro uživatele, kteří provozují OmniRoute na VPS, Dockeru nebo jakémkoli vzdáleném serveru**
+56 -54
View File
@@ -1,21 +1,23 @@
# OmniRoute — Guia de Deploy em VM com Cloudflare
# Průvodce nasazením OmniRoute na VM s Cloudflare
Kompletní instalace a konfigurace OmniRoute u VM (VPS) s gerenciou přes Cloudflare.
🌐 **Jazyky:** 🇺🇸 [English](VM_DEPLOYMENT_GUIDE.md) | 🇧🇷 [Português (Brasil)](i18n/pt-BR/VM_DEPLOYMENT_GUIDE.md) | 🇪🇸 [Español](i18n/es/VM_DEPLOYMENT_GUIDE.md) | 🇫🇷 [Français](i18n/fr/VM_DEPLOYMENT_GUIDE.md) | 🇮🇹 [Italiano](i18n/it/VM_DEPLOYMENT_GUIDE.md) | 🇷🇺 [Русский](i18n/ru/VM_DEPLOYMENT_GUIDE.md) | 🇨🇳 [中文 (简体)](i18n/zh-CN/VM_DEPLOYMENT_GUIDE.md) | 🇩🇪 [Deutsch](i18n/de/VM_DEPLOYMENT_GUIDE.md) | 🇮🇳 [हिन्दी](i18n/in/VM_DEPLOYMENT_GUIDE.md) | 🇹🇭 [ไทย](i18n/th/VM_DEPLOYMENT_GUIDE.md) | 🇺🇦 [Українська](i18n/uk-UA/VM_DEPLOYMENT_GUIDE.md) | 🇸🇦 [العربية](i18n/ar/VM_DEPLOYMENT_GUIDE.md) | 🇯🇵 [日本語](i18n/ja/VM_DEPLOYMENT_GUIDE.md) | 🇻🇳 [Tiếng Việt](i18n/vi/VM_DEPLOYMENT_GUIDE.md) | 🇧🇬 [Български](i18n/bg/VM_DEPLOYMENT_GUIDE.md) | 🇩🇰 [Dansk](i18n/da/VM_DEPLOYMENT_GUIDE.md) | 🇫🇮 [Suomi](i18n/fi/VM_DEPLOYMENT_GUIDE.md) | 🇮🇱 [עברית](i18n/he/VM_DEPLOYMENT_GUIDE.md) | 🇭🇺 [Magyar](i18n/hu/VM_DEPLOYMENT_GUIDE.md) | 🇮🇩 [Bahasa Indonesia](i18n/id/VM_DEPLOYMENT_GUIDE.md) | 🇰🇷 [한국어](i18n/ko/VM_DEPLOYMENT_GUIDE.md) | 🇲🇾 [Bahasa Melayu](i18n/ms/VM_DEPLOYMENT_GUIDE.md) | 🇳🇱 [Nederlands](i18n/nl/VM_DEPLOYMENT_GUIDE.md) | 🇳🇴 [Norsk](i18n/no/VM_DEPLOYMENT_GUIDE.md) | 🇵🇹 [Português (Portugal)](i18n/pt/VM_DEPLOYMENT_GUIDE.md) | 🇷🇴 [Română](i18n/ro/VM_DEPLOYMENT_GUIDE.md) | 🇵🇱 [Polski](i18n/pl/VM_DEPLOYMENT_GUIDE.md) | 🇸🇰 [Slovenčina](i18n/sk/VM_DEPLOYMENT_GUIDE.md) | 🇸🇪 [Svenska](i18n/sv/VM_DEPLOYMENT_GUIDE.md) | 🇵🇭 [Filipino](i18n/phi/VM_DEPLOYMENT_GUIDE.md) | 🇨🇿 [Čeština](i18n/cs/VM_DEPLOYMENT_GUIDE.md)
Kompletní průvodce instalací a konfigurací OmniRoute na virtuálním stroji (VPS) se správou domény prostřednictvím Cloudflare.
---
## Předpoklady
Položka | Mínimo | Doporučeno
--- | --- | ---
**Procesor** | 1 virtuální procesor | 2 vCPU
**BERAN** | 1 GB | 2 GB
**Disko** | 10GB SSD | 25GB SSD
**TAK** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS
**Domínio** | Registrován v Cloudflare | —
**Přístavní dělník** | Docker Engine 24+ | Docker 27+
| Položka | Minimální | Doporučeno |
| ------------ | --------------------------- | ---------------- |
| **Procesor** | 1 virtuální procesor | 2 vCPU |
| **RAM** | 1 GB | 2 GB |
| **Disk** | 10GB SSD | 25GB SSD |
| **CPU** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS |
| **Doména** | Zaregistrována v Cloudflare | — |
| **Docker** | Docker Engine 24+ | Docker 27+ |
**Testados poskytovatelů** : Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail.
**Testovaní poskytovatelé**: Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail.
---
@@ -23,12 +25,12 @@ Položka | Mínimo | Doporučeno
### 1.1 Vytvořit ihned
Žádný preferovaný poskytovatel seu VPS:
Žádný preferovaný poskytovatel VPS:
- Vyberte si Ubuntu 24.04 LTS
- Výběr nebo plano minimo (1 vCPU / 1 GB RAM)
- Definujte sílu pro root nebo konfiguraci klíče SSH
- Anote o **IP público** (např.: `203.0.113.10` )
- Vyberte minimální plán (1 vCPU / 1 GB RAM)
- Nastavte silné heslo pro root nebo konfiguraci SSH klíče
- Poznamenejte si **veřejnou IP** (např.: `203.0.113.10`)
### 1.2 Připojení přes SSH
@@ -45,10 +47,10 @@ apt update && apt upgrade -y
### 1.4 Instalace Dockeru
```bash
# Instalar dependências
# Nainstalovat závislosti
apt install -y ca-certificates curl gnupg
# Adicionar repositório oficial do Docker
# Přidat oficiální Docker repository
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
@@ -74,29 +76,29 @@ ufw allow 443/tcp # HTTPS
ufw enable
```
> **Dica** : Maximální zabezpečení, omezení jako porty 80 a 443 přístupů pro IP Cloudflare. Veja a seção [Segurança Avançada](#seguran%C3%A7a-avan%C3%A7ada) .
> **Tip**: Pro maximální zabezpečení omezte porty 80 a 443 pouze na IP Cloudflare. Viz sekce [Pokročilé zabezpečení](#pokrocilé-zabezpečení).
---
## 2. Instalace OmniRoute
### 2.1 Criar diretório de configuração
### 2.1 Vytvořit konfigurační adresář
```bash
mkdir -p /opt/omniroute
```
### 2.2 Criar arquivo de variáveis de ambiente
### 2.2 Vytvořit soubor s proměnnými prostředí
```bash
cat > /opt/omniroute/.env << 'EOF'
# === Segurança ===
JWT_SECRET=ALTERE-PARA-CHAVE-SECRETA-UNICA-64-CHARS
INITIAL_PASSWORD=SuaSenhaSegura123!
API_KEY_SECRET=ALTERE-PARA-OUTRA-CHAVE-SECRETA
STORAGE_ENCRYPTION_KEY=ALTERE-PARA-TERCEIRA-CHAVE-SECRETA
# === Bezpečnost ===
JWT_SECRET=CHANGE-TO-A-UNIQUE-64-CHAR-SECRET-KEY
INITIAL_PASSWORD=YourSecurePassword123!
API_KEY_SECRET=REPLACE-WITH-ANOTHER-SECRET-KEY
STORAGE_ENCRYPTION_KEY=REPLACE-WITH-THIRD-SECRET-KEY
STORAGE_ENCRYPTION_KEY_VERSION=v1
MACHINE_ID_SALT=ALTERE-PARA-SALT-UNICO
MACHINE_ID_SALT=CHANGE-TO-A-UNIQUE-SALT
# === App ===
PORT=20128
@@ -108,9 +110,9 @@ ENABLE_REQUEST_LOGS=true
AUTH_COOKIE_SECURE=false
REQUIRE_API_KEY=false
# === Domain (altere para seu domínio) ===
BASE_URL=https://llms.seudominio.com
NEXT_PUBLIC_BASE_URL=https://llms.seudominio.com
# === Doména (změňte na vaši doménu) ===
BASE_URL=https://llms.vasedomena.com
NEXT_PUBLIC_BASE_URL=https://llms.vasedomena.com
# === Cloud Sync (opcional) ===
# CLOUD_URL=https://cloud.omniroute.online
@@ -118,7 +120,7 @@ NEXT_PUBLIC_BASE_URL=https://llms.seudominio.com
EOF
```
> ⚠️ **DŮLEŽITÉ** : Gere chaves secretas únicas! Použijte `openssl rand -hex 32` para cada chave.
> ⚠️ **DŮLEŽITÉ**: Vygenerujte jedinečné tajné klíče! Použijte `openssl rand -hex 32` pro každý klíč.
### 2.3 Spuštění kontejneru
@@ -147,19 +149,19 @@ Vývojový příklad: `[DB] SQLite database ready` a `listening on port 20128` .
## 3. Konfigurace nginx (reverzní proxy)
### 3.1 Gerar Certificado SSL (Cloudflare Origin)
### 3.1 Vygenerovat SSL certifikát (Cloudflare Origin)
Cloudflare nic neřeší:
1. Používá **SSL/TLS → Origin Server**
2. **Certifikát Clique Create**
3. Deixe os padrões (15 ano, *.seudominio.com)
2. Klikněte na **Vytvořit certifikát**
3. Ponechte výchozí nastavení (15 let, \*.vasedomena.com)
4. Zkopírujte nebo zkopírujte **certifikát původu** a **soukromý klíč**
```bash
mkdir -p /etc/nginx/ssl
# Colar o certificado
# Vložit certifikát
nano /etc/nginx/ssl/origin.crt
# Colar a chave privada
@@ -188,7 +190,7 @@ server {
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name llms.seudominio.com; # Altere para seu domínio
server_name llms.vasedomena.com; # Změňte na vaši doménu
ssl_certificate /etc/nginx/ssl/origin.crt;
ssl_certificate_key /etc/nginx/ssl/origin.key;
@@ -220,7 +222,7 @@ server {
server {
listen 80;
listen [::]:80;
server_name llms.seudominio.com;
server_name llms.vasedomena.com;
return 301 https://$server_name$request_uri;
}
NGINX
@@ -245,11 +247,11 @@ nginx -t && systemctl reload nginx
### 4.1 Další DNS registr
No painel da Cloudflare → DNS:
V dashboardu Cloudflare → DNS:
Typ | Jméno | Obsah | Proxy
--- | --- | --- | ---
A | `llms` | `203.0.113.10` (IP adresa virtuálního počítače) | ✅ Proxy
| Typ | Jméno | Obsah | Proxy |
| --- | ------ | ----------------------------------------------- | -------- |
| A | `llms` | `203.0.113.10` (IP adresa virtuálního počítače) | ✅ Proxy |
### 4.2 Konfigurace SSL
@@ -257,7 +259,7 @@ Em **SSL/TLS → Přehled** :
- Režim: **Plný (Přísný)**
Em **SSL/TLS → Edge certifikáty** :
V **SSL/TLS → Edge Certificates**:
- Vždy používat HTTPS: ✅ Zapnuto
- Minimální verze TLS: TLS 1.2
@@ -266,7 +268,7 @@ Em **SSL/TLS → Edge certifikáty** :
### 4.3 Testar
```bash
curl -sI https://llms.seudominio.com/health
curl -sI https://llms.vasedomena.com/health
# Deve retornar HTTP/2 200
```
@@ -289,14 +291,14 @@ docker run -d --name omniroute --restart unless-stopped \
### Verzovní protokoly
```bash
docker logs -f omniroute # Stream em tempo real
docker logs -f omniroute # Živý stream
docker logs omniroute --tail 50 # Últimas 50 linhas
```
### Ruční zálohování banky
```bash
# Copiar dados do volume para o host
# Kopírovat data z volume do hostitele
docker cp omniroute:/app/data ./backup-$(date +%F)
# Ou comprimir todo o volume
@@ -321,7 +323,7 @@ docker start omniroute
```bash
cat > /etc/nginx/cloudflare-ips.conf << 'CF'
# Cloudflare IPv4 ranges — atualizar periodicamente
# Cloudflare IPv4 ranges — aktualizovat pravidelně
# https://www.cloudflare.com/ips-v4/
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
@@ -342,7 +344,7 @@ real_ip_header CF-Connecting-IP;
CF
```
Přidat `nginx.conf` dentro do bloco `http {}` :
Přidat do `nginx.conf` do bloku `http {}`:
```nginx
include /etc/nginx/cloudflare-ips.conf;
@@ -362,7 +364,7 @@ fail2ban-client status sshd
### Bloquear accesso direto na port do Docker
```bash
# Impedir acesso externo direto à porta 20128
# Zamezit přímému externímu přístupu k portu 20128
iptables -I DOCKER-USER -p tcp --dport 20128 -j DROP
iptables -I DOCKER-USER -i lo -p tcp --dport 20128 -j ACCEPT
@@ -389,11 +391,11 @@ Dokumenty jsou kompletní pro [omnirouteCloud/README.md](../omnirouteCloud/READM
---
## Resumo de Portas
## Přehled portů
Porta | Služba | Přístup
--- | --- | ---
22 | SSH | Veřejné (s fail2ban)
80 | nginx HTTP | Přesměrování → HTTPS
443 | nginx HTTPS | Prostřednictvím proxy serveru Cloudflare
20128 | OmniRoute | Někdy na localhostu (přes nginx)
| Port | Služba | Přístup |
| ----- | ----------- | ---------------------------------------- |
| 22 | SSH | Veřejné (s fail2ban) |
| 80 | nginx HTTP | Přesměrování → HTTPS |
| 443 | nginx HTTPS | Prostřednictvím proxy serveru Cloudflare |
| 20128 | OmniRoute | Někdy na localhostu (přes nginx) |
-164
View File
@@ -1,164 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>V&iacute;cejazy&ccaron;n&aacute; dokumentace</title>
<style>
/* From extension vscode.github */
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vscode-dark img[src$=\#gh-light-mode-only],
.vscode-light img[src$=\#gh-dark-mode-only],
.vscode-high-contrast:not(.vscode-high-contrast-light) img[src$=\#gh-light-mode-only],
.vscode-high-contrast-light img[src$=\#gh-dark-mode-only] {
display: none;
}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
font-size: 14px;
line-height: 1.6;
}
</style>
<style>
.task-list-item {
list-style-type: none;
}
.task-list-item-checkbox {
margin-left: -20px;
vertical-align: middle;
pointer-events: none;
}
</style>
<style>
:root {
--color-note: #0969da;
--color-tip: #1a7f37;
--color-warning: #9a6700;
--color-severe: #bc4c00;
--color-caution: #d1242f;
--color-important: #8250df;
}
</style>
<style>
@media (prefers-color-scheme: dark) {
:root {
--color-note: #2f81f7;
--color-tip: #3fb950;
--color-warning: #d29922;
--color-severe: #db6d28;
--color-caution: #f85149;
--color-important: #a371f7;
}
}
</style>
<style>
.markdown-alert {
padding: 0.5rem 1rem;
margin-bottom: 16px;
color: inherit;
border-left: .25em solid #888;
}
.markdown-alert>:first-child {
margin-top: 0
}
.markdown-alert>:last-child {
margin-bottom: 0
}
.markdown-alert .markdown-alert-title {
display: flex;
font-weight: 500;
align-items: center;
line-height: 1
}
.markdown-alert .markdown-alert-title .octicon {
margin-right: 0.5rem;
display: inline-block;
overflow: visible !important;
vertical-align: text-bottom;
fill: currentColor;
}
.markdown-alert.markdown-alert-note {
border-left-color: var(--color-note);
}
.markdown-alert.markdown-alert-note .markdown-alert-title {
color: var(--color-note);
}
.markdown-alert.markdown-alert-important {
border-left-color: var(--color-important);
}
.markdown-alert.markdown-alert-important .markdown-alert-title {
color: var(--color-important);
}
.markdown-alert.markdown-alert-warning {
border-left-color: var(--color-warning);
}
.markdown-alert.markdown-alert-warning .markdown-alert-title {
color: var(--color-warning);
}
.markdown-alert.markdown-alert-tip {
border-left-color: var(--color-tip);
}
.markdown-alert.markdown-alert-tip .markdown-alert-title {
color: var(--color-tip);
}
.markdown-alert.markdown-alert-caution {
border-left-color: var(--color-caution);
}
.markdown-alert.markdown-alert-caution .markdown-alert-title {
color: var(--color-caution);
}
</style>
</head>
<body class="vscode-body vscode-light">
<h1 id="vícejazyčná-dokumentace">Vícejazyčná dokumentace</h1>
<p>Tento adresář obsahuje strojově asistované překlady založené na anglické dokumentaci.</p>
<ul>
<li><strong>API_REFERENCE.md</strong> : 🇺🇸 <a href="../API_REFERENCE.html">Česky</a> | 🇧🇷 <a href="./pt-BR/API_REFERENCE.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/API_REFERENCE.html">Español</a> | 🇫🇷 <a href="./fr/API_REFERENCE.html">Français</a> | 🇮🇹 <a href="./it/API_REFERENCE.html">Italiano</a> | 🇷🇺 <a href="./ru/API_REFERENCE.html">Русский</a> | 🇨🇳<a href="./zh-CN/API_REFERENCE.html">中文 (简体)</a> | 🇩🇪 <a href="./de/API_REFERENCE.html">Deutsch</a> | 🇮🇳 <a href="./in/API_REFERENCE.html">हिन्दी</a> | 🇹🇭 <a href="./th/API_REFERENCE.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/API_REFERENCE.html">Українська</a> | 🇸🇦 <a href="./ar/API_REFERENCE.html">العربية</a> | 🇯🇵<a href="./ja/API_REFERENCE.html">日本語</a>| 🇻🇳 <a href="./vi/API_REFERENCE.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/API_REFERENCE.html">Български</a> | 🇩🇰 <a href="./da/API_REFERENCE.html">Dánsko</a> | 🇫🇮 <a href="./fi/API_REFERENCE.html">Suomi</a> | 🇮🇱 <a href="./he/API_REFERENCE.html">עברית</a> | 🇭🇺 <a href="./hu/API_REFERENCE.html">maďarština</a> | 🇮🇩 <a href="./id/API_REFERENCE.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/API_REFERENCE.html">한국어</a> | 🇲🇾 <a href="./ms/API_REFERENCE.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/API_REFERENCE.html">Nizozemsko</a> | 🇳🇴 <a href="./no/API_REFERENCE.html">Norsk</a> | 🇵🇹 <a href="./pt/API_REFERENCE.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/API_REFERENCE.html">Română</a> | 🇵🇱 <a href="./pl/API_REFERENCE.html">Polski</a> | 🇸🇰 <a href="./sk/API_REFERENCE.html">Slovenčina</a> | 🇸🇪 <a href="./sv/API_REFERENCE.html">Svenska</a> | 🇵🇭 <a href="./phi/API_REFERENCE.html">Filipínec</a></li>
<li><strong><a href="http://ARCHITECTURE.html">ARCHITECTURE.md</a></strong> : 🇺🇸 <a href="../ARCHITECTURE.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/ARCHITECTURE.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/ARCHITECTURE.html">Español</a> | 🇫🇷 <a href="./fr/ARCHITECTURE.html">Français</a> | 🇮🇹 <a href="./it/ARCHITECTURE.html">Italiano</a> | 🇷🇺 <a href="./ru/ARCHITECTURE.html">Русский</a> | 🇨🇳<a href="./zh-CN/ARCHITECTURE.html">中文 (简体)</a> | 🇩🇪 <a href="./de/ARCHITECTURE.html">Deutsch</a> | 🇮🇳 <a href="./in/ARCHITECTURE.html">हिन्दी</a> | 🇹🇭 <a href="./th/ARCHITECTURE.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/ARCHITECTURE.html">Українська</a> | 🇸🇦 <a href="./ar/ARCHITECTURE.html">العربية</a> | 🇯🇵<a href="./ja/ARCHITECTURE.html">日本語</a>| 🇻🇳 <a href="./vi/ARCHITECTURE.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/ARCHITECTURE.html">Български</a> | 🇩🇰 <a href="./da/ARCHITECTURE.html">Dánsko</a> | 🇫🇮 <a href="./fi/ARCHITECTURE.html">Suomi</a> | 🇮🇱 <a href="./he/ARCHITECTURE.html">עברית</a> | 🇭🇺 <a href="./hu/ARCHITECTURE.html">maďarština</a> | 🇮🇩 <a href="./id/ARCHITECTURE.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/ARCHITECTURE.html">한국어</a> | 🇲🇾 <a href="./ms/ARCHITECTURE.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/ARCHITECTURE.html">Nizozemsko</a> | 🇳🇴 <a href="./no/ARCHITECTURE.html">Norsk</a> | 🇵🇹 <a href="./pt/ARCHITECTURE.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/ARCHITECTURE.html">Română</a> | 🇵🇱 <a href="./pl/ARCHITECTURE.html">Polski</a> | 🇸🇰 <a href="./sk/ARCHITECTURE.html">Slovenčina</a> | 🇸🇪 <a href="./sv/ARCHITECTURE.html">Svenska</a> | 🇵🇭 <a href="./phi/ARCHITECTURE.html">Filipínec</a></li>
<li><strong>CODEBASE_DOCUMENTATION.md</strong> : 🇺🇸 <a href="../CODEBASE_DOCUMENTATION.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/CODEBASE_DOCUMENTATION.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/CODEBASE_DOCUMENTATION.html">Español</a> | 🇫🇷 <a href="./fr/CODEBASE_DOCUMENTATION.html">Français</a> | 🇮🇹 <a href="./it/CODEBASE_DOCUMENTATION.html">Italiano</a> | 🇷🇺 <a href="./ru/CODEBASE_DOCUMENTATION.html">Русский</a> | 🇨🇳<a href="./zh-CN/CODEBASE_DOCUMENTATION.html">中文 (简体)</a> | 🇩🇪 <a href="./de/CODEBASE_DOCUMENTATION.html">Deutsch</a> | 🇮🇳 <a href="./in/CODEBASE_DOCUMENTATION.html">हिन्दी</a> | 🇹🇭 <a href="./th/CODEBASE_DOCUMENTATION.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/CODEBASE_DOCUMENTATION.html">Українська</a> | 🇸🇦 <a href="./ar/CODEBASE_DOCUMENTATION.html">العربية</a> | 🇯🇵<a href="./ja/CODEBASE_DOCUMENTATION.html">日本語</a>| 🇻🇳 <a href="./vi/CODEBASE_DOCUMENTATION.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/CODEBASE_DOCUMENTATION.html">Български</a> | 🇩🇰 <a href="./da/CODEBASE_DOCUMENTATION.html">Dánsko</a> | 🇫🇮 <a href="./fi/CODEBASE_DOCUMENTATION.html">Suomi</a> | 🇮🇱 <a href="./he/CODEBASE_DOCUMENTATION.html">עברית</a> | 🇭🇺 <a href="./hu/CODEBASE_DOCUMENTATION.html">maďarština</a> | 🇮🇩 <a href="./id/CODEBASE_DOCUMENTATION.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/CODEBASE_DOCUMENTATION.html">한국어</a> | 🇲🇾 <a href="./ms/CODEBASE_DOCUMENTATION.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/CODEBASE_DOCUMENTATION.html">Nizozemsko</a> | 🇳🇴 <a href="./no/CODEBASE_DOCUMENTATION.html">Norsk</a> | 🇵🇹 <a href="./pt/CODEBASE_DOCUMENTATION.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/CODEBASE_DOCUMENTATION.html">Română</a> | 🇵🇱 <a href="./pl/CODEBASE_DOCUMENTATION.html">Polski</a> | 🇸🇰 <a href="./sk/CODEBASE_DOCUMENTATION.html">Slovenčina</a> | 🇸🇪 <a href="./sv/CODEBASE_DOCUMENTATION.html">Svenska</a> | 🇵🇭 <a href="./phi/CODEBASE_DOCUMENTATION.html">Filipínec</a></li>
<li><strong><a href="http://FEATURES.html">FEATURES.md</a></strong> : 🇺🇸 <a href="../FEATURES.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/FEATURES.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/FEATURES.html">Español</a> | 🇫🇷 <a href="./fr/FEATURES.html">Français</a> | 🇮🇹 <a href="./it/FEATURES.html">Italiano</a> | 🇷🇺 <a href="./ru/FEATURES.html">Русский</a> | 🇨🇳<a href="./zh-CN/FEATURES.html">中文 (简体)</a> | 🇩🇪 <a href="./de/FEATURES.html">Deutsch</a> | 🇮🇳 <a href="./in/FEATURES.html">हिन्दी</a> | 🇹🇭 <a href="./th/FEATURES.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/FEATURES.html">Українська</a> | 🇸🇦 <a href="./ar/FEATURES.html">العربية</a> | 🇯🇵<a href="./ja/FEATURES.html">日本語</a>| 🇻🇳 <a href="./vi/FEATURES.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/FEATURES.html">Български</a> | 🇩🇰 <a href="./da/FEATURES.html">Dánsko</a> | 🇫🇮 <a href="./fi/FEATURES.html">Suomi</a> | 🇮🇱 <a href="./he/FEATURES.html">עברית</a> | 🇭🇺 <a href="./hu/FEATURES.html">maďarština</a> | 🇮🇩 <a href="./id/FEATURES.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/FEATURES.html">한국어</a> | 🇲🇾 <a href="./ms/FEATURES.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/FEATURES.html">Nizozemsko</a> | 🇳🇴 <a href="./no/FEATURES.html">Norsk</a> | 🇵🇹 <a href="./pt/FEATURES.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/FEATURES.html">Română</a> | 🇵🇱 <a href="./pl/FEATURES.html">Polski</a> | 🇸🇰 <a href="./sk/FEATURES.html">Slovenčina</a> | 🇸🇪 <a href="./sv/FEATURES.html">Svenska</a> | 🇵🇭 <a href="./phi/FEATURES.html">Filipínec</a></li>
<li><strong><a href="http://TOUBLESHOOTING.html">TOUBLESHOOTING.md</a></strong> : 🇺🇸 <a href="../TROUBLESHOOTING.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/TROUBLESHOOTING.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/TROUBLESHOOTING.html">Español</a> | 🇫🇷 <a href="./fr/TROUBLESHOOTING.html">Français</a> | 🇮🇹 <a href="./it/TROUBLESHOOTING.html">Italiano</a> | 🇷🇺 <a href="./ru/TROUBLESHOOTING.html">Русский</a> | 🇨🇳<a href="./zh-CN/TROUBLESHOOTING.html">中文 (简体)</a> | 🇩🇪 <a href="./de/TROUBLESHOOTING.html">Deutsch</a> | 🇮🇳 <a href="./in/TROUBLESHOOTING.html">हिन्दी</a> | 🇹🇭 <a href="./th/TROUBLESHOOTING.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/TROUBLESHOOTING.html">Українська</a> | 🇸🇦 <a href="./ar/TROUBLESHOOTING.html">العربية</a> | 🇯🇵<a href="./ja/TROUBLESHOOTING.html">日本語</a>| 🇻🇳 <a href="./vi/TROUBLESHOOTING.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/TROUBLESHOOTING.html">Български</a> | 🇩🇰 <a href="./da/TROUBLESHOOTING.html">Dánsko</a> | 🇫🇮 <a href="./fi/TROUBLESHOOTING.html">Suomi</a> | 🇮🇱 <a href="./he/TROUBLESHOOTING.html">עברית</a> | 🇭🇺 <a href="./hu/TROUBLESHOOTING.html">maďarština</a> | 🇮🇩 <a href="./id/TROUBLESHOOTING.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/TROUBLESHOOTING.html">한국어</a> | 🇲🇾 <a href="./ms/TROUBLESHOOTING.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/TROUBLESHOOTING.html">Nizozemsko</a> | 🇳🇴 <a href="./no/TROUBLESHOOTING.html">Norsk</a> | 🇵🇹 <a href="./pt/TROUBLESHOOTING.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/TROUBLESHOOTING.html">Română</a> | 🇵🇱 <a href="./pl/TROUBLESHOOTING.html">Polski</a> | 🇸🇰 <a href="./sk/TROUBLESHOOTING.html">Slovenčina</a> | 🇸🇪 <a href="./sv/TROUBLESHOOTING.html">Svenska</a> | 🇵🇭 <a href="./phi/TROUBLESHOOTING.html">Filipínec</a></li>
<li><strong>USER_GUIDE.md</strong> : 🇺🇸 <a href="../USER_GUIDE.html">anglicky</a> | 🇧🇷 <a href="./pt-BR/USER_GUIDE.html">Português (Brazílie)</a> | 🇪🇸 <a href="./es/USER_GUIDE.html">Español</a> | 🇫🇷 <a href="./fr/USER_GUIDE.html">Français</a> | 🇮🇹 <a href="./it/USER_GUIDE.html">Italiano</a> | 🇷🇺 <a href="./ru/USER_GUIDE.html">Русский</a> | 🇨🇳<a href="./zh-CN/USER_GUIDE.html">中文 (简体)</a> | 🇩🇪 <a href="./de/USER_GUIDE.html">Deutsch</a> | 🇮🇳 <a href="./in/USER_GUIDE.html">हिन्दी</a> | 🇹🇭 <a href="./th/USER_GUIDE.html">ไทย</a> | 🇺🇦 <a href="./uk-UA/USER_GUIDE.html">Українська</a> | 🇸🇦 <a href="./ar/USER_GUIDE.html">العربية</a> | 🇯🇵<a href="./ja/USER_GUIDE.html">日本語</a>| 🇻🇳 <a href="./vi/USER_GUIDE.html">Tiếng Việt</a> | 🇧🇬 <a href="./bg/USER_GUIDE.html">Български</a> | 🇩🇰 <a href="./da/USER_GUIDE.html">Dánsko</a> | 🇫🇮 <a href="./fi/USER_GUIDE.html">Suomi</a> | 🇮🇱 <a href="./he/USER_GUIDE.html">עברית</a> | 🇭🇺 <a href="./hu/USER_GUIDE.html">maďarština</a> | 🇮🇩 <a href="./id/USER_GUIDE.html">Bahasa Indonésie</a> | 🇰🇷 <a href="./ko/USER_GUIDE.html">한국어</a> | 🇲🇾 <a href="./ms/USER_GUIDE.html">Bahasa Melayu</a> | 🇳🇱 <a href="./nl/USER_GUIDE.html">Nizozemsko</a> | 🇳🇴 <a href="./no/USER_GUIDE.html">Norsk</a> | 🇵🇹 <a href="./pt/USER_GUIDE.html">Português (Portugalsko)</a> | 🇷🇴 <a href="./ro/USER_GUIDE.html">Română</a> | 🇵🇱 <a href="./pl/USER_GUIDE.html">Polski</a> | 🇸🇰 <a href="./sk/USER_GUIDE.html">Slovenčina</a> | 🇸🇪 <a href="./sv/USER_GUIDE.html">Svenska</a> | 🇵🇭 <a href="./phi/USER_GUIDE.html">Filipínec</a></li>
</ul>
<h2 id="nedávná-poznámka-zásady-limitů-pro-účty-codex">Nedávná poznámka: Zásady limitů pro účty Codex</h2>
<p>Dokumentace nyní zahrnuje chování zásad kvót na úrovni účtu Codex:</p>
<ul>
<li>Přepínání pro jednotlivé účty: <code>5h</code> a <code>Weekly</code> (ZAP/VYP).</li>
<li>Zásady prahových hodnot: povolené okno dosahující &gt;=90 % označuje účet jako nezpůsobilý k výběru.</li>
<li>Automatická rotace: provoz se přesune na další způsobilý účet Codex.</li>
<li>Automatické opětovné použití: účet se opět stane způsobilým po úspěšném <code>resetAt</code> poskytovatele.</li>
</ul>
<p>Vygenerováno 26. února 2026.</p>
</body>
</html>
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.1.1
version: 3.1.10
description: |
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
endpoint that routes requests to multiple AI providers with load balancing,
+2
View File
@@ -18,6 +18,7 @@ const nextConfig = {
"thread-stream",
"better-sqlite3",
"keytar",
"wreq-js",
"zod",
"child_process",
"fs",
@@ -72,6 +73,7 @@ const nextConfig = {
const KNOWN_EXTERNALS = new Set([
"better-sqlite3",
"keytar",
"wreq-js",
"zod",
"pino",
"pino-pretty",
+9
View File
@@ -66,6 +66,15 @@ export const DEFAULT_MAX_TOKENS = 64000;
// Minimum max tokens for tool calling (to prevent truncated arguments)
export const DEFAULT_MIN_TOKENS = 32000;
export const PROVIDER_MAX_TOKENS: Record<string, number> = {
groq: 16384, // Groq strict per-model enforcement
openai: 16384, // GPT-4/4o standard
anthropic: 65536, // Claude models
gemini: 65536, // Gemini Studio
};
export const DEFAULT_PROVIDER_MAX_TOKENS = 32000;
// HTTP status codes
export const HTTP_STATUS = {
BAD_REQUEST: 400,
+2 -2
View File
@@ -291,7 +291,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
alias: "qw",
format: "openai",
executor: "default",
baseUrl: "https://portal.qwen.ai/v1/chat/completions",
baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions",
authType: "oauth",
authHeader: "bearer",
headers: {
@@ -626,6 +626,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
"Anthropic-Beta": "claude-code-20250219,interleaved-thinking-2025-05-14",
},
models: [
{ id: "glm-5.1", name: "GLM 5.1" },
{ id: "glm-5", name: "GLM 5" },
{ id: "glm-5-turbo", name: "GLM 5 Turbo" },
{ id: "glm-4.7-flash", name: "GLM 4.7 Flash" },
@@ -635,7 +636,6 @@ export const REGISTRY: Record<string, RegistryEntry> = {
{ id: "glm-4.5v", name: "GLM 4.5V (Vision)" },
{ id: "glm-4.5", name: "GLM 4.5" },
{ id: "glm-4.5-air", name: "GLM 4.5 Air" },
{ id: "glm-4-32b", name: "GLM 4 32B" },
],
},
+297 -20
View File
@@ -15,7 +15,7 @@ import { getModelTargetFormat, PROVIDER_ID_TO_ALIAS } from "../config/providerMo
import { resolveModelAlias } from "../services/modelDeprecation.ts";
import { getUnsupportedParams } from "../config/providerRegistry.ts";
import { createErrorResult, parseUpstreamError, formatProviderError } from "../utils/error.ts";
import { HTTP_STATUS } from "../config/constants.ts";
import { HTTP_STATUS, PROVIDER_MAX_TOKENS } from "../config/constants.ts";
import { classifyProviderError, PROVIDER_ERROR_TYPES } from "../services/errorClassifier.ts";
import { updateProviderConnection } from "@/lib/db/providers";
import { logAuditEvent } from "@/lib/compliance";
@@ -26,12 +26,17 @@ import {
appendRequestLog,
saveCallLog,
} from "@/lib/usageDb";
import { getLoggedInputTokens, getLoggedOutputTokens } from "@/lib/usage/tokenAccounting";
import { recordCost } from "@/domain/costRules";
import { calculateCost } from "@/lib/usage/costCalculator";
import { CLAUDE_OAUTH_TOOL_PREFIX } from "../translator/request/openai-to-claude.ts";
import {
getModelNormalizeToolCallId,
getModelPreserveOpenAIDeveloperRole,
getModelUpstreamExtraHeaders,
} from "@/lib/localDb";
import { getExecutor } from "../executors/index.ts";
import {
parseCodexQuotaHeaders,
getCodexResetTime,
@@ -84,6 +89,202 @@ export function shouldUseNativeCodexPassthrough({
return segments.includes("responses");
}
function buildClaudePassthroughToolNameMap(body: Record<string, unknown> | null | undefined) {
if (!body || !Array.isArray(body.tools)) return null;
const toolNameMap = new Map<string, string>();
for (const tool of body.tools) {
const toolRecord = tool as Record<string, unknown>;
const toolData =
toolRecord?.type === "function" &&
toolRecord.function &&
typeof toolRecord.function === "object"
? (toolRecord.function as Record<string, unknown>)
: toolRecord;
const originalName = typeof toolData?.name === "string" ? toolData.name.trim() : "";
if (!originalName) continue;
toolNameMap.set(`${CLAUDE_OAUTH_TOOL_PREFIX}${originalName}`, originalName);
}
return toolNameMap.size > 0 ? toolNameMap : null;
}
function restoreClaudePassthroughToolNames(
responseBody: Record<string, unknown>,
toolNameMap: Map<string, string> | null
) {
if (!toolNameMap || !Array.isArray(responseBody?.content)) return responseBody;
let changed = false;
const content = responseBody.content.map((block: Record<string, unknown>) => {
if (block?.type !== "tool_use" || typeof block?.name !== "string") return block;
const restoredName = toolNameMap.get(block.name) ?? block.name;
if (restoredName === block.name) return block;
changed = true;
return {
...block,
name: restoredName,
};
});
if (!changed) return responseBody;
return {
...responseBody,
content,
};
}
function getHeaderValueCaseInsensitive(
headers: Record<string, unknown> | null | undefined,
targetName: string
) {
if (!headers || typeof headers !== "object") return null;
const lowered = targetName.toLowerCase();
for (const [key, value] of Object.entries(headers)) {
if (key.toLowerCase() === lowered && typeof value === "string" && value.trim()) {
return value.trim();
}
}
return null;
}
function buildClaudePromptCacheLogMeta(
targetFormat: string,
finalBody: Record<string, unknown> | null | undefined,
providerHeaders: Record<string, unknown> | null | undefined
) {
if (targetFormat !== FORMATS.CLAUDE || !finalBody || typeof finalBody !== "object") return null;
const describeCacheControl = (cacheControl: Record<string, unknown> | undefined, extra = {}) => ({
type:
cacheControl && typeof cacheControl.type === "string" && cacheControl.type.trim()
? cacheControl.type.trim()
: "ephemeral",
ttl:
cacheControl && typeof cacheControl.ttl === "string" && cacheControl.ttl.trim()
? cacheControl.ttl.trim()
: null,
...extra,
});
const systemBreakpoints = Array.isArray(finalBody.system)
? finalBody.system.flatMap((block, index) => {
if (!block || typeof block !== "object") return [];
const cacheControl =
block.cache_control && typeof block.cache_control === "object"
? block.cache_control
: null;
return cacheControl ? [describeCacheControl(cacheControl, { index })] : [];
})
: [];
const toolBreakpoints = Array.isArray(finalBody.tools)
? finalBody.tools.flatMap((tool, index) => {
if (!tool || typeof tool !== "object") return [];
const cacheControl =
tool.cache_control && typeof tool.cache_control === "object" ? tool.cache_control : null;
const name = typeof tool.name === "string" && tool.name.trim() ? tool.name.trim() : null;
return cacheControl ? [describeCacheControl(cacheControl, { index, name })] : [];
})
: [];
const messageBreakpoints = Array.isArray(finalBody.messages)
? finalBody.messages.flatMap((message, messageIndex) => {
if (!message || typeof message !== "object" || !Array.isArray(message.content)) return [];
const role =
typeof message.role === "string" && message.role.trim() ? message.role.trim() : "unknown";
return message.content.flatMap((block, contentIndex) => {
if (!block || typeof block !== "object") return [];
const cacheControl =
block.cache_control && typeof block.cache_control === "object"
? block.cache_control
: null;
if (!cacheControl) return [];
return [
describeCacheControl(cacheControl, {
messageIndex,
contentIndex,
role,
blockType:
typeof block.type === "string" && block.type.trim() ? block.type.trim() : "unknown",
}),
];
});
})
: [];
const totalBreakpoints =
systemBreakpoints.length + toolBreakpoints.length + messageBreakpoints.length;
const anthropicBeta = getHeaderValueCaseInsensitive(providerHeaders, "Anthropic-Beta");
if (totalBreakpoints === 0 && !anthropicBeta) return null;
return {
applied: totalBreakpoints > 0,
totalBreakpoints,
anthropicBeta,
systemBreakpoints,
toolBreakpoints,
messageBreakpoints,
};
}
function toPositiveNumber(value: unknown) {
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
}
function buildCacheUsageLogMeta(usage: Record<string, unknown> | null | undefined) {
if (!usage || typeof usage !== "object") return null;
const promptTokenDetails =
usage.prompt_tokens_details && typeof usage.prompt_tokens_details === "object"
? (usage.prompt_tokens_details as Record<string, unknown>)
: undefined;
const hasCacheFields =
"cache_read_input_tokens" in usage ||
"cached_tokens" in usage ||
"cache_creation_input_tokens" in usage ||
(!!promptTokenDetails &&
("cached_tokens" in promptTokenDetails || "cache_creation_tokens" in promptTokenDetails));
const cacheReadTokens = toPositiveNumber(
usage.cache_read_input_tokens ?? usage.cached_tokens ?? promptTokenDetails?.cached_tokens
);
const cacheCreationTokens = toPositiveNumber(
usage.cache_creation_input_tokens ?? promptTokenDetails?.cache_creation_tokens
);
if (!hasCacheFields) return null;
return {
cacheReadTokens,
cacheCreationTokens,
};
}
function attachLogMeta(
payload: Record<string, unknown> | null | undefined,
meta: Record<string, unknown> | null | undefined
) {
if (!meta || typeof meta !== "object") return payload;
const compactMeta = Object.fromEntries(
Object.entries(meta).filter(([, value]) => value !== null && value !== undefined)
);
if (Object.keys(compactMeta).length === 0) return payload;
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
return { _omniroute: compactMeta, _payload: payload ?? null };
}
const existing =
payload._omniroute &&
typeof payload._omniroute === "object" &&
!Array.isArray(payload._omniroute)
? payload._omniroute
: {};
return {
...payload,
_omniroute: {
...existing,
...compactMeta,
},
};
}
/**
* Core chat handler - shared between SSE and Worker
* Returns { success, response, status, error } for caller to handle fallback
@@ -415,7 +616,7 @@ export async function handleChatCore({
FORMATS.OPENAI,
FORMATS.CLAUDE,
model,
translatedBody,
{ ...translatedBody, _disableToolPrefix: true },
stream,
credentials,
provider,
@@ -426,10 +627,12 @@ export async function handleChatCore({
} else {
translatedBody = { ...body };
// Issue #199: Disable tool name prefix when routing Claude-format requests
// to non-Claude backends (prefix causes tool name mismatches)
const claudeProviders = ["claude", "anthropic"];
if (targetFormat === FORMATS.CLAUDE && !claudeProviders.includes(provider?.toLowerCase?.())) {
// Issue #199 + #618: Always disable tool name prefix in Claude passthrough.
// The proxy_ prefix was designed for OpenAI→Claude translation to avoid
// conflicts with Claude OAuth tools, but in the passthrough path the tools
// are already in Claude format. Applying the prefix turns "Bash" into
// "proxy_Bash", which Claude rejects ("No such tool available: proxy_Bash").
if (targetFormat === FORMATS.CLAUDE) {
translatedBody._disableToolPrefix = true;
}
@@ -564,7 +767,14 @@ export async function handleChatCore({
}
// Extract toolNameMap for response translation (Claude OAuth)
const toolNameMap = translatedBody._toolNameMap;
const translatedToolNameMap = translatedBody._toolNameMap;
const nativeClaudeToolNameMap = isClaudePassthrough
? buildClaudePassthroughToolNameMap(body)
: null;
const toolNameMap =
translatedToolNameMap instanceof Map && translatedToolNameMap.size > 0
? translatedToolNameMap
: nativeClaudeToolNameMap;
delete translatedBody._toolNameMap;
delete translatedBody._disableToolPrefix;
@@ -586,6 +796,22 @@ export async function handleChatCore({
}
}
// Provider-specific max_tokens caps (#711)
// Some providers reject requests when max_tokens exceeds their API limit.
// Cap before sending to avoid upstream HTTP 400 errors.
const providerCap = PROVIDER_MAX_TOKENS[provider];
if (providerCap) {
for (const field of ["max_tokens", "max_completion_tokens"] as const) {
if (typeof translatedBody[field] === "number" && translatedBody[field] > providerCap) {
log?.debug?.(
"PARAMS",
`Capping ${field} from ${translatedBody[field]} to ${providerCap} for ${provider}`
);
translatedBody[field] = providerCap;
}
}
}
// Get executor for this provider
const executor = getExecutor(provider);
const getExecutionCredentials = () =>
@@ -665,6 +891,7 @@ export async function handleChatCore({
let providerUrl;
let providerHeaders;
let finalBody;
let claudePromptCacheLogMeta = null;
try {
const result = await executeProviderRequest(effectiveModel, true);
@@ -673,6 +900,11 @@ export async function handleChatCore({
providerUrl = result.url;
providerHeaders = result.headers;
finalBody = result.transformedBody;
claudePromptCacheLogMeta = buildClaudePromptCacheLogMeta(
targetFormat,
finalBody,
providerHeaders
);
// Log target request (final request to provider)
reqLogger.logTargetRequest(providerUrl, providerHeaders, finalBody);
@@ -702,7 +934,9 @@ export async function handleChatCore({
provider,
connectionId,
duration: Date.now() - startTime,
requestBody: body,
requestBody: attachLogMeta(body, {
claudePromptCache: claudePromptCacheLogMeta,
}),
error: error.message,
sourceFormat,
targetFormat,
@@ -842,7 +1076,9 @@ export async function handleChatCore({
provider,
connectionId,
duration: Date.now() - startTime,
requestBody: body,
requestBody: attachLogMeta(body, {
claudePromptCache: claudePromptCacheLogMeta,
}),
error: message,
sourceFormat,
targetFormat,
@@ -1012,6 +1248,10 @@ export async function handleChatCore({
}
}
if (sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE) {
responseBody = restoreClaudePassthroughToolNames(responseBody, toolNameMap);
}
// Notify success - caller can clear error status if needed
if (onRequestSuccess) {
await onRequestSuccess();
@@ -1024,6 +1264,7 @@ export async function handleChatCore({
);
// Save structured call log with full payloads
const cacheUsageLogMeta = buildCacheUsageLogMeta(usage);
saveCallLog({
method: "POST",
path: clientRawRequest?.endpoint || "/v1/chat/completions",
@@ -1034,8 +1275,19 @@ export async function handleChatCore({
connectionId,
duration: Date.now() - startTime,
tokens: usage,
requestBody: body,
responseBody,
requestBody: attachLogMeta(body, {
claudePromptCache: claudePromptCacheLogMeta,
}),
responseBody: attachLogMeta(responseBody, {
claudePromptCache: claudePromptCacheLogMeta
? {
applied: claudePromptCacheLogMeta.applied,
totalBreakpoints: claudePromptCacheLogMeta.totalBreakpoints,
anthropicBeta: claudePromptCacheLogMeta.anthropicBeta,
}
: null,
claudePromptCacheUsage: cacheUsageLogMeta,
}),
sourceFormat,
targetFormat,
comboName,
@@ -1044,7 +1296,7 @@ export async function handleChatCore({
noLog: apiKeyInfo?.noLog === true,
}).catch(() => {});
if (usage && typeof usage === "object") {
const msg = `[${new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" })}] 📊 [USAGE] ${provider.toUpperCase()} | in=${usage?.prompt_tokens || 0} | out=${usage?.completion_tokens || 0}${connectionId ? ` | account=${connectionId.slice(0, 8)}...` : ""}`;
const msg = `[${new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" })}] 📊 [USAGE] ${provider.toUpperCase()} | in=${getLoggedInputTokens(usage)} | out=${getLoggedOutputTokens(usage)}${connectionId ? ` | account=${connectionId.slice(0, 8)}...` : ""}`;
console.log(`${COLORS.green}${msg}${COLORS.reset}`);
saveRequestUsage({
@@ -1065,6 +1317,11 @@ export async function handleChatCore({
});
}
if (apiKeyInfo?.id && usage) {
const estimatedCost = await calculateCost(provider, model, usage);
if (estimatedCost > 0) recordCost(apiKeyInfo.id, estimatedCost);
}
// Translate response to client's expected format (usually OpenAI)
// Pass toolNameMap so Claude OAuth proxy_ prefix is stripped in tool_use blocks (#605)
let translatedResponse = needsTranslation(targetFormat, sourceFormat)
@@ -1166,6 +1423,7 @@ export async function handleChatCore({
usage: streamUsage,
responseBody: streamResponseBody,
}) => {
const cacheUsageLogMeta = buildCacheUsageLogMeta(streamUsage);
saveCallLog({
method: "POST",
path: clientRawRequest?.endpoint || "/v1/chat/completions",
@@ -1176,8 +1434,19 @@ export async function handleChatCore({
connectionId,
duration: Date.now() - startTime,
tokens: streamUsage || {},
requestBody: body,
responseBody: streamResponseBody ?? undefined,
requestBody: attachLogMeta(body, {
claudePromptCache: claudePromptCacheLogMeta,
}),
responseBody: attachLogMeta(streamResponseBody ?? undefined, {
claudePromptCache: claudePromptCacheLogMeta
? {
applied: claudePromptCacheLogMeta.applied,
totalBreakpoints: claudePromptCacheLogMeta.totalBreakpoints,
anthropicBeta: claudePromptCacheLogMeta.anthropicBeta,
}
: null,
claudePromptCacheUsage: cacheUsageLogMeta,
}),
sourceFormat,
targetFormat,
comboName,
@@ -1185,22 +1454,29 @@ export async function handleChatCore({
apiKeyName: apiKeyInfo?.name || null,
noLog: apiKeyInfo?.noLog === true,
}).catch(() => {});
if (apiKeyInfo?.id && streamUsage) {
calculateCost(provider, model, streamUsage)
.then((estimatedCost) => {
if (estimatedCost > 0) recordCost(apiKeyInfo.id, estimatedCost);
})
.catch(() => {});
}
};
// For Codex provider, translate response from openai-responses to openai (Chat Completions) format
// For providers using Responses API format, translate stream back to openai (Chat Completions) format
// UNLESS client is Droid CLI which expects openai-responses format back
const isDroidCLI =
userAgent?.toLowerCase().includes("droid") || userAgent?.toLowerCase().includes("codex-cli");
const needsCodexTranslation =
provider === "codex" &&
const needsResponsesTranslation =
targetFormat === FORMATS.OPENAI_RESPONSES &&
sourceFormat === FORMATS.OPENAI &&
!isResponsesEndpoint &&
!isDroidCLI;
if (needsCodexTranslation) {
// Codex returns openai-responses, translate to openai (Chat Completions) that clients expect
log?.debug?.("STREAM", `Codex translation mode: openai-responses → openai`);
if (needsResponsesTranslation) {
// Provider returns openai-responses, translate to openai (Chat Completions) that clients expect
log?.debug?.("STREAM", `Responses translation mode: openai-responses → openai`);
transformStream = createSSETransformStreamWithLogger(
"openai-responses",
"openai",
@@ -1233,6 +1509,7 @@ export async function handleChatCore({
transformStream = createPassthroughStreamWithLogger(
provider,
reqLogger,
toolNameMap,
model,
connectionId,
body,
+1 -1
View File
@@ -36,8 +36,8 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
let usage = null;
const getToolCallKey = (toolCall: Record<string, unknown>) => {
if (toolCall?.id) return `id:${toolCall.id}`;
if (Number.isInteger(toolCall?.index)) return `idx:${toolCall.index}`;
if (toolCall?.id) return `id:${toolCall.id}`;
unknownToolCallSeq += 1;
return `seq:${unknownToolCallSeq}`;
};
+4 -1
View File
@@ -29,7 +29,10 @@ export function extractUsageFromResponse(responseBody, provider) {
return {
prompt_tokens: responsesUsage.input_tokens || 0,
completion_tokens: responsesUsage.output_tokens || 0,
cached_tokens: responsesUsage.cache_read_input_tokens,
cache_read_input_tokens: responsesUsage.cache_read_input_tokens,
cached_tokens:
responsesUsage.input_tokens_details?.cached_tokens ??
responsesUsage.cache_read_input_tokens,
cache_creation_input_tokens: responsesUsage.cache_creation_input_tokens,
reasoning_tokens:
responsesUsage.reasoning_tokens || responsesUsage.output_tokens_details?.reasoning_tokens,
+1 -1
View File
@@ -439,7 +439,7 @@ async function handleListModelsCatalog(args: { provider?: string; capability?: s
if (args.provider && !args.capability) {
// Use direct provider fetch to get real-time API status
path = `/api/providers/${encodeURIComponent(args.provider)}/models`;
path = `/api/providers/${encodeURIComponent(args.provider)}/models?excludeHidden=true`;
isProviderSpecific = true;
} else {
const params = new URLSearchParams();
+4 -1
View File
@@ -36,7 +36,8 @@ const FITNESS_TABLE: Record<string, Record<string, number>> = {
"grok-3": 0.8,
// Kimi K2.5 — agentic with tool calling, good at code tasks
"kimi-k2": 0.82,
// GLM-5 — Z.AI model with 128k output
// GLM-5.1 / GLM-5 — Z.AI reasoning models, 200K context / 128k output
"glm-5.1": 0.78,
"glm-5": 0.78,
// MiniMax M2.5 — reasoning support helps complex code
"minimax-m2.5": 0.75,
@@ -78,6 +79,7 @@ const FITNESS_TABLE: Record<string, Record<string, number>> = {
"deepseek-r1": 0.88,
"deepseek-chat": 0.8,
"kimi-k2": 0.82, // Kimi K2.5 agentic — good for analysis
"glm-5.1": 0.82, // GLM-5.1 free reasoning, 200K context for long analysis
"glm-5": 0.78, // GLM-5 with 128k output for long analysis
"minimax-m2.5": 0.76,
},
@@ -114,6 +116,7 @@ const FITNESS_TABLE: Record<string, Record<string, number>> = {
"grok-4": 0.74,
"grok-3": 0.73,
"kimi-k2": 0.76, // agentic multi-step tasks
"glm-5.1": 0.75,
"glm-5": 0.7,
"minimax-m2.5": 0.7,
},
+9
View File
@@ -568,6 +568,15 @@ export async function handleComboChat({
: handleSingleModel;
// ─────────────────────────────────────────────────────────────────────────
// Route to pinned model if context caching specifies one (Fix #679)
if (pinnedModel) {
log.info(
"COMBO",
`Bypassing strategy — routing directly to pinned context model: ${pinnedModel}`
);
return handleSingleModelWrapped(body, pinnedModel);
}
// Route to round-robin handler if strategy matches
if (strategy === "round-robin") {
return handleRoundRobinCombo({
+33 -3
View File
@@ -86,6 +86,24 @@ export function fixToolUseOrdering(messages) {
return merged;
}
function ensureMessageContentArray(msg) {
if (Array.isArray(msg?.content)) return msg.content;
if (typeof msg?.content === "string" && msg.content.trim()) {
msg.content = [{ type: "text", text: msg.content }];
return msg.content;
}
return [];
}
function markMessageCacheControl(msg, ttl) {
const content = ensureMessageContentArray(msg);
if (content.length === 0) return false;
const lastIndex = content.length - 1;
content[lastIndex].cache_control =
ttl !== undefined ? { type: "ephemeral", ttl } : { type: "ephemeral" };
return true;
}
// Prepare request for Claude format endpoints
// - Cleanup cache_control
// - Filter empty messages
@@ -156,15 +174,27 @@ export function prepareClaudeRequest(body, provider = null) {
const lastMessageIsUser = lastMessage?.role === "user";
const thinkingEnabled = body.thinking?.type === "enabled" && lastMessageIsUser;
// Claude Code-style prompt caching:
// - cache the second-to-last user turn for conversation reuse
// - cache the last assistant turn so the next user turn can reuse it
const userMessageIndexes = filtered.reduce((indexes, msg, index) => {
if (msg?.role === "user") indexes.push(index);
return indexes;
}, []);
const secondToLastUserIndex =
userMessageIndexes.length >= 2 ? userMessageIndexes[userMessageIndexes.length - 2] : -1;
if (secondToLastUserIndex >= 0) {
markMessageCacheControl(filtered[secondToLastUserIndex]);
}
// Pass 2 (reverse): add cache_control to last assistant + handle thinking for Anthropic
let lastAssistantProcessed = false;
for (let i = filtered.length - 1; i >= 0; i--) {
const msg = filtered[i];
if (msg.role === "assistant" && Array.isArray(msg.content)) {
if (msg.role === "assistant" && Array.isArray(ensureMessageContentArray(msg))) {
// Add cache_control to last block of first (from end) assistant with content
if (!lastAssistantProcessed && msg.content.length > 0) {
msg.content[msg.content.length - 1].cache_control = { type: "ephemeral" };
if (!lastAssistantProcessed && markMessageCacheControl(msg)) {
lastAssistantProcessed = true;
}
@@ -0,0 +1,207 @@
/**
* Shared sanitizers for tool payloads that arrive from IDEs/SDKs with
* JSON Schema numeric constraints encoded as strings or invalid descriptions.
*/
type JsonRecord = Record<string, unknown>;
const NUMERIC_SCHEMA_FIELDS = [
"minimum",
"maximum",
"exclusiveMinimum",
"exclusiveMaximum",
"minLength",
"maxLength",
"minItems",
"maxItems",
"minProperties",
"maxProperties",
"multipleOf",
] as const;
function isPlainObject(value: unknown): value is JsonRecord {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function coerceNumericString(value: unknown): unknown {
if (typeof value !== "string") return value;
const trimmed = value.trim();
if (trimmed.length === 0) return value;
const parsed = Number(trimmed);
return Number.isFinite(parsed) ? parsed : value;
}
function mapRecordValues(record: JsonRecord): JsonRecord {
return Object.fromEntries(
Object.entries(record).map(([key, value]) => [key, coerceSchemaNumericFields(value)])
);
}
function sanitizeDescriptionValue(value: unknown): string | undefined {
if (value === undefined) return undefined;
if (value === null) return "";
return typeof value === "string" ? value : String(value);
}
export function coerceSchemaNumericFields(schema: unknown): unknown {
if (Array.isArray(schema)) {
return schema.map((entry) => coerceSchemaNumericFields(entry));
}
if (!isPlainObject(schema)) return schema;
const result: JsonRecord = { ...schema };
for (const field of NUMERIC_SCHEMA_FIELDS) {
if (field in result) {
result[field] = coerceNumericString(result[field]);
}
}
if (isPlainObject(result.properties)) {
result.properties = mapRecordValues(result.properties);
}
if (isPlainObject(result.patternProperties)) {
result.patternProperties = mapRecordValues(result.patternProperties);
}
if (isPlainObject(result.definitions)) {
result.definitions = mapRecordValues(result.definitions);
}
if (isPlainObject(result.$defs)) {
result.$defs = mapRecordValues(result.$defs);
}
if (isPlainObject(result.dependentSchemas)) {
result.dependentSchemas = mapRecordValues(result.dependentSchemas);
}
if (result.items !== undefined) {
result.items = coerceSchemaNumericFields(result.items);
}
if (result.additionalProperties && typeof result.additionalProperties === "object") {
result.additionalProperties = coerceSchemaNumericFields(result.additionalProperties);
}
if (result.unevaluatedProperties && typeof result.unevaluatedProperties === "object") {
result.unevaluatedProperties = coerceSchemaNumericFields(result.unevaluatedProperties);
}
if (Array.isArray(result.prefixItems)) {
result.prefixItems = result.prefixItems.map((entry) => coerceSchemaNumericFields(entry));
}
if (Array.isArray(result.anyOf)) {
result.anyOf = result.anyOf.map((entry) => coerceSchemaNumericFields(entry));
}
if (Array.isArray(result.oneOf)) {
result.oneOf = result.oneOf.map((entry) => coerceSchemaNumericFields(entry));
}
if (Array.isArray(result.allOf)) {
result.allOf = result.allOf.map((entry) => coerceSchemaNumericFields(entry));
}
if (isPlainObject(result.not)) {
result.not = coerceSchemaNumericFields(result.not);
}
if (isPlainObject(result.if)) {
result.if = coerceSchemaNumericFields(result.if);
}
if (isPlainObject(result.then)) {
result.then = coerceSchemaNumericFields(result.then);
}
if (isPlainObject(result.else)) {
result.else = coerceSchemaNumericFields(result.else);
}
return result;
}
export function sanitizeToolDescription(tool: unknown): unknown {
if (!isPlainObject(tool)) return tool;
const result: JsonRecord = { ...tool };
if (isPlainObject(result.function) && "description" in result.function) {
const description = sanitizeDescriptionValue(result.function.description);
if (description !== undefined) {
result.function = { ...result.function, description };
}
}
if (!isPlainObject(result.function) && "description" in result) {
const description = sanitizeDescriptionValue(result.description);
if (description !== undefined) {
result.description = description;
}
}
if (Array.isArray(result.functionDeclarations)) {
result.functionDeclarations = result.functionDeclarations.map((declaration) => {
if (!isPlainObject(declaration) || !("description" in declaration)) return declaration;
const description = sanitizeDescriptionValue(declaration.description);
return description === undefined ? declaration : { ...declaration, description };
});
}
return result;
}
export function coerceToolSchemas(tools: unknown): unknown {
if (!Array.isArray(tools)) return tools;
return tools.map((tool) => {
if (!isPlainObject(tool)) return tool;
const result: JsonRecord = { ...tool };
if (isPlainObject(result.function) && "parameters" in result.function) {
result.function = {
...result.function,
parameters: coerceSchemaNumericFields(result.function.parameters),
};
}
if (result.input_schema !== undefined) {
result.input_schema = coerceSchemaNumericFields(result.input_schema);
}
if ("parameters" in result && !isPlainObject(result.function)) {
result.parameters = coerceSchemaNumericFields(result.parameters);
}
if (Array.isArray(result.functionDeclarations)) {
result.functionDeclarations = result.functionDeclarations.map((declaration) => {
if (!isPlainObject(declaration) || !("parameters" in declaration)) return declaration;
return {
...declaration,
parameters: coerceSchemaNumericFields(declaration.parameters),
};
});
}
return result;
});
}
export function sanitizeToolDescriptions(tools: unknown): unknown {
if (!Array.isArray(tools)) return tools;
return tools.map((tool) => sanitizeToolDescription(tool));
}
export function injectEmptyReasoningContentForToolCalls(
messages: unknown,
provider: unknown
): unknown {
if (!Array.isArray(messages) || String(provider || "").toLowerCase() !== "deepseek") {
return messages;
}
return messages.map((message) => {
if (!isPlainObject(message)) return message;
if (
message.role !== "assistant" ||
!Array.isArray(message.tool_calls) ||
message.tool_calls.length === 0 ||
message.reasoning_content !== undefined
) {
return message;
}
return { ...message, reasoning_content: "" };
});
}
+36
View File
@@ -2,6 +2,11 @@ import { FORMATS } from "./formats.ts";
import { ensureToolCallIds, fixMissingToolResponses } from "./helpers/toolCallHelper.ts";
import { prepareClaudeRequest } from "./helpers/claudeHelper.ts";
import { filterToOpenAIFormat } from "./helpers/openaiHelper.ts";
import {
coerceToolSchemas,
injectEmptyReasoningContentForToolCalls,
sanitizeToolDescriptions,
} from "./helpers/schemaCoercion.ts";
import { getRequestTranslator, getResponseTranslator } from "./registry.ts";
import { bootstrapTranslatorRegistry } from "./bootstrap.ts";
import { normalizeThinkingConfig } from "../services/provider.ts";
@@ -171,10 +176,41 @@ export function translateRequest(
);
}
if (result.tools !== undefined) {
result.tools = coerceToolSchemas(result.tools);
result.tools = sanitizeToolDescriptions(result.tools);
}
if (targetFormat === FORMATS.OPENAI && result.messages && Array.isArray(result.messages)) {
result.messages = injectEmptyReasoningContentForToolCalls(result.messages, provider);
}
// Ensure unique tool_call ids on final payload (translators may have introduced duplicates)
ensureToolCallIds(result, { use9CharId });
fixMissingToolResponses(result);
if (result.tools) {
result.tools = coerceToolSchemas(result.tools);
result.tools = sanitizeToolDescriptions(result.tools);
}
// Inject reasoning_content = "" for DeepSeek/Reasoning models assistant messages with tool_calls
// if omitted by the client, to avoid upstream 400 errors (e.g. "Messages with role 'assistant' that contain tool_calls must also include reasoning_content")
const isReasoner =
provider === "deepseek" || (typeof model === "string" && /r1|reason/i.test(model));
if (isReasoner && result.messages && Array.isArray(result.messages)) {
for (const msg of result.messages) {
if (
msg.role === "assistant" &&
Array.isArray(msg.tool_calls) &&
msg.tool_calls.length > 0 &&
msg.reasoning_content === undefined
) {
msg.reasoning_content = "";
}
}
}
return result;
}
@@ -90,7 +90,6 @@ export function claudeToOpenAIResponse(chunk, state) {
tool_calls: [
{
index: toolCall.index,
id: toolCall.id,
function: { arguments: delta.partial_json },
},
],
@@ -464,6 +464,9 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
}
// Function call arguments delta
// NOTE: Do NOT include `id` or `type` here - only first chunk (response.output_item.added)
// should have them. Including `id` on every chunk causes openai-to-claude.ts to emit
// a new content_block_start for each delta, breaking Claude Code ACP sessions.
if (eventType === "response.function_call_arguments.delta") {
const argsDelta = data.delta || "";
if (!argsDelta) return null;
@@ -480,8 +483,6 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
tool_calls: [
{
index: state.toolCallIndex,
id: state.currentToolCallId,
type: "function",
function: { arguments: argsDelta },
},
],
+9 -2
View File
@@ -13,10 +13,17 @@ export function clientWantsJsonResponse(acceptHeader: unknown): boolean {
/**
* Resolves stream behavior from request body + Accept header.
* OpenAI-compatible behavior: stream only when `stream: true` and client did not force JSON.
* Priority: explicit `stream: true/false` in body wins.
* Accept header only acts as fallback when stream is not explicitly set.
* Fixes #656: clients sending both `stream: true` and `Accept: application/json`
* should still get streaming responses body intent takes precedence.
*/
export function resolveStreamFlag(bodyStream: unknown, acceptHeader: unknown): boolean {
return bodyStream === true && !clientWantsJsonResponse(acceptHeader);
// Explicit body value always wins
if (bodyStream === true) return true;
if (bodyStream === false) return false;
// No explicit stream param — fall back to Accept header heuristic
return !clientWantsJsonResponse(acceptHeader);
}
/**
+24
View File
@@ -72,6 +72,22 @@ function getOpenAIIntermediateChunks(value: unknown): unknown[] {
return Array.isArray(candidate) ? candidate : [];
}
function restoreClaudePassthroughToolUseName(parsed: JsonRecord, toolNameMap: unknown): boolean {
if (!(toolNameMap instanceof Map)) return false;
if (!parsed || typeof parsed !== "object") return false;
const block =
parsed.content_block && typeof parsed.content_block === "object"
? (parsed.content_block as JsonRecord)
: null;
if (!block || block.type !== "tool_use" || typeof block.name !== "string") return false;
const restoredName = toolNameMap.get(block.name) ?? block.name;
if (restoredName === block.name) return false;
block.name = restoredName;
return true;
}
// Note: TextDecoder/TextEncoder are created per-stream inside createSSEStream()
// to avoid shared state issues with concurrent streams (TextDecoder with {stream:true}
// maintains internal buffering state between decode() calls).
@@ -248,6 +264,7 @@ export function createSSEStream(options: StreamOptions = {}) {
if (eu.cache_creation_input_tokens)
u.cache_creation_input_tokens = eu.cache_creation_input_tokens;
}
const restoredToolName = restoreClaudePassthroughToolUseName(parsed, toolNameMap);
// Track content length and accumulate from Claude format
if (parsed.delta?.text) {
totalContentLength += parsed.delta.text.length;
@@ -257,6 +274,11 @@ export function createSSEStream(options: StreamOptions = {}) {
totalContentLength += parsed.delta.thinking.length;
passthroughAccumulatedContent += parsed.delta.thinking;
}
if (restoredToolName) {
output = `data: ${JSON.stringify(parsed)}
`;
injectedUsage = true;
}
} else {
// Chat Completions: full sanitization pipeline
parsed = sanitizeStreamingChunk(parsed);
@@ -771,6 +793,7 @@ export function createSSETransformStreamWithLogger(
export function createPassthroughStreamWithLogger(
provider: string | null = null,
reqLogger: StreamLogger | null = null,
toolNameMap: unknown = null,
model: string | null = null,
connectionId: string | null = null,
body: unknown = null,
@@ -781,6 +804,7 @@ export function createPassthroughStreamWithLogger(
mode: STREAM_MODE.PASSTHROUGH,
provider,
reqLogger,
toolNameMap,
model,
connectionId,
apiKeyInfo,
+19 -17
View File
@@ -39,32 +39,34 @@ export function parseSSELine(line) {
// Check if chunk has valuable content (not empty)
export function hasValuableContent(chunk, format) {
// OpenAI format
if (format === FORMATS.OPENAI && chunk.choices?.[0]?.delta) {
if (format === FORMATS.OPENAI) {
if (!chunk.choices?.[0]?.delta) return false;
const delta = chunk.choices[0].delta;
return (
(delta.content && delta.content !== "") ||
(delta.reasoning_content && delta.reasoning_content !== "") ||
(delta.tool_calls && delta.tool_calls.length > 0) ||
chunk.choices[0].finish_reason ||
delta.role
);
if (typeof delta.content === "string" && delta.content.length > 0) return true;
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0)
return true;
if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) return true;
if (chunk.choices[0].finish_reason) return true;
if (typeof delta.role === "string" && delta.role.length > 0) return true;
return false;
}
// Claude format
if (format === FORMATS.CLAUDE) {
const isContentBlockDelta = chunk.type === "content_block_delta";
const hasText = chunk.delta?.text && chunk.delta.text !== "";
const hasThinking = chunk.delta?.thinking && chunk.delta.thinking !== "";
const hasInputJson = chunk.delta?.partial_json && chunk.delta.partial_json !== "";
if (isContentBlockDelta && !hasText && !hasThinking && !hasInputJson) {
return false;
if (isContentBlockDelta) {
const hasText = typeof chunk.delta?.text === "string" && chunk.delta.text.length > 0;
const hasThinking =
typeof chunk.delta?.thinking === "string" && chunk.delta.thinking.length > 0;
const hasInputJson =
typeof chunk.delta?.partial_json === "string" && chunk.delta.partial_json.length > 0;
if (!hasText && !hasThinking && !hasInputJson) return false;
}
return true;
}
// Gemini format: filter chunks with no actual content parts
if (format === FORMATS.GEMINI && chunk.candidates?.[0]) {
// Gemini / Antigravity format: filter chunks with no actual content parts
if ((format === FORMATS.GEMINI || format === FORMATS.ANTIGRAVITY) && chunk.candidates?.[0]) {
const candidate = chunk.candidates[0];
// Keep chunks with finish reason or safety ratings (they signal completion)
if (candidate.finishReason) return true;
@@ -73,7 +75,7 @@ export function hasValuableContent(chunk, format) {
if (!parts || parts.length === 0) return false;
// Filter out chunks where all parts have empty text
const hasContent = parts.some(
(p) => (p.text && p.text !== "") || p.functionCall || p.executableCode
(p) => (typeof p.text === "string" && p.text.length > 0) || p.functionCall || p.executableCode
);
return hasContent;
}
+12 -8
View File
@@ -3,6 +3,12 @@
*/
import { saveRequestUsage, appendRequestLog } from "@/lib/usageDb";
import {
getLoggedInputTokens,
getLoggedOutputTokens,
getPromptCacheCreationTokens,
getPromptCacheReadTokens,
} from "@/lib/usage/tokenAccounting";
import { FORMATS } from "../translator/formats.ts";
// ANSI color codes
@@ -415,8 +421,8 @@ export function logUsage(provider, usage, model = null, connectionId = null, api
// Support both formats:
// - OpenAI: prompt_tokens, completion_tokens
// - Claude: input_tokens, output_tokens
const inTokens = usage?.prompt_tokens || usage?.input_tokens || 0;
const outTokens = usage?.completion_tokens || usage?.output_tokens || 0;
const inTokens = getLoggedInputTokens(usage);
const outTokens = getLoggedOutputTokens(usage);
const accountPrefix = connectionId ? connectionId.slice(0, 8) + "..." : "unknown";
let msg = `[${getTimeString()}] 📊 ${COLORS.green}[USAGE] ${p} | in=${inTokens} | out=${outTokens} | account=${accountPrefix}${COLORS.reset}`;
@@ -427,10 +433,10 @@ export function logUsage(provider, usage, model = null, connectionId = null, api
}
// Add cache info if present (unified from different formats)
const cacheRead = usage.cache_read_input_tokens || usage.cached_tokens;
const cacheRead = getPromptCacheReadTokens(usage);
if (cacheRead) msg += ` | cache_read=${cacheRead}`;
const cacheCreation = usage.cache_creation_input_tokens;
const cacheCreation = getPromptCacheCreationTokens(usage);
if (cacheCreation) msg += ` | cache_create=${cacheCreation}`;
const reasoning = usage.reasoning_tokens;
@@ -438,11 +444,9 @@ export function logUsage(provider, usage, model = null, connectionId = null, api
console.log(msg);
// Save to usage DB
// input = total input tokens (non-cached + cache_read + cache_creation)
// This ensures analytics show correct totals for heavily-cached requests
// Save to usage DB with cache-read tracked separately from the main input counter.
const tokens = {
input: inTokens + (cacheRead || 0) + (cacheCreation || 0),
input: inTokens,
output: outTokens,
cacheRead: cacheRead || 0,
cacheCreation: cacheCreation || 0,
+319 -2
View File
@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.1.1",
"version": "3.1.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.1.1",
"version": "3.1.10",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [
@@ -60,6 +60,7 @@
"@types/node": "^25.2.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"c8": "^11.0.0",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
@@ -511,6 +512,16 @@
}
}
},
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@braintree/sanitize-url": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
@@ -2160,6 +2171,16 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -6007,6 +6028,13 @@
"@types/node": "*"
}
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/js-cookie": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
@@ -7659,6 +7687,40 @@
"node": ">=6.0.0"
}
},
"node_modules/c8": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz",
"integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==",
"dev": true,
"license": "ISC",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.1",
"@istanbuljs/schema": "^0.1.3",
"find-up": "^5.0.0",
"foreground-child": "^3.1.1",
"istanbul-lib-coverage": "^3.2.0",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.1.6",
"test-exclude": "^8.0.0",
"v8-to-istanbul": "^9.0.0",
"yargs": "^17.7.2",
"yargs-parser": "^21.1.1"
},
"bin": {
"c8": "bin/c8.js"
},
"engines": {
"node": "20 || >=22"
},
"peerDependencies": {
"monocart-coverage-reports": "^2"
},
"peerDependenciesMeta": {
"monocart-coverage-reports": {
"optional": true
}
}
},
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -10522,6 +10584,23 @@
"node": ">=0.10.0"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
@@ -10803,6 +10882,24 @@
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -10816,6 +10913,45 @@
"node": ">=10.13.0"
}
},
"node_modules/glob/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
@@ -11282,6 +11418,13 @@
"node": ">=16.9.0"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/html-url-attributes": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -12210,6 +12353,45 @@
"node": ">=0.10.0"
}
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/iterator.prototype": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
@@ -13059,6 +13241,35 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/markdown-extensions": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz",
@@ -14461,6 +14672,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mixin-deep": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
@@ -15278,6 +15499,33 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "11.2.7",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
"integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
@@ -18144,6 +18392,60 @@
"node": ">=6"
}
},
"node_modules/test-exclude": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz",
"integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^13.0.6",
"minimatch": "^10.2.2"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/test-exclude/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/test-exclude/node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/test-exclude/node_modules/minimatch": {
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/thread-stream": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
@@ -18902,6 +19204,21 @@
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
"integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
"dev": true,
"license": "ISC",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.12",
"@types/istanbul-lib-coverage": "^2.0.1",
"convert-source-map": "^2.0.0"
},
"engines": {
"node": ">=10.12.0"
}
},
"node_modules/v8n": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/v8n/-/v8n-1.5.1.tgz",
+6 -2
View File
@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "3.1.1",
"version": "3.1.10",
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
"type": "module",
"bin": {
@@ -72,7 +72,10 @@
"test:protocols:e2e": "node scripts/run-protocol-clients-tests.mjs",
"test:vitest": "vitest run open-sse/mcp-server/__tests__/*.test.ts open-sse/services/autoCombo/__tests__/*.test.ts",
"test:ecosystem": "node scripts/run-ecosystem-tests.mjs",
"test:coverage": "npx c8 --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.mjs",
"test:coverage": "c8 --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 55 --lines 55 --functions 55 --branches 60 node --import tsx/esm --test tests/unit/*.test.mjs",
"test:coverage:legacy": "c8 --exclude=open-sse --check-coverage --lines 50 --functions 50 --branches 50 node --import tsx/esm --test tests/unit/*.test.mjs",
"coverage:report": "c8 report --exclude=tests/** --exclude=**/*.test.* --reporter=text --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov",
"coverage:report:legacy": "c8 report --exclude=open-sse --reporter=text --reporter=text-summary",
"test:all": "npm run test:unit && npm run test:vitest && npm run test:ecosystem && npm run test:e2e",
"check": "npm run lint && npm run test",
"prepublishOnly": "npm run build:cli",
@@ -124,6 +127,7 @@
"@types/node": "^25.2.3",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"c8": "^11.0.0",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"eslint": "^9.39.2",
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#4A90E2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
<path d="M8 14s1.5 2 4 2 4-2 4-2"/>
<line x1="9" y1="9" x2="9.01" y2="9"/>
<line x1="15" y1="9" x2="15.01" y2="9"/>
</svg>

After

Width:  |  Height:  |  Size: 364 B

+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""
Translation check script for OmniRoute.
Checks if all translation keys used in code exist in en.json.
Usage:
python scripts/check_translations.py
python scripts/check_translations.py --verbose
python scripts/check_translations.py --fix
"""
import json
import re
import os
import sys
import argparse
def get_namespaces_in_code(src_dir='src'):
"""Find all namespaces used in code via useTranslations()."""
used_ns = set()
for root, dirs, files in os.walk(src_dir):
for f in files:
if not (f.endswith('.tsx') or f.endswith('.ts')):
continue
try:
content = open(os.path.join(root, f), 'r', encoding='utf-8').read()
matches = re.findall(r'useTranslations\(["\']+([^"\']+)["\']+\)', content)
used_ns.update(matches)
except (IOError, UnicodeDecodeError) as e:
print(f"Warning: could not process file {os.path.join(root, f)}: {e}", file=sys.stderr)
return used_ns
def get_keys_in_json(json_path):
"""Get all keys (including nested) from a JSON file."""
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
keys = set()
def traverse(obj, prefix=''):
if isinstance(obj, dict):
for k, v in obj.items():
key = f"{prefix}.{k}" if prefix else k
keys.add(key)
if isinstance(v, dict):
traverse(v, key)
elif isinstance(obj, list):
for item in obj:
traverse(item, prefix)
traverse(data)
return keys
def check_translations(src_dir='src', en_json_path='src/i18n/messages/en.json', verbose=False):
"""Check if all translation keys used in code exist in en.json."""
# Get namespaces used in code
used_ns = get_namespaces_in_code(src_dir)
# Get namespaces in en.json
with open(en_json_path, 'r', encoding='utf-8') as f:
en_data = json.load(f)
en_ns = set(en_data.keys())
# Find missing namespaces
missing_ns = sorted(used_ns - en_ns)
# Get all keys from en.json
en_keys = get_keys_in_json(en_json_path)
# Get all keys used in code
used_keys = set()
for root, dirs, files in os.walk(src_dir):
for f in files:
if not (f.endswith('.tsx') or f.endswith('.ts')):
continue
try:
content = open(os.path.join(root, f), 'r', encoding='utf-8').read()
matches = re.findall(r't\([\'"]+([^\'")]+)[\'"]+\)', content)
used_keys.update(matches)
except (IOError, UnicodeDecodeError) as e:
print(f"Warning: could not process file {os.path.join(root, f)}: {e}", file=sys.stderr)
# Filter out non-translation keys
# Note: check if key IS a path or ends with extension, not just contains it
# e.g., "invoice.ts.description" (ts = timestamp) should NOT be filtered
# but "components/Button.tsx" or "utils.ts" should be
def is_likely_file_path(key: str) -> bool:
if key.endswith('.ts') or key.endswith('.tsx') or key.endswith('.js') or key.endswith('.json'):
return True
if '/.ts' in key or '/.tsx' in key or '/.js' in key or '/.json' in key:
return True
if '\\.ts' in key or '\\.tsx' in key or '\\.js' in key or '\\.json' in key:
return True
return False
filtered = {k for k in used_keys if len(k) > 1
and not any(x in k for x in ['../', '@', '\\', '#', '?'])
and not is_likely_file_path(k)
and k not in ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH']
and not k.startswith('#')
and k not in [',', '-', '.', ':', '?', ' ', '']}
# Find missing keys
real_missing = []
for k in filtered:
if k in en_keys:
continue
# Check if key matches any full key in en.json
matched = False
for ek in en_keys:
if k in ek or ek.endswith(k):
matched = True
break
if matched:
continue
# Filter out paths and obvious non-translations
if k.startswith('./') or k.startswith('/') or k.startswith('x-') or k.startswith('user-'):
continue
# Use same logic - check if key IS a path or ends with extension
if is_likely_file_path(k):
continue
if k in ['Authorization', 'Content-Disposition', 'IOPlatformUUID', 'REG_SZ']:
continue
real_missing.append(k)
return missing_ns, sorted(real_missing)
def main():
parser = argparse.ArgumentParser(description='Check translation keys in en.json')
parser.add_argument('--verbose', '-v', action='store_true', help='Show detailed output')
parser.add_argument('--fix', action='store_true', help='Generate fix suggestions')
parser.add_argument('--src', default='src', help='Source directory')
parser.add_argument('--json', default='src/i18n/messages/en.json', help='Path to en.json')
args = parser.parse_args()
missing_ns, missing_keys = check_translations(args.src, args.json, args.verbose)
has_issues = False
if missing_ns:
has_issues = True
print("=== MISSING NAMESPACES ===")
for ns in missing_ns:
print(f" - {ns}")
if args.fix:
print(f" → Add '{ns}' section to en.json")
if missing_keys:
has_issues = True
print("\n=== MISSING TRANSLATION KEYS ===")
for k in missing_keys:
print(f" - {k}")
if args.fix:
print(f" → Add to appropriate namespace in en.json")
if not has_issues:
print("✓ All translation namespaces and keys are present in en.json")
sys.exit(0)
else:
print(f"\nTotal: {len(missing_ns)} namespace(s), {len(missing_keys)} key(s) missing")
sys.exit(1)
if __name__ == '__main__':
main()
+36 -6
View File
@@ -109,6 +109,15 @@ const LOCALE_SPECS = [
readmeName: "ไทย",
docsName: "ไทย",
},
{
code: "tr",
googleTl: "tr",
label: "TR",
flag: "🇹🇷",
languageName: "Türkçe",
readmeName: "Türkçe",
docsName: "Türkçe",
},
{
code: "uk-UA",
googleTl: "uk",
@@ -608,6 +617,9 @@ function collectStringLeaves(node, pathSoFar = [], output = []) {
function setByPath(target, pathTokens, value) {
let current = target;
for (let i = 0; i < pathTokens.length - 1; i += 1) {
if (current[pathTokens[i]] === undefined) {
current[pathTokens[i]] = typeof pathTokens[i + 1] === "number" ? [] : {};
}
current = current[pathTokens[i]];
}
current[pathTokens[pathTokens.length - 1]] = value;
@@ -728,25 +740,43 @@ async function generateMessageTranslations() {
const sourceJson = JSON.parse(sourceRaw);
const leaves = collectStringLeaves(sourceJson);
const sourceValues = leaves.map((entry) => entry.value);
for (const spec of LOCALE_SPECS) {
if (spec.code === "en" || spec.code === "pt-BR") {
if (spec.code === "en") {
continue;
}
const targetPath = path.join(MESSAGES_DIR, `${spec.code}.json`);
let targetJson = {};
if (await fileExists(targetPath)) {
console.log(`[messages] Skipping ${spec.code} (already exists).`);
const targetRaw = await fs.readFile(targetPath, "utf8");
try {
targetJson = JSON.parse(targetRaw);
} catch (e) {
console.warn(`[messages] Failed to parse ${spec.code}.json`);
}
}
const missingLeaves = leaves.filter((leaf) => {
let current = targetJson;
for (const token of leaf.path) {
if (current === undefined || current === null) return true;
current = current[token];
}
return current === undefined || current === null || current === "";
});
if (missingLeaves.length === 0) {
console.log(`[messages] ${spec.code} is up-to-date.`);
continue;
}
console.log(`[messages] Translating ${spec.code}...`);
console.log(`[messages] Translating ${missingLeaves.length} missing keys for ${spec.code}...`);
const sourceValues = missingLeaves.map((entry) => entry.value);
const translatedValues = await translateStrings(sourceValues, spec.googleTl);
const targetJson = structuredClone(sourceJson);
translatedValues.forEach((value, index) => {
setByPath(targetJson, leaves[index].path, value);
setByPath(targetJson, missingLeaves[index].path, value);
});
await fs.writeFile(targetPath, `${JSON.stringify(targetJson, null, 2)}\n`, "utf8");
+1 -1
View File
@@ -45,7 +45,7 @@ async function main() {
const vitestProcess = spawn(
process.execPath,
["./node_modules/vitest/vitest.mjs", "run", "tests/e2e/protocol-clients.test.ts"],
["./node_modules/vitest/vitest.mjs", "run", "tests/e2e/protocol-clients.test.ts", "--dir", "tests"],
{
stdio: "inherit",
env: testEnv,
+2 -1
View File
@@ -15,7 +15,7 @@
*/
import { execSync } from "child_process";
import { readFileSync, writeFileSync, existsSync } from "fs";
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import os from "os";
@@ -162,6 +162,7 @@ const outFile = outArg
const outPath = join(ROOT, outFile);
mkdirSync(dirname(outPath), { recursive: true });
writeFileSync(outPath, report);
console.log(report);
console.log(`\n✅ Report saved to: ${outPath}`);
+584
View File
@@ -0,0 +1,584 @@
#!/usr/bin/env python3
"""
OmniRoute i18n Translation Validator
Script for comparing source (en.json) with any translation
Detects missing translations and source changes needing updates
Usage:
python validate_translation.py # Uses TRANSLATION_LANG env or --lang argument
python validate_translation.py --lang cs # Validate Czech (cs.json)
python validate_translation.py -l de # Validate German (de.json)
TRANSLATION_LANG=fr python validate_translation.py # Validate French
Environment variables:
TRANSLATION_LANG Target language code (e.g., cs, de, fr)
"""
import json
import sys
import os
from pathlib import Path
from typing import Dict, List, Set, Tuple, Any
import argparse
# Colors (ANSI)
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
NC = '\033[0m'
# Configuration - find repo root relative to this script
_script_dir = Path(__file__).parent.resolve()
# If script is in scripts/ subfolder, go up one level to repo root
if _script_dir.name == "scripts":
SCRIPT_DIR = _script_dir.parent
else:
SCRIPT_DIR = _script_dir
MESSAGES_DIR = SCRIPT_DIR / "src" / "i18n" / "messages"
SOURCE_FILE = MESSAGES_DIR / "en.json"
# Get target language from env or argument
def get_target_lang() -> str:
"""Get target language from ENV or CLI argument."""
# First check environment variable
env_lang = os.environ.get('TRANSLATION_LANG')
if env_lang:
return env_lang
# Then check command line argument (will be set in main)
if hasattr(get_target_lang, 'cli_lang'):
return get_target_lang.cli_lang
# Default to cs for backwards compatibility
return "cs"
# Keys that should NOT be translated (technical terms, proper names, etc.)
UNTRANSLATABLE_KEYS = {
# ICU/Plural formats
"apiManager.modelsCount",
# Technical/Protocol names
"a2aDashboard.metadata",
"a2aDashboard.ok",
"a2aDashboard.url",
"cliTools.baseUrlPlaceholder",
"cliTools.platforms",
"cliTools.toolDescriptions.claude",
"cliTools.toolDescriptions.codex",
"cliTools.toolDescriptions.cursor",
"combos.roundRobin",
"common.model",
"docs.clientCherryStudioTitle",
"docs.clientClaudeTitle",
"docs.clientCursorTitle",
"docs.github",
"docs.protocolA2aTitle",
"docs.protocolMcpTitle",
"endpoint.chat",
"endpoint.chatCompletions",
"endpoint.cloudProxy",
"endpoint.mcpCardTitle",
"endpoint.rerank",
"header.a2a",
"header.mcp",
"health.cpu",
"health.latencyP50",
"health.latencyP95",
"health.latencyP99",
"health.millisecondsShort",
"health.notAvailable",
"health.ok",
"home.aliasLabel",
"home.oauthLabel",
"landing.brandName",
"landing.flowProviderAnthropic",
"landing.flowProviderGemini",
"landing.flowProviderGithubCopilot",
"landing.flowProviderOpenAI",
"landing.flowToolClaudeCode",
"landing.flowToolCline",
"landing.flowToolCursor",
"landing.flowToolOpenAICodex",
"landing.github",
"legal.terms",
"legal.privacy",
"logs.endpoint",
"logs.proxy",
"logs.console",
"logs.request",
"logs.audit",
"media.interpolation",
"media.upscale",
"media.samples",
"search.search",
"search.searchTools",
"search.webSearch",
"search.fileSearch",
"settings.theme",
"settings.language",
"settings.currency",
"settings.timezone",
"stats.requests",
"stats.tokens",
"stats.latency",
"stats.errors",
"themesPage.dark",
"themesPage.light",
"themesPage.system",
"translator.translate",
"translator.translateFrom",
"translator.translateTo",
"translator.detect",
"translator.detectedLanguage",
"usage.totalRequests",
"usage.totalTokens",
"usage.inputTokens",
"usage.outputTokens",
"usage.promptTokens",
"usage.completionTokens",
"usage.cacheReadTokens",
"usage.cacheWriteTokens",
}
def print_header(msg: str) -> None:
print(f"\n{BLUE}{'='*50}{NC}")
print(f"{BLUE}{msg}{NC}")
print(f"{BLUE}{'='*50}{NC}")
def print_success(msg: str) -> None:
print(f"{GREEN}{msg}{NC}")
def print_warning(msg: str) -> None:
print(f"{YELLOW}{msg}{NC}")
def print_error(msg: str) -> None:
print(f"{RED}{msg}{NC}")
def load_json(path: Path) -> Dict:
"""Load JSON file"""
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError as e:
print_error(f"Invalid JSON in {path}: {e}")
sys.exit(1)
def get_all_keys(obj: Any, prefix: str = "") -> Set[str]:
"""Recursively get all leaf keys from JSON object"""
keys = set()
if isinstance(obj, dict):
for key, value in obj.items():
new_prefix = f"{prefix}.{key}" if prefix else key
if isinstance(value, dict):
keys.update(get_all_keys(value, new_prefix))
elif isinstance(value, list):
# Handle arrays - check first element for structure
if value and isinstance(value[0], dict):
for i, item in enumerate(value):
keys.update(get_all_keys(item, f"{new_prefix}[{i}]"))
else:
keys.add(new_prefix)
else:
keys.add(new_prefix)
return keys
def find_missing_keys(source: Dict, trans: Dict) -> Set[str]:
"""Keys in source but not in translation"""
source_keys = get_all_keys(source)
trans_keys = get_all_keys(trans)
return source_keys - trans_keys
def find_extra_keys(source: Dict, trans: Dict) -> Set[str]:
"""Keys in translation but not in source"""
source_keys = get_all_keys(source)
trans_keys = get_all_keys(trans)
return trans_keys - source_keys
def get_value_by_path(obj: Dict, path: str) -> Any:
"""Get value from nested dict using dot notation"""
keys = path.replace('[', '.').replace(']', '').split('.')
current = obj
for key in keys:
if key.isdigit():
idx = int(key)
if isinstance(current, list) and idx < len(current):
current = current[idx]
else:
return None
else:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return None
return current
def find_untranslated(source: Dict, trans: Dict) -> Set[str]:
"""Keys where source value equals translation (not translated), excluding untranslatable keys"""
source_keys = get_all_keys(source)
untranslated = set()
for key in source_keys:
# Skip keys that are in the untranslatable list
if key in UNTRANSLATABLE_KEYS:
continue
source_val = get_value_by_path(source, key)
trans_val = get_value_by_path(trans, key)
if source_val is not None and source_val == trans_val:
untranslated.add(key)
return untranslated
def compare_category(source: Dict, trans: Dict, category: str) -> Tuple[bool, List[str]]:
"""Compare a specific category, return (complete, missing_keys)"""
if category not in source:
return False, [f"Category '{category}' not in source"]
if category not in trans:
return False, [f"Category '{category}' missing in translation"]
source_keys = get_all_keys(source[category])
trans_keys = get_all_keys(trans[category])
missing = source_keys - trans_keys
return len(missing) == 0, list(missing)
def get_translation_file() -> Path:
"""Get the translation file path based on target language."""
lang = get_target_lang()
return MESSAGES_DIR / f"{lang}.json"
def generate_report():
"""Generate full translation report"""
translation_file = get_translation_file()
print_header("OmniRoute Translation Report")
print(f"Source: {SOURCE_FILE}")
print(f"Translation: {translation_file}\n")
source = load_json(SOURCE_FILE)
trans = load_json(translation_file)
# Count keys
source_count = len(get_all_keys(source))
trans_count = len(get_all_keys(trans))
print(f"{BLUE}Key Statistics:{NC}")
print(f" Source keys: {source_count}")
print(f" Translation keys: {trans_count}\n")
# Missing keys
print_header("Missing Translations")
missing = find_missing_keys(source, trans)
if missing:
print(f"{RED}Found {len(missing)} missing keys:{NC}")
for key in sorted(missing)[:50]: # Limit output
print(f" - {key}")
if len(missing) > 50:
print(f" ... and {len(missing) - 50} more")
else:
print_success("No missing translations!")
# Extra keys
print_header("Extra Keys")
extra = find_extra_keys(source, trans)
if extra:
print(f"{YELLOW}Found {len(extra)} extra keys:{NC}")
for key in sorted(extra)[:50]:
print(f" - {key}")
else:
print_success("No extra keys!")
# Untranslated
print_header("Untranslated Keys (same as source)")
untranslated = find_untranslated(source, trans)
if untranslated:
print(f"{YELLOW}Found {len(untranslated)} untranslated keys:{NC}")
for key in sorted(untranslated)[:50]:
print(f" - {key}")
if len(untranslated) > 50:
print(f" ... and {len(untranslated) - 50} more")
else:
print_success("All keys appear to be translated!")
# Per-category status
print_header("Per-Category Status")
for category in sorted(source.keys()):
complete, missing = compare_category(source, trans, category)
if complete:
print_success(f"{category} - complete")
else:
print_error(f"{category} - missing {len(missing)} keys")
# Summary
print_header("Summary")
if not missing and not extra and not untranslated:
print(f"{GREEN}🎉 Translation is fully synchronized!{NC}")
return 0
else:
print(f"{RED}Translation needs attention:{NC}")
print(f" - Missing: {len(missing)}")
print(f" - Extra: {len(extra)}")
print(f" - Untranslated: {len(untranslated)}")
return 1
def quick_check() -> int:
"""Quick check - just show counts"""
translation_file = get_translation_file()
source = load_json(SOURCE_FILE)
trans = load_json(translation_file)
missing = find_missing_keys(source, trans)
untranslated = find_untranslated(source, trans)
print(f"Missing: {len(missing)}")
print(f"Untranslated: {len(untranslated)}")
return 0 if not missing and not untranslated else 1
def show_diff(category: str) -> int:
"""Show detailed diff for a category"""
translation_file = get_translation_file()
source = load_json(SOURCE_FILE)
trans = load_json(translation_file)
if category not in source:
print_error(f"Category '{category}' not found in source")
print("Available categories:")
for cat in sorted(source.keys()):
print(f" - {cat}")
return 1
print_header(f"Diff for category: {category}")
print(f"{BLUE}{'Key':<30} | {'Source':<25} | {'Translation':<25}{NC}")
print("-" * 85)
source_keys = get_all_keys(source[category])
for key in sorted(source_keys):
source_val = get_value_by_path(source[category], key)
trans_val = get_value_by_path(trans.get(category, {}), key)
# Truncate long values
source_str = str(source_val)[:25] if source_val else "(null)"
trans_str = str(trans_val)[:25] if trans_val else "(missing)"
if source_val == trans_val:
status = f"{YELLOW}(same){NC}"
elif trans_val is None:
status = f"{RED}(missing){NC}"
else:
status = f"{GREEN}(ok){NC}"
print(f"{key:<30} | {source_str:<25} | {trans_str:<25} {status}")
return 0
def export_csv(output_file: str) -> int:
"""Export to CSV"""
translation_file = get_translation_file()
source = load_json(SOURCE_FILE)
trans = load_json(translation_file)
print_header(f"Exporting to CSV: {output_file}")
source_keys = get_all_keys(source)
with open(output_file, 'w', encoding='utf-8') as f:
f.write("key,source_value,translation_value,status\n")
for key in sorted(source_keys):
source_val = get_value_by_path(source, key)
trans_val = get_value_by_path(trans, key)
# Escape commas
source_str = str(source_val).replace(',', ';')
trans_str = str(trans_val).replace(',', ';') if trans_val else ""
if trans_val is None:
status = "MISSING"
elif source_val == trans_val:
status = "UNTRANSLATED"
else:
status = "OK"
f.write(f'"{key}","{source_str}","{trans_str}",{status}\n')
print_success(f"Exported to {output_file}")
return 0
def export_markdown(output_file: str) -> int:
"""Export all keys to separate Markdown files - translated and untranslated"""
translation_file = get_translation_file()
source = load_json(SOURCE_FILE)
trans = load_json(translation_file)
print_header(f"Exporting to Markdown: {output_file}")
source_keys = get_all_keys(source)
missing = find_missing_keys(source, trans)
untranslated = find_untranslated(source, trans)
# Separate translated and untranslated
translated_keys = []
untranslated_sorted = sorted(untranslated)
for key in sorted(source_keys):
if key not in missing and key not in untranslated:
translated_keys.append(key)
translated_count = len(translated_keys)
untranslated_count = len(untranslated_sorted)
# Export untranslated (main output file)
with open(output_file, 'w', encoding='utf-8') as f:
f.write("# Nepřeložené klíče (Untranslated Keys)\n\n")
f.write(f"Zdroj: `{SOURCE_FILE.name}` | Překlad: `{TRANSLATION_FILE.name}`\n\n")
f.write(f"**Celkem: {untranslated_count} nepreložených klíčů**\n\n")
f.write("| # | Klíč (Key) | Originál | Nepřeloženo |\n")
f.write("|---|------------|----------|------------|\n")
for i, key in enumerate(untranslated_sorted, 1):
source_val = get_value_by_path(source, key)
trans_val = get_value_by_path(trans, key)
source_str = str(source_val).replace('|', '\\|')[:60]
trans_str = str(trans_val).replace('|', '\\|')[:60]
f.write(f"| {i} | `{key}` | {source_str} | {trans_str} |\n")
f.write("\n## Shrnutí (Summary)\n\n")
f.write(f"- Celkem klíčů: {len(source_keys)}\n")
f.write(f"- Chybějících: {len(missing)}\n")
f.write(f"- Nepřeložených: {untranslated_count}\n")
f.write(f"- Přeložených: {translated_count}\n")
# Export translated to separate file
translated_file = output_file.replace('.md', '_translated.md')
translation_filename = translation_file.name
with open(translated_file, 'w', encoding='utf-8') as f:
f.write("# Přeložené klíče (Translated Keys)\n\n")
f.write(f"Zdroj: `{SOURCE_FILE.name}` | Překlad: `{translation_filename}`\n\n")
f.write(f"**Celkem: {translated_count} přeložených klíčů**\n\n")
f.write("| # | Klíč (Key) | Originál | Překlad |\n")
f.write("|---|------------|----------|---------|\n")
for i, key in enumerate(translated_keys, 1):
source_val = get_value_by_path(source, key)
trans_val = get_value_by_path(trans, key)
source_str = str(source_val).replace('|', '\\|')[:40]
trans_str = str(trans_val).replace('|', '\\|')[:40]
f.write(f"| {i} | `{key}` | {source_str} | {trans_str} |\n")
print_success(f"Exported: {output_file} ({untranslated_count} keys)")
print_success(f"Exported: {translated_file} ({translated_count} keys)")
return 0
def usage():
print("""
OmniRoute i18n Translation Validator
Usage: validate_translation.py [command] [options]
Options:
-l, --lang <code> Target language code (e.g., cs, de, fr)
Default: cs or TRANSLATION_LANG env variable
Commands:
(default) Generate full report
quick Quick check - just show counts
diff <category> Show detailed diff for a category
csv [file] Export to CSV (default: translation_report.csv)
md [file] Export to Markdown (default: translation_report.md)
Examples:
python validate_translation.py # Full report (default: cs)
python validate_translation.py --lang de # Validate German
python validate_translation.py -l fr # Validate French
TRANSLATION_LANG=es python validate_translation.py # Validate Spanish
python validate_translation.py quick # Quick status check
python validate_translation.py diff common # Diff common category
python validate_translation.py csv # Export to CSV
python validate_translation.py md # Export to Markdown
""")
def main():
# Parse global arguments first
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-l', '--lang', dest='lang', default=None)
parser.add_argument('command', nargs='?')
parser.add_argument('arg', nargs='?')
# Parse known args only to allow commands to handle their own args
args, _ = parser.parse_known_args()
# Set language from argument or use default
if args.lang:
get_target_lang.cli_lang = args.lang
elif not os.environ.get('TRANSLATION_LANG'):
# Default to cs for backwards compatibility
get_target_lang.cli_lang = "cs"
# Check if translation file exists
translation_file = get_translation_file()
if not translation_file.exists():
print_error(f"Translation file not found: {translation_file}")
print(f"Available languages:")
for f in sorted(MESSAGES_DIR.glob("*.json")):
if f.name != "en.json":
print(f" - {f.stem}")
return 1
# Execute command
if not args.command or args.command in ('help', '--help', '-h'):
return generate_report()
if args.command == "quick":
return quick_check()
elif args.command == "diff":
if not args.arg:
print_error("Please specify category")
usage()
return 1
return show_diff(args.arg)
elif args.command == "csv":
output = args.arg if args.arg else "translation_report.csv"
return export_csv(output)
elif args.command == "md":
output = args.arg if args.arg else "translation_report.md"
return export_markdown(output)
else:
print_error(f"Unknown command: {args.command}")
usage()
return 1
if __name__ == "__main__":
sys.exit(main())
@@ -403,6 +403,10 @@ interface ConnectionRowProps {
proxyHost?: string;
onRefreshToken?: () => void;
isRefreshing?: boolean;
onApplyCodexAuthLocal?: () => void;
isApplyingCodexAuthLocal?: boolean;
onExportCodexAuthFile?: () => void;
isExportingCodexAuthFile?: boolean;
}
interface AddApiKeyModalProps {
@@ -821,6 +825,8 @@ export default function ProviderDetailPage() {
modelCompatOverrides: Array<CompatModelRow & { id: string }>;
}>({ customModels: [], modelCompatOverrides: [] });
const [compatSavingModelId, setCompatSavingModelId] = useState<string | null>(null);
const [applyingCodexAuthId, setApplyingCodexAuthId] = useState<string | null>(null);
const [exportingCodexAuthId, setExportingCodexAuthId] = useState<string | null>(null);
const providerInfo = providerNode
? {
@@ -1248,6 +1254,39 @@ export default function ProviderDetailPage() {
// T12: Manual token refresh
const [refreshingId, setRefreshingId] = useState<string | null>(null);
const parseApiErrorMessage = async (res: Response, fallback: string) => {
const contentType = res.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
const data = await res.json().catch(() => ({}));
if (typeof data?.error === "string" && data.error.trim()) {
return data.error;
}
if (data?.error?.message) {
return data.error.message;
}
}
const text = await res.text().catch(() => "");
return text.trim() || fallback;
};
const getAttachmentFilename = (res: Response, fallback: string) => {
const disposition = res.headers.get("content-disposition") || "";
const utf8Match = disposition.match(/filename\*=UTF-8''([^;]+)/i);
if (utf8Match?.[1]) {
return decodeURIComponent(utf8Match[1]);
}
const plainMatch = disposition.match(/filename="([^"]+)"/i);
if (plainMatch?.[1]) {
return plainMatch[1];
}
return fallback;
};
const handleRefreshToken = async (connectionId: string) => {
if (refreshingId) return;
setRefreshingId(connectionId);
@@ -1268,6 +1307,82 @@ export default function ProviderDetailPage() {
}
};
const handleApplyCodexAuthLocal = async (connectionId: string) => {
if (applyingCodexAuthId) return;
setApplyingCodexAuthId(connectionId);
const defaultSuccess =
typeof t.has === "function" && t.has("codexAuthAppliedLocal")
? t("codexAuthAppliedLocal")
: "Codex auth.json applied locally";
const defaultError =
typeof t.has === "function" && t.has("codexAuthApplyFailed")
? t("codexAuthApplyFailed")
: "Failed to apply Codex auth.json locally";
try {
const res = await fetch(`/api/providers/${connectionId}/codex-auth/apply-local`, {
method: "POST",
});
if (!res.ok) {
notify.error(await parseApiErrorMessage(res, defaultError));
return;
}
notify.success(defaultSuccess);
} catch (error) {
console.error("Error applying Codex auth locally:", error);
notify.error(defaultError);
} finally {
setApplyingCodexAuthId(null);
}
};
const handleExportCodexAuthFile = async (connectionId: string) => {
if (exportingCodexAuthId) return;
setExportingCodexAuthId(connectionId);
const defaultSuccess =
typeof t.has === "function" && t.has("codexAuthExported")
? t("codexAuthExported")
: "Codex auth.json exported";
const defaultError =
typeof t.has === "function" && t.has("codexAuthExportFailed")
? t("codexAuthExportFailed")
: "Failed to export Codex auth.json";
try {
const res = await fetch(`/api/providers/${connectionId}/codex-auth/export`, {
method: "POST",
});
if (!res.ok) {
notify.error(await parseApiErrorMessage(res, defaultError));
return;
}
const blob = await res.blob();
const filename = getAttachmentFilename(res, "codex-auth.json");
const objectUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = objectUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.setTimeout(() => window.URL.revokeObjectURL(objectUrl), 1000);
notify.success(defaultSuccess);
} catch (error) {
console.error("Error exporting Codex auth file:", error);
notify.error(defaultError);
} finally {
setExportingCodexAuthId(null);
}
};
const handleSwapPriority = async (conn1, conn2) => {
if (!conn1 || !conn2) return;
try {
@@ -1553,7 +1668,19 @@ export default function ProviderDetailPage() {
{ method: "DELETE" }
);
if (res.ok) {
// Also delete all aliases that belong to this provider
const aliasEntries = Object.entries(modelAliases).filter(([, model]) =>
(model as string).startsWith(`${providerStorageAlias}/`)
);
await Promise.all(
aliasEntries.map(([alias]) =>
fetch(`/api/models/alias?alias=${encodeURIComponent(alias)}`, {
method: "DELETE",
}).catch(() => {})
)
);
await fetchProviderModelMeta();
await fetchAliases();
notify.success(t("clearAllModelsSuccess"));
} else {
notify.error(t("clearAllModelsFailed"));
@@ -2091,6 +2218,18 @@ export default function ProviderDetailPage() {
onReauth={isOAuth ? () => setShowOAuthModal(true) : undefined}
onRefreshToken={isOAuth ? () => handleRefreshToken(conn.id) : undefined}
isRefreshing={refreshingId === conn.id}
onApplyCodexAuthLocal={
providerId === "codex"
? () => handleApplyCodexAuthLocal(conn.id)
: undefined
}
isApplyingCodexAuthLocal={applyingCodexAuthId === conn.id}
onExportCodexAuthFile={
providerId === "codex"
? () => handleExportCodexAuthFile(conn.id)
: undefined
}
isExportingCodexAuthFile={exportingCodexAuthId === conn.id}
onProxy={() =>
setProxyTarget({
level: "key",
@@ -2182,6 +2321,18 @@ export default function ProviderDetailPage() {
onReauth={isOAuth ? () => setShowOAuthModal(true) : undefined}
onRefreshToken={isOAuth ? () => handleRefreshToken(conn.id) : undefined}
isRefreshing={refreshingId === conn.id}
onApplyCodexAuthLocal={
providerId === "codex"
? () => handleApplyCodexAuthLocal(conn.id)
: undefined
}
isApplyingCodexAuthLocal={applyingCodexAuthId === conn.id}
onExportCodexAuthFile={
providerId === "codex"
? () => handleExportCodexAuthFile(conn.id)
: undefined
}
isExportingCodexAuthFile={exportingCodexAuthId === conn.id}
onProxy={() =>
setProxyTarget({
level: "key",
@@ -3764,11 +3915,23 @@ function ConnectionRow({
proxyHost,
onRefreshToken,
isRefreshing,
onApplyCodexAuthLocal,
isApplyingCodexAuthLocal,
onExportCodexAuthFile,
isExportingCodexAuthFile,
}: ConnectionRowProps) {
const t = useTranslations("providers");
const displayName = isOAuth
? connection.name || connection.email || connection.displayName || t("oauthAccount")
: connection.name;
const applyCodexAuthLabel =
typeof t.has === "function" && t.has("applyCodexAuthLocal")
? t("applyCodexAuthLocal")
: "Apply auth";
const exportCodexAuthLabel =
typeof t.has === "function" && t.has("exportCodexAuthFile")
? t("exportCodexAuthFile")
: "Export auth";
// Use useState + useEffect for impure Date.now() to avoid calling during render
const [isCooldown, setIsCooldown] = useState(false);
@@ -4002,6 +4165,34 @@ function ConnectionRow({
Token
</Button>
)}
{isCodex && onApplyCodexAuthLocal && (
<Button
size="sm"
variant="ghost"
icon="download_done"
loading={isApplyingCodexAuthLocal}
disabled={isApplyingCodexAuthLocal}
onClick={onApplyCodexAuthLocal}
className="!h-7 !px-2 text-xs text-emerald-500 hover:text-emerald-400"
title={applyCodexAuthLabel}
>
{applyCodexAuthLabel}
</Button>
)}
{isCodex && onExportCodexAuthFile && (
<Button
size="sm"
variant="ghost"
icon="download"
loading={isExportingCodexAuthFile}
disabled={isExportingCodexAuthFile}
onClick={onExportCodexAuthFile}
className="!h-7 !px-2 text-xs text-sky-500 hover:text-sky-400"
title={exportCodexAuthLabel}
>
{exportCodexAuthLabel}
</Button>
)}
<Toggle
size="sm"
checked={connection.isActive ?? true}
@@ -4078,6 +4269,10 @@ ConnectionRow.propTypes = {
onEdit: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onReauth: PropTypes.func,
onApplyCodexAuthLocal: PropTypes.func,
isApplyingCodexAuthLocal: PropTypes.bool,
onExportCodexAuthFile: PropTypes.func,
isExportingCodexAuthFile: PropTypes.bool,
};
function AddApiKeyModal({
@@ -18,6 +18,11 @@ export default function SystemStorageTab() {
const [importStatus, setImportStatus] = useState({ type: "", message: "" });
const [confirmImport, setConfirmImport] = useState(false);
const [pendingImportFile, setPendingImportFile] = useState<File | null>(null);
const [maxCallLogs, setMaxCallLogs] = useState(10000);
const [maxCallLogsDraft, setMaxCallLogsDraft] = useState("10000");
const [settingsLoading, setSettingsLoading] = useState(true);
const [maxCallLogsSaving, setMaxCallLogsSaving] = useState(false);
const [maxCallLogsStatus, setMaxCallLogsStatus] = useState({ type: "", message: "" });
const fileInputRef = useRef<HTMLInputElement>(null);
const locale = useLocale();
const t = useTranslations("settings");
@@ -54,6 +59,27 @@ export default function SystemStorageTab() {
}
};
const loadSettings = async () => {
setSettingsLoading(true);
try {
const res = await fetch("/api/settings");
if (!res.ok) return;
const data = await res.json();
const value =
typeof data.maxCallLogs === "number" &&
Number.isInteger(data.maxCallLogs) &&
data.maxCallLogs > 0
? data.maxCallLogs
: 10000;
setMaxCallLogs(value);
setMaxCallLogsDraft(String(value));
} catch (err) {
console.error("Failed to fetch settings:", err);
} finally {
setSettingsLoading(false);
}
};
const handleManualBackup = async () => {
setManualBackupLoading(true);
setManualBackupStatus({ type: "", message: "" });
@@ -119,8 +145,47 @@ export default function SystemStorageTab() {
useEffect(() => {
loadStorageHealth();
loadSettings();
}, []);
const handleSaveMaxCallLogs = async () => {
const parsed = Number.parseInt(maxCallLogsDraft, 10);
if (!Number.isInteger(parsed) || parsed <= 0) {
setMaxCallLogsStatus({
type: "error",
message: "Enter a positive integer for the call log limit.",
});
return;
}
setMaxCallLogsSaving(true);
setMaxCallLogsStatus({ type: "", message: "" });
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ maxCallLogs: parsed }),
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "Failed to save call log limit");
}
setMaxCallLogs(parsed);
setMaxCallLogsDraft(String(parsed));
setMaxCallLogsStatus({
type: "success",
message: "Call log retention limit saved.",
});
} catch (err) {
setMaxCallLogsStatus({
type: "error",
message: (err as Error).message || "Failed to save call log limit",
});
} finally {
setMaxCallLogsSaving(false);
}
};
const handleExport = async () => {
setExportLoading(true);
try {
@@ -276,6 +341,56 @@ export default function SystemStorageTab() {
</div>
</div>
<div className="p-3 rounded-lg bg-bg border border-border mb-4">
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-sm font-medium text-text-main">Call log retention limit</p>
<p className="text-xs text-text-muted">
Keep only the most recent call log entries in SQLite. Older entries are pruned
automatically after each new request log is saved.
</p>
</div>
<Badge variant="default" size="sm">
{maxCallLogs.toLocaleString()}
</Badge>
</div>
<div className="flex flex-wrap items-center gap-2 mt-3">
<input
type="number"
min="1"
step="1"
value={maxCallLogsDraft}
onChange={(e) => setMaxCallLogsDraft(e.target.value)}
disabled={settingsLoading || maxCallLogsSaving}
className="w-40 rounded-lg border border-border bg-bg-secondary px-3 py-2 text-sm text-text-main focus:outline-none focus:ring-1 focus:ring-primary/40"
aria-label="Call log retention limit"
/>
<Button
variant="outline"
size="sm"
onClick={handleSaveMaxCallLogs}
loading={maxCallLogsSaving}
disabled={settingsLoading}
>
Save limit
</Button>
</div>
{maxCallLogsStatus.message && (
<div
className={`mt-3 rounded-lg border px-3 py-2 text-sm ${
maxCallLogsStatus.type === "success"
? "border-green-500/20 bg-green-500/10 text-green-500"
: "border-red-500/20 bg-red-500/10 text-red-500"
}`}
role="alert"
>
{maxCallLogsStatus.message}
</div>
)}
</div>
{/* Export / Import */}
<div className="flex flex-wrap items-center gap-2 mb-4">
<Button variant="outline" size="sm" onClick={handleExport} loading={exportLoading}>
@@ -0,0 +1,41 @@
import { NextResponse } from "next/server";
import { ensureCliConfigWriteAllowed } from "@/shared/services/cliRuntime";
import { CodexAuthFileError, writeCodexAuthFileToLocalCli } from "@/lib/oauth/utils/codexAuthFile";
function toErrorResponse(error: unknown) {
if (error instanceof CodexAuthFileError) {
return NextResponse.json(
{
error: error.message,
code: error.code,
},
{ status: error.status }
);
}
const message = error instanceof Error ? error.message : "Failed to apply Codex auth file";
return NextResponse.json({ error: message }, { status: 500 });
}
export async function POST(_request: Request, { params }: { params: Promise<{ id: string }> }) {
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {
return NextResponse.json({ error: writeGuard, code: "writes_disabled" }, { status: 403 });
}
const { id } = await params;
const result = await writeCodexAuthFileToLocalCli(id);
return NextResponse.json({
success: true,
connectionId: id,
connectionLabel: result.connectionLabel,
authPath: result.authPath,
writtenAt: new Date().toISOString(),
});
} catch (error) {
console.error("[Codex Auth Apply] Failed:", error);
return toErrorResponse(error);
}
}
@@ -0,0 +1,37 @@
import { NextResponse } from "next/server";
import { buildCodexAuthFile, CodexAuthFileError } from "@/lib/oauth/utils/codexAuthFile";
function toErrorResponse(error: unknown) {
if (error instanceof CodexAuthFileError) {
return NextResponse.json(
{
error: error.message,
code: error.code,
},
{ status: error.status }
);
}
const message = error instanceof Error ? error.message : "Failed to export Codex auth file";
return NextResponse.json({ error: message }, { status: 500 });
}
export async function POST(_request: Request, { params }: { params: Promise<{ id: string }> }) {
try {
const { id } = await params;
const built = await buildCodexAuthFile(id);
return new Response(built.content, {
status: 200,
headers: {
"Content-Type": "application/json; charset=utf-8",
"Content-Disposition": `attachment; filename="${built.fileName}"`,
"Cache-Control": "no-store, max-age=0",
"X-Content-Type-Options": "nosniff",
},
});
} catch (error) {
console.error("[Codex Auth Export] Failed:", error);
return toErrorResponse(error);
}
}
+35 -8
View File
@@ -5,6 +5,7 @@ import {
isAnthropicCompatibleProvider,
} from "@/shared/constants/providers";
import { PROVIDER_MODELS } from "@/shared/constants/models";
import { getModelIsHidden } from "@/lib/localDb";
type JsonRecord = Record<string, unknown>;
@@ -58,6 +59,8 @@ const STATIC_MODEL_PROVIDERS: Record<string, () => Array<{ id: string; name: str
antigravity: () => [
{ id: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 Thinking" },
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
@@ -140,7 +143,7 @@ const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> = {
})),
},
qwen: {
url: "https://portal.qwen.ai/v1/models",
url: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
@@ -317,14 +320,31 @@ const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> = {
authPrefix: "Bearer ",
parseResponse: (data) => data.data || data.models || [],
},
"opencode-zen": {
url: "https://opencode.ai/zen/v1/models",
method: "GET",
headers: { "Content-Type": "application/json" },
authHeader: "Authorization",
authPrefix: "Bearer ",
parseResponse: (data) => data.data || data.models || [],
},
};
/**
* GET /api/providers/[id]/models - Get models list from provider
*/
export async function GET(request, { params }) {
export async function GET(
request: Request,
context: { params: Promise<{ id: string }> | { id: string } }
) {
try {
const { id } = await params;
const params = await context.params;
const { id } = params;
// Check if we should exclude hidden models (used by MCP tools to prevent hidden model leaks)
const { searchParams } = new URL(request.url);
const excludeHidden = searchParams.get("excludeHidden") === "true";
const connection = await getProviderConnectionById(id);
if (!connection) {
@@ -339,6 +359,13 @@ export async function GET(request, { params }) {
return NextResponse.json({ error: "Invalid connection provider" }, { status: 400 });
}
const buildResponse = (payload: any, statusConfig?: ResponseInit) => {
if (excludeHidden && payload.models && Array.isArray(payload.models)) {
payload.models = payload.models.filter((m: any) => !getModelIsHidden(provider, m.id));
}
return NextResponse.json(payload, statusConfig);
};
const connectionId = typeof connection.id === "string" ? connection.id : id;
const apiKey = typeof connection.apiKey === "string" ? connection.apiKey : "";
const accessToken = typeof connection.accessToken === "string" ? connection.accessToken : "";
@@ -423,7 +450,7 @@ export async function GET(request, { params }) {
? "local_catalog"
: "api";
return NextResponse.json({
return buildResponse({
provider,
connectionId,
models,
@@ -435,7 +462,7 @@ export async function GET(request, { params }) {
}
if (provider === "claude") {
return NextResponse.json({
return buildResponse({
provider,
connectionId,
models: STATIC_MODEL_PROVIDERS.claude(),
@@ -480,7 +507,7 @@ export async function GET(request, { params }) {
const data = await response.json();
const models = data.data || data.models || [];
return NextResponse.json({
return buildResponse({
provider,
connectionId,
models,
@@ -493,7 +520,7 @@ export async function GET(request, { params }) {
? STATIC_MODEL_PROVIDERS[provider as keyof typeof STATIC_MODEL_PROVIDERS]
: undefined;
if (staticModelsFn) {
return NextResponse.json({
return buildResponse({
provider,
connectionId,
models: staticModelsFn(),
@@ -559,7 +586,7 @@ export async function GET(request, { params }) {
const data = await response.json();
const models = config.parseResponse(data);
return NextResponse.json({
return buildResponse({
provider,
connectionId,
models,
+1 -1
View File
@@ -59,7 +59,7 @@ const OAUTH_TEST_CONFIG = {
refreshable: true,
},
qwen: {
// portal.qwen.ai/v1/models returns 404 — endpoint no longer exists.
// DashScope (previously portal.qwen.ai) /v1/models might return 404 or auth issues.
// Use checkExpiry instead — actual connectivity is validated via real requests.
checkExpiry: true,
refreshable: true,
+5
View File
@@ -114,6 +114,11 @@ export async function PATCH(request) {
setCliCompatProviders(body.cliCompatProviders || []);
}
if ("maxCallLogs" in body) {
const { invalidateCallLogsMaxCache } = await import("@/lib/usage/callLogs");
invalidateCallLogsMaxCache();
}
const { password, ...safeSettings } = settings;
return NextResponse.json(safeSettings);
} catch (error) {
+5 -1
View File
@@ -7,6 +7,7 @@ import {
getAllCustomModels,
getSettings,
getProviderNodes,
getModelIsHidden,
} from "@/lib/localDb";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { getAllEmbeddingModels } from "@omniroute/open-sse/config/embeddingRegistry.ts";
@@ -223,7 +224,7 @@ export async function getUnifiedModelsResponse(
// Add combos first (they appear at the top) — only active ones
for (const combo of combos) {
if (combo.isActive === false) continue;
if (combo.isActive === false || combo.isHidden === true) continue;
models.push({
id: combo.name,
object: "model",
@@ -255,6 +256,8 @@ export async function getUnifiedModelsResponse(
for (const model of providerModels) {
const aliasId = `${alias}/${model.id}`;
if (getModelIsHidden(canonicalProviderId, model.id)) continue;
const visionFields =
getVisionCapabilityFields(aliasId) || getVisionCapabilityFields(model.id);
// Model-level context length overrides provider default
@@ -416,6 +419,7 @@ export async function getUnifiedModelsResponse(
for (const model of providerCustomModels) {
const modelId = typeof model.id === "string" ? model.id : null;
if (!modelId) continue;
if (model.isHidden === true) continue;
// Skip if already added as built-in
const aliasId = `${alias}/${modelId}`;
+1 -1
View File
@@ -219,7 +219,7 @@ body {
/* Material Symbols */
.material-symbols-outlined {
font-family: "Material Symbols Outlined", sans-serif;
font-family: "Material Symbols Outlined", sans-serif !important;
font-weight: normal;
font-style: normal;
font-size: 24px;
+2
View File
@@ -27,6 +27,7 @@ export const LOCALES = [
"sk",
"sv",
"th",
"tr",
"uk-UA",
"vi",
"zh-CN",
@@ -68,6 +69,7 @@ export const LANGUAGES: readonly {
{ code: "sk", label: "SK", name: "Slovenčina", flag: "🇸🇰" },
{ code: "sv", label: "SV", name: "Svenska", flag: "🇸🇪" },
{ code: "th", label: "TH", name: "ไทย", flag: "🇹🇭" },
{ code: "tr", label: "TR", name: "Türkçe", flag: "🇹🇷" },
{ code: "uk-UA", label: "UK-UA", name: "Українська", flag: "🇺🇦" },
{ code: "vi", label: "VI", name: "Tiếng Việt", flag: "🇻🇳" },
{ code: "zh-CN", label: "ZH-CN", name: "中文 (简体)", flag: "🇨🇳" },
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "مجاني",
"skipToContent": "انتقل إلى المحتوى",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "قبول",
"accountId": "معرف الحساب",
"alias": "الاسم المستعار",
"apiKeyId": "معرف مفتاح واجهة برمجة التطبيقات",
"apiKeyName": "اسم مفتاح واجهة برمجة التطبيقات",
"apiKeySecret": "سر مفتاح API",
"authorization": "إذن",
"content-type": "نوع المحتوى",
"content-length": "طول المحتوى",
"cookie": "ملف تعريف الارتباط",
"file": "ملف",
"host": "المضيف",
"id": "معرف",
"import": "استيراد",
"limit": "الحد",
"offset": "إزاحة",
"open": "مفتوح",
"origin": "الأصل",
"promptTokens": "الرموز الفورية",
"completionTokens": "رموز الإنجاز",
"totalTokens": "مجموع الرموز",
"rawModel": "النموذج الخام",
"scope": "النطاق",
"skill": "مهارة",
"sortBy": "فرز حسب",
"sortOrder": "ترتيب الفرز",
"tab": "علامة التبويب",
"text": "نص",
"textarea": "منطقة النص",
"tool": "أداة",
"toolId": "معرف الأداة",
"web": "ويب",
"whereUsed": "حيث تستخدم",
"whitelist": "القائمة البيضاء",
"blacklist": "القائمة السوداء",
"resolve": "حل",
"force": "القوة",
"base64url": "عنوان URL Base64",
"hex": "عرافة",
"range": "النطاق",
"component": "مكون",
"redirect_uri": "إعادة توجيه URI",
"idempotency-key": "مفتاح العجز",
"error_description": "وصف الخطأ",
"code": "الكود",
"compatible": "متوافق",
"chat-completions": "استكمالات الدردشة",
"oauth": "OAuth",
"auth_token": "رمز المصادقة",
"crypto": "تشفير",
"hours": "ساعات",
"selfsigned": "موقعة ذاتيا",
"proxy_id": "معرف الوكيل",
"proxyId": "معرف الوكيل",
"connectionId": "معرف الاتصال",
"resolveConnectionId": "حل معرف الاتصال",
"resolve_connection_id": "حل معرف الاتصال",
"scope_id": "معرف النطاق",
"scopeId": "معرف النطاق",
"jwtSecret": "JWT سر",
"keytar": "كيتار",
"better-sqlite3": "أفضل-sqlite3",
"undici": "undici",
"builder-id": "معرف البناء",
"musicDesc": "وصف الموسيقى",
"musicGeneration": "جيل الموسيقى",
"idc": "آي دي سي",
"cloud-status-changed": "تغيرت حالة السحابة",
"where_used": "حيث تستخدم",
"windowMs": "النافذة (مللي ثانية)",
"social-github": "جيثب",
"social-google": "جوجل",
"TOOL_ALLOWLIST": "القائمة المسموح بها للأداة",
"TOOL_DENYLIST": "قائمة رفض الأداة",
"Failed to save pricing": "فشل حفظ الأسعار",
"Failed to reset pricing": "فشل في إعادة تعيين التسعير",
"apikey": "مفتاح واجهة برمجة التطبيقات",
"http": "HTTP"
},
"sidebar": {
"home": "الصفحة الرئيسية",
@@ -179,7 +257,11 @@
"requestsShort": "{count} طلب",
"providerModelsTitle": "{provider} - النماذج",
"copiedModel": "تم النسخ: {model}",
"aliasLabel": "الاسم المستعار"
"aliasLabel": "الاسم المستعار",
"updateNow": "التحديث الآن",
"updating": "جارٍ التحديث...",
"updateAvailableDesc": "نسخة جديدة متاحة. انقر للتحديث.",
"updateStarted": "بدأ التحديث..."
},
"analytics": {
"title": "التحليلات",
@@ -535,6 +617,9 @@
"title": "إضافة تكوين النموذج",
"desc": "أضف التكوين التالي إلى مجموعة النماذج الخاصة بك:"
}
},
"notes": {
"0": "متابعة يستخدم ملف التكوين JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "يتطلب OpenCode تكوين مفتاح API.",
"1": "قم بتعيين عنوان URL الأساسي إلى نقطة نهاية OmniRoute الخاصة بك."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "يتطلب كيرو حساب أمازون."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "استكمالات الدردشة",
"importingModels": "جارٍ الاستيراد...",
"importFromModels": "الاستيراد من / النماذج",
"clearAllModels": "مسح جميع النماذج",
"clearAllModelsConfirm": "هل أنت متأكد أنك تريد إزالة جميع النماذج لهذا المزود؟ لا يمكن التراجع عن هذا.",
"clearAllModelsSuccess": "تم مسح جميع النماذج",
"clearAllModelsFailed": "فشل في مسح النماذج",
"addConnectionToImport": "أضف اتصالاً لتمكين الاستيراد.",
"noModelsConfigured": "لم يتم تكوين أي نماذج",
"connectionCount": "{count} الاتصال (الاتصالات)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "المزامنة التلقائية",
"autoSyncTooltip": "تحديث قائمة النماذج تلقائيًا كل 24 ساعة (قابلة للتكوين عبر MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "تم تمكين المزامنة التلقائية — سيتم تحديث النماذج بشكل دوري",
"autoSyncDisabled": "تم تعطيل المزامنة التلقائية",
"autoSyncToggleFailed": "فشل في تبديل المزامنة التلقائية",
"clearAllModels": "مسح كافة النماذج",
"clearAllModelsConfirm": "هل أنت متأكد أنك تريد إزالة كافة النماذج لهذا الموفر؟ لا يمكن التراجع عن هذا.",
"clearAllModelsSuccess": "تم مسح جميع النماذج",
"clearAllModelsFailed": "فشل في مسح النماذج"
},
"settings": {
"title": "الإعدادات",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "أعد تشغيل الخادم وسيتم استخدام كلمة المرور الجديدة",
"backToLogin": "العودة إلى تسجيل الدخول",
"forgotPassword": "هل نسيت كلمة المرور؟",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "إذن",
"Content-Disposition": "التصرف في المحتوى",
"waitingForAuthorization": "في انتظار الترخيص...",
"waitingForGoogleAuthorization": "في انتظار إذن Google...",
"waitingForOpenAIAuthorization": "في انتظار ترخيص OpenAI...",
"waitingForAntigravityAuthorization": "في انتظار تصريح مكافحة الجاذبية...",
"waitingForIFlowAuthorization": "في انتظار ترخيص iFlow...",
"exchangingCodeForTokens": "استبدال الكود بالرموز..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "توليد تحويل النص إلى كلام (ElevenLabs، OpenAI TTS).",
"endpointEmbeddingsNote": "إنشاء تضمين النص (OpenAI، Cohere، Voyage)."
},
"legal": {
"privacyPolicy": "سياسة الخصوصية",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "دردشة بسيطة",
"streaming": "الجري",
"system-prompt": "موجه النظام",
"thinking": "التفكير",
"tool-calling": "استدعاء الأداة",
"multi-turn": "متعدد المنعطفات"
},
"templateDescriptions": {
"simple-chat": "قالب الدردشة الأساسي مع رسالة النظام",
"streaming": "نموذج لتدفق الردود",
"system-prompt": "قالب مع موجه النظام المخصص",
"thinking": "قالب مع ميزانية التفكير/الاستدلال",
"tool-calling": "نموذج لاستدعاء الأداة/الوظيفة",
"multi-turn": "قالب للمحادثات متعددة المنعطفات"
},
"templatePayloads": {
"simpleChat": {
"system": "أنت مساعد AI مفيد.",
"userGreeting": "مرحبا! كيف يمكنني مساعدتك اليوم؟"
},
"streaming": {
"prompt": "اكتب قصة عنه"
},
"systemPrompt": {
"question": "ما هو معنى الحياة؟",
"systemInstruction": "تقديم إجابة مدروسة وفلسفية."
},
"thinking": {
"question": "شرح الحوسبة الكمومية"
},
"toolCalling": {
"cityNameDescription": "اسم المدينة لمعرفة الطقس لها",
"toolDescription": "الحصول على الطقس الحالي لموقع ما",
"userWeather": "ما هو الطقس في طوكيو ؟"
},
"multiTurn": {
"system": "أنت مساعد مفيد.",
"assistantExample": "سأكون سعيدًا بمساعدتك في ذلك.",
"userInitial": "انا بحاجة الى مساعدة مع",
"userFollowUp": "هل يمكنك توضيح ذلك؟"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "безплатно",
"skipToContent": "Преминете към съдържанието",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Приеми",
"accountId": "ID на акаунта",
"alias": "Псевдоним",
"apiKeyId": "ID на API ключ",
"apiKeyName": "Име на API ключ",
"apiKeySecret": "API Key Secret",
"authorization": "Упълномощаване",
"content-type": "Тип съдържание",
"content-length": "Дължина на съдържанието",
"cookie": "бисквитка",
"file": "Файл",
"host": "Домакин",
"id": "ID",
"import": "Импортиране",
"limit": "Лимит",
"offset": "Офсет",
"open": "Отворете",
"origin": "Произход",
"promptTokens": "Подкани токени",
"completionTokens": "Токени за завършване",
"totalTokens": "Общо токени",
"rawModel": "Суров модел",
"scope": "Обхват",
"skill": "Умение",
"sortBy": "Сортиране по",
"sortOrder": "Ред на сортиране",
"tab": "Таб",
"text": "Текст",
"textarea": "Текстово поле",
"tool": "Инструмент",
"toolId": "ID на инструмента",
"web": "Мрежа",
"whereUsed": "Къде се използва",
"whitelist": "Бял списък",
"blacklist": "Черен списък",
"resolve": "Разрешете",
"force": "Сила",
"base64url": "Base64 URL",
"hex": "шестнадесетичен",
"range": "Обхват",
"component": "Компонент",
"redirect_uri": "URI за пренасочване",
"idempotency-key": "Ключ за идемпотентност",
"error_description": "Описание на грешката",
"code": "Код",
"compatible": "Съвместим",
"chat-completions": "Чат завършвания",
"oauth": "OAuth",
"auth_token": "Токен за удостоверяване",
"crypto": "Крипто",
"hours": "часове",
"selfsigned": "Самоподписан",
"proxy_id": "ID на прокси",
"proxyId": "ID на прокси",
"connectionId": "ID на връзката",
"resolveConnectionId": "Разрешете ИД на връзката",
"resolve_connection_id": "Разрешете ИД на връзката",
"scope_id": "ID на обхвата",
"scopeId": "ID на обхвата",
"jwtSecret": "JWT Secret",
"keytar": "Keytar",
"better-sqlite3": "по-добре-sqlite3",
"undici": "undici",
"builder-id": "ID на строителя",
"musicDesc": "Описание на музиката",
"musicGeneration": "Музикално поколение",
"idc": "IDC",
"cloud-status-changed": "Състоянието на облака е променено",
"where_used": "Къде се използва",
"windowMs": "Прозорец (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Списък с разрешени инструменти",
"TOOL_DENYLIST": "Списък за отказ на инструмента",
"Failed to save pricing": "Неуспешно запазване на цената",
"Failed to reset pricing": "Неуспешно нулиране на цените",
"apikey": "API ключ",
"http": "HTTP"
},
"sidebar": {
"home": "Начало",
@@ -179,7 +257,11 @@
"requestsShort": "{count} изискване",
"providerModelsTitle": "{provider} - Модели",
"copiedModel": "Копирано: {model}",
"aliasLabel": "псевдоним"
"aliasLabel": "псевдоним",
"updateNow": "Актуализирайте сега",
"updating": "Актуализиране...",
"updateAvailableDesc": "Налична е нова версия. Кликнете, за да актуализирате.",
"updateStarted": "Актуализацията започна..."
},
"analytics": {
"title": "Анализ",
@@ -535,6 +617,9 @@
"title": "Добавяне на конфигурация на модела",
"desc": "Добавете следната конфигурация към вашия масив от модели:"
}
},
"notes": {
"0": "Продължаване използва JSON конфигурационен файл."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode изисква конфигурация на API ключ.",
"1": "Задайте основния URL адрес на вашата крайна точка OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Киро изисква акаунт в Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Чат завършвания",
"importingModels": "Импортиране...",
"importFromModels": "Импортиране от /models",
"clearAllModels": "Изчисти всички модели",
"clearAllModelsConfirm": "Сигурни ли сте, че искате да премахнете всички модели за този доставчик? Това не може да бъде отменено.",
"clearAllModelsSuccess": "Всички модели са изчистени",
"clearAllModelsFailed": "Неуспешно изчистване на моделите",
"addConnectionToImport": "Добавете връзка, за да активирате импортирането.",
"noModelsConfigured": "Няма конфигурирани модели",
"connectionCount": "{count} връзка(и)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Автоматично синхронизиране",
"autoSyncTooltip": "Автоматично опресняване на списъка с модели на всеки 24 часа (може да се конфигурира чрез MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Автоматичното синхронизиране е активирано — моделите ще се опресняват периодично",
"autoSyncDisabled": "Автоматичното синхронизиране е деактивирано",
"autoSyncToggleFailed": "Неуспешно превключване на автоматичното синхронизиране",
"clearAllModels": "Изчистване на всички модели",
"clearAllModelsConfirm": "Сигурни ли сте, че искате да премахнете всички модели за този доставчик? Това не може да бъде отменено.",
"clearAllModelsSuccess": "Всички модели изчистени",
"clearAllModelsFailed": "Неуспешно изчистване на моделите"
},
"settings": {
"title": "Настройки",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Рестартирайте сървъра - той ще използва новата парола",
"backToLogin": "Назад към Вход",
"forgotPassword": "Забравена парола?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Упълномощаване",
"Content-Disposition": "Съдържание-разположение",
"waitingForAuthorization": "Чака се оторизация...",
"waitingForGoogleAuthorization": "Изчаква се разрешение от Google...",
"waitingForOpenAIAuthorization": "Изчаква се разрешение за OpenAI...",
"waitingForAntigravityAuthorization": "Изчаква се разрешение за антигравитация...",
"waitingForIFlowAuthorization": "Изчаква се разрешение за iFlow...",
"exchangingCodeForTokens": "Размяна на код за токени..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Генериране на текст към реч (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Генериране на вграждане на текст (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Политика за поверителност",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Обикновен чат",
"streaming": "Поточно предаване",
"system-prompt": "Системен ред",
"thinking": "Мислене",
"tool-calling": "Извикване на инструмент",
"multi-turn": "Многооборотен"
},
"templateDescriptions": {
"simple-chat": "Основен шаблон за чат със системно съобщение",
"streaming": "Шаблон за поточно предаване на отговори",
"system-prompt": "Шаблон с персонализирана системна подкана",
"thinking": "Шаблон с бюджет за разсъждения/мислене",
"tool-calling": "Шаблон за извикване на инструмент/функция",
"multi-turn": "Шаблон за многооборотни разговори"
},
"templatePayloads": {
"simpleChat": {
"system": "Вие сте полезен AI помощник.",
"userGreeting": "здравей Как мога да ви помогна днес?"
},
"streaming": {
"prompt": "Напишете история за"
},
"systemPrompt": {
"question": "Какъв е смисълът на живота?",
"systemInstruction": "Дайте обмислен, философски отговор."
},
"thinking": {
"question": "Обяснете квантовите изчисления"
},
"toolCalling": {
"cityNameDescription": "Името на града, за който да получите прогноза за времето",
"toolDescription": "Вземете текущото време за местоположение",
"userWeather": "Какво е времето в Токио?"
},
"multiTurn": {
"system": "Вие сте полезен помощник.",
"assistantExample": "Ще се радвам да ви помогна с това.",
"userInitial": "Имам нужда от помощ за",
"userFollowUp": "Можете ли да разкажете по-подробно за това?"
}
}
}
+175 -19
View File
@@ -42,7 +42,7 @@
"reloadPage": "Znovu načíst stránku",
"connected": "Připojeno",
"disconnected": "Odpojeno",
"notConfigured": "Nenakonfigurováno",
"notConfigured": "Nenastaveno",
"testConnection": "Test připojení",
"enable": "Umožnit",
"disable": "Zakázat",
@@ -58,7 +58,85 @@
"free": "Uvolnit",
"skipToContent": "Přejít na obsah",
"maintenanceServerIssues": "Server má problémy. Některé funkce nemusí být dostupné.",
"maintenanceServerUnreachable": "Server je nedostupný. Připojuji znovu..."
"maintenanceServerUnreachable": "Server je nedostupný. Připojuji znovu...",
"accept": "Přijmout",
"accountId": "ID účtu",
"alias": "Alias",
"apiKeyId": "ID API klíče",
"apiKeyName": "Název API klíče",
"apiKeySecret": "Tajný klíč API",
"authorization": "Autorizace",
"content-type": "Typ obsahu",
"content-length": "Délka obsahu",
"cookie": "Cookie",
"file": "Soubor",
"host": "Hostitel",
"id": "ID",
"import": "Importovat",
"limit": "Limit",
"offset": "Offset",
"open": "Otevřít",
"origin": "Původ",
"promptTokens": "Prompt tokeny",
"completionTokens": "Completion tokeny",
"totalTokens": "Celkem tokenů",
"rawModel": "Raw model",
"scope": "Rozsah",
"skill": "Dovednost",
"sortBy": "Řadit podle",
"sortOrder": "Pořadí",
"tab": "Záložka",
"text": "Text",
"textarea": "Textové pole",
"tool": "Nástroj",
"toolId": "ID nástroje",
"web": "Web",
"whereUsed": "Kde použito",
"whitelist": "Povolený seznam",
"blacklist": "Blokovaný seznam",
"resolve": "Vyřešit",
"force": "Vynutit",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Rozsah",
"component": "Komponenta",
"redirect_uri": "Přesměrovací URI",
"idempotency-key": "Klíč idempotence",
"error_description": "Popis chyby",
"code": "Kód",
"compatible": "Kompatibilní",
"chat-completions": "Chat completions",
"oauth": "OAuth",
"auth_token": "Auth token",
"crypto": "Kryptografie",
"hours": "Hodiny",
"selfsigned": "Self-signed",
"proxy_id": "ID proxy",
"proxyId": "ID proxy",
"connectionId": "ID připojení",
"resolveConnectionId": "Vyřešit ID připojení",
"resolve_connection_id": "Vyřešit ID připojení",
"scope_id": "ID rozsahu",
"scopeId": "ID rozsahu",
"jwtSecret": "JWT tajemství",
"keytar": "Keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "Builder ID",
"musicDesc": "Popis hudby",
"musicGeneration": "Generování hudby",
"idc": "IDC",
"cloud-status-changed": "Změna stavu cloudu",
"where_used": "Kde použito",
"windowMs": "Okno (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Povolené nástroje",
"TOOL_DENYLIST": "Blokované nástroje",
"Failed to save pricing": "Nepodařilo se uložit ceny",
"Failed to reset pricing": "Nepodařilo se resetovat ceny",
"apikey": "API klíč",
"http": "HTTP"
},
"sidebar": {
"home": "Domov",
@@ -163,7 +241,7 @@
"providersOverview": "Přehled poskytovatelů",
"configuredOf": "{configured} nastaveno z {total} dostupných poskytovatelů",
"noModelsAvailable": "Pro tohoto poskytovatele nejsou k dispozici žádné modely.",
"configureFirst": "Nejprve nakonfigurujte připojení v {providers}",
"configureFirst": "Nejprve nastavte připojení v {providers}",
"configureProvider": "Nastavení poskytovatele",
"modelAvailable": "Dostupný model: {count}",
"modelsAvailable": "Dostupné modely: {count}",
@@ -179,7 +257,11 @@
"requestsShort": "{count} požadavků",
"providerModelsTitle": "{provider} - Modely",
"copiedModel": "Zkopírován: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Aktualizovat",
"updating": "Aktualizuji...",
"updateAvailableDesc": "K dispozici je nová verze. Kliknutím aktualizujte.",
"updateStarted": "Aktualizace začala..."
},
"analytics": {
"title": "Analytika",
@@ -325,7 +407,7 @@
"generate": "Generovat",
"generating": "Generuji...",
"loadingModels": "Načítání dostupných modelů...",
"noModels": "Žádné modely k dispozici. Nejprve nakonfigurujte poskytovatele s mediálními funkcemi.",
"noModels": "Žádné modely k dispozici. Nejprve nastavte poskytovatele s mediálními funkcemi.",
"error": "Generování selhalo",
"result": "Výsledky",
"imageDescription": "Generujte obrázky z textových pokynů pomocí OpenAI, xAI, Together, Hyperbolic, SD WebUI, ComfyUI a dalších.",
@@ -343,7 +425,7 @@
"rerankResults": "Rerank výsledky",
"searchHistory": "Historie vyhledávání",
"urlOverlap": "Překrývání URL adres",
"noSearchProviders": "Nejsou nakonfigurováni žádní poskytovatelé vyhledávání. Přidejte poskytovatele v Nastavení.",
"noSearchProviders": "Nejsou nastaveni žádní poskytovatelé vyhledávání. Přidejte poskytovatele v Nastavení.",
"noRerankModels": "Není k dispozici žádný rerank model",
"webSearch": "Vyhledávání na webu",
"provider": "Poskytovatel",
@@ -396,7 +478,7 @@
"baseUrl": "Základní URL",
"apiKey": "API klíč",
"configured": "Nastaveno",
"notConfigured": "Nekonfigurováno",
"notConfigured": "Nenastaveno",
"notInstalled": "Neinstalováno",
"custom": "Vlastní",
"unknown": "Neznámý",
@@ -539,7 +621,7 @@
"codex": "Použijte, pokud váš tým používá OpenAI Codex CLI a autentizaci založenou na profilech.",
"droid": "Použijte, pokud potřebujete odlehčeného agenta terminálu zaměřeného na rychlé kódování a smyčky provádění příkazů.",
"openclaw": "Použijte, pokud chcete kódovacího agenta ve stylu Open Claw, ale směrovaného přes zásady OmniRoute.",
"cline": "Použijte, pokud konfigurujete kódovací agenty uvnitř editorů a chcete návodné nastavení s modely OmniRoute.",
"cline": "Použijte, pokud nastavujete kódovací agenty uvnitř editorů a chcete návodné nastavení s modely OmniRoute.",
"kilo": "Použijte, pokud váš pracovní postup závisí na příkazech Kilo Code a rychlých iterativních úpravách.",
"cursor": "Použijte při kódování v Cursoru, pokud potřebujete vlastní modely kompatibilní s OpenAI přes OmniRoute.",
"continue": "Použijte, pokud spouštíte Continue v IDE a potřebujete přenosnou nastavení poskytovatele založenou na JSON.",
@@ -607,6 +689,9 @@
"title": "Přidat nastavení modelu",
"desc": "Přidejte do pole modely následující nastavení:"
}
},
"notes": {
"0": "Continue používá konfigurační soubor JSON."
}
},
"opencode": {
@@ -625,6 +710,10 @@
"4": {
"title": "Vybrat model"
}
},
"notes": {
"0": "OpenCode vyžaduje nastavení API klíče.",
"1": "Nastavte základní URL na váš OmniRoute endpoint."
}
},
"kiro": {
@@ -643,6 +732,9 @@
"4": {
"title": "Vyberte model"
}
},
"notes": {
"0": "Kiro vyžaduje Amazon účet."
}
}
}
@@ -685,7 +777,7 @@
"more": "+{count} dalších",
"reqs": "požadavky",
"success": "úspěch",
"proxyConfigured": "Proxy nakonfigurována",
"proxyConfigured": "Proxy nastavena",
"copyComboName": "Kopírovat název komba",
"enableCombo": "Povolit kombo",
"disableCombo": "Zakázat kombo",
@@ -959,7 +1051,7 @@
"openA2aDashboard": "Otevřít správu A2A",
"mcpQuickStartTitle": "Rychlý start MCP",
"mcpQuickStartStep1": "Spusťte MCP server pomocí příkazu `omniroute --mcp`.",
"mcpQuickStartStep2": "Nakonfigurujte klienta MCP pro připojení přes transport stdio.",
"mcpQuickStartStep2": "Nastavte klienta MCP pro připojení přes transport stdio.",
"mcpQuickStartStep3": "Spusťte nástroje jako `omniroute_get_health` a `omniroute_list_combos`.",
"a2aQuickStartTitle": "Rychlý start A2A",
"a2aQuickStartStep1": "Kartu agenta najdete na adrese `/.well-known/agent.json`.",
@@ -1303,7 +1395,7 @@
"addAnthropicCompatible": "Přidat Anthropic Kompatibilní",
"addNewProvider": "Přidat nového poskytovatele",
"backToProviders": "Zpět k poskytovatelům",
"configureNewProvider": "Nakonfigurujte nového AI poskytovatele pro použití s vašimi aplikacemi.",
"configureNewProvider": "Nastavte nového AI poskytovatele pro použití s vašimi aplikacemi.",
"providerLabel": "Poskytovatel",
"selectProvider": "Vyberte poskytovatele",
"selectedProvider": "Vybraný poskytovatel",
@@ -1378,6 +1470,10 @@
"chatCompletions": "Chat Completions",
"importingModels": "Importuji...",
"importFromModels": "Import z /models",
"clearAllModels": "Vymazat všechny modely",
"clearAllModelsConfirm": "Opravdu chcete odebrat všechny modely pro tohoto poskytovatele? Tuto akci nelze vrátit.",
"clearAllModelsSuccess": "Všechny modely vymazány",
"clearAllModelsFailed": "Nepodařilo se vymazat modely",
"addConnectionToImport": "Přidejte připojení pro povolení importu.",
"noModelsConfigured": "Žádné nastavené modely",
"connectionCount": "{count} připojení",
@@ -1540,7 +1636,16 @@
"chatPathHint": "Vlastní chat cesta pro poskytovatele s nestandardí API (např. /v4/chat/completions)",
"modelsPathLabel": "Cesta koncového bodu modelu",
"modelsPathPlaceholder": "/models",
"modelsPathHint": "Cesta k vlastním modelům pro validaci (např. /v4/models)"
"modelsPathHint": "Cesta k vlastním modelům pro validaci (např. /v4/models)",
"autoSync": "Automatická synchronizace",
"autoSyncTooltip": "Automaticky obnovuje seznam modelů každých 24 hodin (lze nastavit přes MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automatická synchronizace povolena modely se budou pravidelně obnovovat",
"autoSyncDisabled": "Automatická synchronizace zakázána",
"autoSyncToggleFailed": "Nepodařilo se přepnout automatickou synchronizaci",
"clearAllModels": "Vymazat všechny modely",
"clearAllModelsConfirm": "Opravdu chcete odstranit všechny modely pro tohoto poskytovatele?",
"clearAllModelsSuccess": "Všechny modely vymazány",
"clearAllModelsFailed": "Nepodařilo se vymazat modely"
},
"settings": {
"title": "Nastavení",
@@ -1636,7 +1741,7 @@
"loadingCacheStats": "Načítám statistiky mezipaměti…",
"globalProxy": "Globální Proxy",
"globalProxyDesc": "Nastavte globální odchozí proxy pro všechna API volání. Jednotliví poskytovatelé, komba a klíče mohou toto přepsat.",
"noGlobalProxy": "Není nakonfigurována globální proxy",
"noGlobalProxy": "Není nastavena globální proxy",
"globalLabel": "Globální",
"configure": "Nastavit",
"globalSystemPrompt": "Globální systémový Prompt",
@@ -2328,7 +2433,7 @@
"loading": "Načítám...",
"invalidPassword": "Neplatné heslo",
"errorOccurredRetry": "Došlo k chybě. Zkuste to prosím znovu.",
"configureInstance": "Pojďme nakonfigurovat vaši instanci OmniRoute",
"configureInstance": "Pojďme nastavit vaši instanci OmniRoute",
"runOnboardingWizard": "Spusťte průvodce zavedením, nastavte si heslo a připojte svého prvního AI poskytovatele.",
"startOnboarding": "Začít s nástupem",
"secureYourInstance": "Zabezpečte svou instanci",
@@ -2367,7 +2472,15 @@
"restartServerWithNewPassword": "Restartujte server - použije se nové heslo.",
"backToLogin": "Zpět na přihlášení",
"forgotPassword": "Zapomněli jste heslo?",
"defaultPasswordHint": "Výchozí heslo: 123456 (pokud nebylo nastaveno INITIAL_PASSWORD)"
"defaultPasswordHint": "Výchozí heslo: 123456 (pokud nebylo nastaveno INITIAL_PASSWORD)",
"waitingForAuthorization": "Čekám na autorizaci...",
"waitingForGoogleAuthorization": "Čekám na Google autorizaci...",
"waitingForOpenAIAuthorization": "Čekám na OpenAI autorizaci...",
"waitingForAntigravityAuthorization": "Čekám na Antigravity autorizaci...",
"waitingForIFlowAuthorization": "Čekám na iFlow autorizaci...",
"exchangingCodeForTokens": "Vyměňuji kód za tokeny...",
"Authorization": "Autorizace",
"Content-Disposition": "Obsah-Dispozice"
},
"landing": {
"brandName": "OmniRoute",
@@ -2409,7 +2522,7 @@
"howItWorksStep3Title": "3. AI Poskytovatelé",
"howItWorksStep3Description": "Požadavek je okamžitě splněn OpenAI, Anthropic, Gemini nebo jinými.",
"getStartedIn30Seconds": "Začněte za 30 sekund",
"getStartedDescription": "Nainstalujte si OmniRoute, nakonfigurujte poskytovatele přes webovou nástěnku a začněte směrovat AI požadavky.",
"getStartedDescription": "Nainstalujte si OmniRoute, nastavte poskytovatele přes webovou nástěnku a začněte směrovat AI požadavky.",
"installOmniRoute": "Instalace OmniRoute",
"installStepDescription": "Spuštěním příkazu npx okamžitě spustíte server",
"openDashboard": "Otevřít nástěnku",
@@ -2423,7 +2536,7 @@
"serverRunningOnLabel": "Server běží na",
"dashboardLabel": "Nástěnka",
"readyToRoute": "Připraveno k trasování! ✓",
"configureProvidersNote": "📝 Nakonfigurujte poskytovatele na nástěnce nebo použijte proměnné prostředí",
"configureProvidersNote": "📝 Nastavte poskytovatele na nástěnce nebo použijte proměnné prostředí",
"dataLocation": "Umístění dat:",
"dataLocationMacLinux": "macOS/Linux:",
"dataLocationWindows": "Windows:",
@@ -2501,7 +2614,7 @@
"featureHealthTitle": "Monitorování zdraví",
"featureHealthText": "Kontroly stavu v reálném čase, stav poskytovatele, stavy jističů a automatická detekce limitů sazeb s exponenciálním odpočítáváním.",
"featureCliTitle": "CLI Nástroje",
"featureCliText": "Spravujte nastavení IDE, exportujte/importujte zálohy, vyhledávejte profily Codex a konfigurujte nastavení z nástěnky.",
"featureCliText": "Spravujte nastavení IDE, exportujte/importujte zálohy, vyhledávejte profily Codex a upravte nastavení z nástěnky.",
"featureSecurityTitle": "Zabezpečení a zásady",
"featureSecurityText": "Ověřování klíčů API, filtrování IP adres, ochrana proti vkládání výzev, zásady domény, správa relací a protokolování auditu.",
"featureCloudSyncTitle": "Synchronizace s cloudem",
@@ -2628,7 +2741,7 @@
"termsResponsibilityCompliance": "Musíte dodržovat podmínky služby každého AI poskytovatele, k jehož API přistupujete prostřednictvím OmniRoute.",
"termsResponsibilitySecurity": "Nesete odpovědnost za zabezpečení vaší lokální instalace OmniRoute, včetně nastavení hesla a omezení přístupu k síti.",
"termsSection3Title": "3. Jak to funguje",
"termsSection3Text": "OmniRoute funguje jako zprostředkující proxy. API Volání odeslaná do OmniRoute jsou překládána a přeposílána vámi nakonfigurovaným AI poskytovatelům. OmniRoute nemění obsah vašich požadavků ani odpovědí nad rámec nezbytného překladu protokolu.",
"termsSection3Text": "OmniRoute funguje jako zprostředkující proxy. API Volání odeslaná do OmniRoute jsou překládána a přeposílána vámi nastaveným AI poskytovatelům. OmniRoute nemění obsah vašich požadavků ani odpovědí nad rámec nezbytného překladu protokolu.",
"termsSection4Title": "4. Zpracování dat",
"termsDataStoredLocally": "Všechna data jsou uložena lokálně na vašem počítači v databázi SQLite.",
"termsNoTransmission": "OmniRoute nepřenáší žádná data na externí servery, pokud explicitně nepovolíte funkce synchronizace s cloudem.",
@@ -2691,5 +2804,48 @@
"modePackCostSaver": "Úspora nákladů",
"modePackQualityFirst": "Kvalita na prvním místě",
"modePackOfflineFriendly": "Offline přátelské"
},
"templateNames": {
"simple-chat": "Jednoduchý chat",
"streaming": "Streamování",
"system-prompt": "Systémový prompt",
"thinking": "Přemýšlení",
"tool-calling": "Volání nástrojů",
"multi-turn": "Multitah"
},
"templateDescriptions": {
"simple-chat": "Základní šablona chatu se systémovou zprávou",
"streaming": "Šablona pro streamování odpovědí",
"system-prompt": "Šablona s vlastním systémovým promptem",
"thinking": "Šablona s rozpočtem na přemýšlení/odůvodňování",
"tool-calling": "Šablona pro volání nástrojů/funkcí",
"multi-turn": "Šablona pro konverzace s Multitahy"
},
"templatePayloads": {
"simpleChat": {
"system": "Jste užitečný asistent AI.",
"userGreeting": "Dobrý den! Jak vám dnes mohu pomoci?"
},
"streaming": {
"prompt": "Napište příběh o"
},
"systemPrompt": {
"question": "Jaký je smysl života?",
"systemInstruction": "Poskytněte promyšlenou, filozofickou odpověď."
},
"thinking": {
"question": "Vysvětlete kvantové výpočty"
},
"toolCalling": {
"cityNameDescription": "Název města, pro které se má zjistit počasí",
"toolDescription": "Získejte aktuální počasí pro místo",
"userWeather": "Jaké je počasí v Tokiu?"
},
"multiTurn": {
"system": "Jste užitečný pomocník.",
"assistantExample": "Rád vám s tím pomohu.",
"userInitial": "Potřebuji pomoct",
"userFollowUp": "Můžete to upřesnit?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratis",
"skipToContent": "Gå til indhold",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Accepter",
"accountId": "Konto-id",
"alias": "Alias",
"apiKeyId": "API-nøgle-id",
"apiKeyName": "API-nøglenavn",
"apiKeySecret": "API-nøglehemmelighed",
"authorization": "Autorisation",
"content-type": "Indholdstype",
"content-length": "Indholdslængde",
"cookie": "Cookie",
"file": "Fil",
"host": "vært",
"id": "ID",
"import": "Importer",
"limit": "Grænse",
"offset": "Offset",
"open": "Åbn",
"origin": "Oprindelse",
"promptTokens": "Spørg tokens",
"completionTokens": "Fuldførelsestokens",
"totalTokens": "Samlede tokens",
"rawModel": "Rå model",
"scope": "Omfang",
"skill": "Færdighed",
"sortBy": "Sorter efter",
"sortOrder": "Sorteringsrækkefølge",
"tab": "Tab",
"text": "Tekst",
"textarea": "Tekstområde",
"tool": "Værktøj",
"toolId": "Værktøjs-id",
"web": "Web",
"whereUsed": "Hvor brugt",
"whitelist": "Hvidliste",
"blacklist": "Sortliste",
"resolve": "Løs",
"force": "Kraft",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Rækkevidde",
"component": "Komponent",
"redirect_uri": "Omdiriger URI",
"idempotency-key": "Idempotens nøgle",
"error_description": "Fejlbeskrivelse",
"code": "Kode",
"compatible": "Kompatibel",
"chat-completions": "Chatafslutninger",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "Krypto",
"hours": "Timer",
"selfsigned": "Selvsigneret",
"proxy_id": "Proxy ID",
"proxyId": "Proxy ID",
"connectionId": "Forbindelses-id",
"resolveConnectionId": "Løs forbindelses-id",
"resolve_connection_id": "Løs forbindelses-id",
"scope_id": "Omfang ID",
"scopeId": "Omfang ID",
"jwtSecret": "JWT-hemmelighed",
"keytar": "Keytar",
"better-sqlite3": "bedre-sqlite3",
"undici": "undici",
"builder-id": "Bygherre-id",
"musicDesc": "Musikbeskrivelse",
"musicGeneration": "Musik Generation",
"idc": "IDC",
"cloud-status-changed": "Cloud-status ændret",
"where_used": "Hvor brugt",
"windowMs": "Vindue (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Værktøjstilladelsesliste",
"TOOL_DENYLIST": "Værktøj Denylist",
"Failed to save pricing": "Kunne ikke gemme prisen",
"Failed to reset pricing": "Failed to reset pricing",
"apikey": "API nøgle",
"http": "HTTP"
},
"sidebar": {
"home": "Hjem",
@@ -179,7 +257,11 @@
"requestsShort": "{count} req",
"providerModelsTitle": "{provider} - Modeller",
"copiedModel": "Kopieret: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Opdater nu",
"updating": "Opdaterer...",
"updateAvailableDesc": "En ny version er tilgængelig. Klik for at opdatere.",
"updateStarted": "Opdatering startet..."
},
"analytics": {
"title": "Analytics",
@@ -535,6 +617,9 @@
"title": "Tilføj Model Config",
"desc": "Tilføj følgende konfiguration til dit modelarray:"
}
},
"notes": {
"0": "Continue bruger JSON-konfigurationsfil."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode kræver API-nøglekonfiguration.",
"1": "Indstil basis-URL'en til dit OmniRoute-slutpunkt."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro kræver en Amazon-konto."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Chatafslutninger",
"importingModels": "Importerer...",
"importFromModels": "Importer fra /models",
"clearAllModels": "Ryd alle modeller",
"clearAllModelsConfirm": "Er du sikker på, at du vil fjerne alle modeller for denne udbyder? Dette kan ikke fortrydes.",
"clearAllModelsSuccess": "Alle modeller ryddet",
"clearAllModelsFailed": "Kunne ikke rydde modeller",
"addConnectionToImport": "Tilføj en forbindelse for at aktivere import.",
"noModelsConfigured": "Ingen modeller konfigureret",
"connectionCount": "{count} forbindelse(r)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Auto-synkronisering",
"autoSyncTooltip": "Opdater modellisten automatisk hver 24. time (kan konfigureres via MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automatisk synkronisering aktiveret - modellerne opdateres med jævne mellemrum",
"autoSyncDisabled": "Automatisk synkronisering deaktiveret",
"autoSyncToggleFailed": "Automatisk synkronisering kunne ikke slås til eller fra",
"clearAllModels": "Ryd alle modeller",
"clearAllModelsConfirm": "Er du sikker på, at du vil fjerne alle modeller for denne udbyder? Dette kan ikke fortrydes.",
"clearAllModelsSuccess": "Alle modeller ryddet",
"clearAllModelsFailed": "Det lykkedes ikke at rydde modeller"
},
"settings": {
"title": "Indstillinger",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Genstart serveren - den vil bruge den nye adgangskode",
"backToLogin": "Tilbage til Login",
"forgotPassword": "Glemt adgangskode?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Autorisation",
"Content-Disposition": "Indhold-Disposition",
"waitingForAuthorization": "Venter på godkendelse...",
"waitingForGoogleAuthorization": "Venter på Google-godkendelse...",
"waitingForOpenAIAuthorization": "Venter på OpenAI-godkendelse...",
"waitingForAntigravityAuthorization": "Venter på antigravity-autorisation...",
"waitingForIFlowAuthorization": "Venter på iFlow-godkendelse...",
"exchangingCodeForTokens": "Udveksler kode til tokens..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Tekst-til-tale generation (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generering af tekstindlejring (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Privatlivspolitik",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simpel chat",
"streaming": "Streaming",
"system-prompt": "Systemprompt",
"thinking": "Tænker",
"tool-calling": "Værktøjsopkald",
"multi-turn": "Multivending"
},
"templateDescriptions": {
"simple-chat": "Grundlæggende chatskabelon med systembesked",
"streaming": "Skabelon til streaming svar",
"system-prompt": "Skabelon med brugerdefineret systemprompt",
"thinking": "Skabelon med ræsonnement/tænkebudget",
"tool-calling": "Skabelon til værktøj/funktionskald",
"multi-turn": "Skabelon til samtaler med flere sving"
},
"templatePayloads": {
"simpleChat": {
"system": "Du er en hjælpsom AI-assistent.",
"userGreeting": "Hej! Hvordan kan jeg hjælpe dig i dag?"
},
"streaming": {
"prompt": "Skriv en historie om"
},
"systemPrompt": {
"question": "Hvad er meningen med livet?",
"systemInstruction": "Giv et tankevækkende, filosofisk svar."
},
"thinking": {
"question": "Forklar kvanteberegning"
},
"toolCalling": {
"cityNameDescription": "Navnet på byen for at få vejr til",
"toolDescription": "Få det aktuelle vejr for en placering",
"userWeather": "Hvordan er vejret i Tokyo?"
},
"multiTurn": {
"system": "Du er en hjælpsom assistent.",
"assistantExample": "Det hjælper jeg dig gerne med.",
"userInitial": "Jeg har brug for hjælp til",
"userFollowUp": "Kan du uddybe det?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Kostenlos",
"skipToContent": "Zum Inhalt springen",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Akzeptieren",
"accountId": "Konto-ID",
"alias": "Alias",
"apiKeyId": "API-Schlüssel-ID",
"apiKeyName": "API-Schlüsselname",
"apiKeySecret": "API-Schlüsselgeheimnis",
"authorization": "Autorisierung",
"content-type": "Inhaltstyp",
"content-length": "Inhaltslänge",
"cookie": "Cookie",
"file": "Datei",
"host": "Host",
"id": "ID",
"import": "Importieren",
"limit": "Limit",
"offset": "Offset",
"open": "Öffnen",
"origin": "Herkunft",
"promptTokens": "Prompt-Token",
"completionTokens": "Completion-Token",
"totalTokens": "Gesamte Token",
"rawModel": "Raw-Modell",
"scope": "Umfang",
"skill": "Fertigkeit",
"sortBy": "Sortieren nach",
"sortOrder": "Sortierreihenfolge",
"tab": "Tab",
"text": "Text",
"textarea": "Textfeld",
"tool": "Werkzeug",
"toolId": "Werkzeug-ID",
"web": "Web",
"whereUsed": "Wo verwendet",
"whitelist": "Whitelist",
"blacklist": "Blacklist",
"resolve": "Auflösen",
"force": "Erzwingen",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Bereich",
"component": "Komponente",
"redirect_uri": "Umleitungs-URI",
"idempotency-key": "Idempotenz-Schlüssel",
"error_description": "Fehlerbeschreibung",
"code": "Code",
"compatible": "Kompatibel",
"chat-completions": "Chat-Completions",
"oauth": "OAuth",
"auth_token": "Auth-Token",
"crypto": "Krypto",
"hours": "Stunden",
"selfsigned": "Selbstsigniert",
"proxy_id": "Proxy-ID",
"proxyId": "Proxy-ID",
"connectionId": "Verbindungs-ID",
"resolveConnectionId": "Verbindungs-ID auflösen",
"resolve_connection_id": "Verbindungs-ID auflösen",
"scope_id": "Umfang-ID",
"scopeId": "Umfang-ID",
"jwtSecret": "JWT-Geheimnis",
"keytar": "Keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "Builder-ID",
"musicDesc": "Musikbeschreibung",
"musicGeneration": "Musikgenerierung",
"idc": "IDC",
"cloud-status-changed": "Cloud-Status geändert",
"where_used": "Wo verwendet",
"windowMs": "Fenster (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Werkzeug-Whitelist",
"TOOL_DENYLIST": "Werkzeug-Blacklist",
"Failed to save pricing": "Preise speichern fehlgeschlagen",
"Failed to reset pricing": "Preise zurücksetzen fehlgeschlagen",
"apikey": "API-Schlüssel",
"http": "HTTP"
},
"sidebar": {
"home": "Zuhause",
@@ -179,7 +257,11 @@
"requestsShort": "{count} Anfr.",
"providerModelsTitle": "{provider} Modelle",
"copiedModel": "Kopiert: {model}",
"aliasLabel": "Alias"
"aliasLabel": "Alias",
"updateNow": "Jetzt aktualisieren",
"updating": "Aktualisiere...",
"updateAvailableDesc": "Eine neue Version ist verfügbar. Klicken Sie zum Aktualisieren.",
"updateStarted": "Aktualisierung gestartet..."
},
"analytics": {
"title": "Analytik",
@@ -535,6 +617,9 @@
"title": "Modellkonfiguration hinzufügen",
"desc": "Fügen Sie Ihrem Modellarray die folgende Konfiguration hinzu:"
}
},
"notes": {
"0": "Continue verwendet eine JSON-Konfigurationsdatei."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode erfordert API-Schlüssel-Konfiguration.",
"1": "Base URL auf Ihren OmniRoute-Endpunkt setzen."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro erfordert ein Amazon-Konto."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Chat-Abschlüsse",
"importingModels": "Importieren...",
"importFromModels": "Import aus /models",
"clearAllModels": "Alle Modelle löschen",
"clearAllModelsConfirm": "Sind Sie sicher, dass Sie alle Modelle für diesen Anbieter entfernen möchten? Dies kann nicht rückgängig gemacht werden.",
"clearAllModelsSuccess": "Alle Modelle gelöscht",
"clearAllModelsFailed": "Modelle konnten nicht gelöscht werden",
"addConnectionToImport": "Fügen Sie eine Verbindung hinzu, um den Import zu ermöglichen.",
"noModelsConfigured": "Keine Modelle konfiguriert",
"connectionCount": "{count} Verbindung(en)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Auto-Sync",
"autoSyncTooltip": "Modellliste automatisch alle 24 Stunden aktualisieren (konfigurierbar über MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Auto-Sync aktiviert — Modelle werden regelmäßig aktualisiert",
"autoSyncDisabled": "Auto-Sync deaktiviert",
"autoSyncToggleFailed": "Auto-Sync umschalten fehlgeschlagen",
"clearAllModels": "Alle Modelle löschen",
"clearAllModelsConfirm": "Möchten Sie wirklich alle Modelle für diesen Anbieter löschen?",
"clearAllModelsSuccess": "Alle Modelle gelöscht",
"clearAllModelsFailed": "Modelle löschen fehlgeschlagen"
},
"settings": {
"title": "Einstellungen",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Starten Sie den Server neu er verwendet das neue Passwort",
"backToLogin": "Zurück zum Anmelden",
"forgotPassword": "Passwort vergessen?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Warte auf Autorisierung...",
"waitingForGoogleAuthorization": "Warte auf Google-Autorisierung...",
"waitingForOpenAIAuthorization": "Warte auf OpenAI-Autorisierung...",
"waitingForAntigravityAuthorization": "Warte auf Antigravity-Autorisierung...",
"waitingForIFlowAuthorization": "Warte auf iFlow-Autorisierung...",
"exchangingCodeForTokens": "Tausche Code gegen Token aus...",
"Authorization": "Autorisierung",
"Content-Disposition": "Inhaltliche Disposition"
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Sprachsynthese (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Text-Embedding-Generierung (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Datenschutzrichtlinie",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Einfacher Chat",
"streaming": "Streaming",
"system-prompt": "System-Prompt",
"thinking": "Denken",
"tool-calling": "Werkzeugaufruf",
"multi-turn": "Mehrere Runden"
},
"templateDescriptions": {
"simple-chat": "Einfache Chat-Vorlage mit Systemnachricht",
"streaming": "Vorlage für Streaming-Antworten",
"system-prompt": "Vorlage mit benutzerdefiniertem System-Prompt",
"thinking": "Vorlage mit Denk-/Argumentationsbudget",
"tool-calling": "Vorlage für Werkzeug-/Funktionsaufrufe",
"multi-turn": "Vorlage für mehrstufige Konversationen"
},
"templatePayloads": {
"simpleChat": {
"system": "Sie sind ein hilfreicher KI-Assistent.",
"userGreeting": "Hallo! Wie kann ich Ihnen heute helfen?"
},
"streaming": {
"prompt": "Schreiben Sie eine Geschichte darüber"
},
"systemPrompt": {
"question": "Was ist der Sinn des Lebens?",
"systemInstruction": "Geben Sie eine nachdenkliche, philosophische Antwort."
},
"thinking": {
"question": "Erklären Sie Quantencomputing"
},
"toolCalling": {
"cityNameDescription": "Der Name der Stadt, für die das Wetter ermittelt werden soll",
"toolDescription": "Erhalten Sie das aktuelle Wetter für einen Standort",
"userWeather": "Wie ist das Wetter in Tokio?"
},
"multiTurn": {
"system": "Sie sind ein hilfreicher Assistent.",
"assistantExample": "Gerne helfe ich Ihnen dabei.",
"userInitial": "Ich brauche Hilfe dabei",
"userFollowUp": "Können Sie das näher erläutern?"
}
}
}
+152 -3
View File
@@ -58,7 +58,85 @@
"free": "Free",
"skipToContent": "Skip to content",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Accept",
"accountId": "Account ID",
"alias": "Alias",
"apiKeyId": "API Key ID",
"apiKeyName": "API Key Name",
"apiKeySecret": "API Key Secret",
"authorization": "Authorization",
"content-type": "Content Type",
"content-length": "Content Length",
"cookie": "Cookie",
"file": "File",
"host": "Host",
"id": "ID",
"import": "Import",
"limit": "Limit",
"offset": "Offset",
"open": "Open",
"origin": "Origin",
"promptTokens": "Prompt Tokens",
"completionTokens": "Completion Tokens",
"totalTokens": "Total Tokens",
"rawModel": "Raw Model",
"scope": "Scope",
"skill": "Skill",
"sortBy": "Sort By",
"sortOrder": "Sort Order",
"tab": "Tab",
"text": "Text",
"textarea": "Textarea",
"tool": "Tool",
"toolId": "Tool ID",
"web": "Web",
"whereUsed": "Where Used",
"whitelist": "Whitelist",
"blacklist": "Blacklist",
"resolve": "Resolve",
"force": "Force",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Range",
"component": "Component",
"redirect_uri": "Redirect URI",
"idempotency-key": "Idempotency Key",
"error_description": "Error Description",
"code": "Code",
"compatible": "Compatible",
"chat-completions": "Chat Completions",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "Crypto",
"hours": "Hours",
"selfsigned": "Self-signed",
"proxy_id": "Proxy ID",
"proxyId": "Proxy ID",
"connectionId": "Connection ID",
"resolveConnectionId": "Resolve Connection ID",
"resolve_connection_id": "Resolve Connection ID",
"scope_id": "Scope ID",
"scopeId": "Scope ID",
"jwtSecret": "JWT Secret",
"keytar": "Keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "Builder ID",
"musicDesc": "Music Description",
"musicGeneration": "Music Generation",
"idc": "IDC",
"cloud-status-changed": "Cloud status changed",
"where_used": "Where Used",
"windowMs": "Window (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Tool Allowlist",
"TOOL_DENYLIST": "Tool Denylist",
"Failed to save pricing": "Failed to save pricing",
"Failed to reset pricing": "Failed to reset pricing",
"apikey": "API Key",
"http": "HTTP"
},
"sidebar": {
"home": "Home",
@@ -179,7 +257,11 @@
"requestsShort": "{count} reqs",
"providerModelsTitle": "{provider} - Models",
"copiedModel": "Copied: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Update Now",
"updating": "Updating...",
"updateAvailableDesc": "A new version is available. Click to update.",
"updateStarted": "Update started..."
},
"analytics": {
"title": "Analytics",
@@ -607,6 +689,9 @@
"title": "Add Model Config",
"desc": "Add the following configuration to your models array:"
}
},
"notes": {
"0": "Continue uses JSON config file."
}
},
"opencode": {
@@ -625,6 +710,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode requires API key configuration.",
"1": "Set the base URL to your OmniRoute endpoint."
}
},
"kiro": {
@@ -643,6 +732,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro requires Amazon account."
}
}
}
@@ -1543,6 +1635,12 @@
"compatibleProdPlaceholder": "{type} Compatible (Prod)",
"tokenRefreshed": "Token refreshed successfully",
"tokenRefreshFailed": "Token refresh failed",
"applyCodexAuthLocal": "Apply auth",
"exportCodexAuthFile": "Export auth",
"codexAuthAppliedLocal": "Codex auth.json applied locally",
"codexAuthApplyFailed": "Failed to apply Codex auth.json locally",
"codexAuthExported": "Codex auth.json exported",
"codexAuthExportFailed": "Failed to export Codex auth.json",
"advancedSettings": "Advanced Settings",
"chatPathLabel": "Chat Endpoint Path",
"chatPathPlaceholder": "/chat/completions",
@@ -2376,7 +2474,15 @@
"restartServerWithNewPassword": "Restart the server - it will use the new password",
"backToLogin": "Back to Login",
"forgotPassword": "Forgot password?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Authorization",
"Content-Disposition": "Content-Disposition",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2700,5 +2806,48 @@
"modePackCostSaver": "Cost Saver",
"modePackQualityFirst": "Quality First",
"modePackOfflineFriendly": "Offline Friendly"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template with system message",
"streaming": "Template for streaming responses",
"system-prompt": "Template with custom system prompt",
"thinking": "Template with reasoning/thinking budget",
"tool-calling": "Template for tool/function calling",
"multi-turn": "Template for multi-turn conversations"
},
"templatePayloads": {
"simpleChat": {
"system": "You are a helpful AI assistant.",
"userGreeting": "Hello! How can I help you today?"
},
"streaming": {
"prompt": "Write a story about"
},
"systemPrompt": {
"question": "What is the meaning of life?",
"systemInstruction": "Provide a thoughtful, philosophical answer."
},
"thinking": {
"question": "Explain quantum computing"
},
"toolCalling": {
"cityNameDescription": "The name of the city to get weather for",
"toolDescription": "Get current weather for a location",
"userWeather": "What's the weather in Tokyo?"
},
"multiTurn": {
"system": "You are a helpful assistant.",
"assistantExample": "I'd be happy to help you with that.",
"userInitial": "I need help with",
"userFollowUp": "Can you elaborate on that?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratis",
"skipToContent": "Saltar al contenido",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "google",
"TOOL_ALLOWLIST": "Lista de herramientas permitidas",
"TOOL_DENYLIST": "Lista de denegaciones de herramientas",
"Failed to save pricing": "No se pudo guardar el precio",
"Failed to reset pricing": "No se pudo restablecer el precio",
"apikey": "Clave API",
"http": "HTTP"
},
"sidebar": {
"home": "Inicio",
@@ -179,7 +257,11 @@
"requestsShort": "{count} reqs",
"providerModelsTitle": "{provider} - Modelos",
"copiedModel": "Copiado: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Actualizar ahora",
"updating": "Actualizando...",
"updateAvailableDesc": "Hay una nueva versión disponible. Haga clic para actualizar.",
"updateStarted": "Actualización iniciada..."
},
"analytics": {
"title": "Analítica",
@@ -535,6 +617,9 @@
"title": "Agregar configuración de modelo",
"desc": "Agregue la siguiente configuración a su matriz de modelos:"
}
},
"notes": {
"0": "Continue usa un archivo de configuración JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode requiere configuración de clave API.",
"1": "Establece la URL base a tu endpoint de OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro requiere cuenta de Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Finalizaciones de chat",
"importingModels": "Importando...",
"importFromModels": "Importar desde /modelos",
"clearAllModels": "Borrar todos los modelos",
"clearAllModelsConfirm": "¿Estás seguro de que quieres eliminar todos los modelos de este proveedor? Esta acción no se puede deshacer.",
"clearAllModelsSuccess": "Todos los modelos eliminados",
"clearAllModelsFailed": "Error al eliminar los modelos",
"addConnectionToImport": "Agregue una conexión para permitir la importación.",
"noModelsConfigured": "No hay modelos configurados",
"connectionCount": "{count} conexión(es)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Sincronización automática",
"autoSyncTooltip": "Actualiza automáticamente la lista de modelos cada 24 horas (configurable vía MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Sincronización automática activada — los modelos se actualizarán periódicamente",
"autoSyncDisabled": "Sincronización automática desactivada",
"autoSyncToggleFailed": "Error al alternar sincronización automática",
"clearAllModels": "Borrar todos los modelos",
"clearAllModelsConfirm": "¿Estás seguro de que quieres eliminar todos los modelos de este proveedor?",
"clearAllModelsSuccess": "Todos los modelos borrados",
"clearAllModelsFailed": "Error al borrar modelos"
},
"settings": {
"title": "Configuración",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Reinicia el servidor; usará la nueva contraseña",
"backToLogin": "Volver a iniciar sesión",
"forgotPassword": "¿Olvidaste tu contraseña?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "Autorización",
"Content-Disposition": "Disposición de contenido"
},
"landing": {
"brandName": "OmniRuta",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Generación de texto a voz (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generación de embeddings de texto (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Política de privacidad",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "Eres un útil asistente de IA.",
"userGreeting": "¡Hola! ¿Cómo puedo ayudarte hoy?"
},
"streaming": {
"prompt": "Escribe una historia sobre"
},
"systemPrompt": {
"question": "¿Cuál es el significado de la vida?",
"systemInstruction": "Proporcione una respuesta reflexiva y filosófica."
},
"thinking": {
"question": "Explicar la computación cuántica"
},
"toolCalling": {
"cityNameDescription": "El nombre de la ciudad para obtener el clima.",
"toolDescription": "Obtener el clima actual para una ubicación",
"userWeather": "¿Cuál es el clima en Tokio?"
},
"multiTurn": {
"system": "Eres un asistente útil.",
"assistantExample": "Estaré encantado de ayudarte con eso.",
"userInitial": "necesito ayuda con",
"userFollowUp": "¿Puedes dar más detalles sobre eso?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Ilmainen",
"skipToContent": "Siirry sisältöön",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Hyväksy",
"accountId": "Tilin tunnus",
"alias": "Alias",
"apiKeyId": "API-avaimen tunnus",
"apiKeyName": "API-avaimen nimi",
"apiKeySecret": "API-avaimen salaisuus",
"authorization": "Valtuutus",
"content-type": "Sisältötyyppi",
"content-length": "Sisällön pituus",
"cookie": "Eväste",
"file": "Tiedosto",
"host": "Isäntä",
"id": "ID",
"import": "Tuo",
"limit": "Raja",
"offset": "Offset",
"open": "Avaa",
"origin": "Alkuperä",
"promptTokens": "Kehotusmerkit",
"completionTokens": "Päättymismerkit",
"totalTokens": "Tokeneja yhteensä",
"rawModel": "Raaka malli",
"scope": "Laajuus",
"skill": "Taito",
"sortBy": "Lajitteluperuste",
"sortOrder": "Lajittelujärjestys",
"tab": "Tab",
"text": "Teksti",
"textarea": "Textarea",
"tool": "Työkalu",
"toolId": "Työkalun tunnus",
"web": "Web",
"whereUsed": "Missä käytetty",
"whitelist": "Valkoinen lista",
"blacklist": "Musta lista",
"resolve": "Ratkaise",
"force": "Voimaa",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Alue",
"component": "Komponentti",
"redirect_uri": "Uudelleenohjaus URI",
"idempotency-key": "Idempotenssiavain",
"error_description": "Virheen kuvaus",
"code": "Koodi",
"compatible": "Yhteensopiva",
"chat-completions": "Chatin loppuun saattaminen",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "Krypto",
"hours": "Tuntia",
"selfsigned": "Itse allekirjoitettu",
"proxy_id": "Välityspalvelimen tunnus",
"proxyId": "Välityspalvelimen tunnus",
"connectionId": "Yhteystunnus",
"resolveConnectionId": "Ratkaise yhteystunnus",
"resolve_connection_id": "Ratkaise yhteystunnus",
"scope_id": "Laajuustunnus",
"scopeId": "Laajuustunnus",
"jwtSecret": "JWT Secret",
"keytar": "Keytar",
"better-sqlite3": "parempi-sqlite3",
"undici": "undici",
"builder-id": "Rakentajan tunnus",
"musicDesc": "Musiikki Kuvaus",
"musicGeneration": "Musiikin sukupolvi",
"idc": "IDC",
"cloud-status-changed": "Pilven tila muuttunut",
"where_used": "Missä käytetty",
"windowMs": "Ikkuna (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Työkalujen sallittu luettelo",
"TOOL_DENYLIST": "Tool Denylist",
"Failed to save pricing": "Hinnoittelun tallentaminen epäonnistui",
"Failed to reset pricing": "Hinnoittelun nollaaminen epäonnistui",
"apikey": "API-avain",
"http": "HTTP"
},
"sidebar": {
"home": "Kotiin",
@@ -179,7 +257,11 @@
"requestsShort": "{count} vaatimus",
"providerModelsTitle": "{provider} - Mallit",
"copiedModel": "Kopioitu: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Päivitä nyt",
"updating": "Päivitetään...",
"updateAvailableDesc": "Uusi versio on saatavilla. Päivitä napsauttamalla.",
"updateStarted": "Päivitys aloitettu..."
},
"analytics": {
"title": "Analytics",
@@ -535,6 +617,9 @@
"title": "Lisää mallin kokoonpano",
"desc": "Lisää seuraavat kokoonpanot mallien matriisiisi:"
}
},
"notes": {
"0": "Jatka käyttää JSON-määritystiedostoa."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode vaatii API-avaimen määrityksen.",
"1": "Aseta perus-URL-osoite OmniRoute-päätepisteellesi."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro vaatii Amazon-tilin."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Chatin loppuun saattaminen",
"importingModels": "Tuodaan...",
"importFromModels": "Tuo / mallit",
"clearAllModels": "Tyhjennä kaikki mallit",
"clearAllModelsConfirm": "Haluatko varmasti poistaa kaikki tämän palveluntarjoajan mallit? Tätä ei voi kumota.",
"clearAllModelsSuccess": "Kaikki mallit tyhjennetty",
"clearAllModelsFailed": "Mallien tyhjentäminen epäonnistui",
"addConnectionToImport": "Lisää yhteys ottaaksesi tuonnin käyttöön.",
"noModelsConfigured": "Ei malleja määritetty",
"connectionCount": "{count} yhteyttä",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Automaattinen synkronointi",
"autoSyncTooltip": "Päivitä malliluettelo automaattisesti 24 tunnin välein (konfiguroitavissa kohdassa MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automaattinen synkronointi käytössä mallit päivittyvät säännöllisesti",
"autoSyncDisabled": "Automaattinen synkronointi poistettu käytöstä",
"autoSyncToggleFailed": "Automaattisen synkronoinnin vaihtaminen epäonnistui",
"clearAllModels": "Tyhjennä kaikki mallit",
"clearAllModelsConfirm": "Haluatko varmasti poistaa kaikki tämän palveluntarjoajan mallit? Tätä ei voi kumota.",
"clearAllModelsSuccess": "Kaikki mallit tyhjennetty",
"clearAllModelsFailed": "Mallien tyhjennys epäonnistui"
},
"settings": {
"title": "Asetukset",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Käynnistä palvelin uudelleen - se käyttää uutta salasanaa",
"backToLogin": "Takaisin kirjautumiseen",
"forgotPassword": "Unohditko salasanan?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Valtuutus",
"Content-Disposition": "Sisältö-sijoittelu",
"waitingForAuthorization": "Odotetaan valtuutusta...",
"waitingForGoogleAuthorization": "Odotetaan Googlen valtuutusta...",
"waitingForOpenAIAuthorization": "Odotetaan OpenAI-valtuutusta...",
"waitingForAntigravityAuthorization": "Odotetaan antigravitaatiovaltuutusta...",
"waitingForIFlowAuthorization": "Odotetaan iFlow-valtuutusta...",
"exchangingCodeForTokens": "Vaihdetaan koodia tokeneihin..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Tekstistä puheeksi luominen (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Tekstin upottaminen (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Tietosuojakäytäntö",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Yksinkertainen chat",
"streaming": "Suoratoisto",
"system-prompt": "Järjestelmäkehote",
"thinking": "Ajatteleminen",
"tool-calling": "Työkalun kutsuminen",
"multi-turn": "Monikierros"
},
"templateDescriptions": {
"simple-chat": "Peruskeskustelumalli järjestelmäviestillä",
"streaming": "Malli vastausten suoratoistoon",
"system-prompt": "Malli mukautetulla järjestelmäkehotteella",
"thinking": "Malli perustelulla/ajattelulla",
"tool-calling": "Malli työkalun/toiminnon kutsumiseen",
"multi-turn": "Malli usean käännöksen keskusteluille"
},
"templatePayloads": {
"simpleChat": {
"system": "Olet avulias AI-avustaja.",
"userGreeting": "Hei! Kuinka voin auttaa sinua tänään?"
},
"streaming": {
"prompt": "Kirjoita tarina aiheesta"
},
"systemPrompt": {
"question": "Mikä on elämän tarkoitus?",
"systemInstruction": "Anna harkittu, filosofinen vastaus."
},
"thinking": {
"question": "Selitä kvanttilaskenta"
},
"toolCalling": {
"cityNameDescription": "Kaupungin nimi, jolle sää haetaan",
"toolDescription": "Hanki sijainnin nykyinen sää",
"userWeather": "Millainen sää on Tokiossa?"
},
"multiTurn": {
"system": "Olet avulias avustaja.",
"assistantExample": "Autan sinua siinä mielelläni.",
"userInitial": "Tarvitsen apua",
"userFollowUp": "Voitko tarkentaa sitä?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratuit",
"skipToContent": "Passer au contenu",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Liste autorisée des outils",
"TOOL_DENYLIST": "Liste de refus d'outils",
"Failed to save pricing": "Échec de l'enregistrement des prix",
"Failed to reset pricing": "Échec de la réinitialisation des prix",
"apikey": "Clé API",
"http": "HTTP"
},
"sidebar": {
"home": "Accueil",
@@ -179,7 +257,11 @@
"requestsShort": "{count} reqs",
"providerModelsTitle": "{provider} - Modèles",
"copiedModel": "Copié : {model}",
"aliasLabel": "pseudonyme"
"aliasLabel": "pseudonyme",
"updateNow": "Mettre à jour",
"updating": "Mise à jour...",
"updateAvailableDesc": "Une nouvelle version est disponible. Cliquez pour mettre à jour.",
"updateStarted": "Mise à jour démarrée..."
},
"analytics": {
"title": "Analyse",
@@ -535,6 +617,9 @@
"title": "Ajouter une configuration de modèle",
"desc": "Ajoutez la configuration suivante à votre tableau models :"
}
},
"notes": {
"0": "Continue utilise un fichier de configuration JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode nécessite la configuration de la clé API.",
"1": "Définissez l'URL de base sur votre endpoint OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro nécessite un compte Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Achèvements des discussions",
"importingModels": "Importation...",
"importFromModels": "Importer depuis /models",
"clearAllModels": "Supprimer tous les modèles",
"clearAllModelsConfirm": "Êtes-vous sûr de vouloir supprimer tous les modèles de ce fournisseur ? Cette action est irréversible.",
"clearAllModelsSuccess": "Tous les modèles supprimés",
"clearAllModelsFailed": "Échec de la suppression des modèles",
"addConnectionToImport": "Ajoutez une connexion pour activer l'importation.",
"noModelsConfigured": "Aucun modèle configuré",
"connectionCount": "{count} connexion(s)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Synchronisation automatique",
"autoSyncTooltip": "Actualise automatiquement la liste des modèles toutes les 24h (configurable via MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Synchronisation automatique activée — les modèles seront actualisés périodiquement",
"autoSyncDisabled": "Synchronisation automatique désactivée",
"autoSyncToggleFailed": "Échec de l'activation de la synchronisation automatique",
"clearAllModels": "Effacer tous les modèles",
"clearAllModelsConfirm": "Êtes-vous sûr de vouloir supprimer tous les modèles pour ce fournisseur?",
"clearAllModelsSuccess": "Tous les modèles effacés",
"clearAllModelsFailed": "Échec de l'effacement des modèles"
},
"settings": {
"title": "Paramètres",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Redémarrez le serveur - il utilisera le nouveau mot de passe",
"backToLogin": "Retour à la connexion",
"forgotPassword": "Mot de passe oublié ?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "Autorisation",
"Content-Disposition": "Disposition du contenu"
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Génération texte-vers-discours (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Génération d'embeddings texte (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Politique de confidentialité",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "Vous êtes un assistant IA utile.",
"userGreeting": "Bonjour ! Comment puis-je vous aider aujourd'hui ?"
},
"streaming": {
"prompt": "Écrire une histoire sur"
},
"systemPrompt": {
"question": "Quel est le sens de la vie ?",
"systemInstruction": "Fournissez une réponse réfléchie et philosophique."
},
"thinking": {
"question": "Expliquer l'informatique quantique"
},
"toolCalling": {
"cityNameDescription": "Le nom de la ville pour laquelle obtenir la météo",
"toolDescription": "Obtenir la météo actuelle pour un emplacement",
"userWeather": "Quel temps fait-il à Tokyo ?"
},
"multiTurn": {
"system": "Vous êtes un assistant utile.",
"assistantExample": "Je serais heureux de vous aider avec cela.",
"userInitial": "J'ai besoin d'aide pour",
"userFollowUp": "Pouvez-vous développer cela ?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "חינם",
"skipToContent": "דלג לתוכן",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "קבל",
"accountId": "מזהה חשבון",
"alias": "כינוי",
"apiKeyId": "מזהה מפתח API",
"apiKeyName": "שם מפתח API",
"apiKeySecret": "סוד מפתח API",
"authorization": "הרשאה",
"content-type": "סוג תוכן",
"content-length": "אורך תוכן",
"cookie": "קוקי",
"file": "קובץ",
"host": "מארח",
"id": "תעודה מזהה",
"import": "ייבוא",
"limit": "הגבלה",
"offset": "קיזוז",
"open": "פתוח",
"origin": "מוצא",
"promptTokens": "אסימוני הנחיה",
"completionTokens": "אסימוני השלמה",
"totalTokens": "סך הכל אסימונים",
"rawModel": "דגם גלם",
"scope": "היקף",
"skill": "מיומנות",
"sortBy": "מיין לפי",
"sortOrder": "סדר מיון",
"tab": "כרטיסייה",
"text": "טקסט",
"textarea": "Textarea",
"tool": "כלי",
"toolId": "מזהה כלי",
"web": "אינטרנט",
"whereUsed": "היכן בשימוש",
"whitelist": "רשימת הלבנים",
"blacklist": "רשימה שחורה",
"resolve": "פתרון",
"force": "כוח",
"base64url": "כתובת אתר של Base64",
"hex": "משושה",
"range": "טווח",
"component": "רכיב",
"redirect_uri": "כתובת URL להפניה מחדש",
"idempotency-key": "מפתח אידמפוטנציה",
"error_description": "תיאור שגיאה",
"code": "קוד",
"compatible": "תואם",
"chat-completions": "השלמת צ'אט",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "קריפטו",
"hours": "שעות",
"selfsigned": "חתום בעצמו",
"proxy_id": "מזהה פרוקסי",
"proxyId": "מזהה פרוקסי",
"connectionId": "מזהה חיבור",
"resolveConnectionId": "פתרון מזהה חיבור",
"resolve_connection_id": "פתרון מזהה חיבור",
"scope_id": "מזהה היקף",
"scopeId": "מזהה היקף",
"jwtSecret": "סוד JWT",
"keytar": "Keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "מזהה בונה",
"musicDesc": "תיאור מוזיקה",
"musicGeneration": "דור המוזיקה",
"idc": "הבינתחומי",
"cloud-status-changed": "סטטוס הענן השתנה",
"where_used": "היכן בשימוש",
"windowMs": "חלון (ms)",
"social-github": "GitHub",
"social-google": "גוגל",
"TOOL_ALLOWLIST": "רשימת ההיתרים של הכלים",
"TOOL_DENYLIST": "רשימת הכלים",
"Failed to save pricing": "חיסכון במחיר נכשל",
"Failed to reset pricing": "איפוס התמחור נכשל",
"apikey": "מפתח API",
"http": "HTTP"
},
"sidebar": {
"home": "בית",
@@ -179,7 +257,11 @@
"requestsShort": "{count} בקשות",
"providerModelsTitle": "{provider} - דגמים",
"copiedModel": "הועתק: {model}",
"aliasLabel": "כינוי"
"aliasLabel": "כינוי",
"updateNow": "עדכן עכשיו",
"updating": "מעדכן...",
"updateAvailableDesc": "גרסה חדשה זמינה. לחץ לעדכון.",
"updateStarted": "העדכון התחיל..."
},
"analytics": {
"title": "אנליטיקס",
@@ -535,6 +617,9 @@
"title": "הוסף דגם Config",
"desc": "הוסף את התצורה הבאה למערך הדגמים שלך:"
}
},
"notes": {
"0": "המשך משתמש בקובץ התצורה של JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode דורש תצורת מפתח API.",
"1": "הגדר את כתובת האתר הבסיסית לנקודת הקצה של OmniRoute שלך."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro דורש חשבון אמזון."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "השלמת צ'אט",
"importingModels": "מייבא...",
"importFromModels": "ייבוא מ /models",
"clearAllModels": "מחק את כל המודלים",
"clearAllModelsConfirm": "האם אתה בטוח שברצונך להסיר את כל המודלים עבור ספק זה? לא ניתן לבטל פעולה זו.",
"clearAllModelsSuccess": "כל המודלים נמחקו",
"clearAllModelsFailed": "מחיקת המודלים נכשלה",
"addConnectionToImport": "הוסף חיבור כדי לאפשר ייבוא.",
"noModelsConfigured": "לא הוגדרו דגמים",
"connectionCount": "{count} חיבור(ים)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "סנכרון אוטומטי",
"autoSyncTooltip": "רענן אוטומטית את רשימת הדגמים כל 24 שעות (ניתן להגדרה באמצעות MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "סנכרון אוטומטי מופעל - הדגמים יתרעננו מעת לעת",
"autoSyncDisabled": "הסנכרון האוטומטי מושבת",
"autoSyncToggleFailed": "החלפת הסנכרון האוטומטי נכשלה",
"clearAllModels": "נקה את כל הדגמים",
"clearAllModelsConfirm": "האם אתה בטוח שברצונך להסיר את כל הדגמים עבור ספק זה? לא ניתן לבטל זאת.",
"clearAllModelsSuccess": "כל הדגמים נוקו",
"clearAllModelsFailed": "ניקוי הדגמים נכשל"
},
"settings": {
"title": "הגדרות",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "הפעל מחדש את השרת - הוא ישתמש בסיסמה החדשה",
"backToLogin": "חזרה לכניסה",
"forgotPassword": "שכחת סיסמה?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "הרשאה",
"Content-Disposition": "תוכן-נטייה",
"waitingForAuthorization": "ממתין לאישור...",
"waitingForGoogleAuthorization": "ממתין לאישור Google...",
"waitingForOpenAIAuthorization": "ממתין להרשאת OpenAI...",
"waitingForAntigravityAuthorization": "ממתין לאישור נגד כבידה...",
"waitingForIFlowAuthorization": "ממתין להרשאת iFlow...",
"exchangingCodeForTokens": "מחליף קוד לאסימונים..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "יצירת טקסט לדיבור (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "יצירת הטבעת טקסט (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "מדיניות פרטיות",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "צ'אט פשוט",
"streaming": "סטרימינג",
"system-prompt": "הנחית מערכת",
"thinking": "חושבים",
"tool-calling": "כלי שיחות",
"multi-turn": "רב סיבובים"
},
"templateDescriptions": {
"simple-chat": "תבנית צ'אט בסיסית עם הודעת מערכת",
"streaming": "תבנית להזרמת תגובות",
"system-prompt": "תבנית עם הודעת מערכת מותאמת אישית",
"thinking": "תבנית עם היגיון/תקציב חשיבה",
"tool-calling": "תבנית לקריאת כלי/פונקציה",
"multi-turn": "תבנית לשיחות מרובי פניות"
},
"templatePayloads": {
"simpleChat": {
"system": "אתה עוזר AI מועיל.",
"userGreeting": "שלום! איך אני יכול לעזור לך היום?"
},
"streaming": {
"prompt": "כתבו סיפור על"
},
"systemPrompt": {
"question": "מהי משמעות החיים?",
"systemInstruction": "ספק תשובה מהורהרת, פילוסופית."
},
"thinking": {
"question": "הסבר מחשוב קוונטי"
},
"toolCalling": {
"cityNameDescription": "שם העיר שאפשר לקבל עליה מזג אוויר",
"toolDescription": "קבל מזג אוויר עדכני עבור מיקום",
"userWeather": "מה מזג האוויר בטוקיו?"
},
"multiTurn": {
"system": "אתה עוזר מועיל.",
"assistantExample": "אני אשמח לעזור לך עם זה.",
"userInitial": "אני צריך עזרה עם",
"userFollowUp": "אתה יכול לפרט על זה?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Ingyenes",
"skipToContent": "Ugrás a tartalomhoz",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Elfogadás",
"accountId": "Számlaazonosító",
"alias": "Alias",
"apiKeyId": "API kulcs azonosítója",
"apiKeyName": "API kulcs neve",
"apiKeySecret": "API kulcs titka",
"authorization": "Engedélyezés",
"content-type": "Tartalom típusa",
"content-length": "Tartalom hossza",
"cookie": "Cookie",
"file": "Fájl",
"host": "Házigazda",
"id": "ID",
"import": "Importálás",
"limit": "Limit",
"offset": "Offset",
"open": "Nyissa meg",
"origin": "Eredet",
"promptTokens": "Prompt Tokenek",
"completionTokens": "Befejezési tokenek",
"totalTokens": "Összes token",
"rawModel": "Nyers modell",
"scope": "Hatály",
"skill": "Ügyesség",
"sortBy": "Rendezés alapja",
"sortOrder": "Rendezési sorrend",
"tab": "Tab",
"text": "Szöveg",
"textarea": "Textarea",
"tool": "Eszköz",
"toolId": "Szerszámazonosító",
"web": "Web",
"whereUsed": "Hol használták",
"whitelist": "Fehérlista",
"blacklist": "Feketelista",
"resolve": "Oldja meg",
"force": "Kényszer",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Tartomány",
"component": "Összetevő",
"redirect_uri": "Átirányítási URI",
"idempotency-key": "Idempotencia kulcs",
"error_description": "Hiba leírása",
"code": "kód",
"compatible": "Kompatibilis",
"chat-completions": "Csevegés befejezése",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "Crypto",
"hours": "Órák",
"selfsigned": "Önaláírt",
"proxy_id": "Proxy azonosítója",
"proxyId": "Proxy azonosítója",
"connectionId": "Csatlakozási azonosító",
"resolveConnectionId": "A kapcsolatazonosító feloldása",
"resolve_connection_id": "A kapcsolatazonosító feloldása",
"scope_id": "Hatókör azonosítója",
"scopeId": "Hatókör azonosítója",
"jwtSecret": "JWT Secret",
"keytar": "Keytar",
"better-sqlite3": "jobb-sqlite3",
"undici": "undici",
"builder-id": "Építőazonosító",
"musicDesc": "Zene Leírás",
"musicGeneration": "Zenegeneráció",
"idc": "IDC",
"cloud-status-changed": "A felhő állapota megváltozott",
"where_used": "Hol használták",
"windowMs": "Ablak (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Eszköz engedélyezési lista",
"TOOL_DENYLIST": "Tool Denylist",
"Failed to save pricing": "Nem sikerült menteni az árakat",
"Failed to reset pricing": "Nem sikerült visszaállítani az árakat",
"apikey": "API kulcs",
"http": "HTTP"
},
"sidebar": {
"home": "Otthon",
@@ -179,7 +257,11 @@
"requestsShort": "{count} igény",
"providerModelsTitle": "{provider} - Modellek",
"copiedModel": "Másolva: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Frissítés most",
"updating": "Frissítés...",
"updateAvailableDesc": "Új verzió érhető el. Kattintson a frissítéshez.",
"updateStarted": "Frissítés elindult..."
},
"analytics": {
"title": "Analytics",
@@ -535,6 +617,9 @@
"title": "Add Model Config",
"desc": "Adja hozzá a következő konfigurációt a modellek tömbjéhez:"
}
},
"notes": {
"0": "A folytatás a JSON konfigurációs fájl használatával."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Az OpenCode API-kulcs-konfigurációt igényel.",
"1": "Állítsa be az alap URL-t az OmniRoute végpontra."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "A Kiro Amazon-fiókot igényel."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Csevegés befejezése",
"importingModels": "Importálás...",
"importFromModels": "Importálás a /models-ből",
"clearAllModels": "Összes modell törlése",
"clearAllModelsConfirm": "Biztosan el szeretné távolítani az összes modellt ehhez a szolgáltatóhoz? Ez a művelet nem vonható vissza.",
"clearAllModelsSuccess": "Összes modell törölve",
"clearAllModelsFailed": "A modellek törlése sikertelen",
"addConnectionToImport": "Adjon hozzá egy kapcsolatot az importálás engedélyezéséhez.",
"noModelsConfigured": "Nincsenek konfigurálva modellek",
"connectionCount": "{count} kapcsolat",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Automatikus szinkronizálás",
"autoSyncTooltip": "A modelllista automatikus frissítése 24 óránként (konfigurálható: MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automatikus szinkronizálás engedélyezve a modellek rendszeresen frissülnek",
"autoSyncDisabled": "Az automatikus szinkronizálás letiltva",
"autoSyncToggleFailed": "Failed to toggle auto-sync",
"clearAllModels": "Minden modell törlése",
"clearAllModelsConfirm": "Biztosan eltávolítja ennek a szolgáltatónak az összes modelljét? Ezt nem lehet visszavonni.",
"clearAllModelsSuccess": "Minden modell törölve",
"clearAllModelsFailed": "Nem sikerült törölni a modelleket"
},
"settings": {
"title": "Beállítások elemre",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Indítsa újra a szervert - az új jelszót fogja használni",
"backToLogin": "Vissza a Bejelentkezéshez",
"forgotPassword": "Elfelejtetted a jelszavad?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Engedélyezés",
"Content-Disposition": "Tartalom-Diszpozíció",
"waitingForAuthorization": "Várakozás az engedélyezésre...",
"waitingForGoogleAuthorization": "Várakozás a Google engedélyére...",
"waitingForOpenAIAuthorization": "Várakozás az OpenAI engedélyezésére...",
"waitingForAntigravityAuthorization": "Várakozás az Antigravitációs engedélyezésre...",
"waitingForIFlowAuthorization": "Várakozás az iFlow engedélyezésére...",
"exchangingCodeForTokens": "Kód cseréje tokenre..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Szövegfelolvasó generálás (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Szövegbeágyazás generálása (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Adatvédelmi szabályzat",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Egyszerű csevegés",
"streaming": "Streaming",
"system-prompt": "Rendszer prompt",
"thinking": "Gondolkodás",
"tool-calling": "Szerszámhívás",
"multi-turn": "Többfordulós"
},
"templateDescriptions": {
"simple-chat": "Alapvető chat-sablon rendszerüzenettel",
"streaming": "Sablon a válaszok streameléséhez",
"system-prompt": "Sablon egyéni rendszerkéréssel",
"thinking": "Sablon érveléssel/gondolkodó költségvetéssel",
"tool-calling": "Sablon az eszköz/funkció hívásához",
"multi-turn": "Sablon többfordulós beszélgetésekhez"
},
"templatePayloads": {
"simpleChat": {
"system": "Segítőkész AI-asszisztens vagy.",
"userGreeting": "Sziasztok! Hogyan segíthetek ma?"
},
"streaming": {
"prompt": "Írj egy történetet róla"
},
"systemPrompt": {
"question": "Mi az élet értelme?",
"systemInstruction": "Adjon megfontolt, filozófiai választ."
},
"thinking": {
"question": "Magyarázza el a kvantumszámítást!"
},
"toolCalling": {
"cityNameDescription": "A város neve, amelyhez időjárást kell kérni",
"toolDescription": "Az adott hely aktuális időjárásának megtekintése",
"userWeather": "Milyen az idő Tokióban?"
},
"multiTurn": {
"system": "Segítőkész asszisztens vagy.",
"assistantExample": "Szívesen segítek ebben.",
"userInitial": "Segítségre van szükségem",
"userFollowUp": "Kifejtenéd ezt bővebben?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratis",
"skipToContent": "Lewati ke konten",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Daftar Alat yang Diizinkan",
"TOOL_DENYLIST": "Daftar Penolakan Alat",
"Failed to save pricing": "Gagal menyimpan harga",
"Failed to reset pricing": "Gagal menyetel ulang harga",
"apikey": "Kunci API",
"http": "HTTP"
},
"sidebar": {
"home": "Rumah",
@@ -179,7 +257,11 @@
"requestsShort": "{count} permintaan",
"providerModelsTitle": "{provider} - Model",
"copiedModel": "Disalin: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Perbarui sekarang",
"updating": "Memperbarui...",
"updateAvailableDesc": "Versi baru tersedia. Klik untuk memperbarui.",
"updateStarted": "Pembaruan dimulai..."
},
"analytics": {
"title": "Analisis",
@@ -535,6 +617,9 @@
"title": "Tambahkan Konfigurasi Model",
"desc": "Tambahkan konfigurasi berikut ke array model Anda:"
}
},
"notes": {
"0": "Continue menggunakan file konfigurasi JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode memerlukan konfigurasi API key.",
"1": "Setel base URL ke endpoint OmniRoute Anda."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro memerlukan akun Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Penyelesaian Obrolan",
"importingModels": "Mengimpor...",
"importFromModels": "Impor dari /models",
"clearAllModels": "Hapus Semua Model",
"clearAllModelsConfirm": "Apakah Anda yakin ingin menghapus semua model untuk penyedia ini? Tindakan ini tidak dapat dibatalkan.",
"clearAllModelsSuccess": "Semua model dihapus",
"clearAllModelsFailed": "Gagal menghapus model",
"addConnectionToImport": "Tambahkan koneksi untuk mengaktifkan impor.",
"noModelsConfigured": "Tidak ada model yang dikonfigurasi",
"connectionCount": "{count} koneksi",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Sinkronisasi Otomatis",
"autoSyncTooltip": "Segarkan daftar model secara otomatis setiap 24 jam (dapat dikonfigurasi melalui MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Sinkronisasi otomatis diaktifkan — model akan disegarkan secara berkala",
"autoSyncDisabled": "Sinkronisasi otomatis dinonaktifkan",
"autoSyncToggleFailed": "Gagal mengaktifkan sinkronisasi otomatis",
"clearAllModels": "Hapus Semua Model",
"clearAllModelsConfirm": "Apakah Anda yakin ingin menghapus semua model untuk penyedia ini?",
"clearAllModelsSuccess": "Semua model dihapus",
"clearAllModelsFailed": "Gagal menghapus model"
},
"settings": {
"title": "Pengaturan",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Mulai ulang server - server akan menggunakan kata sandi baru",
"backToLogin": "Kembali ke Masuk",
"forgotPassword": "Lupa kata sandi?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "Otorisasi",
"Content-Disposition": "Disposisi Konten"
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"troubleshootingCodexFamily": "Untuk model keluarga GitHub Codex, pertahankan model sebagai gh/codex-model; router memilih / merespons secara otomatis.",
"troubleshootingTestConnection": "Gunakan Dasbor > Penyedia > Uji Koneksi sebelum menguji dari IDE atau klien eksternal.",
"troubleshootingCircuitBreaker": "Jika penyedia menunjukkan pemutus sirkuit terbuka, tunggu hingga cooldown atau periksa halaman Kesehatan untuk detailnya.",
"troubleshootingOAuth": "Untuk penyedia OAuth, autentikasi ulang jika masa berlaku token habis. Periksa indikator status kartu penyedia."
"troubleshootingOAuth": "Untuk penyedia OAuth, autentikasi ulang jika masa berlaku token habis. Periksa indikator status kartu penyedia.",
"endpointSpeechNote": "Generasi text-to-speech (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generasi embedding teks (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Kebijakan Privasi",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "Anda adalah asisten AI yang membantu.",
"userGreeting": "Halo! Apa yang bisa saya bantu hari ini?"
},
"streaming": {
"prompt": "Tulis cerita tentang"
},
"systemPrompt": {
"question": "Apa arti hidup?",
"systemInstruction": "Berikan jawaban yang bijaksana dan filosofis."
},
"thinking": {
"question": "Jelaskan komputasi kuantum"
},
"toolCalling": {
"cityNameDescription": "Nama kota untuk mengetahui cuaca",
"toolDescription": "Dapatkan cuaca terkini untuk suatu lokasi",
"userWeather": "Bagaimana cuaca di Tokyo?"
},
"multiTurn": {
"system": "Anda adalah asisten yang membantu.",
"assistantExample": "Saya akan dengan senang hati membantu Anda dalam hal itu.",
"userInitial": "Saya butuh bantuan",
"userFollowUp": "Bisakah Anda menjelaskannya lebih lanjut?"
}
}
}
File diff suppressed because it is too large Load Diff
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratuito",
"skipToContent": "Vai al contenuto",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Lista consentita degli strumenti",
"TOOL_DENYLIST": "Elenco negati dello strumento",
"Failed to save pricing": "Impossibile salvare i prezzi",
"Failed to reset pricing": "Impossibile reimpostare i prezzi",
"apikey": "Chiave API",
"http": "HTTP"
},
"sidebar": {
"home": "Casa",
@@ -179,7 +257,11 @@
"requestsShort": "{count} richieste",
"providerModelsTitle": "{provider} - Modelli",
"copiedModel": "Copiato: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Aggiorna ora",
"updating": "Aggiornamento...",
"updateAvailableDesc": "Una nuova versione è disponibile. Fai clic per aggiornare.",
"updateStarted": "Aggiornamento avviato..."
},
"analytics": {
"title": "Analitica",
@@ -535,6 +617,9 @@
"title": "Aggiungi configurazione modello",
"desc": "Aggiungi la seguente configurazione all'array dei modelli:"
}
},
"notes": {
"0": "Continue usa un file di configurazione JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode richiede la configurazione della chiave API.",
"1": "Imposta l'URL base sul tuo endpoint OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro richiede un account Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Completamenti della chat",
"importingModels": "Importazione...",
"importFromModels": "Importa da /modelli",
"clearAllModels": "Cancella tutti i modelli",
"clearAllModelsConfirm": "Sei sicuro di voler rimuovere tutti i modelli per questo provider? Questa azione non può essere annullata.",
"clearAllModelsSuccess": "Tutti i modelli cancellati",
"clearAllModelsFailed": "Impossibile cancellare i modelli",
"addConnectionToImport": "Aggiungi una connessione per abilitare l'importazione.",
"noModelsConfigured": "Nessun modello configurato",
"connectionCount": "{count} connessione/i",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Sincronizzazione automatica",
"autoSyncTooltip": "Aggiorna automaticamente l'elenco dei modelli ogni 24 ore (configurabile tramite MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Sincronizzazione automatica abilitata — i modelli verranno aggiornati periodicamente",
"autoSyncDisabled": "Sincronizzazione automatica disabilitata",
"autoSyncToggleFailed": "Impossibile attivare la sincronizzazione automatica",
"clearAllModels": "Cancella tutti i modelli",
"clearAllModelsConfirm": "Sei sicuro di voler rimuovere tutti i modelli per questo provider?",
"clearAllModelsSuccess": "Tutti i modelli cancellati",
"clearAllModelsFailed": "Impossibile cancellare i modelli"
},
"settings": {
"title": "Impostazioni",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Riavvia il server: utilizzerà la nuova password",
"backToLogin": "Torna all'accesso",
"forgotPassword": "Ha dimenticato la password?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "Autorizzazione",
"Content-Disposition": "Disposizione del contenuto"
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Generazione sintesi vocale (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generazione embedding di testo (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "politica sulla riservatezza",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "Sei un utile assistente AI.",
"userGreeting": "Ciao! Come posso aiutarti oggi?"
},
"streaming": {
"prompt": "Scrivi una storia su"
},
"systemPrompt": {
"question": "Qual è il significato della vita?",
"systemInstruction": "Fornisci una risposta ponderata e filosofica."
},
"thinking": {
"question": "Spiegare l'informatica quantistica"
},
"toolCalling": {
"cityNameDescription": "Il nome della città per la quale ottenere il meteo",
"toolDescription": "Ottieni il meteo attuale per una località",
"userWeather": "Che tempo fa a Tokio?"
},
"multiTurn": {
"system": "Sei un assistente utile.",
"assistantExample": "Sarei felice di aiutarti in questo.",
"userInitial": "Ho bisogno di aiuto con",
"userFollowUp": "Puoi approfondire questo argomento?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "無料",
"skipToContent": "コンテンツにスキップ",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "ツールの許可リスト",
"TOOL_DENYLIST": "ツール拒否リスト",
"Failed to save pricing": "価格設定を保存できませんでした",
"Failed to reset pricing": "価格設定のリセットに失敗しました",
"apikey": "APIキー",
"http": "HTTP"
},
"sidebar": {
"home": "ホーム",
@@ -179,7 +257,11 @@
"requestsShort": "{count} 件",
"providerModelsTitle": "{provider} - モデル",
"copiedModel": "コピーしました: {model}",
"aliasLabel": "別名"
"aliasLabel": "別名",
"updateNow": "今すぐ更新",
"updating": "更新中...",
"updateAvailableDesc": "新しいバージョンが利用可能です。クリックして更新。",
"updateStarted": "更新を開始しました..."
},
"analytics": {
"title": "分析",
@@ -535,6 +617,9 @@
"title": "モデル構成の追加",
"desc": "次の構成をモデル配列に追加します。"
}
},
"notes": {
"0": "ContinueはJSON設定ファイルを使用します。"
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCodeはAPIキー設定が必要です。",
"1": "ベースURLをOmniRouteエンドポイントに設定してください。"
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "KiroはAmazonアカウントが必要です。"
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "チャットの完了",
"importingModels": "インポート中...",
"importFromModels": "/models からインポート",
"clearAllModels": "すべてのモデルを削除",
"clearAllModelsConfirm": "このプロバイダーのすべてのモデルを削除してもよろしいですか?この操作は元に戻せません。",
"clearAllModelsSuccess": "すべてのモデルを削除しました",
"clearAllModelsFailed": "モデルの削除に失敗しました",
"addConnectionToImport": "接続を追加してインポートを有効にします。",
"noModelsConfigured": "モデルが設定されていません",
"connectionCount": "{count} 接続",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "自動同期",
"autoSyncTooltip": "24時間ごとにモデルリストを自動更新(MODEL_SYNC_INTERVAL_HOURSで設定可能)",
"autoSyncEnabled": "自動同期有効 — モデルは定期的に更新されます",
"autoSyncDisabled": "自動同期無効",
"autoSyncToggleFailed": "自動同期の切り替えに失敗",
"clearAllModels": "すべてのモデルを削除",
"clearAllModelsConfirm": "このプロバイダーのすべてのモデルを削除してもよろしいですか?",
"clearAllModelsSuccess": "すべてのモデルを削除しました",
"clearAllModelsFailed": "モデルの削除に失敗"
},
"settings": {
"title": "設定",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "サーバーを再起動すると新しいパスワードが適用されます",
"backToLogin": "ログインに戻る",
"forgotPassword": "パスワードをお忘れですか?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "認可",
"Content-Disposition": "コンテンツの配置"
},
"landing": {
"brandName": "オムニルート",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "テキスト読み上げ(ElevenLabs、OpenAI TTS)。",
"endpointEmbeddingsNote": "テキスト埋め込み生成(OpenAI、Cohere、Voyage)。"
},
"legal": {
"privacyPolicy": "プライバシーポリシー",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "あなたは役に立つ AI アシスタントです。",
"userGreeting": "こんにちは!今日はどのようにお手伝いできますか?"
},
"streaming": {
"prompt": "~についての話を書いてください"
},
"systemPrompt": {
"question": "人生の意味とは何でしょうか?",
"systemInstruction": "思慮深く哲学的な答えを提供してください。"
},
"thinking": {
"question": "量子コンピューティングについて説明する"
},
"toolCalling": {
"cityNameDescription": "天気を取得する都市の名前",
"toolDescription": "場所の現在の天気を取得する",
"userWeather": "東京の天気はどうですか?"
},
"multiTurn": {
"system": "あなたは役に立つアシスタントです。",
"assistantExample": "喜んでお手伝いさせていただきます。",
"userInitial": "助けが必要です",
"userFollowUp": "それについて詳しく教えてもらえますか?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "무료",
"skipToContent": "콘텐츠로 건너뛰기",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "구글",
"TOOL_ALLOWLIST": "도구 허용 목록",
"TOOL_DENYLIST": "도구 거부 목록",
"Failed to save pricing": "가격을 저장하지 못했습니다.",
"Failed to reset pricing": "가격을 재설정하지 못했습니다.",
"apikey": "API 키",
"http": "HTTP"
},
"sidebar": {
"home": "홈",
@@ -179,7 +257,11 @@
"requestsShort": "{count} 요청",
"providerModelsTitle": "{provider} - 모델",
"copiedModel": "복사됨: {model}",
"aliasLabel": "별칭"
"aliasLabel": "별칭",
"updateNow": "지금 업데이트",
"updating": "업데이트 중...",
"updateAvailableDesc": "새 버전을 사용할 수 있습니다. 클릭하여 업데이트하세요.",
"updateStarted": "업데이트 시작됨..."
},
"analytics": {
"title": "분석",
@@ -535,6 +617,9 @@
"title": "모델 구성 추가",
"desc": "모델 배열에 다음 구성을 추가합니다."
}
},
"notes": {
"0": "Continue는 JSON 설정 파일을 사용합니다."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode는 API 키 구성이 필요합니다.",
"1": "기본 URL을 OmniRoute 엔드포인트로 설정하세요."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro는 Amazon 계정이 필요합니다."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "채팅 완료",
"importingModels": "가져오는 중...",
"importFromModels": "/models에서 가져오기",
"clearAllModels": "모든 모델 지우기",
"clearAllModelsConfirm": "이 제공자의 모든 모델을 제거하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
"clearAllModelsSuccess": "모든 모델이 지워졌습니다",
"clearAllModelsFailed": "모델 지우기 실패",
"addConnectionToImport": "가져오기를 활성화하려면 연결을 추가하세요.",
"noModelsConfigured": "구성된 모델이 없습니다.",
"connectionCount": "{count} 연결",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "자동 동기화",
"autoSyncTooltip": "24시간마다 모델 목록 자동 업데이트 (MODEL_SYNC_INTERVAL_HOURS로 구성 가능)",
"autoSyncEnabled": "자동 동기화 활성화 — 모델이 주기적으로 업데이트됩니다",
"autoSyncDisabled": "자동 동기화 비활성화",
"autoSyncToggleFailed": "자동 동기화 전환 실패",
"clearAllModels": "모든 모델 삭제",
"clearAllModelsConfirm": "이 공급자의 모든 모델을 제거하시겠습니까?",
"clearAllModelsSuccess": "모든 모델 삭제됨",
"clearAllModelsFailed": "모델 삭제 실패"
},
"settings": {
"title": "설정",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "서버를 다시 시작하세요. 새 비밀번호가 사용됩니다.",
"backToLogin": "로그인으로 돌아가기",
"forgotPassword": "비밀번호를 잊으셨나요?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "승인",
"Content-Disposition": "컨텐츠 처리"
},
"landing": {
"brandName": "옴니루트",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "음성 합성 (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "텍스트 임베딩 생성 (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "개인 정보 보호 정책",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "당신은 유용한 AI 비서입니다.",
"userGreeting": "안녕하세요! 오늘은 무엇을 도와드릴까요?"
},
"streaming": {
"prompt": "에 관한 이야기를 써 보세요."
},
"systemPrompt": {
"question": "삶의 의미는 무엇입니까?",
"systemInstruction": "사려 깊고 철학적인 답변을 제공하세요."
},
"thinking": {
"question": "양자 컴퓨팅 설명"
},
"toolCalling": {
"cityNameDescription": "날씨를 확인할 도시의 이름",
"toolDescription": "특정 위치의 현재 날씨 확인",
"userWeather": "도쿄 날씨는 어떻습니까?"
},
"multiTurn": {
"system": "당신은 도움이 되는 조수입니다.",
"assistantExample": "기꺼이 도와드리겠습니다.",
"userInitial": "도움이 필요해요",
"userFollowUp": "그것에 대해 자세히 설명해주실 수 있나요?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Percuma",
"skipToContent": "Langkau ke kandungan",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Terima",
"accountId": "ID Akaun",
"alias": "Alias",
"apiKeyId": "ID Kunci API",
"apiKeyName": "Nama Kunci API",
"apiKeySecret": "Rahsia Kunci API",
"authorization": "Keizinan",
"content-type": "Jenis Kandungan",
"content-length": "Panjang Kandungan",
"cookie": "Kuki",
"file": "Fail",
"host": "hos",
"id": "ID",
"import": "Import",
"limit": "had",
"offset": "Offset",
"open": "Buka",
"origin": "asal usul",
"promptTokens": "Token Gesaan",
"completionTokens": "Token Penyiapan",
"totalTokens": "Jumlah Token",
"rawModel": "Model Mentah",
"scope": "Skop",
"skill": "Kemahiran",
"sortBy": "Isih Mengikut",
"sortOrder": "Susun Susunan",
"tab": "Tab",
"text": "Teks",
"textarea": "Textarea",
"tool": "alat",
"toolId": "ID alat",
"web": "Web",
"whereUsed": "Di mana Digunakan",
"whitelist": "Senarai putih",
"blacklist": "Senarai hitam",
"resolve": "Selesaikan",
"force": "Paksa",
"base64url": "URL Base64",
"hex": "Hex",
"range": "Julat",
"component": "Komponen",
"redirect_uri": "Ubah hala URI",
"idempotency-key": "Kunci Idepotency",
"error_description": "Penerangan Ralat",
"code": "Kod",
"compatible": "serasi",
"chat-completions": "Selesai Sembang",
"oauth": "OAuth",
"auth_token": "Token Pengesahan",
"crypto": "Kripto",
"hours": "jam",
"selfsigned": "Ditandatangani sendiri",
"proxy_id": "ID proksi",
"proxyId": "ID proksi",
"connectionId": "ID Sambungan",
"resolveConnectionId": "Selesaikan ID Sambungan",
"resolve_connection_id": "Selesaikan ID Sambungan",
"scope_id": "ID Skop",
"scopeId": "ID Skop",
"jwtSecret": "Rahsia JWT",
"keytar": "Keytar",
"better-sqlite3": "lebih baik-sqlite3",
"undici": "undici",
"builder-id": "ID Pembina",
"musicDesc": "Penerangan Muzik",
"musicGeneration": "Penjanaan Muzik",
"idc": "IDC",
"cloud-status-changed": "Status awan berubah",
"where_used": "Di mana Digunakan",
"windowMs": "Tetingkap (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Senarai Benarkan Alat",
"TOOL_DENYLIST": "Senarai Penafian Alat",
"Failed to save pricing": "Gagal menyimpan harga",
"Failed to reset pricing": "Gagal menetapkan semula harga",
"apikey": "Kunci API",
"http": "HTTP"
},
"sidebar": {
"home": "Rumah",
@@ -179,7 +257,11 @@
"requestsShort": "{count} permintaan",
"providerModelsTitle": "{provider} - Model",
"copiedModel": "Disalin: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Kemas kini Sekarang",
"updating": "Mengemas kini...",
"updateAvailableDesc": "Versi baharu tersedia. Klik untuk mengemas kini.",
"updateStarted": "Kemas kini bermula..."
},
"analytics": {
"title": "Analitis",
@@ -535,6 +617,9 @@
"title": "Tambah Konfigurasi Model",
"desc": "Tambahkan konfigurasi berikut pada tatasusunan model anda:"
}
},
"notes": {
"0": "Teruskan menggunakan fail konfigurasi JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode memerlukan konfigurasi kunci API.",
"1": "Tetapkan URL asas kepada titik akhir OmniRoute anda."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro memerlukan akaun Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Selesai Sembang",
"importingModels": "Mengimport...",
"importFromModels": "Import daripada /models",
"clearAllModels": "Padam Semua Model",
"clearAllModelsConfirm": "Adakah anda pasti mahu membuang semua model untuk pembekal ini? Tindakan ini tidak boleh dibuat asal.",
"clearAllModelsSuccess": "Semua model dipadamkan",
"clearAllModelsFailed": "Gagal memadamkan model",
"addConnectionToImport": "Tambahkan sambungan untuk mendayakan pengimportan.",
"noModelsConfigured": "Tiada model yang dikonfigurasikan",
"connectionCount": "{count} sambungan",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Auto-Segerak",
"autoSyncTooltip": "Muat semula senarai model secara automatik setiap 24j (boleh dikonfigurasikan melalui MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Autosegerak didayakan — model akan dimuat semula secara berkala",
"autoSyncDisabled": "Autosegerak dilumpuhkan",
"autoSyncToggleFailed": "Gagal untuk menogol autosegerak",
"clearAllModels": "Kosongkan Semua Model",
"clearAllModelsConfirm": "Adakah anda pasti mahu mengalih keluar semua model untuk pembekal ini? Ini tidak boleh dibuat asal.",
"clearAllModelsSuccess": "Semua model dibersihkan",
"clearAllModelsFailed": "Gagal mengosongkan model"
},
"settings": {
"title": "tetapan",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Mulakan semula pelayan - ia akan menggunakan kata laluan baharu",
"backToLogin": "Kembali ke Log Masuk",
"forgotPassword": "Lupa kata laluan?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Keizinan",
"Content-Disposition": "Kandungan-Pelupusan",
"waitingForAuthorization": "Menunggu kebenaran...",
"waitingForGoogleAuthorization": "Menunggu kebenaran Google...",
"waitingForOpenAIAuthorization": "Menunggu kebenaran OpenAI...",
"waitingForAntigravityAuthorization": "Menunggu kebenaran Antigraviti...",
"waitingForIFlowAuthorization": "Menunggu kebenaran iFlow...",
"exchangingCodeForTokens": "Bertukar kod untuk token..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Penjanaan teks ke pertuturan (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Penjanaan pembenaman teks (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Dasar Privasi",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Sembang Mudah",
"streaming": "Penstriman",
"system-prompt": "Gesaan Sistem",
"thinking": "Berfikir",
"tool-calling": "Alat Panggilan",
"multi-turn": "Pelbagai pusingan"
},
"templateDescriptions": {
"simple-chat": "Templat sembang asas dengan mesej sistem",
"streaming": "Templat untuk respons penstriman",
"system-prompt": "Templat dengan gesaan sistem tersuai",
"thinking": "Templat dengan bajet penaakulan/pemikiran",
"tool-calling": "Templat untuk panggilan alat/fungsi",
"multi-turn": "Templat untuk perbualan berbilang pusingan"
},
"templatePayloads": {
"simpleChat": {
"system": "Anda seorang pembantu AI yang membantu.",
"userGreeting": "hello! Bagaimana saya boleh membantu anda hari ini?"
},
"streaming": {
"prompt": "Tulis cerita tentang"
},
"systemPrompt": {
"question": "Apakah erti kehidupan?",
"systemInstruction": "Berikan jawapan yang bernas dan berfalsafah."
},
"thinking": {
"question": "Terangkan pengkomputeran kuantum"
},
"toolCalling": {
"cityNameDescription": "Nama bandar untuk mendapatkan cuaca",
"toolDescription": "Dapatkan cuaca semasa untuk lokasi",
"userWeather": "Bagaimanakah cuaca di Tokyo?"
},
"multiTurn": {
"system": "Anda seorang pembantu yang membantu.",
"assistantExample": "Saya berbesar hati untuk membantu anda dengan itu.",
"userInitial": "Saya perlukan bantuan",
"userFollowUp": "Bolehkah anda menghuraikan perkara itu?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratis",
"skipToContent": "Ga naar de inhoud",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Accepteer",
"accountId": "Account-ID",
"alias": "Alias",
"apiKeyId": "API-sleutel-ID",
"apiKeyName": "API-sleutelnaam",
"apiKeySecret": "API-sleutelgeheim",
"authorization": "Autorisatie",
"content-type": "Inhoudstype",
"content-length": "Inhoud lengte",
"cookie": "Koekje",
"file": "Bestand",
"host": "Gastheer",
"id": "Identiteitskaart",
"import": "Importeren",
"limit": "Limiet",
"offset": "Offset",
"open": "Openen",
"origin": "Oorsprong",
"promptTokens": "Prompt-tokens",
"completionTokens": "Voltooiingstokens",
"totalTokens": "Totaal tokens",
"rawModel": "Ruw model",
"scope": "Reikwijdte",
"skill": "Vaardigheid",
"sortBy": "Sorteer op",
"sortOrder": "Sorteervolgorde",
"tab": "Tab",
"text": "Tekst",
"textarea": "Tekstgebied",
"tool": "Gereedschap",
"toolId": "Gereedschaps-ID",
"web": "Web",
"whereUsed": "Waar gebruikt",
"whitelist": "Witte lijst",
"blacklist": "Zwarte lijst",
"resolve": "Oplossen",
"force": "Kracht",
"base64url": "Base64-URL",
"hex": "Hex",
"range": "Bereik",
"component": "Onderdeel",
"redirect_uri": "Omleidings-URI",
"idempotency-key": "Idempotentie sleutel",
"error_description": "Foutbeschrijving",
"code": "Codeer",
"compatible": "Compatibel",
"chat-completions": "Chat-voltooiingen",
"oauth": "OAuth",
"auth_token": "Authenticatietoken",
"crypto": "Crypto",
"hours": "Uur",
"selfsigned": "Zelf ondertekend",
"proxy_id": "Proxy-ID",
"proxyId": "Proxy-ID",
"connectionId": "Verbindings-ID",
"resolveConnectionId": "Verbindings-ID oplossen",
"resolve_connection_id": "Verbindings-ID oplossen",
"scope_id": "Bereik-ID",
"scopeId": "Bereik-ID",
"jwtSecret": "JWT-geheim",
"keytar": "Keytar",
"better-sqlite3": "beter-sqlite3",
"undici": "undici",
"builder-id": "Bouwer-ID",
"musicDesc": "Muziekbeschrijving",
"musicGeneration": "Muziek generatie",
"idc": "IDC",
"cloud-status-changed": "Cloudstatus gewijzigd",
"where_used": "Waar gebruikt",
"windowMs": "Venster (ms)",
"social-github": "GitHub",
"social-google": "Googlen",
"TOOL_ALLOWLIST": "Tool Toelatingslijst",
"TOOL_DENYLIST": "Tool-weigerlijst",
"Failed to save pricing": "Kan de prijzen niet opslaan",
"Failed to reset pricing": "Kan de prijzen niet opnieuw instellen",
"apikey": "API-sleutel",
"http": "HTTP"
},
"sidebar": {
"home": "Thuis",
@@ -179,7 +257,11 @@
"requestsShort": "{count} vereisten",
"providerModelsTitle": "{provider} - Modellen",
"copiedModel": "Gekopieerd: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Nu bijwerken",
"updating": "Updaten...",
"updateAvailableDesc": "Er is een nieuwe versie beschikbaar. Klik om bij te werken.",
"updateStarted": "Update gestart..."
},
"analytics": {
"title": "Analyses",
@@ -535,6 +617,9 @@
"title": "Modelconfiguratie toevoegen",
"desc": "Voeg de volgende configuratie toe aan uw modellenarray:"
}
},
"notes": {
"0": "Doorgaan gebruikt het JSON-configuratiebestand."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode vereist API-sleutelconfiguratie.",
"1": "Stel de basis-URL in op uw OmniRoute-eindpunt."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Voor Kiro is een Amazon-account vereist."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Chat-voltooiingen",
"importingModels": "Importeren...",
"importFromModels": "Importeren uit /modellen",
"clearAllModels": "Alle modellen wissen",
"clearAllModelsConfirm": "Weet je zeker dat je alle modellen voor deze provider wilt verwijderen? Dit kan niet ongedaan worden gemaakt.",
"clearAllModelsSuccess": "Alle modellen gewist",
"clearAllModelsFailed": "Kon modellen niet wissen",
"addConnectionToImport": "Voeg een verbinding toe om importeren mogelijk te maken.",
"noModelsConfigured": "Geen modellen geconfigureerd",
"connectionCount": "{count} verbinding(en)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Automatische synchronisatie",
"autoSyncTooltip": "Modellijst automatisch elke 24 uur vernieuwen (configureerbaar via MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automatische synchronisatie ingeschakeld: modellen worden periodiek vernieuwd",
"autoSyncDisabled": "Automatische synchronisatie uitgeschakeld",
"autoSyncToggleFailed": "Kan automatische synchronisatie niet in- of uitschakelen",
"clearAllModels": "Wis alle modellen",
"clearAllModelsConfirm": "Weet u zeker dat u alle modellen voor deze aanbieder wilt verwijderen? Dit kan niet ongedaan worden gemaakt.",
"clearAllModelsSuccess": "Alle modellen gewist",
"clearAllModelsFailed": "Kan modellen niet wissen"
},
"settings": {
"title": "Instellingen",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Start de server opnieuw op. Deze gebruikt het nieuwe wachtwoord",
"backToLogin": "Terug naar Inloggen",
"forgotPassword": "Wachtwoord vergeten?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Autorisatie",
"Content-Disposition": "Inhoud-dispositie",
"waitingForAuthorization": "Wachten op toestemming...",
"waitingForGoogleAuthorization": "Wachten op Google-autorisatie...",
"waitingForOpenAIAuthorization": "Wachten op OpenAI-autorisatie...",
"waitingForAntigravityAuthorization": "Wachten op toestemming voor anti-zwaartekracht...",
"waitingForIFlowAuthorization": "Wachten op iFlow-autorisatie...",
"exchangingCodeForTokens": "Code uitwisselen voor tokens..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Tekst-naar-spraakgeneratie (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Genereren van tekstinsluiting (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Privacybeleid",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Eenvoudig chatten",
"streaming": "Streamen",
"system-prompt": "Systeemprompt",
"thinking": "Denken",
"tool-calling": "Gereedschap bellen",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basischatsjabloon met systeembericht",
"streaming": "Sjabloon voor het streamen van antwoorden",
"system-prompt": "Sjabloon met aangepaste systeemprompt",
"thinking": "Sjabloon met redeneer-/denkbudget",
"tool-calling": "Sjabloon voor het oproepen van gereedschap/functies",
"multi-turn": "Sjabloon voor gesprekken met meerdere beurten"
},
"templatePayloads": {
"simpleChat": {
"system": "Je bent een behulpzame AI-assistent.",
"userGreeting": "Hallo! Hoe kan ik je vandaag helpen?"
},
"streaming": {
"prompt": "Schrijf een verhaal over"
},
"systemPrompt": {
"question": "Wat is de zin van het leven?",
"systemInstruction": "Geef een doordacht, filosofisch antwoord."
},
"thinking": {
"question": "Kwantumcomputers uitleggen"
},
"toolCalling": {
"cityNameDescription": "De naam van de stad waarvoor u het weer wilt ophalen",
"toolDescription": "Ontvang het huidige weer voor een locatie",
"userWeather": "Hoe is het weer in Tokio?"
},
"multiTurn": {
"system": "Je bent een behulpzame assistent.",
"assistantExample": "Ik help je daar graag mee.",
"userInitial": "Ik heb hulp nodig bij",
"userFollowUp": "Kunt u dat nader toelichten?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratis",
"skipToContent": "Gå til innhold",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Godta",
"accountId": "Konto-ID",
"alias": "Alias",
"apiKeyId": "API-nøkkel-ID",
"apiKeyName": "API-nøkkelnavn",
"apiKeySecret": "API-nøkkelhemmelighet",
"authorization": "Autorisasjon",
"content-type": "Innholdstype",
"content-length": "Innholdslengde",
"cookie": "Cookie",
"file": "Fil",
"host": "Vert",
"id": "ID",
"import": "Importer",
"limit": "Begrens",
"offset": "Offset",
"open": "Åpne",
"origin": "Opprinnelse",
"promptTokens": "Spør Tokens",
"completionTokens": "Fullføringssymboler",
"totalTokens": "Totalt tokens",
"rawModel": "Rå modell",
"scope": "Omfang",
"skill": "Ferdighet",
"sortBy": "Sorter etter",
"sortOrder": "Sorteringsrekkefølge",
"tab": "Tab",
"text": "Tekst",
"textarea": "Tekstområde",
"tool": "Verktøy",
"toolId": "Verktøy-ID",
"web": "Web",
"whereUsed": "Hvor det brukes",
"whitelist": "Hviteliste",
"blacklist": "Svarteliste",
"resolve": "Løs",
"force": "Kraft",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Rekkevidde",
"component": "Komponent",
"redirect_uri": "Omdiriger URI",
"idempotency-key": "Idempotensnøkkel",
"error_description": "Feilbeskrivelse",
"code": "Kode",
"compatible": "Kompatibel",
"chat-completions": "Chatfullføringer",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "Krypto",
"hours": "Timer",
"selfsigned": "Selvsignert",
"proxy_id": "Proxy ID",
"proxyId": "Proxy ID",
"connectionId": "Tilkoblings-ID",
"resolveConnectionId": "Løs tilkoblings-ID",
"resolve_connection_id": "Løs tilkoblings-ID",
"scope_id": "Omfang ID",
"scopeId": "Omfang ID",
"jwtSecret": "JWT-hemmelighet",
"keytar": "Keytar",
"better-sqlite3": "bedre-sqlite3",
"undici": "undici",
"builder-id": "Byggherre-ID",
"musicDesc": "Musikkbeskrivelse",
"musicGeneration": "Musikkgenerasjon",
"idc": "IDC",
"cloud-status-changed": "Skystatus endret",
"where_used": "Hvor det brukes",
"windowMs": "Vindu (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Verktøytillatelsesliste",
"TOOL_DENYLIST": "Verktøyfornektelsesliste",
"Failed to save pricing": "Kunne ikke lagre prisen",
"Failed to reset pricing": "Kunne ikke tilbakestille priser",
"apikey": "API-nøkkel",
"http": "HTTP"
},
"sidebar": {
"home": "Hjem",
@@ -179,7 +257,11 @@
"requestsShort": "{count} req",
"providerModelsTitle": "{provider} - Modeller",
"copiedModel": "Kopiert: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Oppdater nå",
"updating": "Oppdaterer...",
"updateAvailableDesc": "En ny versjon er tilgjengelig. Klikk for å oppdatere.",
"updateStarted": "Oppdatering startet..."
},
"analytics": {
"title": "Analytics",
@@ -535,6 +617,9 @@
"title": "Legg til modellkonfig",
"desc": "Legg til følgende konfigurasjon til modellarrayet ditt:"
}
},
"notes": {
"0": "Continue bruker JSON-konfigurasjonsfilen."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode krever API-nøkkelkonfigurasjon.",
"1": "Angi basis-URL til OmniRoute-endepunktet."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro krever Amazon-konto."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Chatfullføringer",
"importingModels": "Importerer...",
"importFromModels": "Importer fra /models",
"clearAllModels": "Slett alle modeller",
"clearAllModelsConfirm": "Er du sikker på at du vil fjerne alle modeller for denne leverandøren? Dette kan ikke angres.",
"clearAllModelsSuccess": "Alle modeller slettet",
"clearAllModelsFailed": "Kunne ikke slette modeller",
"addConnectionToImport": "Legg til en tilkobling for å aktivere import.",
"noModelsConfigured": "Ingen modeller er konfigurert",
"connectionCount": "{count} tilkobling(er)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Auto-synkronisering",
"autoSyncTooltip": "Oppdater modelllisten automatisk hver 24. time (kan konfigureres via MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automatisk synkronisering aktivert modellene oppdateres med jevne mellomrom",
"autoSyncDisabled": "Automatisk synkronisering er deaktivert",
"autoSyncToggleFailed": "Kunne ikke slå på automatisk synkronisering",
"clearAllModels": "Fjern alle modeller",
"clearAllModelsConfirm": "Er du sikker på at du vil fjerne alle modellene for denne leverandøren? Dette kan ikke angres.",
"clearAllModelsSuccess": "Alle modeller ryddet",
"clearAllModelsFailed": "Kunne ikke slette modeller"
},
"settings": {
"title": "Innstillinger",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Start serveren på nytt - den vil bruke det nye passordet",
"backToLogin": "Tilbake til pålogging",
"forgotPassword": "Glemt passord?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Autorisasjon",
"Content-Disposition": "Innhold-Disposisjon",
"waitingForAuthorization": "Venter på autorisasjon...",
"waitingForGoogleAuthorization": "Venter på Google-autorisasjon...",
"waitingForOpenAIAuthorization": "Venter på OpenAI-godkjenning...",
"waitingForAntigravityAuthorization": "Venter på antigravity-autorisasjon...",
"waitingForIFlowAuthorization": "Venter på iFlow-autorisasjon...",
"exchangingCodeForTokens": "Utveksler kode for tokens..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Tekst-til-tale generering (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generering av tekstinnbygging (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Personvernerklæring",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Enkel chat",
"streaming": "Streaming",
"system-prompt": "Systemmelding",
"thinking": "Tenker",
"tool-calling": "Verktøyanrop",
"multi-turn": "Multisving"
},
"templateDescriptions": {
"simple-chat": "Grunnleggende chatmal med systemmelding",
"streaming": "Mal for strømmesvar",
"system-prompt": "Mal med tilpasset systemforespørsel",
"thinking": "Mal med resonnement/tenkebudsjett",
"tool-calling": "Mal for verktøy/funksjonskall",
"multi-turn": "Mal for samtaler med flere svinger"
},
"templatePayloads": {
"simpleChat": {
"system": "Du er en hjelpsom AI-assistent.",
"userGreeting": "Hei! Hvordan kan jeg hjelpe deg i dag?"
},
"streaming": {
"prompt": "Skriv en historie om"
},
"systemPrompt": {
"question": "Hva er meningen med livet?",
"systemInstruction": "Gi et gjennomtenkt, filosofisk svar."
},
"thinking": {
"question": "Forklar kvanteberegning"
},
"toolCalling": {
"cityNameDescription": "Navnet på byen å få vær for",
"toolDescription": "Få gjeldende vær for et sted",
"userWeather": "Hvordan er været i Tokyo?"
},
"multiTurn": {
"system": "Du er en hjelpsom assistent.",
"assistantExample": "Jeg hjelper deg gjerne med det.",
"userInitial": "Jeg trenger hjelp med",
"userFollowUp": "Kan du utdype det?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Libre",
"skipToContent": "Lumaktaw sa nilalaman",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Tanggapin",
"accountId": "Account ID",
"alias": "alyas",
"apiKeyId": "API Key ID",
"apiKeyName": "Pangalan ng API Key",
"apiKeySecret": "Lihim ng Key ng API",
"authorization": "Awtorisasyon",
"content-type": "Uri ng Nilalaman",
"content-length": "Haba ng Nilalaman",
"cookie": "Cookie",
"file": "file",
"host": "Host",
"id": "ID",
"import": "Mag-import",
"limit": "Limitahan",
"offset": "Offset",
"open": "Bukas",
"origin": "Pinagmulan",
"promptTokens": "Mga Prompt Token",
"completionTokens": "Mga Token ng Pagkumpleto",
"totalTokens": "Kabuuang Token",
"rawModel": "Hilaw na Modelo",
"scope": "Saklaw",
"skill": "Kasanayan",
"sortBy": "Pagbukud-bukurin Ayon",
"sortOrder": "Pagbukud-bukurin",
"tab": "Tab",
"text": "Text",
"textarea": "Textarea",
"tool": "Tool",
"toolId": "Tool ID",
"web": "Web",
"whereUsed": "Kung Saan Ginamit",
"whitelist": "Whitelist",
"blacklist": "Blacklist",
"resolve": "Lutasin",
"force": "Puwersa",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Saklaw",
"component": "Component",
"redirect_uri": "I-redirect ang URI",
"idempotency-key": "Idepotency Key",
"error_description": "Paglalarawan ng Error",
"code": "Code",
"compatible": "Magkatugma",
"chat-completions": "Mga Pagkumpleto ng Chat",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "Crypto",
"hours": "Oras",
"selfsigned": "Self-signed",
"proxy_id": "Proxy ID",
"proxyId": "Proxy ID",
"connectionId": "ID ng Koneksyon",
"resolveConnectionId": "Lutasin ang Connection ID",
"resolve_connection_id": "Lutasin ang Connection ID",
"scope_id": "ID ng Saklaw",
"scopeId": "ID ng Saklaw",
"jwtSecret": "Sikreto ng JWT",
"keytar": "Keytar",
"better-sqlite3": "mas mahusay-sqlite3",
"undici": "undici",
"builder-id": "ID ng Tagabuo",
"musicDesc": "Paglalarawan ng Musika",
"musicGeneration": "Pagbuo ng Musika",
"idc": "IDC",
"cloud-status-changed": "Nagbago ang status ng cloud",
"where_used": "Kung Saan Ginamit",
"windowMs": "Window (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Tool Allowlist",
"TOOL_DENYLIST": "Tool Denylist",
"Failed to save pricing": "Nabigong i-save ang pagpepresyo",
"Failed to reset pricing": "Nabigong i-reset ang pagpepresyo",
"apikey": "API Key",
"http": "HTTP"
},
"sidebar": {
"home": "Bahay",
@@ -179,7 +257,11 @@
"requestsShort": "{count} mga kahilingan",
"providerModelsTitle": "{provider} - Mga Modelo",
"copiedModel": "Nakopya: {model}",
"aliasLabel": "alyas"
"aliasLabel": "alyas",
"updateNow": "Update Ngayon",
"updating": "Ina-update...",
"updateAvailableDesc": "Available ang isang bagong bersyon. I-click para mag-update.",
"updateStarted": "Nagsimula ang pag-update..."
},
"analytics": {
"title": "Analytics",
@@ -535,6 +617,9 @@
"title": "Magdagdag ng Model Config",
"desc": "Idagdag ang sumusunod na configuration sa iyong array ng mga modelo:"
}
},
"notes": {
"0": "Magpatuloy ay gumagamit ng JSON config file."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Nangangailangan ang OpenCode ng configuration ng API key.",
"1": "Itakda ang base URL sa iyong OmniRoute endpoint."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Ang Kiro ay nangangailangan ng Amazon account."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Mga Pagkumpleto ng Chat",
"importingModels": "Ini-import...",
"importFromModels": "Mag-import mula sa /models",
"clearAllModels": "Burahin Lahat ng Modelo",
"clearAllModelsConfirm": "Sigurado ka bang gusto mong alisin lahat ng modelo para sa provider na ito? Hindi na ito mababawi.",
"clearAllModelsSuccess": "Lahat ng modelo ay nabura",
"clearAllModelsFailed": "Nabigong burahin ang mga modelo",
"addConnectionToImport": "Magdagdag ng koneksyon upang paganahin ang pag-import.",
"noModelsConfigured": "Walang mga modelong na-configure",
"connectionCount": "{count} (mga) koneksyon",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Auto-Sync",
"autoSyncTooltip": "Awtomatikong i-refresh ang listahan ng modelo tuwing 24h (mako-configure sa pamamagitan ng MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Pinagana ang auto-sync — pana-panahong magre-refresh ang mga modelo",
"autoSyncDisabled": "Na-disable ang auto-sync",
"autoSyncToggleFailed": "Nabigong i-toggle ang auto-sync",
"clearAllModels": "I-clear ang Lahat ng Modelo",
"clearAllModelsConfirm": "Sigurado ka bang gusto mong alisin ang lahat ng modelo para sa provider na ito? Hindi na ito maaaring bawiin.",
"clearAllModelsSuccess": "Na-clear ang lahat ng mga modelo",
"clearAllModelsFailed": "Nabigong i-clear ang mga modelo"
},
"settings": {
"title": "Mga setting",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "I-restart ang server - gagamitin nito ang bagong password",
"backToLogin": "Bumalik sa Login",
"forgotPassword": "Nakalimutan ang password?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Awtorisasyon",
"Content-Disposition": "Nilalaman-Disposisyon",
"waitingForAuthorization": "Naghihintay ng awtorisasyon...",
"waitingForGoogleAuthorization": "Naghihintay ng pahintulot ng Google...",
"waitingForOpenAIAuthorization": "Naghihintay ng awtorisasyon sa OpenAI...",
"waitingForAntigravityAuthorization": "Naghihintay ng awtorisasyon sa Antigravity...",
"waitingForIFlowAuthorization": "Naghihintay ng awtorisasyon ng iFlow...",
"exchangingCodeForTokens": "Pagpapalit ng code para sa mga token..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Pagbuo ng text-to-speech (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Pagbuo ng text embed (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Patakaran sa Privacy",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simpleng Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Nag-iisip",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Pangunahing template ng chat na may mensahe ng system",
"streaming": "Template para sa streaming na mga tugon",
"system-prompt": "Template na may pasadyang system prompt",
"thinking": "Template na may pangangatwiran/pag-iisip na badyet",
"tool-calling": "Template para sa tool/function na pagtawag",
"multi-turn": "Template para sa mga multi-turn na pag-uusap"
},
"templatePayloads": {
"simpleChat": {
"system": "Isa kang matulunging AI assistant.",
"userGreeting": "Hello! Paano kita matutulungan ngayon?"
},
"streaming": {
"prompt": "Sumulat ng isang kuwento tungkol sa"
},
"systemPrompt": {
"question": "Ano ang kahulugan ng buhay?",
"systemInstruction": "Magbigay ng maalalahanin, pilosopong sagot."
},
"thinking": {
"question": "Ipaliwanag ang quantum computing"
},
"toolCalling": {
"cityNameDescription": "Ang pangalan ng lungsod upang makakuha ng panahon para sa",
"toolDescription": "Kunin ang kasalukuyang panahon para sa isang lokasyon",
"userWeather": "Ano ang lagay ng panahon sa Tokyo?"
},
"multiTurn": {
"system": "Isa kang matulunging katulong.",
"assistantExample": "Ikalulugod kong tulungan ka niyan.",
"userInitial": "Kailangan ko ng tulong sa",
"userFollowUp": "Maaari mo bang ipaliwanag iyon?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Bezpłatny",
"skipToContent": "Przejdź do treści",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "Google'a",
"TOOL_ALLOWLIST": "Lista dozwolonych narzędzi",
"TOOL_DENYLIST": "Lista odrzuconych narzędzi",
"Failed to save pricing": "Nie udało się zapisać cen",
"Failed to reset pricing": "Nie udało się zresetować cen",
"apikey": "Klucz API",
"http": "HTTP"
},
"sidebar": {
"home": "Dom",
@@ -179,7 +257,11 @@
"requestsShort": "{count} wymagania",
"providerModelsTitle": "{provider} - Modele",
"copiedModel": "Skopiowano: {model}",
"aliasLabel": "pseudonim"
"aliasLabel": "pseudonim",
"updateNow": "Zaktualizuj",
"updating": "Aktualizowanie...",
"updateAvailableDesc": "Dostępna jest nowa wersja. Kliknij, aby zaktualizować.",
"updateStarted": "Aktualizacja rozpoczęta..."
},
"analytics": {
"title": "Analityka",
@@ -535,6 +617,9 @@
"title": "Dodaj konfigurację modelu",
"desc": "Dodaj następującą konfigurację do tablicy modeli:"
}
},
"notes": {
"0": "Continue używa pliku konfiguracyjnego JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode wymaga konfiguracji klucza API.",
"1": "Ustaw URL bazowy na swój endpoint OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro wymaga konta Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Zakończenia czatu",
"importingModels": "Importowanie...",
"importFromModels": "Importuj z /modele",
"clearAllModels": "Wyczyść wszystkie modele",
"clearAllModelsConfirm": "Czy na pewno chcesz usunąć wszystkie modele dla tego dostawcy? Tej operacji nie można cofnąć.",
"clearAllModelsSuccess": "Wszystkie modele wyczyszczone",
"clearAllModelsFailed": "Nie udało się wyczyścić modeli",
"addConnectionToImport": "Dodaj połączenie, aby umożliwić importowanie.",
"noModelsConfigured": "Nie skonfigurowano żadnych modeli",
"connectionCount": "{count} połączenia",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Automatyczna synchronizacja",
"autoSyncTooltip": "Automatycznie odświeża listę modeli co 24 godziny (konfigurowalne przez MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automatyczna synchronizacja włączona — modele będą okresowo odświeżane",
"autoSyncDisabled": "Automatyczna synchronizacja wyłączona",
"autoSyncToggleFailed": "Nie udało się przełączyć automatycznej synchronizacji",
"clearAllModels": "Wyczyść wszystkie modele",
"clearAllModelsConfirm": "Czy na pewno chcesz usunąć wszystkie modele dla tego dostawcy?",
"clearAllModelsSuccess": "Wszystkie modele wyczyszczone",
"clearAllModelsFailed": "Nie udało się wyczyścić modeli"
},
"settings": {
"title": "Ustawienia",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Zrestartuj serwer - będzie używał nowego hasła",
"backToLogin": "Powrót do logowania",
"forgotPassword": "Zapomniałeś hasła?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "Autoryzacja",
"Content-Disposition": "Dyspozycja treści"
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Generowanie mowy tekstowej (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generowanie osadzeń tekstu (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Polityka prywatności",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "Jesteś pomocnym asystentem AI.",
"userGreeting": "Witam! Jak mogę Ci dzisiaj pomóc?"
},
"streaming": {
"prompt": "Napisz opowiadanie o"
},
"systemPrompt": {
"question": "Jaki jest sens życia?",
"systemInstruction": "Podaj przemyślaną, filozoficzną odpowiedź."
},
"thinking": {
"question": "Wyjaśnij obliczenia kwantowe"
},
"toolCalling": {
"cityNameDescription": "Nazwa miasta, dla którego chcesz uzyskać pogodę",
"toolDescription": "Uzyskaj aktualną pogodę dla danej lokalizacji",
"userWeather": "Jaka jest pogoda w Tokio?"
},
"multiTurn": {
"system": "Jesteś pomocnym asystentem.",
"assistantExample": "Chętnie Ci w tym pomogę.",
"userInitial": "Potrzebuję pomocy",
"userFollowUp": "Możesz to rozwinąć?"
}
}
}
+169 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratuito",
"skipToContent": "Pular para conteúdo",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Aceitar",
"accountId": "ID da conta",
"alias": "Alias",
"apiKeyId": "ID da chave de API",
"apiKeyName": "Nome da chave de API",
"apiKeySecret": "Segredo da chave API",
"authorization": "Autorização",
"content-type": "Tipo de conteúdo",
"content-length": "Comprimento do conteúdo",
"cookie": "Biscoito",
"file": "Arquivo",
"host": "Anfitrião",
"id": "ID",
"import": "Importar",
"limit": "Limite",
"offset": "Deslocamento",
"open": "Abrir",
"origin": "Origem",
"promptTokens": "Tokens de prompt",
"completionTokens": "Tokens de conclusão",
"totalTokens": "Total de fichas",
"rawModel": "Modelo Bruto",
"scope": "Escopo",
"skill": "Habilidade",
"sortBy": "Classificar por",
"sortOrder": "Ordem de classificação",
"tab": "Guia",
"text": "Texto",
"textarea": "Área de texto",
"tool": "Ferramenta",
"toolId": "ID da ferramenta",
"web": "Rede",
"whereUsed": "Onde usado",
"whitelist": "Lista de permissões",
"blacklist": "Lista negra",
"resolve": "Resolver",
"force": "Força",
"base64url": "URL Base64",
"hex": "Feitiço",
"range": "Alcance",
"component": "Componente",
"redirect_uri": "Redirecionar URI",
"idempotency-key": "Chave de Idempotência",
"error_description": "Descrição do erro",
"code": "Código",
"compatible": "Compatível",
"chat-completions": "Conclusões de bate-papo",
"oauth": "OAuth",
"auth_token": "Token de autenticação",
"crypto": "Criptografia",
"hours": "Horas",
"selfsigned": "Autoassinado",
"proxy_id": "ID do proxy",
"proxyId": "ID do proxy",
"connectionId": "ID de conexão",
"resolveConnectionId": "Resolver ID de conexão",
"resolve_connection_id": "Resolver ID de conexão",
"scope_id": "ID do escopo",
"scopeId": "ID do escopo",
"jwtSecret": "Segredo JWT",
"keytar": "Keytar",
"better-sqlite3": "melhor-sqlite3",
"undici": "unici",
"builder-id": "ID do construtor",
"musicDesc": "Descrição da música",
"musicGeneration": "Geração Musical",
"idc": "CDI",
"cloud-status-changed": "Status da nuvem alterado",
"where_used": "Onde usado",
"windowMs": "Janela (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Lista de permissões de ferramentas",
"TOOL_DENYLIST": "Lista de bloqueios de ferramentas",
"Failed to save pricing": "Falha ao salvar o preço",
"Failed to reset pricing": "Falha ao redefinir o preço",
"apikey": "Chave de API",
"http": "HTTP"
},
"sidebar": {
"home": "Início",
@@ -179,7 +257,11 @@
"requestsShort": "{count} reqs",
"providerModelsTitle": "{provider} - Modelos",
"copiedModel": "Copiado: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Atualizar agora",
"updating": "Atualizando...",
"updateAvailableDesc": "Uma nova versão está disponível. Clique para atualizar.",
"updateStarted": "Atualização iniciada..."
},
"analytics": {
"title": "Análises",
@@ -535,6 +617,9 @@
"title": "Adicionar Config de Modelo",
"desc": "Adicione a configuração abaixo ao array de modelos:"
}
},
"notes": {
"0": "Continuar usa o arquivo de configuração JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode requer configuração de chave API.",
"1": "Configure a URL base para seu endpoint do OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro requer uma conta Amazon."
}
}
},
@@ -1326,6 +1418,10 @@
"chatCompletions": "Chat Completions",
"importingModels": "Importando...",
"importFromModels": "Importar de /models",
"clearAllModels": "Limpar Todos os Modelos",
"clearAllModelsConfirm": "Tem certeza de que deseja remover todos os modelos deste provedor? Esta ação não pode ser desfeita.",
"clearAllModelsSuccess": "Todos os modelos foram limpos",
"clearAllModelsFailed": "Falha ao limpar os modelos",
"addConnectionToImport": "Adicione uma conexão para habilitar importação.",
"noModelsConfigured": "Nenhum modelo configurado",
"connectionCount": "{count} conexão(ões)",
@@ -1483,13 +1579,28 @@
"compatProtocolClaude": "Anthropic Messages",
"tokenRefreshed": "Token refreshed successfully",
"tokenRefreshFailed": "Token refresh failed",
"applyCodexAuthLocal": "Aplicar auth",
"exportCodexAuthFile": "Exportar auth",
"codexAuthAppliedLocal": "auth.json do Codex aplicado localmente",
"codexAuthApplyFailed": "Falha ao aplicar o auth.json do Codex localmente",
"codexAuthExported": "auth.json do Codex exportado",
"codexAuthExportFailed": "Falha ao exportar o auth.json do Codex",
"compatBadgeUpstreamHeaders": "Headers",
"compatUpstreamAddRow": "Add header",
"compatUpstreamHeaderName": "Header name",
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Sincronização automática",
"autoSyncTooltip": "Atualize automaticamente a lista de modelos a cada 24h (configurável via MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Sincronização automática habilitada os modelos serão atualizados periodicamente",
"autoSyncDisabled": "Sincronização automática desativada",
"autoSyncToggleFailed": "Falha ao alternar a sincronização automática",
"clearAllModels": "Limpar todos os modelos",
"clearAllModelsConfirm": "Tem certeza de que deseja remover todos os modelos deste provedor? Isto não pode ser desfeito.",
"clearAllModelsSuccess": "Todos os modelos foram apagados",
"clearAllModelsFailed": "Falha ao limpar modelos"
},
"settings": {
"title": "Configurações",
@@ -2316,7 +2427,15 @@
"restartServerWithNewPassword": "Reinicie o servidor - ele usará a nova senha",
"backToLogin": "Voltar para o Login",
"forgotPassword": "Esqueceu a senha?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Autorização",
"Content-Disposition": "Disposição de conteúdo",
"waitingForAuthorization": "Aguardando autorização...",
"waitingForGoogleAuthorization": "Aguardando autorização do Google...",
"waitingForOpenAIAuthorization": "Aguardando autorização do OpenAI...",
"waitingForAntigravityAuthorization": "Aguardando autorização antigravidade...",
"waitingForIFlowAuthorization": "Aguardando autorização do iFlow...",
"exchangingCodeForTokens": "Trocando código por tokens..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2523,7 +2642,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Geração de texto para fala (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Geração de incorporação de texto (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Política de Privacidade",
@@ -2701,5 +2822,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Bate-papo simples",
"streaming": "Transmissão",
"system-prompt": "Alerta do sistema",
"thinking": "Pensando",
"tool-calling": "Chamada de ferramenta",
"multi-turn": "Multivoltas"
},
"templateDescriptions": {
"simple-chat": "Modelo básico de chat com mensagem do sistema",
"streaming": "Modelo para streaming de respostas",
"system-prompt": "Modelo com prompt de sistema personalizado",
"thinking": "Modelo com orçamento de raciocínio/pensamento",
"tool-calling": "Modelo para chamada de ferramenta/função",
"multi-turn": "Modelo para conversas múltiplas"
},
"templatePayloads": {
"simpleChat": {
"system": "Você é um assistente de IA útil.",
"userGreeting": "Olá! Como posso ajudá-lo hoje?"
},
"streaming": {
"prompt": "Escreva uma história sobre"
},
"systemPrompt": {
"question": "Qual é o sentido da vida?",
"systemInstruction": "Forneça uma resposta ponderada e filosófica."
},
"thinking": {
"question": "Explique a computação quântica"
},
"toolCalling": {
"cityNameDescription": "O nome da cidade para obter o clima",
"toolDescription": "Obtenha o clima atual para um local",
"userWeather": "Qual é o clima em Tóquio?"
},
"multiTurn": {
"system": "Você é um assistente útil.",
"assistantExample": "Ficarei feliz em ajudá-lo com isso.",
"userInitial": "preciso de ajuda com",
"userFollowUp": "Você pode explicar isso?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Grátis",
"skipToContent": "Pular para o conteúdo",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Lista de permissões de ferramentas",
"TOOL_DENYLIST": "Lista de bloqueios de ferramentas",
"Failed to save pricing": "Falha ao salvar o preço",
"Failed to reset pricing": "Falha ao redefinir o preço",
"apikey": "Chave de API",
"http": "HTTP"
},
"sidebar": {
"home": "Página inicial",
@@ -179,7 +257,11 @@
"requestsShort": "{count} requisitos",
"providerModelsTitle": "{provider} - Modelos",
"copiedModel": "Copiado: {model}",
"aliasLabel": "apelido"
"aliasLabel": "apelido",
"updateNow": "Atualizar agora",
"updating": "Atualizando...",
"updateAvailableDesc": "Uma nova versão está disponível. Clique para atualizar.",
"updateStarted": "Atualização iniciada..."
},
"analytics": {
"title": "Análise",
@@ -535,6 +617,9 @@
"title": "Adicionar configuração de modelo",
"desc": "Adicione a seguinte configuração ao seu array de modelos:"
}
},
"notes": {
"0": "Continue usa um arquivo de configuração JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode requer configuração de chave de API.",
"1": "Defina a URL base para seu endpoint OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro requer conta Amazon."
}
}
},
@@ -1326,6 +1418,10 @@
"chatCompletions": "Conclusões de bate-papo",
"importingModels": "Importando...",
"importFromModels": "Importar de /modelos",
"clearAllModels": "Limpar Todos os Modelos",
"clearAllModelsConfirm": "Tem a certeza de que quer remover todos os modelos deste fornecedor? Esta ação não pode ser revertida.",
"clearAllModelsSuccess": "Todos os modelos foram limpos",
"clearAllModelsFailed": "Falha ao limpar os modelos",
"addConnectionToImport": "Adicione uma conexão para permitir a importação.",
"noModelsConfigured": "Nenhum modelo configurado",
"connectionCount": "{count} conexões",
@@ -1489,7 +1585,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Sincronização automática",
"autoSyncTooltip": "Atualiza automaticamente a lista de modelos a cada 24 horas (configurável via MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Sincronização automática ativada — modelos serão atualizados periodicamente",
"autoSyncDisabled": "Sincronização automática desativada",
"autoSyncToggleFailed": "Falha ao alternar sincronização automática",
"clearAllModels": "Limpar todos os modelos",
"clearAllModelsConfirm": "Tem certeza que deseja remover todos os modelos deste provedor?",
"clearAllModelsSuccess": "Todos os modelos limpos",
"clearAllModelsFailed": "Falha ao limpar modelos"
},
"settings": {
"title": "Configurações",
@@ -2316,7 +2421,15 @@
"restartServerWithNewPassword": "Reinicie o servidor - ele usará a nova senha",
"backToLogin": "Voltar ao login",
"forgotPassword": "Esqueceu a senha?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "Autorização",
"Content-Disposition": "Disposição de conteúdo"
},
"landing": {
"brandName": "OmniRoute",
@@ -2523,7 +2636,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Geração de texto para fala (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Geração de embeddings de texto (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Política de Privacidade",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "Você é um assistente de IA útil.",
"userGreeting": "Olá! Como posso ajudá-lo hoje?"
},
"streaming": {
"prompt": "Escreva uma história sobre"
},
"systemPrompt": {
"question": "Qual é o sentido da vida?",
"systemInstruction": "Forneça uma resposta ponderada e filosófica."
},
"thinking": {
"question": "Explique a computação quântica"
},
"toolCalling": {
"cityNameDescription": "O nome da cidade para obter o clima",
"toolDescription": "Obtenha o clima atual para um local",
"userWeather": "Qual é o clima em Tóquio?"
},
"multiTurn": {
"system": "Você é um assistente útil.",
"assistantExample": "Ficarei feliz em ajudá-lo com isso.",
"userInitial": "preciso de ajuda com",
"userFollowUp": "Você pode explicar isso?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratuit",
"skipToContent": "Treci la conținut",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Acceptați",
"accountId": "ID contului",
"alias": "Alias",
"apiKeyId": "ID-ul cheii API",
"apiKeyName": "Nume cheie API",
"apiKeySecret": "Secretul cheii API",
"authorization": "Autorizare",
"content-type": "Tip de conținut",
"content-length": "Lungimea conținutului",
"cookie": "Cookie",
"file": "Fișier",
"host": "Gazdă",
"id": "ID",
"import": "Import",
"limit": "Limită",
"offset": "Offset",
"open": "Deschide",
"origin": "Originea",
"promptTokens": "Jetoane prompte",
"completionTokens": "Jetoane de finalizare",
"totalTokens": "Total de jetoane",
"rawModel": "Model brut",
"scope": "Domeniul de aplicare",
"skill": "Îndemânare",
"sortBy": "Sortare după",
"sortOrder": "Ordine de sortare",
"tab": "Tab",
"text": "Text",
"textarea": "Textarea",
"tool": "Instrument",
"toolId": "ID instrument",
"web": "Web",
"whereUsed": "Unde este folosit",
"whitelist": "Lista albă",
"blacklist": "Lista neagră",
"resolve": "Rezolvați",
"force": "Forța",
"base64url": "URL Base64",
"hex": "Hex",
"range": "Gama",
"component": "Componentă",
"redirect_uri": "URI de redirecționare",
"idempotency-key": "Cheia Idempotnței",
"error_description": "Descrierea erorii",
"code": "Cod",
"compatible": "Compatibil",
"chat-completions": "Finalizări de chat",
"oauth": "OAuth",
"auth_token": "Jeton de autentificare",
"crypto": "Cripto",
"hours": "Ore",
"selfsigned": "Autosemnat",
"proxy_id": "ID proxy",
"proxyId": "ID proxy",
"connectionId": "ID conexiune",
"resolveConnectionId": "Rezolvați ID-ul conexiunii",
"resolve_connection_id": "Rezolvați ID-ul conexiunii",
"scope_id": "ID domeniului",
"scopeId": "ID domeniului",
"jwtSecret": "Secretul JWT",
"keytar": "Keytar",
"better-sqlite3": "mai bine-sqlite3",
"undici": "undici",
"builder-id": "ID constructor",
"musicDesc": "Descrierea muzicii",
"musicGeneration": "Generația muzicală",
"idc": "IDC",
"cloud-status-changed": "Starea cloudului s-a schimbat",
"where_used": "Unde este folosit",
"windowMs": "Fereastra (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Lista de instrumente permise",
"TOOL_DENYLIST": "Lista de refulare a instrumentelor",
"Failed to save pricing": "Nu s-a salvat prețul",
"Failed to reset pricing": "Nu s-a resetat prețul",
"apikey": "Cheia API",
"http": "HTTP"
},
"sidebar": {
"home": "Acasă",
@@ -179,7 +257,11 @@
"requestsShort": "{count} solicită",
"providerModelsTitle": "{provider} - Modele",
"copiedModel": "Copiat: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Actualizați acum",
"updating": "Se actualizează...",
"updateAvailableDesc": "Este disponibilă o nouă versiune. Faceți clic pentru a actualiza.",
"updateStarted": "Actualizarea a început..."
},
"analytics": {
"title": "Analytics",
@@ -535,6 +617,9 @@
"title": "Adăugați configurația modelului",
"desc": "Adăugați următoarea configurație la matricea dvs. de modele:"
}
},
"notes": {
"0": "Continuați folosește fișierul de configurare JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode necesită configurarea cheii API.",
"1": "Setați adresa URL de bază la punctul final OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro necesită un cont Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Finalizări de chat",
"importingModels": "Se importă...",
"importFromModels": "Import din /modele",
"clearAllModels": "Șterge toate modelele",
"clearAllModelsConfirm": "Ești sigur că vrei să elimini toate modelele pentru acest furnizor? Această acțiune nu poate fi anulată.",
"clearAllModelsSuccess": "Toate modelele au fost șterse",
"clearAllModelsFailed": "Ștergerea modelelor a eșuat",
"addConnectionToImport": "Adăugați o conexiune pentru a activa importul.",
"noModelsConfigured": "Nu au fost configurate modele",
"connectionCount": "{count} conexiuni",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Sincronizare automată",
"autoSyncTooltip": "Actualizează automat lista de modele la fiecare 24 de ore (configurabil prin MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Sincronizare automată activată — modelele se vor reîmprospăta periodic",
"autoSyncDisabled": "Sincronizarea automată a fost dezactivată",
"autoSyncToggleFailed": "Nu s-a putut comuta sincronizarea automată",
"clearAllModels": "Ștergeți toate modelele",
"clearAllModelsConfirm": "Sigur doriți să eliminați toate modelele pentru acest furnizor? Acest lucru nu poate fi anulat.",
"clearAllModelsSuccess": "Toate modelele au fost eliminate",
"clearAllModelsFailed": "Nu s-au șters modelele"
},
"settings": {
"title": "Setări",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Reporniți serverul - va folosi noua parolă",
"backToLogin": "Înapoi la Logare",
"forgotPassword": "Ai uitat parola?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Autorizare",
"Content-Disposition": "Conținut-Dispoziție",
"waitingForAuthorization": "Se așteaptă autorizația...",
"waitingForGoogleAuthorization": "Se așteaptă autorizarea Google...",
"waitingForOpenAIAuthorization": "Se așteaptă autorizarea OpenAI...",
"waitingForAntigravityAuthorization": "Se așteaptă autorizația antigravitație...",
"waitingForIFlowAuthorization": "Se așteaptă autorizarea iFlow...",
"exchangingCodeForTokens": "Se schimbă codul pentru jetoane..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Generarea text-to-speech (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generarea de încorporare a textului (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Politica de confidențialitate",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Chat simplu",
"streaming": "Streaming",
"system-prompt": "Prompt de sistem",
"thinking": "Gândirea",
"tool-calling": "Apelarea instrumentului",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Șablon de chat de bază cu mesaj de sistem",
"streaming": "Șablon pentru răspunsuri în flux",
"system-prompt": "Șablon cu prompt de sistem personalizat",
"thinking": "Șablon cu buget de raționament/gândire",
"tool-calling": "Șablon pentru apelarea instrumentului/funcției",
"multi-turn": "Șablon pentru conversații în mai multe rânduri"
},
"templatePayloads": {
"simpleChat": {
"system": "Sunteți un asistent AI util.",
"userGreeting": "Bună ziua! Cum te pot ajuta azi?"
},
"streaming": {
"prompt": "Scrie o poveste despre"
},
"systemPrompt": {
"question": "Care este sensul vieții?",
"systemInstruction": "Oferă un răspuns gânditor, filozofic."
},
"thinking": {
"question": "Explicați calculul cuantic"
},
"toolCalling": {
"cityNameDescription": "Numele orașului pentru care să aflați vremea",
"toolDescription": "Obțineți vremea actuală pentru o locație",
"userWeather": "Care este vremea în Tokyo?"
},
"multiTurn": {
"system": "Ești un asistent util.",
"assistantExample": "Aș fi bucuros să te ajut cu asta.",
"userInitial": "Am nevoie de ajutor cu",
"userFollowUp": "Puteți detalia asta?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Бесплатно",
"skipToContent": "Перейти к содержимому",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "accept",
"accountId": "accountId",
"alias": "alias",
"apiKeyId": "apiKeyId",
"apiKeyName": "apiKeyName",
"apiKeySecret": "apiKeySecret",
"authorization": "authorization",
"content-type": "content-type",
"content-length": "content-length",
"cookie": "cookie",
"file": "file",
"host": "host",
"id": "id",
"import": "import",
"limit": "limit",
"offset": "offset",
"open": "open",
"origin": "origin",
"promptTokens": "promptTokens",
"completionTokens": "completionTokens",
"totalTokens": "totalTokens",
"rawModel": "rawModel",
"scope": "scope",
"skill": "skill",
"sortBy": "sortBy",
"sortOrder": "sortOrder",
"tab": "tab",
"text": "text",
"textarea": "textarea",
"tool": "tool",
"toolId": "toolId",
"web": "web",
"whereUsed": "whereUsed",
"whitelist": "whitelist",
"blacklist": "blacklist",
"resolve": "resolve",
"force": "force",
"base64url": "base64url",
"hex": "hex",
"range": "range",
"component": "component",
"redirect_uri": "redirect_uri",
"idempotency-key": "idempotency-key",
"error_description": "error_description",
"code": "code",
"compatible": "compatible",
"chat-completions": "chat-completions",
"oauth": "oauth",
"auth_token": "auth_token",
"crypto": "crypto",
"hours": "hours",
"selfsigned": "selfsigned",
"proxy_id": "proxy_id",
"proxyId": "proxyId",
"connectionId": "connectionId",
"resolveConnectionId": "resolveConnectionId",
"resolve_connection_id": "resolve_connection_id",
"scope_id": "scope_id",
"scopeId": "scopeId",
"jwtSecret": "jwtSecret",
"keytar": "keytar",
"better-sqlite3": "better-sqlite3",
"undici": "undici",
"builder-id": "builder-id",
"musicDesc": "musicDesc",
"musicGeneration": "musicGeneration",
"idc": "idc",
"cloud-status-changed": "cloud-status-changed",
"where_used": "where_used",
"windowMs": "windowMs",
"social-github": "GitHub",
"social-google": "Гугл",
"TOOL_ALLOWLIST": "Белый список инструментов",
"TOOL_DENYLIST": "Список запрещенных инструментов",
"Failed to save pricing": "Не удалось сохранить цену.",
"Failed to reset pricing": "Не удалось сбросить цены.",
"apikey": "API-ключ",
"http": "HTTP"
},
"sidebar": {
"home": "Главная",
@@ -179,7 +257,11 @@
"requestsShort": "{count} требуется",
"providerModelsTitle": "{provider} — Модели",
"copiedModel": "Скопировано: {model}",
"aliasLabel": "псевдоним"
"aliasLabel": "псевдоним",
"updateNow": "Обновить",
"updating": "Обновление...",
"updateAvailableDesc": "Доступна новая версия. Нажмите для обновления.",
"updateStarted": "Обновление начато..."
},
"analytics": {
"title": "Аналитика",
@@ -535,6 +617,9 @@
"title": "Добавить конфигурацию модели",
"desc": "Добавьте следующую конфигурацию в массив моделей:"
}
},
"notes": {
"0": "Continue использует файл конфигурации JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode требует настройки API-ключа.",
"1": "Установите базовый URL на ваш endpoint OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro требует аккаунт Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Завершения чата",
"importingModels": "Импорт...",
"importFromModels": "Импорт из /модели",
"clearAllModels": "Очистить все модели",
"clearAllModelsConfirm": "Вы уверены, что хотите удалить все модели для этого провайдера? Это действие нельзя отменить.",
"clearAllModelsSuccess": "Все модели очищены",
"clearAllModelsFailed": "Не удалось очистить модели",
"addConnectionToImport": "Добавьте соединение, чтобы включить импорт.",
"noModelsConfigured": "Ни одна модель не настроена",
"connectionCount": "{count} соединение(я)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Автосинхронизация",
"autoSyncTooltip": "Автоматически обновляет список моделей каждые 24 часа (настраивается через MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Автосинхронизация включена — модели будут периодически обновляться",
"autoSyncDisabled": "Автосинхронизация отключена",
"autoSyncToggleFailed": "Не удалось переключить автосинхронизацию",
"clearAllModels": "Очистить все модели",
"clearAllModelsConfirm": "Вы уверены, что хотите удалить все модели для этого провайдера?",
"clearAllModelsSuccess": "Все модели очищены",
"clearAllModelsFailed": "Не удалось очистить модели"
},
"settings": {
"title": "Настройки",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Перезагрузите сервер - он будет использовать новый пароль.",
"backToLogin": "Вернуться к входу",
"forgotPassword": "Забыли пароль?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"waitingForAuthorization": "Waiting for authorization...",
"waitingForGoogleAuthorization": "Waiting for Google authorization...",
"waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...",
"waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...",
"waitingForIFlowAuthorization": "Waiting for iFlow authorization...",
"exchangingCodeForTokens": "Exchanging code for tokens...",
"Authorization": "Авторизация",
"Content-Disposition": "Содержание-Расположение"
},
"landing": {
"brandName": "ОмниРоут",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Генерация речи из текста (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Генерация текстовых эмбеддингов (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Политика конфиденциальности",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Simple Chat",
"streaming": "Streaming",
"system-prompt": "System Prompt",
"thinking": "Thinking",
"tool-calling": "Tool Calling",
"multi-turn": "Multi-turn"
},
"templateDescriptions": {
"simple-chat": "Basic chat template",
"streaming": "Streaming template",
"system-prompt": "System prompt template",
"thinking": "Thinking template",
"tool-calling": "Tool calling template",
"multi-turn": "Multi-turn template"
},
"templatePayloads": {
"simpleChat": {
"system": "Вы полезный помощник искусственного интеллекта.",
"userGreeting": "Здравствуйте! Чем я могу помочь вам сегодня?"
},
"streaming": {
"prompt": "Напишите рассказ о"
},
"systemPrompt": {
"question": "В чем смысл жизни?",
"systemInstruction": "Дайте вдумчивый, философский ответ."
},
"thinking": {
"question": "Объясните квантовые вычисления"
},
"toolCalling": {
"cityNameDescription": "Название города, для которого нужно узнать погоду",
"toolDescription": "Получить текущую погоду для местоположения",
"userWeather": "Какая погода в Токио?"
},
"multiTurn": {
"system": "Вы полезный помощник.",
"assistantExample": "Я был бы рад помочь вам в этом.",
"userInitial": "мне нужна помощь с",
"userFollowUp": "Можете ли вы рассказать об этом подробнее?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Zadarmo",
"skipToContent": "Preskočiť na obsah",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Prijať",
"accountId": "ID účtu",
"alias": "Alias",
"apiKeyId": "ID kľúča API",
"apiKeyName": "Názov kľúča API",
"apiKeySecret": "Tajný kľúč API",
"authorization": "Autorizácia",
"content-type": "Typ obsahu",
"content-length": "Dĺžka obsahu",
"cookie": "Cookie",
"file": "Súbor",
"host": "Hostiteľ",
"id": "ID",
"import": "Importovať",
"limit": "Limit",
"offset": "Offset",
"open": "Otvorte",
"origin": "Pôvod",
"promptTokens": "Prompt Tokeny",
"completionTokens": "Žetóny dokončenia",
"totalTokens": "Celkový počet tokenov",
"rawModel": "Surový model",
"scope": "Rozsah",
"skill": "Zručnosť",
"sortBy": "Zoradiť podľa",
"sortOrder": "Poradie zoradenia",
"tab": "Tab",
"text": "Text",
"textarea": "Textarea",
"tool": "Nástroj",
"toolId": "ID nástroja",
"web": "Web",
"whereUsed": "Kde sa používa",
"whitelist": "Whitelist",
"blacklist": "Čierna listina",
"resolve": "Vyriešiť",
"force": "sila",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Rozsah",
"component": "Komponent",
"redirect_uri": "Presmerovať URI",
"idempotency-key": "Kľúč idempotencie",
"error_description": "Popis chyby",
"code": "kód",
"compatible": "Kompatibilné",
"chat-completions": "Dokončenia četu",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "Crypto",
"hours": "hodiny",
"selfsigned": "Vlastne podpísaný",
"proxy_id": "ID proxy",
"proxyId": "ID proxy",
"connectionId": "ID pripojenia",
"resolveConnectionId": "Vyriešiť ID pripojenia",
"resolve_connection_id": "Vyriešiť ID pripojenia",
"scope_id": "ID rozsahu",
"scopeId": "ID rozsahu",
"jwtSecret": "Tajomstvo JWT",
"keytar": "Keytar",
"better-sqlite3": "lepší-sqlite3",
"undici": "undici",
"builder-id": "ID staviteľa",
"musicDesc": "Popis hudby",
"musicGeneration": "Hudobná generácia",
"idc": "IDC",
"cloud-status-changed": "Stav cloudu sa zmenil",
"where_used": "Kde sa používa",
"windowMs": "okno (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Zoznam povolených nástrojov",
"TOOL_DENYLIST": "Nástroj Denylist",
"Failed to save pricing": "Cenu sa nepodarilo uložiť",
"Failed to reset pricing": "Cenu sa nepodarilo resetovať",
"apikey": "API kľúč",
"http": "HTTP"
},
"sidebar": {
"home": "Domov",
@@ -179,7 +257,11 @@
"requestsShort": "{count} req",
"providerModelsTitle": "{provider} - Modely",
"copiedModel": "Skopírované: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Aktualizovať teraz",
"updating": "Aktualizuje sa...",
"updateAvailableDesc": "K dispozícii je nová verzia. Kliknutím aktualizujte.",
"updateStarted": "Aktualizácia spustená..."
},
"analytics": {
"title": "Analytics",
@@ -535,6 +617,9 @@
"title": "Pridať konfiguráciu modelu",
"desc": "Pridajte do poľa modelov nasledujúcu konfiguráciu:"
}
},
"notes": {
"0": "Pokračovať používa konfiguračný súbor JSON."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode vyžaduje konfiguráciu kľúča API.",
"1": "Nastavte základnú URL na váš koncový bod OmniRoute."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro vyžaduje účet Amazon."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Dokončenia četu",
"importingModels": "Importuje sa...",
"importFromModels": "Importovať z /models",
"clearAllModels": "Vymazať všetky modely",
"clearAllModelsConfirm": "Ste si istý, že chcete odstrániť všetky modely pre tohto poskytovateľa? Túto akciu nie je možné vrátiť.",
"clearAllModelsSuccess": "Všetky modely vymazané",
"clearAllModelsFailed": "Nepodarilo sa vymazať modely",
"addConnectionToImport": "Ak chcete povoliť import, pridajte pripojenie.",
"noModelsConfigured": "Nie sú nakonfigurované žiadne modely",
"connectionCount": "{count} pripojení",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Automatická synchronizácia",
"autoSyncTooltip": "Automaticky obnovovať zoznam modelov každých 24 hodín (konfigurovateľné cez MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automatická synchronizácia povolená modely sa budú pravidelne obnovovať",
"autoSyncDisabled": "Automatická synchronizácia je zakázaná",
"autoSyncToggleFailed": "Nepodarilo sa prepnúť automatickú synchronizáciu",
"clearAllModels": "Vymazať všetky modely",
"clearAllModelsConfirm": "Naozaj chcete odstrániť všetky modely tohto poskytovateľa? Toto sa nedá vrátiť späť.",
"clearAllModelsSuccess": "Všetky modely sú vymazané",
"clearAllModelsFailed": "Nepodarilo sa vyčistiť modely"
},
"settings": {
"title": "Nastavenia",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Reštartujte server - použije nové heslo",
"backToLogin": "Späť na Prihlásenie",
"forgotPassword": "Zabudli ste heslo?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Autorizácia",
"Content-Disposition": "Obsah-Dispozícia",
"waitingForAuthorization": "Čaká sa na autorizáciu...",
"waitingForGoogleAuthorization": "Čaká sa na autorizáciu Google...",
"waitingForOpenAIAuthorization": "Čaká sa na autorizáciu OpenAI...",
"waitingForAntigravityAuthorization": "Čaká sa na antigravitačné povolenie...",
"waitingForIFlowAuthorization": "Čaká sa na autorizáciu iFlow...",
"exchangingCodeForTokens": "Výmena kódu za tokeny..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Generovanie prevodu textu na reč (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generovanie vkladania textu (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Zásady ochrany osobných údajov",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Jednoduchý chat",
"streaming": "Streamovanie",
"system-prompt": "Systémová výzva",
"thinking": "Myslenie",
"tool-calling": "Vyvolanie nástroja",
"multi-turn": "Viacotáčkový"
},
"templateDescriptions": {
"simple-chat": "Základná šablóna chatu so systémovou správou",
"streaming": "Šablóna pre odpovede na streamovanie",
"system-prompt": "Šablóna s vlastnou systémovou výzvou",
"thinking": "Šablóna s rozpočtom na uvažovanie/premýšľanie",
"tool-calling": "Šablóna na volanie nástroja/funkcie",
"multi-turn": "Šablóna pre konverzácie s viacerými odbočkami"
},
"templatePayloads": {
"simpleChat": {
"system": "Ste užitočný asistent AI.",
"userGreeting": "Dobrý deň! Ako ti dnes môžem pomôcť?"
},
"streaming": {
"prompt": "Napíšte príbeh o"
},
"systemPrompt": {
"question": "Aký je zmysel života?",
"systemInstruction": "Poskytnite premyslenú, filozofickú odpoveď."
},
"thinking": {
"question": "Vysvetlite kvantové výpočty"
},
"toolCalling": {
"cityNameDescription": "Názov mesta, pre ktoré sa má zistiť počasie",
"toolDescription": "Získajte aktuálne počasie pre miesto",
"userWeather": "Aké je počasie v Tokiu?"
},
"multiTurn": {
"system": "Ste užitočný pomocník.",
"assistantExample": "Rád vám s tým pomôžem.",
"userInitial": "Potrebujem pomoc s",
"userFollowUp": "Môžete to upresniť?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "Gratis",
"skipToContent": "Hoppa till innehållet",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "Acceptera",
"accountId": "Konto-ID",
"alias": "Alias",
"apiKeyId": "API-nyckel-ID",
"apiKeyName": "API-nyckelnamn",
"apiKeySecret": "API-nyckelhemlighet",
"authorization": "Auktorisation",
"content-type": "Innehållstyp",
"content-length": "Innehållslängd",
"cookie": "Cookie",
"file": "Arkiv",
"host": "Värd",
"id": "ID",
"import": "Importera",
"limit": "Begränsa",
"offset": "Offset",
"open": "Öppna",
"origin": "Ursprung",
"promptTokens": "Fråga tokens",
"completionTokens": "Kompletteringstokens",
"totalTokens": "Totala tokens",
"rawModel": "Rå modell",
"scope": "Omfattning",
"skill": "Skicklighet",
"sortBy": "Sortera efter",
"sortOrder": "Sorteringsordning",
"tab": "Tab",
"text": "Text",
"textarea": "Textområde",
"tool": "Verktyg",
"toolId": "Verktygs-ID",
"web": "Webb",
"whereUsed": "Där den används",
"whitelist": "Vitlista",
"blacklist": "Svartlista",
"resolve": "Lös",
"force": "Force",
"base64url": "Base64 URL",
"hex": "Hex",
"range": "Räckvidd",
"component": "Komponent",
"redirect_uri": "Omdirigera URI",
"idempotency-key": "Idempotensnyckel",
"error_description": "Felbeskrivning",
"code": "Kod",
"compatible": "Kompatibel",
"chat-completions": "Chattavslut",
"oauth": "OAuth",
"auth_token": "Auth Token",
"crypto": "Krypto",
"hours": "Timmar",
"selfsigned": "Självsignerad",
"proxy_id": "Proxy-ID",
"proxyId": "Proxy-ID",
"connectionId": "Anslutnings-ID",
"resolveConnectionId": "Lös anslutnings-ID",
"resolve_connection_id": "Lös anslutnings-ID",
"scope_id": "Omfattning ID",
"scopeId": "Omfattning ID",
"jwtSecret": "JWT hemlighet",
"keytar": "Keytar",
"better-sqlite3": "bättre-sqlite3",
"undici": "undici",
"builder-id": "Byggar-ID",
"musicDesc": "Musikbeskrivning",
"musicGeneration": "Musikgeneration",
"idc": "IDC",
"cloud-status-changed": "Molnstatus ändrad",
"where_used": "Där den används",
"windowMs": "Fönster (ms)",
"social-github": "GitHub",
"social-google": "Google",
"TOOL_ALLOWLIST": "Tillståndslista för verktyg",
"TOOL_DENYLIST": "Verktyg Denylist",
"Failed to save pricing": "Det gick inte att spara pris",
"Failed to reset pricing": "Det gick inte att återställa prissättningen",
"apikey": "API-nyckel",
"http": "HTTP"
},
"sidebar": {
"home": "Hem",
@@ -179,7 +257,11 @@
"requestsShort": "{count} krav",
"providerModelsTitle": "{provider} - Modeller",
"copiedModel": "Kopierat: {model}",
"aliasLabel": "alias"
"aliasLabel": "alias",
"updateNow": "Uppdatera nu",
"updating": "Uppdaterar...",
"updateAvailableDesc": "En ny version finns tillgänglig. Klicka för att uppdatera.",
"updateStarted": "Uppdatering startade..."
},
"analytics": {
"title": "Analytics",
@@ -535,6 +617,9 @@
"title": "Lägg till Model Config",
"desc": "Lägg till följande konfiguration till din modellarray:"
}
},
"notes": {
"0": "Continue använder JSON-konfigurationsfilen."
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode kräver API-nyckelkonfiguration.",
"1": "Ställ in basadressen till din OmniRoute-slutpunkt."
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro kräver Amazon-konto."
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "Chattavslut",
"importingModels": "Importerar...",
"importFromModels": "Importera från /models",
"clearAllModels": "Rensa alla modeller",
"clearAllModelsConfirm": "Är du säker på att du vill ta bort alla modeller för denna leverantör? Detta kan inte ångras.",
"clearAllModelsSuccess": "Alla modeller rensade",
"clearAllModelsFailed": "Kunde inte rensa modeller",
"addConnectionToImport": "Lägg till en anslutning för att aktivera import.",
"noModelsConfigured": "Inga modeller har konfigurerats",
"connectionCount": "{count} anslutning(ar)",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "Automatisk synkronisering",
"autoSyncTooltip": "Uppdatera modelllistan automatiskt var 24:e timme (konfigurerbar via MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "Automatisk synkronisering aktiverad — modeller uppdateras regelbundet",
"autoSyncDisabled": "Automatisk synkronisering inaktiverad",
"autoSyncToggleFailed": "Det gick inte att växla automatisk synkronisering",
"clearAllModels": "Rensa alla modeller",
"clearAllModelsConfirm": "Är du säker på att du vill ta bort alla modeller för den här leverantören? Detta kan inte ångras.",
"clearAllModelsSuccess": "Alla modeller rensade",
"clearAllModelsFailed": "Det gick inte att rensa modeller"
},
"settings": {
"title": "Inställningar",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "Starta om servern - den kommer att använda det nya lösenordet",
"backToLogin": "Tillbaka till inloggning",
"forgotPassword": "Glömt lösenordet?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "Auktorisation",
"Content-Disposition": "Innehåll-Disposition",
"waitingForAuthorization": "Väntar på auktorisation...",
"waitingForGoogleAuthorization": "Väntar på Google-auktorisering...",
"waitingForOpenAIAuthorization": "Väntar på OpenAI-auktorisering...",
"waitingForAntigravityAuthorization": "Väntar på antigravitationstillstånd...",
"waitingForIFlowAuthorization": "Väntar på iFlow-auktorisering...",
"exchangingCodeForTokens": "Byter ut kod för tokens..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "Text-till-tal-generering (ElevenLabs, OpenAI TTS).",
"endpointEmbeddingsNote": "Generering av textinbäddning (OpenAI, Cohere, Voyage)."
},
"legal": {
"privacyPolicy": "Sekretesspolicy",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "Enkel chatt",
"streaming": "Streaming",
"system-prompt": "Systemprompt",
"thinking": "Tänker",
"tool-calling": "Verktygsanrop",
"multi-turn": "Flervarv"
},
"templateDescriptions": {
"simple-chat": "Grundläggande chattmall med systemmeddelande",
"streaming": "Mall för strömmande svar",
"system-prompt": "Mall med anpassad systemprompt",
"thinking": "Mall med resonemang/tänkebudget",
"tool-calling": "Mall för verktyg/funktionsanrop",
"multi-turn": "Mall för konversationer med flera svängar"
},
"templatePayloads": {
"simpleChat": {
"system": "Du är en hjälpsam AI-assistent.",
"userGreeting": "Hej! Hur kan jag hjälpa dig idag?"
},
"streaming": {
"prompt": "Skriv en berättelse om"
},
"systemPrompt": {
"question": "Vad är meningen med livet?",
"systemInstruction": "Ge ett genomtänkt, filosofiskt svar."
},
"thinking": {
"question": "Förklara kvantberäkning"
},
"toolCalling": {
"cityNameDescription": "Namnet på staden att få väder för",
"toolDescription": "Få aktuellt väder för en plats",
"userWeather": "Hur är vädret i Tokyo?"
},
"multiTurn": {
"system": "Du är en hjälpsam assistent.",
"assistantExample": "Jag hjälper dig gärna med det.",
"userInitial": "Jag behöver hjälp med",
"userFollowUp": "Kan du utveckla det?"
}
}
}
+163 -5
View File
@@ -58,7 +58,85 @@
"free": "ฟรี",
"skipToContent": "ข้ามไปที่เนื้อหา",
"maintenanceServerIssues": "Server is experiencing issues. Some features may be unavailable.",
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting..."
"maintenanceServerUnreachable": "Server is unreachable. Reconnecting...",
"accept": "ยอมรับ",
"accountId": "รหัสบัญชี",
"alias": "นามแฝง",
"apiKeyId": "รหัสคีย์ API",
"apiKeyName": "ชื่อคีย์ API",
"apiKeySecret": "ความลับของคีย์ API",
"authorization": "การอนุญาต",
"content-type": "ประเภทเนื้อหา",
"content-length": "ความยาวของเนื้อหา",
"cookie": "คุกกี้",
"file": "ไฟล์",
"host": "โฮสต์",
"id": "บัตรประจำตัวประชาชน",
"import": "นำเข้า",
"limit": "ขีดจำกัด",
"offset": "ออฟเซ็ต",
"open": "เปิด",
"origin": "ต้นกำเนิด",
"promptTokens": "โทเค็นพร้อมท์",
"completionTokens": "โทเค็นการสำเร็จ",
"totalTokens": "โทเค็นทั้งหมด",
"rawModel": "โมเดลดิบ",
"scope": "ขอบเขต",
"skill": "ทักษะ",
"sortBy": "เรียงตาม",
"sortOrder": "เรียงลำดับ",
"tab": "แท็บ",
"text": "ข้อความ",
"textarea": "พื้นที่ข้อความ",
"tool": "เครื่องมือ",
"toolId": "รหัสเครื่องมือ",
"web": "เว็บ",
"whereUsed": "ใช้ที่ไหน",
"whitelist": "ไวท์ลิสต์",
"blacklist": "บัญชีดำ",
"resolve": "แก้ไข",
"force": "บังคับ",
"base64url": "URL Base64",
"hex": "ฐานสิบหก",
"range": "พิสัย",
"component": "ส่วนประกอบ",
"redirect_uri": "เปลี่ยนเส้นทาง URI",
"idempotency-key": "คีย์ Idempotency",
"error_description": "คำอธิบายข้อผิดพลาด",
"code": "รหัส",
"compatible": "เข้ากันได้",
"chat-completions": "เสร็จสิ้นการแชท",
"oauth": "OAuth",
"auth_token": "โทเค็นการรับรองความถูกต้อง",
"crypto": "การเข้ารหัสลับ",
"hours": "ชั่วโมง",
"selfsigned": "ลงนามด้วยตนเอง",
"proxy_id": "รหัสพร็อกซี",
"proxyId": "รหัสพร็อกซี",
"connectionId": "รหัสการเชื่อมต่อ",
"resolveConnectionId": "แก้ไขรหัสการเชื่อมต่อ",
"resolve_connection_id": "แก้ไขรหัสการเชื่อมต่อ",
"scope_id": "รหัสขอบเขต",
"scopeId": "รหัสขอบเขต",
"jwtSecret": "ความลับเจดับเบิ้ลยูที",
"keytar": "คีย์ตาร์",
"better-sqlite3": "ดีกว่า-sqlite3",
"undici": "อูนดิ",
"builder-id": "รหัสผู้สร้าง",
"musicDesc": "คำอธิบายเพลง",
"musicGeneration": "ดนตรีเจเนอเรชั่น",
"idc": "ไอดีซี",
"cloud-status-changed": "สถานะคลาวด์เปลี่ยนไป",
"where_used": "ใช้ที่ไหน",
"windowMs": "หน้าต่าง (มิลลิวินาที)",
"social-github": "GitHub",
"social-google": "กูเกิล",
"TOOL_ALLOWLIST": "รายการที่อนุญาตของเครื่องมือ",
"TOOL_DENYLIST": "รายการเครื่องมือที่ปฏิเสธ",
"Failed to save pricing": "บันทึกราคาไม่สำเร็จ",
"Failed to reset pricing": "ไม่สามารถรีเซ็ตราคาได้",
"apikey": "คีย์ API",
"http": "HTTP"
},
"sidebar": {
"home": "บ้าน",
@@ -179,7 +257,11 @@
"requestsShort": "{count} ความต้องการ",
"providerModelsTitle": "{provider} - โมเดล",
"copiedModel": "คัดลอก: {model}",
"aliasLabel": "นามแฝง"
"aliasLabel": "นามแฝง",
"updateNow": "อัปเดตทันที",
"updating": "กำลังอัปเดต...",
"updateAvailableDesc": "มีเวอร์ชันใหม่ให้ใช้งานแล้ว คลิกเพื่ออัปเดต",
"updateStarted": "เริ่มการอัพเดต..."
},
"analytics": {
"title": "การวิเคราะห์",
@@ -535,6 +617,9 @@
"title": "เพิ่มการกำหนดค่าโมเดล",
"desc": "เพิ่มการกำหนดค่าต่อไปนี้ให้กับอาร์เรย์โมเดลของคุณ:"
}
},
"notes": {
"0": "ใช้ไฟล์กำหนดค่า JSON ต่อไป"
}
},
"opencode": {
@@ -553,6 +638,10 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "OpenCode ต้องมีการกำหนดค่าคีย์ API",
"1": "ตั้งค่า URL พื้นฐานเป็นจุดสิ้นสุด OmniRoute ของคุณ"
}
},
"kiro": {
@@ -571,6 +660,9 @@
"4": {
"title": "Select Model"
}
},
"notes": {
"0": "Kiro ต้องการบัญชี Amazon"
}
}
},
@@ -1314,6 +1406,10 @@
"chatCompletions": "เสร็จสิ้นการแชท",
"importingModels": "กำลังนำเข้า...",
"importFromModels": "นำเข้าจาก /models",
"clearAllModels": "ล้างโมเดลทั้งหมด",
"clearAllModelsConfirm": "คุณแน่ใจหรือไม่ว่าต้องการลบโมเดลทั้งหมดของผู้ให้บริการนี้? การดำเนินการนี้ไม่สามารถย้อนกลับได้",
"clearAllModelsSuccess": "ล้างโมเดลทั้งหมดแล้ว",
"clearAllModelsFailed": "ไม่สามารถล้างโมเดลได้",
"addConnectionToImport": "เพิ่มการเชื่อมต่อเพื่อเปิดใช้งานการนำเข้า",
"noModelsConfigured": "ไม่มีโมเดลที่กำหนดค่าไว้",
"connectionCount": "{count} การเชื่อมต่อ",
@@ -1477,7 +1573,16 @@
"compatUpstreamHeaderValue": "Value",
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row"
"compatUpstreamRemoveRow": "Remove row",
"autoSync": "ซิงค์อัตโนมัติ",
"autoSyncTooltip": "รีเฟรชรายการโมเดลโดยอัตโนมัติทุกๆ 24 ชั่วโมง (กำหนดค่าได้ผ่าน MODEL_SYNC_INTERVAL_HOURS)",
"autoSyncEnabled": "เปิดใช้งานการซิงค์อัตโนมัติ — โมเดลจะรีเฟรชเป็นระยะ",
"autoSyncDisabled": "ปิดใช้งานการซิงค์อัตโนมัติแล้ว",
"autoSyncToggleFailed": "ไม่สามารถสลับการซิงค์อัตโนมัติ",
"clearAllModels": "ล้างทุกรุ่น",
"clearAllModelsConfirm": "คุณแน่ใจหรือไม่ว่าต้องการลบโมเดลทั้งหมดสำหรับผู้ให้บริการรายนี้ สิ่งนี้ไม่สามารถยกเลิกได้",
"clearAllModelsSuccess": "เคลียร์ทุกรุ่น",
"clearAllModelsFailed": "ไม่สามารถล้างโมเดลได้"
},
"settings": {
"title": "การตั้งค่า",
@@ -2304,7 +2409,15 @@
"restartServerWithNewPassword": "รีสตาร์ทเซิร์ฟเวอร์ - จะใช้รหัสผ่านใหม่",
"backToLogin": "กลับไปที่เข้าสู่ระบบ",
"forgotPassword": "ลืมรหัสผ่าน?",
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)"
"defaultPasswordHint": "Default password: 123456 (unless INITIAL_PASSWORD was set)",
"Authorization": "การอนุญาต",
"Content-Disposition": "การจัดการเนื้อหา",
"waitingForAuthorization": "กำลังรอการอนุญาต...",
"waitingForGoogleAuthorization": "กำลังรอการอนุญาตจาก Google...",
"waitingForOpenAIAuthorization": "กำลังรอการอนุญาต OpenAI...",
"waitingForAntigravityAuthorization": "กำลังรอการอนุญาตต้านแรงโน้มถ่วง...",
"waitingForIFlowAuthorization": "กำลังรอการอนุญาตจาก iFlow...",
"exchangingCodeForTokens": "การเปลี่ยนรหัสเป็นโทเค็น..."
},
"landing": {
"brandName": "OmniRoute",
@@ -2511,7 +2624,9 @@
"mgmtProxiesBulkAssignNote": "Assign or clear one proxy across many scope IDs in one request.",
"mgmtAssignmentsListNote": "List proxy assignments by scope, scope_id, or proxy_id.",
"mgmtAssignmentsUpdateNote": "Assign or clear proxy for global/provider/account/combo scope.",
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments."
"mgmtLegacyMigrationNote": "Import legacy proxyConfig maps into registry assignments.",
"endpointSpeechNote": "การสร้างข้อความเป็นคำพูด (ElevenLabs, OpenAI TTS)",
"endpointEmbeddingsNote": "การสร้างการฝังข้อความ (OpenAI, Cohere, Voyage)"
},
"legal": {
"privacyPolicy": "นโยบายความเป็นส่วนตัว",
@@ -2689,5 +2804,48 @@
"domainPlaceholder": "example.com",
"requestTimedOut": "Request timed out ({seconds}s)",
"networkError": "Network error"
},
"templateNames": {
"simple-chat": "แชทง่ายๆ",
"streaming": "สตรีมมิ่ง",
"system-prompt": "พร้อมท์ระบบ",
"thinking": "กำลังคิด",
"tool-calling": "การเรียกเครื่องมือ",
"multi-turn": "หลายเลี้ยว"
},
"templateDescriptions": {
"simple-chat": "เทมเพลตการแชทพื้นฐานพร้อมข้อความระบบ",
"streaming": "เทมเพลตสำหรับการตอบกลับแบบสตรีม",
"system-prompt": "เทมเพลตพร้อมแจ้งระบบแบบกำหนดเอง",
"thinking": "เทมเพลตพร้อมงบประมาณการใช้เหตุผล/การคิด",
"tool-calling": "เทมเพลตสำหรับการเรียกใช้เครื่องมือ/ฟังก์ชัน",
"multi-turn": "เทมเพลตสำหรับการสนทนาหลายรอบ"
},
"templatePayloads": {
"simpleChat": {
"system": "คุณเป็นผู้ช่วย AI ที่เป็นประโยชน์",
"userGreeting": "สวัสดี! วันนี้ฉันจะช่วยคุณได้อย่างไร?"
},
"streaming": {
"prompt": "เขียนเรื่องราวเกี่ยวกับ"
},
"systemPrompt": {
"question": "ความหมายของชีวิตคืออะไร?",
"systemInstruction": "ให้คำตอบที่รอบคอบและมีปรัชญา"
},
"thinking": {
"question": "อธิบายการคำนวณควอนตัม"
},
"toolCalling": {
"cityNameDescription": "ชื่อเมืองที่จะเรียกสภาพอากาศ",
"toolDescription": "รับสภาพอากาศปัจจุบันสำหรับสถานที่",
"userWeather": "สภาพอากาศในโตเกียวเป็นอย่างไร?"
},
"multiTurn": {
"system": "คุณเป็นผู้ช่วยที่เป็นประโยชน์",
"assistantExample": "เรายินดีที่จะช่วยเหลือคุณในเรื่องนั้น",
"userInitial": "ฉันต้องการความช่วยเหลือเกี่ยวกับ",
"userFollowUp": "คุณช่วยอธิบายรายละเอียดเกี่ยวกับเรื่องนั้นได้ไหม?"
}
}
}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More