neuro-cli 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Multi-language i18n System
|
|
3
|
+
// Support for English, Turkish, Chinese, Japanese, Spanish
|
|
4
|
+
// Translation keys for all UI strings, auto-detect system lang
|
|
5
|
+
// /lang command to switch, JSON-based translation files
|
|
6
|
+
// ============================================================
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
// -----------------------------------------------------------
|
|
12
|
+
// Built-in translations (core UI strings)
|
|
13
|
+
// -----------------------------------------------------------
|
|
14
|
+
const TRANSLATIONS = {
|
|
15
|
+
en: {
|
|
16
|
+
// General
|
|
17
|
+
'app.name': 'NeuroCLI',
|
|
18
|
+
'app.tagline': 'AI-Powered Terminal Coding Assistant',
|
|
19
|
+
'app.version': 'v{version}',
|
|
20
|
+
// Modes
|
|
21
|
+
'mode.auto': 'Auto mode (smart orchestration)',
|
|
22
|
+
'mode.agent': 'Agent mode',
|
|
23
|
+
'mode.direct': 'Direct mode',
|
|
24
|
+
'mode.plan': 'Plan mode (read-only)',
|
|
25
|
+
'mode.yolo': 'YOLO mode (auto-approve all)',
|
|
26
|
+
// Commands
|
|
27
|
+
'cmd.help': 'Show help message',
|
|
28
|
+
'cmd.model': 'Switch or list models',
|
|
29
|
+
'cmd.agent': 'Switch or list agents',
|
|
30
|
+
'cmd.theme': 'Switch UI theme',
|
|
31
|
+
'cmd.exit': 'Exit NeuroCLI',
|
|
32
|
+
'cmd.clear': 'Clear terminal',
|
|
33
|
+
'cmd.stats': 'Show session statistics',
|
|
34
|
+
'cmd.undo': 'Undo last change',
|
|
35
|
+
'cmd.redo': 'Redo undone change',
|
|
36
|
+
'cmd.sandbox': 'Toggle sandbox mode',
|
|
37
|
+
'cmd.style': 'Switch output style',
|
|
38
|
+
'cmd.thinking': 'Toggle thinking mode',
|
|
39
|
+
'cmd.skills': 'Manage skills',
|
|
40
|
+
'cmd.cache': 'Manage prompt cache',
|
|
41
|
+
'cmd.spending': 'Show spending report',
|
|
42
|
+
'cmd.lang': 'Switch language',
|
|
43
|
+
'cmd.vim': 'Toggle vim mode',
|
|
44
|
+
'cmd.voice': 'Toggle voice I/O',
|
|
45
|
+
'cmd.telemetry': 'Manage telemetry',
|
|
46
|
+
'cmd.sync': 'Cloud sync',
|
|
47
|
+
'cmd.server': 'API server',
|
|
48
|
+
'cmd.dashboard': 'Web dashboard',
|
|
49
|
+
// Status messages
|
|
50
|
+
'status.ready': 'Ready',
|
|
51
|
+
'status.processing': 'Processing...',
|
|
52
|
+
'status.thinking': 'Thinking...',
|
|
53
|
+
'status.streaming': 'Streaming response...',
|
|
54
|
+
'status.done': 'Done',
|
|
55
|
+
'status.error': 'Error',
|
|
56
|
+
'status.cancelled': 'Cancelled',
|
|
57
|
+
'status.approved': 'Approved',
|
|
58
|
+
'status.denied': 'Denied',
|
|
59
|
+
'status.saved': 'Saved',
|
|
60
|
+
'status.loaded': 'Loaded',
|
|
61
|
+
// Warnings
|
|
62
|
+
'warn.sandbox.denied': 'Sandbox: {action} denied for {target}',
|
|
63
|
+
'warn.spend.limit': 'Spending limit reached ({limit})',
|
|
64
|
+
'warn.doom.loop': 'Doom loop detected: {reason}',
|
|
65
|
+
'warn.ignoring': 'Ignored: {path} is in .neuroignore',
|
|
66
|
+
// Info
|
|
67
|
+
'info.model.switch': 'Switched to {model}',
|
|
68
|
+
'info.model.router': 'Model router: {complexity} task -> {model}',
|
|
69
|
+
'info.cache.hit': 'Cache hit - using cached response',
|
|
70
|
+
'info.skill.activated': 'Skill activated: {name} ({trigger})',
|
|
71
|
+
'info.mcp.connected': 'MCP: {count} server(s) connected',
|
|
72
|
+
'info.plugins.loaded': 'Plugins: {count} loaded',
|
|
73
|
+
'info.session.created': 'New session created',
|
|
74
|
+
'info.session.resumed': 'Session resumed',
|
|
75
|
+
'info.session.saved': 'Session saved',
|
|
76
|
+
'info.session.exported': 'Session exported to {path}',
|
|
77
|
+
// Errors
|
|
78
|
+
'error.api.key': 'API key not found. Set OPENROUTER_API_KEY environment variable.',
|
|
79
|
+
'error.api.request': 'API request failed: {message}',
|
|
80
|
+
'error.api.timeout': 'API request timed out',
|
|
81
|
+
'error.model.not_found': 'Model not found: {model}',
|
|
82
|
+
'error.agent.not_found': 'Agent not found: {agent}',
|
|
83
|
+
'error.tool.not_found': 'Tool not found: {tool}',
|
|
84
|
+
'error.session.not_found': 'Session not found: {id}',
|
|
85
|
+
'error.file.not_found': 'File not found: {path}',
|
|
86
|
+
'error.file.read': 'Failed to read file: {path}',
|
|
87
|
+
'error.file.write': 'Failed to write file: {path}',
|
|
88
|
+
// Approval
|
|
89
|
+
'approval.request': 'Approve {tool} call? (risk: {risk})',
|
|
90
|
+
'approval.auto_approved': 'Auto-approved: {tool}',
|
|
91
|
+
'approval.show_diff': 'Showing diff preview...',
|
|
92
|
+
// Prompt
|
|
93
|
+
'prompt.input': 'You',
|
|
94
|
+
'prompt.multiline': 'Multi-line mode (Ctrl+D to submit, Ctrl+C to cancel)',
|
|
95
|
+
// Token usage
|
|
96
|
+
'usage.tokens': 'Tokens: {input} in / {output} out',
|
|
97
|
+
'usage.cost': 'Cost: ${cost}',
|
|
98
|
+
'usage.model': 'Model: {model}',
|
|
99
|
+
// i18n
|
|
100
|
+
'i18n.current': 'Current language: {locale}',
|
|
101
|
+
'i18n.switched': 'Language switched to {locale}',
|
|
102
|
+
'i18n.available': 'Available languages: {locales}',
|
|
103
|
+
// Vim
|
|
104
|
+
'vim.enabled': 'Vim mode enabled. Press Esc to enter normal mode.',
|
|
105
|
+
'vim.disabled': 'Vim mode disabled.',
|
|
106
|
+
'vim.mode.normal': 'NORMAL',
|
|
107
|
+
'vim.mode.insert': 'INSERT',
|
|
108
|
+
'vim.mode.visual': 'VISUAL',
|
|
109
|
+
'vim.mode.command': 'CMD',
|
|
110
|
+
// Voice
|
|
111
|
+
'voice.enabled': 'Voice I/O enabled.',
|
|
112
|
+
'voice.disabled': 'Voice I/O disabled.',
|
|
113
|
+
// Telemetry
|
|
114
|
+
'telemetry.enabled': 'Telemetry enabled. No PII is collected.',
|
|
115
|
+
'telemetry.disabled': 'Telemetry disabled.',
|
|
116
|
+
// Goodbye
|
|
117
|
+
'goodbye': 'Goodbye! Happy coding!',
|
|
118
|
+
},
|
|
119
|
+
tr: {
|
|
120
|
+
'app.name': 'NeuroCLI',
|
|
121
|
+
'app.tagline': 'Yapay Zeka Destekli Terminal Kodlama Asistanı',
|
|
122
|
+
'app.version': 'v{version}',
|
|
123
|
+
'mode.auto': 'Otomatik mod (akıllı orkestrasyon)',
|
|
124
|
+
'mode.agent': 'Ajan modu',
|
|
125
|
+
'mode.direct': 'Doğrudan mod',
|
|
126
|
+
'mode.plan': 'Plan modu (salt okunur)',
|
|
127
|
+
'mode.yolo': 'YOLO modu (hepsini otomatik onayla)',
|
|
128
|
+
'cmd.help': 'Yardım mesajını göster',
|
|
129
|
+
'cmd.model': 'Model değiştir veya listele',
|
|
130
|
+
'cmd.agent': 'Ajan değiştir veya listele',
|
|
131
|
+
'cmd.theme': 'UI temasını değiştir',
|
|
132
|
+
'cmd.exit': 'NeuroCLI\'den çık',
|
|
133
|
+
'cmd.clear': 'Terminali temizle',
|
|
134
|
+
'cmd.stats': 'Oturum istatistiklerini göster',
|
|
135
|
+
'cmd.undo': 'Son değişikliği geri al',
|
|
136
|
+
'cmd.redo': 'Geri alınan değişikliği tekrarla',
|
|
137
|
+
'cmd.sandbox': 'Kum havuzu modunu aç/kapat',
|
|
138
|
+
'cmd.style': 'Çıktı stilini değiştir',
|
|
139
|
+
'cmd.thinking': 'Düşünme modunu aç/kapat',
|
|
140
|
+
'cmd.skills': 'Yetenekleri yönet',
|
|
141
|
+
'cmd.cache': 'İstem önbelleğini yönet',
|
|
142
|
+
'cmd.spending': 'Harcama raporunu göster',
|
|
143
|
+
'cmd.lang': 'Dili değiştir',
|
|
144
|
+
'cmd.vim': 'Vim modunu aç/kapat',
|
|
145
|
+
'cmd.voice': 'Ses G/Ç\'sini aç/kapat',
|
|
146
|
+
'cmd.telemetry': 'Telemetriyi yönet',
|
|
147
|
+
'cmd.sync': 'Bulut senkronizasyonu',
|
|
148
|
+
'cmd.server': 'API sunucusu',
|
|
149
|
+
'cmd.dashboard': 'Web paneli',
|
|
150
|
+
'status.ready': 'Hazır',
|
|
151
|
+
'status.processing': 'İşleniyor...',
|
|
152
|
+
'status.thinking': 'Düşünülüyor...',
|
|
153
|
+
'status.streaming': 'Yanıt akışı...',
|
|
154
|
+
'status.done': 'Tamamlandı',
|
|
155
|
+
'status.error': 'Hata',
|
|
156
|
+
'status.cancelled': 'İptal edildi',
|
|
157
|
+
'status.approved': 'Onaylandı',
|
|
158
|
+
'status.denied': 'Reddedildi',
|
|
159
|
+
'status.saved': 'Kaydedildi',
|
|
160
|
+
'status.loaded': 'Yüklendi',
|
|
161
|
+
'warn.sandbox.denied': 'Kum havuzu: {action} için {target} reddedildi',
|
|
162
|
+
'warn.spend.limit': 'Harcama sınırına ulaşıldı ({limit})',
|
|
163
|
+
'warn.doom.loop': 'Kıyamet döngüsü tespit edildi: {reason}',
|
|
164
|
+
'warn.ignoring': 'Yoksayıldı: {path} .neuroignore\'da',
|
|
165
|
+
'info.model.switch': '{model} modeline geçildi',
|
|
166
|
+
'info.model.router': 'Model yönlendirici: {complexity} görev -> {model}',
|
|
167
|
+
'info.cache.hit': 'Önbellek isabeti - önbelleğe alınmış yanıt kullanılıyor',
|
|
168
|
+
'info.skill.activated': 'Yetenek etkinleştirildi: {name} ({trigger})',
|
|
169
|
+
'info.mcp.connected': 'MCP: {count} sunucu bağlandı',
|
|
170
|
+
'info.plugins.loaded': 'Eklentiler: {count} yüklendi',
|
|
171
|
+
'info.session.created': 'Yeni oturum oluşturuldu',
|
|
172
|
+
'info.session.resumed': 'Oturum devam ettirildi',
|
|
173
|
+
'info.session.saved': 'Oturum kaydedildi',
|
|
174
|
+
'info.session.exported': 'Oturum {path} konumuna dışa aktarıldı',
|
|
175
|
+
'error.api.key': 'API anahtarı bulunamadı. OPENROUTER_API_KEY ortam değişkenini ayarlayın.',
|
|
176
|
+
'error.api.request': 'API isteği başarısız: {message}',
|
|
177
|
+
'error.api.timeout': 'API isteği zaman aşımına uğradı',
|
|
178
|
+
'error.model.not_found': 'Model bulunamadı: {model}',
|
|
179
|
+
'error.agent.not_found': 'Ajan bulunamadı: {agent}',
|
|
180
|
+
'error.tool.not_found': 'Araç bulunamadı: {tool}',
|
|
181
|
+
'error.session.not_found': 'Oturum bulunamadı: {id}',
|
|
182
|
+
'error.file.not_found': 'Dosya bulunamadı: {path}',
|
|
183
|
+
'error.file.read': 'Dosya okunamadı: {path}',
|
|
184
|
+
'error.file.write': 'Dosya yazılamadı: {path}',
|
|
185
|
+
'approval.request': '{tool} çağrısını onayla? (risk: {risk})',
|
|
186
|
+
'approval.auto_approved': 'Otomatik onaylandı: {tool}',
|
|
187
|
+
'approval.show_diff': 'Fark önizlemesi gösteriliyor...',
|
|
188
|
+
'prompt.input': 'Sen',
|
|
189
|
+
'prompt.multiline': 'Çok satırlı mod (göndermek için Ctrl+D, iptal için Ctrl+C)',
|
|
190
|
+
'usage.tokens': 'Jetonlar: {input} giriş / {output} çıkış',
|
|
191
|
+
'usage.cost': 'Maliyet: ${cost}',
|
|
192
|
+
'usage.model': 'Model: {model}',
|
|
193
|
+
'i18n.current': 'Geçerli dil: {locale}',
|
|
194
|
+
'i18n.switched': 'Dil {locale} olarak değiştirildi',
|
|
195
|
+
'i18n.available': 'Kullanılabilir diller: {locales}',
|
|
196
|
+
'vim.enabled': 'Vim modu etkin. Normal moda geçmek için Esc tuşuna basın.',
|
|
197
|
+
'vim.disabled': 'Vim modu devre dışı.',
|
|
198
|
+
'vim.mode.normal': 'NORMAL',
|
|
199
|
+
'vim.mode.insert': 'EKLE',
|
|
200
|
+
'vim.mode.visual': 'GÖRSEL',
|
|
201
|
+
'vim.mode.command': 'KMD',
|
|
202
|
+
'voice.enabled': 'Ses G/Ç etkin.',
|
|
203
|
+
'voice.disabled': 'Ses G/Ç devre dışı.',
|
|
204
|
+
'telemetry.enabled': 'Telemetri etkin. KVK verisi toplanmıyor.',
|
|
205
|
+
'telemetry.disabled': 'Telemetri devre dışı.',
|
|
206
|
+
'goodbye': 'Hoşça kalın! İyi kodlamalar!',
|
|
207
|
+
},
|
|
208
|
+
zh: {
|
|
209
|
+
'app.name': 'NeuroCLI',
|
|
210
|
+
'app.tagline': 'AI驱动的终端编程助手',
|
|
211
|
+
'app.version': 'v{version}',
|
|
212
|
+
'mode.auto': '自动模式(智能编排)',
|
|
213
|
+
'mode.agent': '代理模式',
|
|
214
|
+
'mode.direct': '直接模式',
|
|
215
|
+
'mode.plan': '计划模式(只读)',
|
|
216
|
+
'mode.yolo': 'YOLO模式(自动批准所有)',
|
|
217
|
+
'cmd.help': '显示帮助信息',
|
|
218
|
+
'cmd.model': '切换或列出模型',
|
|
219
|
+
'cmd.agent': '切换或列出代理',
|
|
220
|
+
'cmd.theme': '切换UI主题',
|
|
221
|
+
'cmd.exit': '退出NeuroCLI',
|
|
222
|
+
'cmd.clear': '清除终端',
|
|
223
|
+
'cmd.stats': '显示会话统计',
|
|
224
|
+
'cmd.undo': '撤销上次更改',
|
|
225
|
+
'cmd.redo': '重做已撤销的更改',
|
|
226
|
+
'cmd.sandbox': '切换沙箱模式',
|
|
227
|
+
'cmd.style': '切换输出样式',
|
|
228
|
+
'cmd.thinking': '切换思考模式',
|
|
229
|
+
'cmd.skills': '管理技能',
|
|
230
|
+
'cmd.cache': '管理提示缓存',
|
|
231
|
+
'cmd.spending': '显示消费报告',
|
|
232
|
+
'cmd.lang': '切换语言',
|
|
233
|
+
'cmd.vim': '切换Vim模式',
|
|
234
|
+
'cmd.voice': '切换语音输入输出',
|
|
235
|
+
'cmd.telemetry': '管理遥测',
|
|
236
|
+
'cmd.sync': '云同步',
|
|
237
|
+
'cmd.server': 'API服务器',
|
|
238
|
+
'cmd.dashboard': 'Web仪表板',
|
|
239
|
+
'status.ready': '就绪',
|
|
240
|
+
'status.processing': '处理中...',
|
|
241
|
+
'status.thinking': '思考中...',
|
|
242
|
+
'status.streaming': '流式响应中...',
|
|
243
|
+
'status.done': '完成',
|
|
244
|
+
'status.error': '错误',
|
|
245
|
+
'status.cancelled': '已取消',
|
|
246
|
+
'status.approved': '已批准',
|
|
247
|
+
'status.denied': '已拒绝',
|
|
248
|
+
'status.saved': '已保存',
|
|
249
|
+
'status.loaded': '已加载',
|
|
250
|
+
'warn.sandbox.denied': '沙箱:{target}的{action}被拒绝',
|
|
251
|
+
'warn.spend.limit': '已达到消费限额({limit})',
|
|
252
|
+
'warn.doom.loop': '检测到死循环:{reason}',
|
|
253
|
+
'warn.ignoring': '已忽略:{path}在.neuroignore中',
|
|
254
|
+
'info.model.switch': '已切换到{model}',
|
|
255
|
+
'info.model.router': '模型路由:{complexity}任务 -> {model}',
|
|
256
|
+
'info.cache.hit': '缓存命中 - 使用缓存响应',
|
|
257
|
+
'info.skill.activated': '技能已激活:{name}({trigger})',
|
|
258
|
+
'info.mcp.connected': 'MCP:{count}个服务器已连接',
|
|
259
|
+
'info.plugins.loaded': '插件:{count}个已加载',
|
|
260
|
+
'info.session.created': '新会话已创建',
|
|
261
|
+
'info.session.resumed': '会话已恢复',
|
|
262
|
+
'info.session.saved': '会话已保存',
|
|
263
|
+
'info.session.exported': '会话已导出到{path}',
|
|
264
|
+
'error.api.key': '未找到API密钥。请设置OPENROUTER_API_KEY环境变量。',
|
|
265
|
+
'error.api.request': 'API请求失败:{message}',
|
|
266
|
+
'error.api.timeout': 'API请求超时',
|
|
267
|
+
'error.model.not_found': '未找到模型:{model}',
|
|
268
|
+
'error.agent.not_found': '未找到代理:{agent}',
|
|
269
|
+
'error.tool.not_found': '未找到工具:{tool}',
|
|
270
|
+
'error.session.not_found': '未找到会话:{id}',
|
|
271
|
+
'error.file.not_found': '未找到文件:{path}',
|
|
272
|
+
'error.file.read': '无法读取文件:{path}',
|
|
273
|
+
'error.file.write': '无法写入文件:{path}',
|
|
274
|
+
'approval.request': '批准{tool}调用?(风险:{risk})',
|
|
275
|
+
'approval.auto_approved': '自动批准:{tool}',
|
|
276
|
+
'approval.show_diff': '正在显示差异预览...',
|
|
277
|
+
'prompt.input': '你',
|
|
278
|
+
'prompt.multiline': '多行模式(Ctrl+D提交,Ctrl+C取消)',
|
|
279
|
+
'usage.tokens': '令牌:{input}入 / {output}出',
|
|
280
|
+
'usage.cost': '费用:${cost}',
|
|
281
|
+
'usage.model': '模型:{model}',
|
|
282
|
+
'i18n.current': '当前语言:{locale}',
|
|
283
|
+
'i18n.switched': '语言已切换为{locale}',
|
|
284
|
+
'i18n.available': '可用语言:{locales}',
|
|
285
|
+
'vim.enabled': 'Vim模式已启用。按Esc进入普通模式。',
|
|
286
|
+
'vim.disabled': 'Vim模式已禁用。',
|
|
287
|
+
'vim.mode.normal': '普通',
|
|
288
|
+
'vim.mode.insert': '插入',
|
|
289
|
+
'vim.mode.visual': '可视',
|
|
290
|
+
'vim.mode.command': '命令',
|
|
291
|
+
'voice.enabled': '语音输入输出已启用。',
|
|
292
|
+
'voice.disabled': '语音输入输出已禁用。',
|
|
293
|
+
'telemetry.enabled': '遥测已启用。不收集个人身份信息。',
|
|
294
|
+
'telemetry.disabled': '遥测已禁用。',
|
|
295
|
+
'goodbye': '再见!编码愉快!',
|
|
296
|
+
},
|
|
297
|
+
ja: {
|
|
298
|
+
'app.name': 'NeuroCLI',
|
|
299
|
+
'app.tagline': 'AI搭載ターミナルコーディングアシスタント',
|
|
300
|
+
'app.version': 'v{version}',
|
|
301
|
+
'mode.auto': '自動モード(スマートオーケストレーション)',
|
|
302
|
+
'mode.agent': 'エージェントモード',
|
|
303
|
+
'mode.direct': 'ダイレクトモード',
|
|
304
|
+
'mode.plan': '計画モード(読み取り専用)',
|
|
305
|
+
'mode.yolo': 'YOLOモード(全自動承認)',
|
|
306
|
+
'cmd.help': 'ヘルプメッセージを表示',
|
|
307
|
+
'cmd.model': 'モデルを切り替えまたは一覧表示',
|
|
308
|
+
'cmd.agent': 'エージェントを切り替えまたは一覧表示',
|
|
309
|
+
'cmd.theme': 'UIテーマを切り替え',
|
|
310
|
+
'cmd.exit': 'NeuroCLIを終了',
|
|
311
|
+
'cmd.clear': 'ターミナルをクリア',
|
|
312
|
+
'cmd.stats': 'セッション統計を表示',
|
|
313
|
+
'cmd.undo': '最後の変更を元に戻す',
|
|
314
|
+
'cmd.redo': 'やり直し',
|
|
315
|
+
'cmd.sandbox': 'サンドボックスモードを切り替え',
|
|
316
|
+
'cmd.style': '出力スタイルを切り替え',
|
|
317
|
+
'cmd.thinking': '思考モードを切り替え',
|
|
318
|
+
'cmd.skills': 'スキルを管理',
|
|
319
|
+
'cmd.cache': 'プロンプトキャッシュを管理',
|
|
320
|
+
'cmd.spending': '支出レポートを表示',
|
|
321
|
+
'cmd.lang': '言語を切り替え',
|
|
322
|
+
'cmd.vim': 'Vimモードを切り替え',
|
|
323
|
+
'cmd.voice': '音声I/Oを切り替え',
|
|
324
|
+
'cmd.telemetry': 'テレメトリを管理',
|
|
325
|
+
'cmd.sync': 'クラウド同期',
|
|
326
|
+
'cmd.server': 'APIサーバー',
|
|
327
|
+
'cmd.dashboard': 'Webダッシュボード',
|
|
328
|
+
'status.ready': '準備完了',
|
|
329
|
+
'status.processing': '処理中...',
|
|
330
|
+
'status.thinking': '思考中...',
|
|
331
|
+
'status.streaming': 'ストリーミング応答中...',
|
|
332
|
+
'status.done': '完了',
|
|
333
|
+
'status.error': 'エラー',
|
|
334
|
+
'status.cancelled': 'キャンセル',
|
|
335
|
+
'status.approved': '承認済み',
|
|
336
|
+
'status.denied': '拒否',
|
|
337
|
+
'status.saved': '保存済み',
|
|
338
|
+
'status.loaded': '読み込み済み',
|
|
339
|
+
'warn.sandbox.denied': 'サンドボックス:{target}の{action}が拒否されました',
|
|
340
|
+
'warn.spend.limit': '支出限度額に達しました({limit})',
|
|
341
|
+
'warn.doom.loop': 'デスループが検出されました:{reason}',
|
|
342
|
+
'warn.ignoring': '無視:{path}は.neuroignoreにあります',
|
|
343
|
+
'info.model.switch': '{model}に切り替えました',
|
|
344
|
+
'info.model.router': 'モデルルーター:{complexity}タスク -> {model}',
|
|
345
|
+
'info.cache.hit': 'キャッシュヒット - キャッシュされた応答を使用',
|
|
346
|
+
'info.skill.activated': 'スキルがアクティブ化:{name}({trigger})',
|
|
347
|
+
'info.mcp.connected': 'MCP:{count}台のサーバーが接続されました',
|
|
348
|
+
'info.plugins.loaded': 'プラグイン:{count}個がロードされました',
|
|
349
|
+
'info.session.created': '新しいセッションが作成されました',
|
|
350
|
+
'info.session.resumed': 'セッションが再開されました',
|
|
351
|
+
'info.session.saved': 'セッションが保存されました',
|
|
352
|
+
'info.session.exported': 'セッションが{path}にエクスポートされました',
|
|
353
|
+
'error.api.key': 'APIキーが見つかりません。OPENROUTER_API_KEY環境変数を設定してください。',
|
|
354
|
+
'error.api.request': 'APIリクエストが失敗しました:{message}',
|
|
355
|
+
'error.api.timeout': 'APIリクエストがタイムアウトしました',
|
|
356
|
+
'error.model.not_found': 'モデルが見つかりません:{model}',
|
|
357
|
+
'error.agent.not_found': 'エージェントが見つかりません:{agent}',
|
|
358
|
+
'error.tool.not_found': 'ツールが見つかりません:{tool}',
|
|
359
|
+
'error.session.not_found': 'セッションが見つかりません:{id}',
|
|
360
|
+
'error.file.not_found': 'ファイルが見つかりません:{path}',
|
|
361
|
+
'error.file.read': 'ファイルの読み取りに失敗しました:{path}',
|
|
362
|
+
'error.file.write': 'ファイルの書き込みに失敗しました:{path}',
|
|
363
|
+
'approval.request': '{tool}の呼び出しを承認しますか?(リスク:{risk})',
|
|
364
|
+
'approval.auto_approved': '自動承認:{tool}',
|
|
365
|
+
'approval.show_diff': '差分プレビューを表示中...',
|
|
366
|
+
'prompt.input': 'あなた',
|
|
367
|
+
'prompt.multiline': '複数行モード(Ctrl+Dで送信、Ctrl+Cでキャンセル)',
|
|
368
|
+
'usage.tokens': 'トークン:{input}入力 / {output}出力',
|
|
369
|
+
'usage.cost': 'コスト:${cost}',
|
|
370
|
+
'usage.model': 'モデル:{model}',
|
|
371
|
+
'i18n.current': '現在の言語:{locale}',
|
|
372
|
+
'i18n.switched': '言語が{locale}に切り替えられました',
|
|
373
|
+
'i18n.available': '利用可能な言語:{locales}',
|
|
374
|
+
'vim.enabled': 'Vimモードが有効です。Escでノーマルモードに入ります。',
|
|
375
|
+
'vim.disabled': 'Vimモードが無効です。',
|
|
376
|
+
'vim.mode.normal': 'ノーマル',
|
|
377
|
+
'vim.mode.insert': '挿入',
|
|
378
|
+
'vim.mode.visual': 'ビジュアル',
|
|
379
|
+
'vim.mode.command': 'コマンド',
|
|
380
|
+
'voice.enabled': '音声I/Oが有効です。',
|
|
381
|
+
'voice.disabled': '音声I/Oが無効です。',
|
|
382
|
+
'telemetry.enabled': 'テレメトリが有効です。個人情報は収集されません。',
|
|
383
|
+
'telemetry.disabled': 'テレメトリが無効です。',
|
|
384
|
+
'goodbye': 'さようなら!楽しいコーディングを!',
|
|
385
|
+
},
|
|
386
|
+
es: {
|
|
387
|
+
'app.name': 'NeuroCLI',
|
|
388
|
+
'app.tagline': 'Asistente de Codificación Terminal con IA',
|
|
389
|
+
'app.version': 'v{version}',
|
|
390
|
+
'mode.auto': 'Modo automático (orquestación inteligente)',
|
|
391
|
+
'mode.agent': 'Modo agente',
|
|
392
|
+
'mode.direct': 'Modo directo',
|
|
393
|
+
'mode.plan': 'Modo plan (solo lectura)',
|
|
394
|
+
'mode.yolo': 'Modo YOLO (auto-aprobar todo)',
|
|
395
|
+
'cmd.help': 'Mostrar mensaje de ayuda',
|
|
396
|
+
'cmd.model': 'Cambiar o listar modelos',
|
|
397
|
+
'cmd.agent': 'Cambiar o listar agentes',
|
|
398
|
+
'cmd.theme': 'Cambiar tema de UI',
|
|
399
|
+
'cmd.exit': 'Salir de NeuroCLI',
|
|
400
|
+
'cmd.clear': 'Limpiar terminal',
|
|
401
|
+
'cmd.stats': 'Mostrar estadísticas de sesión',
|
|
402
|
+
'cmd.undo': 'Deshacer último cambio',
|
|
403
|
+
'cmd.redo': 'Rehacer cambio deshecho',
|
|
404
|
+
'cmd.sandbox': 'Alternar modo sandbox',
|
|
405
|
+
'cmd.style': 'Cambiar estilo de salida',
|
|
406
|
+
'cmd.thinking': 'Alternar modo de pensamiento',
|
|
407
|
+
'cmd.skills': 'Gestionar habilidades',
|
|
408
|
+
'cmd.cache': 'Gestionar caché de prompts',
|
|
409
|
+
'cmd.spending': 'Mostrar informe de gastos',
|
|
410
|
+
'cmd.lang': 'Cambiar idioma',
|
|
411
|
+
'cmd.vim': 'Alternar modo vim',
|
|
412
|
+
'cmd.voice': 'Alternar entrada/salida de voz',
|
|
413
|
+
'cmd.telemetry': 'Gestionar telemetría',
|
|
414
|
+
'cmd.sync': 'Sincronización en la nube',
|
|
415
|
+
'cmd.server': 'Servidor API',
|
|
416
|
+
'cmd.dashboard': 'Panel web',
|
|
417
|
+
'status.ready': 'Listo',
|
|
418
|
+
'status.processing': 'Procesando...',
|
|
419
|
+
'status.thinking': 'Pensando...',
|
|
420
|
+
'status.streaming': 'Transmitiendo respuesta...',
|
|
421
|
+
'status.done': 'Hecho',
|
|
422
|
+
'status.error': 'Error',
|
|
423
|
+
'status.cancelled': 'Cancelado',
|
|
424
|
+
'status.approved': 'Aprobado',
|
|
425
|
+
'status.denied': 'Denegado',
|
|
426
|
+
'status.saved': 'Guardado',
|
|
427
|
+
'status.loaded': 'Cargado',
|
|
428
|
+
'warn.sandbox.denied': 'Sandbox: {action} denegada para {target}',
|
|
429
|
+
'warn.spend.limit': 'Límite de gasto alcanzado ({limit})',
|
|
430
|
+
'warn.doom.loop': 'Bucle de muerte detectado: {reason}',
|
|
431
|
+
'warn.ignoring': 'Ignorado: {path} está en .neuroignore',
|
|
432
|
+
'info.model.switch': 'Cambiado a {model}',
|
|
433
|
+
'info.model.router': 'Enrutador de modelo: tarea {complexity} -> {model}',
|
|
434
|
+
'info.cache.hit': 'Acierto de caché - usando respuesta en caché',
|
|
435
|
+
'info.skill.activated': 'Habilidad activada: {name} ({trigger})',
|
|
436
|
+
'info.mcp.connected': 'MCP: {count} servidor(es) conectado(s)',
|
|
437
|
+
'info.plugins.loaded': 'Plugins: {count} cargado(s)',
|
|
438
|
+
'info.session.created': 'Nueva sesión creada',
|
|
439
|
+
'info.session.resumed': 'Sesión reanudada',
|
|
440
|
+
'info.session.saved': 'Sesión guardada',
|
|
441
|
+
'info.session.exported': 'Sesión exportada a {path}',
|
|
442
|
+
'error.api.key': 'Clave API no encontrada. Establezca la variable de entorno OPENROUTER_API_KEY.',
|
|
443
|
+
'error.api.request': 'Solicitud API fallida: {message}',
|
|
444
|
+
'error.api.timeout': 'Solicitud API agotada',
|
|
445
|
+
'error.model.not_found': 'Modelo no encontrado: {model}',
|
|
446
|
+
'error.agent.not_found': 'Agente no encontrado: {agent}',
|
|
447
|
+
'error.tool.not_found': 'Herramienta no encontrada: {tool}',
|
|
448
|
+
'error.session.not_found': 'Sesión no encontrada: {id}',
|
|
449
|
+
'error.file.not_found': 'Archivo no encontrado: {path}',
|
|
450
|
+
'error.file.read': 'Error al leer archivo: {path}',
|
|
451
|
+
'error.file.write': 'Error al escribir archivo: {path}',
|
|
452
|
+
'approval.request': '¿Aprobar llamada {tool}? (riesgo: {risk})',
|
|
453
|
+
'approval.auto_approved': 'Auto-aprobado: {tool}',
|
|
454
|
+
'approval.show_diff': 'Mostrando vista previa de diferencias...',
|
|
455
|
+
'prompt.input': 'Tú',
|
|
456
|
+
'prompt.multiline': 'Modo multilínea (Ctrl+D para enviar, Ctrl+C para cancelar)',
|
|
457
|
+
'usage.tokens': 'Tokens: {input} entrada / {output} salida',
|
|
458
|
+
'usage.cost': 'Costo: ${cost}',
|
|
459
|
+
'usage.model': 'Modelo: {model}',
|
|
460
|
+
'i18n.current': 'Idioma actual: {locale}',
|
|
461
|
+
'i18n.switched': 'Idioma cambiado a {locale}',
|
|
462
|
+
'i18n.available': 'Idiomas disponibles: {locales}',
|
|
463
|
+
'vim.enabled': 'Modo vim activado. Presione Esc para entrar en modo normal.',
|
|
464
|
+
'vim.disabled': 'Modo vim desactivado.',
|
|
465
|
+
'vim.mode.normal': 'NORMAL',
|
|
466
|
+
'vim.mode.insert': 'INSERCIÓN',
|
|
467
|
+
'vim.mode.visual': 'VISUAL',
|
|
468
|
+
'vim.mode.command': 'CMD',
|
|
469
|
+
'voice.enabled': 'Entrada/salida de voz activada.',
|
|
470
|
+
'voice.disabled': 'Entrada/salida de voz desactivada.',
|
|
471
|
+
'telemetry.enabled': 'Telemetría activada. No se recopila PII.',
|
|
472
|
+
'telemetry.disabled': 'Telemetría desactivada.',
|
|
473
|
+
'goodbye': '¡Adiós! ¡Feliz codificación!',
|
|
474
|
+
},
|
|
475
|
+
};
|
|
476
|
+
// -----------------------------------------------------------
|
|
477
|
+
// Locale display names
|
|
478
|
+
// -----------------------------------------------------------
|
|
479
|
+
const LOCALE_NAMES = {
|
|
480
|
+
en: 'English',
|
|
481
|
+
tr: 'Türkçe',
|
|
482
|
+
zh: '中文',
|
|
483
|
+
ja: '日本語',
|
|
484
|
+
es: 'Español',
|
|
485
|
+
};
|
|
486
|
+
// -----------------------------------------------------------
|
|
487
|
+
// I18n System
|
|
488
|
+
// -----------------------------------------------------------
|
|
489
|
+
const I18N_CONFIG_PATH = join(homedir(), '.neuro', 'i18n-config.json');
|
|
490
|
+
export class I18nSystem {
|
|
491
|
+
config;
|
|
492
|
+
customTranslations = new Map();
|
|
493
|
+
constructor(config) {
|
|
494
|
+
this.config = {
|
|
495
|
+
locale: 'en',
|
|
496
|
+
fallbackLocale: 'en',
|
|
497
|
+
customTranslationsDir: join(homedir(), '.neuro', 'translations'),
|
|
498
|
+
autoDetect: true,
|
|
499
|
+
...config,
|
|
500
|
+
};
|
|
501
|
+
this.loadConfig();
|
|
502
|
+
if (this.config.autoDetect && this.config.locale === 'en') {
|
|
503
|
+
this.autoDetectLocale();
|
|
504
|
+
}
|
|
505
|
+
this.loadCustomTranslations();
|
|
506
|
+
}
|
|
507
|
+
// ----------------------------------------------------------
|
|
508
|
+
// Public API
|
|
509
|
+
// ----------------------------------------------------------
|
|
510
|
+
/**
|
|
511
|
+
* Get a translated string by key
|
|
512
|
+
*/
|
|
513
|
+
t(key, params) {
|
|
514
|
+
let value = this.getTranslation(key);
|
|
515
|
+
// Interpolate parameters
|
|
516
|
+
if (params) {
|
|
517
|
+
for (const [paramKey, paramValue] of Object.entries(params)) {
|
|
518
|
+
value = value.replace(`{${paramKey}}`, String(paramValue));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return value;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get current locale
|
|
525
|
+
*/
|
|
526
|
+
getLocale() {
|
|
527
|
+
return this.config.locale;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Set locale
|
|
531
|
+
*/
|
|
532
|
+
setLocale(locale) {
|
|
533
|
+
if (!TRANSLATIONS[locale]) {
|
|
534
|
+
console.log(chalk.red(`Unknown locale: ${locale}. Available: ${this.getAvailableLocales().join(', ')}`));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
this.config.locale = locale;
|
|
538
|
+
this.saveConfig();
|
|
539
|
+
console.log(this.t('i18n.switched', { locale: LOCALE_NAMES[locale] }));
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Get all available locales
|
|
543
|
+
*/
|
|
544
|
+
getAvailableLocales() {
|
|
545
|
+
return Object.keys(TRANSLATIONS);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Get locale display name
|
|
549
|
+
*/
|
|
550
|
+
getLocaleName(locale) {
|
|
551
|
+
return LOCALE_NAMES[locale] || locale;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Get all locale names
|
|
555
|
+
*/
|
|
556
|
+
getLocaleNames() {
|
|
557
|
+
return { ...LOCALE_NAMES };
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Auto-detect system locale
|
|
561
|
+
*/
|
|
562
|
+
autoDetectLocale() {
|
|
563
|
+
const envLang = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANGUAGE || '';
|
|
564
|
+
const detected = this.parseLocaleString(envLang);
|
|
565
|
+
if (detected && TRANSLATIONS[detected]) {
|
|
566
|
+
this.config.locale = detected;
|
|
567
|
+
this.saveConfig();
|
|
568
|
+
}
|
|
569
|
+
return this.config.locale;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Add a custom translation
|
|
573
|
+
*/
|
|
574
|
+
addTranslation(locale, key, value) {
|
|
575
|
+
let custom = this.customTranslations.get(locale);
|
|
576
|
+
if (!custom) {
|
|
577
|
+
custom = {};
|
|
578
|
+
this.customTranslations.set(locale, custom);
|
|
579
|
+
}
|
|
580
|
+
custom[key] = value;
|
|
581
|
+
this.saveCustomTranslations(locale);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Remove a custom translation
|
|
585
|
+
*/
|
|
586
|
+
removeTranslation(locale, key) {
|
|
587
|
+
const custom = this.customTranslations.get(locale);
|
|
588
|
+
if (!custom || !(key in custom))
|
|
589
|
+
return false;
|
|
590
|
+
delete custom[key];
|
|
591
|
+
this.saveCustomTranslations(locale);
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Get all translation keys for current locale
|
|
596
|
+
*/
|
|
597
|
+
getAllKeys() {
|
|
598
|
+
const keys = new Set();
|
|
599
|
+
const base = TRANSLATIONS[this.config.locale] || TRANSLATIONS.en;
|
|
600
|
+
for (const key of Object.keys(base)) {
|
|
601
|
+
keys.add(key);
|
|
602
|
+
}
|
|
603
|
+
const custom = this.customTranslations.get(this.config.locale);
|
|
604
|
+
if (custom) {
|
|
605
|
+
for (const key of Object.keys(custom)) {
|
|
606
|
+
keys.add(key);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return Array.from(keys).sort();
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Export all translations for a locale as JSON
|
|
613
|
+
*/
|
|
614
|
+
exportTranslations(locale) {
|
|
615
|
+
const base = TRANSLATIONS[locale] || {};
|
|
616
|
+
const custom = this.customTranslations.get(locale) || {};
|
|
617
|
+
return JSON.stringify({ ...base, ...custom }, null, 2);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Import translations from a JSON string
|
|
621
|
+
*/
|
|
622
|
+
importTranslations(locale, json) {
|
|
623
|
+
try {
|
|
624
|
+
const parsed = JSON.parse(json);
|
|
625
|
+
let count = 0;
|
|
626
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
627
|
+
this.addTranslation(locale, key, value);
|
|
628
|
+
count++;
|
|
629
|
+
}
|
|
630
|
+
return count;
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
console.log(chalk.red('Failed to parse translation JSON.'));
|
|
634
|
+
return 0;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Print current locale info
|
|
639
|
+
*/
|
|
640
|
+
printStatus() {
|
|
641
|
+
console.log('');
|
|
642
|
+
console.log(chalk.bold('--- NeuroCLI i18n ---'));
|
|
643
|
+
console.log(` ${this.t('i18n.current', { locale: this.getLocaleName(this.config.locale) })}`);
|
|
644
|
+
console.log(` ${this.t('i18n.available', { locales: this.getAvailableLocales().map(l => `${l} (${this.getLocaleName(l)})`).join(', ') })}`);
|
|
645
|
+
console.log(` Translation keys: ${this.getAllKeys().length}`);
|
|
646
|
+
console.log('');
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Get config
|
|
650
|
+
*/
|
|
651
|
+
getConfig() {
|
|
652
|
+
return { ...this.config };
|
|
653
|
+
}
|
|
654
|
+
// ----------------------------------------------------------
|
|
655
|
+
// Private helpers
|
|
656
|
+
// ----------------------------------------------------------
|
|
657
|
+
getTranslation(key) {
|
|
658
|
+
// Check custom translations first
|
|
659
|
+
const custom = this.customTranslations.get(this.config.locale);
|
|
660
|
+
if (custom && custom[key])
|
|
661
|
+
return custom[key];
|
|
662
|
+
// Check built-in translations
|
|
663
|
+
const localeTranslations = TRANSLATIONS[this.config.locale];
|
|
664
|
+
if (localeTranslations && localeTranslations[key])
|
|
665
|
+
return localeTranslations[key];
|
|
666
|
+
// Fallback locale
|
|
667
|
+
const customFallback = this.customTranslations.get(this.config.fallbackLocale);
|
|
668
|
+
if (customFallback && customFallback[key])
|
|
669
|
+
return customFallback[key];
|
|
670
|
+
const fallbackTranslations = TRANSLATIONS[this.config.fallbackLocale];
|
|
671
|
+
if (fallbackTranslations && fallbackTranslations[key])
|
|
672
|
+
return fallbackTranslations[key];
|
|
673
|
+
// Return key itself as last resort
|
|
674
|
+
return key;
|
|
675
|
+
}
|
|
676
|
+
parseLocaleString(localeStr) {
|
|
677
|
+
const lower = localeStr.toLowerCase();
|
|
678
|
+
if (lower.startsWith('tr'))
|
|
679
|
+
return 'tr';
|
|
680
|
+
if (lower.startsWith('zh'))
|
|
681
|
+
return 'zh';
|
|
682
|
+
if (lower.startsWith('ja'))
|
|
683
|
+
return 'ja';
|
|
684
|
+
if (lower.startsWith('es'))
|
|
685
|
+
return 'es';
|
|
686
|
+
if (lower.startsWith('en'))
|
|
687
|
+
return 'en';
|
|
688
|
+
return null;
|
|
689
|
+
}
|
|
690
|
+
saveConfig() {
|
|
691
|
+
try {
|
|
692
|
+
const dir = join(I18N_CONFIG_PATH, '..');
|
|
693
|
+
if (!existsSync(dir))
|
|
694
|
+
mkdirSync(dir, { recursive: true });
|
|
695
|
+
writeFileSync(I18N_CONFIG_PATH, JSON.stringify(this.config, null, 2), 'utf-8');
|
|
696
|
+
}
|
|
697
|
+
catch { /* Silently fail */ }
|
|
698
|
+
}
|
|
699
|
+
loadConfig() {
|
|
700
|
+
try {
|
|
701
|
+
if (existsSync(I18N_CONFIG_PATH)) {
|
|
702
|
+
const raw = readFileSync(I18N_CONFIG_PATH, 'utf-8');
|
|
703
|
+
const saved = JSON.parse(raw);
|
|
704
|
+
this.config = { ...this.config, ...saved };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch { /* Silently fail */ }
|
|
708
|
+
}
|
|
709
|
+
loadCustomTranslations() {
|
|
710
|
+
try {
|
|
711
|
+
const dir = this.config.customTranslationsDir;
|
|
712
|
+
if (!existsSync(dir))
|
|
713
|
+
return;
|
|
714
|
+
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
715
|
+
for (const file of files) {
|
|
716
|
+
const locale = file.replace('.json', '');
|
|
717
|
+
if (!TRANSLATIONS[locale])
|
|
718
|
+
continue;
|
|
719
|
+
const raw = readFileSync(join(dir, file), 'utf-8');
|
|
720
|
+
const translations = JSON.parse(raw);
|
|
721
|
+
this.customTranslations.set(locale, translations);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
catch { /* Silently fail */ }
|
|
725
|
+
}
|
|
726
|
+
saveCustomTranslations(locale) {
|
|
727
|
+
try {
|
|
728
|
+
const dir = this.config.customTranslationsDir;
|
|
729
|
+
if (!existsSync(dir))
|
|
730
|
+
mkdirSync(dir, { recursive: true });
|
|
731
|
+
const custom = this.customTranslations.get(locale);
|
|
732
|
+
if (custom) {
|
|
733
|
+
writeFileSync(join(dir, `${locale}.json`), JSON.stringify(custom, null, 2), 'utf-8');
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
catch { /* Silently fail */ }
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
//# sourceMappingURL=i18n.js.map
|