11use crate :: components:: forbidden:: Forbidden403 ;
22use crate :: types:: PanelPermission ;
33use gloo_net:: http:: Request ;
4+ use i18nrs:: yew:: use_translation;
45use serde:: { Deserialize , Serialize } ;
56use wasm_bindgen_futures:: spawn_local;
67use web_sys:: HtmlInputElement ;
@@ -75,6 +76,7 @@ pub struct CreateApiKeyResponse {
7576
7677#[ function_component( ApiKeysPage ) ]
7778pub fn api_keys_page ( ) -> Html {
79+ let ( i18n, _set_language) = use_translation ( ) ;
7880 let api_keys = use_state ( || Vec :: < ApiKeyListItem > :: new ( ) ) ;
7981 let loading = use_state ( || true ) ;
8082 let error = use_state ( || None :: < String > ) ;
@@ -99,13 +101,13 @@ pub fn api_keys_page() -> Html {
99101 if let Some ( perms) = ( * permissions) . as_ref ( ) {
100102 if !perms. contains ( & PanelPermission :: ManageApiKeys ) {
101103 return html ! {
102- <Forbidden403 required_permission="Gérer les clés API" />
104+ <Forbidden403 required_permission={ i18n . t ( "navbar.apikeys" ) } />
103105 } ;
104106 }
105107 } else {
106108 return html ! {
107109 <div class="flex items-center justify-center min-h-[70vh]" >
108- <div class="text-gray-400 animate-pulse" >{ "Vérification des permissions..." } </div>
110+ <div class="text-gray-400 animate-pulse" >{ i18n . t ( "panel.forbidden.checking_permissions" ) } </div>
109111 </div>
110112 } ;
111113 }
@@ -114,6 +116,7 @@ pub fn api_keys_page() -> Html {
114116 let api_keys = api_keys. clone ( ) ;
115117 let loading = loading. clone ( ) ;
116118 let error = error. clone ( ) ;
119+ let i18n_clone = i18n. clone ( ) ;
117120
118121 use_effect_with ( ( ) , move |_| {
119122 spawn_local ( async move {
@@ -125,14 +128,14 @@ pub fn api_keys_page() -> Html {
125128 api_keys. set ( keys) ;
126129 error. set ( None ) ;
127130 } else {
128- error. set ( Some ( "Failed to parse API keys" . to_string ( ) ) ) ;
131+ error. set ( Some ( i18n_clone . t ( "panel.apikeys.error_parse" ) ) ) ;
129132 }
130133 } else {
131- error. set ( Some ( format ! ( "Failed to load API keys : {}" , resp. status( ) ) ) ) ;
134+ error. set ( Some ( format ! ( "{} : {}" , i18n_clone . t ( "panel.apikeys.error_load" ) , resp. status( ) ) ) ) ;
132135 }
133136 }
134137 Err ( e) => {
135- error. set ( Some ( format ! ( "Network error : {:?}" , e) ) ) ;
138+ error. set ( Some ( format ! ( "{} : {:?}" , i18n_clone . t ( "panel.apikeys.error_network" ) , e) ) ) ;
136139 }
137140 }
138141 loading. set ( false ) ;
@@ -199,20 +202,20 @@ pub fn api_keys_page() -> Html {
199202 html ! {
200203 <div class="space-y-6" >
201204 <div class="flex justify-between items-center" >
202- <h1 class="text-3xl font-bold text-white" >{ "API Keys" } </h1>
205+ <h1 class="text-3xl font-bold text-white" >{ i18n . t ( "panel.apikeys.title" ) } </h1>
203206 <button
204207 onclick={ on_create_click}
205208 class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition"
206209 >
207- { "+ Create API Key" }
210+ { i18n . t ( "panel.apikeys.create" ) }
208211 </button>
209212 </div>
210213
211214 {
212215 if * loading {
213216 html! {
214217 <div class="text-center text-gray-400 py-8" >
215- <p class="animate-pulse" >{ "Loading..." } </p>
218+ <p class="animate-pulse" >{ i18n . t ( "panel.apikeys.loading" ) } </p>
216219 </div>
217220 }
218221 } else if let Some ( err) = ( * error) . clone( ) {
@@ -224,7 +227,7 @@ pub fn api_keys_page() -> Html {
224227 } else if api_keys. is_empty( ) {
225228 html! {
226229 <div class="bg-slate-800 rounded-lg p-8 text-center" >
227- <p class="text-gray-400" >{ "No API keys found. Create your first one!" } </p>
230+ <p class="text-gray-400" >{ i18n . t ( "panel.apikeys.no_keys" ) } </p>
228231 </div>
229232 }
230233 } else {
@@ -273,16 +276,17 @@ pub struct ApiKeyCardProps {
273276
274277#[ function_component( ApiKeyCard ) ]
275278fn api_key_card ( props : & ApiKeyCardProps ) -> Html {
279+ let ( i18n, _set_language) = use_translation ( ) ;
276280 let key = & props. api_key ;
277281 let created_date = chrono:: DateTime :: from_timestamp ( key. created_at , 0 )
278282 . map ( |dt| dt. format ( "%Y-%m-%d %H:%M" ) . to_string ( ) )
279- . unwrap_or_else ( || "Unknown" . to_string ( ) ) ;
283+ . unwrap_or_else ( || i18n . t ( "panel.apikeys.unknown" ) ) ;
280284
281285 let last_used = key
282286 . last_used_at
283287 . and_then ( |ts| chrono:: DateTime :: from_timestamp ( ts, 0 ) )
284288 . map ( |dt| dt. format ( "%Y-%m-%d %H:%M" ) . to_string ( ) )
285- . unwrap_or_else ( || "Never" . to_string ( ) ) ;
289+ . unwrap_or_else ( || i18n . t ( "panel.apikeys.never" ) ) ;
286290
287291 html ! {
288292 <div class="bg-slate-800 rounded-lg p-6 border border-slate-700" >
@@ -297,20 +301,20 @@ fn api_key_card(props: &ApiKeyCardProps) -> Html {
297301 html! {
298302 <>
299303 <span class="px-3 py-1 bg-green-900/30 border border-green-500 text-green-200 rounded-full text-sm" >
300- { "Active" }
304+ { i18n . t ( "panel.apikeys.active" ) }
301305 </span>
302306 <button
303307 onclick={ props. on_revoke. reform( |_| ( ) ) }
304308 class="px-3 py-1 bg-red-900/30 border border-red-500 text-red-200 hover:bg-red-900/50 rounded-md text-sm transition"
305309 >
306- { "Revoke" }
310+ { i18n . t ( "panel.apikeys.revoke" ) }
307311 </button>
308312 </>
309313 }
310314 } else {
311315 html! {
312316 <span class="px-3 py-1 bg-gray-900/30 border border-gray-500 text-gray-400 rounded-full text-sm" >
313- { "Revoked" }
317+ { i18n . t ( "panel.apikeys.revoked" ) }
314318 </span>
315319 }
316320 }
@@ -330,8 +334,8 @@ fn api_key_card(props: &ApiKeyCardProps) -> Html {
330334 } ) . collect:: <Html >( )
331335 }
332336 </div>
333- <p class="text-gray-400" >{ "Created: "} { created_date} </p>
334- <p class="text-gray-400" >{ "Last used: "} { last_used} </p>
337+ <p class="text-gray-400" >{ i18n . t ( "panel.apikeys.created" ) } { " "} { created_date} </p>
338+ <p class="text-gray-400" >{ i18n . t ( "panel.apikeys.last_used" ) } { " "} { last_used} </p>
335339 </div>
336340 </div>
337341 }
@@ -346,6 +350,7 @@ pub struct CreateApiKeyModalProps {
346350
347351#[ function_component( CreateApiKeyModal ) ]
348352fn create_api_key_modal ( props : & CreateApiKeyModalProps ) -> Html {
353+ let ( i18n, _set_language) = use_translation ( ) ;
349354 let name_ref = use_node_ref ( ) ;
350355 let selected_permissions = use_state ( || Vec :: < Permission > :: new ( ) ) ;
351356 let creating = use_state ( || false ) ;
@@ -370,6 +375,7 @@ fn create_api_key_modal(props: &CreateApiKeyModalProps) -> Html {
370375 let creating = creating. clone ( ) ;
371376 let error = error. clone ( ) ;
372377 let on_created = props. on_created . clone ( ) ;
378+ let i18n_clone = i18n. clone ( ) ;
373379
374380 Callback :: from ( move |_| {
375381 let name = name_ref
@@ -378,12 +384,12 @@ fn create_api_key_modal(props: &CreateApiKeyModalProps) -> Html {
378384 . unwrap_or_default ( ) ;
379385
380386 if name. trim ( ) . is_empty ( ) {
381- error. set ( Some ( "Name is required" . to_string ( ) ) ) ;
387+ error. set ( Some ( i18n_clone . t ( "panel.apikeys.modal.error_name_required" ) ) ) ;
382388 return ;
383389 }
384390
385391 if selected_permissions. is_empty ( ) {
386- error. set ( Some ( "At least one permission is required" . to_string ( ) ) ) ;
392+ error. set ( Some ( i18n_clone . t ( "panel.apikeys.modal.error_permission_required" ) ) ) ;
387393 return ;
388394 }
389395
@@ -396,6 +402,7 @@ fn create_api_key_modal(props: &CreateApiKeyModalProps) -> Html {
396402 let creating = creating. clone ( ) ;
397403 let error = error. clone ( ) ;
398404 let on_created = on_created. clone ( ) ;
405+ let i18n_clone2 = i18n_clone. clone ( ) ;
399406
400407 creating. set ( true ) ;
401408 spawn_local ( async move {
@@ -411,14 +418,14 @@ fn create_api_key_modal(props: &CreateApiKeyModalProps) -> Html {
411418 on_created. emit ( response. api_key ) ;
412419 error. set ( None ) ;
413420 } else {
414- error. set ( Some ( "Failed to parse response" . to_string ( ) ) ) ;
421+ error. set ( Some ( i18n_clone2 . t ( "panel.apikeys.modal.error_parse_response" ) ) ) ;
415422 }
416423 } else {
417- error. set ( Some ( format ! ( "Failed to create key : {}" , resp. status( ) ) ) ) ;
424+ error. set ( Some ( format ! ( "{} : {}" , i18n_clone2 . t ( "panel.apikeys.modal.error_create" ) , resp. status( ) ) ) ) ;
418425 }
419426 }
420427 Err ( e) => {
421- error. set ( Some ( format ! ( "Network error : {:?}" , e) ) ) ;
428+ error. set ( Some ( format ! ( "{} : {:?}" , i18n_clone2 . t ( "panel.apikeys.error_network" ) , e) ) ) ;
422429 }
423430 }
424431 creating. set ( false ) ;
@@ -434,27 +441,27 @@ fn create_api_key_modal(props: &CreateApiKeyModalProps) -> Html {
434441 if let Some ( key) = & props. created_key {
435442 html! {
436443 <>
437- <h2 class="text-2xl font-bold text-white" >{ "API Key Created!" } </h2>
444+ <h2 class="text-2xl font-bold text-white" >{ i18n . t ( "panel.apikeys.modal.title_created" ) } </h2>
438445 <div class="bg-yellow-900/20 border border-yellow-500 text-yellow-200 p-4 rounded-md" >
439- <p class="font-semibold mb-2" >{ "⚠️ Important: Save this key now!" } </p>
440- <p class="text-sm" >{ "You won't be able to see it again." } </p>
446+ <p class="font-semibold mb-2" >{ i18n . t ( "panel.apikeys.modal.warning_title" ) } </p>
447+ <p class="text-sm" >{ i18n . t ( "panel.apikeys.modal.warning_message" ) } </p>
441448 </div>
442449 <div class="bg-slate-900 p-4 rounded-md" >
443- <p class="text-xs text-gray-400 mb-2" >{ "Your API Key:" } </p>
450+ <p class="text-xs text-gray-400 mb-2" >{ i18n . t ( "panel.apikeys.modal.your_key" ) } </p>
444451 <code class="text-green-400 font-mono text-sm break-all" >{ key} </code>
445452 </div>
446453 <button
447454 onclick={ props. on_close. reform( |_| ( ) ) }
448455 class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition"
449456 >
450- { "Close" }
457+ { i18n . t ( "panel.apikeys.modal.close" ) }
451458 </button>
452459 </>
453460 }
454461 } else {
455462 html! {
456463 <>
457- <h2 class="text-2xl font-bold text-white" >{ "Create API Key" } </h2>
464+ <h2 class="text-2xl font-bold text-white" >{ i18n . t ( "panel.apikeys.modal.title_create" ) } </h2>
458465
459466 {
460467 if let Some ( err) = ( * error) . clone( ) {
@@ -469,17 +476,17 @@ fn create_api_key_modal(props: &CreateApiKeyModalProps) -> Html {
469476 }
470477
471478 <div>
472- <label class="block text-sm font-medium text-gray-300 mb-2" >{ "Name" } </label>
479+ <label class="block text-sm font-medium text-gray-300 mb-2" >{ i18n . t ( "panel.apikeys.modal.name" ) } </label>
473480 <input
474481 ref={ name_ref}
475482 type ="text"
476- placeholder="My API Key"
483+ placeholder={ i18n . t ( "panel.apikeys.modal.name_placeholder" ) }
477484 class="w-full px-4 py-2 bg-slate-900 border border-slate-700 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
478485 />
479486 </div>
480487
481488 <div>
482- <label class="block text-sm font-medium text-gray-300 mb-2" >{ "Permissions" } </label>
489+ <label class="block text-sm font-medium text-gray-300 mb-2" >{ i18n . t ( "panel.apikeys.modal.permissions" ) } </label>
483490 <div class="space-y-2" >
484491 {
485492 Permission :: all( ) . iter( ) . map( |perm| {
@@ -508,14 +515,14 @@ fn create_api_key_modal(props: &CreateApiKeyModalProps) -> Html {
508515 disabled={ * creating}
509516 class="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 text-white rounded-md transition disabled:opacity-50"
510517 >
511- { "Cancel" }
518+ { i18n . t ( "panel.apikeys.modal.cancel" ) }
512519 </button>
513520 <button
514521 onclick={ on_create}
515522 disabled={ * creating}
516523 class="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition disabled:opacity-50"
517524 >
518- { if * creating { "Creating..." } else { "Create" } }
525+ { if * creating { i18n . t ( "panel.apikeys.modal.creating" ) } else { i18n . t ( "panel.apikeys.modal.create_button" ) } }
519526 </button>
520527 </div>
521528 </>
0 commit comments