@miphamai/cli 0.19.5 → 0.20.1

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/src/ui/input.tsx CHANGED
@@ -4,6 +4,7 @@ import TextInput from 'ink-text-input'
4
4
  import { VimMotionEngine, type VimMode } from './vim-motions.js'
5
5
  import { getCommandList } from './commands.js'
6
6
  import { CommandPicker } from './command-picker.js'
7
+ import { useI18n } from '../i18n-context'
7
8
 
8
9
  interface InputBarProps {
9
10
  onSubmit: (input: string) => void
@@ -20,99 +21,46 @@ interface InputBarProps {
20
21
  onCancel?: () => void
21
22
  }
22
23
 
23
- // ── Status verbs — English (Claude Code-aligned) + Chinese (Mipham originals) ──
24
-
25
- const STATUS_EN = [
26
- 'Doodling',
27
- 'Forging',
28
- 'Germinating',
29
- 'Mosmosing',
30
- 'Cerebrating',
31
- 'Boogieing',
32
- 'Finagling',
33
- 'Misting',
34
- 'Recombobulating',
35
- 'Slithering',
36
- 'Effecting',
37
- 'Roosting',
38
- 'Crystallizing',
39
- 'Canoodling',
40
- 'Billowing',
41
- 'Warping',
42
- 'Improvising',
43
- 'Metamorphing',
44
- 'Gusting',
45
- 'Whiring',
46
- 'Topsy-turvying',
47
- 'Flibbertigibetting',
48
- 'Wibbling',
49
- 'Thundering',
50
- 'Moseying',
51
- 'Julienning',
52
- 'Evaporating',
53
- 'Ruminating',
54
- 'Whisking',
55
- 'Scampering',
56
- 'Meandering',
57
- 'Concoting',
58
- 'Gesticulating',
59
- 'Flamebeing',
60
- 'Quantumizing',
61
- 'Waddling',
62
- 'Fluttering',
63
- 'Sprouting',
64
- 'Elucidating',
65
- 'Embellishing',
66
- 'Razzmatizing',
67
- 'Pondering',
68
- 'Cogitating',
69
- 'Garnishing',
70
- 'Actualizing',
71
- 'Zigzagging',
72
- 'Mustering',
73
- 'Frolicking',
74
- 'Hullaballooing',
75
- 'Doing',
76
- 'Fermenting',
77
- 'Considering',
78
- 'Skedaddling',
79
- 'Actioning',
80
- 'Orbiting',
81
- 'Perambulating',
82
- 'Drizzling',
83
- 'Schlepping',
84
- 'Ionizing',
85
- 'Scurrying',
24
+ // ── Loading verb keys (i18n) ──
25
+
26
+ const LOADING_KEYS = [
27
+ 'ui.loading.doodling',
28
+ 'ui.loading.forging',
29
+ 'ui.loading.cerebrating',
30
+ 'ui.loading.recombobulating',
31
+ 'ui.loading.thinking',
32
+ 'ui.loading.computing',
33
+ 'ui.loading.processing',
34
+ 'ui.loading.analyzing',
35
+ 'ui.loading.generating',
36
+ 'ui.loading.dreaming',
37
+ 'ui.loading.pondering',
38
+ 'ui.loading.ruminating',
39
+ 'ui.loading.deliberating',
40
+ 'ui.loading.contemplating',
41
+ 'ui.loading.synthesizing',
42
+ 'ui.loading.calculating',
43
+ 'ui.loading.inferring',
44
+ 'ui.loading.optimizing',
45
+ 'ui.loading.compiling',
46
+ 'ui.loading.orchestrating',
47
+ 'ui.loading.harmonizing',
48
+ 'ui.loading.galvanizing',
49
+ 'ui.loading.illuminating',
50
+ 'ui.loading.manifesting',
51
+ 'ui.loading.transmogrifying',
52
+ 'ui.loading.actualizing',
86
53
  ]
87
54
 
88
- const STATUS_CN = [
89
- '思忖中',
90
- '推演中',
91
- '凝炼中',
92
- '熬煮中',
93
- '锻造中',
94
- '研磨中',
95
- '解构中',
96
- '冥思中',
97
- '编织中',
98
- '萃取中',
99
- '烹制中',
100
- '淬火中',
101
- '雕琢中',
102
- '融汇中',
103
- '觉照中',
104
- '运转中',
105
- '参详中',
106
- '化合中',
107
- '浸润中',
108
- '焙烤中',
55
+ const COMPLETED_KEYS = [
56
+ 'ui.loading.completed_brewed',
57
+ 'ui.loading.completed_churned',
58
+ 'ui.loading.completed_cooked',
59
+ 'ui.loading.completed_sauteed',
60
+ 'ui.loading.completed_cogitated',
61
+ 'ui.loading.completed_crunched',
109
62
  ]
110
63
 
111
- // Merge both — bilingual status verbs
112
- const STATUS_GERUNDS = [...STATUS_EN, ...STATUS_CN]
113
-
114
- const STATUS_PAST = ['Brewed', 'Churned', 'Cooked', 'Sautéed', 'Cogitated', 'Crunched']
115
-
116
64
  function pick<T>(arr: T[]): T {
117
65
  return arr[Math.floor(Math.random() * arr.length)]!
118
66
  }
@@ -126,8 +74,9 @@ export function InputBar({
126
74
  onCyclePermission,
127
75
  onCancel,
128
76
  }: InputBarProps) {
77
+ const { t } = useI18n()
129
78
  const [value, setValue] = useState('')
130
- const [verb, setVerb] = useState(() => pick(STATUS_GERUNDS))
79
+ const [verb, setVerb] = useState(() => t(pick(LOADING_KEYS)))
131
80
  const [completionVerb, setCompletionVerb] = useState<string | null>(null)
132
81
  const prevLoading = useRef(isLoading)
133
82
 
@@ -144,28 +93,28 @@ export function InputBar({
144
93
  useEffect(() => {
145
94
  if (!isLoading) return
146
95
  const interval = setInterval(() => {
147
- setVerb(pick(STATUS_GERUNDS))
96
+ setVerb(t(pick(LOADING_KEYS)))
148
97
  }, 1200)
149
98
  return () => clearInterval(interval)
150
- }, [isLoading])
99
+ }, [isLoading, t])
151
100
 
152
101
  // Pick a fresh gerund when loading starts
153
102
  useEffect(() => {
154
103
  if (isLoading) {
155
- setVerb(pick(STATUS_GERUNDS))
104
+ setVerb(t(pick(LOADING_KEYS)))
156
105
  setCompletionVerb(null)
157
106
  }
158
- }, [isLoading])
107
+ }, [isLoading, t])
159
108
 
160
109
  // Flash a past participle when loading stops
161
110
  useEffect(() => {
162
111
  if (prevLoading.current === true && isLoading === false) {
163
- setCompletionVerb(pick(STATUS_PAST))
112
+ setCompletionVerb(t(pick(COMPLETED_KEYS)))
164
113
  const timer = setTimeout(() => setCompletionVerb(null), 1500)
165
114
  return () => clearTimeout(timer)
166
115
  }
167
116
  prevLoading.current = isLoading
168
- }, [isLoading])
117
+ }, [isLoading, t])
169
118
 
