@goodandready/dsh-russian-lang 0.3.0 → 0.3.3

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.
Files changed (46) hide show
  1. package/README.md +20 -5
  2. package/README.ru.md +20 -5
  3. package/bin/dsh-i18n.mjs +247 -0
  4. package/lib/client.js +2932 -2360
  5. package/lib/index.js +89 -15
  6. package/lib/locales/core.json +1 -1
  7. package/lib/locales/plugins/03-dshmarket.json +1 -1
  8. package/lib/locales/plugins/17-dsh-context.json +1 -1
  9. package/lib/locales/plugins/26-config-manager.json +1 -1
  10. package/lib/locales/plugins/29-config-manager-recovery.json +1 -1
  11. package/lib/locales/plugins/32-vision-bridge.json +1 -1
  12. package/lib/locales/plugins/33-messenger-gateway.json +1 -1
  13. package/lib/locales/plugins/34-key-rotation.json +1 -1
  14. package/lib/locales/plugins/35-gitea.json +1 -1
  15. package/lib/locales/plugins/36-cost-meter.json +1 -1
  16. package/lib/locales/plugins/37-time-machine.json +1 -1
  17. package/lib/locales/plugins/40-clinebot.json +1 -1
  18. package/lib/locales/plugins/41-lanmode.json +1 -1
  19. package/lib/locales/plugins/42-plugin-console.json +1 -1
  20. package/lib/locales/plugins/48-task-tracker.json +1 -1
  21. package/lib/locales/plugins/49-dsml-guard.json +1 -1
  22. package/lib/locales/plugins/54-workshop-market.json +1 -1
  23. package/lib/locales/plugins/56-cron.json +1 -1
  24. package/lib/locales/plugins/58-grok-xsearch.json +1 -1
  25. package/lib/locales/plugins/59-goal.json +1 -1
  26. package/lib/locales/plugins/60-model-sync.json +1 -1
  27. package/lib/locales/plugins/61-issue-reporter.json +1 -1
  28. package/lib/locales/plugins/62-model-search.json +1 -1
  29. package/lib/locales/plugins/63-agent-orchestrator.json +1 -1
  30. package/lib/locales/plugins/65-subscriptions.json +1 -1
  31. package/lib/locales/plugins/67-voice.json +1 -1
  32. package/lib/locales/plugins/68-image-gen.json +1 -1
  33. package/lib/locales/plugins/70-hypotheses.json +1 -1
  34. package/lib/locales/plugins/72-server-monitor.json +1 -1
  35. package/lib/locales/plugins/73-agentrouter.json +1 -1
  36. package/lib/locales/plugins/77-github-ops.json +1 -0
  37. package/lib/locales/plugins/78-web-gateway.json +1 -0
  38. package/lib/locales/plugins/79-approval-gate.json +1 -0
  39. package/lib/locales/plugins/80-ip-pool.json +1 -0
  40. package/lib/locales/plugins/81-ego-browser.json +1 -0
  41. package/lib/locales/plugins/82-effort-slider.json +1 -0
  42. package/lib/locales/zh-ru.json +1 -1
  43. package/lib/locales.js +50 -12
  44. package/lib/pure.js +306 -14
  45. package/lib/translator.js +55 -0
  46. package/package.json +7 -2
package/lib/index.js CHANGED
@@ -1,15 +1,42 @@
1
+ import crypto from 'node:crypto'
1
2
  import { registerPluginUpdater } from './plugin-updater.js'
2
3
  import { registerTranslator } from './translator.js'
3
- import { getCoreDictionaries, getPluginDictionaries, getZhRuMap } from './locales.js'
4
+ import { getCoreDictionaries, getPluginDictionaries, getPluginDictionariesByNames, getAllDictionaries, getZhRuMap } from './locales.js'
4
5
  // dsh-russian-lang — серверная половина.
5
6
  //
6
7
  // Словари и переключатель живут в браузере (lib/client.js). Хосту переводить
7
8
  // нечего, но у выбора языка есть сохраняемая настройка: регистрируем её схему
8
- // в собственном namespace, чтобы запись переживала браузеры и машины через
9
+ // в собственном namespace, чтобы запись переживала браузеры и машины череж
9
10
  // штатный механизм настроек DSH.
10
11
  import z from '@deepseek-ai/schemastery'
11
12
  import { SYSTEM_PROMPT_PRESETS } from './pure.js'
12
13
 
14
+
15
+ const sendJsonWithEtag = (request, response, obj, maxAge = 300) => {
16
+ const body = Buffer.from(JSON.stringify(obj), 'utf-8')
17
+ const etag = '"' + crypto.createHash('md5').update(body).digest('hex') + '"'
18
+ const ifNoneMatch = request?.headers?.['if-none-match']
19
+ if (ifNoneMatch && (ifNoneMatch === etag || ifNoneMatch === '*' || ifNoneMatch === `W/${etag}`)) {
20
+ response.writeHead(304, {
21
+ 'etag': etag,
22
+ 'cache-control': `public, max-age=${maxAge}, must-revalidate`,
23
+ })
24
+ response.end()
25
+ return
26
+ }
27
+ response.writeHead(200, {
28
+ 'content-type': 'application/json; charset=utf-8',
29
+ 'etag': etag,
30
+ 'cache-control': `public, max-age=${maxAge}, must-revalidate`,
31
+ 'content-length': body.length,
32
+ })
33
+ if (request?.method === 'HEAD') {
34
+ response.end()
35
+ } else {
36
+ response.end(body)
37
+ }
38
+ }
39
+
13
40
  export const name = '@goodandready/dsh-russian-lang'
