@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,8 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { LogOut, User, Mail, Users, BarChart3, Shield, Settings, ArrowRight, ArrowLeft, Target } from "lucide-react";
19
+ import { m } from '@/paraglide/messages.js'
19
20
  import { useState } from "react";
20
- import { useTranslation } from 'react-i18next'
21
21
  import { signIn, signOut } from "@/lib/auth-client";
22
22
  import { Button } from "@/components/ui/button";
23
23
  import { Checkbox } from "@/components/ui/checkbox";
@@ -61,7 +61,6 @@ function maskEmail(email: string): string {
61
61
  }
62
62
 
63
63
  export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
64
- const { t } = useTranslation('common')
65
64
  const [password, setPassword] = useState("");
66
65
  const [email, setEmail] = useState("");
67
66
  const [stayLoggedIn, setStayLoggedIn] = useState(false);
@@ -71,15 +70,15 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
71
70
  e.preventDefault();
72
71
 
73
72
  if (!isAdmin && !email) {
74
- toast.error(t('login.error.enterEmail'));
73
+ toast.error(m.common_login_error_enterEmail());
75
74
  return;
76
75
  }
77
76
  if (!isAdmin && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(email)) {
78
- toast.error(t('login.error.validEmail'));
77
+ toast.error(m.common_login_error_validEmail());
79
78
  return;
80
79
  }
81
80
  if (!password) {
82
- toast.error(t('login.error.enterPassword'));
81
+ toast.error(m.common_login_error_enterPassword());
83
82
  return;
84
83
  }
85
84
 
@@ -99,7 +98,7 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
99
98
  // form.submit() 已触发导航,以下代码在页面卸载前执行
100
99
  // 若 csrf fetch 失败会抛错进入 catch
101
100
  } catch {
102
- toast.error(t('login.error.invalidCredentials'));
101
+ toast.error(m.common_login_error_invalidCredentials());
103
102
  setLoading(false);
104
103
  }
105
104
  }
@@ -122,7 +121,7 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
122
121
  <div className="mx-auto flex h-16 w-16 items-center justify-center rounded-lg bg-primary">
123
122
  <Target className="h-10 w-10 text-primary-foreground" />
124
123
  </div>
125
- <h1 className="text-3xl font-semibold">{t('login.adminPanel')}</h1>
124
+ <h1 className="text-3xl font-semibold">{m.common_login_adminPanel()}</h1>
126
125
  <Card className="h-16 w-full bg-muted/50 border">
127
126
  <CardContent className="flex h-full items-center gap-3 px-3 py-3">
128
127
  <UserAvatar
@@ -133,30 +132,30 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
133
132
  size="lg"
134
133
  />
135
134
  <div className="min-w-0 text-left">
136
- <p className="truncate text-base font-medium">{user?.name || user?.email || t('login.admin')}</p>
135
+ <p className="truncate text-base font-medium">{user?.name || user?.email || m.common_login_admin()}</p>
137
136
  <p className="truncate text-xm text-muted-foreground">{maskEmail(user?.email || "")}</p>
138
137
  </div>
139
138
  </CardContent>
140
139
  </Card>
141
140
  </div>
142
141
  <form onSubmit={handleLogin} className="space-y-6">
143
- <PasswordInput label={t('login.password.label')} placeholder={t('login.password.placeholder')} value={password} onChange={(e) => setPassword(e.target.value)} />
142
+ <PasswordInput label={m.common_login_password_label()} placeholder={m.common_login_password_placeholder()} value={password} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)} />
144
143
  <Field orientation="horizontal" className="gap-2">
145
144
  <FieldLabel>
146
- <Checkbox checked={stayLoggedIn} onCheckedChange={(v) => setStayLoggedIn(v === true)} />
147
- <span className="text-sm">{t('login.rememberMe')}</span>
145
+ <Checkbox checked={stayLoggedIn} onCheckedChange={(v: boolean) => setStayLoggedIn(v === true)} />
146
+ <span className="text-sm">{m.common_login_rememberMe()}</span>
148
147
  </FieldLabel>