170
119
  const [vimMode, setVimMode] = useState<VimMode>('insert')
171
120
  // NOTE: Dual mode state — React state (vimMode) drives UI re-renders (prompt color,
@@ -376,19 +325,19 @@ export function InputBar({
376
325
  searchMode
377
326
  ? `/${searchQuery}`
378
327
  : vimMode === 'normal'
379
- ? '[NORMAL] h/j/k/l w/b 0/$ dd yy p u / (Esc to insert)'
328
+ ? t('ui.input.vim_help')
380
329
  : isLoading
381
330
  ? `${verb}...`
382
331
  : completionVerb
383
332
  ? completionVerb
384
- : 'Type a message (Esc to cancel)...'
333
+ : t('ui.input.placeholder')
385
334
  }
386
335
  />
387
336
  </Box>
388
337
  {/* Slash command hints — shown when typing / in INSERT mode (only when picker is NOT active) */}
389
338
  {slashHints.length > 0 && vimMode === 'insert' && !pickerActive && (
390
339
  <Box marginTop={1} flexDirection="column" gap={1}>
391
- <Text dimColor>Commands: </Text>
340
+ <Text dimColor>{t('ui.slash_hints.label')} </Text>
392
341
  {slashHints.map((cmd, _i) => (
393
342
  <Text key={cmd.name} color="cyan">
394
343
  {cmd.name}
@@ -397,8 +346,11 @@ export function InputBar({
397
346
  <Text dimColor>
398
347
  (
399
348
  {slashHints.length === allCommands.length
400
- ? 'all'
401
- : `${slashHints.length} of ${allCommands.length}`}
349
+ ? t('ui.slash_hints.all')
350
+ : t('ui.slash_hints.count', {
351
+ shown: String(slashHints.length),
352
+ total: String(allCommands.length),
353
+ })}
402
354
  )
403
355
  </Text>
404
356
  </Box>
package/src/ui/picker.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useCallback } from 'react'
2
2
  import { Box, Text, useInput } from 'ink'
3
+ import { useI18n } from '../i18n-context'
3
4
  import type { MiphamConfig, ProviderConfig } from '../shared/index.ts'
4
5
 
5
6
  interface PickerProps {
@@ -19,6 +20,7 @@ export function ModelPicker({
19
20
  onSelect,
20
21
  onClose,
21
22
  }: PickerProps) {
23
+ const { t } = useI18n()
22
24
  // Get active providers only
23
25
  const providers = config.providers.filter((p) => p.status !== 'upcoming')
24
26
  const activeModels = (p: ProviderConfig) => p.models.filter((m) => m.status === 'active')
@@ -118,9 +120,9 @@ export function ModelPicker({
118
120
  {/* Title */}
119
121
  <Box marginBottom={1}>
120
122
  <Text bold color="cyan">
121
- Select Model
123
+ {t('ui.picker.title')}
122
124
  </Text>
123
- <Text dimColor> ←→ navigate · ↑↓ move · Enter select · Esc close</Text>
125
+ <Text dimColor> {t('ui.picker.nav_help')}</Text>
124
126
  </Box>
125
127
 
126
128
  {/* Two-column layout */}
@@ -134,7 +136,7 @@ export function ModelPicker({
134
136
  padding={1}
135
137
  >
136
138
  <Text bold underline dimColor>
137
- PROVIDER {activePanel === 'provider' ? '◀' : ''}
139
+ {t('ui.picker.provider_label')} {activePanel === 'provider' ? '◀' : ''}
138
140
  </Text>
139
141
  {providers.map((p, i) => {
140
142
  const isCurrent = p.id === currentProvider
@@ -150,7 +152,9 @@ export function ModelPicker({
150
152
  {isCurrent ? '✓' : ' '}
151
153
  {p.name.padEnd(16)}
152
154
  </Text>
153
- <Text dimColor>{isUpcoming ? '[coming]' : `${activeModels(p).length}m`}</Text>
155
+ <Text dimColor>
156
+ {isUpcoming ? t('ui.picker.coming_soon') : `${activeModels(p).length}m`}
157
+ </Text>
154
158
  </Box>
155
159
  )
156
160
  })}
@@ -165,10 +169,10 @@ export function ModelPicker({
165
169
  padding={1}
166
170
  >
167
171
  <Text bold underline dimColor>
168
- MODELS {activePanel === 'model' ? '◀' : ''}
172
+ {t('ui.picker.models_label')} {activePanel === 'model' ? '◀' : ''}
169
173
  {selectedProvider ? ` — ${selectedProvider.name}` : ''}
170
174
  </Text>
171
- {models.length === 0 && <Text dimColor> (no active models)</Text>}
175
+ {models.length === 0 && <Text dimColor> {t('ui.picker.no_active_models')}</Text>}
172
176
  {models.map((m, i) => {
173
177
  const isCurrentModel = selectedProvider?.id === currentProvider && m.id === currentModel
174
178
  const isSelected = i === modelIdx
@@ -197,8 +201,8 @@ export function ModelPicker({
197
201
  <Box marginTop={1}>
198
202
  <Text dimColor>
199
203
  {activePanel === 'provider'
200
- ? 'Select a provider → Enter to see models'
201
- : 'Select a model → Enter to switch'}
204
+ ? t('ui.picker.select_provider_hint')
205
+ : t('ui.picker.select_model_hint')}
202
206
  </Text>
203
207
  </Box>
204
208
  </Box>