@allbluecn/admin-app 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 (87) hide show
  1. package/package.json +8 -8
  2. package/src/components/admin/admin-user-menu.tsx +12 -6
  3. package/src/components/admin/developer-log.tsx +42 -44
  4. package/src/components/ai-provider/model-list-editor.tsx +5 -4
  5. package/src/components/ai-provider/provider-form.tsx +63 -64
  6. package/src/components/ai-provider/provider-table.tsx +35 -36
  7. package/src/components/auth/admin-login-form.tsx +23 -24
  8. package/src/components/auth/oauth-section.tsx +3 -5
  9. package/src/components/auth/password-input.tsx +2 -3
  10. package/src/components/common/placeholder-page.tsx +2 -4
  11. package/src/components/component/component-form-dialog.tsx +48 -49
  12. package/src/components/component/component-import-dialog.tsx +5 -5
  13. package/src/components/component/component-table.tsx +13 -14
  14. package/src/components/data-table/data-table-pagination.tsx +4 -6
  15. package/src/components/data-table/table-detail-layout.tsx +2 -3
  16. package/src/components/dependency/changelog-dialog.tsx +3 -4
  17. package/src/components/dependency/columns.tsx +32 -24
  18. package/src/components/dependency/dependency-table.tsx +37 -29
  19. package/src/components/dependency/edit-dialog.tsx +24 -25
  20. package/src/components/knowledge-public/bookmark-form-dialog.tsx +7 -7
  21. package/src/components/knowledge-public/compliance-form-dialog.tsx +13 -13
  22. package/src/components/knowledge-public/legal-form-dialog.tsx +51 -46
  23. package/src/components/layout/sidebar-02/app-sidebar.tsx +33 -34
  24. package/src/components/layout/sidebar-02/nav-main.tsx +1 -1
  25. package/src/components/market/columns.tsx +20 -21
  26. package/src/components/market/market-app-form.tsx +45 -38
  27. package/src/components/market/market-app-list.tsx +16 -17
  28. package/src/components/monitoring/error-list.tsx +18 -19
  29. package/src/components/monitoring/health-cards.tsx +11 -13
  30. package/src/components/monitoring/metrics-stats.tsx +11 -12
  31. package/src/components/monitoring/request-log-table.tsx +8 -9
  32. package/src/components/ui/pagination.tsx +0 -2
  33. package/src/components/ui/sidebar.tsx +1 -1
  34. package/src/components/user/columns.tsx +9 -9
  35. package/src/components/user/settings-activity-log.tsx +39 -29
  36. package/src/components/user/settings-profile.tsx +36 -36
  37. package/src/components/user/user-create-dialog.tsx +4 -4
  38. package/src/components/user/user-filters.tsx +16 -18
  39. package/src/lib/i18n/config.ts +0 -32
  40. package/src/providers/index.tsx +6 -16
  41. package/src/routeTree.gen.ts +1199 -0
  42. package/src/routes/__root.tsx +9 -23
  43. package/src/routes/_nav/apps/route.lazy.tsx +2 -3
  44. package/src/routes/_nav/audit/route.lazy.tsx +10 -11
  45. package/src/routes/_nav/billing/invoices/route.lazy.tsx +6 -7
  46. package/src/routes/_nav/billing/route.lazy.tsx +3 -4
  47. package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +16 -17
  48. package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +9 -10
  49. package/src/routes/_nav/compliance/route.lazy.tsx +34 -35
  50. package/src/routes/_nav/components/route.lazy.tsx +6 -6
  51. package/src/routes/_nav/dashboard/route.lazy.tsx +3 -5
  52. package/src/routes/_nav/dependencies/route.lazy.tsx +7 -8
  53. package/src/routes/_nav/docs/route.lazy.tsx +2 -3
  54. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +40 -41
  55. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +32 -33
  56. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +32 -33
  57. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +24 -31
  58. package/src/routes/_nav/licenses/new/route.lazy.tsx +25 -26
  59. package/src/routes/_nav/licenses/route.lazy.tsx +17 -18
  60. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +26 -27
  61. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +11 -12
  62. package/src/routes/_nav/monitoring/health/route.lazy.tsx +18 -20
  63. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +12 -13
  64. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +13 -15
  65. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +12 -13
  66. package/src/routes/_nav/monitoring/route.lazy.tsx +8 -9
  67. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +55 -57
  68. package/src/routes/_nav/permissions/route.lazy.tsx +48 -50
  69. package/src/routes/_nav/settings/route.lazy.tsx +2 -3
  70. package/src/routes/_nav/sso/route.lazy.tsx +20 -18
  71. package/src/routes/_nav/users/$userId/route.lazy.tsx +86 -82
  72. package/src/routes/_nav/users/route.lazy.tsx +4 -5
  73. package/src/routes/setup/route.lazy.tsx +15 -16
  74. package/src/start.ts +2 -2
  75. package/src/lib/i18n/client.ts +0 -32
  76. package/src/lib/i18n/locales/en/admin-ai.json +0 -128
  77. package/src/lib/i18n/locales/en/admin-billing.json +0 -56
  78. package/src/lib/i18n/locales/en/admin-users.json +0 -88
  79. package/src/lib/i18n/locales/en/common.json +0 -877
  80. package/src/lib/i18n/locales/en/enterprise.json +0 -208
  81. package/src/lib/i18n/locales/zh/admin-ai.json +0 -128
  82. package/src/lib/i18n/locales/zh/admin-billing.json +0 -56
  83. package/src/lib/i18n/locales/zh/admin-users.json +0 -88
  84. package/src/lib/i18n/locales/zh/common.json +0 -877
  85. package/src/lib/i18n/locales/zh/enterprise.json +0 -208
  86. package/src/lib/i18n/server.ts +0 -18
  87. package/src/lib/i18n/types.ts +0 -5
