@ragnoryok1/dsh-client-locale-ru 0.1.7 → 0.1.8

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 CHANGED
@@ -6,6 +6,27 @@
6
6
  Пакет следует за харнессом: целевая версия — `dsh 0.1.7-rc.2` и новее
7
7
  (`>=0.1.0-rc.2 <0.2.0` для `@deepseek-ai/dsh-client-locale`).
8
8
 
9
+ ## [0.1.8] — 2026-09-27
10
+
11
+ ### Добавлено
12
+
13
+ - **Русская экранная типографика** (`src/client/typography.ts`): «ёлочки» вместо прямых
14
+ кавычек, длинное тире между словами, многоточие, неразрывный пробел между числом и
15
+ следующим словом или единицей измерения. Правила осторожные: код, поля ввода,
16
+ редактируемый текст и `[data-typography="off"]` не трогаются, повторный проход ничего не
17
+ меняет. Работает как `ctx.effect`, поэтому выгружается вместе с плагином.
18
+ - **Проверки в CI** (`.github/workflows/verify.yml`): сборка, 12 правил типографики
19
+ (`scripts/check-typography.mjs`) и аудит локали (`scripts/audit.mjs`) против
20
+ **закреплённого тега** харнеса. Новые npm-скрипты: `check:typography`, `audit`, `verify`.
21
+ - Раздел README **«Область покрытия»** с измерением: из 17 самых скачиваемых плагинов
22
+ экосистемы 11 не поставляют собственных строк, поэтому их русский интерфейс обеспечивают
23
+ словари харнеса; словари сторонних плагинов в область пакета не входят.
24
+
25
+ ### Исправлено
26
+
27
+ - **Число переводов в документации**: 2598 → **2304**. Прежняя цифра считала повторяющиеся
28
+ имена ключей в разных namespace дважды.
29
+
9
30
  ## [0.1.7] — 2026-09-27
10
31
 
11
32
  ### Исправлено
@@ -48,7 +69,7 @@
48
69
 
49
70
  ### Состояние
50
71
 
51
- 52 namespace, 2598 переводов, ~2700 строк. Проверки: 0 отсутствующих ключей против
72
+ 52 namespace, 2304 перевода, ~2700 строк. Проверки: 0 отсутствующих ключей против
52
73
  `dsh 0.1.7-rc.2`, 0 мёртвых namespace, 45/45 пар ключей с формами числа.
53
74
 
54
75
  ## [0.1.6] — 2026-09-25
