3131from ..core .project_map import render_project_map
3232from ..core .service_resolver import ServiceResolver
3333from ..core .services import SERVICES , get_service_dependencies
34+ from ..i18n import t
35+
36+
37+ def _translated_component_desc (name : str , fallback : str ) -> str :
38+ """Get translated description for a component, with fallback."""
39+ key = f"component.{ name } "
40+ result = t (key )
41+ return result if result != key else fallback
42+
43+
44+ def _translated_service_desc (name : str , fallback : str ) -> str :
45+ """Get translated description for a service, with fallback."""
46+ key = f"service.{ name } "
47+ result = t (key )
48+ return result if result != key else fallback
3449
3550
3651def add_service_command (
@@ -70,7 +85,7 @@ def add_service_command(
7085 (the default since v0.2.0). Services may auto-add required components.
7186 """
7287
73- typer .secho ( "Aegis Stack - Add Services" , fg = typer . colors . BLUE , bold = True )
88+ typer .echo ( t ( "add_service.title" ) )
7489 typer .echo ("=" * 50 )
7590
7691 # Resolve project path
@@ -79,33 +94,26 @@ def add_service_command(
7994 # Validate it's a Copier project
8095 validate_copier_project (target_path , "add-service" )
8196
82- typer .echo (f" { typer . style ( 'Project:' , fg = typer . colors . CYAN ) } { target_path } " )
97+ typer .echo (t ( "add_service.project" , path = target_path ) )
8398
8499 # Validate services argument or interactive mode
85100 if not interactive and not services :
86- typer .secho (
87- "Error: services argument is required (or use --interactive)" ,
88- fg = "red" ,
89- err = True ,
90- )
91- typer .echo (" Usage: aegis add service auth,ai" , err = True )
92- typer .echo (" Or: aegis add service --interactive" , err = True )
101+ typer .secho (t ("add_service.error_no_args" ), fg = "red" , err = True )
102+ typer .echo (f" { t ('add_service.usage_hint' )} " , err = True )
103+ typer .echo (f" { t ('add_service.interactive_hint' )} " , err = True )
93104 raise typer .Exit (1 )
94105
95106 # Interactive mode
96107 if interactive :
97108 if services :
98- typer .secho (
99- "Warning: --interactive flag ignores service arguments" ,
100- fg = "yellow" ,
101- )
109+ typer .secho (t ("add_service.interactive_ignores_args" ), fg = "yellow" )
102110
103111 from ..cli .interactive import interactive_service_selection
104112
105113 selected_services = interactive_service_selection (target_path )
106114
107115 if not selected_services :
108- typer .secho (" \n No services selected " , fg = "green" )
116+ typer .secho (f" \n { t ( 'add_service.no_selected' ) } " , fg = "green" )
109117 raise typer .Exit (0 )
110118
111119 # Convert to comma-separated string for existing logic
@@ -134,15 +142,17 @@ def add_service_command(
134142 for error in errors :
135143 typer .secho (f"{ error } " , fg = "red" , err = True )
136144 raise typer .Exit (1 )
145+ except typer .Exit :
146+ raise
137147 except Exception as e :
138- typer .secho (f"Service validation failed: { e } " , fg = "red" , err = True )
148+ typer .secho (t ( "add_service.validation_failed" , error = e ) , fg = "red" , err = True )
139149 raise typer .Exit (1 )
140150
141151 # Load existing project configuration
142152 try :
143153 existing_answers = load_copier_answers (target_path )
144154 except Exception as e :
145- typer .secho (f"Failed to load project configuration: { e } " , fg = "red" , err = True )
155+ typer .secho (t ( "add_service.load_config_failed" , error = e ) , fg = "red" , err = True )
146156 raise typer .Exit (1 )
147157
148158 # Check which services are already enabled
@@ -155,13 +165,15 @@ def add_service_command(
155165 already_enabled .append (service )
156166
157167 if already_enabled :
158- typer .echo (f"Already enabled: { ', ' .join (already_enabled )} " , err = False )
168+ typer .echo (
169+ t ("add_service.already_enabled" , services = ", " .join (already_enabled ))
170+ )
159171
160172 # Filter out already enabled services
161173 services_to_add = [s for s in selected_services if s not in already_enabled ]
162174
163175 if not services_to_add :
164- typer .secho ("All requested services are already enabled!" , fg = "green" )
176+ typer .secho (t ( "add_service.all_enabled" ) , fg = "green" )
165177 raise typer .Exit (0 )
166178
167179 # Handle AI service interactive configuration
@@ -214,7 +226,7 @@ def add_service_command(
214226 services_to_add
215227 )
216228 except ValueError as e :
217- typer .secho (f"Failed to resolve service dependencies: { e } " , fg = "red" , err = True )
229+ typer .secho (t ( "add_service.resolve_failed" , error = e ) , fg = "red" , err = True )
218230 raise typer .Exit (1 )
219231
220232 # If AI service selected SQLite backend, ensure database is in required components
@@ -236,36 +248,40 @@ def add_service_command(
236248 missing_components .append (component )
237249
238250 # Show what will be added
239- typer .secho ("\n Services to add:" , fg = typer .colors .CYAN , bold = True )
251+ typer .secho (
252+ f"\n { t ('add_service.services_to_add' )} " , fg = typer .colors .CYAN , bold = True
253+ )
240254 for service in services_to_add :
241255 base_service = service_base_map [service ]
242256 if base_service in SERVICES :
243- desc = SERVICES [base_service ].description
257+ desc = _translated_service_desc (
258+ base_service , SERVICES [base_service ].description
259+ )
244260 typer .echo (f" • { service } : { desc } " )
245261
246262 # Show component requirements
247263 if missing_components :
248- typer .secho (
249- "\n Required components (will be auto-added):" , fg = typer .colors .YELLOW
250- )
264+ typer .secho (f"\n { t ('add_service.required_components' )} " , fg = typer .colors .YELLOW )
251265 for component in missing_components :
252266 if component in COMPONENTS :
253- desc = COMPONENTS [component ].description
267+ desc = _translated_component_desc (
268+ component , COMPONENTS [component ].description
269+ )
254270 typer .echo (f" • { component } : { desc } " )
255271
256272 if enabled_components :
257273 # Filter out core components from display
258274 non_core_enabled = [c for c in enabled_components if c not in CORE_COMPONENTS ]
259275 if non_core_enabled :
260276 typer .secho (
261- f"\n Already have required components: { ', ' .join (non_core_enabled )} " ,
277+ f"\n { t ( 'add_service.already_have_components' , components = ', ' .join (non_core_enabled ) )} " ,
262278 fg = "green" ,
263279 )
264280
265281 # Confirm before proceeding
266282 typer .echo ()
267- if not yes and not typer .confirm ("Add these services?" , default = True ):
268- typer .secho ("Operation cancelled" , fg = "red" )
283+ if not yes and not typer .confirm (t ( "add_service.confirm" ) , default = True ):
284+ typer .secho (t ( "shared.operation_cancelled" ) , fg = "red" )
269285 raise typer .Exit (0 )
270286
271287 # Prepare update data for ManualUpdater
@@ -284,14 +300,14 @@ def add_service_command(
284300 update_data [include_key ] = True
285301
286302 # Add services using ManualUpdater
287- typer .secho ("\n Updating project..." , fg = typer .colors .CYAN , bold = True )
288303 try :
289304 updater = ManualUpdater (target_path )
290305
291306 # Add missing components first
292307 for component in missing_components :
293308 typer .secho (
294- f"\n Adding required component: { component } ..." , fg = typer .colors .CYAN
309+ f"\n { t ('add_service.adding_component' , component = component )} " ,
310+ fg = typer .colors .CYAN ,
295311 )
296312
297313 # Prepare component-specific data
@@ -312,23 +328,33 @@ def add_service_command(
312328
313329 if not result .success :
314330 typer .secho (
315- f"Failed to add component { component } : { result .error_message } " ,
331+ t (
332+ "add_service.failed_component" ,
333+ component = component ,
334+ error = result .error_message ,
335+ ),
316336 fg = "red" ,
317337 err = True ,
318338 )
319339 raise typer .Exit (1 )
320340
321341 if result .files_modified :
322- typer .secho (f" Added { len (result .files_modified )} files" , fg = "green" )
342+ typer .secho (
343+ f" { t ('add_service.added_files' , count = len (result .files_modified ))} " ,
344+ fg = "green" ,
345+ )
323346 if result .files_skipped :
324347 typer .secho (
325- f" Skipped { len (result .files_skipped )} existing files " ,
348+ f" { t ( 'add_service.skipped_files' , count = len (result .files_skipped )) } " ,
326349 fg = "yellow" ,
327350 )
328351
329352 # Now add each service sequentially
330353 for service in services_to_add :
331- typer .secho (f"\n Adding service: { service } ..." , fg = typer .colors .CYAN )
354+ typer .secho (
355+ f"\n { t ('add_service.adding_service' , service = service )} " ,
356+ fg = typer .colors .CYAN ,
357+ )
332358
333359 # Prepare service-specific data
334360 service_data : dict [str , bool | str ] = {}
@@ -361,18 +387,25 @@ def add_service_command(
361387
362388 if not result .success :
363389 typer .secho (
364- f"Failed to add service { service } : { result .error_message } " ,
390+ t (
391+ "add_service.failed_service" ,
392+ service = service ,
393+ error = result .error_message ,
394+ ),
365395 fg = "red" ,
366396 err = True ,
367397 )
368398 raise typer .Exit (1 )
369399
370400 # Show results
371401 if result .files_modified :
372- typer .secho (f" Added { len (result .files_modified )} files" , fg = "green" )
402+ typer .secho (
403+ f" { t ('add_service.added_files' , count = len (result .files_modified ))} " ,
404+ fg = "green" ,
405+ )
373406 if result .files_skipped :
374407 typer .secho (
375- f" Skipped { len (result .files_skipped )} existing files " ,
408+ f" { t ( 'add_service.skipped_files' , count = len (result .files_skipped )) } " ,
376409 fg = "yellow" ,
377410 )
378411
@@ -391,19 +424,21 @@ def add_service_command(
391424 alembic_dir = target_path / "alembic"
392425 if not alembic_dir .exists ():
393426 typer .secho (
394- "\n Bootstrapping alembic infrastructure..." , fg = typer .colors .CYAN
427+ f"\n { t ('add_service.bootstrap_alembic' )} " ,
428+ fg = typer .colors .CYAN ,
395429 )
396430 created = bootstrap_alembic (
397431 target_path , updater .jinja_env , updater .answers
398432 )
399433 for f in created :
400- typer .echo (f" Created: { f } " )
434+ typer .echo (f" { t ( 'add_service.created_file' , file = f ) } " )
401435
402436 if not service_has_migration (target_path , base_service ):
403437 migration_path = generate_migration (target_path , base_service )
404438 if migration_path :
405439 typer .secho (
406- f" Generated migration: { migration_path .name } " , fg = "green"
440+ f" { t ('add_service.generated_migration' , name = migration_path .name )} " ,
441+ fg = "green" ,
407442 )
408443
409444 # Auto-run migrations for services that need them
@@ -418,18 +453,17 @@ def add_service_command(
418453 and (service_base_map [s ] != AnswerKeys .SERVICE_AI or ai_needs_migrations )
419454 ]
420455 if services_with_migrations :
421- typer .secho ("\n Applying database migrations..." , fg = typer .colors .CYAN )
456+ typer .secho (
457+ f"\n { t ('add_service.applying_migrations' )} " , fg = typer .colors .CYAN
458+ )
422459 from ..core .post_gen_tasks import run_migrations
423460
424461 migration_success = run_migrations (target_path , include_migrations = True )
425462
426463 if not migration_success :
427- typer .secho (
428- "Warning: Auto-migration failed. Run 'make migrate' manually." ,
429- fg = "yellow" ,
430- )
464+ typer .secho (t ("add_service.migration_failed" ), fg = "yellow" )
431465
432- typer .secho (" \n Services added successfully! " , fg = "green" )
466+ typer .secho (f" \n { t ( 'add_service.success' ) } " , fg = "green" )
433467
434468 # Show project map with newly added services + auto-added components highlighted
435469 base_services_for_highlight = [service_base_map [s ] for s in services_to_add ]
@@ -463,24 +497,34 @@ def add_service_command(
463497
464498 if AnswerKeys .SERVICE_AUTH in base_services_added :
465499 project_slug = existing_answers .get (AnswerKeys .PROJECT_SLUG , "my-project" )
466- typer .secho ("\n Auth Service Setup:" , fg = typer .colors .CYAN , bold = True )
500+ typer .secho (
501+ f"\n { t ('add_service.auth_setup' )} " , fg = typer .colors .CYAN , bold = True
502+ )
467503 cmd = typer .style (f"{ project_slug } auth create-test-users" , bold = True )
468- typer .echo (f" 1. Create test users: { cmd } " )
504+ typer .echo (t ( "add_service.auth_create_users" , cmd = cmd ) )
469505 url = typer .style ("http://localhost:8000/docs" , bold = True )
470- typer .echo (f" 2. View auth routes: { url } " )
506+ typer .echo (t ( "add_service.auth_view_routes" , url = url ) )
471507
472508 if AnswerKeys .SERVICE_AI in base_services_added :
473509 project_slug = existing_answers .get (AnswerKeys .PROJECT_SLUG , "my-project" )
474- typer .secho ("\n AI Service Setup:" , fg = typer .colors .CYAN , bold = True )
510+ typer .secho (
511+ f"\n { t ('add_service.ai_setup' )} " , fg = typer .colors .CYAN , bold = True
512+ )
475513 typer .echo (
476- f" 1. Set { typer .style ('AI_PROVIDER' , bold = True )} in .env (openai, anthropic, google, groq)"
514+ t (
515+ "add_service.ai_set_provider" ,
516+ env_var = typer .style ("AI_PROVIDER" , bold = True ),
517+ )
477518 )
478519 typer .echo (
479- f" 2. Set provider API key ({ typer .style ('OPENAI_API_KEY' , bold = True )} , etc.)"
520+ t (
521+ "add_service.ai_set_api_key" ,
522+ env_var = typer .style ("OPENAI_API_KEY" , bold = True ),
523+ )
480524 )
481525 cmd = typer .style (f"{ project_slug } ai chat" , bold = True )
482- typer .echo (f" 3. Test with CLI: { cmd } " )
526+ typer .echo (t ( "add_service.ai_test_cli" , cmd = cmd ) )
483527
484528 except Exception as e :
485- typer .secho (f"\n Failed to add services: { e } " , fg = "red" , err = True )
529+ typer .secho (f"\n { t ( 'add_service.failed' , error = e ) } " , fg = "red" , err = True )
486530 raise typer .Exit (1 )
0 commit comments