149
148
  </Field>
150
149
  <RainbowButton type="submit" className="w-full" disabled={loading}>
151
- {loading ? t('login.loading') : t('login.loginAdmin')}
150
+ {loading ? m.common_login_loading() : m.common_login_loginAdmin()}
152
151
  <ArrowRight className="ml-2 h-4 w-4" />
153
152
  </RainbowButton>
154
153
  <div className="flex gap-2">
155
154
  <Button type="button" variant="outline" className="flex-1" onClick={handleSwitchAccount}>
156
- <Users className="w-4 h-4 mr-2" />{t('login.switchAccount')}
155
+ <Users className="w-4 h-4 mr-2" />{m.common_login_switchAccount()}
157
156
  </Button>
158
157
  <Button type="button" variant="outline" className="flex-1" onClick={handleLogout}>
159
- <LogOut className="w-4 h-4 mr-2" />{t('login.logout')}
158
+ <LogOut className="w-4 h-4 mr-2" />{m.common_login_logout()}
160
159
  </Button>
161
160
  </div>
162
161
  </form>
@@ -171,16 +170,16 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
171
170
  <AuthLayout>
172
171
  <AuthLayoutLeft>
173
172
  <div className="space-y-4 text-center">
174
- <AuthHeader title={t('login.noPermission')} titleClassName="text-destructive" />
173
+ <AuthHeader title={m.common_login_noPermission()} titleClassName="text-destructive" />
175
174
  <p className="text-muted-foreground">
176
- {t('login.notAdmin', { name: user.name || user.email })}
175
+ {m.common_login_notAdmin({ name: user.name || user.email || '' })}
177
176
  </p>
178
177
  <Button className="w-full" onClick={() => window.open(`${import.meta.env.VITE_WEB_URL ?? '/'}dashboard`, "_self")}>
179
178
  <ArrowLeft className="h-4 w-4" />
180
- {t('login.backToWorkspace')}
179
+ {m.common_login_backToWorkspace()}
181
180
  </Button>
182
181
  <Button variant="outline" className="w-full" onClick={handleSwitchAccount}>
183
- {t('login.switchAndLogin')}
182
+ {m.common_login_switchAndLogin()}
184
183
  </Button>
185
184
  </div>
186
185
  </AuthLayoutLeft>
@@ -192,18 +191,18 @@ export default function AdminLoginForm({ user, isAdmin }: AdminLoginFormProps) {
192
191
  return (
193
192
  <AuthLayout>
194
193
  <AuthLayoutLeft>
195
- <AuthHeader title={t('login.adminPanel')} subtitle={t('login.subtitle')} />
194
+ <AuthHeader title={m.common_login_adminPanel()} subtitle={m.common_login_subtitle()} />
196
195
  <form onSubmit={handleLogin} className="space-y-6">
197
- <IconInput icon={Mail} label={t('login.email.label')} placeholder={t('login.email.placeholder')} value={email} onChange={(e) => setEmail(e.target.value)} />
198
- <PasswordInput label={t('login.password.label')} placeholder={t('login.password.placeholder')} value={password} onChange={(e) => setPassword(e.target.value)} />
196
+ <IconInput icon={Mail} label={m.common_login_email_label()} placeholder={m.common_login_email_placeholder()} value={email} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)} />
197
+ <PasswordInput label={m.common_login_password_label()} placeholder={m.common_login_password_placeholder()} value={password} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)} />
199
198
  <Field orientation="horizontal" className="gap-2">
200
199
  <FieldLabel>
201
- <Checkbox checked={stayLoggedIn} onCheckedChange={(v) => setStayLoggedIn(v === true)} />
202
- <span className="text-sm">{t('login.rememberMe')}</span>
200
+ <Checkbox checked={stayLoggedIn} onCheckedChange={(v: boolean) => setStayLoggedIn(v === true)} />
201
+ <span className="text-sm">{m.common_login_rememberMe()}</span>
203
202
  </FieldLabel>
204
203
  </Field>