package/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/%40ragnoryok1%2Fdsh-client-locale-ru)](https://www.npmjs.com/package/@ragnoryok1/dsh-client-locale-ru)
4
4
  [![license](https://img.shields.io/npm/l/%40ragnoryok1%2Fdsh-client-locale-ru)](LICENSE)
5
+ [![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)
5
6
  [![dsh](https://img.shields.io/badge/dsh-0.1.7--rc.2%2B-6366f1)](https://github.com/deepseek-ai/deepseek-harness)
6
7
  [![namespaces](https://img.shields.io/badge/namespaces-52-6466f1)](https://github.com/Ragnoryok1/dsh-client-locale-ru)
7
- [![strings](https://img.shields.io/badge/strings-2%2C598-6466f1)](https://github.com/Ragnoryok1/dsh-client-locale-ru)
8
+ [![strings](https://img.shields.io/badge/strings-2%2C304-6466f1)](https://github.com/Ragnoryok1/dsh-client-locale-ru)
8
9
 
9
- **In English.** 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 — 52 namespaces, 2,598 strings, built against dsh `0.1.7-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. MIT, community-maintained, not affiliated with DeepSeek. Details below are in Russian.
10
+ **In English.** 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 — 52 namespaces, 2,304 strings, built against dsh `0.1.7-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. Details below are in Russian.
10
11
 
11
12
  ---
12
13
 
@@ -54,6 +55,68 @@ dsh plugin --profile web add @ragnoryok1/dsh-client-locale-ru
54
55
  «ещё 3 стр.»). Ни одна видимая строка не показывает неверную форму числа.
55
56
  - **Фолбэк** — отсутствующий русский ключ падает на `en` (настроенный `fallback`).
56
57
 
58
+ ## Типографика
59
+
60
+ Пакет приводит уже отрисованный русский текст к типографским правилам: «ёлочки» вместо
61
+ прямых кавычек, длинное тире вместо дефиса между словами, многоточие вместо трёх точек и
62
+ неразрывный пробел между числом и следующим словом или единицей измерения (чтобы «187
63
+ плагинов» не разрывалось на две строки). Правила намеренно осторожны: срабатывают только
64
+ на однозначных шаблонах, а код, поля ввода, редактируемый текст и элементы с
65
+ `data-typography="off"` не трогаются вообще. Повторный проход ничего не меняет.
66
+
67
+ Правила проверяются скриптом, а не на глаз: `npm run check:typography`.
68
+
69
+ ## Проверки
70
+
71
+ ```sh
72
+ npm run build # сборка трёх выходов
73
+ npm run check:typography # 12 правил типографики, включая «не трогать»
74
+ npm run audit -- <дерево харнеса> # аудит локали
75
+ npm run verify -- <дерево харнеса> # всё вместе
76
+ ```
77
+
78
+ Аудит выполняется в CI (`.github/workflows/verify.yml`) против **закреплённого тега**
79
+ харнеса, а не против случайного локального чекаута. Гейтом проверяются две вещи, и обе
80
+ уже ловили настоящие дефекты:
81
+
82
+ - **мёртвые namespace** — каждый namespace, который регистрирует пакет, обязан
83
+ существовать в харнессе. В 0.1.6 переводы каталога расписания были зарегистрированы
84
+ под именем `schedule.manager`, тогда как читает их `schedule.catalog`: проверка покрытия
85
+ по ключам при этом рапортовала «всё переведено»;
86
+ - **пары форм числа** — множества `.one`/`.other` должны совпадать, потому что клиент
87
+ выбирает форму как `count === 1 ? '.one' : '.other'`.
88
+
89
+ Число покрытия выводится, но не является гейтом: точный список «добавить / удалить»
90
+ требует сравнения ключей **по namespace** (это делает релизная оснастка), а сравнение имён
91
+ ключей глобально отметило бы разделы, которые пакет сознательно оставляет на английском.
92
+
93
+ ## Область покрытия: только официальный интерфейс
94
+
95
+ Пакет переводит **собственные namespace клиента харнеса** — 52 namespace, сверенные с
96
+ закреплённым тегом. Словари сторонних плагинов в область сознательно не входят, и вот
97
+ почему.
98
+
99
+ Мы проверили 17 самых скачиваемых плагинов экосистемы (npm, `keywords:dsh-plugin`,
100
+ скачивания за месяц) и посмотрели, какие из них вообще поставляют собственные строки:
101
+
102
+ | Что обнаружено | Плагинов |
103
+ |---|---|
104
+ | **Своих строк нет** — интерфейс берёт текст из namespace харнеса | **11 из 17** |
105
+ | Крупный собственный словарь (подтверждено вручную) | 2 — `dshmarket` (~1262 ключа), `@nanmicoder/dsh-agent-teams` (420) |
106
+ | Только типы `.d.ts`, строк нет | 1 — `dsh-plugin-model-proxy` |
107
+ | Требует отдельной проверки | 3 |
108
+
109
+ Практическое следствие: у **11 из 17** популярнейших плагинов русский интерфейс уже
110
+ работает — их текст лежит в общих namespace харнеса, которые пакет переводит полностью.
111
+ Отдельный словарь нужен только там, где плагин везёт свои строки, а такой словарь — это
112
+ обязательство следить за чужими релизами: у `dshmarket` уже версия 1.66.3, и её ключи
113
+ меняются независимо от харнеса.
114
+
115
+ Поэтому здесь один пакет и одно обязательство: **официальный интерфейс, переведённый и
116
+ проверенный**, без гонки за чужими словарями. Нужен конкретный плагин — заведите issue:
117
+ словарь для него возможен как отдельный слой, привязанный к версии, а не как молчаливое
118
+ обещание «покрыто всё».
119
+
57
120
  ## Разработка
58
121
 
59
122
  ```sh
@@ -66,7 +129,7 @@ npm pack # -> ragnoryok1-dsh-client-locale-ru-<version>.tgz
66
129
 
67
130
  ## Содержимое
68
131
 
69
- - `src/client/dicts.ts` — русские словари (52 namespace, 2598 переводов, ~2700 строк). Это и есть основная ценность.
132
+ - `src/client/dicts.ts` — русские словари (52 namespace, 2304 перевода, ~2700 строк). Это и есть основная ценность.
70
133
  - `src/client/index.ts` — точка входа клиентского плагина (регистрация `ru` + словарей).
71
134
  - `src/index.ts` — пустая host-половина (`apply()`).
72
135
  - `cordis.patch.yml` — профиль-патч (`- insert:` клиентской строки `locale-ru`).
package/lib/client.js CHANGED
@@ -2703,14 +2703,103 @@ window.__ModuleLoader__.load({
2703
2703
  }
2704
2704
  };
2705
2705
  //#endregion
2706
+ //#region src/client/typography.ts
2707
+ /**
2708
+ * Russian screen typography for translated strings.
2709
+ *
2710
+ * The harness locale contract is a flat `Record<string, string>`, so a
2711
+ * language pack cannot reach the composed sentences a component builds from
2712
+ * several keys, and cannot ask the host to insert typography. The rendered DOM
2713
+ * is the one place where every translated string passes through, so this module
2714
+ * rewrites text nodes in place. The rules are deliberately conservative: they
2715
+ * only fire on unambiguous straight-quote and spaced-hyphen patterns, and the
2716
+ * DOM pass skips code, form fields and editable content.
2717
+ */
2718
+ /** Straight double quotes around a span become Russian guillemets. */
2719
+ const QUOTED = /"([^"\n]{1,200})"/g;
2720
+ /** A spaced hyphen between two words is an em dash in Russian typography. */
2721
+ const SPACED_HYPHEN = /(?<=[\p{L}\p{N}»\)]) - (?=[\p{L}\p{N}«\(])/gu;
2722
+ /** Three dots in running text are an ellipsis. */
2723
+ const ELLIPSIS = /\.{3}(?=\s|$)/g;
2724
+ /** A digit followed by a lowercase letter keeps a non-breaking space. */
2725
+ const NUMBER_UNIT = /(\d) ([а-яё]{1,24})(?![а-яё])/giu;
2726
+ /** Non-breaking space keeps a number attached to a short unit-like token. */
2727
+ const NUMBER_SYMBOL = /(\d) ([%°₽$€£]|кб|КБ|МБ|ГБ)(?=\s|$)/giu;
2728
+ /** Guillemets, em dash, ellipsis, and non-breaking spaces in running text. */
2729
+ function applyRussianTypography(text) {
2730
+ return text.replace(QUOTED, "«$1»").replace(SPACED_HYPHEN, " — ").replace(ELLIPSIS, "…").replace(NUMBER_UNIT, "$1\xA0$2").replace(NUMBER_SYMBOL, "$1\xA0$2");
2731
+ }
2732
+ /** Elements whose text is code, data or user input and must stay verbatim. */
2733
+ const SKIP_SELECTOR = [
2734
+ "code",
2735
+ "pre",
2736
+ "kbd",
2737
+ "samp",
2738
+ "var",
2739
+ "textarea",
2740
+ "input",
2741
+ "select",
2742
+ "option",
2743
+ "[contenteditable]:not([contenteditable=\"false\"])",
2744
+ "[data-typography=\"off\"]"
2745
+ ].join(",");
2746
+ /** Text nodes already rewritten, so a later pass leaves them alone. */
2747
+ const processed = /* @__PURE__ */ new WeakSet();
2748
+ /** Whether the node sits inside content that must not be rewritten. */
2749
+ function isSkipped(node) {
2750
+ const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
2751
+ return element === null || element.closest(SKIP_SELECTOR) !== null;
2752
+ }
2753
+ function rewriteNode(node) {
2754
+ if (node.nodeType === Node.TEXT_NODE) {
2755
+ if (processed.has(node)) return;
2756
+ processed.add(node);
2757
+ const before = node.data;
2758
+ const after = applyRussianTypography(before);
2759
+ if (after !== before) node.data = after;
2760
+ return;
2761
+ }
2762
+ if (node.nodeType !== Node.ELEMENT_NODE || isSkipped(node)) return;
2763
+ for (const child of node.childNodes) rewriteNode(child);
2764
+ }
2765
+ /**
2766
+ * Start the typography pass on a document and keep it current as the app
2767
+ * renders. Returns a disposer that stops observing.
2768
+ *
2769
+ * @param doc - document whose body carries the rendered interface.
2770
+ * @returns disposer removing the observer.
2771
+ */
2772
+ function startRussianTypography(doc) {
2773
+ const root = doc.body;
2774
+ if (root === null) return () => {};
2775
+ rewriteNode(root);
2776
+ const observer = new MutationObserver((records) => {
2777
+ for (const record of records) {
2778
+ if (record.type === "characterData") {
2779
+ if (isSkipped(record.target)) continue;
2780
+ rewriteNode(record.target);
2781
+ continue;
2782
+ }
2783
+ for (const added of record.addedNodes) rewriteNode(added);
2784
+ }
2785
+ });
2786
+ observer.observe(root, {
2787
+ childList: true,
2788
+ characterData: true,
2789
+ subtree: true
2790
+ });
2791
+ return () => observer.disconnect();
2792
+ }
2793
+ //#endregion
2706
2794
  //#region src/client/index.ts
2707
2795
  /** Required service: the locale registry that owns languages and dictionaries. */
2708
2796
  const inject = ["locale"];
2709
2797
  /**
2710
- * Client plugin body: add `ru` to the selectable language catalog and register
2711
- * a ru dictionary for every namespace this pack contributes. Registration is
2712
- * an effect, so the contributed language and dictionaries are torn down with
2713
- * this plugin's fiber.
2798
+ * Client plugin body: add `ru` to the selectable language catalog, register
2799
+ * a ru dictionary for every namespace this pack contributes, and start the
2800
+ * Russian typography pass. Registration is an effect, so the contributed
2801
+ * language, dictionaries and the typography observer are torn down with this
2802
+ * plugin's fiber.
2714
2803
  * @param ctx - client root context.
2715
2804
  */
2716
2805
  function apply(ctx) {
@@ -2720,6 +2809,7 @@ window.__ModuleLoader__.load({
2720
2809
  fallback: "en"
2721
2810
  }), "locale-ru: language");
2722
2811
  for (const [ns, dict] of Object.entries(RU_DICTS)) ctx.effect(() => ctx.locale.register(ns, "ru", dict), `locale-ru: ${ns} dictionary`);
2812
+ ctx.effect(() => startRussianTypography(document), "locale-ru: typography");
2723
2813
  }
2724
2814
  //#endregion
2725
2815
  exports.apply = apply;