@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
@@ -24,8 +24,7 @@ import {
24
24
  PaginationNext,
25
25
  PaginationPrevious,
26
26
  } from "@/components/ui/pagination";
27
- import { useTranslation } from 'react-i18next'
28
-
27
+ import { m } from '@/paraglide/messages.js'
29
28
  interface DataTablePaginationProps {
30
29
  page: number;
31
30
  totalPages: number;
@@ -66,19 +65,18 @@ export function DataTablePagination({
66
65
  total,
67
66
  onPageChange,
68
67
  }: DataTablePaginationProps) {
69
- const { t } = useTranslation('common')
70
68
  const pages = getPageNumbers(page, totalPages);
71
69
 
72
70
  return (
73
71
  <div className="flex items-center justify-between gap-4 px-2 py-4">
74
72
  <div className="text-sm text-muted-foreground whitespace-nowrap shrink-0">
75
- {t('pagination.total', { total, page, totalPages })}
73
+ {m.common_pagination_total({ total, page, totalPages })}
76
74
  </div>
77
75
  <Pagination className="justify-end">
78
76
  <PaginationContent>
79
77
  <PaginationItem>
80
78
  <PaginationPrevious
81
- text={t('pagination.prev')}
79
+ text={m.common_pagination_prev()}
82
80
  onClick={() => page > 1 && onPageChange(page - 1)}
83
81
  className={page <= 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}
84
82
  />
@@ -102,7 +100,7 @@ export function DataTablePagination({
102
100
  )}
103
101
  <PaginationItem>
104
102
  <PaginationNext
105
- text={t('pagination.next')}
103
+ text={m.common_pagination_next()}
106
104
  onClick={() => page < totalPages && onPageChange(page + 1)}
107
105
  className={page >= totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"}
108
106
  />
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { useNavigate } from "@tanstack/react-router";
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { Button } from "@/components/ui/button";
21
21
  import { Skeleton } from "@/components/ui/skeleton";
22
22
  import { ArrowLeftIcon } from "lucide-react";
@@ -44,9 +44,8 @@ export function TableDetailLayout({
44
44
  loading,
45
45
  children,
46
46
  }: TableDetailLayoutProps) {
47
- const { t } = useTranslation('common')
48
47
  const navigate = useNavigate();
49
- const resolvedBackLabel = backLabel ?? t('common.backToList')
48
+ const resolvedBackLabel = backLabel ?? m.common_common_backToList()
50
49
 
51
50
  if (loading) {
52
51
  return (
@@ -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 ReactMarkdown from "react-markdown"
21
21
  import remarkGfm from "remark-gfm"
22
22
  import { ExternalLink, FileText } from "lucide-react"
@@ -32,7 +32,6 @@ import {
32
32
 
33
33
  export function ChangelogDialog({ row }: { row: ThirdPartyDependencyRow }) {
34
34
  const [open, setOpen] = useState(false)
35
- const { t } = useTranslation('common')
36
35
  const hasChangelog = !!row.latestChangelog
37
36
 
38
37
  if (!hasChangelog) {
@@ -42,9 +41,9 @@ export function ChangelogDialog({ row }: { row: ThirdPartyDependencyRow }) {
42
41
  return (
43
42
  <Dialog open={open} onOpenChange={setOpen}>
44
43
  <DialogTrigger asChild>
45
- <Button size="sm" variant="ghost" className="h-7 gap-1 px-2" title={t('dependency.table.cell.viewChangelog')}>
44
+ <Button size="sm" variant="ghost" className="h-7 gap-1 px-2" title={m.common_dependency_table_cell_viewChangelog()}>
46
45
  <FileText className="size-3.5" />
47
- <span className="text-xs">{t('dependency.table.cell.changelogBtn')}</span>
46
+ <span className="text-xs">{m.common_dependency_table_cell_changelogBtn()}</span>
48
47
  </Button>
49
48
  </DialogTrigger>
50
49
  <DialogContent className="max-h-[85vh] w-200 max-w-4xl overflow-hidden p-0">
@@ -23,6 +23,7 @@ import {
23
23
  type ColumnDef,
24
24
  } from "@tanstack/react-table"
25
25
  import { CheckCircle2, ExternalLink, Loader2, RefreshCw } from "lucide-react"
26
+ import { m } from '@/paraglide/messages.js'
26
27
  import {
27
28
  DEPENDENCY_CATEGORIES,
28
29
  type ThirdPartyDependencyRow,
@@ -49,37 +50,44 @@ export const STATUS_META: Record<VersionStatus, { className: string }> = {
49
50
  unknown: { className: "bg-muted text-muted-foreground border-border" },
50
51
  }
51
52
 
52
- export function getDependencyStatusLabel(t: (k: string) => string, status: VersionStatus) {
53
- return t(`dependency.statusLabel.${status}`)
53
+ const STATUS_LABELS: Record<string, () => string> = {
54
+ latest: m.common_dependency_statusLabel_latest,
55
+ patch: m.common_dependency_statusLabel_patch,
56
+ minor: m.common_dependency_statusLabel_minor,
57
+ major: m.common_dependency_statusLabel_major,
58
+ unknown: m.common_dependency_statusLabel_unknown,
54
59
  }
55
60
 
56
- export function getDependencyTimeAgo(t: (k: string, params?: Record<string, unknown>) => string, iso: string | null): string {
61
+ export function getDependencyStatusLabel(status: VersionStatus) {
62
+ return (STATUS_LABELS[status] ?? (() => status))()
63
+ }
64
+
65
+ export function getDependencyTimeAgo(iso: string | null): string {
57
66
  if (!iso) return "—"
58
67
  const d = new Date(iso)
59
68
  if (Number.isNaN(d.getTime())) return "—"
60
69
  const diff = Date.now() - d.getTime()
61
70
  const min = Math.floor(diff / 60000)
62
- if (min < 1) return t('dependency.timeAgo.justNow')
63
- if (min < 60) return t('dependency.timeAgo.minutes', { count: min })
71
+ if (min < 1) return m.common_dependency_timeAgo_justNow()
72
+ if (min < 60) return m.common_dependency_timeAgo_minutes({ count: min })
64
73
  const hr = Math.floor(min / 60)
65
- if (hr < 24) return t('dependency.timeAgo.hours', { count: hr })
74
+ if (hr < 24) return m.common_dependency_timeAgo_hours({ count: hr })
66
75
  const day = Math.floor(hr / 24)
67
- return t('dependency.timeAgo.days', { count: day })
76
+ return m.common_dependency_timeAgo_days({ count: day })
68
77
  }
69
78
 
70
79
  interface ColumnOpts {
71
80
  syncing: Set<string>
72
81
  onSync: (name: string) => void
73
82
  onSaved: () => void
74
- t: (k: string, params?: Record<string, unknown>) => string
75
83
  }
76
84
 
77
85
  export function getDependencyColumns(opts: ColumnOpts): ColumnDef<typeof dependencyTableFeatures, ThirdPartyDependencyRow>[] {
78
- const { syncing, onSync, onSaved, t } = opts
86
+ const { syncing, onSync, onSaved } = opts
79
87
  return [
80
88
  {
81
89
  accessorKey: "displayName",
82
- header: t('dependency.table.columns.dependency'),
90
+ header: m.common_dependency_table_columns_dependency(),
83
91
  cell: ({ row }) => {
84
92
  const r = row.original
85
93
  return (
@@ -88,12 +96,12 @@ export function getDependencyColumns(opts: ColumnOpts): ColumnDef<typeof depende
88
96
  <span className="font-medium">{r.displayName}</span>
89
97
  {!r.isWatched && (
90
98
  <Badge variant="outline" className="px-1 py-0 text-[10px] text-muted-foreground">
91
- {t('dependency.table.cell.untracked')}
99
+ {m.common_dependency_table_cell_untracked()}
92
100
  </Badge>
93
101
  )}
94
102
  {r.priority === "high" && r.isWatched && (
95
103
  <Badge variant="outline" className={cn("px-1 py-0 text-[10px]", PRIORITY_BADGE.high)}>
96
- {t('dependency.table.cell.highPriority')}
104
+ {m.common_dependency_table_cell_highPriority()}
97
105
  </Badge>
98
106
  )}
99
107
  </div>
@@ -114,7 +122,7 @@ export function getDependencyColumns(opts: ColumnOpts): ColumnDef<typeof depende
114
122
  },
115
123
  {
116
124
  accessorKey: "description",
117
- header: t('dependency.table.columns.description'),
125
+ header: m.common_dependency_table_columns_description(),
118
126
  cell: ({ row }) => {
119
127
  const desc = row.original.description
120
128
  if (!desc) return <span className="text-xs text-muted-foreground">—</span>
@@ -132,7 +140,7 @@ export function getDependencyColumns(opts: ColumnOpts): ColumnDef<typeof depende
132
140
  },
133
141
  {
134
142
  accessorKey: "category",
135
- header: t('dependency.table.columns.category'),
143
+ header: m.common_dependency_table_columns_category(),
136
144
  cell: ({ row }) => (
137
145
  <Badge variant="outline" className="font-normal">
138
146
  {DEPENDENCY_CATEGORIES.find((c) => c.value === row.original.category)?.label ?? row.original.category}
@@ -141,10 +149,10 @@ export function getDependencyColumns(opts: ColumnOpts): ColumnDef<typeof depende
141
149
  },
142
150
  {
143
151
  accessorKey: "currentVersion",
144
- header: t('dependency.table.columns.currentVersion'),
152
+ header: m.common_dependency_table_columns_currentVersion(),
145
153
  cell: ({ row }) => {
146
154
  const r = row.original
147
- if (!r.currentVersion) return <span className="text-xs text-muted-foreground">{t('dependency.table.cell.notInstalled')}</span>
155
+ if (!r.currentVersion) return <span className="text-xs text-muted-foreground">{m.common_dependency_table_cell_notInstalled()}</span>
148
156
  if (r.usages.length > 1) {
149
157
  return (
150
158
  <Tooltip>
@@ -170,38 +178,38 @@ export function getDependencyColumns(opts: ColumnOpts): ColumnDef<typeof depende
170
178
  },
171
179
  {
172
180
  accessorKey: "latestVersion",
173
- header: t('dependency.table.columns.latestVersion'),
181
+ header: m.common_dependency_table_columns_latestVersion(),
174
182
  cell: ({ row }) =>
175
183
  row.original.latestVersion ?? <span className="text-muted-foreground">—</span>,
176
184
  },
177
185
  {
178
186
  id: "changelog",
179
- header: t('dependency.table.columns.changelog'),
187
+ header: m.common_dependency_table_columns_changelog(),
180
188
  cell: ({ row }) => <ChangelogDialog row={row.original} />,
181
189
  },
182
190
  {
183
191
  accessorKey: "versionStatus",
184
- header: t('dependency.table.columns.status'),
192
+ header: m.common_dependency_table_columns_status(),
185
193
  cell: ({ row }) => {
186
194
  const status = row.original.versionStatus
187
195
  return (
188
196
  <Badge variant="outline" className={cn("font-normal", STATUS_META[status].className)}>
189
197
  {status === "latest" && <CheckCircle2 className="mr-1 size-3" />}
190
- {getDependencyStatusLabel(t, status)}
198
+ {getDependencyStatusLabel(status)}
191
199
  </Badge>
192
200
  )
193
201
  },
194
202
  },
195
203
  {
196
204
  accessorKey: "lastSyncAt",
197
- header: t('dependency.table.columns.lastSync'),
205
+ header: m.common_dependency_table_columns_lastSync(),
198
206
  cell: ({ row }) => (
199
- <span className="text-xs text-muted-foreground">{getDependencyTimeAgo(t, row.original.lastSyncAt)}</span>
207
+ <span className="text-xs text-muted-foreground">{getDependencyTimeAgo(row.original.lastSyncAt)}</span>
200
208
  ),
201
209
  },
202
210
  {
203
211
  id: "actions",
204
- header: () => <div className="text-right">{t('dependency.table.columns.actions')}</div>,
212
+ header: () => <div className="text-right">{m.common_dependency_table_columns_actions()}</div>,
205
213
  cell: ({ row }) => {
206
214
  const r = row.original
207
215
  const isSyncing = syncing.has(r.name)
@@ -213,7 +221,7 @@ export function getDependencyColumns(opts: ColumnOpts): ColumnDef<typeof depende
213
221
  className="size-7 p-0"
214
222
  onClick={() => onSync(r.name)}
215
223
  disabled={isSyncing}
216
- title={t('dependency.table.cell.syncTooltip')}
224
+ title={m.common_dependency_table_cell_syncTooltip()}
217
225
  >
218
226
  {isSyncing ? <Loader2 className="size-3.5 animate-spin" /> : <RefreshCw className="size-3.5" />}
219
227
  </Button>
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { useMemo, useState } from "react"
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import {
21
21
  flexRender,
22
22
  useTable,
@@ -66,6 +66,15 @@ const STATUS_FILTERS: Array<{ value: "all" | VersionStatus }> = [
66
66
  { value: "unknown" },
67
67
  ]
68
68
 
69
+ const STATUS_FILTER_LABELS: Record<typeof STATUS_FILTERS[number]['value'], () => string> = {
70
+ all: m.common_dependency_table_statusFilter_all,
71
+ latest: m.common_dependency_table_statusFilter_latest,
72
+ patch: m.common_dependency_table_statusFilter_patch,
73
+ minor: m.common_dependency_table_statusFilter_minor,
74
+ major: m.common_dependency_table_statusFilter_major,
75
+ unknown: m.common_dependency_table_statusFilter_unknown,
76
+ }
77
+
69
78
  export function DependencyTable({
70
79
  data,
71
80
  onMutated,
@@ -73,7 +82,6 @@ export function DependencyTable({
73
82
  data: ThirdPartyDependencyRow[]
74
83
  onMutated?: () => void
75
84
  }) {
76
- const { t } = useTranslation('common')
77
85
  const [search, setSearch] = useState("")
78
86
  const [categoryFilter, setCategoryFilter] = useState<"all" | DependencyCategory>("all")
79
87
  const [statusFilter, setStatusFilter] = useState<"all" | VersionStatus>("all")
@@ -100,8 +108,8 @@ export function DependencyTable({
100
108
  }, [data, search, categoryFilter, statusFilter, viewFilter])
101
109
 
102
110
  const columns = useMemo(
103
- () => getDependencyColumns({ syncing, onSync: handleSync, onSaved: () => onMutated?.(), t }),
104
- [syncing, onMutated, t],
111
+ () => getDependencyColumns({ syncing, onSync: handleSync, onSaved: () => onMutated?.() }),
112
+ [syncing, onMutated],
105
113
  )
106
114
 
107
115
  const table = useTable({
@@ -128,10 +136,10 @@ export function DependencyTable({
128
136
  try {
129
137
  const res = await syncDependencyFn({ data: { name } })
130
138
  if (res.success) {
131
- toast.success(t('dependency.table.toast.updated', { name, version: res.latestVersion ?? "?" }))
139
+ toast.success(m.common_dependency_table_toast_updated({ name, version: res.latestVersion ?? "?" }))
132
140
  onMutated?.()
133
141
  } else {
134
- toast.error(t('dependency.table.toast.syncFailed', { name, error: res.error ?? t('dependency.table.toast.unknown') }))
142
+ toast.error(m.common_dependency_table_toast_syncFailed({ name, error: res.error ?? m.common_dependency_table_toast_unknown() }))
135
143
  }
136
144
  } finally {
137
145
  setSyncing((s) => {
@@ -145,18 +153,18 @@ export function DependencyTable({
145
153
  async function handleSyncAll() {
146
154
  const names = filtered.map((r) => r.name)
147
155
  if (names.length === 0) {
148
- toast.info(t('dependency.table.toast.noSyncable'))
156
+ toast.info(m.common_dependency_table_toast_noSyncable())
149
157
  return
150
158
  }
151
159
  setSyncingAll(true)
152
160
  try {
153
161
  const results = await syncAllDependenciesFn({ data: { names } })
154
- const ok = results.filter((r) => r.success)
155
- const fail = results.filter((r) => !r.success)
156
- if (ok.length) toast.success(t('dependency.table.toast.syncSuccess', { count: ok.length }))
162
+ const ok = results.filter((r: any) => r.success)
163
+ const fail = results.filter((r: any) => !r.success)
164
+ if (ok.length) toast.success(m.common_dependency_table_toast_syncSuccess({ count: ok.length }))
157
165
  if (fail.length)
158
- toast.error(t('dependency.table.toast.syncFailedCount', { count: fail.length }), {
159
- description: fail.map((f) => `${f.name}: ${f.error}`).slice(0, 3).join("\n"),
166
+ toast.error(m.common_dependency_table_toast_syncFailedCount({ count: fail.length }), {
167
+ description: fail.map((f: any) => `${f.name}: ${f.error}`).slice(0, 3).join("\n"),
160
168
  })
161
169
  onMutated?.()
162
170
  } finally {
@@ -171,16 +179,16 @@ export function DependencyTable({
171
179
  <div className="space-y-4">
172
180
  <div className="flex flex-wrap items-center gap-3">
173
181
  <div className="flex items-center gap-2 text-sm text-muted-foreground">
174
- <Badge variant="outline">{t('dependency.table.stats.watched', { watchedCount })}</Badge>
182
+ <Badge variant="outline">{m.common_dependency_table_stats_watched({ count: watchedCount })}</Badge>
175
183
  <Badge variant="outline" className="border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-400">
176
184
  <TriangleAlert className="mr-1 size-3" />
177
- {t('dependency.table.stats.outdated', { outdatedCount })}
185
+ {m.common_dependency_table_stats_outdated({ count: outdatedCount })}
178
186
  </Badge>
179
187
  </div>
180
188
  <div className="ml-auto flex items-center gap-2">
181
189
  <Button onClick={handleSyncAll} disabled={syncingAll} variant="outline" size="sm">
182
190
  {syncingAll ? <Loader2 className="mr-1 size-3.5 animate-spin" /> : <RefreshCw className="mr-1 size-3.5" />}
183
- {t('dependency.table.syncAll', { count: filtered.length })}
191
+ {m.common_dependency_table_syncAll({ count: filtered.length })}
184
192
  </Button>
185
193
  </div>
186
194
  </div>
@@ -188,7 +196,7 @@ export function DependencyTable({
188
196
  <div className="flex flex-wrap items-center gap-x-4 gap-y-1.5">
189
197
  <div className="flex flex-wrap items-center gap-1.5">
190
198
  <CategoryButton active={categoryFilter === "all"} onClick={() => setCategoryFilter("all")}>
191
- {t('dependency.table.allCategory')}
199
+ {m.common_dependency_table_allCategory()}
192
200
  </CategoryButton>
193
201
  {DEPENDENCY_CATEGORIES.map((c) => (
194
202
  <CategoryButton
@@ -207,7 +215,7 @@ export function DependencyTable({
207
215
  active={statusFilter === s.value}
208
216
  onClick={() => setStatusFilter(s.value)}
209
217
  >
210
- {t(`dependency.table.statusFilter.${s.value}`)}
218
+ {STATUS_FILTER_LABELS[s.value]()}
211
219
  </CategoryButton>
212
220
  ))}
213
221
  </div>
@@ -218,22 +226,22 @@ export function DependencyTable({
218
226
  <Search className="absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
219
227
  <Input
220
228
  value={search}
221
- onChange={(e) => setSearch(e.target.value)}
222
- placeholder={t('dependency.table.searchPlaceholder')}
229
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearch(e.target.value)}
230
+ placeholder={m.common_dependency_table_searchPlaceholder()}
223
231
  className="h-9 w-56 pl-8"
224
232
  />
225
233
  </div>
226
- <Select value={viewFilter} onValueChange={(v) => setViewFilter(v as "watched" | "all")}>
234
+ <Select value={viewFilter} onValueChange={(v: string) => setViewFilter(v as "watched" | "all")}>
227
235
  <SelectTrigger className="h-9 w-32" size="sm">
228
236
  <SelectValue />
229
237
  </SelectTrigger>
230
238
  <SelectContent>
231
- <SelectItem value="watched">{t('dependency.table.viewFilter.watched')}</SelectItem>
232
- <SelectItem value="all">{t('dependency.table.viewFilter.all')}</SelectItem>
239
+ <SelectItem value="watched">{m.common_dependency_table_viewFilter_watched()}</SelectItem>
240
+ <SelectItem value="all">{m.common_dependency_table_viewFilter_all()}</SelectItem>
233
241
  </SelectContent>
234
242
  </Select>
235
243
  <span className="ml-auto text-xs text-muted-foreground">
236
- {t('pagination.total', { total: filtered.length, page: currentPage, totalPages })}
244
+ {m.common_pagination_total({ total: filtered.length, page: currentPage, totalPages })}
237
245
  </span>
238
246
  </div>
239
247
 
@@ -262,7 +270,7 @@ export function DependencyTable({
262
270
  ) : (
263
271
  <TableRow>
264
272
  <TableCell colSpan={columns.length} className="py-10 text-center text-muted-foreground">
265
- {t('dependency.table.empty')}
273
+ {m.common_dependency_table_empty()}
266
274
  </TableCell>
267
275
  </TableRow>
268
276
  )}
@@ -275,9 +283,9 @@ export function DependencyTable({
275
283
  <PaginationContent>
276
284
  <PaginationItem>
277
285
  <PaginationPrevious
278
- text={t('pagination.prev')}
286
+ text={m.common_pagination_prev()}
279
287
  href="#"
280
- onClick={(e) => {
288
+ onClick={(e: React.MouseEvent) => {
281
289
  e.preventDefault()
282
290
  table.previousPage()
283
291
  }}
@@ -288,9 +296,9 @@ export function DependencyTable({
288
296
  {renderPageItems(currentPage, totalPages, (p) => table.setPageIndex(p - 1))}
289
297
  <PaginationItem>
290
298
  <PaginationNext
291
- text={t('pagination.next')}
299
+ text={m.common_pagination_next()}
292
300
  href="#"
293
- onClick={(e) => {
301
+ onClick={(e: React.MouseEvent) => {
294
302
  e.preventDefault()
295
303
  table.nextPage()
296
304
  }}
@@ -342,7 +350,7 @@ function renderPageItems(
342
350
  <PaginationLink
343
351
  href="#"
344
352
  isActive={p === current}
345
- onClick={(e) => {
353
+ onClick={(e: React.MouseEvent) => {
346
354
  e.preventDefault()
347
355
  setPageIndex(p - 1)
348
356
  }}
@@ -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 { toast } from '@/components/ui/sonner'
21
21
  import { Loader2, Pencil } from "lucide-react"
22
22
  import {
@@ -53,7 +53,6 @@ export function EditDependencyDialog({
53
53
  sortOrder: row.sortOrder,
54
54
  })
55
55
  const [saving, setSaving] = useState(false)
56
- const { t } = useTranslation('common')
57
56
 
58
57
  async function handleSave() {
59
58
  setSaving(true)
@@ -71,11 +70,11 @@ export function EditDependencyDialog({
71
70
  sortOrder: form.sortOrder,
72
71
  },
73
72
  })
74
- toast.success(t('dependency.edit.saved'))
73
+ toast.success(m.common_dependency_edit_saved())
75
74
  setOpen(false)
76
75
  onSaved?.()
77
76
  } catch (e) {
78
- toast.error(t('dependency.edit.saveFailed'), { description: e instanceof Error ? e.message : String(e) })
77
+ toast.error(m.common_dependency_edit_saveFailed(), { description: e instanceof Error ? e.message : String(e) })
79
78
  } finally {
80
79
  setSaving(false)
81
80
  }
@@ -89,7 +88,7 @@ export function EditDependencyDialog({
89
88
  <Pencil className="size-3.5 text-muted-foreground/50" />
90
89
  </span>
91
90
  </TooltipTrigger>
92
- <TooltipContent>{t('dependency.edit.untracked')}</TooltipContent>
91
+ <TooltipContent>{m.common_dependency_edit_untracked()}</TooltipContent>
93
92
  </Tooltip>
94
93
  )
95
94
  }
@@ -97,42 +96,42 @@ export function EditDependencyDialog({
97
96
  return (
98
97
  <Dialog open={open} onOpenChange={setOpen}>
99
98
  <DialogTrigger asChild>
100
- <Button size="sm" variant="ghost" className="size-7 p-0" title={t('common.edit')}>
99
+ <Button size="sm" variant="ghost" className="size-7 p-0" title={m.common_common_edit()}>
101
100
  <Pencil className="size-3.5" />
102
101
  </Button>
103
102
  </DialogTrigger>
104
103
  <DialogContent className="max-w-md">
105
104
  <DialogHeader>
106
- <DialogTitle>{t('dependency.edit.dialogTitle', { name: row.name })}</DialogTitle>
105
+ <DialogTitle>{m.common_dependency_edit_dialogTitle({ name: row.name })}</DialogTitle>
107
106
  </DialogHeader>
108
107
  <div className="space-y-3">
109
108
  <div className="space-y-1.5">
110
- <Label>{t('dependency.edit.displayName')}</Label>
111
- <Input value={form.displayName} onChange={(e) => setForm({ ...form, displayName: e.target.value })} />
109
+ <Label>{m.common_dependency_edit_displayName()}</Label>
110
+ <Input value={form.displayName} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm({ ...form, displayName: e.target.value })} />
112
111
  </div>
113
112
  <div className="space-y-1.5">
114
- <Label>{t('dependency.edit.description')}</Label>
113
+ <Label>{m.common_dependency_edit_description()}</Label>
115
114
  <Textarea
116
115
  value={form.description}
117
- onChange={(e) => setForm({ ...form, description: e.target.value })}
118
- placeholder={t('dependency.edit.descriptionPlaceholder')}
116
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setForm({ ...form, description: e.target.value })}
117
+ placeholder={m.common_dependency_edit_descriptionPlaceholder()}
119
118
  rows={2}
120
119
  />
121
120
  </div>
122
121
  <div className="space-y-1.5">
123
- <Label>{t('dependency.edit.homepage')}</Label>
122
+ <Label>{m.common_dependency_edit_homepage()}</Label>
124
123
  <Input
125
124
  value={form.homepage}
126
- onChange={(e) => setForm({ ...form, homepage: e.target.value })}
125
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm({ ...form, homepage: e.target.value })}
127
126
  placeholder="https://"
128
127
  />
129
128
  </div>
130
129
  <div className="grid grid-cols-2 gap-3">
131
130
  <div className="space-y-1.5">
132
- <Label>{t('dependency.edit.category')}</Label>
131
+ <Label>{m.common_dependency_edit_category()}</Label>
133
132
  <Select
134
133
  value={form.category}
135
- onValueChange={(v) => setForm({ ...form, category: v as DependencyCategory })}
134
+ onValueChange={(v: string) => setForm({ ...form, category: v as DependencyCategory })}
136
135
  >
137
136
  <SelectTrigger size="sm">
138
137
  <SelectValue />
@@ -147,10 +146,10 @@ export function EditDependencyDialog({
147
146
  </Select>
148
147
  </div>
149
148
  <div className="space-y-1.5">
150
- <Label>{t('dependency.edit.priority')}</Label>
149
+ <Label>{m.common_dependency_edit_priority()}</Label>
151
150
  <Select
152
151
  value={form.priority}
153
- onValueChange={(v) => setForm({ ...form, priority: v as ThirdPartyDependencyRow["priority"] })}
152
+ onValueChange={(v: string) => setForm({ ...form, priority: v as ThirdPartyDependencyRow["priority"] })}
154
153
  >
155
154
  <SelectTrigger size="sm">
156
155
  <SelectValue />
@@ -170,28 +169,28 @@ export function EditDependencyDialog({
170
169
  type="checkbox"
171
170
  id="isWatched"
172
171
  checked={form.isWatched}
173
- onChange={(e) => setForm({ ...form, isWatched: e.target.checked })}
172
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm({ ...form, isWatched: e.target.checked })}
174
173
  className="size-4"
175
174
  />
176
- <Label htmlFor="isWatched">{t('dependency.edit.watched')}</Label>
175
+ <Label htmlFor="isWatched">{m.common_dependency_edit_watched()}</Label>
177
176
  </div>
178
177
  <div className="space-y-1.5">
179
- <Label>{t('dependency.edit.notes')}</Label>
178
+ <Label>{m.common_dependency_edit_notes()}</Label>
180
179
  <Textarea
181
180
  value={form.notes}
182
- onChange={(e) => setForm({ ...form, notes: e.target.value })}
183
- placeholder={t('dependency.edit.notesPlaceholder')}
181
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setForm({ ...form, notes: e.target.value })}
182
+ placeholder={m.common_dependency_edit_notesPlaceholder()}
184
183
  rows={3}
185
184
  />
186
185
  </div>
187
186
  </div>
188
187
  <DialogFooter>
189
188
  <Button variant="outline" onClick={() => setOpen(false)} disabled={saving}>
190
- {t('common.cancel')}
189
+ {m.common_common_cancel()}
191
190
  </Button>
192
191
  <Button onClick={handleSave} disabled={saving}>
193
192
  {saving && <Loader2 className="mr-1 size-3.5 animate-spin" />}
194
- {t('common.save')}
193
+ {m.common_button_save()}
195
194
  </Button>
196
195
  </DialogFooter>
197
196
  </DialogContent>
@@ -80,7 +80,7 @@ export function BookmarkFormDialog({ open, onOpenChange, item, onSuccess }: Book
80
80
  if (item) {
81
81
  setLoading(true)
82
82
  adminGetBookmarkDetailFn({ data: { id: item.id } })
83
- .then((d) => {
83
+ .then((d: any) => {
84
84
  setForm({
85
85
  title: d.title ?? '',
86
86
  url: d.url ?? '',
@@ -89,7 +89,7 @@ export function BookmarkFormDialog({ open, onOpenChange, item, onSuccess }: Book
89
89
  tags: ((d.tags as string[]) ?? []).join(', '),
90
90
  })
91
91
  })
92
- .catch((e) => toast.error(e instanceof Error ? e.message : '加载失败'))
92
+ .catch((e: unknown) => toast.error(e instanceof Error ? e.message : '加载失败'))
93
93
  .finally(() => setLoading(false))
94
94
  } else {
95
95
  setForm(EMPTY)
@@ -157,7 +157,7 @@ export function BookmarkFormDialog({ open, onOpenChange, item, onSuccess }: Book
157
157
  <Label>标题 *</Label>
158
158
  <Input
159
159
  value={form.title}
160
- onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}
160
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, title: e.target.value }))}
161
161
  placeholder="网站名称"
162
162
  />
163
163
  </div>
@@ -166,7 +166,7 @@ export function BookmarkFormDialog({ open, onOpenChange, item, onSuccess }: Book
166
166
  <Input
167
167
  type="url"
168
168
  value={form.url}
169
- onChange={(e) => setForm((f) => ({ ...f, url: e.target.value }))}
169
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, url: e.target.value }))}
170
170
  placeholder="https://"
171
171
  />
172
172
  <p className="text-xs text-muted-foreground">
@@ -177,7 +177,7 @@ export function BookmarkFormDialog({ open, onOpenChange, item, onSuccess }: Book
177
177
  <Label>分类 ID</Label>
178
178
  <Input
179
179
  value={form.categoryId}
180
- onChange={(e) => setForm((f) => ({ ...f, categoryId: e.target.value }))}
180
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, categoryId: e.target.value }))}
181
181
  placeholder="可选,留空为未分类"
182
182
  />
183
183
  </div>
@@ -185,7 +185,7 @@ export function BookmarkFormDialog({ open, onOpenChange, item, onSuccess }: Book
185
185
  <Label>摘要</Label>
186
186
  <Textarea
187
187
  value={form.summary}
188
- onChange={(e) => setForm((f) => ({ ...f, summary: e.target.value }))}
188
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setForm((f) => ({ ...f, summary: e.target.value }))}
189
189
  rows={2}
190
190
  placeholder="一句话简介"
191
191
  />
@@ -194,7 +194,7 @@ export function BookmarkFormDialog({ open, onOpenChange, item, onSuccess }: Book
194
194
  <Label>标签(逗号分隔)</Label>
195
195
  <Input
196
196
  value={form.tags}
197
- onChange={(e) => setForm((f) => ({ ...f, tags: e.target.value }))}
197
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, tags: e.target.value }))}
198
198
  placeholder="如:工具, AI"
199
199
  />
200
200
  </div>