dsh-consumption-badge 1.3.2 → 1.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.
- package/README.md +3 -1
- package/lib/client.js +71 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -249,6 +249,8 @@ no second settings page.
|
|
|
249
249
|
| Slot labels | The action-row label and the settings tab title are **thunks**, resolved by the slot system at read time, so the tab renames itself on a switch |
|
|
250
250
|
| Late service | If the `locale` service is provided after this plugin's `apply()` ran (boot order differs per composition), the first render retries the bind |
|
|
251
251
|
| Missing service | Without the locale service everything falls back to Chinese — the plugin still works standalone |
|
|
252
|
+
| Both dictionaries | The plugin registers **both** `zh` (identity: key→key) and `en`, so the Chinese result never depends on how a particular host build resolves a missing locale (some use `en` as the fallback locale, which would otherwise leak English into a Chinese UI) |
|
|
253
|
+
| Language override | Settings → Usage → **Language**: `Follow shell` / `Chinese` / `English`. It overrides the shell for this plugin only, persists in `localStorage: dsh-consumption:language`, and is the escape hatch when a shell reports an unexpected locale |
|
|
252
254
|
| Host errors | `lib/index.js` never returns localized prose: failures carry a language-neutral `code` + `detail` (and an English `error` for logs). The client maps `code` → copy in the active language, and prints the host's `error` only for an unknown code |
|
|
253
255
|
|
|
254
256
|
Adding a language means adding one dictionary next to `DICT_EN`; the lookup order is
|
|
@@ -421,7 +423,7 @@ plugin by "running" it — a session-scoped dynamic plugin always dies with the
|
|
|
421
423
|
- Do not hard-code user-visible English or Chinese in new JSX: call `tt('…')` with the Chinese copy
|
|
422
424
|
as the key and add the English entry to `DICT_EN`. `check.mjs` fails if any `tt()` key has no
|
|
423
425
|
English translation or if a translation still contains Chinese.
|
|
424
|
-
- Self-check without booting DSH: `node check.mjs` (
|
|
426
|
+
- Self-check without booting DSH: `node check.mjs` (334 assertions over the route contract, the
|
|
425
427
|
client bundle contract, the pricing/aggregation maths and the bilingual copy). It lives outside
|
|
426
428
|
the published `files` list.
|
|
427
429
|
|
package/lib/client.js
CHANGED
|
@@ -434,6 +434,8 @@ window.__ModuleLoader__.load({
|
|
|
434
434
|
'按模型筛选;同时作为计价兜底': 'Filter by model; also the pricing fallback',
|
|
435
435
|
'仅 {model}': 'only {model}',
|
|
436
436
|
'日志 id {id}': 'log id {id}',
|
|
437
|
+
'语言': 'Language',
|
|
438
|
+
'跟随外壳': 'Follow shell',
|
|
437
439
|
'{count} 个模型各自计价': '{count} models priced separately',
|
|
438
440
|
'按 {model} 约还可跑 {tokens} tokens': 'about {tokens} tokens left at {model} prices',
|
|
439
441
|
'金额按各模型官方价估算(DeepSeek 区分峰谷 ×1/×2,其它厂商按 USD 价 ×7.1 折人民币),仅供参考;数据源为本会话日志,刷新后自动恢复。': 'Costs are estimated from each model’s list price (DeepSeek applies peak/off-peak ×1/×2; other vendors are converted from USD at 7.1), for reference only; the source is this session log, restored automatically after a refresh.',
|
|
@@ -577,6 +579,30 @@ window.__ModuleLoader__.load({
|
|
|
577
579
|
let boundTranslate = null
|
|
578
580
|
let localeService = null
|
|
579
581
|
let appCtx = null
|
|
582
|
+
// 插件自带的语言覆盖:'' = 跟随外壳,'zh' / 'en' = 用户显式指定。
|
|
583
|
+
// 存在的理由:外壳的 locale 服务在不同版本里回退策略不同(本版 FALLBACK_LOCALE=zh,
|
|
584
|
+
// 换成 en 的实现就会让中文界面吃 en 词典而显示英文),npm 包又装在别人的环境里,
|
|
585
|
+
// 所以这里既**显式注册 zh 词典**,也允许用户自己钉死语言。
|
|
586
|
+
const LS_LANGUAGE = 'dsh-consumption:language'
|
|
587
|
+
let languageOverride = readLanguage()
|
|
588
|
+
|
|
589
|
+
function readLanguage() {
|
|
590
|
+
const raw = lsGet(LS_LANGUAGE)
|
|
591
|
+
return raw === 'zh' || raw === 'en' ? raw : ''
|
|
592
|
+
}
|
|
593
|
+
function setLanguageOverride(next) {
|
|
594
|
+
const value = next === 'zh' || next === 'en' ? next : ''
|
|
595
|
+
languageOverride = value
|
|
596
|
+
lsSet(LS_LANGUAGE, value)
|
|
597
|
+
}
|
|
598
|
+
/** 语言下拉的选项(跟随外壳 / 中文 / English)。 */
|
|
599
|
+
function languageOptions() {
|
|
600
|
+
return [
|
|
601
|
+
{ id: '', label: tt('跟随外壳') },
|
|
602
|
+
{ id: 'zh', label: '中文' },
|
|
603
|
+
{ id: 'en', label: 'English' },
|
|
604
|
+
]
|
|
605
|
+
}
|
|
580
606
|
|
|
581
607
|
/** Fill `{name}` placeholders (same template syntax as the locale service). */
|
|
582
608
|
function applyParams(template, params) {
|
|
@@ -615,14 +641,20 @@ window.__ModuleLoader__.load({
|
|
|
615
641
|
if (service === null || typeof service.register !== 'function' || typeof service.bind !== 'function') return
|
|
616
642
|
// Only the `en` side is registered: the key already is the `zh` copy, and
|
|
617
643
|
// the locale service resolves a miss to the key itself.
|
|
644
|
+
// 中英两侧都显式注册:zh 侧是恒等映射(key 就是中文原文),
|
|
645
|
+
// 这样中文界面的结果不再取决于宿主 locale 实现的回退策略(有的版本回退到 en)。
|
|
646
|
+
const dictZh = {}
|
|
647
|
+
for (const key in DICT_EN) {
|
|
648
|
+
if (Object.prototype.hasOwnProperty.call(DICT_EN, key)) dictZh[key] = key
|
|
649
|
+
}
|
|
618
650
|
let ns = LOCALE_NS
|
|
619
651
|
let dispose = null
|
|
620
652
|
try {
|
|
621
|
-
dispose = service.register(ns,
|
|
653
|
+
dispose = service.register(ns, { zh: dictZh, en: DICT_EN })
|
|
622
654
|
} catch (e) {
|
|
623
655
|
ns = LOCALE_NS + '#' + BUILD_TAG + '-' + Math.random().toString(36).slice(2, 7)
|
|
624
656
|
try {
|
|
625
|
-
dispose = service.register(ns,
|
|
657
|
+
dispose = service.register(ns, { zh: dictZh, en: DICT_EN })
|
|
626
658
|
console.warn('[consumption-badge] previous dictionary still registered; bound a private locale namespace ' + ns)
|
|
627
659
|
} catch (e2) {
|
|
628
660
|
console.warn('[consumption-badge] locale dictionary registration failed: ' + msgOf(e2))
|
|
@@ -648,8 +680,16 @@ window.__ModuleLoader__.load({
|
|
|
648
680
|
attachLocale(appCtx)
|
|
649
681
|
}
|
|
650
682
|
|
|
651
|
-
/**
|
|
683
|
+
/**
|
|
684
|
+
* 取当前语言的文案。优先级:用户显式选择 → 外壳 locale 服务 → 原文(中文)。
|
|
685
|
+
* 显式选中文时直接返回 key(中文就是 key 本身),完全不经过宿主,避免任何回退策略问题。
|
|
686
|
+
*/
|
|
652
687
|
function tt(key, params) {
|
|
688
|
+
if (languageOverride === 'zh') return applyParams(key, params)
|
|
689
|
+
if (languageOverride === 'en') {
|
|
690
|
+
const hit = Object.prototype.hasOwnProperty.call(DICT_EN, key) ? DICT_EN[key] : key
|
|
691
|
+
return applyParams(hit, params)
|
|
692
|
+
}
|
|
653
693
|
if (localeService === null) ensureLocaleBound()
|
|
654
694
|
if (boundTranslate !== null) {
|
|
655
695
|
try {
|
|
@@ -671,8 +711,9 @@ window.__ModuleLoader__.load({
|
|
|
671
711
|
}
|
|
672
712
|
}
|
|
673
713
|
|
|
674
|
-
/** Active locale id
|
|
714
|
+
/** Active locale id: 覆盖优先,其次外壳,最后按中文。 */
|
|
675
715
|
function activeLocaleId() {
|
|
716
|
+
if (languageOverride !== '') return languageOverride
|
|
676
717
|
try {
|
|
677
718
|
const snapshot = localeService !== null && typeof localeService.getLocale === 'function' ? localeService.getLocale() : null
|
|
678
719
|
if (snapshot && typeof snapshot.active === 'string' && snapshot.active) return snapshot.active
|
|
@@ -799,10 +840,10 @@ window.__ModuleLoader__.load({
|
|
|
799
840
|
|
|
800
841
|
/* ================= 消耗弹窗背景样式(与个性化插件联动) ================= */
|
|
801
842
|
const MODAL_STYLE_KEY = 'dsh-consumption:modal-style'
|
|
802
|
-
const BUILD_TAG = 'dcb-1.3.
|
|
843
|
+
const BUILD_TAG = 'dcb-1.3.3'
|
|
803
844
|
// 本地版本:本包是零构建的手写客户端包,无法 import package.json,因此把版本号作为常量维护,
|
|
804
845
|
// 并由 check.mjs 守卫与 package.json 严格一致(发布前不一致直接失败)。
|
|
805
|
-
const PLUGIN_VERSION = '1.3.
|
|
846
|
+
const PLUGIN_VERSION = '1.3.3'
|
|
806
847
|
const PKG_NAME = 'dsh-consumption-badge'
|
|
807
848
|
const UPDATE_REGISTRY_URL = 'https://registry.npmjs.org/' + PKG_NAME + '/latest'
|
|
808
849
|
const UPDATE_COMMAND = 'dsh plugin --profile web update ' + PKG_NAME
|
|
@@ -1195,6 +1236,8 @@ window.__ModuleLoader__.load({
|
|
|
1195
1236
|
currency: readCurrency(),
|
|
1196
1237
|
// 模型筛选 / 计价兜底('' = 全部模型,不筛选;同时作为查不到模型时的计价基准)。
|
|
1197
1238
|
modelFilter: readModelFilter(),
|
|
1239
|
+
// 语言覆盖('' = 跟随外壳;'zh' / 'en' = 用户显式指定)。
|
|
1240
|
+
language: readLanguage(),
|
|
1198
1241
|
rates: null,
|
|
1199
1242
|
ratesState: 'idle',
|
|
1200
1243
|
ratesError: '',
|
|
@@ -1448,6 +1491,14 @@ window.__ModuleLoader__.load({
|
|
|
1448
1491
|
emit()
|
|
1449
1492
|
if (next !== 'CNY') ensureRates(false)
|
|
1450
1493
|
}
|
|
1494
|
+
/** 切换界面语言:'' 跟随外壳,或钉死 zh / en(落 localStorage,立即重绘)。 */
|
|
1495
|
+
function setLanguage(next) {
|
|
1496
|
+
const value = next === 'zh' || next === 'en' ? next : ''
|
|
1497
|
+
if (state.language === value) return
|
|
1498
|
+
setLanguageOverride(value)
|
|
1499
|
+
state.language = value
|
|
1500
|
+
emit()
|
|
1501
|
+
}
|
|
1451
1502
|
/** 强制刷新汇率(设置页的「刷新汇率」按钮)。 */
|
|
1452
1503
|
function refreshRates() {
|
|
1453
1504
|
state.rates = null
|
|
@@ -1627,6 +1678,7 @@ window.__ModuleLoader__.load({
|
|
|
1627
1678
|
setCurrency: setCurrency,
|
|
1628
1679
|
refreshRates: refreshRates,
|
|
1629
1680
|
setModelFilter: setModelFilter,
|
|
1681
|
+
setLanguage: setLanguage,
|
|
1630
1682
|
openPanel: openPanel,
|
|
1631
1683
|
closePanel: closePanel,
|
|
1632
1684
|
setModalStyle: setModalStyle,
|
|
@@ -2672,6 +2724,7 @@ window.__ModuleLoader__.load({
|
|
|
2672
2724
|
const setAllStats = s8[1]
|
|
2673
2725
|
// 「模型」下拉的状态放在 store 里(弹层与设置页共享同一个选择)。
|
|
2674
2726
|
const modelFilter = store.state.modelFilter
|
|
2727
|
+
const language = store.state.language
|
|
2675
2728
|
const s9 = React.useState(0)
|
|
2676
2729
|
const setStoreTick = s9[1]
|
|
2677
2730
|
|
|
@@ -2928,6 +2981,18 @@ window.__ModuleLoader__.load({
|
|
|
2928
2981
|
},
|
|
2929
2982
|
h('option', { key: 'all', value: '' }, tt('全部模型')),
|
|
2930
2983
|
modelOptionNodes(MODELS))),
|
|
2984
|
+
// 语言:默认跟随外壳;外壳语言不对劲时可以直接钉死中文/英文。
|
|
2985
|
+
h('div', { className: 'dcb-field' },
|
|
2986
|
+
h('label', { className: 'dcb-field-label', htmlFor: 'dcb-language-select' }, tt('语言')),
|
|
2987
|
+
h('select', {
|
|
2988
|
+
id: 'dcb-language-select',
|
|
2989
|
+
className: 'dcb-select',
|
|
2990
|
+
value: language,
|
|
2991
|
+
onChange: function (event) { store.setLanguage(event.target.value) },
|
|
2992
|
+
},
|
|
2993
|
+
languageOptions().map(function (opt) {
|
|
2994
|
+
return h('option', { key: opt.id || 'auto', value: opt.id }, opt.label)
|
|
2995
|
+
}))),
|
|
2931
2996
|
// 显示币种:报价表永远是人民币,这里只决定展示时换算成什么货币。
|
|
2932
2997
|
h('div', { className: 'dcb-field' },
|
|
2933
2998
|
h('label', { className: 'dcb-field-label', htmlFor: 'dcb-currency-select' }, tt('货币')),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-consumption-badge",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "DSH Web UI plugin: a per-message consumption badge (tokens | estimated cost | peak/valley) plus a session detail modal with cumulative tokens, estimated cost, Beijing-time peak/off-peak pricing, a per-turn dual-axis chart, and the DeepSeek account balance",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|