@@ -16,8 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { Link, Outlet, createRootRouteWithContext, HeadContent, Scripts } from '@tanstack/react-router'
19
- import { I18nextProvider } from 'react-i18next'
20
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
21
20
  import globalsCss from '../styles/globals.css?url'
22
21
  import type { RouterContext } from '@/lib/router-context'
23
22
  import type { AppLocale } from '@/lib/i18n/config'
@@ -26,8 +25,7 @@ import { Toaster } from '@/components/ui/sonner'
26
25
  import { getSessionFn } from '@/server/serverFns/auth/session'
27
26
  import { getLocaleFn } from '@/server/serverFns/locale'
28
27
  import { reportClientError } from '@/server/serverFns/monitoring/report-error'
29
- import { useEffect, useMemo } from 'react'
30
- import { createServerI18n } from '@/lib/i18n/server'
28
+ import { useEffect } from 'react'
31
29
 
32
30
  export const Route = createRootRouteWithContext<RouterContext>()({
33
31
  beforeLoad: async () => {
@@ -103,7 +101,7 @@ function RootComponent() {
103
101
  <HeadContent />
104
102
  </head>
105
103
  <body className="w-full h-full min-h-screen antialiased">
106
- <Providers locale={locale}>
104
+ <Providers>
107
105
  <Outlet />
108
106
  <Toaster position="top-center" />
109
107
  </Providers>
@@ -114,38 +112,26 @@ function RootComponent() {
114
112
  }
115
113
 
116
114
  function NotFoundPage() {
117
- const i18n = useMemo(() => createServerI18n('zh'), [])
118
- return (
119
- <I18nextProvider i18n={i18n}>
120
- <NotFoundPageInner />
121
- </I18nextProvider>
122
- )
115
+ return <NotFoundPageInner />
123
116
  }
124
117
 
125
118
  function NotFoundPageInner() {
126
- const { t } = useTranslation('common')
127
119
  return (
128
120
  <div className="flex h-screen w-full items-center justify-center">
129
121
  <div className="text-center">
130
122
  <h1 className="text-6xl font-bold">404</h1>
131
- <p className="mt-4 text-muted-foreground">{t('errorPage.notFound')}</p>
132
- <Link to="/login" className="mt-6 inline-block text-primary hover:underline">{t('errorPage.goLogin')}</Link>
123
+ <p className="mt-4 text-muted-foreground">{m.common_errorPage_notFound()}</p>
124
+ <Link to="/login" className="mt-6 inline-block text-primary hover:underline">{m.common_errorPage_goLogin()}</Link>
133
125
  </div>
134
126
  </div>
135
127
  )
136
128
  }
137
129
 
138
130
  function ErrorBoundary({ error }: { error: Error }) {
139
- const i18n = useMemo(() => createServerI18n('zh'), [])
140
- return (
141
- <I18nextProvider i18n={i18n}>
142
- <ErrorBoundaryInner error={error} />
143
- </I18nextProvider>
144
- )
131
+ return <ErrorBoundaryInner error={error} />
145
132
  }
146
133
 
147
134
  function ErrorBoundaryInner({ error }: { error: Error }) {
148
- const { t } = useTranslation('common')
149
135
  useEffect(() => {
150
136
  reportClientError({
151
137
  data: {
@@ -161,9 +147,9 @@ function ErrorBoundaryInner({ error }: { error: Error }) {
161
147
  return (
162
148
  <div className="flex h-screen w-full items-center justify-center">
163
149
  <div className="text-center">
164
- <h1 className="text-4xl font-bold text-destructive">{t('errorPage.error')}</h1>
150
+ <h1 className="text-4xl font-bold text-destructive">{m.common_errorPage_error()}</h1>
165
151
  <p className="mt-4 text-muted-foreground">{error.message}</p>
166
- <Link to="/login" className="mt-6 inline-block text-primary hover:underline">{t('errorPage.goLogin')}</Link>
152
+ <Link to="/login" className="mt-6 inline-block text-primary hover:underline">{m.common_errorPage_goLogin()}</Link>
167
153
  </div>
168
154
  </div>
169
155
  )
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { Package2 } from 'lucide-react'
21
21
  import { PlaceholderPage } from '@/components/common/placeholder-page'
22
22
 
@@ -25,6 +25,5 @@ export const Route = createLazyFileRoute('/_nav/apps')({
25
25
  })
26
26
 
27
27
  function AppsPage() {
28
- const { t } = useTranslation('common')
29
- return <PlaceholderPage title={t('apps.title')} icon={Package2} />
28
+ return <PlaceholderPage title={m.common_apps_title()} icon={Package2} />
30
29
  }
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import type React from 'react'
21
21
  import { Badge } from '@/components/ui/badge'
22
22
 
@@ -30,7 +30,6 @@ export const Route = createLazyFileRoute('/_nav/audit')({
30
30
  })
31
31
 
32
32
  function AuditLogPage(): React.ReactElement {
33
- const { t } = useTranslation('enterprise')
34
33
  const { entries, total } = Route.useLoaderData() as {
35
34
  entries: Array<{
36
35
  id: string
@@ -50,25 +49,25 @@ function AuditLogPage(): React.ReactElement {
50
49
  return (
51
50
  <div className="space-y-4">
52
51
  <div>
53
- <h1 className="text-2xl font-bold">{t('audit.title')}</h1>
54
- <p className="text-sm text-muted-foreground mt-1">{t('audit.subtitle', { count: total })}</p>
52
+ <h1 className="text-2xl font-bold">{m.enterprise_audit_title()}</h1>
53
+ <p className="text-sm text-muted-foreground mt-1">{m.enterprise_audit_subtitle({ count: total })}</p>
55
54
  </div>
56
55
 
57
56
  {entries.length === 0 ? (
58
57
  <div className="rounded-lg border border-border p-8 text-center text-muted-foreground">
59
- {t('audit.empty')}
58
+ {m.enterprise_audit_empty()}
60
59
  </div>
61
60
  ) : (
62
61
  <div className="rounded-lg border border-border overflow-hidden">
63
62
  <table className="w-full text-sm">
64
63
  <thead>
65
64
  <tr className="border-b border-border bg-muted/50">
66
- <th className="p-3 text-left font-medium">{t('audit.table.timestamp')}</th>
67
- <th className="p-3 text-left font-medium">{t('audit.table.action')}</th>
68
- <th className="p-3 text-left font-medium">{t('audit.table.resource')}</th>
69
- <th className="p-3 text-left font-medium">{t('audit.table.user')}</th>
70
- <th className="p-3 text-left font-medium">{t('audit.table.ip')}</th>
71
- <th className="p-3 text-left font-medium">{t('audit.table.ua')}</th>
65
+ <th className="p-3 text-left font-medium">{m.enterprise_audit_timestamp()}</th>
66
+ <th className="p-3 text-left font-medium">{m.enterprise_audit_action()}</th>
67
+ <th className="p-3 text-left font-medium">{m.enterprise_audit_target()}</th>
68
+ <th className="p-3 text-left font-medium">{m.enterprise_audit_actor()}</th>
69
+ <th className="p-3 text-left font-medium">{m.enterprise_audit_ip()}</th>
70
+ <th className="p-3 text-left font-medium">{m.enterprise_audit_userAgent()}</th>
72
71
  </tr>
73
72
  </thead>
74
73
  <tbody>
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { formatDate } from 'date-fns'
21
21
  import type React from 'react'
22
22
 
@@ -30,7 +30,6 @@ export const Route = createLazyFileRoute('/_nav/billing/invoices')({
30
30
  })
31
31
 
32
32
  function InvoicesList(): React.ReactElement {
33
- const { t } = useTranslation('admin-billing')
34
33
  const { items } = Route.useLoaderData() as {
35
34
  items: Array<{
36
35
  id: string
@@ -49,16 +48,16 @@ function InvoicesList(): React.ReactElement {
49
48
  <thead>
50
49
  <tr className="bg-muted/50">
51
50
  <th className="text-left px-4 py-2 font-medium">
52
- {t('subscription.workspace')}
51
+ {m['admin-billing_subscription_workspace']()}
53
52
  </th>
54
53
  <th className="text-left px-4 py-2 font-medium">
55
- {t('subscription.invoiceDate')}
54
+ {m['admin-billing_subscription_invoiceDate']()}
56
55
  </th>
57
56
  <th className="text-right px-4 py-2 font-medium">
58
- {t('subscription.invoiceAmount')}
57
+ {m['admin-billing_subscription_invoiceAmount']()}
59
58
  </th>
60
59
  <th className="text-center px-4 py-2 font-medium">
61
- {t('subscription.invoiceStatus')}
60
+ {m['admin-billing_subscription_invoiceStatus']()}
62
61
  </th>
63
62
  </tr>
64
63
  </thead>
@@ -72,7 +71,7 @@ function InvoicesList(): React.ReactElement {
72
71
  {formatDate(inv.periodStart, 'yyyy-MM-dd')}
73
72
  </td>
74
73
  <td className="px-4 py-2 text-right">
75
- {t(`invoice.${inv.currency === 'usd' ? 'usd' : 'cny'}`)}
74
+ {(inv.currency === 'usd' ? m['admin-billing_invoice_usd']() : m['admin-billing_invoice_cny']())}
76
75
  {(inv.amountDue / 100).toFixed(2)}
77
76
  </td>
78
77
  <td className="px-4 py-2 text-center">{inv.status}</td>
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute, Outlet, useMatches } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import type React from 'react'
21
21
 
22
22
  export const Route = createLazyFileRoute('/_nav/billing')({
@@ -29,17 +29,16 @@ export const Route = createLazyFileRoute('/_nav/billing')({
29
29
  })
30
30
 
31
31
  function BillingLazyLayout() {
32
- const { t } = useTranslation('admin-billing')
33
32
  const matches = useMatches()
34
33
  const lastMatch = matches[matches.length - 1]
35
34
  const isIndex = lastMatch?.routeId === '/_nav/billing'
36
35
 
37
36
  return (
38
37
  <div className="space-y-4 p-6">
39
- <h1 className="text-2xl font-bold">{t('page.title')}</h1>
38
+ <h1 className="text-2xl font-bold">{m['admin-billing_page_title']()}</h1>
40
39
  {isIndex && (
41
40
  <p className="text-sm text-muted-foreground">
42
- {t('page.selectSubmenu')}
41
+ {m['admin-billing_page_selectSubmenu']()}
43
42
  </p>
44
43
  )}
45
44
  <Outlet />
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute, Link } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { formatDate } from 'date-fns'
21
21
  import type React from 'react'
22
22
 
@@ -30,7 +30,6 @@ export const Route = createLazyFileRoute('/_nav/billing/subscriptions/$subscript
30
30
  })
31
31
 
32
32
  function SubscriptionDetail(): React.ReactElement {
33
- const { t } = useTranslation('admin-billing')
34
33
  const detail = Route.useLoaderData() as {
35
34
  id: string
36
35
  workspace?: { name?: string } | null
@@ -50,46 +49,46 @@ function SubscriptionDetail(): React.ReactElement {
50
49
  } | null
51
50
 
52
51
  if (!detail) {
53
- return <div className="text-muted-foreground">{t('subscription.notFound')}</div>
52
+ return <div className="text-muted-foreground">{m['admin-billing_subscription_notFound']()}</div>
54
53
  }
55
54
 
56
55
  return (
57
56
  <div className="space-y-6">
58
57
  <div className="flex items-center gap-4">
59
58
  <Link to="/billing/subscriptions" className="text-primary hover:underline">
60
- ← {t('subscription.back')}
59
+ ← {m['admin-billing_subscription_back']()}
61
60
  </Link>
62
- <h2 className="text-xl font-bold">{t('subscription.detailTitle')}</h2>
61
+ <h2 className="text-xl font-bold">{m['admin-billing_subscription_detailTitle']()}</h2>
63
62
  </div>
64
63
 
65
64
  <div className="grid grid-cols-2 gap-4 rounded-lg border border-border p-4">
66
65
  <div>
67
66
  <p className="text-sm text-muted-foreground">
68
- {t('subscription.workspace')}
67
+ {m['admin-billing_subscription_workspace']()}
69
68
  </p>
70
69
  <p className="font-medium">{detail.workspace?.name || '—'}</p>
71
70
  </div>
72
71
  <div>
73
72
  <p className="text-sm text-muted-foreground">
74
- {t('subscription.plan')}
73
+ {m['admin-billing_subscription_plan']()}
75
74
  </p>
76
75
  <p className="font-medium">{detail.plan?.name || '—'}</p>
77
76
  </div>
78
77
  <div>
79
78
  <p className="text-sm text-muted-foreground">
80
- {t('subscription.status')}
79
+ {m['admin-billing_subscription_status']()}
81
80
  </p>
82
81
  <p className="font-medium">{detail.status}</p>
83
82
  </div>
84
83
  <div>
85
84
  <p className="text-sm text-muted-foreground">
86
- {t('subscription.seats')}
85
+ {m['admin-billing_subscription_seats']()}
87
86
  </p>
88
87
  <p className="font-medium">{detail.seats}</p>
89
88
  </div>
90
89
  <div>
91
90
  <p className="text-sm text-muted-foreground">
92
- {t('subscription.periodStart')}
91
+ {m['admin-billing_subscription_periodStart']()}
93
92
  </p>
94
93
  <p className="font-medium">
95
94
  {detail.currentPeriodStart
@@ -99,7 +98,7 @@ function SubscriptionDetail(): React.ReactElement {
99
98
  </div>
100
99
  <div>
101
100
  <p className="text-sm text-muted-foreground">
102
- {t('subscription.periodEnd')}
101
+ {m['admin-billing_subscription_periodEnd']()}
103
102
  </p>
104
103
  <p className="font-medium">
105
104
  {detail.currentPeriodEnd
@@ -110,7 +109,7 @@ function SubscriptionDetail(): React.ReactElement {
110
109
  {detail.providerSubscriptionId && (
111
110
  <div>
112
111
  <p className="text-sm text-muted-foreground">
113
- {t('subscription.providerSubscription')}
112
+ {m['admin-billing_subscription_providerSubscription']()}
114
113
  </p>
115
114
  <p className="font-medium">{detail.providerSubscriptionId}</p>
116
115
  </div>
@@ -120,20 +119,20 @@ function SubscriptionDetail(): React.ReactElement {
120
119
  {detail.invoices.length > 0 && (
121
120
  <div>
122
121
  <h3 className="text-lg font-semibold mb-4">
123
- {t('subscription.invoicesTitle')}
122
+ {m['admin-billing_subscription_invoicesTitle']()}
124
123
  </h3>
125
124
  <div className="rounded-lg border border-border overflow-hidden">
126
125
  <table className="w-full text-sm">
127
126
  <thead>
128
127
  <tr className="bg-muted/50">
129
128
  <th className="text-left px-4 py-2 font-medium">
130
- {t('subscription.invoiceDate')}
129
+ {m['admin-billing_subscription_invoiceDate']()}
131
130
  </th>
132
131
  <th className="text-right px-4 py-2 font-medium">
133
- {t('subscription.invoiceAmount')}
132
+ {m['admin-billing_subscription_invoiceAmount']()}
134
133
  </th>
135
134
  <th className="text-center px-4 py-2 font-medium">
136
- {t('subscription.invoiceStatus')}
135
+ {m['admin-billing_subscription_invoiceStatus']()}
137
136
  </th>
138
137
  </tr>
139
138
  </thead>
@@ -144,7 +143,7 @@ function SubscriptionDetail(): React.ReactElement {
144
143
  {formatDate(inv.periodStart, 'yyyy-MM-dd')}
145
144
  </td>
146
145
  <td className="px-4 py-2 text-right">
147
- {t(`invoice.${inv.currency === 'usd' ? 'usd' : 'cny'}`)}
146
+ {(inv.currency === 'usd' ? m['admin-billing_invoice_usd']() : m['admin-billing_invoice_cny']())}
148
147
  {(inv.amountDue / 100).toFixed(2)}
149
148
  </td>
150
149
  <td className="px-4 py-2 text-center">{inv.status}</td>
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute, Link } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { formatDate } from 'date-fns'
21
21
  import type React from 'react'
22
22
 
@@ -30,7 +30,6 @@ export const Route = createLazyFileRoute('/_nav/billing/subscriptions')({
30
30
  })
31
31
 
32
32
  function SubscriptionsList(): React.ReactElement {
33
- const { t } = useTranslation('admin-billing')
34
33
  const { items, total } = Route.useLoaderData() as {
35
34
  items: Array<{
36
35
  id: string
@@ -47,7 +46,7 @@ function SubscriptionsList(): React.ReactElement {
47
46
  <div>
48
47
  <div className="mb-4 flex items-center justify-between">
49
48
  <p className="text-sm text-muted-foreground">
50
- {t('subscription.count', { count: total })}
49
+ {m['admin-billing_subscription_count']({ count: total })}
51
50
  </p>
52
51
  </div>
53
52
  <div className="rounded-lg border border-border overflow-hidden">
@@ -55,22 +54,22 @@ function SubscriptionsList(): React.ReactElement {
55
54
  <thead>
56
55
  <tr className="bg-muted/50">
57
56
  <th className="text-left px-4 py-2 font-medium">
58
- {t('subscription.workspace')}
57
+ {m['admin-billing_subscription_workspace']()}
59
58
  </th>
60
59
  <th className="text-left px-4 py-2 font-medium">
61
- {t('subscription.plan')}
60
+ {m['admin-billing_subscription_plan']()}
62
61
  </th>
63
62
  <th className="text-center px-4 py-2 font-medium">
64
- {t('subscription.status')}
63
+ {m['admin-billing_subscription_status']()}
65
64
  </th>
66
65
  <th className="text-center px-4 py-2 font-medium">
67
- {t('subscription.seats')}
66
+ {m['admin-billing_subscription_seats']()}
68
67
  </th>
69
68
  <th className="text-left px-4 py-2 font-medium">
70
- {t('subscription.expires')}
69
+ {m['admin-billing_subscription_expires']()}
71
70
  </th>
72
71
  <th className="text-right px-4 py-2 font-medium">
73
- {t('subscription.action')}
72
+ {m['admin-billing_subscription_action']()}
74
73
  </th>
75
74
  </tr>
76
75
  </thead>
@@ -100,7 +99,7 @@ function SubscriptionsList(): React.ReactElement {
100
99
  params={{ subscriptionId: sub.id }}
101
100
  className="text-primary hover:underline"
102
101
  >
103
- {t('subscription.detail')}
102
+ {m['admin-billing_subscription_detail']()}
104
103
  </Link>
105
104
  </td>
106
105
  </tr>
@@ -16,9 +16,9 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
+ import { m } from '@/paraglide/messages.js'
19
20
  import { useQuery, useQueryClient } from '@tanstack/react-query'
20
21
  import { useServerFn } from '@tanstack/react-start'
21
- import { useTranslation } from 'react-i18next'
22
22
  import { toast } from 'sonner'
23
23
  import type React from 'react'
24
24
  import { listComplianceExportsFn, executeComplianceJobFn } from '@/server/serverFns/compliance'
@@ -38,7 +38,6 @@ export const Route = createLazyFileRoute('/_nav/compliance')({
38
38
  const QUERY_KEY = ['compliance-exports']
39
39
 
40
40
  function CompliancePage(): React.ReactElement {
41
- const { t } = useTranslation('enterprise')
42
41
  const queryClient = useQueryClient()
43
42
 
44
43
  const { data } = useQuery({
@@ -55,68 +54,68 @@ function CompliancePage(): React.ReactElement {
55
54
  const handleExecute = async (jobId: string) => {
56
55
  try {
57
56
  await executeFn({ data: { jobId } })
58
- toast.success(t('compliance.export.toast.executed'))
57
+ toast.success(m.enterprise_compliance_export_toast_executed())
59
58
  await queryClient.invalidateQueries({ queryKey: QUERY_KEY })
60
59
  } catch (err) {
61
- toast.error(err instanceof Error ? err.message : t('compliance.export.toast.executeFailed'))
60
+ toast.error(err instanceof Error ? err.message : m.enterprise_compliance_export_toast_executeFailed())
62
61
  }
63
62
  }
64
63
 
65
64
  const isExpired = (job: { status: string; expiresAt: string | Date | null }) =>
66
65
  job.status === 'completed' && job.expiresAt != null && new Date(job.expiresAt) < new Date()
67
66
 
68
- const STATUS_KEYS: Record<string, string> = {
69
- pending: 'pending',
70
- processing: 'processing',
71
- completed: 'completed',
72
- failed: 'failed',
73
- expired: 'expired',
67
+ const STATUS_LABELS: Record<string, () => string> = {
68
+ pending: m.enterprise_compliance_export_status_pending,
69
+ processing: m.enterprise_compliance_export_status_processing,
70
+ completed: m.enterprise_compliance_export_status_completed,
71
+ failed: m.enterprise_compliance_export_status_failed,
72
+ expired: m.enterprise_compliance_export_status_expired,
74
73
  }
75
74
 
76
- const SCOPE_KEYS: Record<string, string> = {
77
- user: 'user',
78
- workspace: 'workspace',
75
+ const SCOPE_LABELS: Record<string, () => string> = {
76
+ user: m.enterprise_compliance_export_scope_user,
77
+ workspace: m.enterprise_compliance_export_scope_workspace,
79
78
  }
80
79
 
81
- const TYPE_KEYS: Record<string, string> = {
82
- export: 'export',
83
- delete: 'delete',
80
+ const TYPE_LABELS: Record<string, () => string> = {
81
+ export: m.enterprise_compliance_export_type_export,
82
+ delete: m.enterprise_compliance_export_type_delete,
84
83
  }
85
84
 
86
85
  return (
87
86
  <div className="space-y-4">
88
87
  <div>
89
- <h1 className="text-2xl font-bold">{t('compliance.export.title')}</h1>
90
- <p className="text-sm text-muted-foreground mt-1">{t('compliance.export.subtitle', { count: total })}</p>
88
+ <h1 className="text-2xl font-bold">{m.enterprise_compliance_export_title()}</h1>
89
+ <p className="text-sm text-muted-foreground mt-1">{m.enterprise_compliance_export_subtitle({ count: total })}</p>
91
90
  </div>
92
91
 
93
92
  {items.length === 0 ? (
94
93
  <div className="rounded-lg border border-border p-8 text-center text-muted-foreground">
95
- {t('compliance.export.empty')}
94
+ {m.enterprise_compliance_export_empty()}
96
95
  </div>
97
96
  ) : (
98
97
  <div className="rounded-lg border border-border overflow-hidden">
99
98
  <table className="w-full text-sm">
100
99
  <thead>
101
100
  <tr className="border-b border-border bg-muted/50">
102
- <th className="p-3 text-left font-medium">{t('compliance.export.table.type')}</th>
103
- <th className="p-3 text-left font-medium">{t('compliance.export.table.scope')}</th>
104
- <th className="p-3 text-left font-medium">{t('compliance.export.table.target')}</th>
105
- <th className="p-3 text-left font-medium">{t('compliance.export.table.status')}</th>
106
- <th className="p-3 text-left font-medium">{t('compliance.export.table.requester')}</th>
107
- <th className="p-3 text-left font-medium">{t('compliance.export.table.createdAt')}</th>
108
- <th className="p-3 text-left font-medium">{t('compliance.export.table.error')}</th>
109
- <th className="p-3 text-left font-medium">{t('compliance.export.table.actions')}</th>
101
+ <th className="p-3 text-left font-medium">{m.enterprise_compliance_export_table_type()}</th>
102
+ <th className="p-3 text-left font-medium">{m.enterprise_compliance_export_table_scope()}</th>
103
+ <th className="p-3 text-left font-medium">{m.enterprise_compliance_export_table_target()}</th>
104
+ <th className="p-3 text-left font-medium">{m.enterprise_compliance_export_table_status()}</th>
105
+ <th className="p-3 text-left font-medium">{m.enterprise_compliance_export_table_requester()}</th>
106
+ <th className="p-3 text-left font-medium">{m.enterprise_compliance_export_table_createdAt()}</th>
107
+ <th className="p-3 text-left font-medium">{m.enterprise_compliance_export_table_error()}</th>
108
+ <th className="p-3 text-left font-medium">{m.enterprise_compliance_export_table_actions()}</th>
110
109
  </tr>
111
110
  </thead>
112
111
  <tbody>
113
- {items.map((item) => (
112
+ {items.map((item: any) => (
114
113
  <tr key={item.id} className="border-b border-border last:border-0">
115
114
  <td className="p-3">
116
- <Badge>{t('compliance.export.type.' + (TYPE_KEYS[item.type] ?? item.type))}</Badge>
115
+ <Badge>{(TYPE_LABELS[item.type] ?? (() => item.type))()}</Badge>
117
116
  </td>
118
117
  <td className="p-3">
119
- {t('compliance.export.scope.' + (SCOPE_KEYS[item.scope] ?? item.scope))}
118
+ {(SCOPE_LABELS[item.scope] ?? (() => item.scope))()}
120
119
  </td>
121
120
  <td className="p-3">{item.targetId.slice(0, 12)}</td>
122
121
  <td className="p-3">
@@ -129,7 +128,7 @@ function CompliancePage(): React.ReactElement {
129
128
  : 'bg-muted text-muted-foreground'
130
129
  }`}
131
130
  >
132
- {t('compliance.export.status.' + (STATUS_KEYS[item.status] ?? item.status))}
131
+ {(STATUS_LABELS[item.status] ?? (() => item.status))()}
133
132
  </span>
134
133
  </td>
135
134
  <td className="p-3">{item.requestedBy?.slice(0, 8) || '—'}</td>
@@ -147,13 +146,13 @@ function CompliancePage(): React.ReactElement {
147
146
  size="sm"
148
147
  onClick={() => handleExecute(item.id)}
149
148
  >
150
- {t('compliance.export.actions.execute')}
149
+ {m.enterprise_compliance_export_actions_execute()}
151
150
  </Button>
152
151
  )}
153
152
  {item.status === 'processing' && (
154
153
  <span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
155
154
  <Loader2 className="size-3 animate-spin" />
156
- {t('compliance.export.actions.processing')}
155
+ {m.enterprise_compliance_export_actions_processing()}
157
156
  </span>
158
157
  )}
159
158
  {item.status === 'completed' && !isExpired(item) && (
@@ -162,13 +161,13 @@ function CompliancePage(): React.ReactElement {
162
161
  download={`allblue-export-${item.id}.json`}
163
162
  >
164
163
  <Button variant="outline" size="sm" asChild={false}>
165
- <span>{t('compliance.export.actions.download')}</span>
164
+ <span>{m.enterprise_compliance_export_actions_download()}</span>
166
165
  </Button>
167
166
  </a>
168
167
  )}
169
168
  {isExpired(item) && (
170
169
  <span className="inline-flex items-center text-xs text-muted-foreground">
171
- {t('compliance.export.actions.expired')}
170
+ {m.enterprise_compliance_export_actions_expired()}
172
171
  </span>
173
172
  )}
174
173
  </div>
@@ -132,8 +132,8 @@ function ComponentsPage() {
132
132
  <Search className="absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
133
133
  <Input
134
134
  value={searchInput}
135
- onChange={(e) => setSearchInput(e.target.value)}
136
- onKeyDown={(e) => { if (e.key === 'Enter') handleSearchSubmit() }}
135
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchInput(e.target.value)}
136
+ onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key === 'Enter') handleSearchSubmit() }}
137
137
  placeholder="搜索组件..."
138
138
  className="pl-8"
139
139
  />
@@ -142,8 +142,8 @@ function ComponentsPage() {
142
142
 
143
143
  <ComponentTable
144
144
  items={items as unknown as ComponentItem[]}
145
- onEdit={(item) => setEditing(item)}
146
- onDelete={(item) => setDeleting(item)}
145
+ onEdit={(item: any) => setEditing(item)}
146
+ onDelete={(item: any) => setDeleting(item)}
147
147
  />
148
148
 
149
149
  <ComponentFormDialog
@@ -154,7 +154,7 @@ function ComponentsPage() {
154
154
  />
155
155
  <ComponentFormDialog
156
156
  open={!!editing}
157
- onOpenChange={(o) => !o && setEditing(null)}
157
+ onOpenChange={(o: boolean) => !o && setEditing(null)}
158
158
  component={editing}
159
159
  onSuccess={() => router.invalidate()}
160
160
  />
@@ -165,7 +165,7 @@ function ComponentsPage() {
165
165
  onSuccess={() => router.invalidate()}
166
166
  />
167
167
 
168
- <AlertDialog open={!!deleting} onOpenChange={(o) => !o && setDeleting(null)}>
168
+ <AlertDialog open={!!deleting} onOpenChange={(o: boolean) => !o && setDeleting(null)}>
169
169
  <AlertDialogContent>
170
170
  <AlertDialogHeader>
171
171
  <AlertDialogTitle>删除组件「{deleting?.name}」?</AlertDialogTitle>
@@ -16,18 +16,16 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
20
-
19
+ import { m } from '@/paraglide/messages.js'
21
20
  export const Route = createLazyFileRoute('/_nav/dashboard')({
22
21
  component: DashboardPage,
23
22
  })
24
23
 
25
24
  function DashboardPage() {
26
- const { t } = useTranslation('common')
27
25
  return (
28
26
  <div className="p-6">
29
- <h2 className="text-2xl font-bold">{t('dashboard.title')}</h2>
30
- <p className="mt-2 text-muted-foreground">{t('dashboard.subtitle')}</p>
27
+ <h2 className="text-2xl font-bold">{m.common_dashboard_title()}</h2>
28
+ <p className="mt-2 text-muted-foreground">{m.common_dashboard_subtitle()}</p>
31
29
  </div>
32
30
  )
33
31
  }