@windrun-huaiin/third-ui 20.0.0 → 20.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 (30) hide show
  1. package/dist/clerk/fingerprint/fingerprint-provider.js +58 -49
  2. package/dist/clerk/fingerprint/fingerprint-provider.mjs +58 -49
  3. package/dist/main/alert-dialog/ads-alert-dialog.d.ts +15 -0
  4. package/dist/main/alert-dialog/ads-alert-dialog.js +24 -0
  5. package/dist/main/alert-dialog/ads-alert-dialog.mjs +22 -0
  6. package/dist/main/alert-dialog/confirm-dialog.d.ts +15 -0
  7. package/dist/main/alert-dialog/confirm-dialog.js +40 -0
  8. package/dist/main/alert-dialog/confirm-dialog.mjs +38 -0
  9. package/dist/main/alert-dialog/dialog-styles.d.ts +14 -0
  10. package/dist/main/alert-dialog/dialog-styles.js +35 -0
  11. package/dist/main/alert-dialog/dialog-styles.mjs +20 -0
  12. package/dist/main/alert-dialog/high-priority-confirm-dialog.d.ts +12 -0
  13. package/dist/main/alert-dialog/high-priority-confirm-dialog.js +23 -0
  14. package/dist/main/alert-dialog/high-priority-confirm-dialog.mjs +21 -0
  15. package/dist/main/alert-dialog/index.d.ts +4 -0
  16. package/dist/main/alert-dialog/info-dialog.d.ts +13 -0
  17. package/dist/main/alert-dialog/info-dialog.js +50 -0
  18. package/dist/main/alert-dialog/info-dialog.mjs +48 -0
  19. package/dist/main/index.d.ts +1 -1
  20. package/dist/main/index.js +7 -1
  21. package/dist/main/index.mjs +4 -1
  22. package/package.json +2 -2
  23. package/src/clerk/fingerprint/fingerprint-provider.tsx +155 -62
  24. package/src/main/{ads-alert-dialog.tsx → alert-dialog/ads-alert-dialog.tsx} +46 -29
  25. package/src/main/alert-dialog/confirm-dialog.tsx +131 -0
  26. package/src/main/alert-dialog/dialog-styles.ts +73 -0
  27. package/src/main/alert-dialog/high-priority-confirm-dialog.tsx +94 -0
  28. package/src/main/alert-dialog/index.ts +7 -0
  29. package/src/main/alert-dialog/info-dialog.tsx +139 -0
  30. package/src/main/index.ts +1 -1
@@ -6,6 +6,7 @@ var jsxRuntime = require('react/jsx-runtime');
6
6
  var icons = require('@windrun-huaiin/base-ui/icons');
7
7
  var lib = require('@windrun-huaiin/base-ui/lib');
8
8
  var utils = require('@windrun-huaiin/lib/utils');
9
+ var nextIntl = require('next-intl');
9
10
  var React = require('react');
10
11
  var useFingerprint = require('./use-fingerprint.js');
11
12
  var ui = require('@windrun-huaiin/base-ui/ui');
@@ -14,6 +15,10 @@ var fingerprintDebug = require('./fingerprint-debug.js');
14
15
  var fingerprintShared = require('./fingerprint-shared.js');
15
16
 
16
17
  const FingerprintContext = React.createContext(undefined);
18
+ function useFingerprintStatusTranslations() {
19
+ const messages = nextIntl.useMessages();
20
+ return messages.fingerprint;
21
+ }
17
22
  /**
18
23
  * Fingerprint Provider Component
19
24
  * 为应用提供fingerprint和匿名用户管理功能
@@ -53,6 +58,7 @@ function withFingerprint(Component, config) {
53
58
  * 组件:显示用户状态和积分信息(用于调试)
54
59
  */
55
60
  function FingerprintStatus() {
61
+ const translations = useFingerprintStatusTranslations();
56
62
  const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, } = useFingerprintContext();
57
63
  const [isOpen, setIsOpen] = React.useState(false);
58
64
  const [panelMode, setPanelMode] = React.useState('info');
@@ -92,7 +98,7 @@ function FingerprintStatus() {
92
98
  return [
93
99
  {
94
100
  key: 'paid',
95
- label: 'Paid',
101
+ label: translations.creditBuckets.paid,
96
102
  icon: jsxRuntime.jsx(icons.Settings2Icon, { className: "size-4 text-green-500 dark:text-green-300" }),
97
103
  balance: xCredit.balancePaid,
98
104
  total: xCredit.totalPaidLimit,
@@ -101,7 +107,7 @@ function FingerprintStatus() {
101
107
  },
102
108
  {
103
109
  key: 'oneTimePaid',
104
- label: 'OneTimePaid',
110
+ label: translations.creditBuckets.oneTimePaid,
105
111
  icon: jsxRuntime.jsx(icons.CoinsIcon, { className: "size-4 text-amber-500 dark:text-amber-300" }),
106
112
  balance: xCredit.balanceOneTimePaid,
107
113
  total: xCredit.totalOneTimePaidLimit,
@@ -110,7 +116,7 @@ function FingerprintStatus() {
110
116
  },
111
117
  {
112
118
  key: 'free',
113
- label: 'Free',
119
+ label: translations.creditBuckets.free,
114
120
  icon: jsxRuntime.jsx(icons.GiftIcon, { className: "size-4 text-purple-500 dark:text-purple-300" }),
115
121
  balance: xCredit.balanceFree,
116
122
  total: xCredit.totalFreeLimit,
@@ -118,15 +124,15 @@ function FingerprintStatus() {
118
124
  end: xCredit.freeEnd,
119
125
  },
120
126
  ];
121
- }, [xCredit]);
127
+ }, [translations.creditBuckets.free, translations.creditBuckets.oneTimePaid, translations.creditBuckets.paid, xCredit]);
122
128
  const subscriptionStatus = React.useMemo(() => {
123
129
  var _a, _b;
124
130
  if (!xSubscription) {
125
131
  return {
126
- status: 'Never',
132
+ status: translations.placeholders.subscriptionStatusNever,
127
133
  priceName: '--',
128
134
  creditsAllocated: '--',
129
- period: 'Unavailable',
135
+ period: translations.placeholders.subscriptionPeriodUnavailable,
130
136
  };
131
137
  }
132
138
  return {
@@ -135,9 +141,9 @@ function FingerprintStatus() {
135
141
  creditsAllocated: typeof xSubscription.creditsAllocated === 'number'
136
142
  ? formatNumber(xSubscription.creditsAllocated)
137
143
  : '--',
138
- period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd),
144
+ period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd, translations),
139
145
  };
140
- }, [xSubscription]);
146
+ }, [translations.placeholders.subscriptionPeriodUnavailable, translations.placeholders.subscriptionStatusNever, xSubscription]);
141
147
  const userStatus = (xUser === null || xUser === void 0 ? void 0 : xUser.status) || '--';
