dsh-opencode-go-usage 1.0.0 → 1.1.0

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 (3) hide show
  1. package/lib/client.js +58 -20
  2. package/lib/index.js +22 -11
  3. package/package.json +3 -2
package/lib/client.js CHANGED
@@ -6,7 +6,8 @@
6
6
  // (react / react/jsx-runtime are in the static table). It registers a widget
7
7
  // into the sidebar's `sidebar.footer.action` list slot and polls the
8
8
  // same-origin proxy route owned by the host half — the GO API key never
9
- // enters the browser.
9
+ // enters the browser. UI strings are bilingual via the DSH locale service
10
+ // (dictionaries registered under the `dsh-opencode-go-usage` namespace).
10
11
  window.__ModuleLoader__.load({
11
12
  id: "dsh-opencode-go-usage",
12
13
  factory: (require) => {
@@ -41,24 +42,57 @@ window.__ModuleLoader__.load({
41
42
  document.head.appendChild(tag);
42
43
  }
43
44
 
45
+ // ── locale dictionaries ──
46
+ const NS = "dsh-opencode-go-usage";
47
+ const zh = {
48
+ "window.rolling": "滚动窗口",
49
+ "window.weekly": "周窗口",
50
+ "window.monthly": "月窗口",
51
+ "window.rolling.hint": "5 小时",
52
+ "status.ok": "正常",
53
+ "time.soon": "即将重置",
54
+ "time.minutes": "{n} 分后重置",
55
+ "time.hours": "{n} 小时 {m} 分后",
56
+ "time.days": "{n} 天 {m} 小时后",
57
+ "refresh": "刷新",
58
+ "loading": "加载中…",
59
+ "rail.title": "OpenCode GO 月用量 {pct}%",
60
+ "rail.plain": "OpenCode GO"
61
+ };
62
+ const en = {
63
+ "window.rolling": "Rolling (5h)",
64
+ "window.weekly": "Weekly",
65
+ "window.monthly": "Monthly",
66
+ "window.rolling.hint": "5 hours",
67
+ "status.ok": "OK",
68
+ "time.soon": "resets soon",
69
+ "time.minutes": "in {n} min",
70
+ "time.hours": "in {n}h {m}m",
71
+ "time.days": "in {n}d {m}h",
72
+ "refresh": "Refresh",
73
+ "loading": "Loading…",
74
+ "rail.title": "OpenCode GO monthly {pct}%",
75
+ "rail.plain": "OpenCode GO"
76
+ };
77
+
44
78
  // ── data helpers ──
45
79
  const WINDOWS = [
46
- { key: "rolling", label: "滚动窗口", hint: "5 小时" },
47
- { key: "weekly", label: "周窗口" },
48
- { key: "monthly", label: "月窗口" }
80
+ { key: "rolling", labelKey: "window.rolling", hintKey: "window.rolling.hint" },
81
+ { key: "weekly", labelKey: "window.weekly", hintKey: null },
82
+ { key: "monthly", labelKey: "window.monthly", hintKey: null }
49
83
  ];
50
84
 
51
- /** Short human relative time until `iso`. */
52
- function timeUntil(iso) {
85
+ /** Short human relative time until `iso`, localized. */
86
+ function timeUntil(iso, t) {
53
87
  const target = new Date(iso).getTime();
54
88
  if (!Number.isFinite(target)) return "";
55
89
  const diff = target - Date.now();
56
- if (diff <= 0) return "即将重置";
90
+ if (diff <= 0) return t("time.soon");
57
91
  const mins = Math.floor(diff / 60000);
58
- if (mins < 60) return mins + " 分后重置";
92
+ if (mins < 60) return t("time.minutes", { n: mins });
59
93
  const hours = Math.floor(mins / 60);
60
- if (hours < 24) return hours + " 小时 " + (mins % 60) + " 分后";
61
- return Math.floor(hours / 24) + " 天 " + (hours % 24) + " 小时后";
94
+ if (hours < 24) return t("time.hours", { n: hours, m: mins % 60 });
95
+ return t("time.days", { n: Math.floor(hours / 24), m: hours % 24 });
62
96
  }
63
97
 
64
98
  /** Fill color by usage percent. */
@@ -70,10 +104,12 @@ window.__ModuleLoader__.load({
70
104
 
71
105
  // ── widget component ──
72
106
  /**
73
- * @param {{ wide?: boolean }} props - owner share from the sidebar
74
- * (`sidebar.footer.action` is rendered with `{ wide }`).
107
+ * @param {{ wide?: boolean, t?: Function }} props - owner share from the
108
+ * sidebar (`sidebar.footer.action` is rendered with `{ wide }`) plus the
109
+ * locale translate seat declared by this registration.
75
110
  */
76
111
  function Widget(props) {
112
+ const t = props.t || ((key) => key);
77
113
  const [data, setData] = react.useState(null);
78
114
  const [error, setError] = react.useState(null);
79
115
  const [stamp, setStamp] = react.useState(0);
@@ -110,7 +146,7 @@ window.__ModuleLoader__.load({
110
146
  if (!props.wide) {
111
147
  return react_jsx_runtime.jsx("div", {
112
148
  className: "ocg-rail",
113
- title: error ? ("OpenCode GO: " + error) : (pct === null ? "OpenCode GO" : "OpenCode GO 月用量 " + pct + "%"),
149
+ title: error ? ("OpenCode GO: " + error) : (pct === null ? t("rail.plain") : t("rail.title", { pct: pct })),
114
150
  children: pct === null ? "GO" : pct + "%"
115
151
  });
116
152
  }
@@ -126,8 +162,8 @@ window.__ModuleLoader__.load({
126
162
  react_jsx_runtime.jsx("div", {
127
163
  className: "ocg-row-label",
128
164
  children: [
129
- react_jsx_runtime.jsx("b", { children: w.label }),
130
- react_jsx_runtime.jsx("span", { children: (win.resetsAt ? timeUntil(win.resetsAt) : w.hint) })
165
+ react_jsx_runtime.jsx("b", { children: t(w.labelKey) }),
166
+ react_jsx_runtime.jsx("span", { children: (win.resetsAt ? timeUntil(win.resetsAt, t) : (w.hintKey ? t(w.hintKey) : "")) })
131
167
  ]
132
168
  }),
133
169
  react_jsx_runtime.jsx("div", {
@@ -143,7 +179,7 @@ window.__ModuleLoader__.load({
143
179
  react_jsx_runtime.jsx("div", {
144
180
  className: "ocg-row-label",
145
181
  children: [
146
- react_jsx_runtime.jsx("span", { children: win.status === "ok" ? "正常" : win.status }),
182
+ react_jsx_runtime.jsx("span", { children: win.status === "ok" ? t("status.ok") : win.status }),
147
183
  react_jsx_runtime.jsx("span", { children: win.percent + "%" })
148
184
  ]
149
185
  })
@@ -161,26 +197,28 @@ window.__ModuleLoader__.load({
161
197
  react_jsx_runtime.jsx("button", {
162
198
  className: "ocg-refresh",
163
199
  onClick: () => setStamp(stamp + 1),
164
- children: "刷新"
200
+ children: t("refresh")
165
201
  })
166
202
  ]
167
203
  }),
168
204
  error ? react_jsx_runtime.jsx("div", { className: "ocg-err", children: error }) : null,
169
- !error && data === null ? react_jsx_runtime.jsx("div", { className: "ocg-err", children: "加载中…" }) : null,
205
+ !error && data === null ? react_jsx_runtime.jsx("div", { className: "ocg-err", children: t("loading") }) : null,
170
206
  rows.length > 0 ? react_jsx_runtime.jsx(react.Fragment, { children: rows }) : null
171
207
  ]
172
208
  });
173
209
  }
174
210
 
175
211
  // ── cordis plugin entry ──
176
- const inject = ["slots"];
212
+ const inject = ["slots", "locale"];
177
213
 
178
214
  function apply(ctx) {
215
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-opencode-go-usage: dictionaries");
179
216
  ctx.effect(() => ctx.slots.register({
180
217
  name: "sidebar.footer.action",
181
218
  id: "dsh-opencode-go-usage",
182
219
  order: 0,
183
- label: "OpenCode GO 用量"
220
+ label: "OpenCode GO",
221
+ locale: NS
184
222
  }, Widget), "dsh-opencode-go-usage: widget registration");
185
223
  }
186
224
 
package/lib/index.js CHANGED
@@ -75,21 +75,24 @@ async function fetchUsage(baseUrl, apiKey) {
75
75
  return await res.json();
76
76
  }
77
77
 
78
- /** Human-friendly chat output. */
79
- function renderUsageText(data) {
78
+ /** Human-friendly chat output, localized (zh | en). */
79
+ function renderUsageText(data, lang) {
80
+ const zh = lang !== "en";
80
81
  const u = data?.usage;
81
82
  if (!u || typeof u !== "object") return "OpenCode GO: unexpected response shape.";
82
- const lines = ["OpenCode GO 套餐用量:"];
83
+ const lines = [zh ? "OpenCode GO 套餐用量:" : "OpenCode GO plan usage:"];
83
84
  const defs = [
84
- ["rolling", "滚动窗口 (5h)"],
85
- ["weekly", "周窗口"],
86
- ["monthly", "月窗口"]
85
+ ["rolling", zh ? "滚动窗口 (5h)" : "Rolling (5h)"],
86
+ ["weekly", zh ? "周窗口" : "Weekly"],
87
+ ["monthly", zh ? "月窗口" : "Monthly"]
87
88
  ];
88
89
  for (const [key, label] of defs) {
89
90
  const w = u[key];
90
91
  if (!w || typeof w.percent !== "number") continue;
91
- const status = w.status === "ok" ? "正常" : w.status;
92
- const resets = w.resetsAt ? `重置于 ${new Date(w.resetsAt).toLocaleString("zh-CN")}` : "";
92
+ const status = w.status === "ok" ? (zh ? "正常" : "ok") : w.status;
93
+ const resets = w.resetsAt
94
+ ? (zh ? `重置于 ${new Date(w.resetsAt).toLocaleString("zh-CN")}` : `resets at ${new Date(w.resetsAt).toLocaleString("en-US")}`)
95
+ : "";
93
96
  lines.push(` ${label}: ${w.percent}% (${status})${resets ? `, ${resets}` : ""}`);
94
97
  }
95
98
  return lines.join("\n");
@@ -140,10 +143,14 @@ export function apply(ctx, rawConfig = {}) {
140
143
 
141
144
  const readConfig = () => {
142
145
  const stored = ctx.settings.get(namespace) ?? {};
146
+ // DSH's own locale preference ("locale.preference": zh | en); absent
147
+ // falls back to zh (the browser decides when the user never picked).
148
+ const locale = ctx.settings.get("locale")?.preference ?? "zh";
143
149
  return {
144
150
  apiKeyEnv: stored.apiKeyEnv,
145
151
  baseUrl: stored.baseUrl,
146
- cacheMs: stored.cacheMs
152
+ cacheMs: stored.cacheMs,
153
+ locale
147
154
  };
148
155
  };
149
156
 
@@ -157,7 +164,11 @@ export function apply(ctx, rawConfig = {}) {
157
164
  if (quotaCache !== null && now - quotaCache.at < config.cacheMs) return quotaCache.promise;
158
165
  const promise = (async () => {
159
166
  const key = await resolveApiKey(ctx, config.apiKeyEnv);
160
- if (key === null) throw new Error(`OpenCode GO: no API key (set credential ${config.apiKeyEnv})`);
167
+ if (key === null) {
168
+ throw new Error(config.locale === "en"
169
+ ? `OpenCode GO: no API key (set credential ${config.apiKeyEnv})`
170
+ : `OpenCode GO: 未配置 API key(在 credential ${config.apiKeyEnv} 中设置)`);
171
+ }
161
172
  return await fetchUsage(config.baseUrl, key);
162
173
  })();
163
174
  quotaCache = { at: now, promise };
@@ -225,7 +236,7 @@ export function apply(ctx, rawConfig = {}) {
225
236
  handler: async () => {
226
237
  try {
227
238
  const data = await quotaOnce();
228
- return { kind: "success", text: renderUsageText(data) };
239
+ return { kind: "success", text: renderUsageText(data, readConfig().locale) };
229
240
  } catch (error) {
230
241
  return { kind: "error", text: error instanceof Error ? error.message : String(error) };
231
242
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-opencode-go-usage",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "DSH (DeepSeek Harness) plugin: OpenCode GO plan quota widget in the sidebar, same-origin usage proxy, and a /opencode-go chat command",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -40,5 +40,6 @@
40
40
  "@deepseek-ai/dsh-client-runtime"
41
41
  ]
42
42
  }
43
- }
43
+ },
44
+ "repository": "github:yumusb/dsh-opencode-go-usage"
44
45
  }