14
41
 
15
42
  const RussianLangSettingsSchema = z.object({
@@ -36,7 +63,7 @@ export function apply(ctx) {
36
63
  })
37
64
 
38
65
  // #68: русский системный промпт агента (опционально). Регистрируем секцию
39
- // промпта, которая задаёт стиль ответов по-русски, когда включён флаг
66
+ // промпта, которая задёт стиль ответов по-русски, когда включён флаг
40
67
  // russian-lang.agentPrompt. Секция — официальная точка расширения ядра.
41
68
  ctx.inject(['settings', 'systemPrompt'], (settingsCtx, prompt) => {
42
69
  const NS = 'russian-lang'
@@ -57,7 +84,7 @@ export function apply(ctx) {
57
84
  sync()
58
85
  })
59
86
 
60
- // Web endpoints: One-click plugin updater, modular plugin dictionary serving, & translator gateway
87
+ // Web endpoints: One-click plugin updater, modular lazy-plugin dictionary serving, & translator gateway
61
88
  if (typeof ctx.inject === 'function') {
62
89
  ctx.inject(['webServer'], (wctx) => {
63
90
  try {
@@ -73,33 +100,80 @@ export function apply(ctx) {
73
100
  let ws = null
74
101
  try { ws = wctx?.webServer } catch (_) { ws = null }
75
102
 
76
- let unregDict = () => {}
103
+ const unregsDict = []
77
104
  if (ws && typeof ws.register === 'function') {
78
- unregDict = ws.register({
105
+ // 1. Core dictionary only (fast-boot, ~135 KB)
106
+ unregsDict.push(ws.register({
79
107
  kind: 'exact',
80
- path: '/api/dsh-russian-lang/dict/all',
108
+ path: '/api/dsh-russian-lang/dict/core',
81
109
  handler: async (request, response) => {
82
110
  try {
111
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
112
+ response.writeHead(405, { allow: 'GET, HEAD', 'content-type': 'application/json; charset=utf-8' })
113
+ response.end(JSON.stringify({ error: 'Method not allowed' }))
114
+ return
115
+ }
83
116
  const core = getCoreDictionaries()
84
- const plugins = getPluginDictionaries()
85
117
  const zhRu = getZhRuMap()
86
- response.writeHead(200, {
87
- 'content-type': 'application/json; charset=utf-8',
88
- 'cache-control': 'public, max-age=300',
89
- })
90
- response.end(JSON.stringify(Object.assign({}, core, plugins, { core, plugins, zhRu })))
118
+ sendJsonWithEtag(request, response, { core, zhRu })
119
+ } catch (err) {
120
+ response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
121
+ response.end(JSON.stringify({ error: String(err) }))
122
+ }
123
+ },
124
+ }))
125
+
126
+ // 2. On-demand plugins dictionary serving (query: ?names=p1,p2...)
127
+ unregsDict.push(ws.register({
128
+ kind: 'prefix',
129
+ path: '/api/dsh-russian-lang/dict/plugins',
130
+ handler: async (request, response) => {
131
+ try {
132
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
133
+ response.writeHead(405, { allow: 'GET, HEAD', 'content-type': 'application/json; charset=utf-8' })
134
+ response.end(JSON.stringify({ error: 'Method not allowed' }))
135
+ return
136
+ }
137
+ const url = new URL(request.url, 'http://localhost')
138
+ const namesParam = url.searchParams.get('names') || url.searchParams.get('list') || ''
139
+ const names = namesParam ? namesParam.split(',').map((s) => s.trim()).filter(Boolean) : null
140
+ const plugins = getPluginDictionariesByNames(names)
141
+ sendJsonWithEtag(request, response, { plugins })
142
+ } catch (err) {
143
+ response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
144
+ response.end(JSON.stringify({ error: String(err) }))
145
+ }
146
+ },
147
+ }))
148
+
149
+ // 3. Backward compatibility all-dictionaries endpoint
150
+ unregsDict.push(ws.register({
151
+ kind: 'exact',
152
+ path: '/api/dsh-russian-lang/dict/all',
153
+ handler: async (request, response) => {
154
+ try {
155
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
156
+ response.writeHead(405, { allow: 'GET, HEAD', 'content-type': 'application/json; charset=utf-8' })
157
+ response.end(JSON.stringify({ error: 'Method not allowed' }))
158
+ return
159
+ }
160
+ const all = getAllDictionaries()
161
+ const zhRu = getZhRuMap()
162
+ sendJsonWithEtag(request, response, Object.assign({}, all, { core: getCoreDictionaries(), plugins: getPluginDictionaries(), zhRu }))
91
163
  } catch (err) {
92
164
  response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
93
165
  response.end(JSON.stringify({ error: String(err) }))
94
166
  }
95
167
  },
96
- })
168
+ }))
97
169
  }
98
170
 
99
171
  return () => {
100
172
  if (typeof unregUpdater === 'function') unregUpdater()
101
173
  if (typeof unregTranslator === 'function') unregTranslator()
102
- if (typeof unregDict === 'function') unregDict()
174
+ for (const u of unregsDict) {
175
+ if (typeof u === 'function') u()
176
+ }
103
177
  }
104
178
  }
105
179
  if (typeof wctx.effect === 'function') wctx.effect(mount, 'russian-lang: web routes')