205
204
  <RainbowButton type="submit" className="w-full" disabled={loading}>
206
- {loading ? t('login.loggingIn') : t('login.loginAdmin')}
205
+ {loading ? m.common_login_loggingIn() : m.common_login_loginAdmin()}
207
206
  <ArrowRight className="ml-2 h-4 w-4" />
208
207
  </RainbowButton>
209
208
  </form>
@@ -1,3 +1,4 @@
1
+ import { m } from '@/paraglide/messages.js'
1
2
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
3
  // AllBlue - 理想之海
3
4
  // Copyright (C) 2026 AllBlue Contributors
@@ -15,8 +16,6 @@
15
16
  // You should have received a copy of the GNU Affero General Public License
16
17
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18
 
18
- import { useTranslation } from 'react-i18next'
19
-
20
19
  interface OAuthSectionProps {
21
20
  providers: { name: string; icon: React.ReactNode }[]
22
21
  separatorText?: string
@@ -24,8 +23,7 @@ interface OAuthSectionProps {
24
23
  }
25
24
 
26
25
  export function OAuthSection({ providers, separatorText, actionText = '登录' }: OAuthSectionProps) {
27
- const { t } = useTranslation('common')
28
- const resolvedActionText = actionText === '登录' ? t('login.action') : actionText
26
+ const resolvedActionText = actionText === '登录' ? m.common_login_action() : actionText
29
27
  return (
30
28
  <div className="space-y-3">
31
29
  {providers.map((provider, index) => (
@@ -35,7 +33,7 @@ export function OAuthSection({ providers, separatorText, actionText = '登录' }
35
33
  className="w-full inline-flex items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent transition-colors"
36
34
  >
37
35
  {provider.icon}
38
- {t('login.oauthWith', { provider: provider.name, action: resolvedActionText })}
36
+ {m.common_login_oauthWith({ provider: provider.name, action: resolvedActionText })}
39
37
  </button>
40
38
  {index < providers.length - 1 && separatorText && (
41
39
  <div className="relative flex items-center justify-center my-3">
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { useState } from 'react'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { Input } from '@/components/ui/input'
21
21
  import { Label } from '@/components/ui/label'
22
22
  import { Lock } from 'lucide-react'
@@ -26,7 +26,6 @@ interface PasswordInputProps extends Omit<React.InputHTMLAttributes<HTMLInputEle
26
26
  }
27
27
 
28
28
  export function PasswordInput({ label, id, className, ...props }: PasswordInputProps) {
29
- const { t } = useTranslation('common')
30
29
  const inputId = id || label.toLowerCase()
31
30
  const [isVisible, setIsVisible] = useState(false)
32
31
 
@@ -47,7 +46,7 @@ export function PasswordInput({ label, id, className, ...props }: PasswordInputP
47
46
  type="button"
48
47
  onClick={() => setIsVisible(!isVisible)}
49
48
  className="absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground outline-none transition-colors hover:text-foreground focus:z-10 focus:ring-[3px] focus:ring-ring/50"
50
- aria-label={isVisible ? t('login.hidePassword') : t('login.showPassword')}
49
+ aria-label={isVisible ? m.common_login_hidePassword() : m.common_login_showPassword()}
51
50
  >
52
51
  {isVisible ? (
53
52
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
@@ -16,8 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import type { LucideIcon } from 'lucide-react'
19
- import { useTranslation } from 'react-i18next'
20
-
19
+ import { m } from '@/paraglide/messages.js'
21
20
  type PlaceholderPageProps = {
22
21
  title: string
23
22
  description?: string
@@ -25,7 +24,6 @@ type PlaceholderPageProps = {
25
24
  }
26
25
 
27
26
  export function PlaceholderPage({ title, description, icon: Icon }: PlaceholderPageProps) {
28
- const { t } = useTranslation('common')
29
27
  return (
30
28
  <div className="flex h-full min-h-[60vh] items-center justify-center">
31
29
  <div className="flex flex-col items-center gap-4 rounded-xl border bg-card p-12 text-center shadow-sm">
@@ -34,7 +32,7 @@ export function PlaceholderPage({ title, description, icon: Icon }: PlaceholderP
34
32
  </div>
35
33
  <h1 className="text-xl font-semibold">{title}</h1>
36
34
  <p className="text-sm text-muted-foreground">
37
- {description ?? t('common.comingSoon')}
35
+ {description ?? m.common_common_comingSoon()}
38
36
  </p>
39
37
  </div>
40
38
  </div>
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { useState, useEffect } from 'react'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { Button } from '@/components/ui/button'
21
21
  import { Input } from '@/components/ui/input'
22
22
  import { Label } from '@/components/ui/label'
@@ -51,7 +51,6 @@ interface ComponentFormDialogProps {
51
51
  }
52
52
 
53
53
  export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }: ComponentFormDialogProps) {
54
- const { t } = useTranslation('common')
55
54
  const isEdit = !!component
56
55
  const [name, setName] = useState('')
57
56
  const [description, setDescription] = useState('')
@@ -85,7 +84,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
85
84
 
86
85
  async function handleSave() {
87
86
  if (!name.trim()) {
88
- toast.error(t('component.form.nameRequired'))
87
+ toast.error(m.common_component_form_nameRequired())
89
88
  return
90
89
  }
91
90
  setSaving(true)
@@ -109,7 +108,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
109
108
  defaultProps: propsState,
110
109
  },
111
110
  })
112
- toast.success(t('component.form.updated'))
111
+ toast.success(m.common_component_form_updated())
113
112
  } else {
114
113
  await createComponentFn({
115
114
  data: {
@@ -119,12 +118,12 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
119
118
  description: description.trim() || undefined,
120
119
  },
121
120
  })
122
- toast.success(t('component.form.created'))
121
+ toast.success(m.common_component_form_created())
123
122
  }
124
123
  onSuccess()
125
124
  onOpenChange(false)
126
125
  } catch (e) {
127
- toast.error(e instanceof Error ? e.message : t('component.form.saveFailed'))
126
+ toast.error(e instanceof Error ? e.message : m.common_component_form_saveFailed())
128
127
  } finally {
129
128
  setSaving(false)
130
129
  }
@@ -132,18 +131,18 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
132
131
 
133
132
  function categoryToDisplay(k: string): string {
134
133
  const map: Record<string, string> = {
135
- base: t('component.categories.base'),
136
- block: t('component.categories.block'),
137
- template: t('component.categories.template'),
134
+ base: m.common_component_categories_base(),
135
+ block: m.common_component_categories_block(),
136
+ template: m.common_component_categories_template(),
138
137
  }
139
138
  return map[k] ?? k
140
139
  }
141
140
 
142
141
  function categoryFromKey(label: string | null | undefined): string | undefined {
143
142
  if (!label) return undefined
144
- const zhBase = t('component.categories.base')
145
- const zhBlock = t('component.categories.block')
146
- const zhTemplate = t('component.categories.template')
143
+ const zhBase = m.common_component_categories_base()
144
+ const zhBlock = m.common_component_categories_block()
145
+ const zhTemplate = m.common_component_categories_template()
147
146
  if (label === zhBase) return 'base'
148
147
  if (label === zhBlock) return 'block'
149
148
  if (label === zhTemplate) return 'template'
@@ -152,7 +151,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
152
151
 
153
152
  function renderPreview() {
154
153
  if (!component) {
155
- return <span className="text-xs text-muted-foreground">{t('component.form.previewUnavailable')}</span>
154
+ return <span className="text-xs text-muted-foreground">{m.common_component_form_previewUnavailable()}</span>
156
155
  }
157
156
  const config = getComponent(component.prototypeKey ?? '')
158
157
  const Comp = config?.component
@@ -180,7 +179,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
180
179
  if (schema.length === 0) return null
181
180
  return (
182
181
  <div className="col-span-2 mt-2 space-y-2 border-t pt-3">
183
- <Label className="text-muted-foreground">{t('component.form.defaultProps')}</Label>
182
+ <Label className="text-muted-foreground">{m.common_component_form_defaultProps()}</Label>
184
183
  <div className="grid grid-cols-2 gap-3">
185
184
  {schema.map((prop) => {
186
185
  const val = propsState[prop.key] ?? prop.default
@@ -191,11 +190,11 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
191
190
  {prop.type === 'boolean' ? (
192
191
  <Switch checked={!!val} onCheckedChange={setVal} />
193
192
  ) : prop.type === 'number' ? (
194
- <Input type="number" value={Number(val)} onChange={(e) => setVal(Number(e.target.value))} />
193
+ <Input type="number" value={Number(val)} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(Number(e.target.value))} />
195
194
  ) : prop.type === 'color' ? (
196
195
  <div className="flex gap-2">
197
- <input type="color" value={String(val)} onChange={(e) => setVal(e.target.value)} className="size-8 shrink-0 rounded border" />
198
- <Input value={String(val)} onChange={(e) => setVal(e.target.value)} className="font-mono text-xs" />
196
+ <input type="color" value={String(val)} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(e.target.value)} className="size-8 shrink-0 rounded border" />
197
+ <Input value={String(val)} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(e.target.value)} className="font-mono text-xs" />
199
198
  </div>
200
199
  ) : prop.type === 'select' && prop.options ? (
201
200
  <Select value={String(val)} onValueChange={setVal}>
@@ -207,7 +206,7 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
207
206
  </SelectContent>
208
207
  </Select>
209
208
  ) : (
210
- <Input value={String(val ?? '')} onChange={(e) => setVal(e.target.value)} />
209
+ <Input value={String(val ?? '')} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(e.target.value)} />
211
210
  )}
212
211
  </div>
213
212
  )
@@ -221,13 +220,13 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
221
220
  <Dialog open={open} onOpenChange={onOpenChange}>
222
221
  <DialogContent className="sm:max-w-4xl">
223
222
  <DialogHeader>
224
- <DialogTitle>{isEdit ? t('component.form.edit') : t('component.form.create')}</DialogTitle>
223
+ <DialogTitle>{isEdit ? m.common_component_form_edit() : m.common_component_form_create()}</DialogTitle>
225
224
  </DialogHeader>
226
225
 
227
226
  <div className="flex min-h-[300px] gap-4">
228
227
  {/* 左侧预览区 */}
229
228
  <div className="flex w-60 shrink-0 flex-col gap-1.5">
230
- <Label className="text-muted-foreground">{t('component.form.preview')}</Label>
229
+ <Label className="text-muted-foreground">{m.common_component_form_preview()}</Label>
231
230
  <div className="flex flex-1 items-center justify-center overflow-hidden rounded-lg border bg-muted/30">
232
231
  {renderPreview()}
233
232
  </div>
@@ -242,94 +241,94 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
242
241
  {/* 右侧表单 */}
243
242
  <div className="grid flex-1 grid-cols-2 gap-x-4 gap-y-3">
244
243
  <div className="col-span-2 space-y-1.5">
245
- <Label>{t('component.form.name')}</Label>
246
- <Input value={name} onChange={(e) => setName(e.target.value)} placeholder={t('component.form.namePlaceholder')} />
244
+ <Label>{m.common_component_form_name()}</Label>
245
+ <Input value={name} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)} placeholder={m.common_component_form_namePlaceholder()} />
247
246
  </div>
248
247
 
249
248
  <div className="col-span-2 space-y-1.5">
250
- <Label>{t('component.form.description')}</Label>
249
+ <Label>{m.common_component_form_description()}</Label>
251
250
  <Textarea
252
251
  value={description}
253
- onChange={(e) => setDescription(e.target.value)}
254
- placeholder={t('component.form.descriptionPlaceholder')}
252
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)}
253
+ placeholder={m.common_component_form_descriptionPlaceholder()}
255
254
  rows={2}
256
255
  />
257
256
  </div>
258
257
 
259
258
  <div className="space-y-1.5">
260
- <Label>{t('component.form.category')}</Label>
259
+ <Label>{m.common_component_form_category()}</Label>
261
260
  <Select value={category} onValueChange={setCategory}>
262
261
  <SelectTrigger className="w-full">
263
262
  <SelectValue />
264
263
  </SelectTrigger>
265
264
  <SelectContent>
266
- <SelectItem key="base" value="base">{t('component.categories.base')}</SelectItem>
267
- <SelectItem key="block" value="block">{t('component.categories.block')}</SelectItem>
268
- <SelectItem key="template" value="template">{t('component.categories.template')}</SelectItem>
265
+ <SelectItem key="base" value="base">{m.common_component_categories_base()}</SelectItem>
266
+ <SelectItem key="block" value="block">{m.common_component_categories_block()}</SelectItem>
267
+ <SelectItem key="template" value="template">{m.common_component_categories_template()}</SelectItem>
269
268
  </SelectContent>
270
269
  </Select>
271
270
  </div>
272
271
 
273
272
  <div className="space-y-1.5">
274
- <Label>{t('component.form.type')}</Label>
273
+ <Label>{m.common_component_form_type()}</Label>
275
274
  <Select value={type} onValueChange={setType}>
276
275
  <SelectTrigger className="w-full">
277
276
  <SelectValue />
278
277
  </SelectTrigger>
279
278
  <SelectContent>
280
- <SelectItem key="component" value="component">{t('component.types.base')}</SelectItem>
281
- <SelectItem key="block" value="block">{t('component.types.block')}</SelectItem>
282
- <SelectItem key="template" value="template">{t('component.types.template')}</SelectItem>
279
+ <SelectItem key="component" value="component">{m.common_component_types_base()}</SelectItem>
280
+ <SelectItem key="block" value="block">{m.common_component_types_block()}</SelectItem>
281
+ <SelectItem key="template" value="template">{m.common_component_types_template()}</SelectItem>
283
282
  </SelectContent>
284
283
  </Select>
285
284
  </div>
286
285
 
287
286
  <div className="space-y-1.5">
288
- <Label>{t('component.form.subCategory')}</Label>
289
- <Input value={subCategory} onChange={(e) => setSubCategory(e.target.value)} placeholder={t('component.form.subCategoryPlaceholder')} />
287
+ <Label>{m.common_component_form_subCategory()}</Label>
288
+ <Input value={subCategory} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSubCategory(e.target.value)} placeholder={m.common_component_form_subCategoryPlaceholder()} />
290
289
  </div>
291
290
 
292
291
  <div className="space-y-1.5">
293
- <Label>{t('component.form.tags')}</Label>
294
- <Input value={tags} onChange={(e) => setTags(e.target.value)} placeholder={t('component.form.tagsPlaceholder')} />
292
+ <Label>{m.common_component_form_tags()}</Label>
293
+ <Input value={tags} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setTags(e.target.value)} placeholder={m.common_component_form_tagsPlaceholder()} />
295
294
  </div>
296
295
 
297
296
  <div className="col-span-2 space-y-1.5">
298
- <Label>{t('component.form.path')}</Label>
299
- <Input value={path} onChange={(e) => setPath(e.target.value)} placeholder={t('component.form.pathPlaceholder')} className="font-mono text-xs" />
300
- <p className="text-[10px] text-muted-foreground">{t('component.form.pathHint')}</p>
297
+ <Label>{m.common_component_form_path()}</Label>
298
+ <Input value={path} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPath(e.target.value)} placeholder={m.common_component_form_pathPlaceholder()} className="font-mono text-xs" />
299
+ <p className="text-[10px] text-muted-foreground">{m.common_component_form_pathHint()}</p>
301
300
  </div>
302
301
 
303
302
  <div className="space-y-1.5">
304
- <Label>{t('component.form.defaultWidth')}</Label>
303
+ <Label>{m.common_component_form_defaultWidth()}</Label>
305
304
  <Input
306
305
  type="number"
307
306
  value={defaultWidth}
308
- onChange={(e) => setDefaultWidth(Number(e.target.value))}
307
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDefaultWidth(Number(e.target.value))}
309
308
  />
310
309
  </div>
311
310
 
312
311
  <div className="space-y-1.5">
313
- <Label>{t('component.form.defaultHeight')}</Label>
312
+ <Label>{m.common_component_form_defaultHeight()}</Label>
314
313
  <Input
315
314
  type="number"
316
315
  value={defaultHeight}
317
- onChange={(e) => setDefaultHeight(Number(e.target.value))}
316
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDefaultHeight(Number(e.target.value))}
318
317
  />
319
318
  </div>
320
319
 
321
320
  <div className="space-y-1.5">
322
- <Label>{t('component.form.sortOrder')}</Label>
321
+ <Label>{m.common_component_form_sortOrder()}</Label>
323
322
  <Input
324
323
  type="number"
325
324
  value={sortOrder}
326
- onChange={(e) => setSortOrder(Number(e.target.value))}
325
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSortOrder(Number(e.target.value))}
327
326
  />
328
327
  </div>
329
328
 
330
329
  <div className="flex items-center gap-2 pt-6">
331
330
  <Switch checked={isHidden} onCheckedChange={setIsHidden} />
332
- <Label className="cursor-pointer" onClick={() => setIsHidden(!isHidden)}>{t('component.form.hidden')}</Label>
331
+ <Label className="cursor-pointer" onClick={() => setIsHidden(!isHidden)}>{m.common_component_form_hidden()}</Label>
333
332
  </div>
334
333
 
335
334
  {renderPropsEditor()}
@@ -337,9 +336,9 @@ export function ComponentFormDialog({ open, onOpenChange, component, onSuccess }
337
336
  </div>
338
337
 
339
338
  <DialogFooter>
340
- <Button variant="outline" onClick={() => onOpenChange(false)}>{t('common.cancel')}</Button>
339
+ <Button variant="outline" onClick={() => onOpenChange(false)}>{m.common_common_cancel()}</Button>
341
340
  <Button onClick={handleSave} disabled={saving}>
342
- {saving ? t('status.saving') : t('common.save')}
341
+ {saving ? m.common_status_saving() : m.common_button_save()}
343
342
  </Button>
344
343
  </DialogFooter>
345
344
  </DialogContent>
@@ -144,7 +144,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
144
144
  <div className="flex gap-2">
145
145
  <Input
146
146
  value={packageName}
147
- onChange={(e) => setPackageName(e.target.value)}
147
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPackageName(e.target.value)}
148
148
  placeholder="如:lucide-react"
149
149
  className="font-mono text-xs"
150
150
  />
@@ -160,7 +160,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
160
160
  <div className="flex gap-2">
161
161
  <Input
162
162
  value={importPath}
163
- onChange={(e) => setImportPath(e.target.value)}
163
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setImportPath(e.target.value)}
164
164
  placeholder="@magicui/safari 或 @/components/magicui/safari"
165
165
  className="font-mono text-xs"
166
166
  />
@@ -180,7 +180,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
180
180
  <Label>导出名</Label>
181
181
  <Input
182
182
  value={exportName}
183
- onChange={(e) => setExportName(e.target.value)}
183
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setExportName(e.target.value)}
184
184
  placeholder="default 或具名"
