@@ -16,21 +16,65 @@ import {
1616 Badge ,
1717 Skeleton ,
1818} from '@object-ui/components' ;
19+ import { useObjectTranslation } from '@object-ui/i18n' ;
1920import { Settings as SettingsIcon } from 'lucide-react' ;
2021import { getIcon } from '../../utils/getIcon' ;
2122import { listSettingsManifests } from './api' ;
2223import { resolveLabel , type SettingsManifest } from './types' ;
24+ import { useSettingsLabel } from './useSettingsLabel' ;
25+
26+ /**
27+ * One manifest card. Extracted so it can resolve the manifest's own
28+ * translated title/description via {@link useSettingsLabel} (hooks can't be
29+ * called inside a `.map()` callback in the parent).
30+ */
31+ function SettingCard ( { m, onOpen } : { m : SettingsManifest ; onOpen : ( ) => void } ) {
32+ const { t } = useObjectTranslation ( ) ;
33+ const labels = useSettingsLabel ( m . namespace ) ;
34+ const Icon = m . icon ? getIcon ( m . icon ) : SettingsIcon ;
35+ const literalLabel = resolveLabel ( m . label ) ;
36+ const title = labels . title ( literalLabel ) ;
37+ const description = labels . description ( m . description ?? undefined ) ;
38+
39+ return (
40+ < Card
41+ className = "cursor-pointer hover:border-primary/50 hover:shadow-sm transition-all"
42+ onClick = { onOpen }
43+ >
44+ < CardHeader className = "pb-3" >
45+ < div className = "flex items-start justify-between" >
46+ < Icon className = "h-6 w-6 text-muted-foreground" />
47+ { m . beta ? (
48+ < Badge variant = "secondary" className = "text-[10px]" >
49+ { t ( 'console.settingsHub.beta' ) }
50+ </ Badge >
51+ ) : null }
52+ </ div >
53+ < CardTitle className = "text-base mt-2" > { title } </ CardTitle >
54+ { description ? < CardDescription className = "text-xs" > { description } </ CardDescription > : null }
55+ </ CardHeader >
56+ < CardContent className = "pt-0" >
57+ < div className = "text-[11px] text-muted-foreground" >
58+ { t ( 'console.settingsHub.settingsCount' , { n : m . specifiers . length } ) }
59+ </ div >
60+ </ CardContent >
61+ </ Card >
62+ ) ;
63+ }
2364
2465export function SettingsHub ( ) {
2566 const navigate = useNavigate ( ) ;
67+ const { t } = useObjectTranslation ( ) ;
2668 const [ manifests , setManifests ] = useState < SettingsManifest [ ] | null > ( null ) ;
2769 const [ error , setError ] = useState < string | null > ( null ) ;
2870
2971 useEffect ( ( ) => {
3072 listSettingsManifests ( )
3173 . then ( ( r ) => setManifests ( r . manifests ?? [ ] ) )
32- . catch ( ( err ) => setError ( err ?. message ?? 'Failed to load settings' ) ) ;
33- } , [ ] ) ;
74+ . catch ( ( err ) =>
75+ setError ( err ?. message ?? t ( 'console.settingsHub.loadError' ) ) ,
76+ ) ;
77+ } , [ t ] ) ;
3478
3579 const byCategory = useMemo ( ( ) => {
3680 if ( ! manifests ) return null ;
@@ -49,8 +93,8 @@ export function SettingsHub() {
4993 < div className = "flex items-center gap-3 mb-6" >
5094 < SettingsIcon className = "h-7 w-7 text-muted-foreground" />
5195 < div >
52- < h1 className = "text-2xl font-semibold tracking-tight" > Settings </ h1 >
53- < p className = "text-sm text-muted-foreground" > Configure your workspace, integrations, and feature flags. </ p >
96+ < h1 className = "text-2xl font-semibold tracking-tight" > { t ( 'console.settingsHub.title' ) } </ h1 >
97+ < p className = "text-sm text-muted-foreground" > { t ( 'console.settingsHub.subtitle' ) } </ p >
5498 </ div >
5599 </ div >
56100
@@ -72,42 +116,23 @@ export function SettingsHub() {
72116 ) : manifests . length === 0 ? (
73117 < Card >
74118 < CardContent className = "py-12 text-center text-sm text-muted-foreground" >
75- No settings registered. Plugins can register settings manifests via the SettingsService.
119+ { t ( 'console.settingsHub.empty' ) }
76120 </ CardContent >
77121 </ Card >
78122 ) : (
79123 Array . from ( byCategory ?? [ ] ) . map ( ( [ category , items ] ) => (
80124 < section key = { category } className = "mb-8" >
81125 < h2 className = "text-sm font-semibold tracking-wide uppercase text-muted-foreground mb-3" >
82- { category }
126+ { t ( `console.settingsHub.categories. ${ category } ` , { defaultValue : category } ) }
83127 </ h2 >
84128 < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" >
85- { items . map ( ( m ) => {
86- const Icon = m . icon ? getIcon ( m . icon ) : SettingsIcon ;
87- return (
88- < Card
89- key = { m . namespace }
90- className = "cursor-pointer hover:border-primary/50 hover:shadow-sm transition-all"
91- onClick = { ( ) => navigate ( `/system/settings/${ m . namespace } ` ) }
92- >
93- < CardHeader className = "pb-3" >
94- < div className = "flex items-start justify-between" >
95- < Icon className = "h-6 w-6 text-muted-foreground" />
96- { m . beta ? < Badge variant = "secondary" className = "text-[10px]" > Beta</ Badge > : null }
97- </ div >
98- < CardTitle className = "text-base mt-2" > { resolveLabel ( m . label ) } </ CardTitle >
99- { m . description ? (
100- < CardDescription className = "text-xs" > { m . description } </ CardDescription >
101- ) : null }
102- </ CardHeader >
103- < CardContent className = "pt-0" >
104- < div className = "text-[11px] text-muted-foreground" >
105- { m . specifiers . length } setting{ m . specifiers . length === 1 ? '' : 's' }
106- </ div >
107- </ CardContent >
108- </ Card >
109- ) ;
110- } ) }
129+ { items . map ( ( m ) => (
130+ < SettingCard
131+ key = { m . namespace }
132+ m = { m }
133+ onOpen = { ( ) => navigate ( `/system/settings/${ m . namespace } ` ) }
134+ />
135+ ) ) }
111136 </ div >
112137 </ section >
113138 ) )
0 commit comments