1+ import { cookies } from "next/headers" ;
2+ import { getUiCopy } from "@cortexpilot/frontend-shared/uiCopy" ;
3+ import { normalizeUiLocale , UI_LOCALE_STORAGE_KEY } from "@cortexpilot/frontend-shared/uiLocale" ;
14import { Badge } from "../../components/ui/badge" ;
25import { Button } from "../../components/ui/button" ;
36import { Card , CardContent , CardHeader } from "../../components/ui/card" ;
@@ -20,11 +23,22 @@ function summarizeToolPermissions(value: unknown): string[] {
2023
2124const DEFAULT_CONTRACT_LIMIT = 10 ;
2225
26+ async function resolveDashboardLocale ( ) {
27+ try {
28+ const cookieStore = await cookies ( ) ;
29+ return normalizeUiLocale ( cookieStore . get ( UI_LOCALE_STORAGE_KEY ) ?. value ) ;
30+ } catch {
31+ return normalizeUiLocale ( undefined ) ;
32+ }
33+ }
34+
2335export default async function ContractsPage ( {
2436 searchParams,
2537} : {
2638 searchParams ?: Promise < { q ?: string ; limit ?: string } > ;
2739} ) {
40+ const locale = await resolveDashboardLocale ( ) ;
41+ const contractsPageCopy = getUiCopy ( locale ) . dashboard . contractsPage ;
2842 const { data : contracts , warning } = await safeLoad ( fetchContracts , [ ] as ContractRecord [ ] , "Contract list" ) ;
2943 const resolvedSearchParams = ( await searchParams ) || { } ;
3044 const query = String ( resolvedSearchParams . q || "" ) . trim ( ) . toLowerCase ( ) ;
@@ -44,34 +58,40 @@ export default async function ContractsPage({
4458 < header className = "app-section" >
4559 < div className = "section-header" >
4660 < div >
47- < h1 id = "contracts-page-title" className = "page-title" > Contracts </ h1 >
48- < p className = "page-subtitle" > Inspect task-contract envelopes, derived bundle/runtime projections, and raw artifact detail in one read-only surface. </ p >
61+ < h1 id = "contracts-page-title" className = "page-title" > { contractsPageCopy . title } </ h1 >
62+ < p className = "page-subtitle" > { contractsPageCopy . subtitle } </ p >
4963 </ div >
50- < Badge > { contracts . length } contracts </ Badge >
64+ < Badge > { contractsPageCopy . countsBadge ( contracts . length ) } </ Badge >
5165 </ div >
5266 </ header >
5367 < section className = "app-section" aria-label = "Contract list" >
5468 < Card >
5569 < form className = "toolbar" method = "get" >
5670 < label className = "diff-gate-filter-field" >
57- < span className = "muted" > Search </ span >
58- < Input type = "search" name = "q" defaultValue = { query } placeholder = "Filter by task_id / run_id / path" />
71+ < span className = "muted" > { contractsPageCopy . searchLabel } </ span >
72+ < Input type = "search" name = "q" defaultValue = { query } placeholder = { contractsPageCopy . searchPlaceholder } />
5973 </ label >
6074 < input type = "hidden" name = "limit" value = { String ( limit ) } />
61- < Button type = "submit" variant = "secondary" disabled = { ! canApplyFilter } > Apply filter </ Button >
75+ < Button type = "submit" variant = "secondary" disabled = { ! canApplyFilter } > { contractsPageCopy . applyFilter } </ Button >
6276 </ form >
63- < p className = "mono muted" > Showing { visibleContracts . length } / { filteredContracts . length } contracts. Default first-page limit: { DEFAULT_CONTRACT_LIMIT } .</ p >
77+ < p className = "mono muted" >
78+ { contractsPageCopy . filterSummary ( visibleContracts . length , filteredContracts . length , DEFAULT_CONTRACT_LIMIT ) }
79+ </ p >
6480 </ Card >
6581 { warning ? (
6682 < Card asChild variant = "unstyled" >
67- < p className = "alert alert-warning" role = "status" > { warning } </ p >
83+ < div className = "alert alert-warning" role = "status" >
84+ < p > { contractsPageCopy . warningTitle } </ p >
85+ < p className = "mono" > { contractsPageCopy . warningNextStep } </ p >
86+ < p className = "mono" > { warning } </ p >
87+ </ div >
6888 </ Card >
6989 ) : null }
7090 { filteredContracts . length === 0 ? (
7191 < Card >
7292 < div className = "empty-state-stack" >
73- < span className = "muted" > No contracts yet </ span >
74- < span className = "mono muted" > Contracts are generated automatically when work is assigned. </ span >
93+ < span className = "muted" > { contractsPageCopy . emptyTitle } </ span >
94+ < span className = "mono muted" > { contractsPageCopy . emptyHint } </ span >
7595 </ div >
7696 </ Card >
7797 ) : (
@@ -87,65 +107,81 @@ export default async function ContractsPage({
87107 return (
88108 < Card key = { key } variant = "detail" >
89109 < CardHeader >
90- < span className = "card-header-title" > { contract . path || contract . task_id || "Contract" } </ span >
91- < Badge > { contract . source || "unknown" } </ Badge >
110+ < span className = "card-header-title" >
111+ { contract . path || contract . task_id || contractsPageCopy . fallbackValues . unknownContract }
112+ </ span >
113+ < Badge > { contract . source || contractsPageCopy . fallbackValues . unknownSource } </ Badge >
92114 </ CardHeader >
93115 < CardContent >
94116 < div className = "data-list" >
95117 { contract . task_id ? (
96118 < div className = "data-list-row" >
97- < span className = "data-list-label" > Task ID </ span >
119+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . taskId } </ span >
98120 < span className = "data-list-value mono" > { contract . task_id } </ span >
99121 </ div >
100122 ) : null }
101123 { contract . run_id ? (
102124 < div className = "data-list-row" >
103- < span className = "data-list-label" > Run ID </ span >
125+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . runId } </ span >
104126 < span className = "data-list-value mono" > { contract . run_id } </ span >
105127 </ div >
106128 ) : null }
107129 < div className = "data-list-row" >
108- < span className = "data-list-label" > Assigned role</ span >
109- < span className = "data-list-value" > { contract . assigned_role || "Not assigned" } </ span >
130+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . assignedRole } </ span >
131+ < span className = "data-list-value" >
132+ { contract . assigned_role || contractsPageCopy . fallbackValues . notAssigned }
133+ </ span >
110134 </ div >
111135 < div className = "data-list-row" >
112- < span className = "data-list-label" > Execution authority </ span >
136+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . executionAuthority } </ span >
113137 < span className = "data-list-value" >
114- { contract . execution_authority ? < Badge variant = "running" > { contract . execution_authority } </ Badge > : < span className = "muted" > Not published</ span > }
138+ { contract . execution_authority ? (
139+ < Badge variant = "running" > { contract . execution_authority } </ Badge >
140+ ) : (
141+ < span className = "muted" > { contractsPageCopy . fallbackValues . notPublished } </ span >
142+ ) }
115143 </ span >
116144 </ div >
117145 < div className = "data-list-row" >
118- < span className = "data-list-label" > Skills bundle </ span >
146+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . skillsBundle } </ span >
119147 < span className = "data-list-value mono muted" >
120- { roleBinding ? formatBindingReadModelLabel ( roleBinding . skills_bundle_ref ) : "Not derived" }
148+ { roleBinding
149+ ? formatBindingReadModelLabel ( roleBinding . skills_bundle_ref )
150+ : contractsPageCopy . fallbackValues . notDerived }
121151 </ span >
122152 </ div >
123153 < div className = "data-list-row" >
124- < span className = "data-list-label" > MCP bundle </ span >
154+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . mcpBundle } </ span >
125155 < span className = "data-list-value mono muted" >
126- { roleBinding ? formatBindingReadModelLabel ( roleBinding . mcp_bundle_ref ) : "Not derived" }
156+ { roleBinding
157+ ? formatBindingReadModelLabel ( roleBinding . mcp_bundle_ref )
158+ : contractsPageCopy . fallbackValues . notDerived }
127159 </ span >
128160 </ div >
129161 < div className = "data-list-row" >
130- < span className = "data-list-label" > Runtime binding </ span >
162+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . runtimeBinding } </ span >
131163 < span className = "data-list-value mono muted" >
132- { roleBinding ? formatRoleBindingRuntimeSummary ( roleBinding ) : "Not derived" }
164+ { roleBinding
165+ ? formatRoleBindingRuntimeSummary ( roleBinding )
166+ : contractsPageCopy . fallbackValues . notDerived }
133167 </ span >
134168 </ div >
135169 < div className = "data-list-row" >
136- < span className = "data-list-label" > Runtime capability </ span >
170+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . runtimeCapability } </ span >
137171 < span className = "data-list-value mono muted" >
138- { roleBinding ?. runtime_binding ?. capability ?. lane || "Not derived" }
172+ { roleBinding ?. runtime_binding ?. capability ?. lane || contractsPageCopy . fallbackValues . notDerived }
139173 </ span >
140174 </ div >
141175 < div className = "data-list-row" >
142- < span className = "data-list-label" > Tool execution </ span >
176+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . toolExecution } </ span >
143177 < span className = "data-list-value mono muted" >
144- { roleBinding ? formatRoleBindingRuntimeCapabilitySummary ( roleBinding ) : "Not derived" }
178+ { roleBinding
179+ ? formatRoleBindingRuntimeCapabilitySummary ( roleBinding )
180+ : contractsPageCopy . fallbackValues . notDerived }
145181 </ span >
146182 </ div >
147183 < div className = "data-list-row" >
148- < span className = "data-list-label" > Allowed paths </ span >
184+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . allowedPaths } </ span >
149185 < span className = "data-list-value" >
150186 { allowedPaths . length > 0 ? (
151187 < span className = "chip-list" >
@@ -154,12 +190,12 @@ export default async function ContractsPage({
154190 ) ) }
155191 </ span >
156192 ) : (
157- < span className = "muted" > Unrestricted </ span >
193+ < span className = "muted" > { contractsPageCopy . fallbackValues . unrestricted } </ span >
158194 ) }
159195 </ span >
160196 </ div >
161197 < div className = "data-list-row" >
162- < span className = "data-list-label" > Acceptance tests </ span >
198+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . acceptanceTests } </ span >
163199 < span className = "data-list-value" >
164200 { acceptanceTests . length > 0 ? (
165201 < span className = "chip-list" >
@@ -168,12 +204,12 @@ export default async function ContractsPage({
168204 ) ) }
169205 </ span >
170206 ) : (
171- < span className = "muted" > None </ span >
207+ < span className = "muted" > { contractsPageCopy . fallbackValues . noAcceptanceTests } </ span >
172208 ) }
173209 </ span >
174210 </ div >
175211 < div className = "data-list-row" >
176- < span className = "data-list-label" > Tool permissions </ span >
212+ < span className = "data-list-label" > { contractsPageCopy . fieldLabels . toolPermissions } </ span >
177213 < span className = "data-list-value" >
178214 { permissionSummary . length > 0 ? (
179215 < span className = "chip-list" >
@@ -182,14 +218,14 @@ export default async function ContractsPage({
182218 ) ) }
183219 </ span >
184220 ) : (
185- < span className = "muted" > Default </ span >
221+ < span className = "muted" > { contractsPageCopy . fallbackValues . defaultPermissions } </ span >
186222 ) }
187223 </ span >
188224 </ div >
189225 </ div >
190226 </ CardContent >
191227 < details className = "collapsible" >
192- < summary > Full contract JSON </ summary >
228+ < summary > { contractsPageCopy . fullJsonSummary } </ summary >
193229 < div className = "collapsible-body" >
194230 < pre className = "mono" > { JSON . stringify ( payload || { raw_preview : contract . raw_preview } , null , 2 ) } </ pre >
195231 </ div >
@@ -201,10 +237,12 @@ export default async function ContractsPage({
201237 ) }
202238 { filteredContracts . length > limit ? (
203239 < Card >
204- < p className = "mono muted" > { filteredContracts . length - limit } more contracts are hidden. </ p >
240+ < p className = "mono muted" > { contractsPageCopy . moreHidden ( filteredContracts . length - limit ) } </ p >
205241 < div className = "toolbar mt-2" >
206242 < Button asChild variant = "secondary" >
207- < a href = { `/contracts?${ new URLSearchParams ( { q : query , limit : String ( filteredContracts . length ) } ) . toString ( ) } ` } > Show all</ a >
243+ < a href = { `/contracts?${ new URLSearchParams ( { q : query , limit : String ( filteredContracts . length ) } ) . toString ( ) } ` } >
244+ { contractsPageCopy . showAll }
245+ </ a >
208246 </ Button >
209247 </ div >
210248 </ Card >
0 commit comments