185
185
  className="font-mono text-xs"
186
186
  />
@@ -189,7 +189,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
189
189
  <Label>组件标识 *</Label>
190
190
  <Input
191
191
  value={componentKey}
192
- onChange={(e) => setComponentKey(e.target.value)}
192
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setComponentKey(e.target.value)}
193
193
  placeholder="如:my-button"
194
194
  className="font-mono text-xs"
195
195
  />
@@ -201,7 +201,7 @@ export function ComponentImportDialog({ open, onOpenChange, onSuccess }: Compone
201
201
  <Label>显示名称 *</Label>
202
202
  <Input
203
203
  value={displayName}
204
- onChange={(e) => setDisplayName(e.target.value)}
204
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setDisplayName(e.target.value)}
205
205
  placeholder="如:我的按钮"
206
206
  />
207
207
  </div>
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { Edit, Trash2 } from 'lucide-react'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { Badge } from '@/components/ui/badge'
21
21
  import { Button } from '@/components/ui/button'
22
22
  import {
@@ -62,16 +62,15 @@ export function ComponentTable({
62
62
  onEdit: (item: ComponentItem) => void
63
63
  onDelete: (item: ComponentItem) => void
64
64
  }) {
65
- const { t } = useTranslation('common')
66
65
  const categoryLabel = (k: string) => {
67
- const map: Record<string, string> = { base: t('component.categories.base'), block: t('component.categories.block'), template: t('component.categories.template') }
66
+ const map: Record<string, string> = { base: m.common_component_categories_base(), block: m.common_component_categories_block(), template: m.common_component_categories_template() }
68
67
  return map[k] ?? k
69
68
  }
70
69
 
71
70
  if (items.length === 0) {
72
71
  return (
73
72
  <div className="flex h-40 items-center justify-center rounded-lg border border-dashed">
74
- <p className="text-sm text-muted-foreground">{t('component.table.empty')}</p>
73
+ <p className="text-sm text-muted-foreground">{m.common_component_table_empty()}</p>
75
74
  </div>
76
75
  )
77
76
  }
@@ -81,13 +80,13 @@ export function ComponentTable({
81
80
  <Table>
82
81
  <TableHeader>
83
82
  <TableRow>
84
- <TableHead className="w-[28%]">{t('component.table.columns.component')}</TableHead>
85
- <TableHead>{t('component.table.columns.category')}</TableHead>
86
- <TableHead>{t('component.table.columns.type')}</TableHead>
87
- <TableHead>{t('component.table.columns.tags')}</TableHead>
88
- <TableHead>{t('component.table.columns.source')}</TableHead>
89
- <TableHead>{t('component.table.columns.status')}</TableHead>
90
- <TableHead className="text-right">{t('component.table.columns.actions')}</TableHead>
83
+ <TableHead className="w-[28%]">{m.common_component_table_columns_component()}</TableHead>
84
+ <TableHead>{m.common_component_table_columns_category()}</TableHead>
85
+ <TableHead>{m.common_component_table_columns_type()}</TableHead>
86
+ <TableHead>{m.common_component_table_columns_tags()}</TableHead>
87
+ <TableHead>{m.common_component_table_columns_source()}</TableHead>
88
+ <TableHead>{m.common_component_table_columns_status()}</TableHead>
89
+ <TableHead className="text-right">{m.common_component_table_columns_actions()}</TableHead>
91
90
  </TableRow>
92
91
  </TableHeader>
93
92
  <TableBody>
@@ -124,12 +123,12 @@ export function ComponentTable({
124
123
  </TableCell>
125
124
  <TableCell>
126
125
  <span className={cn('text-xs', item.isHidden ? 'text-muted-foreground' : 'text-foreground')}>
127
- {item.isHidden ? t('component.table.status.hidden') : t('component.table.status.visible')}
126
+ {item.isHidden ? m.common_component_table_status_hidden() : m.common_component_table_status_visible()}
128
127
  </span>
129
128
  </TableCell>
130
129
  <TableCell className="text-right">
131
130
  <div className="flex items-center justify-end gap-1">
132
- <Button variant="ghost" size="icon-sm" onClick={() => onEdit(item)} aria-label={t('common.edit')}>
131
+ <Button variant="ghost" size="icon-sm" onClick={() => onEdit(item)} aria-label={m.common_common_edit()}>
133
132
  <Edit className="size-4" />
134
133
  </Button>
135
134
  <Button
@@ -137,7 +136,7 @@ export function ComponentTable({
137
136
  size="icon-sm"
138
137
  onClick={() => onDelete(item)}
139
138
  className="text-destructive hover:bg-destructive/10"
140
- aria-label={t('common.delete')}
139
+ aria-label={m.common_common_delete()}
141
140
  >
142
141
  <Trash2 className="size-4" />
143
142
  </Button>