@goodandready/dsh-subscriptions 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.
Files changed (2) hide show
  1. package/lib/client.js +102 -29
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -35,15 +35,76 @@ window.__ModuleLoader__.load({
35
35
  document.head.appendChild(tag)
36
36
  }
37
37
 
38
- function badgeFor(account) {
39
- if (!account || !account.configured) return 'Not connected'
40
- if (account.validationUrl) return 'Verify account'
41
- if (account.cooldownUntil && account.cooldownUntil > Date.now()) return 'Cooling down'
42
- if (account.usagePercent != null && account.usagePercent >= 100) return 'Usage 100%'
43
- return 'Connected'
38
+ // Строки карточки живут в реестре локалей: так их переводит отдельный
39
+ // пакет, не трогая код этого плагина. Английский — язык по умолчанию,
40
+ // на него же приходится откат, если перевода нет.
41
+ const NS = 'dsh-subscriptions'
42
+ const en = {
43
+ 'notConnected': 'Not connected',
44
+ 'verifyAccount': 'Verify account',
45
+ 'coolingDown': 'Cooling down',
46
+ 'usageFull': 'Usage 100%',
47
+ 'connected': 'Connected',
48
+ 'title': 'Subscriptions',
49
+ 'intro': 'Log in with a consumer subscription. Tokens stay on the host credentials store. The browser never reads them back. After Connect, if the provider lands on localhost or a vendor page, paste the redirected URL or the code here.',
50
+ 'useOrigin': 'Use this Web UI origin as OAuth redirect_uri',
51
+ 'useOriginHint': 'Leave off unless you registered your own OAuth client for this origin. Vendor CLI clients usually require their published redirect URI plus the paste step.',
52
+ 'reconnect': 'Reconnect',
53
+ 'connect': 'Connect',
54
+ 'disconnect': 'Disconnect',
55
+ 'removeSlot': 'Remove slot',
56
+ 'pastePlaceholder': 'Paste redirected URL or code',
57
+ 'submitCode': 'Submit code',
58
+ 'verifyPrefix': 'Google requires one-time account verification. ',
59
+ 'verifyLink': 'Open verification link',
60
+ 'verifySuffix': ' then reconnect.',
61
+ 'addAccount': '+ Add account',
62
+ 'save': 'Save',
63
+ 'saved': 'Saved',
64
+ 'loading': 'Loading\u2026',
65
+ 'accountLabel': 'Account',
66
+ 'plan': 'Plan',
67
+ 'storedAs': 'Stored as',
68
+ }
69
+ const ru = {
70
+ 'notConnected': 'Не подключено',
71
+ 'verifyAccount': 'Требуется проверка',
72
+ 'coolingDown': 'Пауза после лимита',
73
+ 'usageFull': 'Лимит исчерпан',
74
+ 'connected': 'Подключено',
75
+ 'title': 'Подписки',
76
+ 'intro': 'Вход по обычной пользовательской подписке. Токены остаются в хранилище учётных данных харнесса, браузер их обратно не читает. Если после «Подключить» провайдер увёл на localhost или на свою страницу, вставьте сюда адрес перехода или код.',
77
+ 'useOrigin': 'Использовать адрес этого веб-интерфейса как redirect_uri',
78
+ 'useOriginHint': 'Включайте, только если зарегистрировали собственного клиента OAuth на этот адрес. Штатным консольным клиентам провайдеров нужен их опубликованный адрес возврата и вставка кода вручную.',
79
+ 'reconnect': 'Переподключить',
80
+ 'connect': 'Подключить',
81
+ 'disconnect': 'Отключить',
82
+ 'removeSlot': 'Убрать место',
83
+ 'pastePlaceholder': 'Адрес перехода или код',
84
+ 'submitCode': 'Отправить код',
85
+ 'verifyPrefix': 'Google требует однократной проверки аккаунта. ',
86
+ 'verifyLink': 'Открыть страницу проверки',
87
+ 'verifySuffix': ' затем подключитесь заново.',
88
+ 'addAccount': '+ Добавить аккаунт',
89
+ 'save': 'Сохранить',
90
+ 'saved': 'Сохранено',
91
+ 'loading': 'Загрузка…',
92
+ 'accountLabel': 'Аккаунт',
93
+ 'plan': 'Тариф',
94
+ 'storedAs': 'Хранится как',
95
+ }
96
+
97
+ function badgeFor(account, t) {
98
+ if (!account || !account.configured) return t('notConnected')
99
+ if (account.validationUrl) return t('verifyAccount')
100
+ if (account.cooldownUntil && account.cooldownUntil > Date.now()) return t('coolingDown')
101
+ if (account.usagePercent != null && account.usagePercent >= 100) return t('usageFull')
102
+ return t('connected')
44
103
  }
45
104
 
46
- function SubsSection() {
105
+ function SubsSection(props) {
106
+ // Переводчик приходит от слота, потому что в его записи указан locale.
107
+ const t = (props && props.t) || ((key) => key)
47
108
  const [draft, setDraft] = React.useState(null)
48
109
  const [accounts, setAccounts] = React.useState([])
49
110
  const [providers, setProviders] = React.useState([])
@@ -67,7 +128,7 @@ window.__ModuleLoader__.load({
67
128
  return () => { alive = false }
68
129
  }, [])
69
130
 
70
- if (!draft) return React.createElement('div', { className: 'dsub-wrap' }, 'Loading\u2026')
131
+ if (!draft) return React.createElement('div', { className: 'dsub-wrap' }, t('loading'))
71
132
 
72
133
  const slots = Array.isArray(draft.slots) ? draft.slots : []
73
134
  const setSlots = (next) => setDraft((d) => Object.assign({}, d, { slots: next }))
@@ -137,19 +198,19 @@ window.__ModuleLoader__.load({
137
198
 
138
199
  return React.createElement('div', { className: 'dsub-wrap' },
139
200
  React.createElement('div', { className: 'dsub-block' },
140
- React.createElement('div', { className: 'dsub-h' }, 'Subscriptions'),
201
+ React.createElement('div', { className: 'dsub-h' }, t('title')),
141
202
  React.createElement('div', { className: 'dsub-sub' },
142
- 'Log in with a consumer subscription. Tokens stay on the host credentials store. The browser never reads them back. After Connect, if the provider lands on localhost or a vendor page, paste the redirected URL or the code here.'),
203
+ t('intro')),
143
204
  React.createElement('label', { className: 'dsub-row' },
144
205
  React.createElement('input', {
145
206
  type: 'checkbox',
146
207
  checked: !!draft.useWebCallback,
147
208
  onChange: (e) => setDraft((d) => Object.assign({}, d, { useWebCallback: e.target.checked })),
148
209
  }),
149
- React.createElement('span', null, 'Use this Web UI origin as OAuth redirect_uri'),
210
+ React.createElement('span', null, t('useOrigin')),
150
211
  ),
151
212
  React.createElement('div', { className: 'dsub-sub' },
152
- 'Leave off unless you registered your own OAuth client for this origin. Vendor CLI clients usually require their published redirect URI plus the paste step.'),
213
+ t('useOriginHint')),
153
214
  ),
154
215
  names.map((prov) => {
155
216
  const rows = slots
@@ -166,25 +227,25 @@ window.__ModuleLoader__.load({
166
227
  React.createElement('input', {
167
228
  className: 'dsub-grow',
168
229
  value: row.slot.label || '',
169
- placeholder: account.label || ('Account ' + row.slot.index),
230
+ placeholder: account.label || (t('accountLabel') + ' ' + row.slot.index),
170
231
  onChange: (e) => {
171
232
  const next = slots.slice()
172
233
  next[row.i] = Object.assign({}, next[row.i], { label: e.target.value })
173
234
  setSlots(next)
174
235
  },
175
236
  }),
176
- React.createElement('span', { className: 'dsub-badge' + (account.validationUrl ? ' dsub-badge-warn' : (on ? ' dsub-badge-on' : '')) }, badgeFor(account)),
237
+ React.createElement('span', { className: 'dsub-badge' + (account.validationUrl ? ' dsub-badge-warn' : (on ? ' dsub-badge-on' : '')) }, badgeFor(account, t)),
177
238
  React.createElement('button', {
178
239
  type: 'button', className: 'dsub-mini',
179
240
  onClick: () => connect(row.slot.provider, row.slot.index).catch((e) => setErr(String(e.message || e))),
180
- }, on ? 'Reconnect' : 'Connect'),
241
+ }, on ? t('reconnect') : t('connect')),
181
242
  React.createElement('button', {
182
243
  type: 'button', className: 'dsub-mini',
183
244
  disabled: !on,
184
245
  onClick: () => logout(row.slot.provider, row.slot.index).catch((e) => setErr(String(e.message || e))),
185
- }, 'Disconnect'),
246
+ }, t('disconnect')),
186
247
  React.createElement('button', {
187
- type: 'button', className: 'dsub-mini', title: 'Remove slot',
248
+ type: 'button', className: 'dsub-mini', title: t('removeSlot'),
188
249
  onClick: () => {
189
250
  const slot = row.slot
190
251
  Promise.resolve(on ? logout(slot.provider, slot.index) : null)
@@ -197,49 +258,61 @@ window.__ModuleLoader__.load({
197
258
  React.createElement('input', {
198
259
  className: 'dsub-grow',
199
260
  value: paste[key] || '',
200
- placeholder: 'Paste redirected URL or code',
261
+ placeholder: t('pastePlaceholder'),
201
262
  onChange: (e) => setPaste((p) => Object.assign({}, p, { [key]: e.target.value })),
202
263
  }),
203
264
  React.createElement('button', {
204
265
  type: 'button', className: 'dsub-mini',
205
266
  onClick: () => complete(row.slot.provider, row.slot.index).catch((e) => setErr(String(e.message || e))),
206
- }, 'Submit code'),
267
+ }, t('submitCode')),
207
268
  ),
208
269
  account.accountNotice ? React.createElement('div', { className: 'dsub-verify' }, account.accountNotice) : null,
209
270
  account.validationUrl ? React.createElement('div', { className: 'dsub-verify' },
210
- 'Google requires one-time account verification. ',
211
- React.createElement('a', { href: account.validationUrl, target: '_blank', rel: 'noopener noreferrer' }, 'Open verification link'),
212
- ' then reconnect.',
271
+ t('verifyPrefix'),
272
+ React.createElement('a', { href: account.validationUrl, target: '_blank', rel: 'noopener noreferrer' }, t('verifyLink')),
273
+ t('verifySuffix'),
213
274
  ) : null,
214
- account.paidTierName ? React.createElement('span', { className: 'dsub-sub' }, 'Plan: ' + account.paidTierName) : null,
215
- account.ref ? React.createElement('span', { className: 'dsub-sub' }, 'Stored as ' + account.ref) : null,
275
+ account.paidTierName ? React.createElement('span', { className: 'dsub-sub' }, t('plan') + ': ' + account.paidTierName) : null,
276
+ account.ref ? React.createElement('span', { className: 'dsub-sub' }, t('storedAs') + ' ' + account.ref) : null,
216
277
  )
217
278
  }),
218
279
  React.createElement('button', {
219
280
  type: 'button', className: 'dsub-mini',
220
281
  onClick: () => addSlot(prov.id),
221
- }, '+ Add account'),
282
+ }, t('addAccount')),
222
283
  )
223
284
  }),
