@ragnoryok1/dsh-client-locale-ru 0.2.0 → 0.2.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/NAMESPACES.md ADDED
@@ -0,0 +1,93 @@
1
+ <!-- Сгенерировано `npm run namespaces`; проверяется `npm run check:namespaces`. -->
2
+ # Что переведено
3
+
4
+ Пакет переводит **77 namespace** — 4495 строк. Колонка «как это выглядит»
5
+ берёт заголовок самого namespace (то, что читает пользователь), а не отдельное описание:
6
+ список нельзя разойтись с переводами.
7
+
8
+ Колонка «источник» говорит, откуда взят английский текст: «харнесс» — собственные словари
9
+ клиента, «пакет@версия» — словарь стороннего плагина, выгруженный из его собственной
10
+ публикации (`npm run fetch:plugins`). Плагины проверяются теми же правилами, поэтому
11
+ список — это обещание, а не перечень.
12
+
13
+ | namespace | как это выглядит | строк | источник |
14
+ |---|---|---|---|
15
+ | `agent-team` | Команда агентов | 25 | харнесс |
16
+ | `approval` | — | 5 | харнесс |
17
+ | `chat` | — | 200 | харнесс |
18
+ | `claude-code-mods` | — | 4 | харнесс |
19
+ | `command` | — | 28 | харнесс |
20
+ | `common` | — | 41 | харнесс |
21
+ | `conversation` | — | 379 | харнесс |
22
+ | `cordis` | — | 51 | харнесс |
23
+ | `cotTranslation` | — | 32 | харнесс |
24
+ | `deepseek-model-label-1017` | — | 1 | харнесс |
25
+ | `deliverables` | — | 60 | харнесс |
26
+ | `documentHtml` | HTML | 4 | харнесс |
27
+ | `documentMarkdown` | — | 4 | харнесс |
28
+ | `dsh-skill-explorer` | — | 75 | @linxin666/dsh-client-ui-skill-explorer@0.4.7 |
29
+ | `dsh-ssh` | — | 154 | @linxin666/dsh-ssh@0.4.7 |
30
+ | `dsh-web-ui-market` | — | 106 | @linxin666/dsh-client-ui-market@0.4.7 |
31
+ | `dsh-web-ui-preset-center` | — | 46 | @linxin666/dsh-client-ui-preset-center@0.4.7 |
32
+ | `dsh-web-ui-session-archive` | — | 124 | @linxin666/dsh-session-archive@0.4.7 |
33
+ | `dsh-web-ui-usage` | — | 63 | @linxin666/dsh-usage@0.4.7 |
34
+ | `feedback` | — | 20 | харнесс |
35
+ | `git-graph` | — | 63 | @linxin666/dsh-client-ui-git-graph@0.4.7 |
36
+ | `goal` | — | 12 | харнесс |
37
+ | `inspectorPanel` | Инспектор Node.js | 4 | харнесс |
38
+ | `job` | — | 50 | харнесс |
39
+ | `liangshen` | — | 42 | @linxin666/dsh-liangshen@0.4.7 |
40
+ | `model` | — | 23 | харнесс |
41
+ | `open-in-app` | — | 45 | харнесс |
42
+ | `permission.access` | Права доступа | 20 | харнесс |
43
+ | `pet` | — | 81 | @linxin666/dsh-pet@0.4.7 |
44
+ | `plan` | — | 17 | харнесс |
45
+ | `pluginManager` | Плагины | 227 | харнесс |
46
+ | `pluginMarket` | Обзор плагинов | 78 | харнесс |
47
+ | `question` | — | 31 | харнесс |
48
+ | `reference` | — | 15 | харнесс |
49
+ | `remote` | Удалённый доступ | 115 | @linxin666/dsh-remote-web-ui@0.4.7 |
50
+ | `schedule.catalog` | — | 90 | харнесс |
51
+ | `schedule.manager` | Задачи автоматизации | 207 | харнесс |
52
+ | `secretMask` | — | 2 | харнесс |
53
+ | `session-inspector` | — | 52 | харнесс |
54
+ | `session-log-download` | — | 10 | харнесс |
55
+ | `settings` | Настройки | 41 | харнесс |
56
+ | `settings.account` | Учётная запись | 94 | харнесс |
57
+ | `settings.agentLoop` | Цикл агента | 12 | харнесс |
58
+ | `settings.agentPreset` | Пресеты агента | 47 | харнесс |
59
+ | `settings.locale` | — | 1 | харнесс |
60
+ | `settings.models` | Модели | 134 | харнесс |
61
+ | `settings.permission` | Права доступа | 20 | харнесс |
62
+ | `settings.pluginInventory` | — | 37 | харнесс |
63
+ | `settings.plugins` | Встроенные плагины | 5 | харнесс |
64
+ | `settings.sessionLog` | — | 4 | харнесс |
65
+ | `settings.shell` | Командная оболочка | 14 | харнесс |
66
+ | `settings.subagent` | — | 35 | харнесс |
67
+ | `settings.theme` | — | 25 | харнесс |
68
+ | `settings.webSearch` | Веб-поиск | 27 | харнесс |
69
+ | `shortcuts` | Горячие клавиши | 56 | харнесс |
70
+ | `shortcuts.layout` | — | 1 | харнесс |
71
+ | `sidebar` | — | 5 | харнесс |
72
+ | `sidebarBrowser` | — | 26 | харнесс |
73
+ | `sidebarCodePreview` | Код | 3 | харнесс |
74
+ | `sidebarDocumentPreview` | — | 22 | харнесс |
75
+ | `sidebarExcel` | Таблица | 15 | харнесс |
76
+ | `sidebarFiles` | — | 17 | харнесс |
77
+ | `sidebarImage` | Изображение | 11 | харнесс |
78
+ | `sidebarOffice` | Документ Office | 15 | харнесс |
79
+ | `sidebarPdf` | PDF | 15 | харнесс |
80
+ | `sidebarRight` | — | 34 | харнесс |
81
+ | `sidebarTerminal` | Терминал | 28 | харнесс |
82
+ | `skill` | — | 8 | харнесс |
83
+ | `slash.menu` | — | 9 | харнесс |
84
+ | `subagent` | — | 79 | харнесс |
85
+ | `task-board` | — | 437 | @linxin666/dsh-client-ui-task-board@0.4.7 |
86
+ | `task-board-github` | — | 113 | @linxin666/dsh-client-ui-task-board-github@0.4.7 |
87
+ | `trajectory` | — | 196 | харнесс |
88
+ | `update` | — | 43 | @linxin666/dsh-update@0.4.7 |
89
+ | `voice-input` | — | 109 | харнесс |
90
+ | `workflowRun` | — | 20 | харнесс |
91
+ | `workspace` | — | 131 | харнесс |
92
+
93
+ Итого: **77** namespace, **4495** строк.
package/README.en.md ADDED
@@ -0,0 +1,551 @@
1
+ <!-- deepseek-harness-meta
2
+ {
3
+ "name": "Russian (ru) language pack for DeepSeek Harness",
4
+ "version": "0.2.1",
5
+ "tags": ["localization", "russian", "i18n", "web-gui", "language-pack"],
6
+ "description": "Full Russian interface for the DeepSeek Harness web GUI: 77 namespaces, 4495 translated strings, Russian screen typography, and a locale audit in CI pinned to a harness tag. Includes the automation-task manager and the reminder catalog.",
7
+ "icon": "https://raw.githubusercontent.com/Ragnoryok1/dsh-client-locale-ru/main/assets/icon.png",
8
+ "compatible_versions": ["v0.2.1-rc.2", "v0.2.1-rc.1", "v0.2.1-alpha.5", "v0.2.1-alpha.4", "v0.2.1-alpha.3", "v0.2.1-alpha.2", "v0.2.1-alpha.1", "v0.2.0-rc.2", "v0.2.0-rc.1", "v0.1.7-rc.2", "v0.1.7-rc.1", "v0.1.7-alpha.2"],
9
+ "screenshots": "images",
10
+ "install_method": "dsh plugin --profile web add @ragnoryok1/dsh-client-locale-ru"
11
+ }
12
+ -->
13
+
14
+ # @ragnoryok1/dsh-client-locale-ru
15
+
16
+ [Русский](README.md) | **English** | [中文](README.zh.md)
17
+
18
+ ![Russian language pack for DeepSeek Harness](assets/banner.jpg)
19
+
20
+ [![npm](https://img.shields.io/npm/v/%40ragnoryok1%2Fdsh-client-locale-ru)](https://www.npmjs.com/package/@ragnoryok1/dsh-client-locale-ru)
21
+ [![license](https://img.shields.io/npm/l/%40ragnoryok1%2Fdsh-client-locale-ru)](LICENSE)
22
+ [![verify](https://github.com/Ragnoryok1/dsh-client-locale-ru/actions/workflows/verify.yml/badge.svg)](https://github.com/Ragnoryok1/dsh-client-locale-ru/actions/workflows/verify.yml)
23
+ [![dsh](https://img.shields.io/badge/dsh-0.2.1--rc.2%2B-6366f1)](https://github.com/deepseek-ai/deepseek-harness)
24
+ [![namespaces](https://img.shields.io/badge/namespaces-77-6466f1)](https://github.com/Ragnoryok1/dsh-client-locale-ru)
25
+ [![strings](https://img.shields.io/badge/strings-4%2C495-6466f1)](https://github.com/Ragnoryok1/dsh-client-locale-ru)
26
+
27
+ Russian (`ru`) language pack for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) web GUI (`dsh`). It registers `ru` as a selectable client language through the locale service (`ctx.locale.addLanguage`) plus one dictionary per namespace — 77 namespaces, 4,495 strings, built against dsh `0.2.1-rc.2`. Install with `dsh plugin --profile web add @ragnoryok1/dsh-client-locale-ru`, then pick **Русский** in Settings → General. Missing keys fall back to English, so a newer harness keeps working. The scope is the harness's own client interface only — third-party plugin dictionaries are deliberately out of scope, and 11 of the 17 most-downloaded ecosystem plugins ship no strings of their own, so they are already Russian through these namespaces. MIT, community-maintained, not affiliated with DeepSeek.
28
+
29
+ > **If the pack was useful to you, please star the repository.** It is the only signal that a
30
+ > translation is wanted, and it is what decides which parts get worked on next.
31
+
32
+ ---
33
+
34
+ The complete inventory is in [NAMESPACES.md](NAMESPACES.md): 77 namespaces, 4495 strings, with the bundled plugin dictionaries marked.
35
+
36
+ The plugin names and descriptions on the Plugins page cannot come from this pack: the harness
37
+ keeps them in `locale/<language>.json` inside each package, builds a translation map from them,
38
+ and its locale service resolves that map without consulting dictionaries at all — «maps do not
39
+ consult registered dictionaries». 221 harness packages ship `locale/en.json`, 219 of them ship
40
+ `locale/zh.json`, and **none ships `locale/ru.json`**, so those 261 strings stay English with any
41
+ language pack; only `locale/ru.json` files on the harness side can close them.
42
+ `scripts/descriptor-coverage.mjs` counts that gap so it is not mixed into our dictionary coverage; the mechanism and the 32 prepared translations are in `docs/descriptor-upstream.md`.
43
+ Plugins outside the harness tree are translated the same way, but their English comes from
44
+ their own publication: `plugins/<namespace>.json` records the package, version, licence and the
45
+ English strings extracted from its bundle (`npm run fetch:plugins`). `npm run report:plugins`
46
+ shows what in your installation is still English: it walks the profile's `node_modules`, reads
47
+ each plugin's own dictionary and compares its keys with ours. A plugin without a dictionary is
48
+ reported as such — there is nothing to translate until it asks for one — and a plugin that
49
+ already carries its own Russian is marked separately. That is how dictionaries
50
+ that would otherwise not exist at all get covered — today `update` (43 strings) for
51
+ `@linxin666/dsh-update`, `git-graph` (63 strings) for `@linxin666/dsh-client-ui-git-graph`,
52
+ `dsh-web-ui-usage` (63 strings) for `@linxin666/dsh-usage` and `dsh-web-ui-session-archive`
53
+ (124 strings) for `@linxin666/dsh-session-archive`, `pet` (81 strings) for `@linxin666/dsh-pet`
54
+ `remote` (115 strings) for `@linxin666/dsh-remote-web-ui`, `dsh-skill-explorer`
55
+ (75 strings) for `@linxin666/dsh-client-ui-skill-explorer` and `dsh-web-ui-market`
56
+ (106 strings) for `@linxin666/dsh-client-ui-market`, `liangshen` (42 strings) for
57
+ `@linxin666/dsh-liangshen` and `dsh-web-ui-preset-center` (46 strings) for
58
+ `@linxin666/dsh-client-ui-preset-center`, `dsh-ssh` (154 strings) for `@linxin666/dsh-ssh` and
59
+ `task-board` (437 strings) for `@linxin666/dsh-client-ui-task-board` and `task-board-github`
60
+ (113 strings) for `@linxin666/dsh-client-ui-task-board-github` — all thirteen extractable
61
+ dictionaries of that family, each translated in full. Another pack's Russian is never copied: there would be nothing to verify it
62
+ against, while the audit compares our value with the English template of that same plugin.
63
+
64
+ ## What it looks like
65
+
66
+ Russian in the language list and the translated settings sections (the screenshots were taken in build `0.1.8`:
67
+ the same screen, but the wording and some sections have changed since — the font-size caption, for one, now reads
68
+ «Влияет только на содержимое диалога»):):
69
+
70
+ ![Settings in Russian: language, appearance, themes](images/settings-ru.png)
71
+
72
+ ![The plugin list in Russian: section headings and the «Экспериментально» badges](images/plugins-ru.png)
73
+
74
+ Both screenshots are cropped at the left edge of the panel: workspace and session names are
75
+ not in frame.
76
+
77
+ Russian locale (`ru`) for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) web GUI (`dsh`).
78
+
79
+ The plugin adds **Russian** as a selectable language through the locale's language-pack contract: `ctx.locale.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })` plus one `ru` dictionary per namespace. Once **Русский** is picked in Settings → General, the interface switches over immediately.
80
+
81
+ > **If the package was useful to you, give it a star.** It is the only way to show that a
82
+ > translation is wanted, and it genuinely affects which tasks get taken on. Not a single
83
+ > “like” button in the interface does that.
84
+ >
85
+ > The repository, meanwhile, remains the place where the state of the translation is visible:
86
+ > a [CHANGELOG](CHANGELOG.md) breaking down every release (including the mistakes that were found
87
+ > in it by its own maintainers), the CI checks, and the list of what is deliberately not translated.
88
+
89
+ ## Installation
90
+
91
+ This is a **hybrid plugin** (`dsh.bundle` + `dsh.client`), so it installs as an ordinary profile bundle through `dsh plugin`:
92
+
93
+ ```sh
94
+ dsh plugin --profile web add @ragnoryok1/dsh-client-locale-ru
95
+ ```
96
+
97
+ After that, the next time `dsh web` starts, **Русский** will appear in the settings. A separate `pnpm add` into a built `dsh web` is not needed — the plugin pulls in the built-in locale through the `locale` service.
98
+
99
+ The manifest is checked against the harness's own rules rather than by eye (`npm run check:manifest`, part of `verify`). Each rule was read out of the harness sources and each is a failure the user would otherwise meet first: `parseDshClient` accepts `platform` as any string while the consumers keep only `platform === 'web'`, so a typo like `'Web'` would **silently drop** the client half; a declared `dsh.client` without the `./client` export makes `client-modules` throw; and `dsh.bundle.patch` has to point at a file that exists.
100
+
101
+ Installing from GitHub works too: `dsh plugin --profile web add Ragnoryok1/dsh-client-locale-ru` (the form the plugin catalogue generates, without the npm scope) installs the repository archive, and the built `lib/` is committed — no build runs on your machine, and none is needed. That is exactly why `npm run check:built` (part of `verify`) rebuilds `lib/` and compares it with the commit: a stale bundle in git would reach the user, while the audit would never see it, because the audit reads the sources. The published package is 482 kB: the banner ships as JPEG, and the icon and source map stay out of the npm payload.
102
+
103
+ ## Why hybrid
104
+
105
+ `dsh web` takes client (UI) plugins only from **workspace packages**, and an external npm package carrying just a `dsh.client` is not picked up. For an external plugin with a UI half to be installable, it has to declare **`dsh.bundle`** (the profile layer), whose `cordis.patch.yml` adds the client entry `locale-ru` to the browser roster through `- insert:`. Other third-party plugins are built the same way (for example `@zseven-w/dsh-android`).
106
+
107
+ ## How it works
108
+
109
+ - **Host half** (`lib/index.js`) — an empty `apply()`: a language plugin needs no server logic, it only provides a valid cordis entry for the Loader.
110
+ - **Browser half** (`lib/client.js`) — registers `ru` and the dictionaries through the `locale` service (injected from `ctx`). There are no framework runtime imports — only the service.
111
+ - **No runtime dependencies** — `lib/client.js` imports no other packages, so it does not drag in heavy dependencies.
112
+ - **Effects** — the language and the dictionaries are registered through `ctx.effect(...)`, so they are unloaded together with the plugin (HMR-safe).
113
+ - **Plural forms** — the locale contract carries no pluralization rules: components choose the
114
+ form as `count === 1 ? 'key.one' : 'key.other'`, while Russian needs three (1 вызов /
115
+ 2 вызова / 5 вызовов). The package takes that over: alongside `.one` and `.other` the
116
+ dictionaries carry `.few` and `.many`, and a wrapper around the runtime's `translate`
117
+ recomputes the form with `Intl.PluralRules('ru')` for any call carrying a numeric `count`
118
+ or `n`. The number decides, not the key the component picked, so «2 вызова инструментов»
119
+ and «5 вызовов инструментов» come out of the same call site. The forms are not limited to
120
+ paired keys: where the harness passes a number to a key it never paired (`terminal.expand`
121
+ with `{n}`), the pack adds its own `.one` / `.few` / `.many` — «Показать ещё 1 строку /
122
+ 3 строки / 7 строк» — and the wrapper looks a form up for those keys too. Where a form does
123
+ not fit (a sentence carrying several numbers), the value is rewritten to be count-neutral:
124
+ «Символов: {total}» instead of «{total} символов», «файлов: {files}» instead of
125
+ «{files} файлов». Checked by `npm run check:plural` at counts 0, 1, 2, 3, 5 and 22, on an
126
+ unpaired key, on a fractional count, and on a foreign language pack whose forms our rules
127
+ must not rewrite.
128
+ - **Fallback** — a missing Russian key falls back to `en` (the configured `fallback`).
129
+
130
+ ## Typography
131
+
132
+ The package brings already-rendered Russian text into line with typographic rules: guillemets
133
+ («ёлочки») instead of straight quotes, an em dash instead of a hyphen between words, an ellipsis
134
+ instead of three dots, a non-breaking space between a number and the following word or unit of
135
+ measurement (so that «187 плагинов» does not break across two lines), and the same space after a
136
+ short word — a preposition, a conjunction or a particle — so that «в режим» wraps as a whole
137
+ instead of leaving «в» alone at the end of a line. The rules are deliberately conservative: they
138
+ fire only on unambiguous patterns, and code, input fields, editable text and elements with
139
+ `data-typography="off"` are not touched at all. A second pass changes nothing.
140
+
141
+ A rule for numeric ranges (an en dash in «5–10») is **deliberately absent**: the only string in
142
+ the whole pack with a hyphen between digits is the cron example `0 9 * * 1-5`, and a dash would
143
+ corrupt it. Measured, not assumed.
144
+
145
+ The rules are checked by a script rather than by eye: `npm run check:typography`.
146
+
147
+ The pass itself sees text nodes only, while a dictionary value also travels into attributes —
148
+ `title`, `aria-label`, `placeholder` — that it never reaches. The values are therefore checked
149
+ separately: the pack may not contain a straight quote, a hyphen standing for a dash, a dash
150
+ without spaces, or three dots. Otherwise the same string would look different depending on
151
+ where it landed, and in a hint or a screen-reader label the rules would not work at all.
152
+
153
+ Text that changes **after** the first pass — token counters, timers, progress — is rewritten
154
+ again, so a live «4 с» keeps its non-breaking space. This is checked on a real document rather
155
+ than on strings: `npm run check:typography:dom` (jsdom, six cases — the first pass, a later text
156
+ change, an added subtree, disposal, a foreign language, and a language switch).
157
+
158
+ The pass runs **only while Russian is active**: the rules are Russian, and applying them to
159
+ another language would edit text this pack does not own — `"quotes"` would become «quotes» in an
160
+ English interface. The pack listens to `locale/change` and starts or stops the pass on a switch
161
+ rather than on installation.
162
+
163
+ ## Checks
164
+
165
+ ```sh
166
+ npm run build # the three outputs
167
+ npm run check:typography # 18 typography rules, including "leave alone"
168
+ npm run check:locale # dictionaries side by side: 6 checks
169
+ npm run check:plural # plural forms: 1 вызов / 2 вызова / 5 вызовов
170
+ npm run check:composition # composed phrases: «Пн, Ср в 09:00», «Раз в 2 дня»
171
+ npm run check:namespaces # NAMESPACES.md matches the built bundle
172
+ npm run check:tarball # what gets installed: the archive is unpacked and checked
173
+ npm run check:workflows # both workflows run the whole verify chain
174
+ npm run check:readmes:pairs # the three READMEs move together (recorded section hashes)
175
+ npm run audit -- <harness tree> # the locale audit
176
+ npm run verify -- <harness tree> # everything together
177
+
178
+ npm run namespaces # regenerate NAMESPACES.md and pack-count.json
179
+ npm run readmes:record # re-record section hashes after editing all three READMEs
180
+ npm run diff:harness -- <old> <new> # what the harness reworded under us
181
+ ```
182
+
183
+ A separate check composes phrases the way the harness does and reads the result whole. The
184
+ audit compares a value with its own template, so a phrase that is correct in isolation but
185
+ broken by substitution stays invisible to it: `frequency.weekly` was filled as «По {weekdays}
186
+ в {time} », while the days arrive as «Пн, Ср» — producing «По Пн, Ср в 09:00», which no
187
+ line-by-line check can see. The same way «Каждые 2 дня» (now «Раз в 2 дня»), «Каждый день в
188
+ 09:00 (Europe/Moscow)» and a composed interval with the plural forms are pinned. The third find
189
+ of the same kind: `time.locale` is not text but a tag for `Intl`, and it was `en` — a rule
190
+ restricted to a month read «Каждый день в January», and the zone label «Moscow Standard Time»
191
+ inside a Russian interface. The tag is `ru` now (the harness's Chinese dictionary uses `zh-CN`
192
+ the same way), and the month sits in parentheses: «Каждый день (январь, март)».
193
+
194
+ The same part-by-part reading found the cron wording: abbreviations («Каждые {step} мин»,
195
+ «Каждые {count} ч») and bookish turns («в минуту {minutes}», «минуты {minutes} в часы
196
+ {hours}»). The counted parts gained plural forms — «Каждые 3 минуты» / «Каждые 5 минут»,
197
+ «Каждые 2 часа» / «Каждые 6 часов» — and the rest were rewritten into one readable labelled
198
+ style: «Каждый час, минуты: 5, 30», «минуты: 5, часы: 09–17». The whole schedule sentence is
199
+ asserted end to end: «Пн–Пт каждые 5 минут». The date stamps are pinned the same way: «5 окт. 2026 г., 15:51» and the zone label «Москва, стандартное время» come from Intl with our tag, without a single Latin word.
200
+
201
+ The audit runs in CI (`.github/workflows/verify.yml`) against a **pinned harness tag**,
202
+ not against some random local checkout. The same run also fires **weekly on a schedule**: a
203
+ translation rots without anyone editing it — the upstream moves on, everything is green
204
+ locally, and the drift surfaces only when a user builds. The gates cover completeness (keys and
205
+ namespaces), plural forms, placeholders, English and Chinese residue, terminology, Latin words in
206
+ prose, text hygiene, edge spaces, style, typography of values, «ё», composed phrases, the length
207
+ of settings sidebar labels and the advertised numbers — and every one of them has already caught
208
+ real defects. Beside them the audit
209
+ prints **advisory reports**,
210
+ which are not gates because they need a human decision: how many namespaces were compared and
211
+ which could not be linked, whether any «number + inflecting word» is left, and whether a short
212
+ string grew past 2.8× its English (a layout risk — «Effort» → «Уровень рассуждения» is normal
213
+ Russian length, not a defect).
214
+
215
+ The audit reads harness templates in the shape the harness writes them: a literal, an **array of
216
+ paragraphs** (that is how the preset guides are stored) and a **reference to a constant**
217
+ (`'hint.plan': PLAN_NEXT_ACTION_EN`). While only the first shape was read, ten keys — eight of
218
+ them whole guide documents — silently fell out of every comparison. And the other way round: an
219
+ empty policy file (`keep-english.json`, a `terminology.json` section) now fails the audit instead
220
+ of widening a gate unnoticed. The numbers the READMEs advertise are checked against the pack the
221
+ same way, because a stale count is simply a wrong claim — and the same numbers are read from the
222
+ `package.json` description, which is what npm shows in search and on the package page (that is
223
+ where «55 namespaces» lived longest) — including the grouped spelling: the
224
+ old leads said `2 489`, and the gate only read the ungrouped form, so the pattern now accepts
225
+ thousands separators. The sentence naming the harness version the pack was built against is
226
+ checked next to it, against the tag the workflow pins, because that workflow is what verifies
227
+ the pack (both leads claimed `0.2.1-alpha.2` while CI had already moved to
228
+ `dsh-v0.2.1-rc.2`). The published size is measured there too: `npm run check:size` packs the
229
+ archive and compares the advertised kilobytes with the real ones, allowing one kilobyte of slack
230
+ because gzip compresses slightly differently across Node builds while the sentence rounds to
231
+ whole kilobytes.
232
+
233
+ - **dead namespaces** — every namespace the package registers has to
234
+ exist in the harness. In 0.1.6 the schedule catalog translations were registered
235
+ under the name `schedule.manager`, whereas `schedule.catalog` is what reads them: the per-key
236
+ coverage check nonetheless reported «всё переведено»;
237
+ - **plural-form pairs** — the `.one`/`.other` sets have to match, because the client
238
+ chooses the form as `count === 1 ? '.one' : '.other'`. That is not enough for Russian:
239
+ every pair also needs `.few` and `.many`, or the wrapper quietly falls back to the neutral
240
+ `.other` and shows «2 д» where «2 дня» was possible. The check runs **per namespace** —
241
+ the wrapper looks the form up in the namespace that asked, so a pair missing on one screen
242
+ while present on another would otherwise stay invisible. It found the four time units
243
+ (`unit.day/hour/minute/second` in `schedule.catalog`) and `trigger` in the task manager;
244
+ the units stopped being abbreviations («д», «ч», «мин») and gained full forms in the right
245
+ case — «через 2 дня», «через 5 часов», and «Каждые 2 дня» became «Раз в 2 дня». The same
246
+ requirement covers the keys the pack added itself (`terminal.expand` and its kin: the
247
+ harness passes them a count but never pairs them) — their forms rest on us alone, and
248
+ losing one would be invisible to everything else;
249
+ - **placeholders** — a Russian value has to carry exactly the `{name}` placeholders its
250
+ English template carries: an extra one reaches the screen literally
251
+ (`Заняло {duration}7мин 26с`, fixed in #2), a dropped one silently removes the
252
+ interpolated value. Packages that ship only the Chinese dictionary are compared
253
+ against `zh`. Our **own plural forms** — the `.few`/`.many` we add, and the `.one` of a key
254
+ the harness never paired — do not exist upstream, so each is compared with the template of
255
+ a sibling form of the same word (`.one`/`.other`) or of the key itself. Without that, 52
256
+ values were never checked at all: that is exactly how a `{count}` can disappear from a form
257
+ and leave the sentence without its number.
258
+ - **residue** — a value has to be Russian: an exact copy of the English template, a leftover
259
+ Chinese character, or an empty string where the template has text is a defect. English
260
+ copies are allowed only by a decision recorded in `scripts/keep-english.json` (addresses,
261
+ format-only strings, protocol and tool names, abbreviations), so a new untranslated line
262
+ cannot slip through unnoticed. The decisions themselves are checked too: an entry that no
263
+ longer excuses anything fails the build — either its key left the pack, or the value was
264
+ translated and the decision went stale. This is how the `deepSeekBaseUrl` entry was found:
265
+ the English template gained a note about `DEEPSEEK_BASE_URL`, while the input placeholder
266
+ has to be a bare example URL, so that note would have been noise inside the field. The entry
267
+ is gone, the value stays. A decision may also name a closed list: `chat · app.*` allows
268
+ thirty application names in one line, because a dictionary of application names is a list of
269
+ names. And a value with no word outside its placeholders (`{from}–{to}`, `{percent}%`, `, `)
270
+ needs no decision at all: there is nothing in it to translate, so the policy stays about
271
+ words rather than about punctuation. The residue rule reads **every** template of a key, not
272
+ only the `en` block: 60 values in dictionaries with an unrecognised block used to go
273
+ unexamined, while now 2911 values are compared, 68 of them covered by decisions (38 entries,
274
+ one of them a pattern), and the zone is gone as a class. The rule did not become wider:
275
+ an empty value is a defect only where no template is empty either, since Russian justly
276
+ drops the prefix that English drops too (`message.stepProcess.sharedPrefix`).
277
+ - **dead keys** — a key no harness dictionary defines and no harness source names is a
278
+ leftover from an older version: it shows nothing and only misleads. Such keys fail the
279
+ build. A key a component still asks for stays in the advisory report, because the pack's
280
+ value is what reaches the screen.
281
+ - **terminology** — one concept, one rendering. Forbidden variants («кеш» for «кэш», «сеанс»
282
+ for «сессия», «этап» for «шаг») fail the build, and `scripts/terminology.json` keeps both
283
+ the decision and counts of the chosen forms, so drift shows up before it becomes a defect.
284
+ The check immediately found `confirm.description` translated two ways in two namespaces —
285
+ «шаги» and «этапы». One word may not be spelled both with and without «ё»: «ещё» next to
286
+ «еще» reads as machine translation, so the check folds «ё» to «е» and fails on two spellings
287
+ of one folded form. Real distinct pairs such as «все» and «всё» are declared in
288
+ `scripts/yo-pairs.json`, and a declaration whose spellings left the pack fails as well. The
289
+ same report covers Latin words inside Russian phrases: a product,
290
+ command or code token is allowed, an ordinary English word in a sentence is not. A product name
291
+ has to be spelled the way the harness spells it: «Github Desktop» for «GitHub Desktop» reads as a
292
+ typo in the product's own name, and the pack is the only place it can appear. The reference is
293
+ the English templates; a word the harness writes only in lowercase («cursor», «desktop») does not
294
+ count, because there a capital is exactly the product name. Next to it
295
+ comes mechanical hygiene: double spaces, a space before punctuation, unpaired «ёлочки» and
296
+ brackets, a non-breaking space inside a value, and edge spaces — a value that starts or ends
297
+ with one is glued to a neighbour by the component, so it has to match the English template
298
+ of its own namespace. The same template checks style: a terminator the English carries must
299
+ not disappear (this is how `registryCustomHint` was found without its period), and the case
300
+ of the first letter has to match — except for acronyms like `JSON` and templates that open
301
+ with a placeholder. The address to the reader is checked on its own: inside a sentence the
302
+ Russian interface writes «вы» lowercase, and «Вы» with a capital letter only at the start of
303
+ a sentence, where it is nothing but spelling. Finally the pack is compared with itself: one
304
+ English sentence has to come back as one Russian sentence. When two namespaces render it
305
+ differently — templates of four words or more are compared, because a short label legitimately
306
+ follows its context — the build fails. This is how `confirm.description` was found: one
307
+ rendering promised to «пропускать шаги подтверждения», the other to «уменьшать число шагов»,
308
+ and the English «subsequent tasks» had become «текущей задаче» in the second. A deliberate
309
+ divergence is declared in `scripts/one-rendering.json` with its reason, and an exception that
310
+ no longer matches anything fails as well — otherwise the list of decisions would quietly widen
311
+ the check. The opposite direction is printed as an advisory: the same Russian answer to
312
+ **different** English sentences of four words or more hints at a copy-paste — a hint carried
313
+ over from a neighbouring key, or two distinct facts collapsed into one phrase. It cannot be a
314
+ gate, because Russian legitimately merges words English keeps apart, so both sides are printed
315
+ together and a human decides.
316
+
317
+ A separate workflow watches the tag itself: `watch-upstream` compares the pinned tag with
318
+ the harness tags once a week, opens an issue with the steps when a newer one exists, and turns
319
+ the run red — so lagging behind is found on a schedule rather than in a user's screenshot. The
320
+ version comparison is checked without the network (`npm run check:upstream`: prerelease order,
321
+ equal versions, a move between lines) and is
322
+ part of `verify`: a release has to outrank its own prereleases, or the watch would fire forever.
323
+
324
+ The coverage number is not a gate: some sections are deliberately left in English. Next to it
325
+ the audit prints a **per-namespace** report: it rebuilds the namespace-to-dictionary link from
326
+ whoever registers it (`NS`, or a literal in `locale.register`; `bind` and `PropsLocale` only read
327
+ a namespace) and lists keys missing from both their own namespace and the shared `common`
328
+ dictionary the runtime falls back to. A global name comparison cannot see those holes: a
329
+ translation filed under another namespace counts as coverage while the screen shows English.
330
+ The report is advisory — the same key name can mean different things in different namespaces
331
+ (`list.aria` is "Active reminders" in the catalog and "Background jobs" in jobs), so a
332
+ translation is copied across namespaces only where one dictionary serves both. On the pinned
333
+ tag the report is empty: every string the harness renders in a namespace we register lives in
334
+ that namespace. All 56 of the pack's namespaces are compared — the link comes both from a
335
+ dictionary imported beside the registration and from the names that registration passes
336
+ (`locale.register(NS, { zh, en })`); without the second rule `common` and `settings.locale`,
337
+ whose dictionaries sit in a neighbouring directory, would stay uncovered. The other direction is
338
+ a failure rather than a report: a namespace the harness registers and fills with keys, which the
339
+ pack never registers, leaves that screen in English while key coverage still looks complete.
340
+ This is how `settings.sessionLog` was found — the “Upload Session Log” switch on the general
341
+ settings page.
342
+
343
+ ## Coverage scope: the official interface only
344
+
345
+ The package translates **the harness client's own namespaces** — 77 namespaces, reconciled against
346
+ the pinned tag. Third-party plugin dictionaries are deliberately out of scope, and here is
347
+ why.
348
+
349
+ We checked the 17 most-downloaded plugins in the ecosystem (npm, `keywords:dsh-plugin`,
350
+ monthly downloads) and looked at which of them ship strings of their own at all:
351
+
352
+ | What we found | Plugins |
353
+ |---|---|
354
+ | **No strings of their own** — the interface takes its text from harness namespaces | **11 of 17** |
355
+ | A large dictionary of their own (confirmed by hand) | 2 — `dshmarket` (~1262 keys), `@nanmicoder/dsh-agent-teams` (420) |
356
+ | `.d.ts` types only, no strings | 1 — `dsh-plugin-model-proxy` |
357
+ | Needs a separate check | 3 |
358
+
359
+ The practical consequence: for **11 of the 17** most popular plugins the Russian interface
360
+ already works — their text lives in the shared harness namespaces, which the package translates
361
+ in full. A separate dictionary is needed only where a plugin carries strings of its own, and such
362
+ a dictionary is a commitment to track someone else's releases: `dshmarket` is already at version 1.66.3,
363
+ and its keys change independently of the harness.
364
+
365
+ That is why there is one package and one commitment here: **the official interface, translated and
366
+ verified**, with no race after other people's dictionaries. Need a specific plugin? Open an issue:
367
+ a dictionary for it is possible as a separate layer pinned to a version, rather than as a silent
368
+ promise that «покрыто всё».
369
+
370
+ ## Development
371
+
372
+ ```sh
373
+ pnpm install
374
+ pnpm run build # tsdown -> lib/index.js (node half) + lib/client.js (client bundle)
375
+ npm pack # -> ragnoryok1-dsh-client-locale-ru-<version>.tgz
376
+ ```
377
+
378
+ `lib/client.js` is built in the format the `dsh` client loader understands (`__ModuleLoader__.load`) and exports `inject`/`apply`.
379
+
380
+ ## Releases
381
+
382
+ Publishing to npm is done by the `publish` workflow on a tag — the credentials live in a repository
383
+ secret (`NPM_TOKEN`), so the token never has to be typed into a terminal or passed around.
384
+ Publishing by hand from a machine is possible, but unnecessary.
385
+
386
+ ```
387
+ npm version 0.3.0 --no-git-tag-version # поднять версию в package.json
388
+ # …и синхронно обновить версию в блоке deepseek-harness-meta в этом README
389
+ git commit -am "release: 0.3.0" && git tag v0.3.0 && git push origin v0.3.0
390
+ ```
391
+
392
+ Before publishing, the workflow checks:
393
+
394
+ 1. the version in the tag matches the version in `package.json`;
395
+ 2. the version in the `deepseek-harness-meta` block matches `package.json` — the marketplace takes it from there,
396
+ and reads it **only on first indexing**, so a mismatch does not fix itself;
397
+ 3. the typography (18 rules);
398
+ 4. the localization audit against the pinned harness tag — dead namespaces, duplicates and plural-form
399
+ pairs will fail and stop the publication;
400
+ 5. **compatibility with every version in `compatible_versions`**: each one's locale runtime is
401
+ downloaded from npm and three suites — language coexistence, plural forms and composed phrases
402
+ — run against it (`npm run check:compat`, part of CI), so the promise in the meta block is
403
+ checked rather than assumed.
404
+
405
+ The declaration itself is checked separately (`npm run check:peers`, part of `verify`): the harness
406
+ reads our `peerDependencies` for `@deepseek-ai/dsh*` not as package versions but as a range of
407
+ **runtime versions**, with `includePrerelease`, and a range that fails to satisfy the running dsh
408
+ makes the user see an incompatibility warning and an exemption prompt. The script runs that rule
409
+ over every claimed version — all eight pass today.
410
+
411
+ To check all the same things locally, without publishing:
412
+
413
+ ```
414
+ npm run verify -- путь/к/дереву/харнесса
415
+ ```
416
+
417
+ The workflow also has a manual run with a «только проверки, без публикации» toggle
418
+ (`workflow_dispatch` → `dry-run`).
419
+
420
+ ## Contents
421
+
422
+ - `src/client/dicts.ts` — the Russian dictionaries (77 namespaces, 4495 translations, ~3000 lines). This is where the main value lies.
423
+ - `src/client/index.ts` — the client plugin entry point (registration of `ru` + the dictionaries).
424
+ - `src/index.ts` — the empty host half (`apply()`).
425
+ - `cordis.patch.yml` — the profile patch (`- insert:` of the client entry `locale-ru`).
426
+
427
+ ## What is translated
428
+
429
+ The current set follows the harness tags: the keys were taken from the matching commit, and keys
430
+ added later fall back to `en` on lookup, so the package does not break the interface on newer versions.
431
+
432
+ ### 0.1.7 — dead namespace fixed, task manager translated, plural forms rewritten
433
+
434
+ The headline item in the release is **a fix for a mistake from 0.1.6**. At the time it was recorded
435
+ that the namespace `schedule.catalog` had been retired and that `schedule.manager` had taken its place.
436
+ In reality **both** namespaces live in the harness, separately: `schedule.catalog` is the reminder
437
+ catalog in the session header with frequencies and units, `schedule.manager` is the new automation-task
438
+ manager. Because of that wrong conclusion, ~30 translation strings (reminders, frequency, time
439
+ units) were registered under a name nobody reads and were **never shown** in the interface. The
440
+ catalog is now registered as `schedule.catalog`.
441
+
442
+ - **fixed:** the schedule catalog translations are visible again — `trigger.*`, `list.*`,
443
+ `delete.*`, `frequency.*`, `cron.*`, `unit.*`, `relative.*`, `mark.aria`, `hover.more`;
444
+ - **`schedule.manager` added** (188 keys) — the automation-task manager entirely in
445
+ Russian: the task panel and catalog, search, status filter, task details and its tabs,
446
+ the **run log** with retention rules, the schedule editor (date, time, hour, minute,
447
+ second, time zone, repeat interval), repeat rules (once, every N minutes/hours/
448
+ seconds, daily, Mon–Fri, weekly, custom cron), the cron form with date and minute
449
+ pickers, deletion with confirmation, notifications and cards;
450
+ - **plural forms rewritten** (17 key pairs): forms that broke on the numbers 2–4
451
+ were replaced with ones that are correct for any `n ≥ 2` — for example «2 субагентов» → «Субагентов: 2»,
452
+ «каждые 2 часов» → «каждые 2 ч». Another 8 strings with `{count}` before an inflected word
453
+ were switched to abbreviations: «ещё 3 строк» → «ещё 3 стр.»;
454
+ - **7 missing keys added**: `blocked.composer`,
455
+ `defaultWorkspace.title`, `schedule.active`, `settings.transcript.expanded`,
456
+ `status.scheduled`, `status.overdue` and the namespace `shortcuts.layout` (the label for
457
+ the command that shows the left panel);
458
+ - **a new check in the project:** a namespace-name audit. The previous coverage check
459
+ compared keys inside a namespace but never compared the names themselves, so an entire
460
+ namespace could be registered under a non-existent name and quietly do nothing. Names
461
+ are now extracted from every way they can be declared (`const NS`/`SETTINGS_NS`/`namespace`,
462
+ `locale.register`, `locale.bind`, `PropsLocale<…>`) and compared as sets.
463
+
464
+ 0.1.2 added sections that 0.1.1 did not have: `open-in-app` («Открыть в
465
+ приложении»), `sidebarFiles`, `sidebarRight` (the right sidebar with files) and
466
+ `sidebarCodePreview` (code preview). It also updated the keys that changed in
467
+ harness 0.1.5 and removed 21 keys that no longer exist there.
468
+
469
+ In 0.1.3 the package caught up with harness 0.1.6-alpha.1: 139 keys and 7 new
470
+ sections were added — `sidebarTerminal` (the terminal in the right sidebar), `settings.archivedSessions`
471
+ (archived sessions) and the document-preview family (`sidebarDocumentPreview`,
472
+ `documentHtml`, `sidebarImage`, `documentMarkdown`, `sidebarPdf`). Besides that, the experimental
473
+ «Автопроверка» was translated (`permission.access`: `auto.*`), along with the command-palette
474
+ labels and tokens, the endpoint fields in the model settings, and the
475
+ changes in `conversation`, `trajectory`, `chat`, `session-log-download`.
476
+ 28 keys that had disappeared from the harness were removed.
477
+
478
+ The coverage check runs against the harness sources: for each namespace the
479
+ key sets are compared, so the next release starts from an exact
480
+ “add / check / remove” list.
481
+
482
+ In 0.1.6 the package caught up with harness **0.1.7-rc.2**: **252 keys** added, 18 updated,
483
+ 26 removed, and two new namespaces added. The changes:
484
+
485
+ - **`shortcuts`** (new namespace, 56 keys) — the hotkey editor: recording
486
+ a combination, conflicts, reserved combinations, hints for macOS and
487
+ Windows, restoring the default values;
488
+ - **`schedule.manager`** (70 keys at the time) — the automation-task manager: list,
489
+ deletion, repeat, run history, Cron expressions. **It was wrongly recorded that
490
+ the namespace `schedule.catalog` had been retired by this; 0.1.7 fixed that — both
491
+ namespaces exist side by side**;
492
+ - **`settings.account`** (50 keys) — the app's first launch: the welcome,
493
+ the choice of purpose and of how much of the work is shown, topping up the balance, signing out of
494
+ the account while tasks are running;
495
+ - added the labels for adding and removing tools on the fly
496
+ (`message.tools*`, `trajectory.layout.*`), the right sidebar commands
497
+ (`sidebarRight`), the «сначала выберите сессию» states in the panels, and updated
498
+ the name of the settings section: «Инструменты для кода» instead of «Инструментов
499
+ разработчика»;
500
+ - 18 keys updated to match the changed English text, 26 removed. A single term,
501
+ «рабочая папка», instead of «рабочей области» in one place.
502
+
503
+ In 0.1.5 the package caught up with harness **0.1.7-rc.1**: **49 keys** added, 13 updated,
504
+ 16 removed, and the namespace `directory-browser` dropped (it is gone in rc.1). There appeared
505
+ the labels for the steps tools go through (`message.stepProcess.prepare.*` and `done.*` —
506
+ reading images, writing files, preparing for code search, web search,
507
+ subagent coordination and others), full-screen image viewing
508
+ (`image.*`), the «Стандартный» and «Подробный» modes for showing the work, installing plugins
509
+ from GitHub with a timeout and a Chinese mirror, the warning about an incompatible
510
+ plugin version, the choice of model download source for voice input, and `Cordis` command
511
+ messages. The wording was tightened where upstream replaced “teammate/agent”
512
+ with “subagent” and “Beta” with “Experimental”.
513
+
514
+ 0.1.5 also fixed the counting unit in the plugin inventory: it was «187 плагины»,
515
+ now it is «187 плагинов». The built-in interface screenshots were translated too, npm
516
+ keywords were added, and the peer range of `@deepseek-ai/dsh-client-locale`
517
+ (`>=0.1.0-rc.2 <0.2.0`) was fixed — previously `^0.1.0` did not resolve, because the package
518
+ is published only under prerelease versions.
519
+
520
+ In 0.1.4 the package caught up with harness **0.1.7-alpha.2** — the biggest jump:
521
+ **905 keys** added, 29 changed, 126 removed. New sections:
522
+
523
+ - **`pluginManager`** — the built-in plugin panel: installation, removal,
524
+ enabling and disabling components, versions, registries and install hints;
525
+ - **`settings.account`** — the DeepSeek account: sign-in, balance, spend,
526
+ API key;
527
+ - the separate settings sections the former `settings.plugins` split into:
528
+ **`settings.agentLoop`**, **`settings.shell`**, **`settings.subagent`**,
529
+ **`settings.webSearch`**;
530
+ - **`sidebarBrowser`** — the built-in browser in the right panel (address, sandbox,
531
+ restoring the page);
532
+ - **`sidebarExcel`** and **`sidebarOffice`** — preview of Office spreadsheets and
533
+ documents; zooming (`zoom.*`) was added to PDFs and images;
534
+ - **`voice-input`** — experimental voice input.
535
+
536
+ Also translated were the new texts in `conversation` (+194), `chat` (+53),
537
+ `workspace` (+43: session archive, pinning), `deliverables` (+31: the review of
538
+ work changes), `job` (+24: live output of background tasks), `settings` (+22:
539
+ desktop app update), `plan`, `open-in-app`, `trajectory`,
540
+ `model`, `reference`, `session-log-download`, and in the agent-mode
541
+ guides (`settings.agentPreset`, 12 long texts). The section
542
+ `settings.archivedSessions` was removed: in 0.1.7 upstream dropped that package, and the session
543
+ archive moved to `workspace`.
544
+
545
+ ## License
546
+
547
+ MIT
548
+
549
+ ## Changelog
550
+
551
+ [CHANGELOG.md](CHANGELOG.md) — what was in each release.