142
148
  const totalCredits = formatNumber(xCredit === null || xCredit === void 0 ? void 0 : xCredit.totalBalance);
143
149
  const subStatus = subscriptionStatus.status;
@@ -145,22 +151,22 @@ function FingerprintStatus() {
145
151
  const runContextParallelInitTest = () => tslib.__awaiter(this, void 0, void 0, function* () {
146
152
  const debugFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : fingerprintDebug.getOrCreateDebugFingerprintOverride();
147
153
  if (!debugFingerprintId) {
148
- setTestResult('Test fingerprint override is not ready yet.');
154
+ setTestResult(translations.messages.testFingerprintNotReady);
149
155
  return;
150
156
  }
151
157
  setActiveDebugFingerprintId(debugFingerprintId);
152
158
  setIsRunningTest(true);
153
- setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
159
+ setTestResult(tpl(translations.messages.runningFrontendPreventionTest, { fingerprintId: debugFingerprintId }));
154
160
  try {
155
161
  yield Promise.all([
156
162
  initializeDebugAnonymousUser(debugFingerprintId),
157
163
  initializeDebugAnonymousUser(debugFingerprintId),
158
164
  initializeDebugAnonymousUser(debugFingerprintId),
159
165
  ]);
160
- setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
166
+ setTestResult(tpl(translations.messages.frontendPreventionTestFinished, { fingerprintId: debugFingerprintId }));
161
167
  }
162
168
  catch (testError) {
163
- setTestResult(`Frontend Prevention Test failed: ${formatErrorMessage(testError)}`);
169
+ setTestResult(tpl(translations.messages.frontendPreventionTestFailed, { error: formatErrorMessage(testError, translations) }));
164
170
  }
165
171
  finally {
166
172
  setIsRunningTest(false);
@@ -180,7 +186,7 @@ function FingerprintStatus() {
180
186
  });
181
187
  if (!response.ok) {
182
188
  const errorData = yield response.json().catch(() => ({}));
183
- throw new Error(errorData.error || 'Failed to initialize anonymous user');
189
+ throw new Error(errorData.error || translations.messages.failedToInitializeAnonymousUser);
184
190
  }
185
191
  yield response.json().catch(() => ({}));
186
192
  }
@@ -191,12 +197,12 @@ function FingerprintStatus() {
191
197
  const runRawParallelPostTest = () => tslib.__awaiter(this, void 0, void 0, function* () {
192
198
  const normalizedFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : fingerprintDebug.getOrCreateDebugFingerprintOverride();
193
199
  if (!normalizedFingerprintId) {
194
- setTestResult('Test fingerprint override is not ready yet.');
200
+ setTestResult(translations.messages.testFingerprintNotReady);
195
201
  return;
196
202
  }
197
203
  setActiveDebugFingerprintId(normalizedFingerprintId);
198
204
  setIsRunningTest(true);
199
- setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
205
+ setTestResult(tpl(translations.messages.runningBackendIdempotencyTest, { fingerprintId: normalizedFingerprintId }));
200
206
  try {
201
207
  const fingerprintHeaders = yield fingerprintClient.createFingerprintHeaders();
202
208
  const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
@@ -227,16 +233,16 @@ function FingerprintStatus() {
227
233
  .filter((payload) => !payload.ok)
228
234
  .map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
229
235
  setTestResult([
230
- `Backend Idempotency Test done.`,
231
- `created=${createdUserIds.length}`,
232
- `reused=${reusedUserIds.length}`,
233
- `failed=${failedStatuses.length}`,
234
- `createdUserIds=[${createdUserIds.join(', ')}]`,
235
- failedStatuses.length > 0 ? `failedStatuses=[${failedStatuses.join(', ')}]` : null,
236
+ translations.messages.backendIdempotencyTestDone,
237
+ tpl(translations.messages.createdCount, { count: createdUserIds.length }),
238
+ tpl(translations.messages.reusedCount, { count: reusedUserIds.length }),
239
+ tpl(translations.messages.failedCount, { count: failedStatuses.length }),
240
+ tpl(translations.messages.createdUserIds, { value: createdUserIds.join(', ') }),
241
+ failedStatuses.length > 0 ? tpl(translations.messages.failedStatuses, { value: failedStatuses.join(', ') }) : null,
236
242
  ].filter(Boolean).join('\n'));
237
243
  }
238
244
  catch (testError) {
239
- setTestResult(`Backend Idempotency Test failed: ${formatErrorMessage(testError)}`);
245
+ setTestResult(tpl(translations.messages.backendIdempotencyTestFailed, { error: formatErrorMessage(testError, translations) }));
240
246
  }
241
247
  finally {
242
248
  setIsRunningTest(false);
@@ -245,31 +251,31 @@ function FingerprintStatus() {
245
251
  const regenerateTestFingerprint = () => {
246
252
  const nextFingerprintId = fingerprintDebug.regenerateDebugFingerprintOverride();
247
253
  setActiveDebugFingerprintId(nextFingerprintId);
248
- setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
254
+ setTestResult(tpl(translations.messages.generatedTestFingerprintOverride, { fingerprintId: nextFingerprintId }));
249
255
  };
250
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isOpen && (jsxRuntime.jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: utils.cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsxRuntime.jsx(icons.LightbulbIcon, { className: "size-6 text-white" }) })), isOpen && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxRuntime.jsxs("div", { ref: modalRef, className: utils.cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsxRuntime.jsx("header", { className: "mb-4", children: jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxRuntime.jsxs("div", { className: utils.cn("flex items-center gap-2 text-base font-bold tracking-wider", lib.themeIconColor), children: [jsxRuntime.jsx(icons.ShieldUserIcon, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: utils.cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
256
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [!isOpen && (jsxRuntime.jsx("button", { onClick: handleToggle, type: "button", "aria-label": translations.panel.toggleAriaLabel, className: utils.cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsxRuntime.jsx(icons.LightbulbIcon, { className: "size-6 text-white" }) })), isOpen && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxRuntime.jsxs("div", { ref: modalRef, className: utils.cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsxRuntime.jsx("header", { className: "mb-4", children: jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxRuntime.jsxs("div", { className: utils.cn("flex items-center gap-2 text-base font-bold tracking-wider", lib.themeIconColor), children: [jsxRuntime.jsx(icons.ShieldUserIcon, { className: "size-4" }), translations.panel.title] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: utils.cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
251
257
  ? utils.cn('border-transparent text-white', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass)
252
- : themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsxRuntime.jsx("span", { children: "Concurrent Test" }), jsxRuntime.jsx("span", { className: utils.cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
258
+ : themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsxRuntime.jsx("span", { children: translations.panel.testModeLabel }), jsxRuntime.jsx("span", { className: utils.cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
253
259
  ? 'bg-white/25'
254
- : 'bg-slate-300 dark:bg-slate-700'), children: jsxRuntime.jsx("span", { className: utils.cn('inline-block size-4 rounded-full shadow-sm transition-transform', panelMode === 'test' ? 'bg-white' : 'bg-white dark:bg-slate-100', panelMode === 'test' ? 'translate-x-4' : 'translate-x-0.5') }) })] }), jsxRuntime.jsx("button", { type: "button", "aria-label": "Close fingerprint panel", className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsxRuntime.jsx(icons.XIcon, { className: "size-4" }) })] })] }) }), jsxRuntime.jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(icons.FingerprintIcon, { className: "size-4" }), title: "User", rightInfo: jsxRuntime.jsx(StatusTag, { value: userStatus }), items: [
255
- { label: 'UserID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
256
- { label: 'NickName', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
257
- { label: 'FingerprintID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
258
- { label: 'ClerkUserID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
259
- { label: 'Email', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
260
- { label: 'StripeCusID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
261
- { label: 'CreatedAt', value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
262
- ] }), jsxRuntime.jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(icons.GemIcon, { className: "size-4" }), title: "Credits Info", rightInfo: jsxRuntime.jsx("span", { className: utils.cn("font-semibold", lib.themeIconColor), children: totalCredits }) }), jsxRuntime.jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
260
+ : 'bg-slate-300 dark:bg-slate-700'), children: jsxRuntime.jsx("span", { className: utils.cn('inline-block size-4 rounded-full shadow-sm transition-transform', panelMode === 'test' ? 'bg-white' : 'bg-white dark:bg-slate-100', panelMode === 'test' ? 'translate-x-4' : 'translate-x-0.5') }) })] }), jsxRuntime.jsx("button", { type: "button", "aria-label": translations.panel.closeAriaLabel, className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsxRuntime.jsx(icons.XIcon, { className: "size-4" }) })] })] }) }), jsxRuntime.jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(icons.FingerprintIcon, { className: "size-4" }), title: translations.sections.user, rightInfo: jsxRuntime.jsx(StatusTag, { value: userStatus, translations: translations }), items: [
261
+ { label: translations.labels.userId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
262
+ { label: translations.labels.nickName, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
263
+ { label: translations.labels.fingerprintId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
264
+ { label: translations.labels.clerkUserId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
265
+ { label: translations.labels.email, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
266
+ { label: translations.labels.stripeCusId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
267
+ { label: translations.labels.createdAt, value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
268
+ ] }), jsxRuntime.jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(icons.GemIcon, { className: "size-4" }), title: translations.sections.creditsInfo, rightInfo: jsxRuntime.jsx("span", { className: utils.cn("font-semibold", lib.themeIconColor), children: totalCredits }) }), jsxRuntime.jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
263
269
  const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
264
- return (jsxRuntime.jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsxRuntime.jsx("span", { children: bucket.label })] }), jsxRuntime.jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsxRuntime.jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsxRuntime.jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxRuntime.jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsxRuntime.jsx("span", { children: formatRangeText(bucket.start, bucket.end) }), jsxRuntime.jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
265
- })) : (jsxRuntime.jsx(EmptyPlaceholder, { label: "No Credits Yet", icon: jsxRuntime.jsx(icons.DatabaseZapIcon, { className: "size-4" }) })) })] }), jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(icons.BellIcon, { className: "size-4" }), title: "Subscription", rightInfo: jsxRuntime.jsx(StatusTag, { value: subStatus }), items: [
266
- { label: 'Plan', value: subscriptionStatus.priceName },
267
- { label: 'Period', value: subscriptionStatus.period },
268
- { label: 'Allocated', value: subscriptionStatus.creditsAllocated },
269
- { label: 'SubID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
270
- { label: 'OrderID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
271
- { label: 'PriceID', value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
272
- ] })] })) : (jsxRuntime.jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(icons.DatabaseZapIcon, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsxRuntime.jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxRuntime.jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Real Browser" }), jsxRuntime.jsx(ui.CopyableText, { text: fingerprintId || '' })] }), jsxRuntime.jsxs("div", { className: "space-y-1", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Test Override" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsxRuntime.jsx("div", { className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsxRuntime.jsx(ui.CopyableText, { text: activeDebugFingerprintId || '' }) }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": "Generate new test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsxRuntime.jsx(icons.RefreshCcwIcon, { className: "size-4" }) })] })] })] }), jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: utils.cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: "Frontend Prevention Test" }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: utils.cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass), children: "Backend Idempotency Test" })] }), jsxRuntime.jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsxRuntime.jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || 'No test executed yet.' }) })] })), error && (jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [jsxRuntime.jsx(icons.XIcon, { className: "mt-0.5 size-4 shrink-0" }), jsxRuntime.jsx("span", { children: error })] }), jsxRuntime.jsx("button", { type: "button", "aria-label": "Dismiss error", onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsxRuntime.jsx(icons.XIcon, { className: "size-4" }) })] }))] })] })] }))] }));
270
+ return (jsxRuntime.jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsxRuntime.jsx("span", { children: bucket.label })] }), jsxRuntime.jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsxRuntime.jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsxRuntime.jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxRuntime.jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsxRuntime.jsx("span", { children: formatRangeText(bucket.start, bucket.end, translations) }), jsxRuntime.jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
271
+ })) : (jsxRuntime.jsx(EmptyPlaceholder, { label: translations.placeholders.noCreditsYet, icon: jsxRuntime.jsx(icons.DatabaseZapIcon, { className: "size-4" }) })) })] }), jsxRuntime.jsx(PanelSection, { icon: jsxRuntime.jsx(icons.BellIcon, { className: "size-4" }), title: translations.sections.subscription, rightInfo: jsxRuntime.jsx(StatusTag, { value: subStatus, translations: translations }), items: [
272
+ { label: translations.labels.plan, value: subscriptionStatus.priceName },
273
+ { label: translations.labels.period, value: subscriptionStatus.period },
274
+ { label: translations.labels.allocated, value: subscriptionStatus.creditsAllocated },
275
+ { label: translations.labels.subId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
276
+ { label: translations.labels.orderId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
277
+ { label: translations.labels.priceId, value: jsxRuntime.jsx(ui.CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
278
+ ] })] })) : (jsxRuntime.jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsxRuntime.jsx(PanelHeader, { icon: jsxRuntime.jsx(icons.DatabaseZapIcon, { className: "size-4" }), title: translations.sections.concurrentBaseInfo, rightInfo: jsxRuntime.jsx(StatusTag, { value: isRunningTest ? translations.status.pending : translations.status.idle, translations: translations }) }), jsxRuntime.jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: translations.labels.realBrowser }), jsxRuntime.jsx(ui.CopyableText, { text: fingerprintId || '' })] }), jsxRuntime.jsxs("div", { className: "space-y-1", children: [jsxRuntime.jsx("span", { className: "text-slate-400 dark:text-slate-500", children: translations.labels.testOverride }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsxRuntime.jsx("div", { className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsxRuntime.jsx(ui.CopyableText, { text: activeDebugFingerprintId || '' }) }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": translations.actions.generateNewTestFingerprintAriaLabel, className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsxRuntime.jsx(icons.RefreshCcwIcon, { className: "size-4" }) })] })] })] }), jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: utils.cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: translations.actions.frontendPreventionTest }), jsxRuntime.jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: utils.cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', lib.themeButtonGradientClass, lib.themeButtonGradientHoverClass), children: translations.actions.backendIdempotencyTest })] }), jsxRuntime.jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsxRuntime.jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || translations.placeholders.noTestExecutedYet }) })] })), error && (jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [jsxRuntime.jsx(icons.XIcon, { className: "mt-0.5 size-4 shrink-0" }), jsxRuntime.jsx("span", { children: error })] }), jsxRuntime.jsx("button", { type: "button", "aria-label": translations.actions.dismissErrorAriaLabel, onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsxRuntime.jsx(icons.XIcon, { className: "size-4" }) })] }))] })] })] }))] }));
273
279
  }
274
280
  /* ==================== 新增辅助组件 ==================== */
275
281
  // 标题行:左侧图标+标题,右侧信息(右对齐)
@@ -296,11 +302,11 @@ function computeProgress(balance, total) {
296
302
  return 0;
297
303
  return Math.min(Math.max(ratio, 0), 1);
298
304
  }
299
- function formatRangeText(start, end) {
305
+ function formatRangeText(start, end, translations) {
300
306
  const safeStart = start && start.trim() ? start : '';
301
307
  const safeEnd = end && end.trim() ? end : '';
302
308
  if (!safeStart && !safeEnd) {
303
- return 'No records';
309
+ return translations.placeholders.noRecords;
304
310
  }
305
311
  if (!safeStart) {
306
312
  return safeEnd;
@@ -310,9 +316,9 @@ function formatRangeText(start, end) {
310
316
  }
311
317
  return `${safeStart} - ${safeEnd}`;
312
318
  }
313
- function StatusTag({ value }) {
319
+ function StatusTag({ value, translations, }) {
314
320
  if (!value)
315
- return jsxRuntime.jsx("span", { className: "text-slate-400", children: "None" });
321
+ return jsxRuntime.jsx("span", { className: "text-slate-400", children: translations.placeholders.none });
316
322
  const normalized = value.toLowerCase();
317
323
  const colorMap = {
318
324
  // 绿色:正常/活跃
@@ -335,14 +341,17 @@ function StatusTag({ value }) {
335
341
  const badgeClass = colorMap[normalized] || defaultColor;
336
342
  return (jsxRuntime.jsx("span", { className: utils.cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
337
343
  }
338
- function formatErrorMessage(error) {
344
+ function formatErrorMessage(error, translations) {
339
345
  if (error instanceof Error) {
340
346
  return error.message;
341
347
  }
342
348
  if (typeof error === 'string') {
343
349
  return error;
344
350
  }
345
- return 'Unknown error';
351
+ return translations.placeholders.unknownError;
352
+ }
353
+ function tpl(template, values) {
354
+ return template.replace(/\{(\w+)\}/g, (_, key) => { var _a; return String((_a = values[key]) !== null && _a !== void 0 ? _a : ''); });
346
355
  }
347
356
 
348
357
  exports.FingerprintProvider = FingerprintProvider;
@@ -4,6 +4,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { Settings2Icon, CoinsIcon, GiftIcon, LightbulbIcon, ShieldUserIcon, XIcon, FingerprintIcon, GemIcon, DatabaseZapIcon, BellIcon, RefreshCcwIcon } from '@windrun-huaiin/base-ui/icons';
5
5
  import { themeIconColor, themeButtonGradientClass, themeButtonGradientHoverClass } from '@windrun-huaiin/base-ui/lib';
6
6
  import { cn } from '@windrun-huaiin/lib/utils';
7
+ import { useMessages } from 'next-intl';
7
8
  import { createContext, useContext, useState, useRef, useEffect, useMemo } from 'react';
8
9
  import { useFingerprint } from './use-fingerprint.mjs';
9
10
  import { CopyableText } from '@windrun-huaiin/base-ui/ui';
@@ -12,6 +13,10 @@ import { getOrCreateDebugFingerprintOverride, regenerateDebugFingerprintOverride
12
13
  import { FINGERPRINT_SOURCE_REFER } from './fingerprint-shared.mjs';
13
14
 
14
15
  const FingerprintContext = createContext(undefined);
16
+ function useFingerprintStatusTranslations() {
17
+ const messages = useMessages();
18
+ return messages.fingerprint;
19
+ }
15
20
  /**
16
21
  * Fingerprint Provider Component
17
22
  * 为应用提供fingerprint和匿名用户管理功能
@@ -51,6 +56,7 @@ function withFingerprint(Component, config) {
51
56
  * 组件:显示用户状态和积分信息(用于调试)
52
57
  */
53
58
  function FingerprintStatus() {
59
+ const translations = useFingerprintStatusTranslations();
54
60
  const { fingerprintId, xUser, xCredit, xSubscription, error, clearError, } = useFingerprintContext();
55
61
  const [isOpen, setIsOpen] = useState(false);
56
62
  const [panelMode, setPanelMode] = useState('info');
@@ -90,7 +96,7 @@ function FingerprintStatus() {
90
96
  return [
91
97
  {
92
98
  key: 'paid',
93
- label: 'Paid',
99
+ label: translations.creditBuckets.paid,
94
100
  icon: jsx(Settings2Icon, { className: "size-4 text-green-500 dark:text-green-300" }),
95
101
  balance: xCredit.balancePaid,
96
102
  total: xCredit.totalPaidLimit,
@@ -99,7 +105,7 @@ function FingerprintStatus() {
99
105
  },
100
106
  {
101
107
  key: 'oneTimePaid',
102
- label: 'OneTimePaid',
108
+ label: translations.creditBuckets.oneTimePaid,
103
109
  icon: jsx(CoinsIcon, { className: "size-4 text-amber-500 dark:text-amber-300" }),
104
110
  balance: xCredit.balanceOneTimePaid,
105
111
  total: xCredit.totalOneTimePaidLimit,
@@ -108,7 +114,7 @@ function FingerprintStatus() {
108
114
  },
109
115
  {
110
116
  key: 'free',
111
- label: 'Free',
117
+ label: translations.creditBuckets.free,
112
118
  icon: jsx(GiftIcon, { className: "size-4 text-purple-500 dark:text-purple-300" }),
113
119
  balance: xCredit.balanceFree,
114
120
  total: xCredit.totalFreeLimit,
@@ -116,15 +122,15 @@ function FingerprintStatus() {
116
122
  end: xCredit.freeEnd,
117
123
  },
118
124
  ];
119
- }, [xCredit]);
125
+ }, [translations.creditBuckets.free, translations.creditBuckets.oneTimePaid, translations.creditBuckets.paid, xCredit]);
120
126
  const subscriptionStatus = useMemo(() => {
121
127
  var _a, _b;
122
128
  if (!xSubscription) {
123
129
  return {
124
- status: 'Never',
130
+ status: translations.placeholders.subscriptionStatusNever,
125
131
  priceName: '--',
126
132
  creditsAllocated: '--',
127
- period: 'Unavailable',
133
+ period: translations.placeholders.subscriptionPeriodUnavailable,
128
134
  };
129
135
  }
130
136
  return {
@@ -133,9 +139,9 @@ function FingerprintStatus() {
133
139
  creditsAllocated: typeof xSubscription.creditsAllocated === 'number'
134
140
  ? formatNumber(xSubscription.creditsAllocated)
135
141
  : '--',
136
- period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd),
142
+ period: formatRangeText(xSubscription.subPeriodStart, xSubscription.subPeriodEnd, translations),
137
143
  };
138
- }, [xSubscription]);
144
+ }, [translations.placeholders.subscriptionPeriodUnavailable, translations.placeholders.subscriptionStatusNever, xSubscription]);
139
145
  const userStatus = (xUser === null || xUser === void 0 ? void 0 : xUser.status) || '--';
140
146
  const totalCredits = formatNumber(xCredit === null || xCredit === void 0 ? void 0 : xCredit.totalBalance);
141
147
  const subStatus = subscriptionStatus.status;
@@ -143,22 +149,22 @@ function FingerprintStatus() {
143
149
  const runContextParallelInitTest = () => __awaiter(this, void 0, void 0, function* () {
144
150
  const debugFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : getOrCreateDebugFingerprintOverride();
145
151
  if (!debugFingerprintId) {
146
- setTestResult('Test fingerprint override is not ready yet.');
152
+ setTestResult(translations.messages.testFingerprintNotReady);
147
153
  return;
148
154
  }
149
155
  setActiveDebugFingerprintId(debugFingerprintId);
150
156
  setIsRunningTest(true);
151
- setTestResult(`Running Frontend Prevention Test with fingerprint: ${debugFingerprintId}`);
157
+ setTestResult(tpl(translations.messages.runningFrontendPreventionTest, { fingerprintId: debugFingerprintId }));
152
158
  try {
153
159
  yield Promise.all([
154
160
  initializeDebugAnonymousUser(debugFingerprintId),
155
161
  initializeDebugAnonymousUser(debugFingerprintId),
156
162
  initializeDebugAnonymousUser(debugFingerprintId),
157
163
  ]);
158
- setTestResult(`Frontend Prevention Test finished. Active test fingerprint: ${debugFingerprintId}`);
164
+ setTestResult(tpl(translations.messages.frontendPreventionTestFinished, { fingerprintId: debugFingerprintId }));
159
165
  }
160
166
  catch (testError) {
161
- setTestResult(`Frontend Prevention Test failed: ${formatErrorMessage(testError)}`);
167
+ setTestResult(tpl(translations.messages.frontendPreventionTestFailed, { error: formatErrorMessage(testError, translations) }));
162
168
  }
163
169
  finally {
164
170
  setIsRunningTest(false);
@@ -178,7 +184,7 @@ function FingerprintStatus() {
178
184
  });
179
185
  if (!response.ok) {
180
186
  const errorData = yield response.json().catch(() => ({}));
181
- throw new Error(errorData.error || 'Failed to initialize anonymous user');
187
+ throw new Error(errorData.error || translations.messages.failedToInitializeAnonymousUser);
182
188
  }
183
189
  yield response.json().catch(() => ({}));
184
190
  }
@@ -189,12 +195,12 @@ function FingerprintStatus() {
189
195
  const runRawParallelPostTest = () => __awaiter(this, void 0, void 0, function* () {
190
196
  const normalizedFingerprintId = activeDebugFingerprintId !== null && activeDebugFingerprintId !== void 0 ? activeDebugFingerprintId : getOrCreateDebugFingerprintOverride();
191
197
  if (!normalizedFingerprintId) {
192
- setTestResult('Test fingerprint override is not ready yet.');
198
+ setTestResult(translations.messages.testFingerprintNotReady);
193
199
  return;
194
200
  }
195
201
  setActiveDebugFingerprintId(normalizedFingerprintId);
196
202
  setIsRunningTest(true);
197
- setTestResult(`Running Backend Idempotency Test with fingerprint: ${normalizedFingerprintId}`);
203
+ setTestResult(tpl(translations.messages.runningBackendIdempotencyTest, { fingerprintId: normalizedFingerprintId }));
198
204
  try {
199
205
  const fingerprintHeaders = yield createFingerprintHeaders();
200
206
  const requests = Array.from({ length: 3 }, () => fetch('/api/user/anonymous/init', {
@@ -225,16 +231,16 @@ function FingerprintStatus() {
225
231
  .filter((payload) => !payload.ok)
226
232
  .map((payload) => `${payload.status}${payload.error ? `:${payload.error}` : ''}`);
227
233
  setTestResult([
228
- `Backend Idempotency Test done.`,
229
- `created=${createdUserIds.length}`,
230
- `reused=${reusedUserIds.length}`,
231
- `failed=${failedStatuses.length}`,
232
- `createdUserIds=[${createdUserIds.join(', ')}]`,
233
- failedStatuses.length > 0 ? `failedStatuses=[${failedStatuses.join(', ')}]` : null,
234
+ translations.messages.backendIdempotencyTestDone,
235
+ tpl(translations.messages.createdCount, { count: createdUserIds.length }),
236
+ tpl(translations.messages.reusedCount, { count: reusedUserIds.length }),
237
+ tpl(translations.messages.failedCount, { count: failedStatuses.length }),
238
+ tpl(translations.messages.createdUserIds, { value: createdUserIds.join(', ') }),
239
+ failedStatuses.length > 0 ? tpl(translations.messages.failedStatuses, { value: failedStatuses.join(', ') }) : null,
234
240
  ].filter(Boolean).join('\n'));
235
241
  }
236
242
  catch (testError) {
237
- setTestResult(`Backend Idempotency Test failed: ${formatErrorMessage(testError)}`);
243
+ setTestResult(tpl(translations.messages.backendIdempotencyTestFailed, { error: formatErrorMessage(testError, translations) }));
238
244
  }
239
245
  finally {
240
246
  setIsRunningTest(false);
@@ -243,31 +249,31 @@ function FingerprintStatus() {
243
249
  const regenerateTestFingerprint = () => {
244
250
  const nextFingerprintId = regenerateDebugFingerprintOverride();
245
251
  setActiveDebugFingerprintId(nextFingerprintId);
246
- setTestResult(`Generated test fingerprint override: ${nextFingerprintId}`);
252
+ setTestResult(tpl(translations.messages.generatedTestFingerprintOverride, { fingerprintId: nextFingerprintId }));
247
253
  };
248
- return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label": "Fingerprint debug panel", className: cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', themeButtonGradientClass, themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsx(LightbulbIcon, { className: "size-6 text-white" }) })), isOpen && (jsxs(Fragment, { children: [jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxs("div", { ref: modalRef, className: cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsx("header", { className: "mb-4", children: jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxs("div", { className: cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor), children: [jsx(ShieldUserIcon, { className: "size-4" }), "Fingerprint Debug Panel"] }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
254
+ return (jsxs(Fragment, { children: [!isOpen && (jsx("button", { onClick: handleToggle, type: "button", "aria-label": translations.panel.toggleAriaLabel, className: cn('fixed left-2 top-2 md:left-2 md:top-3 z-10000 inline-flex size-8 md:size-11 items-center justify-center rounded-full', themeButtonGradientClass, themeButtonGradientHoverClass, 'text-white rounded-full shadow-lg hover:shadow-xl transition-all duration-300'), children: jsx(LightbulbIcon, { className: "size-6 text-white" }) })), isOpen && (jsxs(Fragment, { children: [jsx("div", { onClick: handleBackdropClick, className: "fixed inset-0 z-9998 bg-black/60 backdrop-blur-sm" }), jsxs("div", { ref: modalRef, className: cn('fixed inset-3 z-9999 mx-auto w-[min(95vw,520px)] overflow-y-auto rounded-2xl border', 'border-slate-200/70 bg-white/95 p-4 shadow-2xl backdrop-blur-sm', 'font-sans text-sm text-slate-700 dark:border-white/12 dark:bg-slate-950/95 dark:text-slate-200', 'sm:inset-auto md:left-2 sm:top-1 md:right-auto sm:w-[min(520px,95vw)] sm:p-5'), children: [jsx("header", { className: "mb-4", children: jsxs("div", { className: "flex items-start justify-between gap-3", children: [jsxs("div", { className: cn("flex items-center gap-2 text-base font-bold tracking-wider", themeIconColor), children: [jsx(ShieldUserIcon, { className: "size-4" }), translations.panel.title] }), jsxs("div", { className: "flex items-center gap-2", children: [jsxs("button", { type: "button", onClick: () => setPanelMode((prev) => prev === 'info' ? 'test' : 'info'), className: cn('inline-flex items-center gap-2 rounded-full border px-2 py-1 text-[11px] font-semibold shadow-sm transition-all duration-200', panelMode === 'test'
249
255
  ? cn('border-transparent text-white', themeButtonGradientClass, themeButtonGradientHoverClass)
250
- : themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsx("span", { children: "Concurrent Test" }), jsx("span", { className: cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
256
+ : themedGhostButtonClass), "aria-pressed": panelMode === 'test', children: [jsx("span", { children: translations.panel.testModeLabel }), jsx("span", { className: cn('relative inline-flex h-5 w-9 items-center rounded-full transition-colors', panelMode === 'test'
251
257
  ? 'bg-white/25'
252
- : 'bg-slate-300 dark:bg-slate-700'), children: jsx("span", { className: cn('inline-block size-4 rounded-full shadow-sm transition-transform', panelMode === 'test' ? 'bg-white' : 'bg-white dark:bg-slate-100', panelMode === 'test' ? 'translate-x-4' : 'translate-x-0.5') }) })] }), jsx("button", { type: "button", "aria-label": "Close fingerprint panel", className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsx(XIcon, { className: "size-4" }) })] })] }) }), jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxs(Fragment, { children: [jsx(PanelSection, { icon: jsx(FingerprintIcon, { className: "size-4" }), title: "User", rightInfo: jsx(StatusTag, { value: userStatus }), items: [
253
- { label: 'UserID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
254
- { label: 'NickName', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
255
- { label: 'FingerprintID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
256
- { label: 'ClerkUserID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
257
- { label: 'Email', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
258
- { label: 'StripeCusID', value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
259
- { label: 'CreatedAt', value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
260
- ] }), jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsx(PanelHeader, { icon: jsx(GemIcon, { className: "size-4" }), title: "Credits Info", rightInfo: jsx("span", { className: cn("font-semibold", themeIconColor), children: totalCredits }) }), jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
258
+ : 'bg-slate-300 dark:bg-slate-700'), children: jsx("span", { className: cn('inline-block size-4 rounded-full shadow-sm transition-transform', panelMode === 'test' ? 'bg-white' : 'bg-white dark:bg-slate-100', panelMode === 'test' ? 'translate-x-4' : 'translate-x-0.5') }) })] }), jsx("button", { type: "button", "aria-label": translations.panel.closeAriaLabel, className: "rounded-full p-2 text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 dark:text-slate-300 dark:hover:bg-white/10 dark:hover:text-white", onClick: () => setIsOpen(false), children: jsx(XIcon, { className: "size-4" }) })] })] }) }), jsxs("section", { className: "space-y-1", children: [panelMode === 'info' ? (jsxs(Fragment, { children: [jsx(PanelSection, { icon: jsx(FingerprintIcon, { className: "size-4" }), title: translations.sections.user, rightInfo: jsx(StatusTag, { value: userStatus, translations: translations }), items: [
259
+ { label: translations.labels.userId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userId) || '' }) },
260
+ { label: translations.labels.nickName, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.userName) || '' }) },
261
+ { label: translations.labels.fingerprintId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.fingerprintId) || fingerprintId || '' }) },
262
+ { label: translations.labels.clerkUserId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.clerkUserId) || '' }) },
263
+ { label: translations.labels.email, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.email) || '' }) },
264
+ { label: translations.labels.stripeCusId, value: jsx(CopyableText, { text: (xUser === null || xUser === void 0 ? void 0 : xUser.stripeCusId) || '' }) },
265
+ { label: translations.labels.createdAt, value: (xUser === null || xUser === void 0 ? void 0 : xUser.createdAt) || '--' },
266
+ ] }), jsxs("div", { className: "space-y-2 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/50", children: [jsx(PanelHeader, { icon: jsx(GemIcon, { className: "size-4" }), title: translations.sections.creditsInfo, rightInfo: jsx("span", { className: cn("font-semibold", themeIconColor), children: totalCredits }) }), jsx("div", { className: "space-y-3", children: creditBuckets.length > 0 ? (creditBuckets.map((bucket) => {
261
267
  const percent = Math.round(computeProgress(bucket.balance, bucket.total) * 100);
262
- return (jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsx("span", { children: bucket.label })] }), jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsx("span", { children: formatRangeText(bucket.start, bucket.end) }), jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
263
- })) : (jsx(EmptyPlaceholder, { label: "No Credits Yet", icon: jsx(DatabaseZapIcon, { className: "size-4" }) })) })] }), jsx(PanelSection, { icon: jsx(BellIcon, { className: "size-4" }), title: "Subscription", rightInfo: jsx(StatusTag, { value: subStatus }), items: [
264
- { label: 'Plan', value: subscriptionStatus.priceName },
265
- { label: 'Period', value: subscriptionStatus.period },
266
- { label: 'Allocated', value: subscriptionStatus.creditsAllocated },
267
- { label: 'SubID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
268
- { label: 'OrderID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
269
- { label: 'PriceID', value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
270
- ] })] })) : (jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsx(PanelHeader, { icon: jsx(DatabaseZapIcon, { className: "size-4" }), title: "Concurrent Base Info", rightInfo: jsx(StatusTag, { value: isRunningTest ? 'pending' : 'idle' }) }), jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Real Browser" }), jsx(CopyableText, { text: fingerprintId || '' })] }), jsxs("div", { className: "space-y-1", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: "Test Override" }), jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsx("div", { className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsx(CopyableText, { text: activeDebugFingerprintId || '' }) }), jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": "Generate new test fingerprint", className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsx(RefreshCcwIcon, { className: "size-4" }) })] })] })] }), jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: "Frontend Prevention Test" }), jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', themeButtonGradientClass, themeButtonGradientHoverClass), children: "Backend Idempotency Test" })] }), jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || 'No test executed yet.' }) })] })), error && (jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxs("div", { className: "flex items-start gap-2", children: [jsx(XIcon, { className: "mt-0.5 size-4 shrink-0" }), jsx("span", { children: error })] }), jsx("button", { type: "button", "aria-label": "Dismiss error", onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsx(XIcon, { className: "size-4" }) })] }))] })] })] }))] }));
268
+ return (jsxs("div", { className: "rounded-lg border border-slate-200/70 bg-white/70 p-3 dark:border-white/10 dark:bg-slate-900/40", children: [jsxs("div", { className: "flex items-center justify-between text-xs font-medium text-slate-600 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center gap-1.5", children: [bucket.icon, jsx("span", { children: bucket.label })] }), jsxs("span", { className: "font-semibold text-slate-700 dark:text-slate-100", children: [formatNumber(bucket.balance), " / ", formatNumber(bucket.total)] })] }), jsx("div", { className: "mt-2 h-1.5 w-full rounded-full bg-slate-200 dark:bg-slate-800", children: jsx("div", { className: "h-full rounded-full bg-linear-to-r from-purple-500 via-pink-500 to-rose-400 transition-[width]", style: { width: `${percent}%` } }) }), jsxs("div", { className: "mt-2 flex items-center justify-between text-[11px] text-slate-500 dark:text-slate-400", children: [jsx("span", { children: formatRangeText(bucket.start, bucket.end, translations) }), jsxs("span", { children: [percent, "%"] })] })] }, bucket.key));
269
+ })) : (jsx(EmptyPlaceholder, { label: translations.placeholders.noCreditsYet, icon: jsx(DatabaseZapIcon, { className: "size-4" }) })) })] }), jsx(PanelSection, { icon: jsx(BellIcon, { className: "size-4" }), title: translations.sections.subscription, rightInfo: jsx(StatusTag, { value: subStatus, translations: translations }), items: [
270
+ { label: translations.labels.plan, value: subscriptionStatus.priceName },
271
+ { label: translations.labels.period, value: subscriptionStatus.period },
272
+ { label: translations.labels.allocated, value: subscriptionStatus.creditsAllocated },
273
+ { label: translations.labels.subId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.paySubscriptionId) || '' }) },
274
+ { label: translations.labels.orderId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.orderId) || '' }) },
275
+ { label: translations.labels.priceId, value: jsx(CopyableText, { text: (xSubscription === null || xSubscription === void 0 ? void 0 : xSubscription.priceId) || '' }) },
276
+ ] })] })) : (jsxs("div", { className: "space-y-3 rounded-xl border border-slate-200/70 bg-white/85 p-4 shadow-sm dark:border-white/12 dark:bg-slate-900/45", children: [jsx(PanelHeader, { icon: jsx(DatabaseZapIcon, { className: "size-4" }), title: translations.sections.concurrentBaseInfo, rightInfo: jsx(StatusTag, { value: isRunningTest ? translations.status.pending : translations.status.idle, translations: translations }) }), jsxs("div", { className: "space-y-2 text-xs text-slate-500 dark:text-slate-300", children: [jsxs("div", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: translations.labels.realBrowser }), jsx(CopyableText, { text: fingerprintId || '' })] }), jsxs("div", { className: "space-y-1", children: [jsx("span", { className: "text-slate-400 dark:text-slate-500", children: translations.labels.testOverride }), jsxs("div", { className: "flex items-center gap-2 py-1", children: [jsx("div", { className: "min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 font-mono text-[0.5rem] sm:text-[0.625rem] md:text-xs leading-tight text-slate-700 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100", children: jsx(CopyableText, { text: activeDebugFingerprintId || '' }) }), jsx("button", { type: "button", disabled: isRunningTest, onClick: regenerateTestFingerprint, "aria-label": translations.actions.generateNewTestFingerprintAriaLabel, className: "inline-flex size-9 items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-slate-950 dark:text-slate-100 dark:hover:bg-slate-900", children: jsx(RefreshCcwIcon, { className: "size-4" }) })] })] })] }), jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: [jsx("button", { type: "button", disabled: isRunningTest, onClick: runContextParallelInitTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', themedGhostButtonClass), children: translations.actions.frontendPreventionTest }), jsx("button", { type: "button", disabled: isRunningTest, onClick: runRawParallelPostTest, className: cn('shrink-0 rounded-full border px-3 py-2 text-xs font-semibold text-white shadow-sm transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50', 'border-transparent', themeButtonGradientClass, themeButtonGradientHoverClass), children: translations.actions.backendIdempotencyTest })] }), jsx("div", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50/80 p-3 dark:border-white/10 dark:bg-slate-950/50", children: jsx("pre", { className: "overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-5 text-slate-600 dark:text-slate-300", children: testResult || translations.placeholders.noTestExecutedYet }) })] })), error && (jsxs("div", { className: "flex items-start justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs text-amber-600 shadow-sm dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200", children: [jsxs("div", { className: "flex items-start gap-2", children: [jsx(XIcon, { className: "mt-0.5 size-4 shrink-0" }), jsx("span", { children: error })] }), jsx("button", { type: "button", "aria-label": translations.actions.dismissErrorAriaLabel, onClick: clearError, className: "shrink-0 rounded-full p-1 text-amber-500 transition hover:bg-amber-100 hover:text-amber-700 dark:text-amber-200 dark:hover:bg-amber-500/10 dark:hover:text-amber-100", children: jsx(XIcon, { className: "size-4" }) })] }))] })] })] }))] }));
271
277
  }
272
278
  /* ==================== 新增辅助组件 ==================== */
273
279
  // 标题行:左侧图标+标题,右侧信息(右对齐)
@@ -294,11 +300,11 @@ function computeProgress(balance, total) {
294
300
  return 0;
295
301
  return Math.min(Math.max(ratio, 0), 1);
296
302
  }
297
- function formatRangeText(start, end) {
303
+ function formatRangeText(start, end, translations) {
298
304
  const safeStart = start && start.trim() ? start : '';
299
305
  const safeEnd = end && end.trim() ? end : '';
300
306
  if (!safeStart && !safeEnd) {
301
- return 'No records';
307
+ return translations.placeholders.noRecords;
302
308
  }
303
309
  if (!safeStart) {
304
310
  return safeEnd;
@@ -308,9 +314,9 @@ function formatRangeText(start, end) {
308
314
  }
309
315
  return `${safeStart} - ${safeEnd}`;
310
316
  }
311
- function StatusTag({ value }) {
317
+ function StatusTag({ value, translations, }) {
312
318
  if (!value)
313
- return jsx("span", { className: "text-slate-400", children: "None" });
319
+ return jsx("span", { className: "text-slate-400", children: translations.placeholders.none });
314
320
  const normalized = value.toLowerCase();
315
321
  const colorMap = {
316
322
  // 绿色:正常/活跃
@@ -333,14 +339,17 @@ function StatusTag({ value }) {
333
339
  const badgeClass = colorMap[normalized] || defaultColor;
334
340
  return (jsx("span", { className: cn('inline-block rounded-full px-2 py-0.5 text-xs capitalize font-medium', badgeClass), children: value }));
335
341
  }
336
- function formatErrorMessage(error) {
342
+ function formatErrorMessage(error, translations) {
337
343
  if (error instanceof Error) {
338
344
  return error.message;
339
345
  }
340
346
  if (typeof error === 'string') {
341
347
  return error;
342
348
  }
343
- return 'Unknown error';
349
+ return translations.placeholders.unknownError;
350
+ }
351
+ function tpl(template, values) {
352
+ return template.replace(/\{(\w+)\}/g, (_, key) => { var _a; return String((_a = values[key]) !== null && _a !== void 0 ? _a : ''); });
344
353
  }
345
354
 
346
355
  export { FingerprintProvider, FingerprintStatus, useFingerprintContext, useFingerprintContextSafe, withFingerprint };
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ interface AdsAlertDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ title: React.ReactNode;
6
+ description: React.ReactNode;
7
+ imgSrc?: string;
8
+ imgHref?: string;
9
+ onCancel?: () => void;
10
+ cancelText?: string;
11
+ confirmText?: string;
12
+ onConfirm?: () => void;
13
+ }
14
+ export declare function AdsAlertDialog({ open, onOpenChange, title, description, imgSrc, imgHref, cancelText, onCancel, confirmText, onConfirm, }: AdsAlertDialogProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};