@juicesharp/rpiv-voice 1.4.2
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/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/audio/error-log.ts +37 -0
- package/audio/hallucination-filter.ts +71 -0
- package/audio/mic-source.ts +38 -0
- package/audio/model-download.ts +268 -0
- package/audio/pcm.ts +45 -0
- package/audio/sherpa-onnx-node.d.ts +55 -0
- package/audio/stt-engine.ts +117 -0
- package/command/pipeline-runner.ts +238 -0
- package/command/splash-runner.ts +72 -0
- package/command/voice-command.ts +251 -0
- package/config/voice-config.ts +80 -0
- package/docs/cover.png +0 -0
- package/docs/cover.svg +173 -0
- package/docs/equalizer.svg +86 -0
- package/docs/overlay.jpg +0 -0
- package/docs/overlay.png +0 -0
- package/docs/vertical-cover.png +0 -0
- package/docs/vertical-cover.svg +239 -0
- package/index.ts +66 -0
- package/locales/de.json +39 -0
- package/locales/en.json +42 -0
- package/locales/es.json +39 -0
- package/locales/fr.json +39 -0
- package/locales/pt-BR.json +39 -0
- package/locales/pt.json +39 -0
- package/locales/ru.json +39 -0
- package/locales/uk.json +39 -0
- package/package.json +94 -0
- package/state/i18n-bridge.ts +51 -0
- package/state/key-router.ts +46 -0
- package/state/screen-intent.ts +27 -0
- package/state/selectors/contract.ts +13 -0
- package/state/selectors/derivations.ts +9 -0
- package/state/selectors/focus.ts +6 -0
- package/state/selectors/projections.ts +112 -0
- package/state/state-reducer.ts +197 -0
- package/state/state.ts +48 -0
- package/state/status-intent.ts +23 -0
- package/state/voice-session.ts +176 -0
- package/view/component-binding.ts +24 -0
- package/view/components/equalizer-view.ts +237 -0
- package/view/components/settings-field-view.ts +77 -0
- package/view/components/settings-form-view.ts +26 -0
- package/view/components/splash-view.ts +98 -0
- package/view/components/status-bar-view.ts +112 -0
- package/view/components/transcript-view.ts +50 -0
- package/view/overlay-view.ts +82 -0
- package/view/props-adapter.ts +29 -0
- package/view/screen-content-strategy.ts +58 -0
- package/view/stateful-view.ts +7 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 540 1200" role="img" aria-label="rpiv-voice — Local voice dictation for Pi" preserveAspectRatio="xMidYMid meet">
|
|
2
|
+
<title>rpiv-voice — Local voice dictation for Pi</title>
|
|
3
|
+
<defs>
|
|
4
|
+
<style>
|
|
5
|
+
.mono { font-family: 'JetBrains Mono', 'Fira Code', 'SFMono-Regular', ui-monospace, Menlo, Consolas, monospace; }
|
|
6
|
+
.ink { fill: #F4EFE6; }
|
|
7
|
+
.paper { fill: #0E0D0B; }
|
|
8
|
+
.muted { fill: #7A7468; }
|
|
9
|
+
.dim { fill: #4A463F; }
|
|
10
|
+
.fade { fill: #6A645A; }
|
|
11
|
+
.accent { fill: #FF7A3D; }
|
|
12
|
+
.mint { fill: #6FD0B8; }
|
|
13
|
+
.rec { fill: #E5594E; }
|
|
14
|
+
.rule { stroke: #2A2620; }
|
|
15
|
+
.rule-bright { stroke: #FF7A3D; }
|
|
16
|
+
.rule-mint { stroke: #6FD0B8; }
|
|
17
|
+
</style>
|
|
18
|
+
<filter id="grain" x="0" y="0" width="100%" height="100%">
|
|
19
|
+
<feTurbulence type="fractalNoise" baseFrequency="1.4" numOctaves="2" seed="31"/>
|
|
20
|
+
<feColorMatrix values="0 0 0 0 0.96 0 0 0 0 0.94 0 0 0 0 0.88 0 0 0 0.06 0"/>
|
|
21
|
+
</filter>
|
|
22
|
+
<pattern id="grid" width="32" height="32" patternUnits="userSpaceOnUse">
|
|
23
|
+
<path d="M32 0 H0 V32" fill="none" stroke="#161411" stroke-width="0.5"/>
|
|
24
|
+
</pattern>
|
|
25
|
+
<pattern id="dots" width="6" height="6" patternUnits="userSpaceOnUse">
|
|
26
|
+
<circle cx="3" cy="3" r="0.6" fill="#231F1A"/>
|
|
27
|
+
</pattern>
|
|
28
|
+
<radialGradient id="vignette" cx="50%" cy="50%" r="70%">
|
|
29
|
+
<stop offset="60%" stop-color="#0E0D0B" stop-opacity="0"/>
|
|
30
|
+
<stop offset="100%" stop-color="#000" stop-opacity="0.55"/>
|
|
31
|
+
</radialGradient>
|
|
32
|
+
<radialGradient id="recPulse" cx="50%" cy="50%" r="50%">
|
|
33
|
+
<stop offset="0%" stop-color="#E5594E" stop-opacity="0.45"/>
|
|
34
|
+
<stop offset="100%" stop-color="#E5594E" stop-opacity="0"/>
|
|
35
|
+
</radialGradient>
|
|
36
|
+
</defs>
|
|
37
|
+
|
|
38
|
+
<rect width="540" height="1200" class="paper"/>
|
|
39
|
+
<rect width="540" height="1200" fill="url(#grid)" opacity="0.55"/>
|
|
40
|
+
<rect width="540" height="1200" fill="url(#dots)" opacity="0.4"/>
|
|
41
|
+
<rect width="540" height="1200" filter="url(#grain)" opacity="0.5"/>
|
|
42
|
+
<rect width="540" height="1200" fill="url(#vignette)"/>
|
|
43
|
+
|
|
44
|
+
<g class="rule-bright" stroke-width="1" fill="none" opacity="0.95">
|
|
45
|
+
<path d="M24 56 L40 56 M40 40 L40 56"/>
|
|
46
|
+
<path d="M516 56 L500 56 M500 40 L500 56"/>
|
|
47
|
+
<path d="M24 1144 L40 1144 M40 1160 L40 1144"/>
|
|
48
|
+
<path d="M516 1144 L500 1144 M500 1160 L500 1144"/>
|
|
49
|
+
</g>
|
|
50
|
+
|
|
51
|
+
<line x1="40" y1="56" x2="500" y2="56" class="rule" stroke-width="1"/>
|
|
52
|
+
<text class="mono muted" x="40" y="44" font-size="9" letter-spacing="2">@JUICESHARP · PI EXTENSION · NO. 010 – VOICE</text>
|
|
53
|
+
<text class="mono muted" x="500" y="44" font-size="9" letter-spacing="2" text-anchor="end">MIT · v1.3 · 540 × 1200</text>
|
|
54
|
+
|
|
55
|
+
<text class="mono ink" x="40" y="124" font-size="56" font-weight="700" letter-spacing="-2">rpiv<tspan class="accent">-</tspan>voice</text>
|
|
56
|
+
<line x1="40" y1="138" x2="100" y2="138" class="rule-bright" stroke-width="2"/>
|
|
57
|
+
<text class="mono muted" x="40" y="160" font-size="12" letter-spacing="0.3">Talk to Pi instead of typing. Whisper. On your laptop.</text>
|
|
58
|
+
<text class="mono dim" x="40" y="178" font-size="9" letter-spacing="1.4">LOCAL STT · ~99 LANGUAGES · NO CLOUD · NO API KEYS</text>
|
|
59
|
+
|
|
60
|
+
<text class="mono muted" x="40" y="222" font-size="9" letter-spacing="2">FIG. 01 — DICTATION OVERLAY</text>
|
|
61
|
+
<text class="mono muted" x="500" y="222" font-size="9" letter-spacing="2" text-anchor="end">cmd: /voice</text>
|
|
62
|
+
<line x1="40" y1="232" x2="500" y2="232" class="rule" stroke-width="0.75"/>
|
|
63
|
+
|
|
64
|
+
<!-- HERO 1: dictation overlay -->
|
|
65
|
+
<g transform="translate(40 250)">
|
|
66
|
+
<rect x="0" y="0" width="460" height="232" fill="#13110D" stroke="#2A2620" stroke-width="1"/>
|
|
67
|
+
|
|
68
|
+
<!-- transcript: committed + dim partial -->
|
|
69
|
+
<text class="mono ink" x="14" y="32" font-size="13">Refactor the splash runner so download</text>
|
|
70
|
+
<text class="mono ink" x="14" y="50" font-size="13">progress shows percent and bytes on a</text>
|
|
71
|
+
<text class="mono ink" x="14" y="68" font-size="13">single line.</text>
|
|
72
|
+
<text class="mono fade" x="14" y="88" font-size="13">Add a unit test for the hallucination…</text>
|
|
73
|
+
|
|
74
|
+
<!-- mint divider -->
|
|
75
|
+
<line x1="14" y1="108" x2="446" y2="108" class="rule-mint" stroke-width="1.1" stroke-opacity="0.65"/>
|
|
76
|
+
|
|
77
|
+
<!-- equalizer waveform -->
|
|
78
|
+
<g transform="translate(14 122)" class="mint" fill="#6FD0B8" opacity="0.85">
|
|
79
|
+
<rect x="0" y="14" width="6" height="6"/>
|
|
80
|
+
<rect x="10" y="10" width="6" height="14"/>
|
|
81
|
+
<rect x="20" y="6" width="6" height="22"/>
|
|
82
|
+
<rect x="30" y="2" width="6" height="30"/>
|
|
83
|
+
<rect x="40" y="8" width="6" height="18"/>
|
|
84
|
+
<rect x="50" y="0" width="6" height="34"/>
|
|
85
|
+
<rect x="60" y="6" width="6" height="22"/>
|
|
86
|
+
<rect x="70" y="2" width="6" height="30"/>
|
|
87
|
+
<rect x="80" y="10" width="6" height="14"/>
|
|
88
|
+
<rect x="90" y="4" width="6" height="26"/>
|
|
89
|
+
<rect x="100" y="0" width="6" height="34"/>
|
|
90
|
+
<rect x="110" y="6" width="6" height="22"/>
|
|
91
|
+
<rect x="120" y="2" width="6" height="30"/>
|
|
92
|
+
<rect x="130" y="8" width="6" height="18"/>
|
|
93
|
+
<rect x="140" y="4" width="6" height="26"/>
|
|
94
|
+
<rect x="150" y="10" width="6" height="14"/>
|
|
95
|
+
<rect x="160" y="6" width="6" height="22"/>
|
|
96
|
+
<rect x="170" y="12" width="6" height="10"/>
|
|
97
|
+
<rect x="180" y="8" width="6" height="18"/>
|
|
98
|
+
<rect x="190" y="14" width="6" height="6"/>
|
|
99
|
+
<rect x="200" y="10" width="6" height="14"/>
|
|
100
|
+
<rect x="210" y="14" width="6" height="6"/>
|
|
101
|
+
<rect x="220" y="16" width="6" height="2"/>
|
|
102
|
+
<rect x="230" y="16" width="6" height="2"/>
|
|
103
|
+
<rect x="240" y="16" width="6" height="2"/>
|
|
104
|
+
<rect x="250" y="16" width="6" height="2"/>
|
|
105
|
+
<rect x="260" y="16" width="6" height="2"/>
|
|
106
|
+
<rect x="270" y="16" width="6" height="2"/>
|
|
107
|
+
<rect x="280" y="16" width="6" height="2"/>
|
|
108
|
+
<rect x="290" y="16" width="6" height="2"/>
|
|
109
|
+
<rect x="300" y="16" width="6" height="2"/>
|
|
110
|
+
<rect x="310" y="16" width="6" height="2"/>
|
|
111
|
+
<rect x="320" y="16" width="6" height="2"/>
|
|
112
|
+
<rect x="330" y="16" width="6" height="2"/>
|
|
113
|
+
<rect x="340" y="16" width="6" height="2"/>
|
|
114
|
+
<rect x="350" y="16" width="6" height="2"/>
|
|
115
|
+
<rect x="360" y="16" width="6" height="2"/>
|
|
116
|
+
<rect x="370" y="16" width="6" height="2"/>
|
|
117
|
+
<rect x="380" y="16" width="6" height="2"/>
|
|
118
|
+
<rect x="390" y="16" width="6" height="2"/>
|
|
119
|
+
<rect x="400" y="16" width="6" height="2"/>
|
|
120
|
+
<rect x="410" y="16" width="6" height="2"/>
|
|
121
|
+
<rect x="420" y="16" width="6" height="2"/>
|
|
122
|
+
<rect x="430" y="16" width="6" height="2"/>
|
|
123
|
+
</g>
|
|
124
|
+
|
|
125
|
+
<!-- status row: red dot + timer + key hints -->
|
|
126
|
+
<circle cx="22" cy="184" r="11" fill="url(#recPulse)"/>
|
|
127
|
+
<text class="rec mono" x="14" y="190" font-size="13">●</text>
|
|
128
|
+
<text class="mono" font-size="11" y="190">
|
|
129
|
+
<tspan x="34" class="muted" font-weight="700">0:14</tspan>
|
|
130
|
+
<tspan dx="10" class="dim">·</tspan>
|
|
131
|
+
<tspan dx="10" class="ink" font-weight="700">Enter</tspan>
|
|
132
|
+
<tspan dx="6" class="muted">paste</tspan>
|
|
133
|
+
<tspan dx="10" class="dim">·</tspan>
|
|
134
|
+
<tspan dx="10" class="ink" font-weight="700">Space</tspan>
|
|
135
|
+
<tspan dx="6" class="muted">pause</tspan>
|
|
136
|
+
<tspan dx="10" class="dim">·</tspan>
|
|
137
|
+
<tspan dx="10" class="ink" font-weight="700">Tab</tspan>
|
|
138
|
+
<tspan dx="6" class="muted">settings</tspan>
|
|
139
|
+
<tspan dx="10" class="dim">·</tspan>
|
|
140
|
+
<tspan dx="10" class="ink" font-weight="700">Esc</tspan>
|
|
141
|
+
</text>
|
|
142
|
+
|
|
143
|
+
<!-- footnote -->
|
|
144
|
+
<line x1="14" y1="206" x2="446" y2="206" class="rule" stroke-width="0.5" stroke-dasharray="1 3"/>
|
|
145
|
+
<text class="mono dim" x="14" y="222" font-size="9" letter-spacing="1.4">VAD-CHUNKED · LIVE PARTIAL · PASTE ON ENTER</text>
|
|
146
|
+
</g>
|
|
147
|
+
|
|
148
|
+
<!-- FIG 02 — Settings screen -->
|
|
149
|
+
<text class="mono muted" x="40" y="514" font-size="9" letter-spacing="2">FIG. 02 — SETTINGS (TAB)</text>
|
|
150
|
+
<text class="mono muted" x="500" y="514" font-size="9" letter-spacing="2" text-anchor="end">~/.config/rpiv-voice/voice.json</text>
|
|
151
|
+
<line x1="40" y1="524" x2="500" y2="524" class="rule" stroke-width="0.75"/>
|
|
152
|
+
|
|
153
|
+
<g transform="translate(40 542)">
|
|
154
|
+
<rect x="0" y="0" width="460" height="172" fill="#13110D" stroke="#2A2620" stroke-width="1"/>
|
|
155
|
+
|
|
156
|
+
<text class="mono accent" x="14" y="22" font-size="11" letter-spacing="1.4" font-weight="700">◆ SETTINGS</text>
|
|
157
|
+
<text class="mono dim" x="446" y="22" font-size="9" letter-spacing="1.2" text-anchor="end">Ctrl-S to save</text>
|
|
158
|
+
|
|
159
|
+
<line x1="14" y1="34" x2="446" y2="34" class="rule" stroke-width="0.5" stroke-dasharray="1 3"/>
|
|
160
|
+
|
|
161
|
+
<text class="mono muted" x="14" y="58" font-size="11">microphone</text>
|
|
162
|
+
<text class="mono ink" x="446" y="58" font-size="11" text-anchor="end">MacBook Pro Microphone (default)</text>
|
|
163
|
+
|
|
164
|
+
<text class="mono muted" x="14" y="84" font-size="11">language</text>
|
|
165
|
+
<text class="mono ink" x="446" y="84" font-size="11" text-anchor="end">auto — whisper-base int8</text>
|
|
166
|
+
|
|
167
|
+
<text class="mono muted" x="14" y="110" font-size="11">filter whisper noise</text>
|
|
168
|
+
<g transform="translate(380 96)">
|
|
169
|
+
<rect x="0" y="0" width="66" height="20" rx="10" fill="#1B0F08" stroke="#2A2620" stroke-width="0.75"/>
|
|
170
|
+
<circle cx="50" cy="10" r="7" fill="#FF7A3D"/>
|
|
171
|
+
<text class="mono accent" x="14" y="14" font-size="9" letter-spacing="1.2" font-weight="700">ON</text>
|
|
172
|
+
</g>
|
|
173
|
+
|
|
174
|
+
<line x1="14" y1="128" x2="446" y2="128" class="rule" stroke-width="0.5" stroke-dasharray="1 3"/>
|
|
175
|
+
<text class="mono dim" x="14" y="148" font-size="9" letter-spacing="1.4">strips "thanks for watching" · "[Music]" · loop tokens</text>
|
|
176
|
+
</g>
|
|
177
|
+
|
|
178
|
+
<!-- Pipeline strip -->
|
|
179
|
+
<text class="mono muted" x="40" y="754" font-size="9" letter-spacing="2">FIG. 03 — PIPELINE</text>
|
|
180
|
+
<line x1="40" y1="764" x2="500" y2="764" class="rule" stroke-width="0.75"/>
|
|
181
|
+
|
|
182
|
+
<g transform="translate(40 786)">
|
|
183
|
+
<!-- 4 stages -->
|
|
184
|
+
<g>
|
|
185
|
+
<rect x="0" y="0" width="100" height="48" fill="#15120E" stroke="#2A2620" stroke-width="0.6"/>
|
|
186
|
+
<text class="mono mint" x="50" y="20" font-size="10" font-weight="700" letter-spacing="1.2" text-anchor="middle">SPEAK</text>
|
|
187
|
+
<text class="mono muted" x="50" y="36" font-size="9" letter-spacing="1.2" text-anchor="middle">decibri mic</text>
|
|
188
|
+
</g>
|
|
189
|
+
<text class="accent mono" x="108" y="30" font-size="14" font-weight="700">→</text>
|
|
190
|
+
<g>
|
|
191
|
+
<rect x="120" y="0" width="100" height="48" fill="#15120E" stroke="#2A2620" stroke-width="0.6"/>
|
|
192
|
+
<text class="mono mint" x="170" y="20" font-size="10" font-weight="700" letter-spacing="1.2" text-anchor="middle">VAD CHUNK</text>
|
|
193
|
+
<text class="mono muted" x="170" y="36" font-size="9" letter-spacing="1.2" text-anchor="middle">silero</text>
|
|
194
|
+
</g>
|
|
195
|
+
<text class="accent mono" x="228" y="30" font-size="14" font-weight="700">→</text>
|
|
196
|
+
<g>
|
|
197
|
+
<rect x="240" y="0" width="100" height="48" fill="#15120E" stroke="#2A2620" stroke-width="0.6"/>
|
|
198
|
+
<text class="mono mint" x="290" y="20" font-size="10" font-weight="700" letter-spacing="1.2" text-anchor="middle">DECODE</text>
|
|
199
|
+
<text class="mono muted" x="290" y="36" font-size="9" letter-spacing="1.2" text-anchor="middle">sherpa-onnx</text>
|
|
200
|
+
</g>
|
|
201
|
+
<text class="accent mono" x="348" y="30" font-size="14" font-weight="700">→</text>
|
|
202
|
+
<g>
|
|
203
|
+
<rect x="360" y="0" width="100" height="48" fill="#15120E" stroke="#2A2620" stroke-width="0.6"/>
|
|
204
|
+
<text class="mono accent" x="410" y="20" font-size="10" font-weight="700" letter-spacing="1.2" text-anchor="middle">PASTE</text>
|
|
205
|
+
<text class="mono muted" x="410" y="36" font-size="9" letter-spacing="1.2" text-anchor="middle">Pi editor</text>
|
|
206
|
+
</g>
|
|
207
|
+
</g>
|
|
208
|
+
|
|
209
|
+
<text class="mono dim" x="40" y="868" font-size="9" letter-spacing="1.4">EVERY STAGE LOCAL · AUDIO NEVER LEAVES THE LAPTOP</text>
|
|
210
|
+
|
|
211
|
+
<!-- Stat strip -->
|
|
212
|
+
<line x1="40" y1="896" x2="500" y2="896" class="rule" stroke-width="0.5" stroke-dasharray="1 3"/>
|
|
213
|
+
<g transform="translate(40 916)">
|
|
214
|
+
<text class="mono dim" x="0" y="12" font-size="8.5" letter-spacing="1.6">MODEL</text>
|
|
215
|
+
<text class="mono ink" x="0" y="32" font-size="13" font-weight="700">whisper-base</text>
|
|
216
|
+
<text class="mono muted" x="0" y="48" font-size="9" letter-spacing="1.2">multilingual int8</text>
|
|
217
|
+
|
|
218
|
+
<text class="mono dim" x="160" y="12" font-size="8.5" letter-spacing="1.6">DOWNLOAD</text>
|
|
219
|
+
<text class="mono ink" x="160" y="32" font-size="13" font-weight="700">~198 MB</text>
|
|
220
|
+
<text class="mono muted" x="160" y="48" font-size="9" letter-spacing="1.2">first run only</text>
|
|
221
|
+
|
|
222
|
+
<text class="mono dim" x="300" y="12" font-size="8.5" letter-spacing="1.6">ON DISK</text>
|
|
223
|
+
<text class="mono ink" x="300" y="32" font-size="13" font-weight="700">~157 MB</text>
|
|
224
|
+
<text class="mono muted" x="300" y="48" font-size="9" letter-spacing="1.2">~/.pi/models/</text>
|
|
225
|
+
|
|
226
|
+
<text class="mono dim" x="400" y="12" font-size="8.5" letter-spacing="1.6">LANGUAGES</text>
|
|
227
|
+
<text class="mono ink" x="400" y="32" font-size="13" font-weight="700">~99</text>
|
|
228
|
+
<text class="mono muted" x="400" y="48" font-size="9" letter-spacing="1.2">autodetect</text>
|
|
229
|
+
</g>
|
|
230
|
+
|
|
231
|
+
<line x1="40" y1="1100" x2="500" y2="1100" class="rule" stroke-width="0.5" stroke-dasharray="1 3"/>
|
|
232
|
+
<text class="mono muted" x="40" y="1118" font-size="9" letter-spacing="1.8">SPEAK → CHUNK → DECODE → PASTE</text>
|
|
233
|
+
|
|
234
|
+
<line x1="40" y1="1144" x2="500" y2="1144" class="rule" stroke-width="1"/>
|
|
235
|
+
<text class="mono muted" x="40" y="1168" font-size="9.5" letter-spacing="1.8">
|
|
236
|
+
<tspan class="accent">▶</tspan> pi install npm:@juicesharp/rpiv-voice
|
|
237
|
+
</text>
|
|
238
|
+
<text class="mono muted" x="500" y="1168" font-size="9" letter-spacing="2" text-anchor="end">ON-DEVICE</text>
|
|
239
|
+
</svg>
|
package/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rpiv-voice — Pi extension. Registers the `/voice` command for local voice
|
|
3
|
+
* dictation backed by sherpa-onnx Whisper.
|
|
4
|
+
*
|
|
5
|
+
* UI strings localize at render time via the i18n bridge. Strings are
|
|
6
|
+
* registered with rpiv-i18n here, once, at module init — but only when the
|
|
7
|
+
* SDK is actually installed. If `@juicesharp/rpiv-i18n` is missing
|
|
8
|
+
* (standalone install of just this package), the dynamic-load shim no-ops
|
|
9
|
+
* and the bridge's `t(key, fallback)` returns the inline English literal at
|
|
10
|
+
* every call site. The extension stays online either way.
|
|
11
|
+
*
|
|
12
|
+
* Adding a locale: drop `locales/<code>.json` next to en.json (mirroring
|
|
13
|
+
* the key set), then add the load + entry to the `registerStrings` call
|
|
14
|
+
* below. See `@juicesharp/rpiv-i18n` README → "Contributing translations"
|
|
15
|
+
* for the full convention.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import { registerVoiceCommand } from "./command/voice-command.js";
|
|
22
|
+
import { I18N_NAMESPACE } from "./state/i18n-bridge.js";
|
|
23
|
+
|
|
24
|
+
type TranslationMap = Readonly<Record<string, string>>;
|
|
25
|
+
type I18nSDK = { registerStrings: (namespace: string, byLocale: Record<string, TranslationMap>) => void };
|
|
26
|
+
|
|
27
|
+
function loadLocale(code: string): TranslationMap {
|
|
28
|
+
// A missing or malformed locale file degrades gracefully: registerStrings
|
|
29
|
+
// records an empty map for the locale, so render-time `t(key, fallback)`
|
|
30
|
+
// returns the canonical English literal at the call site. Crashing here
|
|
31
|
+
// would take the entire /voice command offline at module init.
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(
|
|
34
|
+
readFileSync(fileURLToPath(new URL(`./locales/${code}.json`, import.meta.url)), "utf-8"),
|
|
35
|
+
) as TranslationMap;
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.warn(
|
|
38
|
+
`rpiv-voice: failed to load locales/${code}.json — falling back to English (${(err as Error).message})`,
|
|
39
|
+
);
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Dynamic import keeps `@juicesharp/rpiv-i18n` a soft optional peer: when the
|
|
45
|
+
// SDK is installed alongside this package the strings register and
|
|
46
|
+
// `/languages` flips them live; when it isn't, the import rejects here, we
|
|
47
|
+
// no-op, and the bridge's English-fallback shim keeps the extension online.
|
|
48
|
+
try {
|
|
49
|
+
const sdk = (await import("@juicesharp/rpiv-i18n")) as I18nSDK;
|
|
50
|
+
sdk.registerStrings(I18N_NAMESPACE, {
|
|
51
|
+
de: loadLocale("de"),
|
|
52
|
+
en: loadLocale("en"),
|
|
53
|
+
es: loadLocale("es"),
|
|
54
|
+
fr: loadLocale("fr"),
|
|
55
|
+
pt: loadLocale("pt"),
|
|
56
|
+
"pt-BR": loadLocale("pt-BR"),
|
|
57
|
+
ru: loadLocale("ru"),
|
|
58
|
+
uk: loadLocale("uk"),
|
|
59
|
+
});
|
|
60
|
+
} catch {
|
|
61
|
+
// SDK absent — extension still loads with English-only UI.
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default function (pi: ExtensionAPI): void {
|
|
65
|
+
registerVoiceCommand(pi);
|
|
66
|
+
}
|
package/locales/de.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.description": "Text per Sprache diktieren — lokales STT, keine Cloud",
|
|
3
|
+
|
|
4
|
+
"error.requires_interactive": "/voice erfordert den interaktiven Modus",
|
|
5
|
+
"error.model_download_failed": "STT-Modell konnte nicht heruntergeladen werden. Internetverbindung prüfen.",
|
|
6
|
+
"error.engine_load_failed": "STT-Modell konnte nicht geladen werden.",
|
|
7
|
+
"error.mic_unavailable": "Mikrofon nicht verfügbar. Prüfe, ob ein Eingabegerät angeschlossen ist und Pi Mikrofonzugriff hat.",
|
|
8
|
+
|
|
9
|
+
"splash.preparing": "Modell wird vorbereitet…",
|
|
10
|
+
"splash.downloading": "Whisper wird geladen…",
|
|
11
|
+
"splash.extracting": "Modelldateien werden entpackt…",
|
|
12
|
+
"splash.verifying": "Modelldateien werden geprüft…",
|
|
13
|
+
"splash.loading_engine": "Sprachmodell wird geladen…",
|
|
14
|
+
"splash.initializing_mic": "Mikrofon wird initialisiert…",
|
|
15
|
+
|
|
16
|
+
"footer.enter_paste": "Enter zum Einfügen",
|
|
17
|
+
"footer.space_pause": "Leertaste zum Pausieren",
|
|
18
|
+
"footer.space_resume": "Leertaste zum Fortsetzen",
|
|
19
|
+
"footer.tab_settings": "Tab für Einstellungen",
|
|
20
|
+
"footer.esc_cancel": "Esc zum Abbrechen",
|
|
21
|
+
"footer.esc_back": "Esc zum Zurückgehen",
|
|
22
|
+
"footer.ctrl_s_save": "Strg-S zum Speichern",
|
|
23
|
+
"footer.enter_toggle": "Enter zum Umschalten",
|
|
24
|
+
|
|
25
|
+
"transcript.placeholder": "Höre zu...",
|
|
26
|
+
|
|
27
|
+
"notify.settings_saved": "Spracheinstellungen gespeichert",
|
|
28
|
+
|
|
29
|
+
"status.recording": "Aufnahme",
|
|
30
|
+
"status.paused": "Pausiert",
|
|
31
|
+
|
|
32
|
+
"settings.microphone_label": "Mikrofon",
|
|
33
|
+
"settings.microphone_value_default": "System-Standardmikrofon",
|
|
34
|
+
"settings.language_label": "Sprache",
|
|
35
|
+
"settings.language_value_auto": "Automatisch erkennen",
|
|
36
|
+
"settings.language_hint": "Mit /languages ändern.",
|
|
37
|
+
"settings.hallucination_filter_label": "Whisper-Rauschen filtern",
|
|
38
|
+
"settings.hallucination_filter_hint": "Entfernt Stille-Artefakte. Bei Einzelwort-Diktat ausschalten."
|
|
39
|
+
}
|
package/locales/en.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.description": "Dictate text with your voice — local STT, no cloud",
|
|
3
|
+
|
|
4
|
+
"error.requires_interactive": "/voice requires interactive mode",
|
|
5
|
+
"error.model_download_failed": "Failed to download STT model. Check your internet connection.",
|
|
6
|
+
"error.model_extract_failed": "Downloaded STT model archive is corrupt. Please retry.",
|
|
7
|
+
"error.model_verify_failed": "STT model files are incomplete after download. Please retry.",
|
|
8
|
+
"error.model_stale_install": "STT model files were removed or corrupted. They will be redownloaded on next launch.",
|
|
9
|
+
"error.engine_load_failed": "Failed to load STT model.",
|
|
10
|
+
"error.mic_unavailable": "Microphone unavailable. Check that an input device is connected and that Pi has microphone permission.",
|
|
11
|
+
|
|
12
|
+
"splash.preparing": "Preparing model…",
|
|
13
|
+
"splash.downloading": "Downloading Whisper…",
|
|
14
|
+
"splash.extracting": "Extracting model files…",
|
|
15
|
+
"splash.verifying": "Verifying model files…",
|
|
16
|
+
"splash.loading_engine": "Loading speech model…",
|
|
17
|
+
"splash.initializing_mic": "Initializing microphone…",
|
|
18
|
+
|
|
19
|
+
"footer.enter_paste": "Enter to paste",
|
|
20
|
+
"footer.space_pause": "Space to pause",
|
|
21
|
+
"footer.space_resume": "Space to resume",
|
|
22
|
+
"footer.tab_settings": "Tab for settings",
|
|
23
|
+
"footer.esc_cancel": "Esc to cancel",
|
|
24
|
+
"footer.esc_back": "Esc to go back",
|
|
25
|
+
"footer.ctrl_s_save": "Ctrl-S to save",
|
|
26
|
+
"footer.enter_toggle": "Enter to toggle",
|
|
27
|
+
|
|
28
|
+
"transcript.placeholder": "Listening...",
|
|
29
|
+
|
|
30
|
+
"notify.settings_saved": "Voice settings saved",
|
|
31
|
+
|
|
32
|
+
"status.recording": "Recording",
|
|
33
|
+
"status.paused": "Paused",
|
|
34
|
+
|
|
35
|
+
"settings.microphone_label": "Microphone",
|
|
36
|
+
"settings.microphone_value_default": "System default input",
|
|
37
|
+
"settings.language_label": "Language",
|
|
38
|
+
"settings.language_value_auto": "Auto-detect",
|
|
39
|
+
"settings.language_hint": "Run /languages to change.",
|
|
40
|
+
"settings.hallucination_filter_label": "Filter Whisper noise",
|
|
41
|
+
"settings.hallucination_filter_hint": "Drops silence-segment artifacts. Turn off for single-word dictation."
|
|
42
|
+
}
|
package/locales/es.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.description": "Dicta texto con tu voz — STT local, sin nube",
|
|
3
|
+
|
|
4
|
+
"error.requires_interactive": "/voice requiere modo interactivo",
|
|
5
|
+
"error.model_download_failed": "No se pudo descargar el modelo STT. Comprueba tu conexión a internet.",
|
|
6
|
+
"error.engine_load_failed": "No se pudo cargar el modelo STT.",
|
|
7
|
+
"error.mic_unavailable": "Micrófono no disponible. Comprueba que haya un dispositivo de entrada conectado y que Pi tenga permiso de micrófono.",
|
|
8
|
+
|
|
9
|
+
"splash.preparing": "Preparando el modelo…",
|
|
10
|
+
"splash.downloading": "Descargando Whisper…",
|
|
11
|
+
"splash.extracting": "Extrayendo archivos del modelo…",
|
|
12
|
+
"splash.verifying": "Verificando archivos del modelo…",
|
|
13
|
+
"splash.loading_engine": "Cargando modelo de voz…",
|
|
14
|
+
"splash.initializing_mic": "Inicializando micrófono…",
|
|
15
|
+
|
|
16
|
+
"footer.enter_paste": "Enter para pegar",
|
|
17
|
+
"footer.space_pause": "Espacio para pausar",
|
|
18
|
+
"footer.space_resume": "Espacio para reanudar",
|
|
19
|
+
"footer.tab_settings": "Tab para ajustes",
|
|
20
|
+
"footer.esc_cancel": "Esc para cancelar",
|
|
21
|
+
"footer.esc_back": "Esc para volver",
|
|
22
|
+
"footer.ctrl_s_save": "Ctrl-S para guardar",
|
|
23
|
+
"footer.enter_toggle": "Enter para alternar",
|
|
24
|
+
|
|
25
|
+
"transcript.placeholder": "Escuchando...",
|
|
26
|
+
|
|
27
|
+
"notify.settings_saved": "Ajustes de voz guardados",
|
|
28
|
+
|
|
29
|
+
"status.recording": "Grabando",
|
|
30
|
+
"status.paused": "Pausado",
|
|
31
|
+
|
|
32
|
+
"settings.microphone_label": "Micrófono",
|
|
33
|
+
"settings.microphone_value_default": "Entrada por defecto del sistema",
|
|
34
|
+
"settings.language_label": "Idioma",
|
|
35
|
+
"settings.language_value_auto": "Detección automática",
|
|
36
|
+
"settings.language_hint": "Ejecuta /languages para cambiar.",
|
|
37
|
+
"settings.hallucination_filter_label": "Filtrar ruido de Whisper",
|
|
38
|
+
"settings.hallucination_filter_hint": "Descarta artefactos de silencio. Desactívalo para dictar palabras sueltas."
|
|
39
|
+
}
|
package/locales/fr.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.description": "Dictez du texte à la voix — STT local, sans cloud",
|
|
3
|
+
|
|
4
|
+
"error.requires_interactive": "/voice nécessite le mode interactif",
|
|
5
|
+
"error.model_download_failed": "Impossible de télécharger le modèle STT. Vérifiez votre connexion internet.",
|
|
6
|
+
"error.engine_load_failed": "Impossible de charger le modèle STT.",
|
|
7
|
+
"error.mic_unavailable": "Microphone indisponible. Vérifiez qu'un périphérique d'entrée est branché et que Pi a l'autorisation micro.",
|
|
8
|
+
|
|
9
|
+
"splash.preparing": "Préparation du modèle…",
|
|
10
|
+
"splash.downloading": "Téléchargement de Whisper…",
|
|
11
|
+
"splash.extracting": "Extraction des fichiers du modèle…",
|
|
12
|
+
"splash.verifying": "Vérification des fichiers du modèle…",
|
|
13
|
+
"splash.loading_engine": "Chargement du modèle vocal…",
|
|
14
|
+
"splash.initializing_mic": "Initialisation du microphone…",
|
|
15
|
+
|
|
16
|
+
"footer.enter_paste": "Entrée pour coller",
|
|
17
|
+
"footer.space_pause": "Espace pour mettre en pause",
|
|
18
|
+
"footer.space_resume": "Espace pour reprendre",
|
|
19
|
+
"footer.tab_settings": "Tab pour les paramètres",
|
|
20
|
+
"footer.esc_cancel": "Échap pour annuler",
|
|
21
|
+
"footer.esc_back": "Échap pour revenir",
|
|
22
|
+
"footer.ctrl_s_save": "Ctrl-S pour enregistrer",
|
|
23
|
+
"footer.enter_toggle": "Entrée pour basculer",
|
|
24
|
+
|
|
25
|
+
"transcript.placeholder": "Écoute...",
|
|
26
|
+
|
|
27
|
+
"notify.settings_saved": "Paramètres vocaux enregistrés",
|
|
28
|
+
|
|
29
|
+
"status.recording": "Enregistrement",
|
|
30
|
+
"status.paused": "En pause",
|
|
31
|
+
|
|
32
|
+
"settings.microphone_label": "Microphone",
|
|
33
|
+
"settings.microphone_value_default": "Entrée par défaut du système",
|
|
34
|
+
"settings.language_label": "Langue",
|
|
35
|
+
"settings.language_value_auto": "Détection automatique",
|
|
36
|
+
"settings.language_hint": "Exécutez /languages pour changer.",
|
|
37
|
+
"settings.hallucination_filter_label": "Filtrer le bruit de Whisper",
|
|
38
|
+
"settings.hallucination_filter_hint": "Supprime les artefacts des silences. À désactiver pour dicter un seul mot."
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.description": "Dite texto com a sua voz — STT local, sem nuvem",
|
|
3
|
+
|
|
4
|
+
"error.requires_interactive": "/voice requer o modo interativo",
|
|
5
|
+
"error.model_download_failed": "Falha ao baixar o modelo STT. Verifique sua conexão com a internet.",
|
|
6
|
+
"error.engine_load_failed": "Falha ao carregar o modelo STT.",
|
|
7
|
+
"error.mic_unavailable": "Microfone indisponível. Verifique se há um dispositivo de entrada conectado e se o Pi tem permissão de microfone.",
|
|
8
|
+
|
|
9
|
+
"splash.preparing": "Preparando o modelo…",
|
|
10
|
+
"splash.downloading": "Baixando Whisper…",
|
|
11
|
+
"splash.extracting": "Extraindo arquivos do modelo…",
|
|
12
|
+
"splash.verifying": "Verificando arquivos do modelo…",
|
|
13
|
+
"splash.loading_engine": "Carregando o modelo de voz…",
|
|
14
|
+
"splash.initializing_mic": "Inicializando o microfone…",
|
|
15
|
+
|
|
16
|
+
"footer.enter_paste": "Enter para colar",
|
|
17
|
+
"footer.space_pause": "Espaço para pausar",
|
|
18
|
+
"footer.space_resume": "Espaço para retomar",
|
|
19
|
+
"footer.tab_settings": "Tab para configurações",
|
|
20
|
+
"footer.esc_cancel": "Esc para cancelar",
|
|
21
|
+
"footer.esc_back": "Esc para voltar",
|
|
22
|
+
"footer.ctrl_s_save": "Ctrl-S para salvar",
|
|
23
|
+
"footer.enter_toggle": "Enter para alternar",
|
|
24
|
+
|
|
25
|
+
"transcript.placeholder": "Ouvindo...",
|
|
26
|
+
|
|
27
|
+
"notify.settings_saved": "Configurações de voz salvas",
|
|
28
|
+
|
|
29
|
+
"status.recording": "Gravando",
|
|
30
|
+
"status.paused": "Pausado",
|
|
31
|
+
|
|
32
|
+
"settings.microphone_label": "Microfone",
|
|
33
|
+
"settings.microphone_value_default": "Entrada padrão do sistema",
|
|
34
|
+
"settings.language_label": "Idioma",
|
|
35
|
+
"settings.language_value_auto": "Detecção automática",
|
|
36
|
+
"settings.language_hint": "Execute /languages para mudar.",
|
|
37
|
+
"settings.hallucination_filter_label": "Filtrar ruído do Whisper",
|
|
38
|
+
"settings.hallucination_filter_hint": "Descarta artefatos de silêncio. Desligue ao ditar palavras isoladas."
|
|
39
|
+
}
|
package/locales/pt.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.description": "Dite texto com a sua voz — STT local, sem nuvem",
|
|
3
|
+
|
|
4
|
+
"error.requires_interactive": "/voice requer o modo interativo",
|
|
5
|
+
"error.model_download_failed": "Falha ao transferir o modelo STT. Verifica a tua ligação à internet.",
|
|
6
|
+
"error.engine_load_failed": "Falha ao carregar o modelo STT.",
|
|
7
|
+
"error.mic_unavailable": "Microfone indisponível. Verifica se há um dispositivo de entrada ligado e se o Pi tem permissão de microfone.",
|
|
8
|
+
|
|
9
|
+
"splash.preparing": "A preparar o modelo…",
|
|
10
|
+
"splash.downloading": "A transferir Whisper…",
|
|
11
|
+
"splash.extracting": "A extrair ficheiros do modelo…",
|
|
12
|
+
"splash.verifying": "A verificar ficheiros do modelo…",
|
|
13
|
+
"splash.loading_engine": "A carregar o modelo de voz…",
|
|
14
|
+
"splash.initializing_mic": "A inicializar o microfone…",
|
|
15
|
+
|
|
16
|
+
"footer.enter_paste": "Enter para colar",
|
|
17
|
+
"footer.space_pause": "Espaço para pausar",
|
|
18
|
+
"footer.space_resume": "Espaço para retomar",
|
|
19
|
+
"footer.tab_settings": "Tab para definições",
|
|
20
|
+
"footer.esc_cancel": "Esc para cancelar",
|
|
21
|
+
"footer.esc_back": "Esc para voltar",
|
|
22
|
+
"footer.ctrl_s_save": "Ctrl-S para guardar",
|
|
23
|
+
"footer.enter_toggle": "Enter para alternar",
|
|
24
|
+
|
|
25
|
+
"transcript.placeholder": "A ouvir...",
|
|
26
|
+
|
|
27
|
+
"notify.settings_saved": "Definições de voz guardadas",
|
|
28
|
+
|
|
29
|
+
"status.recording": "A gravar",
|
|
30
|
+
"status.paused": "Em pausa",
|
|
31
|
+
|
|
32
|
+
"settings.microphone_label": "Microfone",
|
|
33
|
+
"settings.microphone_value_default": "Entrada predefinida do sistema",
|
|
34
|
+
"settings.language_label": "Idioma",
|
|
35
|
+
"settings.language_value_auto": "Deteção automática",
|
|
36
|
+
"settings.language_hint": "Executa /languages para mudar.",
|
|
37
|
+
"settings.hallucination_filter_label": "Filtrar ruído do Whisper",
|
|
38
|
+
"settings.hallucination_filter_hint": "Descarta artefactos de silêncio. Desliga ao ditar palavras isoladas."
|
|
39
|
+
}
|
package/locales/ru.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.description": "Диктуйте текст голосом — локальное распознавание, без облака",
|
|
3
|
+
|
|
4
|
+
"error.requires_interactive": "/voice требует интерактивного режима",
|
|
5
|
+
"error.model_download_failed": "Не удалось скачать модель STT. Проверьте подключение к интернету.",
|
|
6
|
+
"error.engine_load_failed": "Не удалось загрузить модель STT.",
|
|
7
|
+
"error.mic_unavailable": "Микрофон недоступен. Проверьте, что устройство ввода подключено и у Pi есть разрешение на микрофон.",
|
|
8
|
+
|
|
9
|
+
"splash.preparing": "Подготовка модели…",
|
|
10
|
+
"splash.downloading": "Загрузка Whisper…",
|
|
11
|
+
"splash.extracting": "Распаковка файлов модели…",
|
|
12
|
+
"splash.verifying": "Проверка файлов модели…",
|
|
13
|
+
"splash.loading_engine": "Загрузка речевой модели…",
|
|
14
|
+
"splash.initializing_mic": "Инициализация микрофона…",
|
|
15
|
+
|
|
16
|
+
"footer.enter_paste": "Enter — вставить",
|
|
17
|
+
"footer.space_pause": "Пробел — пауза",
|
|
18
|
+
"footer.space_resume": "Пробел — продолжить",
|
|
19
|
+
"footer.tab_settings": "Tab — настройки",
|
|
20
|
+
"footer.esc_cancel": "Esc — отменить",
|
|
21
|
+
"footer.esc_back": "Esc — назад",
|
|
22
|
+
"footer.ctrl_s_save": "Ctrl-S — сохранить",
|
|
23
|
+
"footer.enter_toggle": "Enter — переключить",
|
|
24
|
+
|
|
25
|
+
"transcript.placeholder": "Слушаю...",
|
|
26
|
+
|
|
27
|
+
"notify.settings_saved": "Настройки голоса сохранены",
|
|
28
|
+
|
|
29
|
+
"status.recording": "Запись",
|
|
30
|
+
"status.paused": "Пауза",
|
|
31
|
+
|
|
32
|
+
"settings.microphone_label": "Микрофон",
|
|
33
|
+
"settings.microphone_value_default": "Устройство ввода по умолчанию",
|
|
34
|
+
"settings.language_label": "Язык",
|
|
35
|
+
"settings.language_value_auto": "Автоопределение",
|
|
36
|
+
"settings.language_hint": "Используйте /languages для смены.",
|
|
37
|
+
"settings.hallucination_filter_label": "Фильтр шумов Whisper",
|
|
38
|
+
"settings.hallucination_filter_hint": "Убирает артефакты тишины. Выключите для диктовки одного слова."
|
|
39
|
+
}
|
package/locales/uk.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.description": "Диктуйте текст голосом — локальне розпізнавання, без хмари",
|
|
3
|
+
|
|
4
|
+
"error.requires_interactive": "/voice потребує інтерактивного режиму",
|
|
5
|
+
"error.model_download_failed": "Не вдалося завантажити модель STT. Перевірте підключення до інтернету.",
|
|
6
|
+
"error.engine_load_failed": "Не вдалося завантажити модель STT.",
|
|
7
|
+
"error.mic_unavailable": "Мікрофон недоступний. Перевірте, що пристрій вводу підключено й у Pi є дозвіл на мікрофон.",
|
|
8
|
+
|
|
9
|
+
"splash.preparing": "Підготовка моделі…",
|
|
10
|
+
"splash.downloading": "Завантаження Whisper…",
|
|
11
|
+
"splash.extracting": "Розпаковування файлів моделі…",
|
|
12
|
+
"splash.verifying": "Перевірка файлів моделі…",
|
|
13
|
+
"splash.loading_engine": "Завантаження мовленнєвої моделі…",
|
|
14
|
+
"splash.initializing_mic": "Ініціалізація мікрофона…",
|
|
15
|
+
|
|
16
|
+
"footer.enter_paste": "Enter — вставити",
|
|
17
|
+
"footer.space_pause": "Пробіл — пауза",
|
|
18
|
+
"footer.space_resume": "Пробіл — продовжити",
|
|
19
|
+
"footer.tab_settings": "Tab — налаштування",
|
|
20
|
+
"footer.esc_cancel": "Esc — скасувати",
|
|
21
|
+
"footer.esc_back": "Esc — назад",
|
|
22
|
+
"footer.ctrl_s_save": "Ctrl-S — зберегти",
|
|
23
|
+
"footer.enter_toggle": "Enter — перемкнути",
|
|
24
|
+
|
|
25
|
+
"transcript.placeholder": "Слухаю...",
|
|
26
|
+
|
|
27
|
+
"notify.settings_saved": "Налаштування голосу збережено",
|
|
28
|
+
|
|
29
|
+
"status.recording": "Запис",
|
|
30
|
+
"status.paused": "Пауза",
|
|
31
|
+
|
|
32
|
+
"settings.microphone_label": "Мікрофон",
|
|
33
|
+
"settings.microphone_value_default": "Пристрій вводу за замовчуванням",
|
|
34
|
+
"settings.language_label": "Мова",
|
|
35
|
+
"settings.language_value_auto": "Автовизначення",
|
|
36
|
+
"settings.language_hint": "Використайте /languages, щоб змінити.",
|
|
37
|
+
"settings.hallucination_filter_label": "Фільтр шумів Whisper",
|
|
38
|
+
"settings.hallucination_filter_hint": "Прибирає артефакти тиші. Вимкніть для диктування одного слова."
|
|
39
|
+
}
|