224
285
  React.createElement('div', { className: 'dsub-row' },
225
286
  React.createElement('button', {
226
287
  type: 'button', className: 'dsub-save',
227
288
  onClick: () => save().catch((e) => setErr(String(e.message || e))),
228
- }, 'Save'),
229
- saved ? React.createElement('span', { className: 'dsub-ok' }, 'Saved') : null,
289
+ }, t('save')),
290
+ saved ? React.createElement('span', { className: 'dsub-ok' }, t('saved')) : null,
230
291
  err ? React.createElement('span', { className: 'dsub-bad' }, err) : null,
231
292
  ),
232
293
  )
233
294
  }
234
295
 
235
296
  function registerSettings(ctx) {
297
+ ctx.effect(() => ctx.locale.register(NS, { en, ru }), 'dsh-subscriptions: словари')
298
+ // Подпись раздела рисует боковой список, а не наш компонент: props.t
299
+ // туда не доходит, поэтому берём переводчик, привязанный к namespace.
300
+ const t = ctx.locale.bind(NS)
236
301
  ctx.slots.inject('settings.section', () => ctx.slots.register(
237
- { name: 'settings.section', id: '@goodandready/dsh-subscriptions', order: 28, label: () => 'Subscriptions', inject: () => ({ ctx: ctx }) },
302
+ {
303
+ name: 'settings.section',
304
+ id: '@goodandready/dsh-subscriptions',
305
+ order: 28,
306
+ // locale в записи слота — то, из-за чего компонент получает props.t.
307
+ locale: NS,
308
+ label: () => t('title'),
309
+ inject: () => ({ ctx: ctx }),
310
+ },
238
311
  SubsSection,
239
312
  ))
240
313
  }
241
314
 
242
- exports.inject = ['slots']
315
+ exports.inject = ['slots', 'locale']
243
316
  exports.apply = function apply(ctx) {
244
317
  registerSettings(ctx)
245
318
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, and Antigravity subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",