diff --git a/cmd/ai/ai_git.go b/cmd/ai/ai_git.go index 560812c..fe1e6c7 100644 --- a/cmd/ai/ai_git.go +++ b/cmd/ai/ai_git.go @@ -12,6 +12,7 @@ import ( "time" "github.com/host-uk/core/pkg/agentic" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -32,30 +33,19 @@ var ( var taskCommitCmd = &cobra.Command{ Use: "task:commit [task-id]", - Short: "Auto-commit changes with task reference", - Long: `Creates a git commit with a task reference and co-author attribution. - -Commit message format: - feat(scope): description - - Task: #123 - Co-Authored-By: Claude - -Examples: - core ai task:commit abc123 --message 'add user authentication' - core ai task:commit abc123 -m 'fix login bug' --scope auth - core ai task:commit abc123 -m 'update docs' --push`, - Args: cobra.ExactArgs(1), + Short: i18n.T("cmd.ai.task_commit.short"), + Long: i18n.T("cmd.ai.task_commit.long"), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { taskID := args[0] if taskCommitMessage == "" { - return fmt.Errorf("commit message required (--message or -m)") + return fmt.Errorf(i18n.T("cmd.ai.task_commit.message_required")) } cfg, err := agentic.LoadConfig("") if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.load_config"), err) } client := agentic.NewClientFromConfig(cfg) @@ -66,7 +56,7 @@ Examples: // Get task details task, err := client.GetTask(ctx, taskID) if err != nil { - return fmt.Errorf("failed to get task: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.get_task"), err) } // Build commit message with optional scope @@ -81,35 +71,35 @@ Examples: // Get current directory cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.working_dir"), err) } // Check for uncommitted changes hasChanges, err := agentic.HasUncommittedChanges(ctx, cwd) if err != nil { - return fmt.Errorf("failed to check git status: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.git_status"), err) } if !hasChanges { - fmt.Println("No uncommitted changes to commit.") + fmt.Println(i18n.T("cmd.ai.task_commit.no_changes")) return nil } // Create commit - fmt.Printf("%s Creating commit for task %s...\n", dimStyle.Render(">>"), taskID) + fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.ai.task_commit.creating", map[string]interface{}{"ID": taskID})) if err := agentic.AutoCommit(ctx, task, cwd, fullMessage); err != nil { - return fmt.Errorf("failed to commit: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.commit"), err) } - fmt.Printf("%s Committed: %s\n", successStyle.Render(">>"), fullMessage) + fmt.Printf("%s %s %s\n", successStyle.Render(">>"), i18n.T("cmd.ai.task_commit.committed"), fullMessage) // Push if requested if taskCommitPush { - fmt.Printf("%s Pushing changes...\n", dimStyle.Render(">>")) + fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.ai.task_commit.pushing")) if err := agentic.PushChanges(ctx, cwd); err != nil { - return fmt.Errorf("failed to push: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.push"), err) } - fmt.Printf("%s Changes pushed successfully\n", successStyle.Render(">>")) + fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("cmd.ai.task_commit.pushed")) } return nil @@ -118,23 +108,15 @@ Examples: var taskPRCmd = &cobra.Command{ Use: "task:pr [task-id]", - Short: "Create a pull request for a task", - Long: `Creates a GitHub pull request linked to a task. - -Requires the GitHub CLI (gh) to be installed and authenticated. - -Examples: - core ai task:pr abc123 - core ai task:pr abc123 --title 'Add authentication feature' - core ai task:pr abc123 --draft --labels 'enhancement,needs-review' - core ai task:pr abc123 --base develop`, - Args: cobra.ExactArgs(1), + Short: i18n.T("cmd.ai.task_pr.short"), + Long: i18n.T("cmd.ai.task_pr.long"), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { taskID := args[0] cfg, err := agentic.LoadConfig("") if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.load_config"), err) } client := agentic.NewClientFromConfig(cfg) @@ -145,31 +127,31 @@ Examples: // Get task details task, err := client.GetTask(ctx, taskID) if err != nil { - return fmt.Errorf("failed to get task: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.get_task"), err) } // Get current directory cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.working_dir"), err) } // Check current branch branch, err := agentic.GetCurrentBranch(ctx, cwd) if err != nil { - return fmt.Errorf("failed to get current branch: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.get_branch"), err) } if branch == "main" || branch == "master" { - return fmt.Errorf("cannot create PR from %s branch; create a feature branch first", branch) + return fmt.Errorf(i18n.T("cmd.ai.task_pr.branch_error", map[string]interface{}{"Branch": branch})) } // Push current branch - fmt.Printf("%s Pushing branch %s...\n", dimStyle.Render(">>"), branch) + fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.ai.task_pr.pushing_branch", map[string]interface{}{"Branch": branch})) if err := agentic.PushChanges(ctx, cwd); err != nil { // Try setting upstream if _, err := runGitCommand(cwd, "push", "-u", "origin", branch); err != nil { - return fmt.Errorf("failed to push branch: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.push_branch"), err) } } @@ -185,14 +167,14 @@ Examples: } // Create PR - fmt.Printf("%s Creating pull request...\n", dimStyle.Render(">>")) + fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.ai.task_pr.creating")) prURL, err := agentic.CreatePR(ctx, task, cwd, opts) if err != nil { - return fmt.Errorf("failed to create PR: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.create_pr"), err) } - fmt.Printf("%s Pull request created!\n", successStyle.Render(">>")) - fmt.Printf(" URL: %s\n", prURL) + fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("cmd.ai.task_pr.created")) + fmt.Printf(" %s %s\n", i18n.T("cmd.ai.label.url"), prURL) return nil }, @@ -200,15 +182,15 @@ Examples: func init() { // task:commit command flags - taskCommitCmd.Flags().StringVarP(&taskCommitMessage, "message", "m", "", "Commit message (without task reference)") - taskCommitCmd.Flags().StringVar(&taskCommitScope, "scope", "", "Scope for the commit type (e.g., auth, api, ui)") - taskCommitCmd.Flags().BoolVar(&taskCommitPush, "push", false, "Push changes after committing") + taskCommitCmd.Flags().StringVarP(&taskCommitMessage, "message", "m", "", i18n.T("cmd.ai.task_commit.flag.message")) + taskCommitCmd.Flags().StringVar(&taskCommitScope, "scope", "", i18n.T("cmd.ai.task_commit.flag.scope")) + taskCommitCmd.Flags().BoolVar(&taskCommitPush, "push", false, i18n.T("cmd.ai.task_commit.flag.push")) // task:pr command flags - taskPRCmd.Flags().StringVar(&taskPRTitle, "title", "", "PR title (defaults to task title)") - taskPRCmd.Flags().BoolVar(&taskPRDraft, "draft", false, "Create as draft PR") - taskPRCmd.Flags().StringVar(&taskPRLabels, "labels", "", "Labels to add (comma-separated)") - taskPRCmd.Flags().StringVar(&taskPRBase, "base", "", "Base branch (defaults to main)") + taskPRCmd.Flags().StringVar(&taskPRTitle, "title", "", i18n.T("cmd.ai.task_pr.flag.title")) + taskPRCmd.Flags().BoolVar(&taskPRDraft, "draft", false, i18n.T("cmd.ai.task_pr.flag.draft")) + taskPRCmd.Flags().StringVar(&taskPRLabels, "labels", "", i18n.T("cmd.ai.task_pr.flag.labels")) + taskPRCmd.Flags().StringVar(&taskPRBase, "base", "", i18n.T("cmd.ai.task_pr.flag.base")) } func addTaskCommitCommand(parent *cobra.Command) { diff --git a/cmd/ai/ai_tasks.go b/cmd/ai/ai_tasks.go index e390e9c..ec87f05 100644 --- a/cmd/ai/ai_tasks.go +++ b/cmd/ai/ai_tasks.go @@ -11,6 +11,7 @@ import ( "time" "github.com/host-uk/core/pkg/agentic" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -32,18 +33,8 @@ var ( var tasksCmd = &cobra.Command{ Use: "tasks", - Short: "List available tasks from core-agentic", - Long: `Lists tasks from the core-agentic service. - -Configuration is loaded from: - 1. Environment variables (AGENTIC_TOKEN, AGENTIC_BASE_URL) - 2. .env file in current directory - 3. ~/.core/agentic.yaml - -Examples: - core ai tasks - core ai tasks --status pending --priority high - core ai tasks --labels bug,urgent`, + Short: i18n.T("cmd.ai.tasks.short"), + Long: i18n.T("cmd.ai.tasks.long"), RunE: func(cmd *cobra.Command, args []string) error { limit := tasksLimit if limit == 0 { @@ -52,7 +43,7 @@ Examples: cfg, err := agentic.LoadConfig("") if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.load_config"), err) } client := agentic.NewClientFromConfig(cfg) @@ -77,11 +68,11 @@ Examples: tasks, err := client.ListTasks(ctx, opts) if err != nil { - return fmt.Errorf("failed to list tasks: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.list_tasks"), err) } if len(tasks) == 0 { - fmt.Println("No tasks found.") + fmt.Println(i18n.T("cmd.ai.tasks.none_found")) return nil } @@ -92,18 +83,12 @@ Examples: var taskCmd = &cobra.Command{ Use: "task [task-id]", - Short: "Show task details or auto-select a task", - Long: `Shows details of a specific task or auto-selects the highest priority task. - -Examples: - core ai task abc123 # Show task details - core ai task abc123 --claim # Show and claim the task - core ai task abc123 --context # Show task with gathered context - core ai task --auto # Auto-select highest priority pending task`, + Short: i18n.T("cmd.ai.task.short"), + Long: i18n.T("cmd.ai.task.long"), RunE: func(cmd *cobra.Command, args []string) error { cfg, err := agentic.LoadConfig("") if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.load_config"), err) } client := agentic.NewClientFromConfig(cfg) @@ -126,11 +111,11 @@ Examples: Limit: 50, }) if err != nil { - return fmt.Errorf("failed to list tasks: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.list_tasks"), err) } if len(tasks) == 0 { - fmt.Println("No pending tasks available.") + fmt.Println(i18n.T("cmd.ai.task.no_pending")) return nil } @@ -150,12 +135,12 @@ Examples: taskClaim = true // Auto-select implies claiming } else { if taskID == "" { - return fmt.Errorf("task ID required (or use --auto)") + return fmt.Errorf(i18n.T("cmd.ai.task.id_required")) } task, err = client.GetTask(ctx, taskID) if err != nil { - return fmt.Errorf("failed to get task: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.get_task"), err) } } @@ -164,7 +149,7 @@ Examples: cwd, _ := os.Getwd() taskCtx, err := agentic.BuildTaskContext(task, cwd) if err != nil { - fmt.Printf("%s Failed to build context: %s\n", errorStyle.Render(">>"), err) + fmt.Printf("%s %s: %s\n", errorStyle.Render(">>"), i18n.T("cmd.ai.task.context_failed"), err) } else { fmt.Println(taskCtx.FormatContext()) } @@ -174,15 +159,15 @@ Examples: if taskClaim && task.Status == agentic.StatusPending { fmt.Println() - fmt.Printf("%s Claiming task...\n", dimStyle.Render(">>")) + fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.ai.task.claiming")) claimedTask, err := client.ClaimTask(ctx, task.ID) if err != nil { - return fmt.Errorf("failed to claim task: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.claim_task"), err) } - fmt.Printf("%s Task claimed successfully!\n", successStyle.Render(">>")) - fmt.Printf(" Status: %s\n", formatTaskStatus(claimedTask.Status)) + fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("cmd.ai.task.claimed")) + fmt.Printf(" %s %s\n", i18n.T("cmd.ai.label.status"), formatTaskStatus(claimedTask.Status)) } return nil @@ -191,16 +176,16 @@ Examples: func init() { // tasks command flags - tasksCmd.Flags().StringVar(&tasksStatus, "status", "", "Filter by status (pending, in_progress, completed, blocked)") - tasksCmd.Flags().StringVar(&tasksPriority, "priority", "", "Filter by priority (critical, high, medium, low)") - tasksCmd.Flags().StringVar(&tasksLabels, "labels", "", "Filter by labels (comma-separated)") - tasksCmd.Flags().IntVar(&tasksLimit, "limit", 20, "Max number of tasks to return") - tasksCmd.Flags().StringVar(&tasksProject, "project", "", "Filter by project") + tasksCmd.Flags().StringVar(&tasksStatus, "status", "", i18n.T("cmd.ai.tasks.flag.status")) + tasksCmd.Flags().StringVar(&tasksPriority, "priority", "", i18n.T("cmd.ai.tasks.flag.priority")) + tasksCmd.Flags().StringVar(&tasksLabels, "labels", "", i18n.T("cmd.ai.tasks.flag.labels")) + tasksCmd.Flags().IntVar(&tasksLimit, "limit", 20, i18n.T("cmd.ai.tasks.flag.limit")) + tasksCmd.Flags().StringVar(&tasksProject, "project", "", i18n.T("cmd.ai.tasks.flag.project")) // task command flags - taskCmd.Flags().BoolVar(&taskAutoSelect, "auto", false, "Auto-select highest priority pending task") - taskCmd.Flags().BoolVar(&taskClaim, "claim", false, "Claim the task after showing details") - taskCmd.Flags().BoolVar(&taskShowContext, "context", false, "Show gathered context for AI collaboration") + taskCmd.Flags().BoolVar(&taskAutoSelect, "auto", false, i18n.T("cmd.ai.task.flag.auto")) + taskCmd.Flags().BoolVar(&taskClaim, "claim", false, i18n.T("cmd.ai.task.flag.claim")) + taskCmd.Flags().BoolVar(&taskShowContext, "context", false, i18n.T("cmd.ai.task.flag.context")) } func addTasksCommand(parent *cobra.Command) { @@ -212,7 +197,7 @@ func addTaskCommand(parent *cobra.Command) { } func printTaskList(tasks []agentic.Task) { - fmt.Printf("\n%d task(s) found:\n\n", len(tasks)) + fmt.Printf("\n%s\n\n", i18n.T("cmd.ai.tasks.found", map[string]interface{}{"Count": len(tasks)})) for _, task := range tasks { id := taskIDStyle.Render(task.ID) @@ -231,37 +216,37 @@ func printTaskList(tasks []agentic.Task) { } fmt.Println() - fmt.Printf("%s\n", dimStyle.Render("Use 'core ai task ' to view details")) + fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.ai.tasks.hint"))) } func printTaskDetails(task *agentic.Task) { fmt.Println() - fmt.Printf("%s %s\n", dimStyle.Render("ID:"), taskIDStyle.Render(task.ID)) - fmt.Printf("%s %s\n", dimStyle.Render("Title:"), taskTitleStyle.Render(task.Title)) - fmt.Printf("%s %s\n", dimStyle.Render("Priority:"), formatTaskPriority(task.Priority)) - fmt.Printf("%s %s\n", dimStyle.Render("Status:"), formatTaskStatus(task.Status)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.id")), taskIDStyle.Render(task.ID)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.title")), taskTitleStyle.Render(task.Title)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.priority")), formatTaskPriority(task.Priority)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.status")), formatTaskStatus(task.Status)) if task.Project != "" { - fmt.Printf("%s %s\n", dimStyle.Render("Project:"), task.Project) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.project")), task.Project) } if len(task.Labels) > 0 { - fmt.Printf("%s %s\n", dimStyle.Render("Labels:"), taskLabelStyle.Render(strings.Join(task.Labels, ", "))) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.labels")), taskLabelStyle.Render(strings.Join(task.Labels, ", "))) } if task.ClaimedBy != "" { - fmt.Printf("%s %s\n", dimStyle.Render("Claimed by:"), task.ClaimedBy) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.claimed_by")), task.ClaimedBy) } - fmt.Printf("%s %s\n", dimStyle.Render("Created:"), formatAge(task.CreatedAt)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.created")), formatAge(task.CreatedAt)) fmt.Println() - fmt.Printf("%s\n", dimStyle.Render("Description:")) + fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.ai.label.description"))) fmt.Println(task.Description) if len(task.Files) > 0 { fmt.Println() - fmt.Printf("%s\n", dimStyle.Render("Related files:")) + fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.ai.label.related_files"))) for _, f := range task.Files { fmt.Printf(" - %s\n", f) } @@ -269,20 +254,20 @@ func printTaskDetails(task *agentic.Task) { if len(task.Dependencies) > 0 { fmt.Println() - fmt.Printf("%s %s\n", dimStyle.Render("Blocked by:"), strings.Join(task.Dependencies, ", ")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.blocked_by")), strings.Join(task.Dependencies, ", ")) } } func formatTaskPriority(p agentic.TaskPriority) string { switch p { case agentic.PriorityCritical: - return taskPriorityHighStyle.Render("[CRITICAL]") + return taskPriorityHighStyle.Render("[" + i18n.T("cmd.ai.priority.critical") + "]") case agentic.PriorityHigh: - return taskPriorityHighStyle.Render("[HIGH]") + return taskPriorityHighStyle.Render("[" + i18n.T("cmd.ai.priority.high") + "]") case agentic.PriorityMedium: - return taskPriorityMediumStyle.Render("[MEDIUM]") + return taskPriorityMediumStyle.Render("[" + i18n.T("cmd.ai.priority.medium") + "]") case agentic.PriorityLow: - return taskPriorityLowStyle.Render("[LOW]") + return taskPriorityLowStyle.Render("[" + i18n.T("cmd.ai.priority.low") + "]") default: return dimStyle.Render("[" + string(p) + "]") } @@ -291,13 +276,13 @@ func formatTaskPriority(p agentic.TaskPriority) string { func formatTaskStatus(s agentic.TaskStatus) string { switch s { case agentic.StatusPending: - return taskStatusPendingStyle.Render("pending") + return taskStatusPendingStyle.Render(i18n.T("cmd.ai.status.pending")) case agentic.StatusInProgress: - return taskStatusInProgressStyle.Render("in_progress") + return taskStatusInProgressStyle.Render(i18n.T("cmd.ai.status.in_progress")) case agentic.StatusCompleted: - return taskStatusCompletedStyle.Render("completed") + return taskStatusCompletedStyle.Render(i18n.T("cmd.ai.status.completed")) case agentic.StatusBlocked: - return taskStatusBlockedStyle.Render("blocked") + return taskStatusBlockedStyle.Render(i18n.T("cmd.ai.status.blocked")) default: return dimStyle.Render(string(s)) } diff --git a/cmd/ai/ai_updates.go b/cmd/ai/ai_updates.go index 74fd1e2..31cae93 100644 --- a/cmd/ai/ai_updates.go +++ b/cmd/ai/ai_updates.go @@ -8,6 +8,7 @@ import ( "time" "github.com/host-uk/core/pkg/agentic" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -27,23 +28,19 @@ var ( var taskUpdateCmd = &cobra.Command{ Use: "task:update [task-id]", - Short: "Update task status or progress", - Long: `Updates a task's status, progress, or adds notes. - -Examples: - core ai task:update abc123 --status in_progress - core ai task:update abc123 --progress 50 --notes 'Halfway done'`, - Args: cobra.ExactArgs(1), + Short: i18n.T("cmd.ai.task_update.short"), + Long: i18n.T("cmd.ai.task_update.long"), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { taskID := args[0] if taskUpdateStatus == "" && taskUpdateProgress == 0 && taskUpdateNotes == "" { - return fmt.Errorf("at least one of --status, --progress, or --notes required") + return fmt.Errorf(i18n.T("cmd.ai.task_update.flag_required")) } cfg, err := agentic.LoadConfig("") if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.load_config"), err) } client := agentic.NewClientFromConfig(cfg) @@ -60,29 +57,25 @@ Examples: } if err := client.UpdateTask(ctx, taskID, update); err != nil { - return fmt.Errorf("failed to update task: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.update_task"), err) } - fmt.Printf("%s Task %s updated successfully\n", successStyle.Render(">>"), taskID) + fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("cmd.ai.task_update.success", map[string]interface{}{"ID": taskID})) return nil }, } var taskCompleteCmd = &cobra.Command{ Use: "task:complete [task-id]", - Short: "Mark a task as completed", - Long: `Marks a task as completed with optional output and artifacts. - -Examples: - core ai task:complete abc123 --output 'Feature implemented' - core ai task:complete abc123 --failed --error 'Build failed'`, - Args: cobra.ExactArgs(1), + Short: i18n.T("cmd.ai.task_complete.short"), + Long: i18n.T("cmd.ai.task_complete.long"), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { taskID := args[0] cfg, err := agentic.LoadConfig("") if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.load_config"), err) } client := agentic.NewClientFromConfig(cfg) @@ -97,13 +90,13 @@ Examples: } if err := client.CompleteTask(ctx, taskID, result); err != nil { - return fmt.Errorf("failed to complete task: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ai.error.complete_task"), err) } if taskCompleteFailed { - fmt.Printf("%s Task %s marked as failed\n", errorStyle.Render(">>"), taskID) + fmt.Printf("%s %s\n", errorStyle.Render(">>"), i18n.T("cmd.ai.task_complete.failed", map[string]interface{}{"ID": taskID})) } else { - fmt.Printf("%s Task %s completed successfully\n", successStyle.Render(">>"), taskID) + fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("cmd.ai.task_complete.success", map[string]interface{}{"ID": taskID})) } return nil }, @@ -111,14 +104,14 @@ Examples: func init() { // task:update command flags - taskUpdateCmd.Flags().StringVar(&taskUpdateStatus, "status", "", "New status (pending, in_progress, completed, blocked)") - taskUpdateCmd.Flags().IntVar(&taskUpdateProgress, "progress", 0, "Progress percentage (0-100)") - taskUpdateCmd.Flags().StringVar(&taskUpdateNotes, "notes", "", "Notes about the update") + taskUpdateCmd.Flags().StringVar(&taskUpdateStatus, "status", "", i18n.T("cmd.ai.task_update.flag.status")) + taskUpdateCmd.Flags().IntVar(&taskUpdateProgress, "progress", 0, i18n.T("cmd.ai.task_update.flag.progress")) + taskUpdateCmd.Flags().StringVar(&taskUpdateNotes, "notes", "", i18n.T("cmd.ai.task_update.flag.notes")) // task:complete command flags - taskCompleteCmd.Flags().StringVar(&taskCompleteOutput, "output", "", "Summary of the completed work") - taskCompleteCmd.Flags().BoolVar(&taskCompleteFailed, "failed", false, "Mark the task as failed") - taskCompleteCmd.Flags().StringVar(&taskCompleteErrorMsg, "error", "", "Error message if failed") + taskCompleteCmd.Flags().StringVar(&taskCompleteOutput, "output", "", i18n.T("cmd.ai.task_complete.flag.output")) + taskCompleteCmd.Flags().BoolVar(&taskCompleteFailed, "failed", false, i18n.T("cmd.ai.task_complete.flag.failed")) + taskCompleteCmd.Flags().StringVar(&taskCompleteErrorMsg, "error", "", i18n.T("cmd.ai.task_complete.flag.error")) } func addTaskUpdateCommand(parent *cobra.Command) { diff --git a/cmd/ai/commands.go b/cmd/ai/commands.go index e9850d2..aa07370 100644 --- a/cmd/ai/commands.go +++ b/cmd/ai/commands.go @@ -10,42 +10,26 @@ // - claude: Claude Code CLI integration (planned) package ai -import "github.com/spf13/cobra" +import ( + "github.com/host-uk/core/pkg/i18n" + "github.com/spf13/cobra" +) var aiCmd = &cobra.Command{ Use: "ai", - Short: "AI agent task management", - Long: `Manage tasks from the core-agentic service for AI-assisted development. - -Commands: - tasks List tasks (filterable by status, priority, labels) - task View task details or auto-select highest priority - task:update Update task status or progress - task:complete Mark task as completed or failed - task:commit Create git commit with task reference - task:pr Create GitHub PR linked to task - claude Claude Code integration - -Workflow: - core ai tasks # List pending tasks - core ai task --auto --claim # Auto-select and claim a task - core ai task:commit -m 'msg' # Commit with task reference - core ai task:complete # Mark task done`, + Short: i18n.T("cmd.ai.short"), + Long: i18n.T("cmd.ai.long"), } var claudeCmd = &cobra.Command{ Use: "claude", - Short: "Claude Code integration", - Long: `Tools for working with Claude Code. - -Commands: - run Run Claude in the current directory - config Manage Claude configuration`, + Short: i18n.T("cmd.ai.claude.short"), + Long: i18n.T("cmd.ai.claude.long"), } var claudeRunCmd = &cobra.Command{ Use: "run", - Short: "Run Claude Code in the current directory", + Short: i18n.T("cmd.ai.claude.run.short"), RunE: func(cmd *cobra.Command, args []string) error { return runClaudeCode() }, @@ -53,7 +37,7 @@ var claudeRunCmd = &cobra.Command{ var claudeConfigCmd = &cobra.Command{ Use: "config", - Short: "Manage Claude configuration", + Short: i18n.T("cmd.ai.claude.config.short"), RunE: func(cmd *cobra.Command, args []string) error { return showClaudeConfig() }, diff --git a/cmd/build/build.go b/cmd/build/build.go index d5c9a57..143581d 100644 --- a/cmd/build/build.go +++ b/cmd/build/build.go @@ -5,6 +5,7 @@ import ( "embed" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -54,16 +55,8 @@ var ( var buildCmd = &cobra.Command{ Use: "build", - Short: "Build projects with auto-detection and cross-compilation", - Long: `Builds the current project with automatic type detection. -Supports Go, Wails, Docker, LinuxKit, and Taskfile projects. -Configuration can be provided via .core/build.yaml or command-line flags. - -Examples: - core build # Auto-detect and build - core build --type docker # Build Docker image - core build --type linuxkit # Build LinuxKit image - core build --type linuxkit --config linuxkit.yml --format qcow2-bios`, + Short: i18n.T("cmd.build.short"), + Long: i18n.T("cmd.build.long"), RunE: func(cmd *cobra.Command, args []string) error { return runProjectBuild(buildType, ciMode, targets, outputDir, doArchive, doChecksum, configPath, format, push, imageName, noSign, notarize) }, @@ -71,7 +64,7 @@ Examples: var fromPathCmd = &cobra.Command{ Use: "from-path", - Short: "Build from a local directory.", + Short: i18n.T("cmd.build.from_path.short"), RunE: func(cmd *cobra.Command, args []string) error { if fromPath == "" { return errPathRequired @@ -82,7 +75,7 @@ var fromPathCmd = &cobra.Command{ var pwaCmd = &cobra.Command{ Use: "pwa", - Short: "Build from a live PWA URL.", + Short: i18n.T("cmd.build.pwa.short"), RunE: func(cmd *cobra.Command, args []string) error { if pwaURL == "" { return errURLRequired @@ -93,14 +86,8 @@ var pwaCmd = &cobra.Command{ var sdkBuildCmd = &cobra.Command{ Use: "sdk", - Short: "Generate API SDKs from OpenAPI spec", - Long: `Generates typed API clients from OpenAPI specifications. -Supports TypeScript, Python, Go, and PHP. - -Examples: - core build sdk # Generate all configured SDKs - core build sdk --lang typescript # Generate only TypeScript SDK - core build sdk --spec api.yaml # Use specific OpenAPI spec`, + Short: i18n.T("cmd.build.sdk.short"), + Long: i18n.T("cmd.build.sdk.long"), RunE: func(cmd *cobra.Command, args []string) error { return runBuildSDK(sdkSpec, sdkLang, sdkVersion, sdkDryRun) }, @@ -108,34 +95,34 @@ Examples: func init() { // Main build command flags - buildCmd.Flags().StringVar(&buildType, "type", "", "Builder type (go, wails, docker, linuxkit, taskfile) - auto-detected if not specified") - buildCmd.Flags().BoolVar(&ciMode, "ci", false, "CI mode - minimal output with JSON artifact list at the end") - buildCmd.Flags().StringVar(&targets, "targets", "", "Comma-separated OS/arch pairs (e.g., linux/amd64,darwin/arm64)") - buildCmd.Flags().StringVar(&outputDir, "output", "", "Output directory for artifacts (default: dist)") - buildCmd.Flags().BoolVar(&doArchive, "archive", true, "Create archives (tar.gz for linux/darwin, zip for windows)") - buildCmd.Flags().BoolVar(&doChecksum, "checksum", true, "Generate SHA256 checksums and CHECKSUMS.txt") + buildCmd.Flags().StringVar(&buildType, "type", "", i18n.T("cmd.build.flag.type")) + buildCmd.Flags().BoolVar(&ciMode, "ci", false, i18n.T("cmd.build.flag.ci")) + buildCmd.Flags().StringVar(&targets, "targets", "", i18n.T("cmd.build.flag.targets")) + buildCmd.Flags().StringVar(&outputDir, "output", "", i18n.T("cmd.build.flag.output")) + buildCmd.Flags().BoolVar(&doArchive, "archive", true, i18n.T("cmd.build.flag.archive")) + buildCmd.Flags().BoolVar(&doChecksum, "checksum", true, i18n.T("cmd.build.flag.checksum")) // Docker/LinuxKit specific - buildCmd.Flags().StringVar(&configPath, "config", "", "Config file path (for linuxkit: YAML config, for docker: Dockerfile)") - buildCmd.Flags().StringVar(&format, "format", "", "Output format for linuxkit (iso-bios, qcow2-bios, raw, vmdk)") - buildCmd.Flags().BoolVar(&push, "push", false, "Push Docker image after build") - buildCmd.Flags().StringVar(&imageName, "image", "", "Docker image name (e.g., host-uk/core-devops)") + buildCmd.Flags().StringVar(&configPath, "config", "", i18n.T("cmd.build.flag.config")) + buildCmd.Flags().StringVar(&format, "format", "", i18n.T("cmd.build.flag.format")) + buildCmd.Flags().BoolVar(&push, "push", false, i18n.T("cmd.build.flag.push")) + buildCmd.Flags().StringVar(&imageName, "image", "", i18n.T("cmd.build.flag.image")) // Signing flags - buildCmd.Flags().BoolVar(&noSign, "no-sign", false, "Skip all code signing") - buildCmd.Flags().BoolVar(¬arize, "notarize", false, "Enable macOS notarization (requires Apple credentials)") + buildCmd.Flags().BoolVar(&noSign, "no-sign", false, i18n.T("cmd.build.flag.no_sign")) + buildCmd.Flags().BoolVar(¬arize, "notarize", false, i18n.T("cmd.build.flag.notarize")) // from-path subcommand flags - fromPathCmd.Flags().StringVar(&fromPath, "path", "", "The path to the static web application files.") + fromPathCmd.Flags().StringVar(&fromPath, "path", "", i18n.T("cmd.build.from_path.flag.path")) // pwa subcommand flags - pwaCmd.Flags().StringVar(&pwaURL, "url", "", "The URL of the PWA to build.") + pwaCmd.Flags().StringVar(&pwaURL, "url", "", i18n.T("cmd.build.pwa.flag.url")) // sdk subcommand flags - sdkBuildCmd.Flags().StringVar(&sdkSpec, "spec", "", "Path to OpenAPI spec file") - sdkBuildCmd.Flags().StringVar(&sdkLang, "lang", "", "Generate only this language (typescript, python, go, php)") - sdkBuildCmd.Flags().StringVar(&sdkVersion, "version", "", "Version to embed in generated SDKs") - sdkBuildCmd.Flags().BoolVar(&sdkDryRun, "dry-run", false, "Show what would be generated without writing files") + sdkBuildCmd.Flags().StringVar(&sdkSpec, "spec", "", i18n.T("cmd.build.sdk.flag.spec")) + sdkBuildCmd.Flags().StringVar(&sdkLang, "lang", "", i18n.T("cmd.build.sdk.flag.lang")) + sdkBuildCmd.Flags().StringVar(&sdkVersion, "version", "", i18n.T("cmd.build.sdk.flag.version")) + sdkBuildCmd.Flags().BoolVar(&sdkDryRun, "dry-run", false, i18n.T("cmd.build.sdk.flag.dry_run")) // Add subcommands buildCmd.AddCommand(fromPathCmd) diff --git a/cmd/build/build_project.go b/cmd/build/build_project.go index b5e7d39..c0e6818 100644 --- a/cmd/build/build_project.go +++ b/cmd/build/build_project.go @@ -17,6 +17,7 @@ import ( buildpkg "github.com/host-uk/core/pkg/build" "github.com/host-uk/core/pkg/build/builders" "github.com/host-uk/core/pkg/build/signing" + "github.com/host-uk/core/pkg/i18n" ) // runProjectBuild handles the main `core build` command with auto-detection. @@ -24,13 +25,13 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi // Get current working directory as project root projectDir, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.error.working_dir"), err) } // Load configuration from .core/build.yaml (or defaults) buildCfg, err := buildpkg.LoadConfig(projectDir) if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.error.load_config"), err) } // Detect project type if not specified @@ -40,11 +41,10 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi } else { projectType, err = buildpkg.PrimaryType(projectDir) if err != nil { - return fmt.Errorf("failed to detect project type: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.error.detect_type"), err) } if projectType == "" { - return fmt.Errorf("no supported project type detected in %s\n"+ - "Supported types: go (go.mod), wails (wails.json), node (package.json), php (composer.json)", projectDir) + return fmt.Errorf("%s", i18n.T("cmd.build.error.no_project_type", map[string]interface{}{"Dir": projectDir})) } } @@ -82,11 +82,11 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi // Print build info (unless CI mode) if !ciMode { - fmt.Printf("%s Building project\n", buildHeaderStyle.Render("Build:")) - fmt.Printf(" Type: %s\n", buildTargetStyle.Render(string(projectType))) - fmt.Printf(" Output: %s\n", buildTargetStyle.Render(outputDir)) - fmt.Printf(" Binary: %s\n", buildTargetStyle.Render(binaryName)) - fmt.Printf(" Targets: %s\n", buildTargetStyle.Render(formatTargets(buildTargets))) + fmt.Printf("%s %s\n", buildHeaderStyle.Render(i18n.T("cmd.build.label.build")), i18n.T("cmd.build.building_project")) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.label.type"), buildTargetStyle.Render(string(projectType))) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.label.output"), buildTargetStyle.Render(outputDir)) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.label.binary"), buildTargetStyle.Render(binaryName)) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.label.targets"), buildTargetStyle.Render(formatTargets(buildTargets))) fmt.Println() } @@ -120,13 +120,13 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi artifacts, err := builder.Build(ctx, cfg, buildTargets) if err != nil { if !ciMode { - fmt.Printf("%s Build failed: %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %s: %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), i18n.T("cmd.build.error.build_failed"), err) } return err } if !ciMode { - fmt.Printf("%s Built %d artifact(s)\n", buildSuccessStyle.Render("Success:"), len(artifacts)) + fmt.Printf("%s %s\n", buildSuccessStyle.Render(i18n.T("cmd.build.label.success")), i18n.T("cmd.build.built_artifacts", map[string]interface{}{"Count": len(artifacts)})) fmt.Println() for _, artifact := range artifacts { relPath, err := filepath.Rel(projectDir, artifact.Path) @@ -153,7 +153,7 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi if signCfg.Enabled && runtime.GOOS == "darwin" { if !ciMode { fmt.Println() - fmt.Printf("%s Signing binaries...\n", buildHeaderStyle.Render("Sign:")) + fmt.Printf("%s %s\n", buildHeaderStyle.Render(i18n.T("cmd.build.label.sign")), i18n.T("cmd.build.signing_binaries")) } // Convert buildpkg.Artifact to signing.Artifact @@ -164,7 +164,7 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi if err := signing.SignBinaries(ctx, signCfg, signingArtifacts); err != nil { if !ciMode { - fmt.Printf("%s Signing failed: %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %s: %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), i18n.T("cmd.build.error.signing_failed"), err) } return err } @@ -172,7 +172,7 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi if signCfg.MacOS.Notarize { if err := signing.NotarizeBinaries(ctx, signCfg, signingArtifacts); err != nil { if !ciMode { - fmt.Printf("%s Notarization failed: %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %s: %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), i18n.T("cmd.build.error.notarization_failed"), err) } return err } @@ -184,13 +184,13 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi if doArchive && len(artifacts) > 0 { if !ciMode { fmt.Println() - fmt.Printf("%s Creating archives...\n", buildHeaderStyle.Render("Archive:")) + fmt.Printf("%s %s\n", buildHeaderStyle.Render(i18n.T("cmd.build.label.archive")), i18n.T("cmd.build.creating_archives")) } archivedArtifacts, err = buildpkg.ArchiveAll(artifacts) if err != nil { if !ciMode { - fmt.Printf("%s Archive failed: %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %s: %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), i18n.T("cmd.build.error.archive_failed"), err) } return err } @@ -240,7 +240,7 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi // JSON output for CI output, err := json.MarshalIndent(outputArtifacts, "", " ") if err != nil { - return fmt.Errorf("failed to marshal artifacts: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.error.marshal_artifacts"), err) } fmt.Println(string(output)) } @@ -252,13 +252,13 @@ func runProjectBuild(buildType string, ciMode bool, targetsFlag string, outputDi func computeAndWriteChecksums(ctx context.Context, projectDir, outputDir string, artifacts []buildpkg.Artifact, signCfg signing.SignConfig, ciMode bool) ([]buildpkg.Artifact, error) { if !ciMode { fmt.Println() - fmt.Printf("%s Computing checksums...\n", buildHeaderStyle.Render("Checksum:")) + fmt.Printf("%s %s\n", buildHeaderStyle.Render(i18n.T("cmd.build.label.checksum")), i18n.T("cmd.build.computing_checksums")) } checksummedArtifacts, err := buildpkg.ChecksumAll(artifacts) if err != nil { if !ciMode { - fmt.Printf("%s Checksum failed: %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %s: %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), i18n.T("cmd.build.error.checksum_failed"), err) } return nil, err } @@ -267,7 +267,7 @@ func computeAndWriteChecksums(ctx context.Context, projectDir, outputDir string, checksumPath := filepath.Join(outputDir, "CHECKSUMS.txt") if err := buildpkg.WriteChecksumFile(checksummedArtifacts, checksumPath); err != nil { if !ciMode { - fmt.Printf("%s Failed to write CHECKSUMS.txt: %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %s: %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), i18n.T("cmd.build.error.write_checksums"), err) } return nil, err } @@ -276,7 +276,7 @@ func computeAndWriteChecksums(ctx context.Context, projectDir, outputDir string, if signCfg.Enabled { if err := signing.SignChecksums(ctx, signCfg, checksumPath); err != nil { if !ciMode { - fmt.Printf("%s GPG signing failed: %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %s: %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), i18n.T("cmd.build.error.gpg_signing_failed"), err) } return nil, err } @@ -321,7 +321,7 @@ func parseTargets(targetsFlag string) ([]buildpkg.Target, error) { osArch := strings.Split(part, "/") if len(osArch) != 2 { - return nil, fmt.Errorf("invalid target format %q, expected OS/arch (e.g., linux/amd64)", part) + return nil, fmt.Errorf("%s", i18n.T("cmd.build.error.invalid_target", map[string]interface{}{"Target": part})) } targets = append(targets, buildpkg.Target{ @@ -331,7 +331,7 @@ func parseTargets(targetsFlag string) ([]buildpkg.Target, error) { } if len(targets) == 0 { - return nil, fmt.Errorf("no valid targets specified") + return nil, fmt.Errorf("%s", i18n.T("cmd.build.error.no_targets")) } return targets, nil @@ -360,10 +360,10 @@ func getBuilder(projectType buildpkg.ProjectType) (buildpkg.Builder, error) { case buildpkg.ProjectTypeTaskfile: return builders.NewTaskfileBuilder(), nil case buildpkg.ProjectTypeNode: - return nil, fmt.Errorf("Node.js builder not yet implemented") + return nil, fmt.Errorf("%s", i18n.T("cmd.build.error.node_not_implemented")) case buildpkg.ProjectTypePHP: - return nil, fmt.Errorf("PHP builder not yet implemented") + return nil, fmt.Errorf("%s", i18n.T("cmd.build.error.php_not_implemented")) default: - return nil, fmt.Errorf("unsupported project type: %s", projectType) + return nil, fmt.Errorf("%s: %s", i18n.T("cmd.build.error.unsupported_type"), projectType) } } diff --git a/cmd/build/build_pwa.go b/cmd/build/build_pwa.go index 6c22fe4..a6476f9 100644 --- a/cmd/build/build_pwa.go +++ b/cmd/build/build_pwa.go @@ -18,6 +18,7 @@ import ( "path/filepath" "strings" + "github.com/host-uk/core/pkg/i18n" "github.com/leaanthony/debme" "github.com/leaanthony/gosod" "golang.org/x/net/html" @@ -26,22 +27,22 @@ import ( // Error sentinels for build commands var ( errPathRequired = errors.New("the --path flag is required") - errURLRequired = errors.New("a URL argument is required") + errURLRequired = errors.New("the --url flag is required") ) // runPwaBuild downloads a PWA from URL and builds it. func runPwaBuild(pwaURL string) error { - fmt.Printf("Starting PWA build from URL: %s\n", pwaURL) + fmt.Printf("%s %s\n", i18n.T("cmd.build.pwa.starting"), pwaURL) tempDir, err := os.MkdirTemp("", "core-pwa-build-*") if err != nil { - return fmt.Errorf("failed to create temporary directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.pwa.error.create_temp_dir"), err) } // defer os.RemoveAll(tempDir) // Keep temp dir for debugging - fmt.Printf("Downloading PWA to temporary directory: %s\n", tempDir) + fmt.Printf("%s %s\n", i18n.T("cmd.build.pwa.downloading_to"), tempDir) if err := downloadPWA(pwaURL, tempDir); err != nil { - return fmt.Errorf("failed to download PWA: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.pwa.error.download_failed"), err) } return runBuild(tempDir) @@ -52,48 +53,48 @@ func downloadPWA(baseURL, destDir string) error { // Fetch the main HTML page resp, err := http.Get(baseURL) if err != nil { - return fmt.Errorf("failed to fetch URL %s: %w", baseURL, err) + return fmt.Errorf("%s %s: %w", i18n.T("cmd.build.pwa.error.fetch_url"), baseURL, err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("failed to read response body: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.pwa.error.read_response"), err) } // Find the manifest URL from the HTML manifestURL, err := findManifestURL(string(body), baseURL) if err != nil { // If no manifest, it's not a PWA, but we can still try to package it as a simple site. - fmt.Println("Warning: no manifest file found. Proceeding with basic site download.") + fmt.Printf("%s %s\n", i18n.T("cmd.build.pwa.warning"), i18n.T("cmd.build.pwa.no_manifest")) if err := os.WriteFile(filepath.Join(destDir, "index.html"), body, 0644); err != nil { - return fmt.Errorf("failed to write index.html: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.pwa.error.write_index"), err) } return nil } - fmt.Printf("Found manifest: %s\n", manifestURL) + fmt.Printf("%s %s\n", i18n.T("cmd.build.pwa.found_manifest"), manifestURL) // Fetch and parse the manifest manifest, err := fetchManifest(manifestURL) if err != nil { - return fmt.Errorf("failed to fetch or parse manifest: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.pwa.error.fetch_manifest"), err) } // Download all assets listed in the manifest assets := collectAssets(manifest, manifestURL) for _, assetURL := range assets { if err := downloadAsset(assetURL, destDir); err != nil { - fmt.Printf("Warning: failed to download asset %s: %v\n", assetURL, err) + fmt.Printf("%s %s %s: %v\n", i18n.T("cmd.build.pwa.warning"), i18n.T("cmd.build.pwa.asset_download_failed"), assetURL, err) } } // Also save the root index.html if err := os.WriteFile(filepath.Join(destDir, "index.html"), body, 0644); err != nil { - return fmt.Errorf("failed to write index.html: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.pwa.error.write_index"), err) } - fmt.Println("PWA download complete.") + fmt.Println(i18n.T("cmd.build.pwa.download_complete")) return nil } @@ -129,7 +130,7 @@ func findManifestURL(htmlContent, baseURL string) (string, error) { f(doc) if manifestPath == "" { - return "", fmt.Errorf("no tag found") + return "", fmt.Errorf("%s", i18n.T("cmd.build.pwa.error.no_manifest_tag")) } base, err := url.Parse(baseURL) @@ -218,14 +219,14 @@ func downloadAsset(assetURL, destDir string) error { // runBuild builds a desktop application from a local directory. func runBuild(fromPath string) error { - fmt.Printf("Starting build from path: %s\n", fromPath) + fmt.Printf("%s %s\n", i18n.T("cmd.build.from_path.starting"), fromPath) info, err := os.Stat(fromPath) if err != nil { - return fmt.Errorf("invalid path specified: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.from_path.error.invalid_path"), err) } if !info.IsDir() { - return fmt.Errorf("path specified must be a directory") + return fmt.Errorf("%s", i18n.T("cmd.build.from_path.error.must_be_directory")) } buildDir := ".core/build/app" @@ -237,33 +238,33 @@ func runBuild(fromPath string) error { outputExe := appName if err := os.RemoveAll(buildDir); err != nil { - return fmt.Errorf("failed to clean build directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.from_path.error.clean_build_dir"), err) } // 1. Generate the project from the embedded template - fmt.Println("Generating application from template...") + fmt.Println(i18n.T("cmd.build.from_path.generating_template")) templateFS, err := debme.FS(guiTemplate, "tmpl/gui") if err != nil { - return fmt.Errorf("failed to anchor template filesystem: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.from_path.error.anchor_template"), err) } sod := gosod.New(templateFS) if sod == nil { - return fmt.Errorf("failed to create new sod instance") + return fmt.Errorf("%s", i18n.T("cmd.build.from_path.error.create_sod")) } templateData := map[string]string{"AppName": appName} if err := sod.Extract(buildDir, templateData); err != nil { - return fmt.Errorf("failed to extract template: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.from_path.error.extract_template"), err) } // 2. Copy the user's web app files - fmt.Println("Copying application files...") + fmt.Println(i18n.T("cmd.build.from_path.copying_files")) if err := copyDir(fromPath, htmlDir); err != nil { - return fmt.Errorf("failed to copy application files: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.from_path.error.copy_files"), err) } // 3. Compile the application - fmt.Println("Compiling application...") + fmt.Println(i18n.T("cmd.build.from_path.compiling")) // Run go mod tidy cmd := exec.Command("go", "mod", "tidy") @@ -271,7 +272,7 @@ func runBuild(fromPath string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { - return fmt.Errorf("go mod tidy failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.from_path.error.go_mod_tidy"), err) } // Run go build @@ -280,10 +281,10 @@ func runBuild(fromPath string) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { - return fmt.Errorf("go build failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.from_path.error.go_build"), err) } - fmt.Printf("\nBuild successful! Executable created at: %s/%s\n", buildDir, outputExe) + fmt.Printf("\n%s %s/%s\n", i18n.T("cmd.build.from_path.success"), buildDir, outputExe) return nil } diff --git a/cmd/build/build_sdk.go b/cmd/build/build_sdk.go index f35cdbc..fb7bec6 100644 --- a/cmd/build/build_sdk.go +++ b/cmd/build/build_sdk.go @@ -11,6 +11,7 @@ import ( "os" "strings" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/sdk" ) @@ -20,7 +21,7 @@ func runBuildSDK(specPath, lang, version string, dryRun bool) error { projectDir, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.build.error.working_dir"), err) } // Load config @@ -34,48 +35,48 @@ func runBuildSDK(specPath, lang, version string, dryRun bool) error { s.SetVersion(version) } - fmt.Printf("%s Generating SDKs\n", buildHeaderStyle.Render("Build SDK:")) + fmt.Printf("%s %s\n", buildHeaderStyle.Render(i18n.T("cmd.build.sdk.label")), i18n.T("cmd.build.sdk.generating")) if dryRun { - fmt.Printf(" %s\n", buildDimStyle.Render("(dry-run mode)")) + fmt.Printf(" %s\n", buildDimStyle.Render(i18n.T("cmd.build.sdk.dry_run_mode"))) } fmt.Println() // Detect spec detectedSpec, err := s.DetectSpec() if err != nil { - fmt.Printf("%s %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), err) return err } - fmt.Printf(" Spec: %s\n", buildTargetStyle.Render(detectedSpec)) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.sdk.spec_label"), buildTargetStyle.Render(detectedSpec)) if dryRun { if lang != "" { - fmt.Printf(" Language: %s\n", buildTargetStyle.Render(lang)) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.sdk.language_label"), buildTargetStyle.Render(lang)) } else { - fmt.Printf(" Languages: %s\n", buildTargetStyle.Render(strings.Join(config.Languages, ", "))) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.sdk.languages_label"), buildTargetStyle.Render(strings.Join(config.Languages, ", "))) } fmt.Println() - fmt.Printf("%s Would generate SDKs (dry-run)\n", buildSuccessStyle.Render("OK:")) + fmt.Printf("%s %s\n", buildSuccessStyle.Render(i18n.T("cmd.build.label.ok")), i18n.T("cmd.build.sdk.would_generate")) return nil } if lang != "" { // Generate single language if err := s.GenerateLanguage(ctx, lang); err != nil { - fmt.Printf("%s %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), err) return err } - fmt.Printf(" Generated: %s\n", buildTargetStyle.Render(lang)) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.sdk.generated_label"), buildTargetStyle.Render(lang)) } else { // Generate all if err := s.Generate(ctx); err != nil { - fmt.Printf("%s %v\n", buildErrorStyle.Render("Error:"), err) + fmt.Printf("%s %v\n", buildErrorStyle.Render(i18n.T("cmd.build.label.error")), err) return err } - fmt.Printf(" Generated: %s\n", buildTargetStyle.Render(strings.Join(config.Languages, ", "))) + fmt.Printf(" %s %s\n", i18n.T("cmd.build.sdk.generated_label"), buildTargetStyle.Render(strings.Join(config.Languages, ", "))) } fmt.Println() - fmt.Printf("%s SDK generation complete\n", buildSuccessStyle.Render("Success:")) + fmt.Printf("%s %s\n", buildSuccessStyle.Render(i18n.T("cmd.build.label.success")), i18n.T("cmd.build.sdk.complete")) return nil } diff --git a/cmd/ci/ci_changelog.go b/cmd/ci/ci_changelog.go index 1fd1916..cdb75f8 100644 --- a/cmd/ci/ci_changelog.go +++ b/cmd/ci/ci_changelog.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/release" ) @@ -11,19 +12,19 @@ import ( func runChangelog(fromRef, toRef string) error { projectDir, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.working_dir"), err) } // Load config for changelog settings cfg, err := release.LoadConfig(projectDir) if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.load_config"), err) } // Generate changelog changelog, err := release.GenerateWithConfig(projectDir, fromRef, toRef, &cfg.Changelog) if err != nil { - return fmt.Errorf("failed to generate changelog: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.generate_changelog"), err) } fmt.Println(changelog) diff --git a/cmd/ci/ci_init.go b/cmd/ci/ci_init.go index e6151d5..b5c464b 100644 --- a/cmd/ci/ci_init.go +++ b/cmd/ci/ci_init.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/release" ) @@ -14,33 +15,34 @@ import ( func runCIReleaseInit() error { projectDir, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.working_dir"), err) } // Check if config already exists if release.ConfigExists(projectDir) { - fmt.Printf("%s Configuration already exists at %s\n", - releaseDimStyle.Render("Note:"), + fmt.Printf("%s %s %s\n", + releaseDimStyle.Render(i18n.T("cmd.ci.label.note")), + i18n.T("cmd.ci.init.config_exists"), release.ConfigPath(projectDir)) reader := bufio.NewReader(os.Stdin) - fmt.Print("Overwrite? [y/N]: ") + fmt.Print(i18n.T("cmd.ci.init.overwrite_prompt")) response, _ := reader.ReadString('\n') response = strings.TrimSpace(strings.ToLower(response)) if response != "y" && response != "yes" { - fmt.Println("Aborted.") + fmt.Println(i18n.T("cli.confirm.abort")) return nil } } - fmt.Printf("%s Creating release configuration\n", releaseHeaderStyle.Render("Init:")) + fmt.Printf("%s %s\n", releaseHeaderStyle.Render(i18n.T("cmd.ci.label.init")), i18n.T("cmd.ci.init.creating")) fmt.Println() reader := bufio.NewReader(os.Stdin) // Project name defaultName := filepath.Base(projectDir) - fmt.Printf("Project name [%s]: ", defaultName) + fmt.Printf("%s [%s]: ", i18n.T("cmd.ci.init.project_name"), defaultName) name, _ := reader.ReadString('\n') name = strings.TrimSpace(name) if name == "" { @@ -48,7 +50,7 @@ func runCIReleaseInit() error { } // Repository - fmt.Print("GitHub repository (owner/repo): ") + fmt.Printf("%s ", i18n.T("cmd.ci.init.github_repo")) repo, _ := reader.ReadString('\n') repo = strings.TrimSpace(repo) @@ -59,12 +61,13 @@ func runCIReleaseInit() error { // Write config if err := release.WriteConfig(cfg, projectDir); err != nil { - return fmt.Errorf("failed to write config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.write_config"), err) } fmt.Println() - fmt.Printf("%s Configuration written to %s\n", - releaseSuccessStyle.Render("Success:"), + fmt.Printf("%s %s %s\n", + releaseSuccessStyle.Render(i18n.T("cmd.ci.label.success")), + i18n.T("cmd.ci.init.config_written"), release.ConfigPath(projectDir)) return nil diff --git a/cmd/ci/ci_publish.go b/cmd/ci/ci_publish.go index 36f31b5..62efe74 100644 --- a/cmd/ci/ci_publish.go +++ b/cmd/ci/ci_publish.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/release" ) @@ -16,13 +17,13 @@ func runCIPublish(dryRun bool, version string, draft, prerelease bool) error { // Get current directory projectDir, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.working_dir"), err) } // Load configuration cfg, err := release.LoadConfig(projectDir) if err != nil { - return fmt.Errorf("failed to load config: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.load_config"), err) } // Apply CLI overrides @@ -43,35 +44,35 @@ func runCIPublish(dryRun bool, version string, draft, prerelease bool) error { } // Print header - fmt.Printf("%s Publishing release\n", releaseHeaderStyle.Render("CI:")) + fmt.Printf("%s %s\n", releaseHeaderStyle.Render(i18n.T("cmd.ci.label.ci")), i18n.T("cmd.ci.publishing")) if dryRun { - fmt.Printf(" %s\n", releaseDimStyle.Render("(dry-run) use --we-are-go-for-launch to publish")) + fmt.Printf(" %s\n", releaseDimStyle.Render(i18n.T("cmd.ci.dry_run_hint"))) } else { - fmt.Printf(" %s\n", releaseSuccessStyle.Render("GO FOR LAUNCH")) + fmt.Printf(" %s\n", releaseSuccessStyle.Render(i18n.T("cmd.ci.go_for_launch"))) } fmt.Println() // Check for publishers if len(cfg.Publishers) == 0 { - return fmt.Errorf("no publishers configured in .core/release.yaml") + return fmt.Errorf(i18n.T("cmd.ci.error.no_publishers")) } // Publish pre-built artifacts rel, err := release.Publish(ctx, cfg, dryRun) if err != nil { - fmt.Printf("%s %v\n", releaseErrorStyle.Render("Error:"), err) + fmt.Printf("%s %v\n", releaseErrorStyle.Render(i18n.T("cmd.ci.label.error")), err) return err } // Print summary fmt.Println() - fmt.Printf("%s Publish completed!\n", releaseSuccessStyle.Render("Success:")) - fmt.Printf(" Version: %s\n", releaseValueStyle.Render(rel.Version)) - fmt.Printf(" Artifacts: %d\n", len(rel.Artifacts)) + fmt.Printf("%s %s\n", releaseSuccessStyle.Render(i18n.T("cmd.ci.label.success")), i18n.T("cmd.ci.publish_completed")) + fmt.Printf(" %s %s\n", i18n.T("cmd.ci.label.version"), releaseValueStyle.Render(rel.Version)) + fmt.Printf(" %s %d\n", i18n.T("cmd.ci.label.artifacts"), len(rel.Artifacts)) if !dryRun { for _, pub := range cfg.Publishers { - fmt.Printf(" Published: %s\n", releaseValueStyle.Render(pub.Type)) + fmt.Printf(" %s %s\n", i18n.T("cmd.ci.label.published"), releaseValueStyle.Render(pub.Type)) } } diff --git a/cmd/ci/ci_release.go b/cmd/ci/ci_release.go index 00f0075..8f45cb1 100644 --- a/cmd/ci/ci_release.go +++ b/cmd/ci/ci_release.go @@ -3,6 +3,7 @@ package ci import ( "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -31,13 +32,8 @@ var ( var ciCmd = &cobra.Command{ Use: "ci", - Short: "Publish releases (dry-run by default)", - Long: `Publishes pre-built artifacts from dist/ to configured targets. -Run 'core build' first to create artifacts. - -SAFE BY DEFAULT: Runs in dry-run mode unless --we-are-go-for-launch is specified. - -Configuration: .core/release.yaml`, + Short: i18n.T("cmd.ci.short"), + Long: i18n.T("cmd.ci.long"), RunE: func(cmd *cobra.Command, args []string) error { dryRun := !ciGoForLaunch return runCIPublish(dryRun, ciVersion, ciDraft, ciPrerelease) @@ -46,8 +42,8 @@ Configuration: .core/release.yaml`, var ciInitCmd = &cobra.Command{ Use: "init", - Short: "Initialize release configuration", - Long: "Creates a .core/release.yaml configuration file interactively.", + Short: i18n.T("cmd.ci.init.short"), + Long: i18n.T("cmd.ci.init.long"), RunE: func(cmd *cobra.Command, args []string) error { return runCIReleaseInit() }, @@ -55,8 +51,8 @@ var ciInitCmd = &cobra.Command{ var ciChangelogCmd = &cobra.Command{ Use: "changelog", - Short: "Generate changelog", - Long: "Generates a changelog from conventional commits.", + Short: i18n.T("cmd.ci.changelog.short"), + Long: i18n.T("cmd.ci.changelog.long"), RunE: func(cmd *cobra.Command, args []string) error { return runChangelog(changelogFromRef, changelogToRef) }, @@ -64,8 +60,8 @@ var ciChangelogCmd = &cobra.Command{ var ciVersionCmd = &cobra.Command{ Use: "version", - Short: "Show or set version", - Long: "Shows the determined version or validates a version string.", + Short: i18n.T("cmd.ci.version.short"), + Long: i18n.T("cmd.ci.version.long"), RunE: func(cmd *cobra.Command, args []string) error { return runCIReleaseVersion() }, @@ -73,14 +69,14 @@ var ciVersionCmd = &cobra.Command{ func init() { // Main ci command flags - ciCmd.Flags().BoolVar(&ciGoForLaunch, "we-are-go-for-launch", false, "Actually publish (default is dry-run for safety)") - ciCmd.Flags().StringVar(&ciVersion, "version", "", "Version to release (e.g., v1.2.3)") - ciCmd.Flags().BoolVar(&ciDraft, "draft", false, "Create release as a draft") - ciCmd.Flags().BoolVar(&ciPrerelease, "prerelease", false, "Mark release as a prerelease") + ciCmd.Flags().BoolVar(&ciGoForLaunch, "we-are-go-for-launch", false, i18n.T("cmd.ci.flag.go_for_launch")) + ciCmd.Flags().StringVar(&ciVersion, "version", "", i18n.T("cmd.ci.flag.version")) + ciCmd.Flags().BoolVar(&ciDraft, "draft", false, i18n.T("cmd.ci.flag.draft")) + ciCmd.Flags().BoolVar(&ciPrerelease, "prerelease", false, i18n.T("cmd.ci.flag.prerelease")) // Changelog subcommand flags - ciChangelogCmd.Flags().StringVar(&changelogFromRef, "from", "", "Starting ref (default: previous tag)") - ciChangelogCmd.Flags().StringVar(&changelogToRef, "to", "", "Ending ref (default: HEAD)") + ciChangelogCmd.Flags().StringVar(&changelogFromRef, "from", "", i18n.T("cmd.ci.changelog.flag.from")) + ciChangelogCmd.Flags().StringVar(&changelogToRef, "to", "", i18n.T("cmd.ci.changelog.flag.to")) // Add subcommands ciCmd.AddCommand(ciInitCmd) diff --git a/cmd/ci/ci_version.go b/cmd/ci/ci_version.go index c9e2b13..1f833d4 100644 --- a/cmd/ci/ci_version.go +++ b/cmd/ci/ci_version.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/release" ) @@ -11,14 +12,14 @@ import ( func runCIReleaseVersion() error { projectDir, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.working_dir"), err) } version, err := release.DetermineVersion(projectDir) if err != nil { - return fmt.Errorf("failed to determine version: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.ci.error.determine_version"), err) } - fmt.Printf("Version: %s\n", releaseValueStyle.Render(version)) + fmt.Printf("%s %s\n", i18n.T("cmd.ci.label.version"), releaseValueStyle.Render(version)) return nil } diff --git a/cmd/dev/dev.go b/cmd/dev/dev.go index f59da43..bae8b48 100644 --- a/cmd/dev/dev.go +++ b/cmd/dev/dev.go @@ -30,6 +30,7 @@ package dev import ( "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -55,31 +56,8 @@ var ( func AddCommands(root *cobra.Command) { devCmd := &cobra.Command{ Use: "dev", - Short: "Multi-repo development workflow", - Long: `Manage multiple git repositories and GitHub integration. - -Uses repos.yaml to discover repositories. Falls back to scanning -the current directory if no registry is found. - -Git Operations: - work Combined status -> commit -> push workflow - health Quick repo health summary - commit Claude-assisted commit messages - push Push repos with unpushed commits - pull Pull repos behind remote - -GitHub Integration (requires gh CLI): - issues List open issues across repos - reviews List PRs awaiting review - ci Check GitHub Actions status - impact Analyse dependency impact - -Dev Environment: - install Download dev environment image - boot Start dev environment VM - stop Stop dev environment VM - shell Open shell in dev VM - status Check dev VM status`, + Short: i18n.T("cmd.dev.short"), + Long: i18n.T("cmd.dev.long"), } root.AddCommand(devCmd) diff --git a/cmd/dev/dev_api.go b/cmd/dev/dev_api.go index 7f0fbde..b215ac6 100644 --- a/cmd/dev/dev_api.go +++ b/cmd/dev/dev_api.go @@ -1,6 +1,7 @@ package dev import ( + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -9,7 +10,7 @@ func addAPICommands(parent *cobra.Command) { // Create the 'api' command apiCmd := &cobra.Command{ Use: "api", - Short: "Tools for managing service APIs", + Short: i18n.T("cmd.dev.api.short"), } parent.AddCommand(apiCmd) diff --git a/cmd/dev/dev_ci.go b/cmd/dev/dev_ci.go index 8430552..e9db2b2 100644 --- a/cmd/dev/dev_ci.go +++ b/cmd/dev/dev_ci.go @@ -9,6 +9,7 @@ import ( "time" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -46,10 +47,8 @@ var ( func addCICommand(parent *cobra.Command) { ciCmd := &cobra.Command{ Use: "ci", - Short: "Check CI status across all repos", - Long: `Fetches GitHub Actions workflow status for all repos. -Shows latest run status for each repo. -Requires the 'gh' CLI to be installed and authenticated.`, + Short: i18n.T("cmd.dev.ci.short"), + Long: i18n.T("cmd.dev.ci.long"), RunE: func(cmd *cobra.Command, args []string) error { branch := ciBranch if branch == "" { @@ -59,9 +58,9 @@ Requires the 'gh' CLI to be installed and authenticated.`, }, } - ciCmd.Flags().StringVar(&ciRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") - ciCmd.Flags().StringVarP(&ciBranch, "branch", "b", "main", "Filter by branch") - ciCmd.Flags().BoolVar(&ciFailedOnly, "failed", false, "Show only failed runs") + ciCmd.Flags().StringVar(&ciRegistryPath, "registry", "", i18n.T("cmd.dev.ci.flag.registry")) + ciCmd.Flags().StringVarP(&ciBranch, "branch", "b", "main", i18n.T("cmd.dev.ci.flag.branch")) + ciCmd.Flags().BoolVar(&ciFailedOnly, "failed", false, i18n.T("cmd.dev.ci.flag.failed")) parent.AddCommand(ciCmd) } @@ -69,7 +68,7 @@ Requires the 'gh' CLI to be installed and authenticated.`, func runCI(registryPath string, branch string, failedOnly bool) error { // Check gh is available if _, err := exec.LookPath("gh"); err != nil { - return fmt.Errorf("'gh' CLI not found. Install from https://cli.github.com/") + return fmt.Errorf(i18n.T("error.gh_not_found")) } // Find or use provided registry @@ -105,7 +104,7 @@ func runCI(registryPath string, branch string, failedOnly bool) error { repoList := reg.List() for i, repo := range repoList { repoFullName := fmt.Sprintf("%s/%s", reg.Org, repo.Name) - fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render("Checking"), i+1, len(repoList), repo.Name) + fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("cli.progress.checking")), i+1, len(repoList), repo.Name) runs, err := fetchWorkflowRuns(repoFullName, repo.Name, branch) if err != nil { @@ -147,18 +146,18 @@ func runCI(registryPath string, branch string, failedOnly bool) error { // Print summary fmt.Println() - fmt.Printf("%d repos checked", len(repoList)) + fmt.Printf("%s", i18n.T("cmd.dev.ci.repos_checked", map[string]interface{}{"Count": len(repoList)})) if success > 0 { - fmt.Printf(" * %s", ciSuccessStyle.Render(fmt.Sprintf("%d passing", success))) + fmt.Printf(" * %s", ciSuccessStyle.Render(i18n.T("cmd.dev.ci.passing", map[string]interface{}{"Count": success}))) } if failed > 0 { - fmt.Printf(" * %s", ciFailureStyle.Render(fmt.Sprintf("%d failing", failed))) + fmt.Printf(" * %s", ciFailureStyle.Render(i18n.T("cmd.dev.ci.failing", map[string]interface{}{"Count": failed}))) } if pending > 0 { - fmt.Printf(" * %s", ciPendingStyle.Render(fmt.Sprintf("%d pending", pending))) + fmt.Printf(" * %s", ciPendingStyle.Render(i18n.T("cmd.dev.ci.pending", map[string]interface{}{"Count": pending}))) } if len(noCI) > 0 { - fmt.Printf(" * %s", ciSkippedStyle.Render(fmt.Sprintf("%d no CI", len(noCI)))) + fmt.Printf(" * %s", ciSkippedStyle.Render(i18n.T("cmd.dev.ci.no_ci", map[string]interface{}{"Count": len(noCI)}))) } fmt.Println() fmt.Println() @@ -183,7 +182,7 @@ func runCI(registryPath string, branch string, failedOnly bool) error { if len(fetchErrors) > 0 { fmt.Println() for _, err := range fetchErrors { - fmt.Printf("%s %s\n", errorStyle.Render("Error:"), err) + fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.dev.ci.error_label")), err) } } diff --git a/cmd/dev/dev_commit.go b/cmd/dev/dev_commit.go index 05a3c5c..3507cfe 100644 --- a/cmd/dev/dev_commit.go +++ b/cmd/dev/dev_commit.go @@ -7,6 +7,7 @@ import ( "github.com/host-uk/core/cmd/shared" "github.com/host-uk/core/pkg/git" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -21,16 +22,15 @@ var ( func addCommitCommand(parent *cobra.Command) { commitCmd := &cobra.Command{ Use: "commit", - Short: "Claude-assisted commits across repos", - Long: `Uses Claude to create commits for dirty repos. -Shows uncommitted changes and invokes Claude to generate commit messages.`, + Short: i18n.T("cmd.dev.commit.short"), + Long: i18n.T("cmd.dev.commit.long"), RunE: func(cmd *cobra.Command, args []string) error { return runCommit(commitRegistryPath, commitAll) }, } - commitCmd.Flags().StringVar(&commitRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") - commitCmd.Flags().BoolVar(&commitAll, "all", false, "Commit all dirty repos without prompting") + commitCmd.Flags().StringVar(&commitRegistryPath, "registry", "", i18n.T("cmd.dev.commit.flag.registry")) + commitCmd.Flags().BoolVar(&commitAll, "all", false, i18n.T("cmd.dev.commit.flag.all")) parent.AddCommand(commitCmd) } @@ -47,7 +47,7 @@ func runCommit(registryPath string, all bool) error { if err != nil { return fmt.Errorf("failed to load registry: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.registry_label")), registryPath) } else { registryPath, err = repos.FindRegistry() if err == nil { @@ -55,7 +55,7 @@ func runCommit(registryPath string, all bool) error { if err != nil { return fmt.Errorf("failed to load registry: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.registry_label")), registryPath) } else { // Fallback: scan current directory cwd, _ := os.Getwd() @@ -63,7 +63,7 @@ func runCommit(registryPath string, all bool) error { if err != nil { return fmt.Errorf("failed to scan directory: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Scanning:"), cwd) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd) registryPath = cwd } } @@ -80,7 +80,7 @@ func runCommit(registryPath string, all bool) error { } if len(paths) == 0 { - fmt.Println("No git repositories found.") + fmt.Println(i18n.T("cmd.dev.no_git_repos")) return nil } @@ -99,22 +99,22 @@ func runCommit(registryPath string, all bool) error { } if len(dirtyRepos) == 0 { - fmt.Println("No uncommitted changes found.") + fmt.Println(i18n.T("cmd.dev.no_changes")) return nil } // Show dirty repos - fmt.Printf("\n%d repo(s) with uncommitted changes:\n\n", len(dirtyRepos)) + fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.repos_with_changes", map[string]interface{}{"Count": len(dirtyRepos)})) for _, s := range dirtyRepos { fmt.Printf(" %s: ", repoNameStyle.Render(s.Name)) if s.Modified > 0 { - fmt.Printf("%s ", dirtyStyle.Render(fmt.Sprintf("%d modified", s.Modified))) + fmt.Printf("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified}))) } if s.Untracked > 0 { - fmt.Printf("%s ", dirtyStyle.Render(fmt.Sprintf("%d untracked", s.Untracked))) + fmt.Printf("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked}))) } if s.Staged > 0 { - fmt.Printf("%s ", aheadStyle.Render(fmt.Sprintf("%d staged", s.Staged))) + fmt.Printf("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged}))) } fmt.Println() } @@ -122,8 +122,8 @@ func runCommit(registryPath string, all bool) error { // Confirm unless --all if !all { fmt.Println() - if !shared.Confirm("Have Claude commit these repos?") { - fmt.Println("Aborted.") + if !shared.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) { + fmt.Println(i18n.T("cli.aborted")) return nil } } @@ -133,22 +133,22 @@ func runCommit(registryPath string, all bool) error { // Commit each dirty repo var succeeded, failed int for _, s := range dirtyRepos { - fmt.Printf("%s %s\n", dimStyle.Render("Committing"), s.Name) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.committing")), s.Name) if err := claudeCommit(ctx, s.Path, s.Name, registryPath); err != nil { fmt.Printf(" %s %s\n", errorStyle.Render("x"), err) failed++ } else { - fmt.Printf(" %s committed\n", successStyle.Render("v")) + fmt.Printf(" %s %s\n", successStyle.Render("v"), i18n.T("cmd.dev.committed")) succeeded++ } fmt.Println() } // Summary - fmt.Printf("%s %d succeeded", successStyle.Render("Done:"), succeeded) + fmt.Printf("%s", successStyle.Render(i18n.T("cmd.dev.done_succeeded", map[string]interface{}{"Count": succeeded}))) if failed > 0 { - fmt.Printf(", %s", errorStyle.Render(fmt.Sprintf("%d failed", failed))) + fmt.Printf(", %s", errorStyle.Render(i18n.T("cmd.dev.count_failed", map[string]interface{}{"Count": failed}))) } fmt.Println() diff --git a/cmd/dev/dev_health.go b/cmd/dev/dev_health.go index 80e8dc0..7a7fe25 100644 --- a/cmd/dev/dev_health.go +++ b/cmd/dev/dev_health.go @@ -8,6 +8,7 @@ import ( "github.com/host-uk/core/cmd/shared" "github.com/host-uk/core/pkg/git" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -22,16 +23,15 @@ var ( func addHealthCommand(parent *cobra.Command) { healthCmd := &cobra.Command{ Use: "health", - Short: "Quick health check across all repos", - Long: `Shows a summary of repository health: -total repos, dirty repos, unpushed commits, etc.`, + Short: i18n.T("cmd.dev.health.short"), + Long: i18n.T("cmd.dev.health.long"), RunE: func(cmd *cobra.Command, args []string) error { return runHealth(healthRegistryPath, healthVerbose) }, } - healthCmd.Flags().StringVar(&healthRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") - healthCmd.Flags().BoolVarP(&healthVerbose, "verbose", "v", false, "Show detailed breakdown") + healthCmd.Flags().StringVar(&healthRegistryPath, "registry", "", i18n.T("cmd.dev.health.flag.registry")) + healthCmd.Flags().BoolVarP(&healthVerbose, "verbose", "v", false, i18n.T("cmd.dev.health.flag.verbose")) parent.AddCommand(healthCmd) } @@ -77,7 +77,7 @@ func runHealth(registryPath string, verbose bool) error { } if len(paths) == 0 { - fmt.Println("No git repositories found.") + fmt.Println(i18n.T("cmd.dev.no_git_repos")) return nil } @@ -125,16 +125,16 @@ func runHealth(registryPath string, verbose bool) error { // Verbose output if verbose { if len(dirtyRepos) > 0 { - fmt.Printf("%s %s\n", warningStyle.Render("Dirty:"), formatRepoList(dirtyRepos)) + fmt.Printf("%s %s\n", warningStyle.Render(i18n.T("cmd.dev.health.dirty_label")), formatRepoList(dirtyRepos)) } if len(aheadRepos) > 0 { - fmt.Printf("%s %s\n", successStyle.Render("Ahead:"), formatRepoList(aheadRepos)) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.dev.health.ahead_label")), formatRepoList(aheadRepos)) } if len(behindRepos) > 0 { - fmt.Printf("%s %s\n", warningStyle.Render("Behind:"), formatRepoList(behindRepos)) + fmt.Printf("%s %s\n", warningStyle.Render(i18n.T("cmd.dev.health.behind_label")), formatRepoList(behindRepos)) } if len(errorRepos) > 0 { - fmt.Printf("%s %s\n", errorStyle.Render("Errors:"), formatRepoList(errorRepos)) + fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.dev.health.errors_label")), formatRepoList(errorRepos)) } fmt.Println() } @@ -144,33 +144,33 @@ func runHealth(registryPath string, verbose bool) error { func printHealthSummary(total int, dirty, ahead, behind, errors []string) { parts := []string{ - shared.StatusPart(total, "repos", shared.ValueStyle), + shared.StatusPart(total, i18n.T("cmd.dev.health.repos"), shared.ValueStyle), } // Dirty status if len(dirty) > 0 { - parts = append(parts, shared.StatusPart(len(dirty), "dirty", shared.WarningStyle)) + parts = append(parts, shared.StatusPart(len(dirty), i18n.T("cmd.dev.health.dirty"), shared.WarningStyle)) } else { - parts = append(parts, shared.StatusText("clean", shared.SuccessStyle)) + parts = append(parts, shared.StatusText(i18n.T("cmd.dev.status.clean"), shared.SuccessStyle)) } // Push status if len(ahead) > 0 { - parts = append(parts, shared.StatusPart(len(ahead), "to push", shared.ValueStyle)) + parts = append(parts, shared.StatusPart(len(ahead), i18n.T("cmd.dev.health.to_push"), shared.ValueStyle)) } else { - parts = append(parts, shared.StatusText("synced", shared.SuccessStyle)) + parts = append(parts, shared.StatusText(i18n.T("cmd.dev.health.synced"), shared.SuccessStyle)) } // Pull status if len(behind) > 0 { - parts = append(parts, shared.StatusPart(len(behind), "to pull", shared.WarningStyle)) + parts = append(parts, shared.StatusPart(len(behind), i18n.T("cmd.dev.health.to_pull"), shared.WarningStyle)) } else { - parts = append(parts, shared.StatusText("up to date", shared.SuccessStyle)) + parts = append(parts, shared.StatusText(i18n.T("cmd.dev.health.up_to_date"), shared.SuccessStyle)) } // Errors (only if any) if len(errors) > 0 { - parts = append(parts, shared.StatusPart(len(errors), "errors", shared.ErrorStyle)) + parts = append(parts, shared.StatusPart(len(errors), i18n.T("cmd.dev.health.errors"), shared.ErrorStyle)) } fmt.Println(shared.StatusLine(parts...)) @@ -180,7 +180,7 @@ func formatRepoList(reposList []string) string { if len(reposList) <= 5 { return joinRepos(reposList) } - return joinRepos(reposList[:5]) + fmt.Sprintf(" +%d more", len(reposList)-5) + return joinRepos(reposList[:5]) + " " + i18n.T("cmd.dev.health.more", map[string]interface{}{"Count": len(reposList) - 5}) } func joinRepos(reposList []string) string { diff --git a/cmd/dev/dev_impact.go b/cmd/dev/dev_impact.go index 3dc7153..9754688 100644 --- a/cmd/dev/dev_impact.go +++ b/cmd/dev/dev_impact.go @@ -5,6 +5,7 @@ import ( "sort" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -23,16 +24,15 @@ var impactRegistryPath string func addImpactCommand(parent *cobra.Command) { impactCmd := &cobra.Command{ Use: "impact ", - Short: "Show impact of changing a repo", - Long: `Analyzes the dependency graph to show which repos -would be affected by changes to the specified repo.`, - Args: cobra.ExactArgs(1), + Short: i18n.T("cmd.dev.impact.short"), + Long: i18n.T("cmd.dev.impact.long"), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return runImpact(impactRegistryPath, args[0]) }, } - impactCmd.Flags().StringVar(&impactRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") + impactCmd.Flags().StringVar(&impactRegistryPath, "registry", "", i18n.T("cmd.dev.impact.flag.registry")) parent.AddCommand(impactCmd) } @@ -55,14 +55,14 @@ func runImpact(registryPath string, repoName string) error { return fmt.Errorf("failed to load registry: %w", err) } } else { - return fmt.Errorf("impact analysis requires repos.yaml with dependency information") + return fmt.Errorf(i18n.T("cmd.dev.impact.requires_registry")) } } // Check repo exists repo, exists := reg.Get(repoName) if !exists { - return fmt.Errorf("repo '%s' not found in registry", repoName) + return fmt.Errorf(i18n.T("error.repo_not_found", map[string]interface{}{"Name": repoName})) } // Build reverse dependency graph @@ -91,22 +91,22 @@ func runImpact(registryPath string, repoName string) error { // Print results fmt.Println() - fmt.Printf("%s %s\n", dimStyle.Render("Impact analysis for"), repoNameStyle.Render(repoName)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.impact.analysis_for")), repoNameStyle.Render(repoName)) if repo.Description != "" { fmt.Printf("%s\n", dimStyle.Render(repo.Description)) } fmt.Println() if len(allAffected) == 0 { - fmt.Printf("%s No repos depend on %s\n", impactSafeStyle.Render("v"), repoName) + fmt.Printf("%s %s\n", impactSafeStyle.Render("v"), i18n.T("cmd.dev.impact.no_dependents", map[string]interface{}{"Name": repoName})) return nil } // Direct dependents if len(direct) > 0 { - fmt.Printf("%s %d direct dependent(s):\n", + fmt.Printf("%s %s\n", impactDirectStyle.Render("*"), - len(direct), + i18n.T("cmd.dev.impact.direct_dependents", map[string]interface{}{"Count": len(direct)}), ) for _, d := range direct { r, _ := reg.Get(d) @@ -121,9 +121,9 @@ func runImpact(registryPath string, repoName string) error { // Indirect dependents if len(indirect) > 0 { - fmt.Printf("%s %d transitive dependent(s):\n", + fmt.Printf("%s %s\n", impactIndirectStyle.Render("o"), - len(indirect), + i18n.T("cmd.dev.impact.transitive_dependents", map[string]interface{}{"Count": len(indirect)}), ) for _, d := range indirect { r, _ := reg.Get(d) @@ -137,10 +137,13 @@ func runImpact(registryPath string, repoName string) error { } // Summary - fmt.Printf("%s Changes to %s affect %s\n", - dimStyle.Render("Summary:"), - repoNameStyle.Render(repoName), - impactDirectStyle.Render(fmt.Sprintf("%d/%d repos", len(allAffected), len(reg.Repos)-1)), + fmt.Printf("%s %s\n", + dimStyle.Render(i18n.T("cmd.dev.impact.summary")), + i18n.T("cmd.dev.impact.changes_affect", map[string]interface{}{ + "Repo": repoNameStyle.Render(repoName), + "Affected": len(allAffected), + "Total": len(reg.Repos) - 1, + }), ) return nil diff --git a/cmd/dev/dev_issues.go b/cmd/dev/dev_issues.go index 7bbe85d..dc65bbc 100644 --- a/cmd/dev/dev_issues.go +++ b/cmd/dev/dev_issues.go @@ -10,6 +10,7 @@ import ( "time" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -60,9 +61,8 @@ var ( func addIssuesCommand(parent *cobra.Command) { issuesCmd := &cobra.Command{ Use: "issues", - Short: "List open issues across all repos", - Long: `Fetches open issues from GitHub for all repos in the registry. -Requires the 'gh' CLI to be installed and authenticated.`, + Short: i18n.T("cmd.dev.issues.short"), + Long: i18n.T("cmd.dev.issues.long"), RunE: func(cmd *cobra.Command, args []string) error { limit := issuesLimit if limit == 0 { @@ -72,9 +72,9 @@ Requires the 'gh' CLI to be installed and authenticated.`, }, } - issuesCmd.Flags().StringVar(&issuesRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") - issuesCmd.Flags().IntVarP(&issuesLimit, "limit", "l", 10, "Max issues per repo") - issuesCmd.Flags().StringVarP(&issuesAssignee, "assignee", "a", "", "Filter by assignee (use @me for yourself)") + issuesCmd.Flags().StringVar(&issuesRegistryPath, "registry", "", i18n.T("cmd.dev.issues.flag.registry")) + issuesCmd.Flags().IntVarP(&issuesLimit, "limit", "l", 10, i18n.T("cmd.dev.issues.flag.limit")) + issuesCmd.Flags().StringVarP(&issuesAssignee, "assignee", "a", "", i18n.T("cmd.dev.issues.flag.assignee")) parent.AddCommand(issuesCmd) } @@ -82,7 +82,7 @@ Requires the 'gh' CLI to be installed and authenticated.`, func runIssues(registryPath string, limit int, assignee string) error { // Check gh is available if _, err := exec.LookPath("gh"); err != nil { - return fmt.Errorf("'gh' CLI not found. Install from https://cli.github.com/") + return fmt.Errorf(i18n.T("error.gh_not_found")) } // Find or use provided registry, fall back to directory scan @@ -118,7 +118,7 @@ func runIssues(registryPath string, limit int, assignee string) error { repoList := reg.List() for i, repo := range repoList { repoFullName := fmt.Sprintf("%s/%s", reg.Org, repo.Name) - fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render("Fetching"), i+1, len(repoList), repo.Name) + fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("cli.progress.fetching")), i+1, len(repoList), repo.Name) issues, err := fetchIssues(repoFullName, repo.Name, limit, assignee) if err != nil { @@ -136,11 +136,11 @@ func runIssues(registryPath string, limit int, assignee string) error { // Print issues if len(allIssues) == 0 { - fmt.Println("No open issues found.") + fmt.Println(i18n.T("cmd.dev.issues.no_issues")) return nil } - fmt.Printf("\n%d open issue(s):\n\n", len(allIssues)) + fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.issues.open_issues", map[string]interface{}{"Count": len(allIssues)})) for _, issue := range allIssues { printIssue(issue) @@ -150,7 +150,7 @@ func runIssues(registryPath string, limit int, assignee string) error { if len(fetchErrors) > 0 { fmt.Println() for _, err := range fetchErrors { - fmt.Printf("%s %s\n", errorStyle.Render("Error:"), err) + fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.dev.issues.error_label")), err) } } diff --git a/cmd/dev/dev_pull.go b/cmd/dev/dev_pull.go index 03b0960..3116546 100644 --- a/cmd/dev/dev_pull.go +++ b/cmd/dev/dev_pull.go @@ -7,6 +7,7 @@ import ( "os/exec" "github.com/host-uk/core/pkg/git" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -21,16 +22,15 @@ var ( func addPullCommand(parent *cobra.Command) { pullCmd := &cobra.Command{ Use: "pull", - Short: "Pull updates across all repos", - Long: `Pulls updates for all repos. -By default only pulls repos that are behind. Use --all to pull all repos.`, + Short: i18n.T("cmd.dev.pull.short"), + Long: i18n.T("cmd.dev.pull.long"), RunE: func(cmd *cobra.Command, args []string) error { return runPull(pullRegistryPath, pullAll) }, } - pullCmd.Flags().StringVar(&pullRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") - pullCmd.Flags().BoolVar(&pullAll, "all", false, "Pull all repos, not just those behind") + pullCmd.Flags().StringVar(&pullRegistryPath, "registry", "", i18n.T("cmd.dev.pull.flag.registry")) + pullCmd.Flags().BoolVar(&pullAll, "all", false, i18n.T("cmd.dev.pull.flag.all")) parent.AddCommand(pullCmd) } @@ -47,7 +47,7 @@ func runPull(registryPath string, all bool) error { if err != nil { return fmt.Errorf("failed to load registry: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.registry_label")), registryPath) } else { registryPath, err = repos.FindRegistry() if err == nil { @@ -55,7 +55,7 @@ func runPull(registryPath string, all bool) error { if err != nil { return fmt.Errorf("failed to load registry: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.registry_label")), registryPath) } else { // Fallback: scan current directory cwd, _ := os.Getwd() @@ -63,7 +63,7 @@ func runPull(registryPath string, all bool) error { if err != nil { return fmt.Errorf("failed to scan directory: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Scanning:"), cwd) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd) } } @@ -79,7 +79,7 @@ func runPull(registryPath string, all bool) error { } if len(paths) == 0 { - fmt.Println("No git repositories found.") + fmt.Println(i18n.T("cmd.dev.no_git_repos")) return nil } @@ -101,19 +101,19 @@ func runPull(registryPath string, all bool) error { } if len(toPull) == 0 { - fmt.Println("All repos up to date. Nothing to pull.") + fmt.Println(i18n.T("cmd.dev.pull.all_up_to_date")) return nil } // Show what we're pulling if all { - fmt.Printf("\nPulling %d repo(s):\n\n", len(toPull)) + fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.pull.pulling_repos", map[string]interface{}{"Count": len(toPull)})) } else { - fmt.Printf("\n%d repo(s) behind upstream:\n\n", len(toPull)) + fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.pull.repos_behind", map[string]interface{}{"Count": len(toPull)})) for _, s := range toPull { fmt.Printf(" %s: %s\n", repoNameStyle.Render(s.Name), - dimStyle.Render(fmt.Sprintf("%d commit(s) behind", s.Behind)), + dimStyle.Render(i18n.T("cmd.dev.pull.commits_behind", map[string]interface{}{"Count": s.Behind})), ) } fmt.Println() @@ -122,7 +122,7 @@ func runPull(registryPath string, all bool) error { // Pull each repo var succeeded, failed int for _, s := range toPull { - fmt.Printf(" %s %s... ", dimStyle.Render("Pulling"), s.Name) + fmt.Printf(" %s %s... ", dimStyle.Render(i18n.T("cmd.dev.pull.pulling")), s.Name) err := gitPull(ctx, s.Path) if err != nil { @@ -136,9 +136,9 @@ func runPull(registryPath string, all bool) error { // Summary fmt.Println() - fmt.Printf("%s %d pulled", successStyle.Render("Done:"), succeeded) + fmt.Printf("%s", successStyle.Render(i18n.T("cmd.dev.pull.done_pulled", map[string]interface{}{"Count": succeeded}))) if failed > 0 { - fmt.Printf(", %s", errorStyle.Render(fmt.Sprintf("%d failed", failed))) + fmt.Printf(", %s", errorStyle.Render(i18n.T("cmd.dev.count_failed", map[string]interface{}{"Count": failed}))) } fmt.Println() diff --git a/cmd/dev/dev_push.go b/cmd/dev/dev_push.go index 81e9a5c..b079da9 100644 --- a/cmd/dev/dev_push.go +++ b/cmd/dev/dev_push.go @@ -7,6 +7,7 @@ import ( "github.com/host-uk/core/cmd/shared" "github.com/host-uk/core/pkg/git" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -21,16 +22,15 @@ var ( func addPushCommand(parent *cobra.Command) { pushCmd := &cobra.Command{ Use: "push", - Short: "Push commits across all repos", - Long: `Pushes unpushed commits for all repos. -Shows repos with commits to push and confirms before pushing.`, + Short: i18n.T("cmd.dev.push.short"), + Long: i18n.T("cmd.dev.push.long"), RunE: func(cmd *cobra.Command, args []string) error { return runPush(pushRegistryPath, pushForce) }, } - pushCmd.Flags().StringVar(&pushRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") - pushCmd.Flags().BoolVarP(&pushForce, "force", "f", false, "Skip confirmation prompt") + pushCmd.Flags().StringVar(&pushRegistryPath, "registry", "", i18n.T("cmd.dev.push.flag.registry")) + pushCmd.Flags().BoolVarP(&pushForce, "force", "f", false, i18n.T("cmd.dev.push.flag.force")) parent.AddCommand(pushCmd) } @@ -47,7 +47,7 @@ func runPush(registryPath string, force bool) error { if err != nil { return fmt.Errorf("failed to load registry: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.registry_label")), registryPath) } else { registryPath, err = repos.FindRegistry() if err == nil { @@ -55,7 +55,7 @@ func runPush(registryPath string, force bool) error { if err != nil { return fmt.Errorf("failed to load registry: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.registry_label")), registryPath) } else { // Fallback: scan current directory cwd, _ := os.Getwd() @@ -63,7 +63,7 @@ func runPush(registryPath string, force bool) error { if err != nil { return fmt.Errorf("failed to scan directory: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Scanning:"), cwd) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd) } } @@ -79,7 +79,7 @@ func runPush(registryPath string, force bool) error { } if len(paths) == 0 { - fmt.Println("No git repositories found.") + fmt.Println(i18n.T("cmd.dev.no_git_repos")) return nil } @@ -98,17 +98,17 @@ func runPush(registryPath string, force bool) error { } if len(aheadRepos) == 0 { - fmt.Println("All repos up to date. Nothing to push.") + fmt.Println(i18n.T("cmd.dev.push.all_up_to_date")) return nil } // Show repos to push - fmt.Printf("\n%d repo(s) with unpushed commits:\n\n", len(aheadRepos)) + fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.push.repos_with_unpushed", map[string]interface{}{"Count": len(aheadRepos)})) totalCommits := 0 for _, s := range aheadRepos { fmt.Printf(" %s: %s\n", repoNameStyle.Render(s.Name), - aheadStyle.Render(fmt.Sprintf("%d commit(s)", s.Ahead)), + aheadStyle.Render(i18n.T("cmd.dev.push.commits_count", map[string]interface{}{"Count": s.Ahead})), ) totalCommits += s.Ahead } @@ -116,8 +116,8 @@ func runPush(registryPath string, force bool) error { // Confirm unless --force if !force { fmt.Println() - if !shared.Confirm(fmt.Sprintf("Push %d commit(s) to %d repo(s)?", totalCommits, len(aheadRepos))) { - fmt.Println("Aborted.") + if !shared.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": totalCommits, "Repos": len(aheadRepos)})) { + fmt.Println(i18n.T("cli.aborted")) return nil } } @@ -145,9 +145,9 @@ func runPush(registryPath string, force bool) error { // Summary fmt.Println() - fmt.Printf("%s %d pushed", successStyle.Render("Done:"), succeeded) + fmt.Printf("%s", successStyle.Render(i18n.T("cmd.dev.push.done_pushed", map[string]interface{}{"Count": succeeded}))) if failed > 0 { - fmt.Printf(", %s", errorStyle.Render(fmt.Sprintf("%d failed", failed))) + fmt.Printf(", %s", errorStyle.Render(i18n.T("cmd.dev.count_failed", map[string]interface{}{"Count": failed}))) } fmt.Println() diff --git a/cmd/dev/dev_reviews.go b/cmd/dev/dev_reviews.go index f6324b3..1edae9d 100644 --- a/cmd/dev/dev_reviews.go +++ b/cmd/dev/dev_reviews.go @@ -10,6 +10,7 @@ import ( "time" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -61,18 +62,16 @@ var ( func addReviewsCommand(parent *cobra.Command) { reviewsCmd := &cobra.Command{ Use: "reviews", - Short: "List PRs needing review across all repos", - Long: `Fetches open PRs from GitHub for all repos in the registry. -Shows review status (approved, changes requested, pending). -Requires the 'gh' CLI to be installed and authenticated.`, + Short: i18n.T("cmd.dev.reviews.short"), + Long: i18n.T("cmd.dev.reviews.long"), RunE: func(cmd *cobra.Command, args []string) error { return runReviews(reviewsRegistryPath, reviewsAuthor, reviewsShowAll) }, } - reviewsCmd.Flags().StringVar(&reviewsRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") - reviewsCmd.Flags().StringVar(&reviewsAuthor, "author", "", "Filter by PR author") - reviewsCmd.Flags().BoolVar(&reviewsShowAll, "all", false, "Show all PRs including drafts") + reviewsCmd.Flags().StringVar(&reviewsRegistryPath, "registry", "", i18n.T("cmd.dev.reviews.flag.registry")) + reviewsCmd.Flags().StringVar(&reviewsAuthor, "author", "", i18n.T("cmd.dev.reviews.flag.author")) + reviewsCmd.Flags().BoolVar(&reviewsShowAll, "all", false, i18n.T("cmd.dev.reviews.flag.all")) parent.AddCommand(reviewsCmd) } @@ -80,7 +79,7 @@ Requires the 'gh' CLI to be installed and authenticated.`, func runReviews(registryPath string, author string, showAll bool) error { // Check gh is available if _, err := exec.LookPath("gh"); err != nil { - return fmt.Errorf("'gh' CLI not found. Install from https://cli.github.com/") + return fmt.Errorf(i18n.T("error.gh_not_found")) } // Find or use provided registry, fall back to directory scan @@ -116,7 +115,7 @@ func runReviews(registryPath string, author string, showAll bool) error { repoList := reg.List() for i, repo := range repoList { repoFullName := fmt.Sprintf("%s/%s", reg.Org, repo.Name) - fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render("Fetching"), i+1, len(repoList), repo.Name) + fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("cli.progress.fetching")), i+1, len(repoList), repo.Name) prs, err := fetchPRs(repoFullName, repo.Name, author) if err != nil { @@ -147,7 +146,7 @@ func runReviews(registryPath string, author string, showAll bool) error { // Print PRs if len(allPRs) == 0 { - fmt.Println("No open PRs found.") + fmt.Println(i18n.T("cmd.dev.reviews.no_prs")) return nil } @@ -165,15 +164,15 @@ func runReviews(registryPath string, author string, showAll bool) error { } fmt.Println() - fmt.Printf("%d open PR(s)", len(allPRs)) + fmt.Printf("%s", i18n.T("cmd.dev.reviews.open_prs", map[string]interface{}{"Count": len(allPRs)})) if pending > 0 { - fmt.Printf(" * %s", prPendingStyle.Render(fmt.Sprintf("%d pending", pending))) + fmt.Printf(" * %s", prPendingStyle.Render(i18n.T("cmd.dev.reviews.pending", map[string]interface{}{"Count": pending}))) } if approved > 0 { - fmt.Printf(" * %s", prApprovedStyle.Render(fmt.Sprintf("%d approved", approved))) + fmt.Printf(" * %s", prApprovedStyle.Render(i18n.T("cmd.dev.reviews.approved", map[string]interface{}{"Count": approved}))) } if changesRequested > 0 { - fmt.Printf(" * %s", prChangesStyle.Render(fmt.Sprintf("%d changes requested", changesRequested))) + fmt.Printf(" * %s", prChangesStyle.Render(i18n.T("cmd.dev.reviews.changes_requested", map[string]interface{}{"Count": changesRequested}))) } fmt.Println() fmt.Println() @@ -186,7 +185,7 @@ func runReviews(registryPath string, author string, showAll bool) error { if len(fetchErrors) > 0 { fmt.Println() for _, err := range fetchErrors { - fmt.Printf("%s %s\n", errorStyle.Render("Error:"), err) + fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.dev.issues.error_label")), err) } } @@ -242,17 +241,17 @@ func printPR(pr GitHubPR) { var status string switch pr.ReviewDecision { case "APPROVED": - status = prApprovedStyle.Render("v approved") + status = prApprovedStyle.Render(i18n.T("cmd.dev.reviews.status_approved")) case "CHANGES_REQUESTED": - status = prChangesStyle.Render("* changes requested") + status = prChangesStyle.Render(i18n.T("cmd.dev.reviews.status_changes")) default: - status = prPendingStyle.Render("o pending review") + status = prPendingStyle.Render(i18n.T("cmd.dev.reviews.status_pending")) } // Draft indicator draft := "" if pr.IsDraft { - draft = prDraftStyle.Render(" [draft]") + draft = prDraftStyle.Render(" " + i18n.T("cmd.dev.reviews.draft")) } age := shared.FormatAge(pr.CreatedAt) diff --git a/cmd/dev/dev_sync.go b/cmd/dev/dev_sync.go index ec37a68..ba4bdec 100644 --- a/cmd/dev/dev_sync.go +++ b/cmd/dev/dev_sync.go @@ -10,6 +10,7 @@ import ( "path/filepath" "text/template" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" "golang.org/x/text/cases" "golang.org/x/text/language" @@ -19,15 +20,13 @@ import ( func addSyncCommand(parent *cobra.Command) { syncCmd := &cobra.Command{ Use: "sync", - Short: "Synchronizes the public service APIs with their internal implementations.", - Long: `This command scans the 'pkg' directory for services and ensures that the -top-level public API for each service is in sync with its internal implementation. -It automatically generates the necessary Go files with type aliases.`, + Short: i18n.T("cmd.dev.sync.short"), + Long: i18n.T("cmd.dev.sync.long"), RunE: func(cmd *cobra.Command, args []string) error { if err := runSync(); err != nil { - return fmt.Errorf("Error: %w", err) + return fmt.Errorf("%s %w", i18n.T("cmd.dev.sync.error_prefix"), err) } - fmt.Println("Public APIs synchronized successfully.") + fmt.Println(i18n.T("cmd.dev.sync.success")) return nil }, } diff --git a/cmd/dev/dev_vm.go b/cmd/dev/dev_vm.go index 1f19560..39af6d9 100644 --- a/cmd/dev/dev_vm.go +++ b/cmd/dev/dev_vm.go @@ -7,6 +7,7 @@ import ( "time" "github.com/host-uk/core/pkg/devops" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -28,14 +29,8 @@ func addVMCommands(parent *cobra.Command) { func addVMInstallCommand(parent *cobra.Command) { installCmd := &cobra.Command{ Use: "install", - Short: "Download and install the dev environment image", - Long: `Downloads the platform-specific dev environment image. - -The image includes Go, PHP, Node.js, Python, Docker, and Claude CLI. -Downloads are cached at ~/.core/images/ - -Examples: - core dev install`, + Short: i18n.T("cmd.dev.vm.install.short"), + Long: i18n.T("cmd.dev.vm.install.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMInstall() }, @@ -51,15 +46,15 @@ func runVMInstall() error { } if d.IsInstalled() { - fmt.Println(successStyle.Render("Dev environment already installed")) + fmt.Println(successStyle.Render(i18n.T("cmd.dev.vm.already_installed"))) fmt.Println() - fmt.Printf("Use %s to check for updates\n", dimStyle.Render("core dev update")) + fmt.Println(i18n.T("cmd.dev.vm.check_updates", map[string]interface{}{"Command": dimStyle.Render("core dev update")})) return nil } - fmt.Printf("%s %s\n", dimStyle.Render("Image:"), devops.ImageName()) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.image_label")), devops.ImageName()) fmt.Println() - fmt.Println("Downloading dev environment...") + fmt.Println(i18n.T("cmd.dev.vm.downloading")) fmt.Println() ctx := context.Background() @@ -70,7 +65,7 @@ func runVMInstall() error { if total > 0 { pct := int(float64(downloaded) / float64(total) * 100) if pct != int(float64(lastProgress)/float64(total)*100) { - fmt.Printf("\r%s %d%%", dimStyle.Render("Progress:"), pct) + fmt.Printf("\r%s %d%%", dimStyle.Render(i18n.T("cmd.dev.vm.progress_label")), pct) lastProgress = downloaded } } @@ -84,9 +79,9 @@ func runVMInstall() error { elapsed := time.Since(start).Round(time.Second) fmt.Println() - fmt.Printf("%s in %s\n", successStyle.Render("Installed"), elapsed) + fmt.Println(i18n.T("cmd.dev.vm.installed_in", map[string]interface{}{"Duration": elapsed})) fmt.Println() - fmt.Printf("Start with: %s\n", dimStyle.Render("core dev boot")) + fmt.Println(i18n.T("cmd.dev.vm.start_with", map[string]interface{}{"Command": dimStyle.Render("core dev boot")})) return nil } @@ -102,21 +97,16 @@ var ( func addVMBootCommand(parent *cobra.Command) { bootCmd := &cobra.Command{ Use: "boot", - Short: "Start the dev environment", - Long: `Boots the dev environment VM. - -Examples: - core dev boot - core dev boot --memory 8192 --cpus 4 - core dev boot --fresh`, + Short: i18n.T("cmd.dev.vm.boot.short"), + Long: i18n.T("cmd.dev.vm.boot.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMBoot(vmBootMemory, vmBootCPUs, vmBootFresh) }, } - bootCmd.Flags().IntVar(&vmBootMemory, "memory", 0, "Memory in MB (default: 4096)") - bootCmd.Flags().IntVar(&vmBootCPUs, "cpus", 0, "Number of CPUs (default: 2)") - bootCmd.Flags().BoolVar(&vmBootFresh, "fresh", false, "Stop existing and start fresh") + bootCmd.Flags().IntVar(&vmBootMemory, "memory", 0, i18n.T("cmd.dev.vm.boot.flag.memory")) + bootCmd.Flags().IntVar(&vmBootCPUs, "cpus", 0, i18n.T("cmd.dev.vm.boot.flag.cpus")) + bootCmd.Flags().BoolVar(&vmBootFresh, "fresh", false, i18n.T("cmd.dev.vm.boot.flag.fresh")) parent.AddCommand(bootCmd) } @@ -128,7 +118,7 @@ func runVMBoot(memory, cpus int, fresh bool) error { } if !d.IsInstalled() { - return fmt.Errorf("dev environment not installed (run 'core dev install' first)") + return fmt.Errorf(i18n.T("cmd.dev.vm.not_installed")) } opts := devops.DefaultBootOptions() @@ -140,9 +130,9 @@ func runVMBoot(memory, cpus int, fresh bool) error { } opts.Fresh = fresh - fmt.Printf("%s %dMB, %d CPUs\n", dimStyle.Render("Config:"), opts.Memory, opts.CPUs) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.config_label")), i18n.T("cmd.dev.vm.config_value", map[string]interface{}{"Memory": opts.Memory, "CPUs": opts.CPUs})) fmt.Println() - fmt.Println("Booting dev environment...") + fmt.Println(i18n.T("cmd.dev.vm.booting")) ctx := context.Background() if err := d.Boot(ctx, opts); err != nil { @@ -150,10 +140,10 @@ func runVMBoot(memory, cpus int, fresh bool) error { } fmt.Println() - fmt.Println(successStyle.Render("Dev environment running")) + fmt.Println(successStyle.Render(i18n.T("cmd.dev.vm.running"))) fmt.Println() - fmt.Printf("Connect with: %s\n", dimStyle.Render("core dev shell")) - fmt.Printf("SSH port: %s\n", dimStyle.Render("2222")) + fmt.Println(i18n.T("cmd.dev.vm.connect_with", map[string]interface{}{"Command": dimStyle.Render("core dev shell")})) + fmt.Printf("%s %s\n", i18n.T("cmd.dev.vm.ssh_port"), dimStyle.Render("2222")) return nil } @@ -162,11 +152,8 @@ func runVMBoot(memory, cpus int, fresh bool) error { func addVMStopCommand(parent *cobra.Command) { stopCmd := &cobra.Command{ Use: "stop", - Short: "Stop the dev environment", - Long: `Stops the running dev environment VM. - -Examples: - core dev stop`, + Short: i18n.T("cmd.dev.vm.stop.short"), + Long: i18n.T("cmd.dev.vm.stop.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMStop() }, @@ -188,17 +175,17 @@ func runVMStop() error { } if !running { - fmt.Println(dimStyle.Render("Dev environment is not running")) + fmt.Println(dimStyle.Render(i18n.T("cmd.dev.vm.not_running"))) return nil } - fmt.Println("Stopping dev environment...") + fmt.Println(i18n.T("cmd.dev.vm.stopping")) if err := d.Stop(ctx); err != nil { return err } - fmt.Println(successStyle.Render("Stopped")) + fmt.Println(successStyle.Render(i18n.T("cmd.dev.vm.stopped"))) return nil } @@ -206,11 +193,8 @@ func runVMStop() error { func addVMStatusCommand(parent *cobra.Command) { statusCmd := &cobra.Command{ Use: "vm-status", - Short: "Show dev environment status", - Long: `Shows the current status of the dev environment. - -Examples: - core dev vm-status`, + Short: i18n.T("cmd.dev.vm.status.short"), + Long: i18n.T("cmd.dev.vm.status.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMStatus() }, @@ -231,19 +215,19 @@ func runVMStatus() error { return err } - fmt.Println(headerStyle.Render("Dev Environment Status")) + fmt.Println(headerStyle.Render(i18n.T("cmd.dev.vm.status_title"))) fmt.Println() // Installation status if status.Installed { - fmt.Printf("%s %s\n", dimStyle.Render("Installed:"), successStyle.Render("Yes")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.installed_label")), successStyle.Render(i18n.T("cmd.dev.vm.installed_yes"))) if status.ImageVersion != "" { - fmt.Printf("%s %s\n", dimStyle.Render("Version:"), status.ImageVersion) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.version_label")), status.ImageVersion) } } else { - fmt.Printf("%s %s\n", dimStyle.Render("Installed:"), errorStyle.Render("No")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.installed_label")), errorStyle.Render(i18n.T("cmd.dev.vm.installed_no"))) fmt.Println() - fmt.Printf("Install with: %s\n", dimStyle.Render("core dev install")) + fmt.Println(i18n.T("cmd.dev.vm.install_with", map[string]interface{}{"Command": dimStyle.Render("core dev install")})) return nil } @@ -251,16 +235,16 @@ func runVMStatus() error { // Running status if status.Running { - fmt.Printf("%s %s\n", dimStyle.Render("Status:"), successStyle.Render("Running")) - fmt.Printf("%s %s\n", dimStyle.Render("Container:"), status.ContainerID[:8]) - fmt.Printf("%s %dMB\n", dimStyle.Render("Memory:"), status.Memory) - fmt.Printf("%s %d\n", dimStyle.Render("CPUs:"), status.CPUs) - fmt.Printf("%s %d\n", dimStyle.Render("SSH Port:"), status.SSHPort) - fmt.Printf("%s %s\n", dimStyle.Render("Uptime:"), formatVMUptime(status.Uptime)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.status_label")), successStyle.Render(i18n.T("cmd.dev.vm.status_running"))) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.container_label")), status.ContainerID[:8]) + fmt.Printf("%s %dMB\n", dimStyle.Render(i18n.T("cmd.dev.vm.memory_label")), status.Memory) + fmt.Printf("%s %d\n", dimStyle.Render(i18n.T("cmd.dev.vm.cpus_label")), status.CPUs) + fmt.Printf("%s %d\n", dimStyle.Render(i18n.T("cmd.dev.vm.ssh_port")), status.SSHPort) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.uptime_label")), formatVMUptime(status.Uptime)) } else { - fmt.Printf("%s %s\n", dimStyle.Render("Status:"), dimStyle.Render("Stopped")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.status_label")), dimStyle.Render(i18n.T("cmd.dev.vm.status_stopped"))) fmt.Println() - fmt.Printf("Start with: %s\n", dimStyle.Render("core dev boot")) + fmt.Println(i18n.T("cmd.dev.vm.start_with", map[string]interface{}{"Command": dimStyle.Render("core dev boot")})) } return nil @@ -286,21 +270,14 @@ var vmShellConsole bool func addVMShellCommand(parent *cobra.Command) { shellCmd := &cobra.Command{ Use: "shell [-- command...]", - Short: "Connect to the dev environment", - Long: `Opens an interactive shell in the dev environment. - -Uses SSH by default, or serial console with --console. - -Examples: - core dev shell - core dev shell --console - core dev shell -- ls -la`, + Short: i18n.T("cmd.dev.vm.shell.short"), + Long: i18n.T("cmd.dev.vm.shell.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMShell(vmShellConsole, args) }, } - shellCmd.Flags().BoolVar(&vmShellConsole, "console", false, "Use serial console instead of SSH") + shellCmd.Flags().BoolVar(&vmShellConsole, "console", false, i18n.T("cmd.dev.vm.shell.flag.console")) parent.AddCommand(shellCmd) } @@ -330,22 +307,15 @@ var ( func addVMServeCommand(parent *cobra.Command) { serveCmd := &cobra.Command{ Use: "serve", - Short: "Mount project and start dev server", - Long: `Mounts the current project into the dev environment and starts a dev server. - -Auto-detects the appropriate serve command based on project files. - -Examples: - core dev serve - core dev serve --port 3000 - core dev serve --path public`, + Short: i18n.T("cmd.dev.vm.serve.short"), + Long: i18n.T("cmd.dev.vm.serve.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMServe(vmServePort, vmServePath) }, } - serveCmd.Flags().IntVarP(&vmServePort, "port", "p", 0, "Port to serve on (default: 8000)") - serveCmd.Flags().StringVar(&vmServePath, "path", "", "Subdirectory to serve") + serveCmd.Flags().IntVarP(&vmServePort, "port", "p", 0, i18n.T("cmd.dev.vm.serve.flag.port")) + serveCmd.Flags().StringVar(&vmServePath, "path", "", i18n.T("cmd.dev.vm.serve.flag.path")) parent.AddCommand(serveCmd) } @@ -377,21 +347,14 @@ var vmTestName string func addVMTestCommand(parent *cobra.Command) { testCmd := &cobra.Command{ Use: "test [-- command...]", - Short: "Run tests in the dev environment", - Long: `Runs tests in the dev environment. - -Auto-detects the test command based on project files, or uses .core/test.yaml. - -Examples: - core dev test - core dev test --name integration - core dev test -- go test -v ./...`, + Short: i18n.T("cmd.dev.vm.test.short"), + Long: i18n.T("cmd.dev.vm.test.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMTest(vmTestName, args) }, } - testCmd.Flags().StringVarP(&vmTestName, "name", "n", "", "Run named test command from .core/test.yaml") + testCmd.Flags().StringVarP(&vmTestName, "name", "n", "", i18n.T("cmd.dev.vm.test.flag.name")) parent.AddCommand(testCmd) } @@ -427,31 +390,16 @@ var ( func addVMClaudeCommand(parent *cobra.Command) { claudeCmd := &cobra.Command{ Use: "claude", - Short: "Start sandboxed Claude session", - Long: `Starts a Claude Code session inside the dev environment sandbox. - -Provides isolation while forwarding selected credentials. -Auto-boots the dev environment if not running. - -Auth options (default: all): - gh - GitHub CLI auth - anthropic - Anthropic API key - ssh - SSH agent forwarding - git - Git config (name, email) - -Examples: - core dev claude - core dev claude --model opus - core dev claude --auth gh,anthropic - core dev claude --no-auth`, + Short: i18n.T("cmd.dev.vm.claude.short"), + Long: i18n.T("cmd.dev.vm.claude.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMClaude(vmClaudeNoAuth, vmClaudeModel, vmClaudeAuthFlags) }, } - claudeCmd.Flags().BoolVar(&vmClaudeNoAuth, "no-auth", false, "Don't forward any auth credentials") - claudeCmd.Flags().StringVarP(&vmClaudeModel, "model", "m", "", "Model to use (opus, sonnet)") - claudeCmd.Flags().StringSliceVar(&vmClaudeAuthFlags, "auth", nil, "Selective auth forwarding (gh,anthropic,ssh,git)") + claudeCmd.Flags().BoolVar(&vmClaudeNoAuth, "no-auth", false, i18n.T("cmd.dev.vm.claude.flag.no_auth")) + claudeCmd.Flags().StringVarP(&vmClaudeModel, "model", "m", "", i18n.T("cmd.dev.vm.claude.flag.model")) + claudeCmd.Flags().StringSliceVar(&vmClaudeAuthFlags, "auth", nil, i18n.T("cmd.dev.vm.claude.flag.auth")) parent.AddCommand(claudeCmd) } @@ -484,18 +432,14 @@ var vmUpdateApply bool func addVMUpdateCommand(parent *cobra.Command) { updateCmd := &cobra.Command{ Use: "update", - Short: "Check for and apply updates", - Long: `Checks for dev environment updates and optionally applies them. - -Examples: - core dev update - core dev update --apply`, + Short: i18n.T("cmd.dev.vm.update.short"), + Long: i18n.T("cmd.dev.vm.update.long"), RunE: func(cmd *cobra.Command, args []string) error { return runVMUpdate(vmUpdateApply) }, } - updateCmd.Flags().BoolVar(&vmUpdateApply, "apply", false, "Download and apply the update") + updateCmd.Flags().BoolVar(&vmUpdateApply, "apply", false, i18n.T("cmd.dev.vm.update.flag.apply")) parent.AddCommand(updateCmd) } @@ -508,7 +452,7 @@ func runVMUpdate(apply bool) error { ctx := context.Background() - fmt.Println("Checking for updates...") + fmt.Println(i18n.T("cmd.dev.vm.checking_updates")) fmt.Println() current, latest, hasUpdate, err := d.CheckUpdate(ctx) @@ -516,38 +460,38 @@ func runVMUpdate(apply bool) error { return fmt.Errorf("failed to check for updates: %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Current:"), valueStyle.Render(current)) - fmt.Printf("%s %s\n", dimStyle.Render("Latest:"), valueStyle.Render(latest)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.current_label")), valueStyle.Render(current)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.latest_label")), valueStyle.Render(latest)) fmt.Println() if !hasUpdate { - fmt.Println(successStyle.Render("Already up to date")) + fmt.Println(successStyle.Render(i18n.T("cmd.dev.vm.up_to_date"))) return nil } - fmt.Println(warningStyle.Render("Update available")) + fmt.Println(warningStyle.Render(i18n.T("cmd.dev.vm.update_available"))) fmt.Println() if !apply { - fmt.Printf("Run %s to update\n", dimStyle.Render("core dev update --apply")) + fmt.Println(i18n.T("cmd.dev.vm.run_to_update", map[string]interface{}{"Command": dimStyle.Render("core dev update --apply")})) return nil } // Stop if running running, _ := d.IsRunning(ctx) if running { - fmt.Println("Stopping current instance...") + fmt.Println(i18n.T("cmd.dev.vm.stopping_current")) _ = d.Stop(ctx) } - fmt.Println("Downloading update...") + fmt.Println(i18n.T("cmd.dev.vm.downloading_update")) fmt.Println() start := time.Now() err = d.Install(ctx, func(downloaded, total int64) { if total > 0 { pct := int(float64(downloaded) / float64(total) * 100) - fmt.Printf("\r%s %d%%", dimStyle.Render("Progress:"), pct) + fmt.Printf("\r%s %d%%", dimStyle.Render(i18n.T("cmd.dev.vm.progress_label")), pct) } }) @@ -559,7 +503,7 @@ func runVMUpdate(apply bool) error { elapsed := time.Since(start).Round(time.Second) fmt.Println() - fmt.Printf("%s in %s\n", successStyle.Render("Updated"), elapsed) + fmt.Println(i18n.T("cmd.dev.vm.updated_in", map[string]interface{}{"Duration": elapsed})) return nil } diff --git a/cmd/dev/dev_work.go b/cmd/dev/dev_work.go index e1ceef7..cca9cca 100644 --- a/cmd/dev/dev_work.go +++ b/cmd/dev/dev_work.go @@ -11,6 +11,7 @@ import ( "github.com/host-uk/core/cmd/shared" "github.com/host-uk/core/pkg/git" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -26,19 +27,16 @@ var ( func addWorkCommand(parent *cobra.Command) { workCmd := &cobra.Command{ Use: "work", - Short: "Multi-repo git operations", - Long: `Manage git status, commits, and pushes across multiple repositories. - -Reads repos.yaml to discover repositories and their relationships. -Shows status, optionally commits with Claude, and pushes changes.`, + Short: i18n.T("cmd.dev.work.short"), + Long: i18n.T("cmd.dev.work.long"), RunE: func(cmd *cobra.Command, args []string) error { return runWork(workRegistryPath, workStatusOnly, workAutoCommit) }, } - workCmd.Flags().BoolVar(&workStatusOnly, "status", false, "Show status only, don't push") - workCmd.Flags().BoolVar(&workAutoCommit, "commit", false, "Use Claude to commit dirty repos before pushing") - workCmd.Flags().StringVar(&workRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") + workCmd.Flags().BoolVar(&workStatusOnly, "status", false, i18n.T("cmd.dev.work.flag.status")) + workCmd.Flags().BoolVar(&workAutoCommit, "commit", false, i18n.T("cmd.dev.work.flag.commit")) + workCmd.Flags().StringVar(&workRegistryPath, "registry", "", i18n.T("cmd.dev.work.flag.registry")) parent.AddCommand(workCmd) } @@ -55,7 +53,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { if err != nil { return fmt.Errorf("failed to load registry: %w", err) } - fmt.Printf("%s %s\n\n", dimStyle.Render("Registry:"), registryPath) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.dev.registry_label")), registryPath) } else { registryPath, err = repos.FindRegistry() if err == nil { @@ -63,7 +61,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { if err != nil { return fmt.Errorf("failed to load registry: %w", err) } - fmt.Printf("%s %s\n\n", dimStyle.Render("Registry:"), registryPath) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.dev.registry_label")), registryPath) } else { // Fallback: scan current directory cwd, _ := os.Getwd() @@ -71,7 +69,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { if err != nil { return fmt.Errorf("failed to scan directory: %w", err) } - fmt.Printf("%s %s\n\n", dimStyle.Render("Scanning:"), cwd) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd) } } @@ -87,7 +85,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { } if len(paths) == 0 { - fmt.Println("No git repositories found.") + fmt.Println(i18n.T("cmd.dev.no_git_repos")) return nil } @@ -124,7 +122,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { // Auto-commit dirty repos if requested if autoCommit && len(dirtyRepos) > 0 { fmt.Println() - fmt.Printf("%s\n", shared.TitleStyle.Render("Committing dirty repos with Claude...")) + fmt.Printf("%s\n", shared.TitleStyle.Render(i18n.T("cmd.dev.commit.committing"))) fmt.Println() for _, s := range dirtyRepos { @@ -154,7 +152,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { if statusOnly { if len(dirtyRepos) > 0 && !autoCommit { fmt.Println() - fmt.Printf("%s\n", dimStyle.Render("Use --commit to have Claude create commits")) + fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.dev.work.use_commit_flag"))) } return nil } @@ -162,19 +160,19 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error { // Push repos with unpushed commits if len(aheadRepos) == 0 { fmt.Println() - fmt.Println("All repos up to date.") + fmt.Println(i18n.T("cmd.dev.work.all_up_to_date")) return nil } fmt.Println() - fmt.Printf("%d repo(s) with unpushed commits:\n", len(aheadRepos)) + fmt.Printf("%s\n", i18n.T("cmd.dev.work.repos_with_unpushed", map[string]interface{}{"Count": len(aheadRepos)})) for _, s := range aheadRepos { - fmt.Printf(" %s: %d commit(s)\n", s.Name, s.Ahead) + fmt.Printf(" %s: %s\n", s.Name, i18n.T("cmd.dev.work.commits_count", map[string]interface{}{"Count": s.Ahead})) } fmt.Println() - if !shared.Confirm("Push all?") { - fmt.Println("Aborted.") + if !shared.Confirm(i18n.T("cmd.dev.push.confirm")) { + fmt.Println(i18n.T("cli.aborted")) return nil } @@ -211,11 +209,11 @@ func printStatusTable(statuses []git.RepoStatus) { // Print header with fixed-width formatting fmt.Printf("%-*s %8s %9s %6s %5s\n", nameWidth, - shared.TitleStyle.Render("Repo"), - shared.TitleStyle.Render("Modified"), - shared.TitleStyle.Render("Untracked"), - shared.TitleStyle.Render("Staged"), - shared.TitleStyle.Render("Ahead"), + shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_repo")), + shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_modified")), + shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_untracked")), + shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_staged")), + shared.TitleStyle.Render(i18n.T("cmd.dev.work.table_ahead")), ) // Print separator @@ -227,7 +225,7 @@ func printStatusTable(statuses []git.RepoStatus) { paddedName := fmt.Sprintf("%-*s", nameWidth, s.Name) fmt.Printf("%s %s\n", repoNameStyle.Render(paddedName), - errorStyle.Render("error: "+s.Error.Error()), + errorStyle.Render(i18n.T("cmd.dev.work.error_prefix")+" "+s.Error.Error()), ) continue } diff --git a/cmd/docs/docs.go b/cmd/docs/docs.go index 9e12f57..851d4c0 100644 --- a/cmd/docs/docs.go +++ b/cmd/docs/docs.go @@ -3,6 +3,7 @@ package docs import ( "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -21,9 +22,8 @@ var ( var docsCmd = &cobra.Command{ Use: "docs", - Short: "Documentation management", - Long: `Manage documentation across all repos. -Scan for docs, check coverage, and sync to core-php/docs/packages/.`, + Short: i18n.T("cmd.docs.short"), + Long: i18n.T("cmd.docs.long"), } func init() { diff --git a/cmd/docs/list.go b/cmd/docs/list.go index c0bfd2b..935054a 100644 --- a/cmd/docs/list.go +++ b/cmd/docs/list.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -13,14 +14,15 @@ var docsListRegistryPath string var docsListCmd = &cobra.Command{ Use: "list", - Short: "List documentation across repos", + Short: i18n.T("cmd.docs.list.short"), + Long: i18n.T("cmd.docs.list.long"), RunE: func(cmd *cobra.Command, args []string) error { return runDocsList(docsListRegistryPath) }, } func init() { - docsListCmd.Flags().StringVar(&docsListRegistryPath, "registry", "", "Path to repos.yaml") + docsListCmd.Flags().StringVar(&docsListRegistryPath, "registry", "", i18n.T("cmd.docs.list.flag.registry")) } func runDocsList(registryPath string) error { @@ -30,11 +32,11 @@ func runDocsList(registryPath string) error { } fmt.Printf("\n%-20s %-8s %-8s %-10s %s\n", - headerStyle.Render("Repo"), - headerStyle.Render("README"), - headerStyle.Render("CLAUDE"), - headerStyle.Render("CHANGELOG"), - headerStyle.Render("docs/"), + headerStyle.Render(i18n.T("cmd.docs.list.header.repo")), + headerStyle.Render(i18n.T("cmd.docs.list.header.readme")), + headerStyle.Render(i18n.T("cmd.docs.list.header.claude")), + headerStyle.Render(i18n.T("cmd.docs.list.header.changelog")), + headerStyle.Render(i18n.T("cmd.docs.list.header.docs")), ) fmt.Println(strings.Repeat("─", 70)) @@ -48,7 +50,7 @@ func runDocsList(registryPath string) error { docsDir := shared.CheckMark(false) if len(info.DocsFiles) > 0 { - docsDir = docsFoundStyle.Render(fmt.Sprintf("%d files", len(info.DocsFiles))) + docsDir = docsFoundStyle.Render(i18n.T("cmd.docs.list.files_count", map[string]interface{}{"Count": len(info.DocsFiles)})) } fmt.Printf("%-20s %-8s %-8s %-10s %s\n", @@ -67,10 +69,9 @@ func runDocsList(registryPath string) error { } fmt.Println() - fmt.Printf("%s %d with docs, %d without\n", - shared.Label("Coverage"), - withDocs, - withoutDocs, + fmt.Printf("%s %s\n", + shared.Label(i18n.T("cmd.docs.list.coverage_label")), + i18n.T("cmd.docs.list.coverage_summary", map[string]interface{}{"WithDocs": withDocs, "WithoutDocs": withoutDocs}), ) return nil diff --git a/cmd/docs/scan.go b/cmd/docs/scan.go index e8d089b..5665ec7 100644 --- a/cmd/docs/scan.go +++ b/cmd/docs/scan.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" ) @@ -29,7 +30,7 @@ func loadRegistry(registryPath string) (*repos.Registry, string, error) { if registryPath != "" { reg, err = repos.LoadRegistry(registryPath) if err != nil { - return nil, "", fmt.Errorf("failed to load registry: %w", err) + return nil, "", fmt.Errorf("%s: %w", i18n.T("cmd.docs.error.load_registry"), err) } basePath = filepath.Dir(registryPath) } else { @@ -37,14 +38,14 @@ func loadRegistry(registryPath string) (*repos.Registry, string, error) { if err == nil { reg, err = repos.LoadRegistry(registryPath) if err != nil { - return nil, "", fmt.Errorf("failed to load registry: %w", err) + return nil, "", fmt.Errorf("%s: %w", i18n.T("cmd.docs.error.load_registry"), err) } basePath = filepath.Dir(registryPath) } else { cwd, _ := os.Getwd() reg, err = repos.ScanDirectory(cwd) if err != nil { - return nil, "", fmt.Errorf("failed to scan directory: %w", err) + return nil, "", fmt.Errorf("%s: %w", i18n.T("cmd.docs.error.scan_directory"), err) } basePath = cwd } diff --git a/cmd/docs/sync.go b/cmd/docs/sync.go index 66db052..d3f44c6 100644 --- a/cmd/docs/sync.go +++ b/cmd/docs/sync.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -18,16 +19,17 @@ var ( var docsSyncCmd = &cobra.Command{ Use: "sync", - Short: "Sync documentation to core-php/docs/packages/", + Short: i18n.T("cmd.docs.sync.short"), + Long: i18n.T("cmd.docs.sync.long"), RunE: func(cmd *cobra.Command, args []string) error { return runDocsSync(docsSyncRegistryPath, docsSyncOutputDir, docsSyncDryRun) }, } func init() { - docsSyncCmd.Flags().StringVar(&docsSyncRegistryPath, "registry", "", "Path to repos.yaml") - docsSyncCmd.Flags().BoolVar(&docsSyncDryRun, "dry-run", false, "Show what would be synced without copying") - docsSyncCmd.Flags().StringVar(&docsSyncOutputDir, "output", "", "Output directory (default: core-php/docs/packages)") + docsSyncCmd.Flags().StringVar(&docsSyncRegistryPath, "registry", "", i18n.T("cmd.docs.sync.flag.registry")) + docsSyncCmd.Flags().BoolVar(&docsSyncDryRun, "dry-run", false, i18n.T("cmd.docs.sync.flag.dry_run")) + docsSyncCmd.Flags().StringVar(&docsSyncOutputDir, "output", "", i18n.T("cmd.docs.sync.flag.output")) } // packageOutputName maps repo name to output folder name @@ -81,11 +83,11 @@ func runDocsSync(registryPath string, outputDir string, dryRun bool) error { } if len(docsInfo) == 0 { - fmt.Println("No documentation found in any repos.") + fmt.Println(i18n.T("cmd.docs.sync.no_docs_found")) return nil } - fmt.Printf("\n%s %d repo(s) with docs/ directories\n\n", dimStyle.Render("Found"), len(docsInfo)) + fmt.Printf("\n%s %s\n\n", dimStyle.Render(i18n.T("cmd.docs.sync.found_label")), i18n.T("cmd.docs.sync.repos_with_docs", map[string]interface{}{"Count": len(docsInfo)})) // Show what will be synced var totalFiles int @@ -95,25 +97,26 @@ func runDocsSync(registryPath string, outputDir string, dryRun bool) error { fmt.Printf(" %s → %s %s\n", repoNameStyle.Render(info.Name), docsFileStyle.Render("packages/"+outName+"/"), - dimStyle.Render(fmt.Sprintf("(%d files)", len(info.DocsFiles)))) + dimStyle.Render(i18n.T("cmd.docs.sync.files_count", map[string]interface{}{"Count": len(info.DocsFiles)}))) for _, f := range info.DocsFiles { fmt.Printf(" %s\n", dimStyle.Render(f)) } } - fmt.Printf("\n%s %d files from %d repos → %s\n", - dimStyle.Render("Total:"), totalFiles, len(docsInfo), outputDir) + fmt.Printf("\n%s %s\n", + dimStyle.Render(i18n.T("cmd.docs.sync.total_label")), + i18n.T("cmd.docs.sync.total_summary", map[string]interface{}{"Files": totalFiles, "Repos": len(docsInfo), "Output": outputDir})) if dryRun { - fmt.Printf("\n%s\n", dimStyle.Render("Dry run - no files copied")) + fmt.Printf("\n%s\n", dimStyle.Render(i18n.T("cmd.docs.sync.dry_run_notice"))) return nil } // Confirm fmt.Println() - if !confirm("Sync?") { - fmt.Println("Aborted.") + if !confirm(i18n.T("cmd.docs.sync.confirm")) { + fmt.Println(i18n.T("cli.confirm.abort")) return nil } @@ -147,7 +150,7 @@ func runDocsSync(registryPath string, outputDir string, dryRun bool) error { synced++ } - fmt.Printf("\n%s Synced %d packages\n", successStyle.Render("Done:"), synced) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.docs.sync.done_label")), i18n.T("cmd.docs.sync.synced_packages", map[string]interface{}{"Count": synced})) return nil } diff --git a/cmd/doctor/checks.go b/cmd/doctor/checks.go index 6b15530..fee8dbb 100644 --- a/cmd/doctor/checks.go +++ b/cmd/doctor/checks.go @@ -3,6 +3,8 @@ package doctor import ( "os/exec" "strings" + + "github.com/host-uk/core/pkg/i18n" ) // check represents a tool check configuration @@ -14,68 +16,72 @@ type check struct { versionFlag string } -// requiredChecks are tools that must be installed -var requiredChecks = []check{ - { - name: "Git", - description: "Version control", - command: "git", - args: []string{"--version"}, - versionFlag: "--version", - }, - { - name: "GitHub CLI", - description: "GitHub integration (issues, PRs, CI)", - command: "gh", - args: []string{"--version"}, - versionFlag: "--version", - }, - { - name: "PHP", - description: "Laravel packages", - command: "php", - args: []string{"-v"}, - versionFlag: "-v", - }, - { - name: "Composer", - description: "PHP dependencies", - command: "composer", - args: []string{"--version"}, - versionFlag: "--version", - }, - { - name: "Node.js", - description: "Frontend builds", - command: "node", - args: []string{"--version"}, - versionFlag: "--version", - }, +// requiredChecks returns tools that must be installed +func requiredChecks() []check { + return []check{ + { + name: i18n.T("cmd.doctor.check.git.name"), + description: i18n.T("cmd.doctor.check.git.description"), + command: "git", + args: []string{"--version"}, + versionFlag: "--version", + }, + { + name: i18n.T("cmd.doctor.check.gh.name"), + description: i18n.T("cmd.doctor.check.gh.description"), + command: "gh", + args: []string{"--version"}, + versionFlag: "--version", + }, + { + name: i18n.T("cmd.doctor.check.php.name"), + description: i18n.T("cmd.doctor.check.php.description"), + command: "php", + args: []string{"-v"}, + versionFlag: "-v", + }, + { + name: i18n.T("cmd.doctor.check.composer.name"), + description: i18n.T("cmd.doctor.check.composer.description"), + command: "composer", + args: []string{"--version"}, + versionFlag: "--version", + }, + { + name: i18n.T("cmd.doctor.check.node.name"), + description: i18n.T("cmd.doctor.check.node.description"), + command: "node", + args: []string{"--version"}, + versionFlag: "--version", + }, + } } -// optionalChecks are tools that are nice to have -var optionalChecks = []check{ - { - name: "pnpm", - description: "Fast package manager", - command: "pnpm", - args: []string{"--version"}, - versionFlag: "--version", - }, - { - name: "Claude Code", - description: "AI-assisted development", - command: "claude", - args: []string{"--version"}, - versionFlag: "--version", - }, - { - name: "Docker", - description: "Container runtime", - command: "docker", - args: []string{"--version"}, - versionFlag: "--version", - }, +// optionalChecks returns tools that are nice to have +func optionalChecks() []check { + return []check{ + { + name: i18n.T("cmd.doctor.check.pnpm.name"), + description: i18n.T("cmd.doctor.check.pnpm.description"), + command: "pnpm", + args: []string{"--version"}, + versionFlag: "--version", + }, + { + name: i18n.T("cmd.doctor.check.claude.name"), + description: i18n.T("cmd.doctor.check.claude.description"), + command: "claude", + args: []string{"--version"}, + versionFlag: "--version", + }, + { + name: i18n.T("cmd.doctor.check.docker.name"), + description: i18n.T("cmd.doctor.check.docker.description"), + command: "docker", + args: []string{"--version"}, + versionFlag: "--version", + }, + } } // runCheck executes a tool check and returns success status and version info diff --git a/cmd/doctor/doctor.go b/cmd/doctor/doctor.go index 89a06ef..4dc96eb 100644 --- a/cmd/doctor/doctor.go +++ b/cmd/doctor/doctor.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -20,27 +21,26 @@ var doctorVerbose bool var doctorCmd = &cobra.Command{ Use: "doctor", - Short: "Check development environment", - Long: `Checks that all required tools are installed and configured. -Run this before 'core setup' to ensure your environment is ready.`, + Short: i18n.T("cmd.doctor.short"), + Long: i18n.T("cmd.doctor.long"), RunE: func(cmd *cobra.Command, args []string) error { return runDoctor(doctorVerbose) }, } func init() { - doctorCmd.Flags().BoolVar(&doctorVerbose, "verbose", false, "Show detailed version information") + doctorCmd.Flags().BoolVar(&doctorVerbose, "verbose", false, i18n.T("cmd.doctor.verbose_flag")) } func runDoctor(verbose bool) error { - fmt.Println("Checking development environment...") + fmt.Println(i18n.T("cmd.doctor.checking")) fmt.Println() var passed, failed, optional int // Check required tools - fmt.Println("Required:") - for _, c := range requiredChecks { + fmt.Println(i18n.T("cmd.doctor.required")) + for _, c := range requiredChecks() { ok, version := runCheck(c) if ok { if verbose { @@ -56,8 +56,8 @@ func runDoctor(verbose bool) error { } // Check optional tools - fmt.Println("\nOptional:") - for _, c := range optionalChecks { + fmt.Printf("\n%s\n", i18n.T("cmd.doctor.optional")) + for _, c := range optionalChecks() { ok, version := runCheck(c) if ok { if verbose { @@ -73,34 +73,34 @@ func runDoctor(verbose bool) error { } // Check GitHub access - fmt.Println("\nGitHub Access:") + fmt.Printf("\n%s\n", i18n.T("cmd.doctor.github")) if checkGitHubSSH() { - fmt.Println(shared.CheckResult(true, "SSH key found", "")) + fmt.Println(shared.CheckResult(true, i18n.T("cmd.doctor.ssh_found"), "")) } else { - fmt.Printf(" %s SSH key missing - run: ssh-keygen && gh ssh-key add\n", errorStyle.Render(shared.SymbolCross)) + fmt.Printf(" %s %s\n", errorStyle.Render(shared.SymbolCross), i18n.T("cmd.doctor.ssh_missing")) failed++ } if checkGitHubCLI() { - fmt.Println(shared.CheckResult(true, "CLI authenticated", "")) + fmt.Println(shared.CheckResult(true, i18n.T("cmd.doctor.cli_auth"), "")) } else { - fmt.Printf(" %s CLI authentication - run: gh auth login\n", errorStyle.Render(shared.SymbolCross)) + fmt.Printf(" %s %s\n", errorStyle.Render(shared.SymbolCross), i18n.T("cmd.doctor.cli_auth_missing")) failed++ } // Check workspace - fmt.Println("\nWorkspace:") + fmt.Printf("\n%s\n", i18n.T("cmd.doctor.workspace")) checkWorkspace() // Summary fmt.Println() if failed > 0 { - fmt.Println(shared.Error(fmt.Sprintf("Doctor: %d issues found", failed))) - fmt.Println("\nInstall missing tools:") + fmt.Println(shared.Error(i18n.T("cmd.doctor.issues", map[string]interface{}{"Count": failed}))) + fmt.Printf("\n%s\n", i18n.T("cmd.doctor.install_missing")) printInstallInstructions() - return fmt.Errorf("%d required tools missing", failed) + return fmt.Errorf("%s", i18n.T("cmd.doctor.issues_error", map[string]interface{}{"Count": failed})) } - fmt.Println(shared.Success("Doctor: Environment ready")) + fmt.Println(shared.Success(i18n.T("cmd.doctor.ready"))) return nil } diff --git a/cmd/doctor/environment.go b/cmd/doctor/environment.go index ed14468..2e8ea28 100644 --- a/cmd/doctor/environment.go +++ b/cmd/doctor/environment.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" ) @@ -44,7 +45,7 @@ func checkGitHubCLI() bool { func checkWorkspace() { registryPath, err := repos.FindRegistry() if err == nil { - fmt.Printf(" %s Found repos.yaml at %s\n", successStyle.Render("✓"), registryPath) + fmt.Printf(" %s %s\n", successStyle.Render("✓"), i18n.T("cmd.doctor.repos_yaml_found", map[string]interface{}{"Path": registryPath})) reg, err := repos.LoadRegistry(registryPath) if err == nil { @@ -69,9 +70,9 @@ func checkWorkspace() { cloned++ } } - fmt.Printf(" %s %d/%d repos cloned\n", successStyle.Render("✓"), cloned, len(allRepos)) + fmt.Printf(" %s %s\n", successStyle.Render("✓"), i18n.T("cmd.doctor.repos_cloned", map[string]interface{}{"Cloned": cloned, "Total": len(allRepos)})) } } else { - fmt.Printf(" %s No repos.yaml found (run from workspace directory)\n", dimStyle.Render("○")) + fmt.Printf(" %s %s\n", dimStyle.Render("○"), i18n.T("cmd.doctor.no_repos_yaml")) } } diff --git a/cmd/doctor/install.go b/cmd/doctor/install.go index 3477d0f..ade4c50 100644 --- a/cmd/doctor/install.go +++ b/cmd/doctor/install.go @@ -3,22 +3,24 @@ package doctor import ( "fmt" "runtime" + + "github.com/host-uk/core/pkg/i18n" ) // printInstallInstructions prints OS-specific installation instructions func printInstallInstructions() { switch runtime.GOOS { case "darwin": - fmt.Println(" brew install git gh php composer node pnpm docker") - fmt.Println(" brew install --cask claude") + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_macos")) + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_macos_cask")) case "linux": - fmt.Println(" # Install via your package manager or:") - fmt.Println(" # Git: apt install git") - fmt.Println(" # GitHub CLI: https://cli.github.com/") - fmt.Println(" # PHP: apt install php8.3-cli") - fmt.Println(" # Node: https://nodejs.org/") - fmt.Println(" # pnpm: npm install -g pnpm") + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_linux_header")) + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_linux_git")) + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_linux_gh")) + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_linux_php")) + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_linux_node")) + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_linux_pnpm")) default: - fmt.Println(" See documentation for your OS") + fmt.Printf(" %s\n", i18n.T("cmd.doctor.install_other")) } } diff --git a/cmd/go/go.go b/cmd/go/go.go index 3ad1a9a..6c68312 100644 --- a/cmd/go/go.go +++ b/cmd/go/go.go @@ -5,6 +5,7 @@ package gocmd import ( "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -19,16 +20,8 @@ var ( func AddGoCommands(root *cobra.Command) { goCmd := &cobra.Command{ Use: "go", - Short: "Go development tools", - Long: "Go development tools with enhanced output and environment setup.\n\n" + - "Commands:\n" + - " test Run tests\n" + - " cov Run tests with coverage report\n" + - " fmt Format Go code\n" + - " lint Run golangci-lint\n" + - " install Install Go binary\n" + - " mod Module management (tidy, download, verify)\n" + - " work Workspace management", + Short: i18n.T("cmd.go.short"), + Long: i18n.T("cmd.go.long"), } root.AddCommand(goCmd) diff --git a/cmd/go/go_format.go b/cmd/go/go_format.go index 235198d..52ff5a9 100644 --- a/cmd/go/go_format.go +++ b/cmd/go/go_format.go @@ -4,6 +4,7 @@ import ( "os" "os/exec" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -16,12 +17,8 @@ var ( func addGoFmtCommand(parent *cobra.Command) { fmtCmd := &cobra.Command{ Use: "fmt", - Short: "Format Go code", - Long: "Format Go code using gofmt or goimports.\n\n" + - "Examples:\n" + - " core go fmt # Check formatting\n" + - " core go fmt --fix # Fix formatting\n" + - " core go fmt --diff # Show diff", + Short: i18n.T("cmd.go.fmt.short"), + Long: i18n.T("cmd.go.fmt.long"), RunE: func(cmd *cobra.Command, args []string) error { fmtArgs := []string{} if fmtFix { @@ -49,9 +46,9 @@ func addGoFmtCommand(parent *cobra.Command) { }, } - fmtCmd.Flags().BoolVar(&fmtFix, "fix", false, "Fix formatting in place") - fmtCmd.Flags().BoolVar(&fmtDiff, "diff", false, "Show diff of changes") - fmtCmd.Flags().BoolVar(&fmtCheck, "check", false, "Check only, exit 1 if not formatted") + fmtCmd.Flags().BoolVar(&fmtFix, "fix", false, i18n.T("cmd.go.fmt.flag.fix")) + fmtCmd.Flags().BoolVar(&fmtDiff, "diff", false, i18n.T("cmd.go.fmt.flag.diff")) + fmtCmd.Flags().BoolVar(&fmtCheck, "check", false, i18n.T("cmd.go.fmt.flag.check")) parent.AddCommand(fmtCmd) } @@ -61,11 +58,8 @@ var lintFix bool func addGoLintCommand(parent *cobra.Command) { lintCmd := &cobra.Command{ Use: "lint", - Short: "Run golangci-lint", - Long: "Run golangci-lint on the codebase.\n\n" + - "Examples:\n" + - " core go lint\n" + - " core go lint --fix", + Short: i18n.T("cmd.go.lint.short"), + Long: i18n.T("cmd.go.lint.long"), RunE: func(cmd *cobra.Command, args []string) error { lintArgs := []string{"run"} if lintFix { @@ -79,7 +73,7 @@ func addGoLintCommand(parent *cobra.Command) { }, } - lintCmd.Flags().BoolVar(&lintFix, "fix", false, "Fix issues automatically") + lintCmd.Flags().BoolVar(&lintFix, "fix", false, i18n.T("cmd.go.lint.flag.fix")) parent.AddCommand(lintCmd) } diff --git a/cmd/go/go_test_cmd.go b/cmd/go/go_test_cmd.go index 8fbefba..8d89b99 100644 --- a/cmd/go/go_test_cmd.go +++ b/cmd/go/go_test_cmd.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -25,27 +26,20 @@ var ( func addGoTestCommand(parent *cobra.Command) { testCmd := &cobra.Command{ Use: "test", - Short: "Run tests with coverage", - Long: "Run Go tests with coverage reporting.\n\n" + - "Sets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings.\n" + - "Filters noisy output and provides colour-coded coverage.\n\n" + - "Examples:\n" + - " core go test\n" + - " core go test --coverage\n" + - " core go test --pkg ./pkg/crypt\n" + - " core go test --run TestHash", + Short: i18n.T("cmd.go.test.short"), + Long: i18n.T("cmd.go.test.long"), RunE: func(cmd *cobra.Command, args []string) error { return runGoTest(testCoverage, testPkg, testRun, testShort, testRace, testJSON, testVerbose) }, } - testCmd.Flags().BoolVar(&testCoverage, "coverage", false, "Show detailed per-package coverage") - testCmd.Flags().StringVar(&testPkg, "pkg", "", "Package to test (default: ./...)") - testCmd.Flags().StringVar(&testRun, "run", "", "Run only tests matching regexp") - testCmd.Flags().BoolVar(&testShort, "short", false, "Run only short tests") - testCmd.Flags().BoolVar(&testRace, "race", false, "Enable race detector") - testCmd.Flags().BoolVar(&testJSON, "json", false, "Output JSON results") - testCmd.Flags().BoolVarP(&testVerbose, "verbose", "v", false, "Verbose output") + testCmd.Flags().BoolVar(&testCoverage, "coverage", false, i18n.T("cmd.go.test.flag.coverage")) + testCmd.Flags().StringVar(&testPkg, "pkg", "", i18n.T("cmd.go.test.flag.pkg")) + testCmd.Flags().StringVar(&testRun, "run", "", i18n.T("cmd.go.test.flag.run")) + testCmd.Flags().BoolVar(&testShort, "short", false, i18n.T("cmd.go.test.flag.short")) + testCmd.Flags().BoolVar(&testRace, "race", false, i18n.T("cmd.go.test.flag.race")) + testCmd.Flags().BoolVar(&testJSON, "json", false, i18n.T("cmd.go.test.flag.json")) + testCmd.Flags().BoolVarP(&testVerbose, "verbose", "v", false, i18n.T("cmd.go.test.flag.verbose")) parent.AddCommand(testCmd) } @@ -79,8 +73,8 @@ func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose boo args = append(args, pkg) if !jsonOut { - fmt.Printf("%s Running tests\n", dimStyle.Render("Test:")) - fmt.Printf(" %s %s\n", dimStyle.Render("Package:"), pkg) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.go.test.label")), i18n.T("cmd.go.test.running")) + fmt.Printf(" %s %s\n", dimStyle.Render(i18n.T("cmd.go.test.package_label")), pkg) fmt.Println() } @@ -119,19 +113,19 @@ func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose boo // Summary if err == nil { - fmt.Printf(" %s %d passed\n", successStyle.Render("✓"), passed) + fmt.Printf(" %s %s\n", successStyle.Render("✓"), i18n.T("cmd.go.test.passed", map[string]interface{}{"Count": passed})) } else { - fmt.Printf(" %s %d passed, %d failed\n", errorStyle.Render("✗"), passed, failed) + fmt.Printf(" %s %s\n", errorStyle.Render("✗"), i18n.T("cmd.go.test.passed_failed", map[string]interface{}{"Passed": passed, "Failed": failed})) } if cov > 0 { - fmt.Printf("\n %s %s\n", shared.ProgressLabel("Coverage"), shared.FormatCoverage(cov)) + fmt.Printf("\n %s %s\n", shared.ProgressLabel(i18n.T("cmd.go.test.coverage")), shared.FormatCoverage(cov)) } if err == nil { - fmt.Printf("\n%s\n", successStyle.Render("PASS All tests passed")) + fmt.Printf("\n%s\n", successStyle.Render(i18n.T("cmd.go.test.all_passed"))) } else { - fmt.Printf("\n%s\n", errorStyle.Render("FAIL Some tests failed")) + fmt.Printf("\n%s\n", errorStyle.Render(i18n.T("cmd.go.test.some_failed"))) } return err @@ -174,23 +168,18 @@ var ( func addGoCovCommand(parent *cobra.Command) { covCmd := &cobra.Command{ Use: "cov", - Short: "Run tests with coverage report", - Long: "Run tests and generate coverage report.\n\n" + - "Examples:\n" + - " core go cov # Run with coverage summary\n" + - " core go cov --html # Generate HTML report\n" + - " core go cov --open # Generate and open HTML report\n" + - " core go cov --threshold 80 # Fail if coverage < 80%", + Short: i18n.T("cmd.go.cov.short"), + Long: i18n.T("cmd.go.cov.long"), RunE: func(cmd *cobra.Command, args []string) error { pkg := covPkg if pkg == "" { // Auto-discover packages with tests pkgs, err := findTestPackages(".") if err != nil { - return fmt.Errorf("failed to discover test packages: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.go.cov.error.discover"), err) } if len(pkgs) == 0 { - return fmt.Errorf("no test packages found") + return fmt.Errorf(i18n.T("cmd.go.cov.error.no_packages")) } pkg = strings.Join(pkgs, " ") } @@ -198,19 +187,19 @@ func addGoCovCommand(parent *cobra.Command) { // Create temp file for coverage data covFile, err := os.CreateTemp("", "coverage-*.out") if err != nil { - return fmt.Errorf("failed to create coverage file: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.go.cov.error.create_file"), err) } covPath := covFile.Name() covFile.Close() defer os.Remove(covPath) - fmt.Printf("%s Running tests with coverage\n", dimStyle.Render("Coverage:")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.go.cov.label")), i18n.T("cmd.go.cov.running")) // Truncate package list if too long for display displayPkg := pkg if len(displayPkg) > 60 { displayPkg = displayPkg[:57] + "..." } - fmt.Printf(" %s %s\n", dimStyle.Render("Package:"), displayPkg) + fmt.Printf(" %s %s\n", dimStyle.Render(i18n.T("cmd.go.test.package_label")), displayPkg) fmt.Println() // Run tests with coverage @@ -232,7 +221,7 @@ func addGoCovCommand(parent *cobra.Command) { if testErr != nil { return testErr } - return fmt.Errorf("failed to get coverage: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.go.cov.error.get_coverage"), err) } // Parse total coverage from last line @@ -252,16 +241,16 @@ func addGoCovCommand(parent *cobra.Command) { // Print coverage summary fmt.Println() - fmt.Printf(" %s %s\n", shared.ProgressLabel("Total"), shared.FormatCoverage(totalCov)) + fmt.Printf(" %s %s\n", shared.ProgressLabel(i18n.T("label.total")), shared.FormatCoverage(totalCov)) // Generate HTML if requested if covHTML || covOpen { htmlPath := "coverage.html" htmlCmd := exec.Command("go", "tool", "cover", "-html="+covPath, "-o="+htmlPath) if err := htmlCmd.Run(); err != nil { - return fmt.Errorf("failed to generate HTML: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.go.cov.error.generate_html"), err) } - fmt.Printf(" %s %s\n", dimStyle.Render("HTML:"), htmlPath) + fmt.Printf(" %s %s\n", dimStyle.Render(i18n.T("cmd.go.cov.html_label")), htmlPath) if covOpen { // Open in browser @@ -272,7 +261,7 @@ func addGoCovCommand(parent *cobra.Command) { case exec.Command("which", "xdg-open").Run() == nil: openCmd = exec.Command("xdg-open", htmlPath) default: - fmt.Printf(" %s\n", dimStyle.Render("(open manually)")) + fmt.Printf(" %s\n", dimStyle.Render(i18n.T("cmd.go.cov.open_manually"))) } if openCmd != nil { openCmd.Run() @@ -282,24 +271,26 @@ func addGoCovCommand(parent *cobra.Command) { // Check threshold if covThreshold > 0 && totalCov < covThreshold { - fmt.Printf("\n%s Coverage %.1f%% is below threshold %.1f%%\n", - errorStyle.Render("FAIL"), totalCov, covThreshold) - return fmt.Errorf("coverage below threshold") + fmt.Printf("\n%s\n", errorStyle.Render(i18n.T("cmd.go.cov.below_threshold", map[string]interface{}{ + "Actual": fmt.Sprintf("%.1f", totalCov), + "Threshold": fmt.Sprintf("%.1f", covThreshold), + }))) + return fmt.Errorf(i18n.T("cmd.go.cov.error.below_threshold")) } if testErr != nil { return testErr } - fmt.Printf("\n%s\n", successStyle.Render("OK")) + fmt.Printf("\n%s\n", successStyle.Render(i18n.T("cli.ok"))) return nil }, } - covCmd.Flags().StringVar(&covPkg, "pkg", "", "Package to test (default: ./...)") - covCmd.Flags().BoolVar(&covHTML, "html", false, "Generate HTML coverage report") - covCmd.Flags().BoolVar(&covOpen, "open", false, "Generate and open HTML report in browser") - covCmd.Flags().Float64Var(&covThreshold, "threshold", 0, "Minimum coverage percentage (exit 1 if below)") + covCmd.Flags().StringVar(&covPkg, "pkg", "", i18n.T("cmd.go.cov.flag.pkg")) + covCmd.Flags().BoolVar(&covHTML, "html", false, i18n.T("cmd.go.cov.flag.html")) + covCmd.Flags().BoolVar(&covOpen, "open", false, i18n.T("cmd.go.cov.flag.open")) + covCmd.Flags().Float64Var(&covThreshold, "threshold", 0, i18n.T("cmd.go.cov.flag.threshold")) parent.AddCommand(covCmd) } diff --git a/cmd/go/go_tools.go b/cmd/go/go_tools.go index e685251..9c266e8 100644 --- a/cmd/go/go_tools.go +++ b/cmd/go/go_tools.go @@ -6,6 +6,7 @@ import ( "os/exec" "path/filepath" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -17,13 +18,8 @@ var ( func addGoInstallCommand(parent *cobra.Command) { installCmd := &cobra.Command{ Use: "install [path]", - Short: "Install Go binary", - Long: "Install Go binary to $GOPATH/bin.\n\n" + - "Examples:\n" + - " core go install # Install current module\n" + - " core go install ./cmd/core # Install specific path\n" + - " core go install --no-cgo # Pure Go (no C dependencies)\n" + - " core go install -v # Verbose output", + Short: i18n.T("cmd.go.install.short"), + Long: i18n.T("cmd.go.install.long"), RunE: func(cmd *cobra.Command, args []string) error { // Get install path from args or default to current dir installPath := "./..." @@ -42,10 +38,10 @@ func addGoInstallCommand(parent *cobra.Command) { } } - fmt.Printf("%s Installing\n", dimStyle.Render("Install:")) - fmt.Printf(" %s %s\n", dimStyle.Render("Path:"), installPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.go.install.label")), i18n.T("cmd.go.install.installing")) + fmt.Printf(" %s %s\n", dimStyle.Render(i18n.T("cmd.go.install.path_label")), installPath) if installNoCgo { - fmt.Printf(" %s %s\n", dimStyle.Render("CGO:"), "disabled") + fmt.Printf(" %s %s\n", dimStyle.Render(i18n.T("cmd.go.install.cgo_label")), i18n.T("cmd.go.install.cgo_disabled")) } cmdArgs := []string{"install"} @@ -62,7 +58,7 @@ func addGoInstallCommand(parent *cobra.Command) { execCmd.Stderr = os.Stderr if err := execCmd.Run(); err != nil { - fmt.Printf("\n%s\n", errorStyle.Render("FAIL Install failed")) + fmt.Printf("\n%s\n", errorStyle.Render(i18n.T("cmd.go.install.failed"))) return err } @@ -74,13 +70,13 @@ func addGoInstallCommand(parent *cobra.Command) { } binDir := filepath.Join(gopath, "bin") - fmt.Printf("\n%s Installed to %s\n", successStyle.Render("OK"), binDir) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.go.install.success")), i18n.T("cmd.go.install.installed_to", map[string]interface{}{"Path": binDir})) return nil }, } - installCmd.Flags().BoolVarP(&installVerbose, "verbose", "v", false, "Verbose output") - installCmd.Flags().BoolVar(&installNoCgo, "no-cgo", false, "Disable CGO (CGO_ENABLED=0)") + installCmd.Flags().BoolVarP(&installVerbose, "verbose", "v", false, i18n.T("cmd.go.install.flag.verbose")) + installCmd.Flags().BoolVar(&installNoCgo, "no-cgo", false, i18n.T("cmd.go.install.flag.no_cgo")) parent.AddCommand(installCmd) } @@ -88,19 +84,14 @@ func addGoInstallCommand(parent *cobra.Command) { func addGoModCommand(parent *cobra.Command) { modCmd := &cobra.Command{ Use: "mod", - Short: "Module management", - Long: "Go module management commands.\n\n" + - "Commands:\n" + - " tidy Add missing and remove unused modules\n" + - " download Download modules to local cache\n" + - " verify Verify dependencies\n" + - " graph Print module dependency graph", + Short: i18n.T("cmd.go.mod.short"), + Long: i18n.T("cmd.go.mod.long"), } // tidy tidyCmd := &cobra.Command{ Use: "tidy", - Short: "Tidy go.mod", + Short: i18n.T("cmd.go.mod.tidy.short"), RunE: func(cmd *cobra.Command, args []string) error { execCmd := exec.Command("go", "mod", "tidy") execCmd.Stdout = os.Stdout @@ -112,7 +103,7 @@ func addGoModCommand(parent *cobra.Command) { // download downloadCmd := &cobra.Command{ Use: "download", - Short: "Download modules", + Short: i18n.T("cmd.go.mod.download.short"), RunE: func(cmd *cobra.Command, args []string) error { execCmd := exec.Command("go", "mod", "download") execCmd.Stdout = os.Stdout @@ -124,7 +115,7 @@ func addGoModCommand(parent *cobra.Command) { // verify verifyCmd := &cobra.Command{ Use: "verify", - Short: "Verify dependencies", + Short: i18n.T("cmd.go.mod.verify.short"), RunE: func(cmd *cobra.Command, args []string) error { execCmd := exec.Command("go", "mod", "verify") execCmd.Stdout = os.Stdout @@ -136,7 +127,7 @@ func addGoModCommand(parent *cobra.Command) { // graph graphCmd := &cobra.Command{ Use: "graph", - Short: "Print dependency graph", + Short: i18n.T("cmd.go.mod.graph.short"), RunE: func(cmd *cobra.Command, args []string) error { execCmd := exec.Command("go", "mod", "graph") execCmd.Stdout = os.Stdout @@ -155,18 +146,14 @@ func addGoModCommand(parent *cobra.Command) { func addGoWorkCommand(parent *cobra.Command) { workCmd := &cobra.Command{ Use: "work", - Short: "Workspace management", - Long: "Go workspace management commands.\n\n" + - "Commands:\n" + - " sync Sync go.work with modules\n" + - " init Initialize go.work\n" + - " use Add module to workspace", + Short: i18n.T("cmd.go.work.short"), + Long: i18n.T("cmd.go.work.long"), } // sync syncCmd := &cobra.Command{ Use: "sync", - Short: "Sync workspace", + Short: i18n.T("cmd.go.work.sync.short"), RunE: func(cmd *cobra.Command, args []string) error { execCmd := exec.Command("go", "work", "sync") execCmd.Stdout = os.Stdout @@ -178,7 +165,7 @@ func addGoWorkCommand(parent *cobra.Command) { // init initCmd := &cobra.Command{ Use: "init", - Short: "Initialize workspace", + Short: i18n.T("cmd.go.work.init.short"), RunE: func(cmd *cobra.Command, args []string) error { execCmd := exec.Command("go", "work", "init") execCmd.Stdout = os.Stdout @@ -200,13 +187,13 @@ func addGoWorkCommand(parent *cobra.Command) { // use useCmd := &cobra.Command{ Use: "use [modules...]", - Short: "Add module to workspace", + Short: i18n.T("cmd.go.work.use.short"), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { // Auto-detect modules modules := findGoModules(".") if len(modules) == 0 { - return fmt.Errorf("no go.mod files found") + return fmt.Errorf(i18n.T("cmd.go.work.error.no_modules")) } for _, mod := range modules { execCmd := exec.Command("go", "work", "use", mod) @@ -215,7 +202,7 @@ func addGoWorkCommand(parent *cobra.Command) { if err := execCmd.Run(); err != nil { return err } - fmt.Printf("Added %s\n", mod) + fmt.Println(i18n.T("cmd.go.work.added", map[string]interface{}{"Module": mod})) } return nil } diff --git a/cmd/php/php.go b/cmd/php/php.go index 6b3913d..a016c4d 100644 --- a/cmd/php/php.go +++ b/cmd/php/php.go @@ -4,6 +4,7 @@ package php import ( "github.com/charmbracelet/lipgloss" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -51,14 +52,8 @@ var ( func AddPHPCommands(root *cobra.Command) { phpCmd := &cobra.Command{ Use: "php", - Short: "Laravel/PHP development tools", - Long: "Manage Laravel development environment with FrankenPHP.\n\n" + - "Services orchestrated:\n" + - " - FrankenPHP/Octane (port 8000, HTTPS on 443)\n" + - " - Vite dev server (port 5173)\n" + - " - Laravel Horizon (queue workers)\n" + - " - Laravel Reverb (WebSocket, port 8080)\n" + - " - Redis (port 6379)", + Short: i18n.T("cmd.php.short"), + Long: i18n.T("cmd.php.long"), } root.AddCommand(phpCmd) diff --git a/cmd/php/php_build.go b/cmd/php/php_build.go index 3b215f0..0660f6e 100644 --- a/cmd/php/php_build.go +++ b/cmd/php/php_build.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/host-uk/core/pkg/i18n" phppkg "github.com/host-uk/core/pkg/php" "github.com/spf13/cobra" ) @@ -25,19 +26,12 @@ var ( func addPHPBuildCommand(parent *cobra.Command) { buildCmd := &cobra.Command{ Use: "build", - Short: "Build Docker or LinuxKit image", - Long: "Build a production-ready container image for the PHP project.\n\n" + - "By default, builds a Docker image using FrankenPHP.\n" + - "Use --type linuxkit to build a LinuxKit VM image instead.\n\n" + - "Examples:\n" + - " core php build # Build Docker image\n" + - " core php build --name myapp --tag v1.0 # Build with custom name/tag\n" + - " core php build --type linuxkit # Build LinuxKit image\n" + - " core php build --type linuxkit --format iso # Build ISO image", + Short: i18n.T("cmd.php.build.short"), + Long: i18n.T("cmd.php.build.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } ctx := context.Background() @@ -61,15 +55,15 @@ func addPHPBuildCommand(parent *cobra.Command) { }, } - buildCmd.Flags().StringVar(&buildType, "type", "", "Build type: docker (default) or linuxkit") - buildCmd.Flags().StringVar(&buildImageName, "name", "", "Image name (default: project directory name)") - buildCmd.Flags().StringVar(&buildTag, "tag", "", "Image tag (default: latest)") - buildCmd.Flags().StringVar(&buildPlatform, "platform", "", "Target platform (e.g., linux/amd64, linux/arm64)") - buildCmd.Flags().StringVar(&buildDockerfile, "dockerfile", "", "Path to custom Dockerfile") - buildCmd.Flags().StringVar(&buildOutputPath, "output", "", "Output path for LinuxKit image") - buildCmd.Flags().StringVar(&buildFormat, "format", "", "LinuxKit output format: qcow2 (default), iso, raw, vmdk") - buildCmd.Flags().StringVar(&buildTemplate, "template", "", "LinuxKit template name (default: server-php)") - buildCmd.Flags().BoolVar(&buildNoCache, "no-cache", false, "Build without cache") + buildCmd.Flags().StringVar(&buildType, "type", "", i18n.T("cmd.php.build.flag.type")) + buildCmd.Flags().StringVar(&buildImageName, "name", "", i18n.T("cmd.php.build.flag.name")) + buildCmd.Flags().StringVar(&buildTag, "tag", "", i18n.T("cmd.php.build.flag.tag")) + buildCmd.Flags().StringVar(&buildPlatform, "platform", "", i18n.T("cmd.php.build.flag.platform")) + buildCmd.Flags().StringVar(&buildDockerfile, "dockerfile", "", i18n.T("cmd.php.build.flag.dockerfile")) + buildCmd.Flags().StringVar(&buildOutputPath, "output", "", i18n.T("cmd.php.build.flag.output")) + buildCmd.Flags().StringVar(&buildFormat, "format", "", i18n.T("cmd.php.build.flag.format")) + buildCmd.Flags().StringVar(&buildTemplate, "template", "", i18n.T("cmd.php.build.flag.template")) + buildCmd.Flags().BoolVar(&buildNoCache, "no-cache", false, i18n.T("cmd.php.build.flag.no_cache")) parent.AddCommand(buildCmd) } @@ -90,23 +84,23 @@ type linuxKitBuildOptions struct { func runPHPBuildDocker(ctx context.Context, projectDir string, opts dockerBuildOptions) error { if !phppkg.IsPHPProject(projectDir) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } - fmt.Printf("%s Building Docker image...\n\n", dimStyle.Render("PHP:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.build.building_docker")) // Show detected configuration config, err := phppkg.DetectDockerfileConfig(projectDir) if err != nil { - return fmt.Errorf("failed to detect project configuration: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.detect_config"), err) } - fmt.Printf("%s %s\n", dimStyle.Render("PHP Version:"), config.PHPVersion) - fmt.Printf("%s %v\n", dimStyle.Render("Laravel:"), config.IsLaravel) - fmt.Printf("%s %v\n", dimStyle.Render("Octane:"), config.HasOctane) - fmt.Printf("%s %v\n", dimStyle.Render("Frontend:"), config.HasAssets) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.php_version")), config.PHPVersion) + fmt.Printf("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.laravel")), config.IsLaravel) + fmt.Printf("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.octane")), config.HasOctane) + fmt.Printf("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.frontend")), config.HasAssets) if len(config.PHPExtensions) > 0 { - fmt.Printf("%s %s\n", dimStyle.Render("Extensions:"), strings.Join(config.PHPExtensions, ", ")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.extensions")), strings.Join(config.PHPExtensions, ", ")) } fmt.Println() @@ -134,19 +128,19 @@ func runPHPBuildDocker(ctx context.Context, projectDir string, opts dockerBuildO buildOpts.Tag = "latest" } - fmt.Printf("%s %s:%s\n", dimStyle.Render("Image:"), buildOpts.ImageName, buildOpts.Tag) + fmt.Printf("%s %s:%s\n", dimStyle.Render(i18n.T("cmd.php.build.image")), buildOpts.ImageName, buildOpts.Tag) if opts.Platform != "" { - fmt.Printf("%s %s\n", dimStyle.Render("Platform:"), opts.Platform) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.platform")), opts.Platform) } fmt.Println() if err := phppkg.BuildDocker(ctx, buildOpts); err != nil { - return fmt.Errorf("build failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.build_failed"), err) } - fmt.Printf("\n%s Docker image built successfully\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.build.docker_success")) fmt.Printf("%s docker run -p 80:80 -p 443:443 %s:%s\n", - dimStyle.Render("Run with:"), + dimStyle.Render(i18n.T("cmd.php.build.docker_run_with")), buildOpts.ImageName, buildOpts.Tag) return nil @@ -154,10 +148,10 @@ func runPHPBuildDocker(ctx context.Context, projectDir string, opts dockerBuildO func runPHPBuildLinuxKit(ctx context.Context, projectDir string, opts linuxKitBuildOptions) error { if !phppkg.IsPHPProject(projectDir) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } - fmt.Printf("%s Building LinuxKit image...\n\n", dimStyle.Render("PHP:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.build.building_linuxkit")) buildOpts := phppkg.LinuxKitBuildOptions{ ProjectDir: projectDir, @@ -174,15 +168,15 @@ func runPHPBuildLinuxKit(ctx context.Context, projectDir string, opts linuxKitBu buildOpts.Template = "server-php" } - fmt.Printf("%s %s\n", dimStyle.Render("Template:"), buildOpts.Template) - fmt.Printf("%s %s\n", dimStyle.Render("Format:"), buildOpts.Format) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.template")), buildOpts.Template) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.format")), buildOpts.Format) fmt.Println() if err := phppkg.BuildLinuxKit(ctx, buildOpts); err != nil { - return fmt.Errorf("build failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.build_failed"), err) } - fmt.Printf("\n%s LinuxKit image built successfully\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.build.linuxkit_success")) return nil } @@ -199,13 +193,8 @@ var ( func addPHPServeCommand(parent *cobra.Command) { serveCmd := &cobra.Command{ Use: "serve", - Short: "Run production container", - Long: "Run a production PHP container.\n\n" + - "This starts the built Docker image in production mode.\n\n" + - "Examples:\n" + - " core php serve --name myapp # Run container\n" + - " core php serve --name myapp -d # Run detached\n" + - " core php serve --name myapp --port 8080 # Custom port", + Short: i18n.T("cmd.php.serve.short"), + Long: i18n.T("cmd.php.serve.long"), RunE: func(cmd *cobra.Command, args []string) error { imageName := serveImageName if imageName == "" { @@ -218,7 +207,7 @@ func addPHPServeCommand(parent *cobra.Command) { } } if imageName == "" { - return fmt.Errorf("--name is required: specify the Docker image name") + return fmt.Errorf(i18n.T("cmd.php.serve.name_required")) } } @@ -235,8 +224,8 @@ func addPHPServeCommand(parent *cobra.Command) { Output: os.Stdout, } - fmt.Printf("%s Running production container...\n\n", dimStyle.Render("PHP:")) - fmt.Printf("%s %s:%s\n", dimStyle.Render("Image:"), imageName, func() string { + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.serve.running")) + fmt.Printf("%s %s:%s\n", dimStyle.Render(i18n.T("cmd.php.build.image")), imageName, func() string { if serveTag == "" { return "latest" } @@ -257,24 +246,24 @@ func addPHPServeCommand(parent *cobra.Command) { fmt.Println() if err := phppkg.ServeProduction(ctx, opts); err != nil { - return fmt.Errorf("failed to start container: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.start_container"), err) } if !serveDetach { - fmt.Printf("\n%s Container stopped\n", dimStyle.Render("PHP:")) + fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.serve.stopped")) } return nil }, } - serveCmd.Flags().StringVar(&serveImageName, "name", "", "Docker image name (required)") - serveCmd.Flags().StringVar(&serveTag, "tag", "", "Image tag (default: latest)") - serveCmd.Flags().StringVar(&serveContainerName, "container", "", "Container name") - serveCmd.Flags().IntVar(&servePort, "port", 0, "HTTP port (default: 80)") - serveCmd.Flags().IntVar(&serveHTTPSPort, "https-port", 0, "HTTPS port (default: 443)") - serveCmd.Flags().BoolVarP(&serveDetach, "detach", "d", false, "Run in detached mode") - serveCmd.Flags().StringVar(&serveEnvFile, "env-file", "", "Path to environment file") + serveCmd.Flags().StringVar(&serveImageName, "name", "", i18n.T("cmd.php.serve.flag.name")) + serveCmd.Flags().StringVar(&serveTag, "tag", "", i18n.T("cmd.php.serve.flag.tag")) + serveCmd.Flags().StringVar(&serveContainerName, "container", "", i18n.T("cmd.php.serve.flag.container")) + serveCmd.Flags().IntVar(&servePort, "port", 0, i18n.T("cmd.php.serve.flag.port")) + serveCmd.Flags().IntVar(&serveHTTPSPort, "https-port", 0, i18n.T("cmd.php.serve.flag.https_port")) + serveCmd.Flags().BoolVarP(&serveDetach, "detach", "d", false, i18n.T("cmd.php.serve.flag.detach")) + serveCmd.Flags().StringVar(&serveEnvFile, "env-file", "", i18n.T("cmd.php.serve.flag.env_file")) parent.AddCommand(serveCmd) } @@ -282,19 +271,16 @@ func addPHPServeCommand(parent *cobra.Command) { func addPHPShellCommand(parent *cobra.Command) { shellCmd := &cobra.Command{ Use: "shell [container]", - Short: "Open shell in running container", - Long: "Open an interactive shell in a running PHP container.\n\n" + - "Examples:\n" + - " core php shell abc123 # Shell into container by ID\n" + - " core php shell myapp # Shell into container by name", - Args: cobra.ExactArgs(1), + Short: i18n.T("cmd.php.shell.short"), + Long: i18n.T("cmd.php.shell.long"), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - fmt.Printf("%s Opening shell in container %s...\n", dimStyle.Render("PHP:"), args[0]) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.shell.opening", map[string]interface{}{"Container": args[0]})) if err := phppkg.Shell(ctx, args[0]); err != nil { - return fmt.Errorf("failed to open shell: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.open_shell"), err) } return nil diff --git a/cmd/php/php_deploy.go b/cmd/php/php_deploy.go index 6f9a874..2715fc9 100644 --- a/cmd/php/php_deploy.go +++ b/cmd/php/php_deploy.go @@ -7,6 +7,7 @@ import ( "time" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" phppkg "github.com/host-uk/core/pkg/php" "github.com/spf13/cobra" ) @@ -41,22 +42,12 @@ var ( func addPHPDeployCommand(parent *cobra.Command) { deployCmd := &cobra.Command{ Use: "deploy", - Short: "Deploy to Coolify", - Long: "Deploy the PHP application to Coolify.\n\n" + - "Requires configuration in .env:\n" + - " COOLIFY_URL=https://coolify.example.com\n" + - " COOLIFY_TOKEN=your-api-token\n" + - " COOLIFY_APP_ID=production-app-id\n" + - " COOLIFY_STAGING_APP_ID=staging-app-id (optional)\n\n" + - "Examples:\n" + - " core php deploy # Deploy to production\n" + - " core php deploy --staging # Deploy to staging\n" + - " core php deploy --force # Force deployment\n" + - " core php deploy --wait # Wait for deployment to complete", + Short: i18n.T("cmd.php.deploy.short"), + Long: i18n.T("cmd.php.deploy.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } env := phppkg.EnvProduction @@ -64,7 +55,7 @@ func addPHPDeployCommand(parent *cobra.Command) { env = phppkg.EnvStaging } - fmt.Printf("%s Deploying to %s...\n\n", dimStyle.Render("Deploy:"), env) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy.deploying", map[string]interface{}{"Environment": env})) ctx := context.Background() @@ -77,28 +68,28 @@ func addPHPDeployCommand(parent *cobra.Command) { status, err := phppkg.Deploy(ctx, opts) if err != nil { - return fmt.Errorf("deployment failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.deploy_failed"), err) } printDeploymentStatus(status) if deployWait { if phppkg.IsDeploymentSuccessful(status.Status) { - fmt.Printf("\n%s Deployment completed successfully\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.deploy.success")) } else { - fmt.Printf("\n%s Deployment ended with status: %s\n", errorStyle.Render("Warning:"), status.Status) + fmt.Printf("\n%s %s\n", errorStyle.Render(i18n.T("cmd.php.label.warning")), i18n.T("cmd.php.deploy.warning_status", map[string]interface{}{"Status": status.Status})) } } else { - fmt.Printf("\n%s Deployment triggered. Use 'core php deploy:status' to check progress.\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.deploy.triggered")) } return nil }, } - deployCmd.Flags().BoolVar(&deployStaging, "staging", false, "Deploy to staging environment") - deployCmd.Flags().BoolVar(&deployForce, "force", false, "Force deployment even if no changes detected") - deployCmd.Flags().BoolVar(&deployWait, "wait", false, "Wait for deployment to complete") + deployCmd.Flags().BoolVar(&deployStaging, "staging", false, i18n.T("cmd.php.deploy.flag.staging")) + deployCmd.Flags().BoolVar(&deployForce, "force", false, i18n.T("cmd.php.deploy.flag.force")) + deployCmd.Flags().BoolVar(&deployWait, "wait", false, i18n.T("cmd.php.deploy.flag.wait")) parent.AddCommand(deployCmd) } @@ -111,16 +102,12 @@ var ( func addPHPDeployStatusCommand(parent *cobra.Command) { statusCmd := &cobra.Command{ Use: "deploy:status", - Short: "Show deployment status", - Long: "Show the status of a deployment.\n\n" + - "Examples:\n" + - " core php deploy:status # Latest production deployment\n" + - " core php deploy:status --staging # Latest staging deployment\n" + - " core php deploy:status --id abc123 # Specific deployment", + Short: i18n.T("cmd.php.deploy_status.short"), + Long: i18n.T("cmd.php.deploy_status.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } env := phppkg.EnvProduction @@ -128,7 +115,7 @@ func addPHPDeployStatusCommand(parent *cobra.Command) { env = phppkg.EnvStaging } - fmt.Printf("%s Checking %s deployment status...\n\n", dimStyle.Render("Deploy:"), env) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy_status.checking", map[string]interface{}{"Environment": env})) ctx := context.Background() @@ -140,7 +127,7 @@ func addPHPDeployStatusCommand(parent *cobra.Command) { status, err := phppkg.DeployStatus(ctx, opts) if err != nil { - return fmt.Errorf("failed to get status: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.status_failed"), err) } printDeploymentStatus(status) @@ -149,8 +136,8 @@ func addPHPDeployStatusCommand(parent *cobra.Command) { }, } - statusCmd.Flags().BoolVar(&deployStatusStaging, "staging", false, "Check staging environment") - statusCmd.Flags().StringVar(&deployStatusDeploymentID, "id", "", "Specific deployment ID") + statusCmd.Flags().BoolVar(&deployStatusStaging, "staging", false, i18n.T("cmd.php.deploy_status.flag.staging")) + statusCmd.Flags().StringVar(&deployStatusDeploymentID, "id", "", i18n.T("cmd.php.deploy_status.flag.id")) parent.AddCommand(statusCmd) } @@ -164,18 +151,12 @@ var ( func addPHPDeployRollbackCommand(parent *cobra.Command) { rollbackCmd := &cobra.Command{ Use: "deploy:rollback", - Short: "Rollback to previous deployment", - Long: "Rollback to a previous deployment.\n\n" + - "If no deployment ID is specified, rolls back to the most recent\n" + - "successful deployment.\n\n" + - "Examples:\n" + - " core php deploy:rollback # Rollback to previous\n" + - " core php deploy:rollback --staging # Rollback staging\n" + - " core php deploy:rollback --id abc123 # Rollback to specific deployment", + Short: i18n.T("cmd.php.deploy_rollback.short"), + Long: i18n.T("cmd.php.deploy_rollback.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } env := phppkg.EnvProduction @@ -183,7 +164,7 @@ func addPHPDeployRollbackCommand(parent *cobra.Command) { env = phppkg.EnvStaging } - fmt.Printf("%s Rolling back %s...\n\n", dimStyle.Render("Deploy:"), env) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy_rollback.rolling_back", map[string]interface{}{"Environment": env})) ctx := context.Background() @@ -196,28 +177,28 @@ func addPHPDeployRollbackCommand(parent *cobra.Command) { status, err := phppkg.Rollback(ctx, opts) if err != nil { - return fmt.Errorf("rollback failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.rollback_failed"), err) } printDeploymentStatus(status) if rollbackWait { if phppkg.IsDeploymentSuccessful(status.Status) { - fmt.Printf("\n%s Rollback completed successfully\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.deploy_rollback.success")) } else { - fmt.Printf("\n%s Rollback ended with status: %s\n", errorStyle.Render("Warning:"), status.Status) + fmt.Printf("\n%s %s\n", errorStyle.Render(i18n.T("cmd.php.label.warning")), i18n.T("cmd.php.deploy_rollback.warning_status", map[string]interface{}{"Status": status.Status})) } } else { - fmt.Printf("\n%s Rollback triggered. Use 'core php deploy:status' to check progress.\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.deploy_rollback.triggered")) } return nil }, } - rollbackCmd.Flags().BoolVar(&rollbackStaging, "staging", false, "Rollback staging environment") - rollbackCmd.Flags().StringVar(&rollbackDeploymentID, "id", "", "Specific deployment ID to rollback to") - rollbackCmd.Flags().BoolVar(&rollbackWait, "wait", false, "Wait for rollback to complete") + rollbackCmd.Flags().BoolVar(&rollbackStaging, "staging", false, i18n.T("cmd.php.deploy_rollback.flag.staging")) + rollbackCmd.Flags().StringVar(&rollbackDeploymentID, "id", "", i18n.T("cmd.php.deploy_rollback.flag.id")) + rollbackCmd.Flags().BoolVar(&rollbackWait, "wait", false, i18n.T("cmd.php.deploy_rollback.flag.wait")) parent.AddCommand(rollbackCmd) } @@ -230,16 +211,12 @@ var ( func addPHPDeployListCommand(parent *cobra.Command) { listCmd := &cobra.Command{ Use: "deploy:list", - Short: "List recent deployments", - Long: "List recent deployments.\n\n" + - "Examples:\n" + - " core php deploy:list # List production deployments\n" + - " core php deploy:list --staging # List staging deployments\n" + - " core php deploy:list --limit 20 # List more deployments", + Short: i18n.T("cmd.php.deploy_list.short"), + Long: i18n.T("cmd.php.deploy_list.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } env := phppkg.EnvProduction @@ -252,17 +229,17 @@ func addPHPDeployListCommand(parent *cobra.Command) { limit = 10 } - fmt.Printf("%s Recent %s deployments:\n\n", dimStyle.Render("Deploy:"), env) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy_list.recent", map[string]interface{}{"Environment": env})) ctx := context.Background() deployments, err := phppkg.ListDeployments(ctx, cwd, env, limit) if err != nil { - return fmt.Errorf("failed to list deployments: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.list_deployments"), err) } if len(deployments) == 0 { - fmt.Printf("%s No deployments found\n", dimStyle.Render("Info:")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.deploy_list.none_found")) return nil } @@ -274,8 +251,8 @@ func addPHPDeployListCommand(parent *cobra.Command) { }, } - listCmd.Flags().BoolVar(&deployListStaging, "staging", false, "List staging deployments") - listCmd.Flags().IntVar(&deployListLimit, "limit", 0, "Number of deployments to list (default: 10)") + listCmd.Flags().BoolVar(&deployListStaging, "staging", false, i18n.T("cmd.php.deploy_list.flag.staging")) + listCmd.Flags().IntVar(&deployListLimit, "limit", 0, i18n.T("cmd.php.deploy_list.flag.limit")) parent.AddCommand(listCmd) } @@ -290,18 +267,18 @@ func printDeploymentStatus(status *phppkg.DeploymentStatus) { statusStyle = phpDeployFailedStyle } - fmt.Printf("%s %s\n", dimStyle.Render("Status:"), statusStyle.Render(status.Status)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.status")), statusStyle.Render(status.Status)) if status.ID != "" { - fmt.Printf("%s %s\n", dimStyle.Render("ID:"), status.ID) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.id")), status.ID) } if status.URL != "" { - fmt.Printf("%s %s\n", dimStyle.Render("URL:"), linkStyle.Render(status.URL)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.url")), linkStyle.Render(status.URL)) } if status.Branch != "" { - fmt.Printf("%s %s\n", dimStyle.Render("Branch:"), status.Branch) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.branch")), status.Branch) } if status.Commit != "" { @@ -309,26 +286,26 @@ func printDeploymentStatus(status *phppkg.DeploymentStatus) { if len(commit) > 7 { commit = commit[:7] } - fmt.Printf("%s %s\n", dimStyle.Render("Commit:"), commit) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.commit")), commit) if status.CommitMessage != "" { // Truncate long messages msg := status.CommitMessage if len(msg) > 60 { msg = msg[:57] + "..." } - fmt.Printf("%s %s\n", dimStyle.Render("Message:"), msg) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.message")), msg) } } if !status.StartedAt.IsZero() { - fmt.Printf("%s %s\n", dimStyle.Render("Started:"), status.StartedAt.Format(time.RFC3339)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.started")), status.StartedAt.Format(time.RFC3339)) } if !status.CompletedAt.IsZero() { - fmt.Printf("%s %s\n", dimStyle.Render("Completed:"), status.CompletedAt.Format(time.RFC3339)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.completed")), status.CompletedAt.Format(time.RFC3339)) if !status.StartedAt.IsZero() { duration := status.CompletedAt.Sub(status.StartedAt) - fmt.Printf("%s %s\n", dimStyle.Render("Duration:"), duration.Round(time.Second)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.duration")), duration.Round(time.Second)) } } } @@ -390,24 +367,24 @@ func formatTimeAgo(t time.Time) string { switch { case duration < time.Minute: - return "just now" + return i18n.T("cli.time.just_now") case duration < time.Hour: mins := int(duration.Minutes()) if mins == 1 { - return "1 minute ago" + return i18n.T("cli.time.minute_ago") } - return fmt.Sprintf("%d minutes ago", mins) + return i18n.T("cli.time.minutes_ago", map[string]interface{}{"Count": mins}) case duration < 24*time.Hour: hours := int(duration.Hours()) if hours == 1 { - return "1 hour ago" + return i18n.T("cli.time.hour_ago") } - return fmt.Sprintf("%d hours ago", hours) + return i18n.T("cli.time.hours_ago", map[string]interface{}{"Count": hours}) default: days := int(duration.Hours() / 24) if days == 1 { - return "1 day ago" + return i18n.T("cli.time.day_ago") } - return fmt.Sprintf("%d days ago", days) + return i18n.T("cli.time.days_ago", map[string]interface{}{"Count": days}) } } diff --git a/cmd/php/php_dev.go b/cmd/php/php_dev.go index c326cd6..853171f 100644 --- a/cmd/php/php_dev.go +++ b/cmd/php/php_dev.go @@ -11,6 +11,7 @@ import ( "time" "github.com/charmbracelet/lipgloss" + "github.com/host-uk/core/pkg/i18n" phppkg "github.com/host-uk/core/pkg/php" "github.com/spf13/cobra" ) @@ -28,13 +29,8 @@ var ( func addPHPDevCommand(parent *cobra.Command) { devCmd := &cobra.Command{ Use: "dev", - Short: "Start Laravel development environment", - Long: "Starts all detected Laravel services.\n\n" + - "Auto-detects:\n" + - " - Vite (vite.config.js/ts)\n" + - " - Horizon (config/horizon.php)\n" + - " - Reverb (config/reverb.php)\n" + - " - Redis (from .env)", + Short: i18n.T("cmd.php.dev.short"), + Long: i18n.T("cmd.php.dev.long"), RunE: func(cmd *cobra.Command, args []string) error { return runPHPDev(phpDevOptions{ NoVite: devNoVite, @@ -48,13 +44,13 @@ func addPHPDevCommand(parent *cobra.Command) { }, } - devCmd.Flags().BoolVar(&devNoVite, "no-vite", false, "Skip Vite dev server") - devCmd.Flags().BoolVar(&devNoHorizon, "no-horizon", false, "Skip Laravel Horizon") - devCmd.Flags().BoolVar(&devNoReverb, "no-reverb", false, "Skip Laravel Reverb") - devCmd.Flags().BoolVar(&devNoRedis, "no-redis", false, "Skip Redis server") - devCmd.Flags().BoolVar(&devHTTPS, "https", false, "Enable HTTPS with mkcert") - devCmd.Flags().StringVar(&devDomain, "domain", "", "Domain for SSL certificate (default: from APP_URL or localhost)") - devCmd.Flags().IntVar(&devPort, "port", 0, "FrankenPHP port (default: 8000)") + devCmd.Flags().BoolVar(&devNoVite, "no-vite", false, i18n.T("cmd.php.dev.flag.no_vite")) + devCmd.Flags().BoolVar(&devNoHorizon, "no-horizon", false, i18n.T("cmd.php.dev.flag.no_horizon")) + devCmd.Flags().BoolVar(&devNoReverb, "no-reverb", false, i18n.T("cmd.php.dev.flag.no_reverb")) + devCmd.Flags().BoolVar(&devNoRedis, "no-redis", false, i18n.T("cmd.php.dev.flag.no_redis")) + devCmd.Flags().BoolVar(&devHTTPS, "https", false, i18n.T("cmd.php.dev.flag.https")) + devCmd.Flags().StringVar(&devDomain, "domain", "", i18n.T("cmd.php.dev.flag.domain")) + devCmd.Flags().IntVar(&devPort, "port", 0, i18n.T("cmd.php.dev.flag.port")) parent.AddCommand(devCmd) } @@ -77,7 +73,7 @@ func runPHPDev(opts phpDevOptions) error { // Check if this is a Laravel project if !phppkg.IsLaravelProject(cwd) { - return fmt.Errorf("not a Laravel project (missing artisan or laravel/framework)") + return fmt.Errorf(i18n.T("cmd.php.error.not_laravel")) } // Get app name for display @@ -86,11 +82,11 @@ func runPHPDev(opts phpDevOptions) error { appName = "Laravel" } - fmt.Printf("%s Starting %s development environment\n\n", dimStyle.Render("PHP:"), appName) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.dev.starting", map[string]interface{}{"AppName": appName})) // Detect services services := phppkg.DetectServices(cwd) - fmt.Printf("%s Detected services:\n", dimStyle.Render("Services:")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.services")), i18n.T("cmd.php.dev.detected_services")) for _, svc := range services { fmt.Printf(" %s %s\n", successStyle.Render("*"), svc) } @@ -125,16 +121,16 @@ func runPHPDev(opts phpDevOptions) error { go func() { <-sigCh - fmt.Printf("\n%s Shutting down...\n", dimStyle.Render("PHP:")) + fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.dev.shutting_down")) cancel() }() if err := server.Start(ctx, devOpts); err != nil { - return fmt.Errorf("failed to start services: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.start_services"), err) } // Print status - fmt.Printf("%s Services started:\n", successStyle.Render("Running:")) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.php.label.running")), i18n.T("cmd.php.dev.services_started")) printServiceStatuses(server.Status()) fmt.Println() @@ -147,19 +143,19 @@ func runPHPDev(opts phpDevOptions) error { appURL = fmt.Sprintf("http://localhost:%d", port) } } - fmt.Printf("%s %s\n", dimStyle.Render("App URL:"), linkStyle.Render(appURL)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.app_url")), linkStyle.Render(appURL)) // Check for Vite if !opts.NoVite && containsService(services, phppkg.ServiceVite) { - fmt.Printf("%s %s\n", dimStyle.Render("Vite:"), linkStyle.Render("http://localhost:5173")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.vite")), linkStyle.Render("http://localhost:5173")) } - fmt.Printf("\n%s\n\n", dimStyle.Render("Press Ctrl+C to stop all services")) + fmt.Printf("\n%s\n\n", dimStyle.Render(i18n.T("cmd.php.dev.press_ctrl_c"))) // Stream unified logs logsReader, err := server.Logs("", true) if err != nil { - fmt.Printf("%s Failed to get logs: %v\n", errorStyle.Render("Warning:"), err) + fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.php.label.warning")), i18n.T("cmd.php.dev.logs_failed", map[string]interface{}{"Error": err})) } else { defer logsReader.Close() @@ -178,10 +174,10 @@ func runPHPDev(opts phpDevOptions) error { shutdown: // Stop services if err := server.Stop(); err != nil { - fmt.Printf("%s Error stopping services: %v\n", errorStyle.Render("Error:"), err) + fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.php.label.error")), i18n.T("cmd.php.dev.stop_error", map[string]interface{}{"Error": err})) } - fmt.Printf("%s All services stopped\n", successStyle.Render("Done:")) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.dev.all_stopped")) return nil } @@ -193,16 +189,15 @@ var ( func addPHPLogsCommand(parent *cobra.Command) { logsCmd := &cobra.Command{ Use: "logs", - Short: "View service logs", - Long: "Stream logs from Laravel services.\n\n" + - "Services: frankenphp, vite, horizon, reverb, redis", + Short: i18n.T("cmd.php.logs.short"), + Long: i18n.T("cmd.php.logs.long"), RunE: func(cmd *cobra.Command, args []string) error { return runPHPLogs(logsService, logsFollow) }, } - logsCmd.Flags().BoolVar(&logsFollow, "follow", false, "Follow log output") - logsCmd.Flags().StringVar(&logsService, "service", "", "Specific service (default: all)") + logsCmd.Flags().BoolVar(&logsFollow, "follow", false, i18n.T("cmd.php.logs.flag.follow")) + logsCmd.Flags().StringVar(&logsService, "service", "", i18n.T("cmd.php.logs.flag.service")) parent.AddCommand(logsCmd) } @@ -214,7 +209,7 @@ func runPHPLogs(service string, follow bool) error { } if !phppkg.IsLaravelProject(cwd) { - return fmt.Errorf("not a Laravel project") + return fmt.Errorf(i18n.T("cmd.php.error.not_laravel_short")) } // Create a minimal server just to access logs @@ -222,7 +217,7 @@ func runPHPLogs(service string, follow bool) error { logsReader, err := server.Logs(service, follow) if err != nil { - return fmt.Errorf("failed to get logs: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.get_logs"), err) } defer logsReader.Close() @@ -254,7 +249,7 @@ func runPHPLogs(service string, follow bool) error { func addPHPStopCommand(parent *cobra.Command) { stopCmd := &cobra.Command{ Use: "stop", - Short: "Stop all Laravel services", + Short: i18n.T("cmd.php.stop.short"), RunE: func(cmd *cobra.Command, args []string) error { return runPHPStop() }, @@ -269,23 +264,23 @@ func runPHPStop() error { return err } - fmt.Printf("%s Stopping services...\n", dimStyle.Render("PHP:")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.stop.stopping")) // We need to find running processes // This is a simplified version - in practice you'd want to track PIDs server := phppkg.NewDevServer(phppkg.Options{Dir: cwd}) if err := server.Stop(); err != nil { - return fmt.Errorf("failed to stop services: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.stop_services"), err) } - fmt.Printf("%s All services stopped\n", successStyle.Render("Done:")) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.dev.all_stopped")) return nil } func addPHPStatusCommand(parent *cobra.Command) { statusCmd := &cobra.Command{ Use: "status", - Short: "Show service status", + Short: i18n.T("cmd.php.status.short"), RunE: func(cmd *cobra.Command, args []string) error { return runPHPStatus() }, @@ -301,7 +296,7 @@ func runPHPStatus() error { } if !phppkg.IsLaravelProject(cwd) { - return fmt.Errorf("not a Laravel project") + return fmt.Errorf(i18n.T("cmd.php.error.not_laravel_short")) } appName := phppkg.GetLaravelAppName(cwd) @@ -309,11 +304,11 @@ func runPHPStatus() error { appName = "Laravel" } - fmt.Printf("%s %s\n\n", dimStyle.Render("Project:"), appName) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.status.project")), appName) // Detect available services services := phppkg.DetectServices(cwd) - fmt.Printf("%s\n", dimStyle.Render("Detected services:")) + fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.php.status.detected_services"))) for _, svc := range services { style := getServiceStyle(string(svc)) fmt.Printf(" %s %s\n", style.Render("*"), svc) @@ -322,11 +317,11 @@ func runPHPStatus() error { // Package manager pm := phppkg.DetectPackageManager(cwd) - fmt.Printf("%s %s\n", dimStyle.Render("Package manager:"), pm) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.package_manager")), pm) // FrankenPHP status if phppkg.IsFrankenPHPProject(cwd) { - fmt.Printf("%s %s\n", dimStyle.Render("Octane server:"), "FrankenPHP") + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.octane_server")), "FrankenPHP") } // SSL status @@ -334,9 +329,9 @@ func runPHPStatus() error { if appURL != "" { domain := phppkg.ExtractDomainFromURL(appURL) if phppkg.CertsExist(domain, phppkg.SSLOptions{}) { - fmt.Printf("%s %s\n", dimStyle.Render("SSL certificates:"), successStyle.Render("installed")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.ssl_certs")), successStyle.Render(i18n.T("cmd.php.status.ssl_installed"))) } else { - fmt.Printf("%s %s\n", dimStyle.Render("SSL certificates:"), dimStyle.Render("not setup")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.ssl_certs")), dimStyle.Render(i18n.T("cmd.php.status.ssl_not_setup"))) } } @@ -348,13 +343,13 @@ var sslDomain string func addPHPSSLCommand(parent *cobra.Command) { sslCmd := &cobra.Command{ Use: "ssl", - Short: "Setup SSL certificates with mkcert", + Short: i18n.T("cmd.php.ssl.short"), RunE: func(cmd *cobra.Command, args []string) error { return runPHPSSL(sslDomain) }, } - sslCmd.Flags().StringVar(&sslDomain, "domain", "", "Domain for certificate (default: from APP_URL)") + sslCmd.Flags().StringVar(&sslDomain, "domain", "", i18n.T("cmd.php.ssl.flag.domain")) parent.AddCommand(sslCmd) } @@ -378,35 +373,35 @@ func runPHPSSL(domain string) error { // Check if mkcert is installed if !phppkg.IsMkcertInstalled() { - fmt.Printf("%s mkcert is not installed\n", errorStyle.Render("Error:")) - fmt.Println("\nInstall with:") - fmt.Println(" macOS: brew install mkcert") - fmt.Println(" Linux: see https://github.com/FiloSottile/mkcert") - return fmt.Errorf("mkcert not installed") + fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.php.label.error")), i18n.T("cmd.php.ssl.mkcert_not_installed")) + fmt.Printf("\n%s\n", i18n.T("cmd.php.ssl.install_with")) + fmt.Printf(" %s\n", i18n.T("cmd.php.ssl.install_macos")) + fmt.Printf(" %s\n", i18n.T("cmd.php.ssl.install_linux")) + return fmt.Errorf(i18n.T("cmd.php.error.mkcert_not_installed")) } - fmt.Printf("%s Setting up SSL for %s\n", dimStyle.Render("SSL:"), domain) + fmt.Printf("%s %s\n", dimStyle.Render("SSL:"), i18n.T("cmd.php.ssl.setting_up", map[string]interface{}{"Domain": domain})) // Check if certs already exist if phppkg.CertsExist(domain, phppkg.SSLOptions{}) { - fmt.Printf("%s Certificates already exist\n", dimStyle.Render("Skip:")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.skip")), i18n.T("cmd.php.ssl.certs_exist")) certFile, keyFile, _ := phppkg.CertPaths(domain, phppkg.SSLOptions{}) - fmt.Printf("%s %s\n", dimStyle.Render("Cert:"), certFile) - fmt.Printf("%s %s\n", dimStyle.Render("Key:"), keyFile) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.cert_label")), certFile) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.key_label")), keyFile) return nil } // Setup SSL if err := phppkg.SetupSSL(domain, phppkg.SSLOptions{}); err != nil { - return fmt.Errorf("failed to setup SSL: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.ssl_setup"), err) } certFile, keyFile, _ := phppkg.CertPaths(domain, phppkg.SSLOptions{}) - fmt.Printf("%s SSL certificates created\n", successStyle.Render("Done:")) - fmt.Printf("%s %s\n", dimStyle.Render("Cert:"), certFile) - fmt.Printf("%s %s\n", dimStyle.Render("Key:"), keyFile) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.ssl.certs_created")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.cert_label")), certFile) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.key_label")), keyFile) return nil } @@ -419,17 +414,17 @@ func printServiceStatuses(statuses []phppkg.ServiceStatus) { var statusText string if s.Error != nil { - statusText = phpStatusError.Render(fmt.Sprintf("error: %v", s.Error)) + statusText = phpStatusError.Render(i18n.T("cmd.php.status.error", map[string]interface{}{"Error": s.Error})) } else if s.Running { - statusText = phpStatusRunning.Render("running") + statusText = phpStatusRunning.Render(i18n.T("cmd.php.status.running")) if s.Port > 0 { - statusText += dimStyle.Render(fmt.Sprintf(" (port %d)", s.Port)) + statusText += dimStyle.Render(fmt.Sprintf(" (%s)", i18n.T("cmd.php.status.port", map[string]interface{}{"Port": s.Port}))) } if s.PID > 0 { - statusText += dimStyle.Render(fmt.Sprintf(" [pid %d]", s.PID)) + statusText += dimStyle.Render(fmt.Sprintf(" [%s]", i18n.T("cmd.php.status.pid", map[string]interface{}{"PID": s.PID}))) } } else { - statusText = phpStatusStopped.Render("stopped") + statusText = phpStatusStopped.Render(i18n.T("cmd.php.status.stopped")) } fmt.Printf(" %s %s\n", style.Render(s.Name+":"), statusText) diff --git a/cmd/php/php_packages.go b/cmd/php/php_packages.go index b80b446..ce59233 100644 --- a/cmd/php/php_packages.go +++ b/cmd/php/php_packages.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/host-uk/core/pkg/i18n" phppkg "github.com/host-uk/core/pkg/php" "github.com/spf13/cobra" ) @@ -11,15 +12,8 @@ import ( func addPHPPackagesCommands(parent *cobra.Command) { packagesCmd := &cobra.Command{ Use: "packages", - Short: "Manage local PHP packages", - Long: "Link and manage local PHP packages for development.\n\n" + - "Similar to npm link, this adds path repositories to composer.json\n" + - "for developing packages alongside your project.\n\n" + - "Commands:\n" + - " link - Link local packages by path\n" + - " unlink - Unlink packages by name\n" + - " update - Update linked packages\n" + - " list - List linked packages", + Short: i18n.T("cmd.php.packages.short"), + Long: i18n.T("cmd.php.packages.long"), } parent.AddCommand(packagesCmd) @@ -32,27 +26,22 @@ func addPHPPackagesCommands(parent *cobra.Command) { func addPHPPackagesLinkCommand(parent *cobra.Command) { linkCmd := &cobra.Command{ Use: "link [paths...]", - Short: "Link local packages", - Long: "Link local PHP packages for development.\n\n" + - "Adds path repositories to composer.json with symlink enabled.\n" + - "The package name is auto-detected from each path's composer.json.\n\n" + - "Examples:\n" + - " core php packages link ../my-package\n" + - " core php packages link ../pkg-a ../pkg-b", - Args: cobra.MinimumNArgs(1), + Short: i18n.T("cmd.php.packages.link.short"), + Long: i18n.T("cmd.php.packages.link.long"), + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } - fmt.Printf("%s Linking packages...\n\n", dimStyle.Render("PHP:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.link.linking")) if err := phppkg.LinkPackages(cwd, args); err != nil { - return fmt.Errorf("failed to link packages: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.link_packages"), err) } - fmt.Printf("\n%s Packages linked. Run 'composer update' to install.\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.packages.link.done")) return nil }, } @@ -63,26 +52,22 @@ func addPHPPackagesLinkCommand(parent *cobra.Command) { func addPHPPackagesUnlinkCommand(parent *cobra.Command) { unlinkCmd := &cobra.Command{ Use: "unlink [packages...]", - Short: "Unlink packages", - Long: "Remove linked packages from composer.json.\n\n" + - "Removes path repositories by package name.\n\n" + - "Examples:\n" + - " core php packages unlink vendor/my-package\n" + - " core php packages unlink vendor/pkg-a vendor/pkg-b", - Args: cobra.MinimumNArgs(1), + Short: i18n.T("cmd.php.packages.unlink.short"), + Long: i18n.T("cmd.php.packages.unlink.long"), + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } - fmt.Printf("%s Unlinking packages...\n\n", dimStyle.Render("PHP:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.unlink.unlinking")) if err := phppkg.UnlinkPackages(cwd, args); err != nil { - return fmt.Errorf("failed to unlink packages: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.unlink_packages"), err) } - fmt.Printf("\n%s Packages unlinked. Run 'composer update' to remove.\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.packages.unlink.done")) return nil }, } @@ -93,25 +78,21 @@ func addPHPPackagesUnlinkCommand(parent *cobra.Command) { func addPHPPackagesUpdateCommand(parent *cobra.Command) { updateCmd := &cobra.Command{ Use: "update [packages...]", - Short: "Update linked packages", - Long: "Run composer update for linked packages.\n\n" + - "If no packages specified, updates all packages.\n\n" + - "Examples:\n" + - " core php packages update\n" + - " core php packages update vendor/my-package", + Short: i18n.T("cmd.php.packages.update.short"), + Long: i18n.T("cmd.php.packages.update.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } - fmt.Printf("%s Updating packages...\n\n", dimStyle.Render("PHP:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.update.updating")) if err := phppkg.UpdatePackages(cwd, args); err != nil { - return fmt.Errorf("composer update failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.update_packages"), err) } - fmt.Printf("\n%s Packages updated\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.packages.update.done")) return nil }, } @@ -122,31 +103,30 @@ func addPHPPackagesUpdateCommand(parent *cobra.Command) { func addPHPPackagesListCommand(parent *cobra.Command) { listCmd := &cobra.Command{ Use: "list", - Short: "List linked packages", - Long: "List all locally linked packages.\n\n" + - "Shows package name, path, and version for each linked package.", + Short: i18n.T("cmd.php.packages.list.short"), + Long: i18n.T("cmd.php.packages.list.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } packages, err := phppkg.ListLinkedPackages(cwd) if err != nil { - return fmt.Errorf("failed to list packages: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.list_packages"), err) } if len(packages) == 0 { - fmt.Printf("%s No linked packages found\n", dimStyle.Render("PHP:")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.list.none_found")) return nil } - fmt.Printf("%s Linked packages:\n\n", dimStyle.Render("PHP:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.list.linked")) for _, pkg := range packages { name := pkg.Name if name == "" { - name = "(unknown)" + name = i18n.T("cmd.php.packages.list.unknown") } version := pkg.Version if version == "" { @@ -154,8 +134,8 @@ func addPHPPackagesListCommand(parent *cobra.Command) { } fmt.Printf(" %s %s\n", successStyle.Render("*"), name) - fmt.Printf(" %s %s\n", dimStyle.Render("Path:"), pkg.Path) - fmt.Printf(" %s %s\n", dimStyle.Render("Version:"), version) + fmt.Printf(" %s %s\n", dimStyle.Render(i18n.T("cmd.php.packages.list.path")), pkg.Path) + fmt.Printf(" %s %s\n", dimStyle.Render(i18n.T("cmd.php.packages.list.version")), version) fmt.Println() } diff --git a/cmd/php/php_quality.go b/cmd/php/php_quality.go index 84c54b2..b385ec4 100644 --- a/cmd/php/php_quality.go +++ b/cmd/php/php_quality.go @@ -10,6 +10,7 @@ import ( "time" "github.com/charmbracelet/lipgloss" + "github.com/host-uk/core/pkg/i18n" phppkg "github.com/host-uk/core/pkg/php" "github.com/spf13/cobra" ) @@ -24,27 +25,21 @@ var ( func addPHPTestCommand(parent *cobra.Command) { testCmd := &cobra.Command{ Use: "test", - Short: "Run PHP tests (PHPUnit/Pest)", - Long: "Run PHP tests using PHPUnit or Pest.\n\n" + - "Auto-detects Pest if tests/Pest.php exists, otherwise uses PHPUnit.\n\n" + - "Examples:\n" + - " core php test # Run all tests\n" + - " core php test --parallel # Run tests in parallel\n" + - " core php test --coverage # Run with coverage\n" + - " core php test --filter UserTest # Filter by test name", + Short: i18n.T("cmd.php.test.short"), + Long: i18n.T("cmd.php.test.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } // Detect test runner runner := phppkg.DetectTestRunner(cwd) - fmt.Printf("%s Running tests with %s\n\n", dimStyle.Render("PHP:"), runner) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.test.running", map[string]interface{}{"Runner": runner})) ctx := context.Background() @@ -61,17 +56,17 @@ func addPHPTestCommand(parent *cobra.Command) { } if err := phppkg.RunTests(ctx, opts); err != nil { - return fmt.Errorf("tests failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.tests_failed"), err) } return nil }, } - testCmd.Flags().BoolVar(&testParallel, "parallel", false, "Run tests in parallel") - testCmd.Flags().BoolVar(&testCoverage, "coverage", false, "Generate code coverage") - testCmd.Flags().StringVar(&testFilter, "filter", "", "Filter tests by name pattern") - testCmd.Flags().StringVar(&testGroup, "group", "", "Run only tests in specified group") + testCmd.Flags().BoolVar(&testParallel, "parallel", false, i18n.T("cmd.php.test.flag.parallel")) + testCmd.Flags().BoolVar(&testCoverage, "coverage", false, i18n.T("cmd.php.test.flag.coverage")) + testCmd.Flags().StringVar(&testFilter, "filter", "", i18n.T("cmd.php.test.flag.filter")) + testCmd.Flags().StringVar(&testGroup, "group", "", i18n.T("cmd.php.test.flag.group")) parent.AddCommand(testCmd) } @@ -84,33 +79,31 @@ var ( func addPHPFmtCommand(parent *cobra.Command) { fmtCmd := &cobra.Command{ Use: "fmt [paths...]", - Short: "Format PHP code with Laravel Pint", - Long: "Format PHP code using Laravel Pint.\n\n" + - "Examples:\n" + - " core php fmt # Check formatting (dry-run)\n" + - " core php fmt --fix # Auto-fix formatting issues\n" + - " core php fmt --diff # Show diff of changes", + Short: i18n.T("cmd.php.fmt.short"), + Long: i18n.T("cmd.php.fmt.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } // Detect formatter formatter, found := phppkg.DetectFormatter(cwd) if !found { - return fmt.Errorf("no formatter found (install Laravel Pint: composer require laravel/pint --dev)") + return fmt.Errorf(i18n.T("cmd.php.fmt.no_formatter")) } - action := "Checking" + var msg string if fmtFix { - action = "Formatting" + msg = i18n.T("cmd.php.fmt.formatting", map[string]interface{}{"Formatter": formatter}) + } else { + msg = i18n.T("cmd.php.fmt.checking", map[string]interface{}{"Formatter": formatter}) } - fmt.Printf("%s %s code with %s\n\n", dimStyle.Render("PHP:"), action, formatter) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), msg) ctx := context.Background() @@ -128,23 +121,23 @@ func addPHPFmtCommand(parent *cobra.Command) { if err := phppkg.Format(ctx, opts); err != nil { if fmtFix { - return fmt.Errorf("formatting failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.fmt_failed"), err) } - return fmt.Errorf("formatting issues found: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.fmt_issues"), err) } if fmtFix { - fmt.Printf("\n%s Code formatted successfully\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.fmt.success")) } else { - fmt.Printf("\n%s No formatting issues found\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.fmt.no_issues")) } return nil }, } - fmtCmd.Flags().BoolVar(&fmtFix, "fix", false, "Auto-fix formatting issues") - fmtCmd.Flags().BoolVar(&fmtDiff, "diff", false, "Show diff of changes") + fmtCmd.Flags().BoolVar(&fmtFix, "fix", false, i18n.T("cmd.php.fmt.flag.fix")) + fmtCmd.Flags().BoolVar(&fmtDiff, "diff", false, i18n.T("cmd.php.fmt.flag.diff")) parent.AddCommand(fmtCmd) } @@ -157,30 +150,25 @@ var ( func addPHPAnalyseCommand(parent *cobra.Command) { analyseCmd := &cobra.Command{ Use: "analyse [paths...]", - Short: "Run PHPStan static analysis", - Long: "Run PHPStan or Larastan static analysis.\n\n" + - "Auto-detects Larastan if installed, otherwise uses PHPStan.\n\n" + - "Examples:\n" + - " core php analyse # Run analysis\n" + - " core php analyse --level 9 # Run at max strictness\n" + - " core php analyse --memory 2G # Increase memory limit", + Short: i18n.T("cmd.php.analyse.short"), + Long: i18n.T("cmd.php.analyse.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } // Detect analyser analyser, found := phppkg.DetectAnalyser(cwd) if !found { - return fmt.Errorf("no static analyser found (install PHPStan: composer require phpstan/phpstan --dev)") + return fmt.Errorf(i18n.T("cmd.php.analyse.no_analyser")) } - fmt.Printf("%s Running static analysis with %s\n\n", dimStyle.Render("PHP:"), analyser) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.analyse.running", map[string]interface{}{"Analyser": analyser})) ctx := context.Background() @@ -197,16 +185,16 @@ func addPHPAnalyseCommand(parent *cobra.Command) { } if err := phppkg.Analyse(ctx, opts); err != nil { - return fmt.Errorf("analysis found issues: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.analysis_issues"), err) } - fmt.Printf("\n%s No issues found\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.analyse.no_issues")) return nil }, } - analyseCmd.Flags().IntVar(&analyseLevel, "level", 0, "PHPStan analysis level (0-9)") - analyseCmd.Flags().StringVar(&analyseMemory, "memory", "", "Memory limit (e.g., 2G)") + analyseCmd.Flags().IntVar(&analyseLevel, "level", 0, i18n.T("cmd.php.analyse.flag.level")) + analyseCmd.Flags().StringVar(&analyseMemory, "memory", "", i18n.T("cmd.php.analyse.flag.memory")) parent.AddCommand(analyseCmd) } @@ -225,39 +213,34 @@ var ( func addPHPPsalmCommand(parent *cobra.Command) { psalmCmd := &cobra.Command{ Use: "psalm", - Short: "Run Psalm static analysis", - Long: "Run Psalm deep static analysis with Laravel plugin support.\n\n" + - "Psalm provides deeper type inference than PHPStan and catches\n" + - "different classes of bugs. Both should be run for best coverage.\n\n" + - "Examples:\n" + - " core php psalm # Run analysis\n" + - " core php psalm --fix # Auto-fix issues where possible\n" + - " core php psalm --level 3 # Run at specific level (1-8)\n" + - " core php psalm --baseline # Generate baseline file", + Short: i18n.T("cmd.php.psalm.short"), + Long: i18n.T("cmd.php.psalm.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } // Check if Psalm is available _, found := phppkg.DetectPsalm(cwd) if !found { - fmt.Printf("%s Psalm not found\n\n", errorStyle.Render("Error:")) - fmt.Printf("%s composer require --dev vimeo/psalm\n", dimStyle.Render("Install:")) - fmt.Printf("%s ./vendor/bin/psalm --init\n", dimStyle.Render("Setup:")) - return fmt.Errorf("psalm not installed") + fmt.Printf("%s %s\n\n", errorStyle.Render(i18n.T("cmd.php.label.error")), i18n.T("cmd.php.psalm.not_found")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.install")), i18n.T("cmd.php.psalm.install")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.setup")), i18n.T("cmd.php.psalm.setup")) + return fmt.Errorf(i18n.T("cmd.php.error.psalm_not_installed")) } - action := "Analysing" + var msg string if psalmFix { - action = "Analysing and fixing" + msg = i18n.T("cmd.php.psalm.analysing_fixing") + } else { + msg = i18n.T("cmd.php.psalm.analysing") } - fmt.Printf("%s %s code with Psalm\n\n", dimStyle.Render("Psalm:"), action) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.psalm")), msg) ctx := context.Background() @@ -271,18 +254,18 @@ func addPHPPsalmCommand(parent *cobra.Command) { } if err := phppkg.RunPsalm(ctx, opts); err != nil { - return fmt.Errorf("psalm found issues: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.psalm_issues"), err) } - fmt.Printf("\n%s No issues found\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.psalm.no_issues")) return nil }, } - psalmCmd.Flags().IntVar(&psalmLevel, "level", 0, "Error level (1=strictest, 8=most lenient)") - psalmCmd.Flags().BoolVar(&psalmFix, "fix", false, "Auto-fix issues where possible") - psalmCmd.Flags().BoolVar(&psalmBaseline, "baseline", false, "Generate/update baseline file") - psalmCmd.Flags().BoolVar(&psalmShowInfo, "show-info", false, "Show info-level issues") + psalmCmd.Flags().IntVar(&psalmLevel, "level", 0, i18n.T("cmd.php.psalm.flag.level")) + psalmCmd.Flags().BoolVar(&psalmFix, "fix", false, i18n.T("cmd.php.psalm.flag.fix")) + psalmCmd.Flags().BoolVar(&psalmBaseline, "baseline", false, i18n.T("cmd.php.psalm.flag.baseline")) + psalmCmd.Flags().BoolVar(&psalmShowInfo, "show-info", false, i18n.T("cmd.php.psalm.flag.show_info")) parent.AddCommand(psalmCmd) } @@ -295,24 +278,19 @@ var ( func addPHPAuditCommand(parent *cobra.Command) { auditCmd := &cobra.Command{ Use: "audit", - Short: "Security audit for dependencies", - Long: "Check PHP and JavaScript dependencies for known vulnerabilities.\n\n" + - "Runs composer audit and npm audit (if package.json exists).\n\n" + - "Examples:\n" + - " core php audit # Check all dependencies\n" + - " core php audit --json # Output as JSON\n" + - " core php audit --fix # Auto-fix where possible (npm only)", + Short: i18n.T("cmd.php.audit.short"), + Long: i18n.T("cmd.php.audit.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } - fmt.Printf("%s Scanning dependencies for vulnerabilities\n\n", dimStyle.Render("Audit:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.audit")), i18n.T("cmd.php.audit.scanning")) ctx := context.Background() @@ -323,7 +301,7 @@ func addPHPAuditCommand(parent *cobra.Command) { Output: os.Stdout, }) if err != nil { - return fmt.Errorf("audit failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.audit_failed"), err) } // Print results @@ -332,15 +310,15 @@ func addPHPAuditCommand(parent *cobra.Command) { for _, result := range results { icon := successStyle.Render("✓") - status := successStyle.Render("secure") + status := successStyle.Render(i18n.T("cmd.php.audit.secure")) if result.Error != nil { icon = errorStyle.Render("✗") - status = errorStyle.Render("error") + status = errorStyle.Render(i18n.T("cmd.php.audit.error")) hasErrors = true } else if result.Vulnerabilities > 0 { icon = errorStyle.Render("✗") - status = errorStyle.Render(fmt.Sprintf("%d vulnerabilities", result.Vulnerabilities)) + status = errorStyle.Render(i18n.T("cmd.php.audit.vulnerabilities", map[string]interface{}{"Count": result.Vulnerabilities})) totalVulns += result.Vulnerabilities } @@ -363,22 +341,22 @@ func addPHPAuditCommand(parent *cobra.Command) { fmt.Println() if totalVulns > 0 { - fmt.Printf("%s Found %d vulnerabilities across dependencies\n", errorStyle.Render("Warning:"), totalVulns) - fmt.Printf("%s composer update && npm update\n", dimStyle.Render("Fix:")) - return fmt.Errorf("vulnerabilities found") + fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.php.label.warning")), i18n.T("cmd.php.audit.found_vulns", map[string]interface{}{"Count": totalVulns})) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.fix")), i18n.T("cmd.php.audit.fix_hint")) + return fmt.Errorf(i18n.T("cmd.php.error.vulns_found")) } if hasErrors { - return fmt.Errorf("audit completed with errors") + return fmt.Errorf(i18n.T("cmd.php.audit.completed_errors")) } - fmt.Printf("%s All dependencies are secure\n", successStyle.Render("Done:")) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.audit.all_secure")) return nil }, } - auditCmd.Flags().BoolVar(&auditJSONOutput, "json", false, "Output in JSON format") - auditCmd.Flags().BoolVar(&auditFix, "fix", false, "Auto-fix vulnerabilities (npm only)") + auditCmd.Flags().BoolVar(&auditJSONOutput, "json", false, i18n.T("cmd.php.audit.flag.json")) + auditCmd.Flags().BoolVar(&auditFix, "fix", false, i18n.T("cmd.php.audit.flag.fix")) parent.AddCommand(auditCmd) } @@ -393,25 +371,19 @@ var ( func addPHPSecurityCommand(parent *cobra.Command) { securityCmd := &cobra.Command{ Use: "security", - Short: "Security vulnerability scanning", - Long: "Scan for security vulnerabilities in configuration and code.\n\n" + - "Checks environment config, file permissions, code patterns,\n" + - "and runs security-focused static analysis.\n\n" + - "Examples:\n" + - " core php security # Run all checks\n" + - " core php security --severity=high # Only high+ severity\n" + - " core php security --json # JSON output", + Short: i18n.T("cmd.php.security.short"), + Long: i18n.T("cmd.php.security.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } - fmt.Printf("%s Running security checks\n\n", dimStyle.Render("Security:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.security")), i18n.T("cmd.php.security.running")) ctx := context.Background() @@ -424,7 +396,7 @@ func addPHPSecurityCommand(parent *cobra.Command) { Output: os.Stdout, }) if err != nil { - return fmt.Errorf("security check failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.security_failed"), err) } // Print results by category @@ -436,7 +408,7 @@ func addPHPSecurityCommand(parent *cobra.Command) { fmt.Println() } currentCategory = category - fmt.Printf(" %s\n", dimStyle.Render(strings.ToUpper(category)+" CHECKS:")) + fmt.Printf(" %s\n", dimStyle.Render(strings.ToUpper(category)+i18n.T("cmd.php.security.checks_suffix"))) } icon := successStyle.Render("✓") @@ -448,7 +420,7 @@ func addPHPSecurityCommand(parent *cobra.Command) { if !check.Passed && check.Message != "" { fmt.Printf(" %s\n", dimStyle.Render(check.Message)) if check.Fix != "" { - fmt.Printf(" %s %s\n", dimStyle.Render("Fix:"), check.Fix) + fmt.Printf(" %s %s\n", dimStyle.Render(i18n.T("cmd.php.security.fix_label")), check.Fix) } } } @@ -456,34 +428,34 @@ func addPHPSecurityCommand(parent *cobra.Command) { fmt.Println() // Print summary - fmt.Printf("%s Security scan complete\n", dimStyle.Render("Summary:")) - fmt.Printf(" %s %d/%d\n", dimStyle.Render("Passed:"), result.Summary.Passed, result.Summary.Total) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.summary")), i18n.T("cmd.php.security.summary")) + fmt.Printf(" %s %d/%d\n", dimStyle.Render(i18n.T("cmd.php.security.passed")), result.Summary.Passed, result.Summary.Total) if result.Summary.Critical > 0 { - fmt.Printf(" %s %d\n", phpSecurityCriticalStyle.Render("Critical:"), result.Summary.Critical) + fmt.Printf(" %s %d\n", phpSecurityCriticalStyle.Render(i18n.T("cmd.php.security.critical")), result.Summary.Critical) } if result.Summary.High > 0 { - fmt.Printf(" %s %d\n", phpSecurityHighStyle.Render("High:"), result.Summary.High) + fmt.Printf(" %s %d\n", phpSecurityHighStyle.Render(i18n.T("cmd.php.security.high")), result.Summary.High) } if result.Summary.Medium > 0 { - fmt.Printf(" %s %d\n", phpSecurityMediumStyle.Render("Medium:"), result.Summary.Medium) + fmt.Printf(" %s %d\n", phpSecurityMediumStyle.Render(i18n.T("cmd.php.security.medium")), result.Summary.Medium) } if result.Summary.Low > 0 { - fmt.Printf(" %s %d\n", phpSecurityLowStyle.Render("Low:"), result.Summary.Low) + fmt.Printf(" %s %d\n", phpSecurityLowStyle.Render(i18n.T("cmd.php.security.low")), result.Summary.Low) } if result.Summary.Critical > 0 || result.Summary.High > 0 { - return fmt.Errorf("critical or high severity issues found") + return fmt.Errorf(i18n.T("cmd.php.error.critical_high_issues")) } return nil }, } - securityCmd.Flags().StringVar(&securitySeverity, "severity", "", "Minimum severity (critical, high, medium, low)") - securityCmd.Flags().BoolVar(&securityJSONOutput, "json", false, "Output in JSON format") - securityCmd.Flags().BoolVar(&securitySarif, "sarif", false, "Output in SARIF format (for GitHub Security)") - securityCmd.Flags().StringVar(&securityURL, "url", "", "URL to check HTTP headers (optional)") + securityCmd.Flags().StringVar(&securitySeverity, "severity", "", i18n.T("cmd.php.security.flag.severity")) + securityCmd.Flags().BoolVar(&securityJSONOutput, "json", false, i18n.T("cmd.php.security.flag.json")) + securityCmd.Flags().BoolVar(&securitySarif, "sarif", false, i18n.T("cmd.php.security.flag.sarif")) + securityCmd.Flags().StringVar(&securityURL, "url", "", i18n.T("cmd.php.security.flag.url")) parent.AddCommand(securityCmd) } @@ -497,25 +469,16 @@ var ( func addPHPQACommand(parent *cobra.Command) { qaCmd := &cobra.Command{ Use: "qa", - Short: "Run full QA pipeline", - Long: "Run the complete quality assurance pipeline.\n\n" + - "Stages:\n" + - " quick: Security audit, code style, PHPStan\n" + - " standard: Psalm, tests\n" + - " full: Rector dry-run, mutation testing (slow)\n\n" + - "Examples:\n" + - " core php qa # Run quick + standard stages\n" + - " core php qa --quick # Only quick checks\n" + - " core php qa --full # All stages including slow ones\n" + - " core php qa --fix # Auto-fix where possible", + Short: i18n.T("cmd.php.qa.short"), + Long: i18n.T("cmd.php.qa.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } // Determine stages @@ -532,18 +495,18 @@ func addPHPQACommand(parent *cobra.Command) { for i, s := range stages { stageNames[i] = string(s) } - fmt.Printf("%s Running QA pipeline (%s)\n\n", dimStyle.Render("QA:"), strings.Join(stageNames, " → ")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.qa")), i18n.T("cmd.php.qa.running", map[string]interface{}{"Stages": strings.Join(stageNames, " -> ")})) ctx := context.Background() var allPassed = true var results []phppkg.QACheckResult for _, stage := range stages { - fmt.Printf("%s\n", phpQAStageStyle.Render("═══ "+strings.ToUpper(string(stage))+" STAGE ═══")) + fmt.Printf("%s\n", phpQAStageStyle.Render(i18n.T("cmd.php.qa.stage_prefix")+strings.ToUpper(string(stage))+i18n.T("cmd.php.qa.stage_suffix"))) checks := phppkg.GetQAChecks(cwd, stage) if len(checks) == 0 { - fmt.Printf(" %s\n\n", dimStyle.Render("No checks available")) + fmt.Printf(" %s\n\n", dimStyle.Render(i18n.T("cmd.php.qa.no_checks"))) continue } @@ -553,10 +516,10 @@ func addPHPQACommand(parent *cobra.Command) { results = append(results, result) icon := phpQAPassedStyle.Render("✓") - status := phpQAPassedStyle.Render("passed") + status := phpQAPassedStyle.Render(i18n.T("cmd.php.qa.passed")) if !result.Passed { icon = phpQAFailedStyle.Render("✗") - status = phpQAFailedStyle.Render("failed") + status = phpQAFailedStyle.Render(i18n.T("cmd.php.qa.failed")) allPassed = false } @@ -577,33 +540,33 @@ func addPHPQACommand(parent *cobra.Command) { } if allPassed { - fmt.Printf("%s All checks passed (%d/%d)\n", phpQAPassedStyle.Render("QA PASSED:"), passedCount, len(results)) + fmt.Printf("%s %s\n", phpQAPassedStyle.Render("QA PASSED:"), i18n.T("cmd.php.qa.all_passed", map[string]interface{}{"Passed": passedCount, "Total": len(results)})) return nil } - fmt.Printf("%s Some checks failed (%d/%d passed)\n\n", phpQAFailedStyle.Render("QA FAILED:"), passedCount, len(results)) + fmt.Printf("%s %s\n\n", phpQAFailedStyle.Render("QA FAILED:"), i18n.T("cmd.php.qa.some_failed", map[string]interface{}{"Passed": passedCount, "Total": len(results)})) // Show what needs fixing - fmt.Printf("%s\n", dimStyle.Render("To fix:")) + fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.php.qa.to_fix"))) for _, check := range failedChecks { fixCmd := getQAFixCommand(check.Name, qaFix) issue := check.Output if issue == "" { issue = "issues found" } - fmt.Printf(" %s %s\n", phpQAFailedStyle.Render("•"), check.Name+": "+issue) + fmt.Printf(" %s %s\n", phpQAFailedStyle.Render("*"), check.Name+": "+issue) if fixCmd != "" { - fmt.Printf(" %s %s\n", dimStyle.Render("→"), fixCmd) + fmt.Printf(" %s %s\n", dimStyle.Render("->"), fixCmd) } } - return fmt.Errorf("QA pipeline failed") + return fmt.Errorf(i18n.T("cmd.php.qa.pipeline_failed")) }, } - qaCmd.Flags().BoolVar(&qaQuick, "quick", false, "Only run quick checks") - qaCmd.Flags().BoolVar(&qaFull, "full", false, "Run all stages including slow checks") - qaCmd.Flags().BoolVar(&qaFix, "fix", false, "Auto-fix issues where possible") + qaCmd.Flags().BoolVar(&qaQuick, "quick", false, i18n.T("cmd.php.qa.flag.quick")) + qaCmd.Flags().BoolVar(&qaFull, "full", false, i18n.T("cmd.php.qa.flag.full")) + qaCmd.Flags().BoolVar(&qaFix, "fix", false, i18n.T("cmd.php.qa.flag.fix")) parent.AddCommand(qaCmd) } @@ -611,25 +574,25 @@ func addPHPQACommand(parent *cobra.Command) { func getQAFixCommand(checkName string, fixEnabled bool) string { switch checkName { case "audit": - return "composer update && npm update" + return i18n.T("cmd.php.qa.fix_audit") case "fmt": if fixEnabled { return "" } return "core php fmt --fix" case "analyse": - return "Fix PHPStan errors shown above" + return i18n.T("cmd.php.qa.fix_phpstan") case "psalm": - return "Fix Psalm errors shown above" + return i18n.T("cmd.php.qa.fix_psalm") case "test": - return "Fix failing tests shown above" + return i18n.T("cmd.php.qa.fix_tests") case "rector": if fixEnabled { return "" } return "core php rector --fix" case "infection": - return "Improve test coverage for mutated code" + return i18n.T("cmd.php.qa.fix_infection") } return "" } @@ -662,42 +625,42 @@ func runQACheck(ctx context.Context, dir string, checkName string, fix bool) php err := phppkg.Format(ctx, phppkg.FormatOptions{Dir: dir, Fix: fix, Output: io.Discard}) result.Passed = err == nil if err != nil { - result.Output = "Code style issues found" + result.Output = i18n.T("cmd.php.qa.issue_style") } case "analyse": err := phppkg.Analyse(ctx, phppkg.AnalyseOptions{Dir: dir, Output: &buf}) result.Passed = err == nil if err != nil { - result.Output = "Static analysis errors" + result.Output = i18n.T("cmd.php.qa.issue_analysis") } case "psalm": err := phppkg.RunPsalm(ctx, phppkg.PsalmOptions{Dir: dir, Fix: fix, Output: io.Discard}) result.Passed = err == nil if err != nil { - result.Output = "Type errors found" + result.Output = i18n.T("cmd.php.qa.issue_types") } case "test": err := phppkg.RunTests(ctx, phppkg.TestOptions{Dir: dir, Output: io.Discard}) result.Passed = err == nil if err != nil { - result.Output = "Test failures" + result.Output = i18n.T("cmd.php.qa.issue_tests") } case "rector": err := phppkg.RunRector(ctx, phppkg.RectorOptions{Dir: dir, Fix: fix, Output: io.Discard}) result.Passed = err == nil if err != nil { - result.Output = "Code improvements available" + result.Output = i18n.T("cmd.php.qa.issue_rector") } case "infection": err := phppkg.RunInfection(ctx, phppkg.InfectionOptions{Dir: dir, Output: io.Discard}) result.Passed = err == nil if err != nil { - result.Output = "Mutation score below threshold" + result.Output = i18n.T("cmd.php.qa.issue_mutation") } } @@ -714,37 +677,33 @@ var ( func addPHPRectorCommand(parent *cobra.Command) { rectorCmd := &cobra.Command{ Use: "rector", - Short: "Automated code refactoring", - Long: "Run Rector for automated code improvements and PHP upgrades.\n\n" + - "Rector can automatically upgrade PHP syntax, improve code quality,\n" + - "and apply framework-specific refactorings.\n\n" + - "Examples:\n" + - " core php rector # Dry-run (show changes)\n" + - " core php rector --fix # Apply changes\n" + - " core php rector --diff # Show detailed diff", + Short: i18n.T("cmd.php.rector.short"), + Long: i18n.T("cmd.php.rector.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } // Check if Rector is available if !phppkg.DetectRector(cwd) { - fmt.Printf("%s Rector not found\n\n", errorStyle.Render("Error:")) - fmt.Printf("%s composer require --dev rector/rector\n", dimStyle.Render("Install:")) - fmt.Printf("%s ./vendor/bin/rector init\n", dimStyle.Render("Setup:")) - return fmt.Errorf("rector not installed") + fmt.Printf("%s %s\n\n", errorStyle.Render(i18n.T("cmd.php.label.error")), i18n.T("cmd.php.rector.not_found")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.install")), i18n.T("cmd.php.rector.install")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.setup")), i18n.T("cmd.php.rector.setup")) + return fmt.Errorf(i18n.T("cmd.php.error.rector_not_installed")) } - action := "Analysing" + var msg string if rectorFix { - action = "Refactoring" + msg = i18n.T("cmd.php.rector.refactoring") + } else { + msg = i18n.T("cmd.php.rector.analysing") } - fmt.Printf("%s %s code with Rector\n\n", dimStyle.Render("Rector:"), action) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.rector")), msg) ctx := context.Background() @@ -758,25 +717,25 @@ func addPHPRectorCommand(parent *cobra.Command) { if err := phppkg.RunRector(ctx, opts); err != nil { if rectorFix { - return fmt.Errorf("rector failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.rector_failed"), err) } // Dry-run returns non-zero if changes would be made - fmt.Printf("\n%s Changes suggested (use --fix to apply)\n", phpQAWarningStyle.Render("Info:")) + fmt.Printf("\n%s %s\n", phpQAWarningStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.rector.changes_suggested")) return nil } if rectorFix { - fmt.Printf("\n%s Code refactored successfully\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.rector.refactored")) } else { - fmt.Printf("\n%s No changes needed\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.rector.no_changes")) } return nil }, } - rectorCmd.Flags().BoolVar(&rectorFix, "fix", false, "Apply changes (default is dry-run)") - rectorCmd.Flags().BoolVar(&rectorDiff, "diff", false, "Show detailed diff of changes") - rectorCmd.Flags().BoolVar(&rectorClearCache, "clear-cache", false, "Clear Rector cache before running") + rectorCmd.Flags().BoolVar(&rectorFix, "fix", false, i18n.T("cmd.php.rector.flag.fix")) + rectorCmd.Flags().BoolVar(&rectorDiff, "diff", false, i18n.T("cmd.php.rector.flag.diff")) + rectorCmd.Flags().BoolVar(&rectorClearCache, "clear-cache", false, i18n.T("cmd.php.rector.flag.clear_cache")) parent.AddCommand(rectorCmd) } @@ -792,34 +751,27 @@ var ( func addPHPInfectionCommand(parent *cobra.Command) { infectionCmd := &cobra.Command{ Use: "infection", - Short: "Mutation testing for test quality", - Long: "Run Infection mutation testing to measure test suite quality.\n\n" + - "Mutation testing modifies your code and checks if tests catch\n" + - "the changes. High mutation score = high quality tests.\n\n" + - "Warning: This can be slow on large codebases.\n\n" + - "Examples:\n" + - " core php infection # Run mutation testing\n" + - " core php infection --min-msi=70 # Require 70% mutation score\n" + - " core php infection --filter=User # Only test User* files", + Short: i18n.T("cmd.php.infection.short"), + Long: i18n.T("cmd.php.infection.long"), RunE: func(cmd *cobra.Command, args []string) error { cwd, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.working_dir"), err) } if !phppkg.IsPHPProject(cwd) { - return fmt.Errorf("not a PHP project (missing composer.json)") + return fmt.Errorf(i18n.T("cmd.php.error.not_php")) } // Check if Infection is available if !phppkg.DetectInfection(cwd) { - fmt.Printf("%s Infection not found\n\n", errorStyle.Render("Error:")) - fmt.Printf("%s composer require --dev infection/infection\n", dimStyle.Render("Install:")) - return fmt.Errorf("infection not installed") + fmt.Printf("%s %s\n\n", errorStyle.Render(i18n.T("cmd.php.label.error")), i18n.T("cmd.php.infection.not_found")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.install")), i18n.T("cmd.php.infection.install")) + return fmt.Errorf(i18n.T("cmd.php.error.infection_not_installed")) } - fmt.Printf("%s Running mutation testing\n", dimStyle.Render("Infection:")) - fmt.Printf("%s This may take a while...\n\n", dimStyle.Render("Note:")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.infection")), i18n.T("cmd.php.infection.running")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.infection.note")) ctx := context.Background() @@ -834,19 +786,19 @@ func addPHPInfectionCommand(parent *cobra.Command) { } if err := phppkg.RunInfection(ctx, opts); err != nil { - return fmt.Errorf("mutation testing failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.infection_failed"), err) } - fmt.Printf("\n%s Mutation testing complete\n", successStyle.Render("Done:")) + fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("cmd.php.label.done")), i18n.T("cmd.php.infection.complete")) return nil }, } - infectionCmd.Flags().IntVar(&infectionMinMSI, "min-msi", 0, "Minimum mutation score indicator (0-100, default: 50)") - infectionCmd.Flags().IntVar(&infectionMinCoveredMSI, "min-covered-msi", 0, "Minimum covered mutation score (0-100, default: 70)") - infectionCmd.Flags().IntVar(&infectionThreads, "threads", 0, "Number of parallel threads (default: 4)") - infectionCmd.Flags().StringVar(&infectionFilter, "filter", "", "Filter files by pattern") - infectionCmd.Flags().BoolVar(&infectionOnlyCovered, "only-covered", false, "Only mutate covered code") + infectionCmd.Flags().IntVar(&infectionMinMSI, "min-msi", 0, i18n.T("cmd.php.infection.flag.min_msi")) + infectionCmd.Flags().IntVar(&infectionMinCoveredMSI, "min-covered-msi", 0, i18n.T("cmd.php.infection.flag.min_covered_msi")) + infectionCmd.Flags().IntVar(&infectionThreads, "threads", 0, i18n.T("cmd.php.infection.flag.threads")) + infectionCmd.Flags().StringVar(&infectionFilter, "filter", "", i18n.T("cmd.php.infection.flag.filter")) + infectionCmd.Flags().BoolVar(&infectionOnlyCovered, "only-covered", false, i18n.T("cmd.php.infection.flag.only_covered")) parent.AddCommand(infectionCmd) } diff --git a/cmd/pkg/pkg.go b/cmd/pkg/pkg.go index 2f576c7..6bbe9a8 100644 --- a/cmd/pkg/pkg.go +++ b/cmd/pkg/pkg.go @@ -3,6 +3,7 @@ package pkg import ( "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -20,14 +21,8 @@ var ( func AddPkgCommands(root *cobra.Command) { pkgCmd := &cobra.Command{ Use: "pkg", - Short: "Package management for core-* repos", - Long: "Manage host-uk/core-* packages and repositories.\n\n" + - "Commands:\n" + - " search Search GitHub for packages\n" + - " install Clone a package from GitHub\n" + - " list List installed packages\n" + - " update Update installed packages\n" + - " outdated Check for outdated packages", + Short: i18n.T("cmd.pkg.short"), + Long: i18n.T("cmd.pkg.long"), } root.AddCommand(pkgCmd) diff --git a/cmd/pkg/pkg_install.go b/cmd/pkg/pkg_install.go index 71c9906..7ff0153 100644 --- a/cmd/pkg/pkg_install.go +++ b/cmd/pkg/pkg_install.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -20,22 +21,18 @@ var ( func addPkgInstallCommand(parent *cobra.Command) { installCmd := &cobra.Command{ Use: "install ", - Short: "Clone a package from GitHub", - Long: "Clones a repository from GitHub.\n\n" + - "Examples:\n" + - " core pkg install host-uk/core-php\n" + - " core pkg install host-uk/core-tenant --dir ./packages\n" + - " core pkg install host-uk/core-admin --add", + Short: i18n.T("cmd.pkg.install.short"), + Long: i18n.T("cmd.pkg.install.long"), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return fmt.Errorf("repository is required (e.g., core pkg install host-uk/core-php)") + return fmt.Errorf(i18n.T("cmd.pkg.error.repo_required")) } return runPkgInstall(args[0], installTargetDir, installAddToReg) }, } - installCmd.Flags().StringVar(&installTargetDir, "dir", "", "Target directory (default: ./packages or current dir)") - installCmd.Flags().BoolVar(&installAddToReg, "add", false, "Add to repos.yaml registry") + installCmd.Flags().StringVar(&installTargetDir, "dir", "", i18n.T("cmd.pkg.install.flag.dir")) + installCmd.Flags().BoolVar(&installAddToReg, "add", false, i18n.T("cmd.pkg.install.flag.add")) parent.AddCommand(installCmd) } @@ -46,7 +43,7 @@ func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error { // Parse org/repo parts := strings.Split(repoArg, "/") if len(parts) != 2 { - return fmt.Errorf("invalid repo format: use org/repo (e.g., host-uk/core-php)") + return fmt.Errorf(i18n.T("cmd.pkg.error.invalid_repo_format")) } org, repoName := parts[0], parts[1] @@ -76,19 +73,19 @@ func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error { repoPath := filepath.Join(targetDir, repoName) if _, err := os.Stat(filepath.Join(repoPath, ".git")); err == nil { - fmt.Printf("%s %s already exists at %s\n", dimStyle.Render("Skip:"), repoName, repoPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.pkg.install.skip_label")), i18n.T("cmd.pkg.install.already_exists", map[string]string{"Name": repoName, "Path": repoPath})) return nil } if err := os.MkdirAll(targetDir, 0755); err != nil { - return fmt.Errorf("failed to create directory: %w", err) + return fmt.Errorf(i18n.T("cmd.pkg.error.create_directory"), err) } - fmt.Printf("%s %s/%s\n", dimStyle.Render("Installing:"), org, repoName) - fmt.Printf("%s %s\n", dimStyle.Render("Target:"), repoPath) + fmt.Printf("%s %s/%s\n", dimStyle.Render(i18n.T("cmd.pkg.install.installing_label")), org, repoName) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.pkg.install.target_label")), repoPath) fmt.Println() - fmt.Printf(" %s... ", dimStyle.Render("Cloning")) + fmt.Printf(" %s... ", dimStyle.Render(i18n.T("cmd.pkg.install.cloning"))) err := gitClone(ctx, org, repoName, repoPath) if err != nil { fmt.Printf("%s\n", errorStyle.Render("✗ "+err.Error())) @@ -98,14 +95,14 @@ func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error { if addToRegistry { if err := addToRegistryFile(org, repoName); err != nil { - fmt.Printf(" %s add to registry: %s\n", errorStyle.Render("✗"), err) + fmt.Printf(" %s %s: %s\n", errorStyle.Render("✗"), i18n.T("cmd.pkg.install.add_to_registry"), err) } else { - fmt.Printf(" %s added to repos.yaml\n", successStyle.Render("✓")) + fmt.Printf(" %s %s\n", successStyle.Render("✓"), i18n.T("cmd.pkg.install.added_to_registry")) } } fmt.Println() - fmt.Printf("%s Installed %s\n", successStyle.Render("Done:"), repoName) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.pkg.install.done_label")), i18n.T("cmd.pkg.install.installed", map[string]string{"Name": repoName})) return nil } @@ -113,7 +110,7 @@ func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error { func addToRegistryFile(org, repoName string) error { regPath, err := repos.FindRegistry() if err != nil { - return fmt.Errorf("no repos.yaml found") + return fmt.Errorf(i18n.T("cmd.pkg.error.no_repos_yaml")) } reg, err := repos.LoadRegistry(regPath) diff --git a/cmd/pkg/pkg_manage.go b/cmd/pkg/pkg_manage.go index 37e11c9..3d1cd0b 100644 --- a/cmd/pkg/pkg_manage.go +++ b/cmd/pkg/pkg_manage.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -15,11 +16,8 @@ import ( func addPkgListCommand(parent *cobra.Command) { listCmd := &cobra.Command{ Use: "list", - Short: "List installed packages", - Long: "Lists all packages in the current workspace.\n\n" + - "Reads from repos.yaml or scans for git repositories.\n\n" + - "Examples:\n" + - " core pkg list", + Short: i18n.T("cmd.pkg.list.short"), + Long: i18n.T("cmd.pkg.list.long"), RunE: func(cmd *cobra.Command, args []string) error { return runPkgList() }, @@ -31,12 +29,12 @@ func addPkgListCommand(parent *cobra.Command) { func runPkgList() error { regPath, err := repos.FindRegistry() if err != nil { - return fmt.Errorf("no repos.yaml found - run from workspace directory") + return fmt.Errorf(i18n.T("cmd.pkg.error.no_repos_yaml_workspace")) } reg, err := repos.LoadRegistry(regPath) if err != nil { - return fmt.Errorf("failed to load registry: %w", err) + return fmt.Errorf(i18n.T("cmd.pkg.error.load_registry"), err) } basePath := reg.BasePath @@ -49,11 +47,11 @@ func runPkgList() error { allRepos := reg.List() if len(allRepos) == 0 { - fmt.Println("No packages in registry.") + fmt.Println(i18n.T("cmd.pkg.list.no_packages")) return nil } - fmt.Printf("%s\n\n", repoNameStyle.Render("Installed Packages")) + fmt.Printf("%s\n\n", repoNameStyle.Render(i18n.T("cmd.pkg.list.title"))) var installed, missing int for _, r := range allRepos { @@ -76,7 +74,7 @@ func runPkgList() error { desc = desc[:37] + "..." } if desc == "" { - desc = dimStyle.Render("(no description)") + desc = dimStyle.Render(i18n.T("cmd.pkg.no_description")) } fmt.Printf(" %s %s\n", status, repoNameStyle.Render(r.Name)) @@ -84,10 +82,10 @@ func runPkgList() error { } fmt.Println() - fmt.Printf("%s %d installed, %d missing\n", dimStyle.Render("Total:"), installed, missing) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.pkg.list.total_label")), i18n.T("cmd.pkg.list.summary", map[string]int{"Installed": installed, "Missing": missing})) if missing > 0 { - fmt.Printf("\nInstall missing: %s\n", dimStyle.Render("core setup")) + fmt.Printf("\n%s %s\n", i18n.T("cmd.pkg.list.install_missing"), dimStyle.Render("core setup")) } return nil @@ -99,20 +97,17 @@ var updateAll bool func addPkgUpdateCommand(parent *cobra.Command) { updateCmd := &cobra.Command{ Use: "update [packages...]", - Short: "Update installed packages", - Long: "Pulls latest changes for installed packages.\n\n" + - "Examples:\n" + - " core pkg update core-php # Update specific package\n" + - " core pkg update --all # Update all packages", + Short: i18n.T("cmd.pkg.update.short"), + Long: i18n.T("cmd.pkg.update.long"), RunE: func(cmd *cobra.Command, args []string) error { if !updateAll && len(args) == 0 { - return fmt.Errorf("specify package name or use --all") + return fmt.Errorf(i18n.T("cmd.pkg.error.specify_package")) } return runPkgUpdate(args, updateAll) }, } - updateCmd.Flags().BoolVar(&updateAll, "all", false, "Update all packages") + updateCmd.Flags().BoolVar(&updateAll, "all", false, i18n.T("cmd.pkg.update.flag.all")) parent.AddCommand(updateCmd) } @@ -120,12 +115,12 @@ func addPkgUpdateCommand(parent *cobra.Command) { func runPkgUpdate(packages []string, all bool) error { regPath, err := repos.FindRegistry() if err != nil { - return fmt.Errorf("no repos.yaml found") + return fmt.Errorf(i18n.T("cmd.pkg.error.no_repos_yaml")) } reg, err := repos.LoadRegistry(regPath) if err != nil { - return fmt.Errorf("failed to load registry: %w", err) + return fmt.Errorf(i18n.T("cmd.pkg.error.load_registry"), err) } basePath := reg.BasePath @@ -145,14 +140,14 @@ func runPkgUpdate(packages []string, all bool) error { toUpdate = packages } - fmt.Printf("%s Updating %d package(s)\n\n", dimStyle.Render("Update:"), len(toUpdate)) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.pkg.update.update_label")), i18n.T("cmd.pkg.update.updating", map[string]int{"Count": len(toUpdate)})) var updated, skipped, failed int for _, name := range toUpdate { repoPath := filepath.Join(basePath, name) if _, err := os.Stat(filepath.Join(repoPath, ".git")); os.IsNotExist(err) { - fmt.Printf(" %s %s (not installed)\n", dimStyle.Render("○"), name) + fmt.Printf(" %s %s (%s)\n", dimStyle.Render("○"), name, i18n.T("cmd.pkg.update.not_installed")) skipped++ continue } @@ -169,7 +164,7 @@ func runPkgUpdate(packages []string, all bool) error { } if strings.Contains(string(output), "Already up to date") { - fmt.Printf("%s\n", dimStyle.Render("up to date")) + fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.pkg.update.up_to_date"))) } else { fmt.Printf("%s\n", successStyle.Render("✓")) } @@ -177,8 +172,8 @@ func runPkgUpdate(packages []string, all bool) error { } fmt.Println() - fmt.Printf("%s %d updated, %d skipped, %d failed\n", - dimStyle.Render("Done:"), updated, skipped, failed) + fmt.Printf("%s %s\n", + dimStyle.Render(i18n.T("cmd.pkg.update.done_label")), i18n.T("cmd.pkg.update.summary", map[string]int{"Updated": updated, "Skipped": skipped, "Failed": failed})) return nil } @@ -187,10 +182,8 @@ func runPkgUpdate(packages []string, all bool) error { func addPkgOutdatedCommand(parent *cobra.Command) { outdatedCmd := &cobra.Command{ Use: "outdated", - Short: "Check for outdated packages", - Long: "Checks which packages have unpulled commits.\n\n" + - "Examples:\n" + - " core pkg outdated", + Short: i18n.T("cmd.pkg.outdated.short"), + Long: i18n.T("cmd.pkg.outdated.long"), RunE: func(cmd *cobra.Command, args []string) error { return runPkgOutdated() }, @@ -202,12 +195,12 @@ func addPkgOutdatedCommand(parent *cobra.Command) { func runPkgOutdated() error { regPath, err := repos.FindRegistry() if err != nil { - return fmt.Errorf("no repos.yaml found") + return fmt.Errorf(i18n.T("cmd.pkg.error.no_repos_yaml")) } reg, err := repos.LoadRegistry(regPath) if err != nil { - return fmt.Errorf("failed to load registry: %w", err) + return fmt.Errorf(i18n.T("cmd.pkg.error.load_registry"), err) } basePath := reg.BasePath @@ -218,7 +211,7 @@ func runPkgOutdated() error { basePath = filepath.Join(filepath.Dir(regPath), basePath) } - fmt.Printf("%s Checking for updates...\n\n", dimStyle.Render("Outdated:")) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.pkg.outdated.outdated_label")), i18n.T("cmd.pkg.outdated.checking")) var outdated, upToDate, notInstalled int @@ -242,8 +235,8 @@ func runPkgOutdated() error { count := strings.TrimSpace(string(output)) if count != "0" { - fmt.Printf(" %s %s (%s commits behind)\n", - errorStyle.Render("↓"), repoNameStyle.Render(r.Name), count) + fmt.Printf(" %s %s (%s)\n", + errorStyle.Render("↓"), repoNameStyle.Render(r.Name), i18n.T("cmd.pkg.outdated.commits_behind", map[string]string{"Count": count})) outdated++ } else { upToDate++ @@ -252,11 +245,11 @@ func runPkgOutdated() error { fmt.Println() if outdated == 0 { - fmt.Printf("%s All packages up to date\n", successStyle.Render("Done:")) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.pkg.outdated.done_label")), i18n.T("cmd.pkg.outdated.all_up_to_date")) } else { - fmt.Printf("%s %d outdated, %d up to date\n", - dimStyle.Render("Summary:"), outdated, upToDate) - fmt.Printf("\nUpdate with: %s\n", dimStyle.Render("core pkg update --all")) + fmt.Printf("%s %s\n", + dimStyle.Render(i18n.T("cmd.pkg.outdated.summary_label")), i18n.T("cmd.pkg.outdated.summary", map[string]int{"Outdated": outdated, "UpToDate": upToDate})) + fmt.Printf("\n%s %s\n", i18n.T("cmd.pkg.outdated.update_with"), dimStyle.Render("core pkg update --all")) } return nil diff --git a/cmd/pkg/pkg_search.go b/cmd/pkg/pkg_search.go index ec9f4e1..689cd6c 100644 --- a/cmd/pkg/pkg_search.go +++ b/cmd/pkg/pkg_search.go @@ -11,6 +11,7 @@ import ( "time" "github.com/host-uk/core/pkg/cache" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "github.com/spf13/cobra" ) @@ -27,14 +28,8 @@ var ( func addPkgSearchCommand(parent *cobra.Command) { searchCmd := &cobra.Command{ Use: "search", - Short: "Search GitHub for packages", - Long: "Searches GitHub for repositories matching a pattern.\n" + - "Uses gh CLI for authenticated search. Results are cached for 1 hour.\n\n" + - "Examples:\n" + - " core pkg search # List all host-uk repos\n" + - " core pkg search --pattern 'core-*' # Search for core-* repos\n" + - " core pkg search --org mycompany # Search different org\n" + - " core pkg search --refresh # Bypass cache", + Short: i18n.T("cmd.pkg.search.short"), + Long: i18n.T("cmd.pkg.search.long"), RunE: func(cmd *cobra.Command, args []string) error { org := searchOrg pattern := searchPattern @@ -52,11 +47,11 @@ func addPkgSearchCommand(parent *cobra.Command) { }, } - searchCmd.Flags().StringVar(&searchOrg, "org", "", "GitHub organization (default: host-uk)") - searchCmd.Flags().StringVar(&searchPattern, "pattern", "", "Repo name pattern (* for wildcard)") - searchCmd.Flags().StringVar(&searchType, "type", "", "Filter by type in name (mod, services, plug, website)") - searchCmd.Flags().IntVar(&searchLimit, "limit", 0, "Max results (default 50)") - searchCmd.Flags().BoolVar(&searchRefresh, "refresh", false, "Bypass cache and fetch fresh data") + searchCmd.Flags().StringVar(&searchOrg, "org", "", i18n.T("cmd.pkg.search.flag.org")) + searchCmd.Flags().StringVar(&searchPattern, "pattern", "", i18n.T("cmd.pkg.search.flag.pattern")) + searchCmd.Flags().StringVar(&searchType, "type", "", i18n.T("cmd.pkg.search.flag.type")) + searchCmd.Flags().IntVar(&searchLimit, "limit", 0, i18n.T("cmd.pkg.search.flag.limit")) + searchCmd.Flags().BoolVar(&searchRefresh, "refresh", false, i18n.T("cmd.pkg.search.flag.refresh")) parent.AddCommand(searchCmd) } @@ -91,22 +86,22 @@ func runPkgSearch(org, pattern, repoType string, limit int, refresh bool) error if found, err := c.Get(cacheKey, &ghRepos); found && err == nil { fromCache = true age := c.Age(cacheKey) - fmt.Printf("%s %s %s\n", dimStyle.Render("Cache:"), org, dimStyle.Render(fmt.Sprintf("(%s ago)", age.Round(time.Second)))) + fmt.Printf("%s %s %s\n", dimStyle.Render(i18n.T("cmd.pkg.search.cache_label")), org, dimStyle.Render(fmt.Sprintf("(%s ago)", age.Round(time.Second)))) } } // Fetch from GitHub if not cached if !fromCache { if !ghAuthenticated() { - return fmt.Errorf("gh CLI not authenticated. Run: gh auth login") + return fmt.Errorf(i18n.T("cmd.pkg.error.gh_not_authenticated")) } if os.Getenv("GH_TOKEN") != "" { - fmt.Printf("%s GH_TOKEN env var is set - this may cause auth issues\n", dimStyle.Render("Note:")) - fmt.Printf("%s Unset it with: unset GH_TOKEN\n\n", dimStyle.Render("")) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.pkg.search.note_label")), i18n.T("cmd.pkg.search.gh_token_warning")) + fmt.Printf("%s %s\n\n", dimStyle.Render(""), i18n.T("cmd.pkg.search.gh_token_unset")) } - fmt.Printf("%s %s... ", dimStyle.Render("Fetching:"), org) + fmt.Printf("%s %s... ", dimStyle.Render(i18n.T("cmd.pkg.search.fetching_label")), org) cmd := exec.Command("gh", "repo", "list", org, "--json", "name,description,visibility,updatedAt,primaryLanguage", @@ -117,13 +112,13 @@ func runPkgSearch(org, pattern, repoType string, limit int, refresh bool) error fmt.Println() errStr := strings.TrimSpace(string(output)) if strings.Contains(errStr, "401") || strings.Contains(errStr, "Bad credentials") { - return fmt.Errorf("authentication failed - try: unset GH_TOKEN && gh auth login") + return fmt.Errorf(i18n.T("cmd.pkg.error.auth_failed")) } - return fmt.Errorf("search failed: %s", errStr) + return fmt.Errorf(i18n.T("cmd.pkg.error.search_failed"), errStr) } if err := json.Unmarshal(output, &ghRepos); err != nil { - return fmt.Errorf("failed to parse results: %w", err) + return fmt.Errorf(i18n.T("cmd.pkg.error.parse_results"), err) } if c != nil { @@ -146,7 +141,7 @@ func runPkgSearch(org, pattern, repoType string, limit int, refresh bool) error } if len(filtered) == 0 { - fmt.Println("No repositories found matching pattern.") + fmt.Println(i18n.T("cmd.pkg.search.no_repos_found")) return nil } @@ -154,12 +149,12 @@ func runPkgSearch(org, pattern, repoType string, limit int, refresh bool) error return filtered[i].Name < filtered[j].Name }) - fmt.Printf("Found %d repositories:\n\n", len(filtered)) + fmt.Printf(i18n.T("cmd.pkg.search.found_repos", map[string]int{"Count": len(filtered)}) + "\n\n") for _, r := range filtered { visibility := "" if r.Visibility == "private" { - visibility = dimStyle.Render(" [private]") + visibility = dimStyle.Render(" " + i18n.T("cmd.pkg.search.private_label")) } desc := r.Description @@ -167,7 +162,7 @@ func runPkgSearch(org, pattern, repoType string, limit int, refresh bool) error desc = desc[:47] + "..." } if desc == "" { - desc = dimStyle.Render("(no description)") + desc = dimStyle.Render(i18n.T("cmd.pkg.no_description")) } fmt.Printf(" %s%s\n", repoNameStyle.Render(r.Name), visibility) @@ -175,7 +170,7 @@ func runPkgSearch(org, pattern, repoType string, limit int, refresh bool) error } fmt.Println() - fmt.Printf("Install with: %s\n", dimStyle.Render(fmt.Sprintf("core pkg install %s/", org))) + fmt.Printf("%s %s\n", i18n.T("cmd.pkg.search.install_with"), dimStyle.Render(fmt.Sprintf("core pkg install %s/", org))) return nil } diff --git a/cmd/sdk/sdk.go b/cmd/sdk/sdk.go index cc219df..e422456 100644 --- a/cmd/sdk/sdk.go +++ b/cmd/sdk/sdk.go @@ -6,6 +6,7 @@ import ( "os" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" sdkpkg "github.com/host-uk/core/pkg/sdk" "github.com/spf13/cobra" ) @@ -20,13 +21,8 @@ var ( var sdkCmd = &cobra.Command{ Use: "sdk", - Short: "SDK validation and API compatibility tools", - Long: `Tools for validating OpenAPI specs and checking API compatibility. -To generate SDKs, use: core build sdk - -Commands: - diff Check for breaking API changes - validate Validate OpenAPI spec syntax`, + Short: i18n.T("cmd.sdk.short"), + Long: i18n.T("cmd.sdk.long"), } var diffBasePath string @@ -34,7 +30,8 @@ var diffSpecPath string var sdkDiffCmd = &cobra.Command{ Use: "diff", - Short: "Check for breaking API changes", + Short: i18n.T("cmd.sdk.diff.short"), + Long: i18n.T("cmd.sdk.diff.long"), RunE: func(cmd *cobra.Command, args []string) error { return runSDKDiff(diffBasePath, diffSpecPath) }, @@ -44,7 +41,8 @@ var validateSpecPath string var sdkValidateCmd = &cobra.Command{ Use: "validate", - Short: "Validate OpenAPI spec", + Short: i18n.T("cmd.sdk.validate.short"), + Long: i18n.T("cmd.sdk.validate.long"), RunE: func(cmd *cobra.Command, args []string) error { return runSDKValidate(validateSpecPath) }, @@ -52,11 +50,11 @@ var sdkValidateCmd = &cobra.Command{ func init() { // sdk diff flags - sdkDiffCmd.Flags().StringVar(&diffBasePath, "base", "", "Base spec (version tag or file)") - sdkDiffCmd.Flags().StringVar(&diffSpecPath, "spec", "", "Current spec file") + sdkDiffCmd.Flags().StringVar(&diffBasePath, "base", "", i18n.T("cmd.sdk.diff.flag.base")) + sdkDiffCmd.Flags().StringVar(&diffSpecPath, "spec", "", i18n.T("cmd.sdk.diff.flag.spec")) // sdk validate flags - sdkValidateCmd.Flags().StringVar(&validateSpecPath, "spec", "", "Path to OpenAPI spec file") + sdkValidateCmd.Flags().StringVar(&validateSpecPath, "spec", "", i18n.T("cmd.sdk.validate.flag.spec")) // Add subcommands sdkCmd.AddCommand(sdkDiffCmd) @@ -66,7 +64,7 @@ func init() { func runSDKDiff(basePath, specPath string) error { projectDir, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.sdk.error.working_dir"), err) } // Detect current spec if not provided @@ -79,49 +77,49 @@ func runSDKDiff(basePath, specPath string) error { } if basePath == "" { - return fmt.Errorf("--base is required (version tag or file path)") + return fmt.Errorf(i18n.T("cmd.sdk.diff.error.base_required")) } - fmt.Printf("%s Checking for breaking changes\n", sdkHeaderStyle.Render("SDK Diff:")) - fmt.Printf(" Base: %s\n", sdkDimStyle.Render(basePath)) - fmt.Printf(" Current: %s\n", sdkDimStyle.Render(specPath)) + fmt.Printf("%s %s\n", sdkHeaderStyle.Render(i18n.T("cmd.sdk.diff.label")), i18n.T("cmd.sdk.diff.checking")) + fmt.Printf(" %s %s\n", i18n.T("cmd.sdk.diff.base_label"), sdkDimStyle.Render(basePath)) + fmt.Printf(" %s %s\n", i18n.T("cmd.sdk.diff.current_label"), sdkDimStyle.Render(specPath)) fmt.Println() result, err := sdkpkg.Diff(basePath, specPath) if err != nil { - fmt.Printf("%s %v\n", sdkErrorStyle.Render("Error:"), err) + fmt.Printf("%s %v\n", sdkErrorStyle.Render(i18n.T("cmd.sdk.label.error")), err) os.Exit(2) } if result.Breaking { - fmt.Printf("%s %s\n", sdkErrorStyle.Render("Breaking:"), result.Summary) + fmt.Printf("%s %s\n", sdkErrorStyle.Render(i18n.T("cmd.sdk.diff.breaking")), result.Summary) for _, change := range result.Changes { fmt.Printf(" - %s\n", change) } os.Exit(1) } - fmt.Printf("%s %s\n", sdkSuccessStyle.Render("OK:"), result.Summary) + fmt.Printf("%s %s\n", sdkSuccessStyle.Render(i18n.T("cmd.sdk.label.ok")), result.Summary) return nil } func runSDKValidate(specPath string) error { projectDir, err := os.Getwd() if err != nil { - return fmt.Errorf("failed to get working directory: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.sdk.error.working_dir"), err) } s := sdkpkg.New(projectDir, &sdkpkg.Config{Spec: specPath}) - fmt.Printf("%s Validating OpenAPI spec\n", sdkHeaderStyle.Render("SDK:")) + fmt.Printf("%s %s\n", sdkHeaderStyle.Render(i18n.T("cmd.sdk.label.sdk")), i18n.T("cmd.sdk.validate.validating")) detectedPath, err := s.DetectSpec() if err != nil { - fmt.Printf("%s %v\n", sdkErrorStyle.Render("Error:"), err) + fmt.Printf("%s %v\n", sdkErrorStyle.Render(i18n.T("cmd.sdk.label.error")), err) return err } - fmt.Printf(" Spec: %s\n", sdkDimStyle.Render(detectedPath)) - fmt.Printf("%s Spec is valid\n", sdkSuccessStyle.Render("OK:")) + fmt.Printf(" %s %s\n", i18n.T("cmd.sdk.validate.spec_label"), sdkDimStyle.Render(detectedPath)) + fmt.Printf("%s %s\n", sdkSuccessStyle.Render(i18n.T("cmd.sdk.label.ok")), i18n.T("cmd.sdk.validate.valid")) return nil } diff --git a/cmd/setup/setup.go b/cmd/setup/setup.go index 69e5f04..2c158ae 100644 --- a/cmd/setup/setup.go +++ b/cmd/setup/setup.go @@ -3,6 +3,7 @@ package setup import ( "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -33,31 +34,20 @@ var ( var setupCmd = &cobra.Command{ Use: "setup", - Short: "Bootstrap workspace or clone packages from registry", - Long: `Sets up a development workspace. - -REGISTRY MODE (repos.yaml exists): - Clones all repositories defined in repos.yaml into packages/. - Skips repos that already exist. Use --only to filter by type. - -BOOTSTRAP MODE (no repos.yaml): - 1. Clones core-devops to set up the workspace - 2. Presents an interactive wizard to select packages - 3. Clones selected packages - -Use --all to skip the wizard and clone everything.`, + Short: i18n.T("cmd.setup.short"), + Long: i18n.T("cmd.setup.long"), RunE: func(cmd *cobra.Command, args []string) error { return runSetupOrchestrator(registryPath, only, dryRun, all, name, build) }, } func init() { - setupCmd.Flags().StringVar(®istryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)") - setupCmd.Flags().StringVar(&only, "only", "", "Only clone repos of these types (comma-separated: foundation,module,product)") - setupCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Show what would be cloned without cloning") - setupCmd.Flags().BoolVar(&all, "all", false, "Skip wizard, clone all packages (non-interactive)") - setupCmd.Flags().StringVar(&name, "name", "", "Project directory name for bootstrap mode") - setupCmd.Flags().BoolVar(&build, "build", false, "Run build after cloning") + setupCmd.Flags().StringVar(®istryPath, "registry", "", i18n.T("cmd.setup.flag.registry")) + setupCmd.Flags().StringVar(&only, "only", "", i18n.T("cmd.setup.flag.only")) + setupCmd.Flags().BoolVar(&dryRun, "dry-run", false, i18n.T("cmd.setup.flag.dry_run")) + setupCmd.Flags().BoolVar(&all, "all", false, i18n.T("cmd.setup.flag.all")) + setupCmd.Flags().StringVar(&name, "name", "", i18n.T("cmd.setup.flag.name")) + setupCmd.Flags().BoolVar(&build, "build", false, i18n.T("cmd.setup.flag.build")) } // AddSetupCommand adds the 'setup' command to the given parent command. diff --git a/cmd/setup/setup_bootstrap.go b/cmd/setup/setup_bootstrap.go index 26b7ca2..59ca9ae 100644 --- a/cmd/setup/setup_bootstrap.go +++ b/cmd/setup/setup_bootstrap.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" ) @@ -45,7 +46,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam return fmt.Errorf("failed to get working directory: %w", err) } - fmt.Printf("%s Bootstrap mode (no repos.yaml found)\n", dimStyle.Render(">>")) + fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.bootstrap_mode")) var targetDir string @@ -58,7 +59,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam if empty { // Clone into current directory targetDir = cwd - fmt.Printf("%s Cloning into current directory\n", dimStyle.Render(">>")) + fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.cloning_current_dir")) } else { // Directory has content - check if it's a git repo root isRepo := isGitRepoRoot(cwd) @@ -90,7 +91,7 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam } targetDir = filepath.Join(cwd, projectName) - fmt.Printf("%s Creating project directory: %s\n", dimStyle.Render(">>"), projectName) + fmt.Printf("%s %s: %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.creating_project_dir"), projectName) if !dryRun { if err := os.MkdirAll(targetDir, 0755); err != nil { @@ -102,25 +103,25 @@ func runBootstrap(ctx context.Context, only string, dryRun, all bool, projectNam // Clone core-devops first devopsPath := filepath.Join(targetDir, devopsRepo) if _, err := os.Stat(filepath.Join(devopsPath, ".git")); os.IsNotExist(err) { - fmt.Printf("%s Cloning %s...\n", dimStyle.Render(">>"), devopsRepo) + fmt.Printf("%s %s %s...\n", dimStyle.Render(">>"), i18n.T("cmd.setup.cloning"), devopsRepo) if !dryRun { if err := gitClone(ctx, defaultOrg, devopsRepo, devopsPath); err != nil { return fmt.Errorf("failed to clone %s: %w", devopsRepo, err) } - fmt.Printf("%s %s cloned\n", successStyle.Render(">>"), devopsRepo) + fmt.Printf("%s %s %s\n", successStyle.Render(">>"), devopsRepo, i18n.T("cmd.setup.cloned")) } else { - fmt.Printf(" Would clone %s/%s to %s\n", defaultOrg, devopsRepo, devopsPath) + fmt.Printf(" %s %s/%s to %s\n", i18n.T("cmd.setup.would_clone"), defaultOrg, devopsRepo, devopsPath) } } else { - fmt.Printf("%s %s already exists\n", dimStyle.Render(">>"), devopsRepo) + fmt.Printf("%s %s %s\n", dimStyle.Render(">>"), devopsRepo, i18n.T("cmd.setup.already_exists")) } // Load the repos.yaml from core-devops registryPath := filepath.Join(devopsPath, devopsReposYaml) if dryRun { - fmt.Printf("\n%s Would load registry from %s and present package wizard\n", dimStyle.Render(">>"), registryPath) + fmt.Printf("\n%s %s %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.would_load_registry"), registryPath) return nil } diff --git a/cmd/setup/setup_registry.go b/cmd/setup/setup_registry.go index 87e64e0..9a62447 100644 --- a/cmd/setup/setup_registry.go +++ b/cmd/setup/setup_registry.go @@ -14,6 +14,7 @@ import ( "strings" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" ) @@ -29,8 +30,8 @@ func runRegistrySetup(ctx context.Context, registryPath, only string, dryRun, al // runRegistrySetupWithReg runs setup with an already-loaded registry. func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryPath, only string, dryRun, all, runBuild bool) error { - fmt.Printf("%s %s\n", dimStyle.Render("Registry:"), registryPath) - fmt.Printf("%s %s\n", dimStyle.Render("Org:"), reg.Org) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.setup.registry_label")), registryPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.setup.org_label")), reg.Org) // Determine base path for cloning basePath := reg.BasePath @@ -47,7 +48,7 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP basePath = filepath.Join(home, basePath[2:]) } - fmt.Printf("%s %s\n", dimStyle.Render("Target:"), basePath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.setup.target_label")), basePath) // Parse type filter var typeFilter []string @@ -55,7 +56,7 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP for _, t := range strings.Split(only, ",") { typeFilter = append(typeFilter, strings.TrimSpace(t)) } - fmt.Printf("%s %s\n", dimStyle.Render("Filter:"), only) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.setup.filter_label")), only) } // Ensure base path exists @@ -136,15 +137,18 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP // Summary fmt.Println() - fmt.Printf("%d to clone, %d exist, %d skipped\n", len(toClone), exists, skipped) + fmt.Printf("%s, %s, %s\n", + i18n.T("cmd.setup.to_clone", map[string]interface{}{"Count": len(toClone)}), + i18n.T("cmd.setup.exist", map[string]interface{}{"Count": exists}), + i18n.T("cmd.setup.skipped", map[string]interface{}{"Count": skipped})) if len(toClone) == 0 { - fmt.Println("\nNothing to clone.") + fmt.Printf("\n%s\n", i18n.T("cmd.setup.nothing_to_clone")) return nil } if dryRun { - fmt.Println("\nWould clone:") + fmt.Printf("\n%s\n", i18n.T("cmd.setup.would_clone_list")) for _, repo := range toClone { fmt.Printf(" %s (%s)\n", repoNameStyle.Render(repo.Name), repo.Type) } @@ -158,7 +162,7 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP return err } if !confirmed { - fmt.Println("Cancelled.") + fmt.Println(i18n.T("cmd.setup.cancelled")) return nil } } @@ -168,7 +172,7 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP var succeeded, failed int for _, repo := range toClone { - fmt.Printf(" %s %s... ", dimStyle.Render("Cloning"), repo.Name) + fmt.Printf(" %s %s... ", dimStyle.Render(i18n.T("cmd.setup.cloning")), repo.Name) repoPath := filepath.Join(basePath, repo.Name) @@ -177,32 +181,32 @@ func runRegistrySetupWithReg(ctx context.Context, reg *repos.Registry, registryP fmt.Printf("%s\n", errorStyle.Render("x "+err.Error())) failed++ } else { - fmt.Printf("%s\n", successStyle.Render("done")) + fmt.Printf("%s\n", successStyle.Render(i18n.T("cmd.setup.done"))) succeeded++ } } // Summary fmt.Println() - fmt.Printf("%s %d cloned", successStyle.Render("Done:"), succeeded) + fmt.Printf("%s %s", successStyle.Render(i18n.T("cmd.setup.done_label")), i18n.T("cmd.setup.cloned_count", map[string]interface{}{"Count": succeeded})) if failed > 0 { - fmt.Printf(", %s", errorStyle.Render(fmt.Sprintf("%d failed", failed))) + fmt.Printf(", %s", errorStyle.Render(i18n.T("cmd.setup.failed_count", map[string]interface{}{"Count": failed}))) } if exists > 0 { - fmt.Printf(", %d already exist", exists) + fmt.Printf(", %s", i18n.T("cmd.setup.already_exist_count", map[string]interface{}{"Count": exists})) } fmt.Println() // Run build if requested if runBuild && succeeded > 0 { fmt.Println() - fmt.Printf("%s Running build...\n", dimStyle.Render(">>")) + fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.running_build")) buildCmd := exec.Command("core", "build") buildCmd.Dir = basePath buildCmd.Stdout = os.Stdout buildCmd.Stderr = os.Stderr if err := buildCmd.Run(); err != nil { - return fmt.Errorf("build failed: %w", err) + return fmt.Errorf("%s: %w", i18n.T("cmd.setup.error.build_failed"), err) } } diff --git a/cmd/setup/setup_repo.go b/cmd/setup/setup_repo.go index 4d49c89..1b7461a 100644 --- a/cmd/setup/setup_repo.go +++ b/cmd/setup/setup_repo.go @@ -12,15 +12,17 @@ import ( "os/exec" "path/filepath" "strings" + + "github.com/host-uk/core/pkg/i18n" ) // runRepoSetup sets up the current repository with .core/ configuration. func runRepoSetup(repoPath string, dryRun bool) error { - fmt.Printf("%s Setting up repository: %s\n", dimStyle.Render(">>"), repoPath) + fmt.Printf("%s %s: %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.repo.setting_up"), repoPath) // Detect project type projectType := detectProjectType(repoPath) - fmt.Printf("%s Detected project type: %s\n", dimStyle.Render(">>"), projectType) + fmt.Printf("%s %s: %s\n", dimStyle.Render(">>"), i18n.T("cmd.setup.repo.detected_type"), projectType) // Create .core directory coreDir := filepath.Join(repoPath, ".core") @@ -39,7 +41,7 @@ func runRepoSetup(repoPath string, dryRun bool) error { } if dryRun { - fmt.Printf("\n%s Would create:\n", dimStyle.Render(">>")) + fmt.Printf("\n%s %s:\n", dimStyle.Render(">>"), i18n.T("cmd.setup.repo.would_create")) for filename, content := range configs { fmt.Printf("\n %s:\n", filepath.Join(coreDir, filename)) // Indent content for display @@ -55,7 +57,7 @@ func runRepoSetup(repoPath string, dryRun bool) error { if err := os.WriteFile(configPath, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write %s: %w", filename, err) } - fmt.Printf("%s Created %s\n", successStyle.Render(">>"), configPath) + fmt.Printf("%s %s %s\n", successStyle.Render(">>"), i18n.T("cmd.setup.repo.created"), configPath) } return nil diff --git a/cmd/setup/setup_wizard.go b/cmd/setup/setup_wizard.go index abe39e0..3b5a710 100644 --- a/cmd/setup/setup_wizard.go +++ b/cmd/setup/setup_wizard.go @@ -13,6 +13,7 @@ import ( "github.com/charmbracelet/huh" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/host-uk/core/pkg/repos" "golang.org/x/term" ) @@ -35,11 +36,11 @@ func promptSetupChoice() (string, error) { form := huh.NewForm( huh.NewGroup( huh.NewSelect[string](). - Title("This directory is a git repository"). - Description("What would you like to do?"). + Title(i18n.T("cmd.setup.wizard.git_repo_title")). + Description(i18n.T("cmd.setup.wizard.what_to_do")). Options( - huh.NewOption("Setup Working Directory", "setup").Selected(true), - huh.NewOption("Create Package (clone repos into subdirectory)", "package"), + huh.NewOption(i18n.T("cmd.setup.wizard.option_setup"), "setup").Selected(true), + huh.NewOption(i18n.T("cmd.setup.wizard.option_package"), "package"), ). Value(&choice), ), @@ -59,8 +60,8 @@ func promptProjectName(defaultName string) (string, error) { form := huh.NewForm( huh.NewGroup( huh.NewInput(). - Title("Project directory name"). - Description("Enter the name for your new workspace directory"). + Title(i18n.T("cmd.setup.wizard.project_name_title")). + Description(i18n.T("cmd.setup.wizard.project_name_desc")). Placeholder(defaultName). Value(&name), ), @@ -158,14 +159,14 @@ func runPackageWizard(reg *repos.Registry, preselectedTypes []string) ([]string, // Header styling headerStyle := shared.TitleStyle.MarginBottom(1) - fmt.Println(headerStyle.Render("Package Selection")) - fmt.Println("Use space to select/deselect, enter to confirm") + fmt.Println(headerStyle.Render(i18n.T("cmd.setup.wizard.package_selection"))) + fmt.Println(i18n.T("cmd.setup.wizard.selection_hint")) fmt.Println() form := huh.NewForm( huh.NewGroup( huh.NewMultiSelect[string](). - Title("Select packages to clone"). + Title(i18n.T("cmd.setup.wizard.select_packages")). Options(options...). Value(&selected). Filterable(true). @@ -195,9 +196,9 @@ func confirmClone(count int, target string) (bool, error) { form := huh.NewForm( huh.NewGroup( huh.NewConfirm(). - Title(fmt.Sprintf("Clone %d packages to %s?", count, target)). - Affirmative("Yes, clone"). - Negative("Cancel"). + Title(i18n.T("cmd.setup.wizard.confirm_clone", map[string]interface{}{"Count": count, "Target": target})). + Affirmative(i18n.T("cmd.setup.wizard.confirm_yes")). + Negative(i18n.T("cmd.setup.wizard.confirm_cancel")). Value(&confirmed), ), ).WithTheme(wizardTheme()) diff --git a/cmd/test/test.go b/cmd/test/test.go index ca5f77a..bc5a82e 100644 --- a/cmd/test/test.go +++ b/cmd/test/test.go @@ -5,6 +5,7 @@ package testcmd import ( "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -33,31 +34,19 @@ var ( var testCmd = &cobra.Command{ Use: "test", - Short: "Run tests with coverage", - Long: `Runs Go tests with coverage reporting. - -Sets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings on macOS. - -Examples: - core test # Run all tests with coverage summary - core test --verbose # Show test output as it runs - core test --coverage # Show detailed per-package coverage - core test --pkg ./pkg/... # Test specific packages - core test --run TestName # Run specific test by name - core test --short # Skip long-running tests - core test --race # Enable race detector - core test --json # Output JSON for CI/agents`, + Short: i18n.T("cmd.test.short"), + Long: i18n.T("cmd.test.long"), RunE: func(cmd *cobra.Command, args []string) error { return runTest(testVerbose, testCoverage, testShort, testPkg, testRun, testRace, testJSON) }, } func init() { - testCmd.Flags().BoolVar(&testVerbose, "verbose", false, "Show test output as it runs (-v)") - testCmd.Flags().BoolVar(&testCoverage, "coverage", false, "Show detailed per-package coverage") - testCmd.Flags().BoolVar(&testShort, "short", false, "Skip long-running tests (-short)") - testCmd.Flags().StringVar(&testPkg, "pkg", "", "Package pattern to test (default: ./...)") - testCmd.Flags().StringVar(&testRun, "run", "", "Run only tests matching this regex (-run)") - testCmd.Flags().BoolVar(&testRace, "race", false, "Enable race detector (-race)") - testCmd.Flags().BoolVar(&testJSON, "json", false, "Output JSON for CI/agents") + testCmd.Flags().BoolVar(&testVerbose, "verbose", false, i18n.T("cmd.test.flag.verbose")) + testCmd.Flags().BoolVar(&testCoverage, "coverage", false, i18n.T("cmd.test.flag.coverage")) + testCmd.Flags().BoolVar(&testShort, "short", false, i18n.T("cmd.test.flag.short")) + testCmd.Flags().StringVar(&testPkg, "pkg", "", i18n.T("cmd.test.flag.pkg")) + testCmd.Flags().StringVar(&testRun, "run", "", i18n.T("cmd.test.flag.run")) + testCmd.Flags().BoolVar(&testRace, "race", false, i18n.T("cmd.test.flag.race")) + testCmd.Flags().BoolVar(&testJSON, "json", false, i18n.T("cmd.test.flag.json")) } diff --git a/cmd/test/test_output.go b/cmd/test/test_output.go index 41b9722..6fb9e89 100644 --- a/cmd/test/test_output.go +++ b/cmd/test/test_output.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" ) type packageCoverage struct { @@ -84,19 +85,19 @@ func printTestSummary(results testResults, showCoverage bool) { // Print pass/fail summary total := results.passed + results.failed if total > 0 { - fmt.Printf(" %s %d passed", testPassStyle.Render("✓"), results.passed) + fmt.Printf(" %s %s", testPassStyle.Render("✓"), i18n.T("cmd.test.passed", map[string]interface{}{"Count": results.passed})) if results.failed > 0 { - fmt.Printf(" %s %d failed", testFailStyle.Render("✗"), results.failed) + fmt.Printf(" %s %s", testFailStyle.Render("✗"), i18n.T("cmd.test.failed", map[string]interface{}{"Count": results.failed})) } if results.skipped > 0 { - fmt.Printf(" %s %d skipped", testSkipStyle.Render("○"), results.skipped) + fmt.Printf(" %s %s", testSkipStyle.Render("○"), i18n.T("cmd.test.skipped", map[string]interface{}{"Count": results.skipped})) } fmt.Println() } // Print failed packages if len(results.failedPkgs) > 0 { - fmt.Printf("\n Failed packages:\n") + fmt.Printf("\n %s\n", i18n.T("cmd.test.failed_packages")) for _, pkg := range results.failedPkgs { fmt.Printf(" %s %s\n", testFailStyle.Render("✗"), pkg) } @@ -107,7 +108,7 @@ func printTestSummary(results testResults, showCoverage bool) { printCoverageSummary(results) } else if results.covCount > 0 { avgCov := results.totalCov / float64(results.covCount) - fmt.Printf("\n Coverage: %s\n", formatCoverage(avgCov)) + fmt.Printf("\n %s %s\n", i18n.T("cmd.test.label.coverage"), formatCoverage(avgCov)) } } @@ -116,7 +117,7 @@ func printCoverageSummary(results testResults) { return } - fmt.Printf("\n %s\n", testHeaderStyle.Render("Coverage by package:")) + fmt.Printf("\n %s\n", testHeaderStyle.Render(i18n.T("cmd.test.coverage_by_package"))) // Sort packages by name sort.Slice(results.packages, func(i, j int) bool { @@ -145,8 +146,9 @@ func printCoverageSummary(results testResults) { // Print average if results.covCount > 0 { avgCov := results.totalCov / float64(results.covCount) - padding := strings.Repeat(" ", maxLen-7+2) - fmt.Printf("\n %s%s%s\n", testHeaderStyle.Render("Average"), padding, formatCoverage(avgCov)) + avgLabel := i18n.T("cmd.test.label.average") + padding := strings.Repeat(" ", maxLen-len(avgLabel)+2) + fmt.Printf("\n %s%s%s\n", testHeaderStyle.Render(avgLabel), padding, formatCoverage(avgCov)) } } diff --git a/cmd/test/test_runner.go b/cmd/test/test_runner.go index 16e5f07..1cf0fb0 100644 --- a/cmd/test/test_runner.go +++ b/cmd/test/test_runner.go @@ -8,12 +8,14 @@ import ( "os/exec" "runtime" "strings" + + "github.com/host-uk/core/pkg/i18n" ) func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bool) error { // Detect if we're in a Go project if _, err := os.Stat("go.mod"); os.IsNotExist(err) { - return fmt.Errorf("no go.mod found - run from a Go project directory") + return fmt.Errorf(i18n.T("cmd.test.error.no_go_mod")) } // Build command arguments @@ -52,10 +54,10 @@ func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bo cmd.Env = append(os.Environ(), getMacOSDeploymentTarget()) if !jsonOutput { - fmt.Printf("%s Running tests\n", testHeaderStyle.Render("Test:")) - fmt.Printf(" Package: %s\n", testDimStyle.Render(pkg)) + fmt.Printf("%s %s\n", testHeaderStyle.Render(i18n.T("cmd.test.label.test")), i18n.T("cmd.test.running")) + fmt.Printf(" %s %s\n", i18n.T("cmd.test.label.package"), testDimStyle.Render(pkg)) if run != "" { - fmt.Printf(" Filter: %s\n", testDimStyle.Render(run)) + fmt.Printf(" %s %s\n", i18n.T("cmd.test.label.filter"), testDimStyle.Render(run)) } fmt.Println() } @@ -91,7 +93,7 @@ func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bo // JSON output for CI/agents printJSONResults(results, exitCode) if exitCode != 0 { - return fmt.Errorf("tests failed") + return fmt.Errorf(i18n.T("cmd.test.error.tests_failed")) } return nil } @@ -106,11 +108,11 @@ func runTest(verbose, coverage, short bool, pkg, run string, race, jsonOutput bo } if exitCode != 0 { - fmt.Printf("\n%s Tests failed\n", testFailStyle.Render("FAIL")) - return fmt.Errorf("tests failed") + fmt.Printf("\n%s %s\n", testFailStyle.Render(i18n.T("cli.fail")), i18n.T("cmd.test.tests_failed")) + return fmt.Errorf(i18n.T("cmd.test.error.tests_failed")) } - fmt.Printf("\n%s All tests passed\n", testPassStyle.Render("PASS")) + fmt.Printf("\n%s %s\n", testPassStyle.Render(i18n.T("cli.pass")), i18n.T("cmd.test.all_passed")) return nil } diff --git a/cmd/vm/container.go b/cmd/vm/container.go index e716fca..1560632 100644 --- a/cmd/vm/container.go +++ b/cmd/vm/container.go @@ -10,6 +10,7 @@ import ( "time" "github.com/host-uk/core/pkg/container" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -27,17 +28,8 @@ var ( func addVMRunCommand(parent *cobra.Command) { runCmd := &cobra.Command{ Use: "run [image]", - Short: "Run a LinuxKit image or template", - Long: "Runs a LinuxKit image as a VM using the available hypervisor.\n\n" + - "Supported image formats: .iso, .qcow2, .vmdk, .raw\n\n" + - "You can also run from a template using --template, which will build and run\n" + - "the image automatically. Use --var to set template variables.\n\n" + - "Examples:\n" + - " core vm run image.iso\n" + - " core vm run -d image.qcow2\n" + - " core vm run --name myvm --memory 2048 --cpus 4 image.iso\n" + - " core vm run --template core-dev --var SSH_KEY=\"ssh-rsa AAAA...\"\n" + - " core vm run --template server-php --var SSH_KEY=\"...\" --var DOMAIN=example.com", + Short: i18n.T("cmd.vm.run.short"), + Long: i18n.T("cmd.vm.run.long"), RunE: func(cmd *cobra.Command, args []string) error { opts := container.RunOptions{ Name: runName, @@ -55,7 +47,7 @@ func addVMRunCommand(parent *cobra.Command) { // Otherwise, require an image path if len(args) == 0 { - return fmt.Errorf("image path is required (or use --template)") + return fmt.Errorf(i18n.T("cmd.vm.run.error.image_required")) } image := args[0] @@ -63,13 +55,13 @@ func addVMRunCommand(parent *cobra.Command) { }, } - runCmd.Flags().StringVar(&runName, "name", "", "Name for the container") - runCmd.Flags().BoolVarP(&runDetach, "detach", "d", false, "Run in detached mode (background)") - runCmd.Flags().IntVar(&runMemory, "memory", 0, "Memory in MB (default: 1024)") - runCmd.Flags().IntVar(&runCPUs, "cpus", 0, "Number of CPUs (default: 1)") - runCmd.Flags().IntVar(&runSSHPort, "ssh-port", 0, "SSH port for exec commands (default: 2222)") - runCmd.Flags().StringVar(&runTemplateName, "template", "", "Run from a LinuxKit template (build + run)") - runCmd.Flags().StringArrayVar(&runVarFlags, "var", nil, "Template variable in KEY=VALUE format (can be repeated)") + runCmd.Flags().StringVar(&runName, "name", "", i18n.T("cmd.vm.run.flag.name")) + runCmd.Flags().BoolVarP(&runDetach, "detach", "d", false, i18n.T("cmd.vm.run.flag.detach")) + runCmd.Flags().IntVar(&runMemory, "memory", 0, i18n.T("cmd.vm.run.flag.memory")) + runCmd.Flags().IntVar(&runCPUs, "cpus", 0, i18n.T("cmd.vm.run.flag.cpus")) + runCmd.Flags().IntVar(&runSSHPort, "ssh-port", 0, i18n.T("cmd.vm.run.flag.ssh_port")) + runCmd.Flags().StringVar(&runTemplateName, "template", "", i18n.T("cmd.vm.run.flag.template")) + runCmd.Flags().StringArrayVar(&runVarFlags, "var", nil, i18n.T("cmd.vm.run.flag.var")) parent.AddCommand(runCmd) } @@ -77,7 +69,7 @@ func addVMRunCommand(parent *cobra.Command) { func runContainer(image, name string, detach bool, memory, cpus, sshPort int) error { manager, err := container.NewLinuxKitManager() if err != nil { - return fmt.Errorf("failed to initialize container manager: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.init_manager")+": %w", err) } opts := container.RunOptions{ @@ -88,27 +80,27 @@ func runContainer(image, name string, detach bool, memory, cpus, sshPort int) er SSHPort: sshPort, } - fmt.Printf("%s %s\n", dimStyle.Render("Image:"), image) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.image")), image) if name != "" { - fmt.Printf("%s %s\n", dimStyle.Render("Name:"), name) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.name")), name) } - fmt.Printf("%s %s\n", dimStyle.Render("Hypervisor:"), manager.Hypervisor().Name()) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.hypervisor")), manager.Hypervisor().Name()) fmt.Println() ctx := context.Background() c, err := manager.Run(ctx, image, opts) if err != nil { - return fmt.Errorf("failed to run container: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.run_container")+": %w", err) } if detach { - fmt.Printf("%s %s\n", successStyle.Render("Started:"), c.ID) - fmt.Printf("%s %d\n", dimStyle.Render("PID:"), c.PID) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.vm.label.started")), c.ID) + fmt.Printf("%s %d\n", dimStyle.Render(i18n.T("cmd.vm.label.pid")), c.PID) fmt.Println() - fmt.Printf("Use 'core vm logs %s' to view output\n", c.ID[:8]) - fmt.Printf("Use 'core vm stop %s' to stop\n", c.ID[:8]) + fmt.Println(i18n.T("cmd.vm.hint.view_logs", map[string]interface{}{"ID": c.ID[:8]})) + fmt.Println(i18n.T("cmd.vm.hint.stop", map[string]interface{}{"ID": c.ID[:8]})) } else { - fmt.Printf("\n%s %s\n", dimStyle.Render("Container stopped:"), c.ID) + fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.container_stopped")), c.ID) } return nil @@ -120,17 +112,14 @@ var psAll bool func addVMPsCommand(parent *cobra.Command) { psCmd := &cobra.Command{ Use: "ps", - Short: "List running VMs", - Long: "Lists all VMs. By default, only shows running VMs.\n\n" + - "Examples:\n" + - " core vm ps\n" + - " core vm ps -a", + Short: i18n.T("cmd.vm.ps.short"), + Long: i18n.T("cmd.vm.ps.long"), RunE: func(cmd *cobra.Command, args []string) error { return listContainers(psAll) }, } - psCmd.Flags().BoolVarP(&psAll, "all", "a", false, "Show all containers (including stopped)") + psCmd.Flags().BoolVarP(&psAll, "all", "a", false, i18n.T("cmd.vm.ps.flag.all")) parent.AddCommand(psCmd) } @@ -138,13 +127,13 @@ func addVMPsCommand(parent *cobra.Command) { func listContainers(all bool) error { manager, err := container.NewLinuxKitManager() if err != nil { - return fmt.Errorf("failed to initialize container manager: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.init_manager")+": %w", err) } ctx := context.Background() containers, err := manager.List(ctx) if err != nil { - return fmt.Errorf("failed to list containers: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.list_containers")+": %w", err) } // Filter if not showing all @@ -160,15 +149,15 @@ func listContainers(all bool) error { if len(containers) == 0 { if all { - fmt.Println("No containers") + fmt.Println(i18n.T("cmd.vm.ps.no_containers")) } else { - fmt.Println("No running containers") + fmt.Println(i18n.T("cmd.vm.ps.no_running")) } return nil } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "ID\tNAME\tIMAGE\tSTATUS\tSTARTED\tPID") + fmt.Fprintln(w, i18n.T("cmd.vm.ps.header")) fmt.Fprintln(w, "--\t----\t-----\t------\t-------\t---") for _, c := range containers { @@ -217,14 +206,11 @@ func formatDuration(d time.Duration) string { func addVMStopCommand(parent *cobra.Command) { stopCmd := &cobra.Command{ Use: "stop ", - Short: "Stop a running VM", - Long: "Stops a running VM by ID.\n\n" + - "Examples:\n" + - " core vm stop abc12345\n" + - " core vm stop abc1", + Short: i18n.T("cmd.vm.stop.short"), + Long: i18n.T("cmd.vm.stop.long"), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return fmt.Errorf("container ID is required") + return fmt.Errorf(i18n.T("cmd.vm.error.id_required")) } return stopContainer(args[0]) }, @@ -236,7 +222,7 @@ func addVMStopCommand(parent *cobra.Command) { func stopContainer(id string) error { manager, err := container.NewLinuxKitManager() if err != nil { - return fmt.Errorf("failed to initialize container manager: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.init_manager")+": %w", err) } // Support partial ID matching @@ -245,14 +231,14 @@ func stopContainer(id string) error { return err } - fmt.Printf("%s %s\n", dimStyle.Render("Stopping:"), fullID[:8]) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.stop.stopping")), fullID[:8]) ctx := context.Background() if err := manager.Stop(ctx, fullID); err != nil { - return fmt.Errorf("failed to stop container: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.stop_container")+": %w", err) } - fmt.Printf("%s\n", successStyle.Render("Stopped")) + fmt.Printf("%s\n", successStyle.Render(i18n.T("cmd.vm.stop.stopped"))) return nil } @@ -273,11 +259,11 @@ func resolveContainerID(manager *container.LinuxKitManager, partialID string) (s switch len(matches) { case 0: - return "", fmt.Errorf("no container found matching: %s", partialID) + return "", fmt.Errorf(i18n.T("cmd.vm.error.no_match", map[string]interface{}{"ID": partialID})) case 1: return matches[0].ID, nil default: - return "", fmt.Errorf("multiple containers match '%s', be more specific", partialID) + return "", fmt.Errorf(i18n.T("cmd.vm.error.multiple_match", map[string]interface{}{"ID": partialID})) } } @@ -287,20 +273,17 @@ var logsFollow bool func addVMLogsCommand(parent *cobra.Command) { logsCmd := &cobra.Command{ Use: "logs ", - Short: "View VM logs", - Long: "View logs from a VM.\n\n" + - "Examples:\n" + - " core vm logs abc12345\n" + - " core vm logs -f abc1", + Short: i18n.T("cmd.vm.logs.short"), + Long: i18n.T("cmd.vm.logs.long"), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return fmt.Errorf("container ID is required") + return fmt.Errorf(i18n.T("cmd.vm.error.id_required")) } return viewLogs(args[0], logsFollow) }, } - logsCmd.Flags().BoolVarP(&logsFollow, "follow", "f", false, "Follow log output") + logsCmd.Flags().BoolVarP(&logsFollow, "follow", "f", false, i18n.T("cmd.vm.logs.flag.follow")) parent.AddCommand(logsCmd) } @@ -308,7 +291,7 @@ func addVMLogsCommand(parent *cobra.Command) { func viewLogs(id string, follow bool) error { manager, err := container.NewLinuxKitManager() if err != nil { - return fmt.Errorf("failed to initialize container manager: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.init_manager")+": %w", err) } fullID, err := resolveContainerID(manager, id) @@ -319,7 +302,7 @@ func viewLogs(id string, follow bool) error { ctx := context.Background() reader, err := manager.Logs(ctx, fullID, follow) if err != nil { - return fmt.Errorf("failed to get logs: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.get_logs")+": %w", err) } defer reader.Close() @@ -331,14 +314,11 @@ func viewLogs(id string, follow bool) error { func addVMExecCommand(parent *cobra.Command) { execCmd := &cobra.Command{ Use: "exec [args...]", - Short: "Execute a command in a VM", - Long: "Execute a command inside a running VM via SSH.\n\n" + - "Examples:\n" + - " core vm exec abc12345 ls -la\n" + - " core vm exec abc1 /bin/sh", + Short: i18n.T("cmd.vm.exec.short"), + Long: i18n.T("cmd.vm.exec.long"), RunE: func(cmd *cobra.Command, args []string) error { if len(args) < 2 { - return fmt.Errorf("container ID and command are required") + return fmt.Errorf(i18n.T("cmd.vm.error.id_and_cmd_required")) } return execInContainer(args[0], args[1:]) }, @@ -350,7 +330,7 @@ func addVMExecCommand(parent *cobra.Command) { func execInContainer(id string, cmd []string) error { manager, err := container.NewLinuxKitManager() if err != nil { - return fmt.Errorf("failed to initialize container manager: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.init_manager")+": %w", err) } fullID, err := resolveContainerID(manager, id) diff --git a/cmd/vm/templates.go b/cmd/vm/templates.go index ed544d3..783e00d 100644 --- a/cmd/vm/templates.go +++ b/cmd/vm/templates.go @@ -10,6 +10,7 @@ import ( "text/tabwriter" "github.com/host-uk/core/pkg/container" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -17,14 +18,8 @@ import ( func addVMTemplatesCommand(parent *cobra.Command) { templatesCmd := &cobra.Command{ Use: "templates", - Short: "Manage LinuxKit templates", - Long: "Manage LinuxKit YAML templates for building VMs.\n\n" + - "Templates provide pre-configured LinuxKit configurations for common use cases.\n" + - "They support variable substitution with ${VAR} and ${VAR:-default} syntax.\n\n" + - "Examples:\n" + - " core vm templates # List available templates\n" + - " core vm templates show core-dev # Show template content\n" + - " core vm templates vars server-php # Show template variables", + Short: i18n.T("cmd.vm.templates.short"), + Long: i18n.T("cmd.vm.templates.long"), RunE: func(cmd *cobra.Command, args []string) error { return listTemplates() }, @@ -41,14 +36,11 @@ func addVMTemplatesCommand(parent *cobra.Command) { func addTemplatesShowCommand(parent *cobra.Command) { showCmd := &cobra.Command{ Use: "show ", - Short: "Display template content", - Long: "Display the content of a LinuxKit template.\n\n" + - "Examples:\n" + - " core templates show core-dev\n" + - " core templates show server-php", + Short: i18n.T("cmd.vm.templates.show.short"), + Long: i18n.T("cmd.vm.templates.show.long"), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return fmt.Errorf("template name is required") + return fmt.Errorf(i18n.T("cmd.vm.error.template_required")) } return showTemplate(args[0]) }, @@ -61,15 +53,11 @@ func addTemplatesShowCommand(parent *cobra.Command) { func addTemplatesVarsCommand(parent *cobra.Command) { varsCmd := &cobra.Command{ Use: "vars ", - Short: "Show template variables", - Long: "Display all variables used in a template.\n\n" + - "Shows required variables (no default) and optional variables (with defaults).\n\n" + - "Examples:\n" + - " core templates vars core-dev\n" + - " core templates vars server-php", + Short: i18n.T("cmd.vm.templates.vars.short"), + Long: i18n.T("cmd.vm.templates.vars.long"), RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { - return fmt.Errorf("template name is required") + return fmt.Errorf(i18n.T("cmd.vm.error.template_required")) } return showTemplateVars(args[0]) }, @@ -82,14 +70,14 @@ func listTemplates() error { templates := container.ListTemplates() if len(templates) == 0 { - fmt.Println("No templates available.") + fmt.Println(i18n.T("cmd.vm.templates.no_templates")) return nil } - fmt.Printf("%s\n\n", repoNameStyle.Render("Available LinuxKit Templates")) + fmt.Printf("%s\n\n", repoNameStyle.Render(i18n.T("cmd.vm.templates.title"))) w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "NAME\tDESCRIPTION") + fmt.Fprintln(w, i18n.T("cmd.vm.templates.header")) fmt.Fprintln(w, "----\t-----------") for _, tmpl := range templates { @@ -102,9 +90,9 @@ func listTemplates() error { w.Flush() fmt.Println() - fmt.Printf("Show template: %s\n", dimStyle.Render("core vm templates show ")) - fmt.Printf("Show variables: %s\n", dimStyle.Render("core vm templates vars ")) - fmt.Printf("Run from template: %s\n", dimStyle.Render("core vm run --template --var SSH_KEY=\"...\"")) + fmt.Printf("%s %s\n", i18n.T("cmd.vm.templates.hint.show"), dimStyle.Render("core vm templates show ")) + fmt.Printf("%s %s\n", i18n.T("cmd.vm.templates.hint.vars"), dimStyle.Render("core vm templates vars ")) + fmt.Printf("%s %s\n", i18n.T("cmd.vm.templates.hint.run"), dimStyle.Render("core vm run --template --var SSH_KEY=\"...\"")) return nil } @@ -115,7 +103,7 @@ func showTemplate(name string) error { return err } - fmt.Printf("%s %s\n\n", dimStyle.Render("Template:"), repoNameStyle.Render(name)) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.vm.label.template")), repoNameStyle.Render(name)) fmt.Println(content) return nil @@ -129,10 +117,10 @@ func showTemplateVars(name string) error { required, optional := container.ExtractVariables(content) - fmt.Printf("%s %s\n\n", dimStyle.Render("Template:"), repoNameStyle.Render(name)) + fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.vm.label.template")), repoNameStyle.Render(name)) if len(required) > 0 { - fmt.Printf("%s\n", errorStyle.Render("Required Variables (no default):")) + fmt.Printf("%s\n", errorStyle.Render(i18n.T("cmd.vm.templates.vars.required"))) for _, v := range required { fmt.Printf(" %s\n", varStyle.Render("${"+v+"}")) } @@ -140,7 +128,7 @@ func showTemplateVars(name string) error { } if len(optional) > 0 { - fmt.Printf("%s\n", successStyle.Render("Optional Variables (with defaults):")) + fmt.Printf("%s\n", successStyle.Render(i18n.T("cmd.vm.templates.vars.optional"))) for v, def := range optional { fmt.Printf(" %s = %s\n", varStyle.Render("${"+v+"}"), @@ -150,7 +138,7 @@ func showTemplateVars(name string) error { } if len(required) == 0 && len(optional) == 0 { - fmt.Println("No variables in this template.") + fmt.Println(i18n.T("cmd.vm.templates.vars.none")) } return nil @@ -161,63 +149,63 @@ func RunFromTemplate(templateName string, vars map[string]string, runOpts contai // Apply template with variables content, err := container.ApplyTemplate(templateName, vars) if err != nil { - return fmt.Errorf("failed to apply template: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.apply_template")+": %w", err) } // Create a temporary directory for the build tmpDir, err := os.MkdirTemp("", "core-linuxkit-*") if err != nil { - return fmt.Errorf("failed to create temp directory: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.create_temp")+": %w", err) } defer os.RemoveAll(tmpDir) // Write the YAML file yamlPath := filepath.Join(tmpDir, templateName+".yml") if err := os.WriteFile(yamlPath, []byte(content), 0644); err != nil { - return fmt.Errorf("failed to write template: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.write_template")+": %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Template:"), repoNameStyle.Render(templateName)) - fmt.Printf("%s %s\n", dimStyle.Render("Building:"), yamlPath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.template")), repoNameStyle.Render(templateName)) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.building")), yamlPath) // Build the image using linuxkit outputPath := filepath.Join(tmpDir, templateName) if err := buildLinuxKitImage(yamlPath, outputPath); err != nil { - return fmt.Errorf("failed to build image: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.build_image")+": %w", err) } // Find the built image (linuxkit creates .iso or other format) imagePath := findBuiltImage(outputPath) if imagePath == "" { - return fmt.Errorf("no image found after build") + return fmt.Errorf(i18n.T("cmd.vm.error.no_image_found")) } - fmt.Printf("%s %s\n", dimStyle.Render("Image:"), imagePath) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.image")), imagePath) fmt.Println() // Run the image manager, err := container.NewLinuxKitManager() if err != nil { - return fmt.Errorf("failed to initialize container manager: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.init_manager")+": %w", err) } - fmt.Printf("%s %s\n", dimStyle.Render("Hypervisor:"), manager.Hypervisor().Name()) + fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.hypervisor")), manager.Hypervisor().Name()) fmt.Println() ctx := context.Background() c, err := manager.Run(ctx, imagePath, runOpts) if err != nil { - return fmt.Errorf("failed to run container: %w", err) + return fmt.Errorf(i18n.T("cmd.vm.error.run_container")+": %w", err) } if runOpts.Detach { - fmt.Printf("%s %s\n", successStyle.Render("Started:"), c.ID) - fmt.Printf("%s %d\n", dimStyle.Render("PID:"), c.PID) + fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.vm.label.started")), c.ID) + fmt.Printf("%s %d\n", dimStyle.Render(i18n.T("cmd.vm.label.pid")), c.PID) fmt.Println() - fmt.Printf("Use 'core vm logs %s' to view output\n", c.ID[:8]) - fmt.Printf("Use 'core vm stop %s' to stop\n", c.ID[:8]) + fmt.Println(i18n.T("cmd.vm.hint.view_logs", map[string]interface{}{"ID": c.ID[:8]})) + fmt.Println(i18n.T("cmd.vm.hint.stop", map[string]interface{}{"ID": c.ID[:8]})) } else { - fmt.Printf("\n%s %s\n", dimStyle.Render("Container stopped:"), c.ID) + fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.T("cmd.vm.label.container_stopped")), c.ID) } return nil @@ -298,7 +286,7 @@ func lookupLinuxKit() (string, error) { } } - return "", fmt.Errorf("linuxkit not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit") + return "", fmt.Errorf(i18n.T("cmd.vm.error.linuxkit_not_found")) } // ParseVarFlags parses --var flags into a map. diff --git a/cmd/vm/vm.go b/cmd/vm/vm.go index 51d7386..33eba40 100644 --- a/cmd/vm/vm.go +++ b/cmd/vm/vm.go @@ -4,6 +4,7 @@ package vm import ( "github.com/charmbracelet/lipgloss" "github.com/host-uk/core/cmd/shared" + "github.com/host-uk/core/pkg/i18n" "github.com/spf13/cobra" ) @@ -25,17 +26,8 @@ var ( func AddVMCommands(root *cobra.Command) { vmCmd := &cobra.Command{ Use: "vm", - Short: "LinuxKit VM management", - Long: "Manage LinuxKit virtual machines.\n\n" + - "LinuxKit VMs are lightweight, immutable VMs built from YAML templates.\n" + - "They run using qemu or hyperkit depending on your system.\n\n" + - "Commands:\n" + - " run Run a VM from image or template\n" + - " ps List running VMs\n" + - " stop Stop a running VM\n" + - " logs View VM logs\n" + - " exec Execute command in VM\n" + - " templates Manage LinuxKit templates", + Short: i18n.T("cmd.vm.short"), + Long: i18n.T("cmd.vm.long"), } root.AddCommand(vmCmd) diff --git a/pkg/i18n/locales/en.json b/pkg/i18n/locales/en.json index a2b729a..bcefb8b 100644 --- a/pkg/i18n/locales/en.json +++ b/pkg/i18n/locales/en.json @@ -59,15 +59,97 @@ }, "cmd.dev.short": "Multi-repo development workflow", - "cmd.dev.work.short": "Combined status, commit, and push workflow", - "cmd.dev.health.short": "Quick repo health summary", - "cmd.dev.commit.short": "Claude-assisted commit messages", - "cmd.dev.push.short": "Push repos with unpushed commits", - "cmd.dev.pull.short": "Pull repos behind remote", - "cmd.dev.issues.short": "List open issues across repos", - "cmd.dev.reviews.short": "List PRs awaiting review", - "cmd.dev.ci.short": "Check GitHub Actions status", - "cmd.dev.impact.short": "Analyse dependency impact", + "cmd.dev.long": "Manage multiple git repositories and GitHub integration.\n\nUses repos.yaml to discover repositories. Falls back to scanning\nthe current directory if no registry is found.\n\nGit Operations:\n work Combined status -> commit -> push workflow\n health Quick repo health summary\n commit Claude-assisted commit messages\n push Push repos with unpushed commits\n pull Pull repos behind remote\n\nGitHub Integration (requires gh CLI):\n issues List open issues across repos\n reviews List PRs awaiting review\n ci Check GitHub Actions status\n impact Analyse dependency impact\n\nDev Environment:\n install Download dev environment image\n boot Start dev environment VM\n stop Stop dev environment VM\n shell Open shell in dev VM\n status Check dev VM status", + + "cmd.dev.work.short": "Multi-repo git operations", + "cmd.dev.work.long": "Manage git status, commits, and pushes across multiple repositories.\n\nReads repos.yaml to discover repositories and their relationships.\nShows status, optionally commits with Claude, and pushes changes.", + "cmd.dev.work.flag.status": "Show status only, don't push", + "cmd.dev.work.flag.commit": "Use Claude to commit dirty repos before pushing", + "cmd.dev.work.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + + "cmd.dev.health.short": "Quick health check across all repos", + "cmd.dev.health.long": "Shows a summary of repository health:\ntotal repos, dirty repos, unpushed commits, etc.", + "cmd.dev.health.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.dev.health.flag.verbose": "Show detailed breakdown", + + "cmd.dev.commit.short": "Claude-assisted commits across repos", + "cmd.dev.commit.long": "Uses Claude to create commits for dirty repos.\nShows uncommitted changes and invokes Claude to generate commit messages.", + "cmd.dev.commit.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.dev.commit.flag.all": "Commit all dirty repos without prompting", + + "cmd.dev.push.short": "Push commits across all repos", + "cmd.dev.push.long": "Pushes unpushed commits for all repos.\nShows repos with commits to push and confirms before pushing.", + "cmd.dev.push.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.dev.push.flag.force": "Skip confirmation prompt", + + "cmd.dev.pull.short": "Pull updates across all repos", + "cmd.dev.pull.long": "Pulls updates for all repos.\nBy default only pulls repos that are behind. Use --all to pull all repos.", + "cmd.dev.pull.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.dev.pull.flag.all": "Pull all repos, not just those behind", + + "cmd.dev.issues.short": "List open issues across all repos", + "cmd.dev.issues.long": "Fetches open issues from GitHub for all repos in the registry.\nRequires the 'gh' CLI to be installed and authenticated.", + "cmd.dev.issues.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.dev.issues.flag.limit": "Max issues per repo", + "cmd.dev.issues.flag.assignee": "Filter by assignee (use @me for yourself)", + + "cmd.dev.reviews.short": "List PRs needing review across all repos", + "cmd.dev.reviews.long": "Fetches open PRs from GitHub for all repos in the registry.\nShows review status (approved, changes requested, pending).\nRequires the 'gh' CLI to be installed and authenticated.", + "cmd.dev.reviews.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.dev.reviews.flag.author": "Filter by PR author", + "cmd.dev.reviews.flag.all": "Show all PRs including drafts", + + "cmd.dev.ci.short": "Check CI status across all repos", + "cmd.dev.ci.long": "Fetches GitHub Actions workflow status for all repos.\nShows latest run status for each repo.\nRequires the 'gh' CLI to be installed and authenticated.", + "cmd.dev.ci.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.dev.ci.flag.branch": "Filter by branch", + "cmd.dev.ci.flag.failed": "Show only failed runs", + + "cmd.dev.impact.short": "Show impact of changing a repo", + "cmd.dev.impact.long": "Analyzes the dependency graph to show which repos\nwould be affected by changes to the specified repo.", + "cmd.dev.impact.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + + "cmd.dev.api.short": "Tools for managing service APIs", + "cmd.dev.sync.short": "Synchronizes public service APIs with internal implementations", + "cmd.dev.sync.long": "This command scans the 'pkg' directory for services and ensures that the\ntop-level public API for each service is in sync with its internal implementation.\nIt automatically generates the necessary Go files with type aliases.", + + "cmd.dev.vm.install.short": "Download and install the dev environment image", + "cmd.dev.vm.install.long": "Downloads the platform-specific dev environment image.\n\nThe image includes Go, PHP, Node.js, Python, Docker, and Claude CLI.\nDownloads are cached at ~/.core/images/\n\nExamples:\n core dev install", + + "cmd.dev.vm.boot.short": "Start the dev environment", + "cmd.dev.vm.boot.long": "Boots the dev environment VM.\n\nExamples:\n core dev boot\n core dev boot --memory 8192 --cpus 4\n core dev boot --fresh", + "cmd.dev.vm.boot.flag.memory": "Memory in MB (default: 4096)", + "cmd.dev.vm.boot.flag.cpus": "Number of CPUs (default: 2)", + "cmd.dev.vm.boot.flag.fresh": "Stop existing and start fresh", + + "cmd.dev.vm.stop.short": "Stop the dev environment", + "cmd.dev.vm.stop.long": "Stops the running dev environment VM.\n\nExamples:\n core dev stop", + + "cmd.dev.vm.status.short": "Show dev environment status", + "cmd.dev.vm.status.long": "Shows the current status of the dev environment.\n\nExamples:\n core dev vm-status", + + "cmd.dev.vm.shell.short": "Connect to the dev environment", + "cmd.dev.vm.shell.long": "Opens an interactive shell in the dev environment.\n\nUses SSH by default, or serial console with --console.\n\nExamples:\n core dev shell\n core dev shell --console\n core dev shell -- ls -la", + "cmd.dev.vm.shell.flag.console": "Use serial console instead of SSH", + + "cmd.dev.vm.serve.short": "Mount project and start dev server", + "cmd.dev.vm.serve.long": "Mounts the current project into the dev environment and starts a dev server.\n\nAuto-detects the appropriate serve command based on project files.\n\nExamples:\n core dev serve\n core dev serve --port 3000\n core dev serve --path public", + "cmd.dev.vm.serve.flag.port": "Port to serve on (default: 8000)", + "cmd.dev.vm.serve.flag.path": "Subdirectory to serve", + + "cmd.dev.vm.test.short": "Run tests in the dev environment", + "cmd.dev.vm.test.long": "Runs tests in the dev environment.\n\nAuto-detects the test command based on project files, or uses .core/test.yaml.\n\nExamples:\n core dev test\n core dev test --name integration\n core dev test -- go test -v ./...", + "cmd.dev.vm.test.flag.name": "Run named test command from .core/test.yaml", + + "cmd.dev.vm.claude.short": "Start sandboxed Claude session", + "cmd.dev.vm.claude.long": "Starts a Claude Code session inside the dev environment sandbox.\n\nProvides isolation while forwarding selected credentials.\nAuto-boots the dev environment if not running.\n\nAuth options (default: all):\n gh - GitHub CLI auth\n anthropic - Anthropic API key\n ssh - SSH agent forwarding\n git - Git config (name, email)\n\nExamples:\n core dev claude\n core dev claude --model opus\n core dev claude --auth gh,anthropic\n core dev claude --no-auth", + "cmd.dev.vm.claude.flag.no_auth": "Don't forward any auth credentials", + "cmd.dev.vm.claude.flag.model": "Model to use (opus, sonnet)", + "cmd.dev.vm.claude.flag.auth": "Selective auth forwarding (gh,anthropic,ssh,git)", + + "cmd.dev.vm.update.short": "Check for and apply updates", + "cmd.dev.vm.update.long": "Checks for dev environment updates and optionally applies them.\n\nExamples:\n core dev update\n core dev update --apply", + "cmd.dev.vm.update.flag.apply": "Download and apply the update", "cmd.dev.status.dirty": "dirty", "cmd.dev.status.clean": "clean", @@ -86,74 +168,1159 @@ "cmd.dev.commit.success": "Committed successfully", "cmd.dev.commit.failed": "Commit failed", - "cmd.php.short": "Laravel/PHP development tools", - "cmd.php.dev.short": "Start development servers", - "cmd.php.test.short": "Run PHPUnit tests", - "cmd.php.fmt.short": "Format code with Pint", - "cmd.php.analyse.short": "Run static analysis", - "cmd.php.build.short": "Build production assets", - "cmd.php.deploy.short": "Deploy to Coolify", - "cmd.php.qa.short": "Run all quality checks", + "cmd.dev.registry_label": "Registry:", + "cmd.dev.scanning_label": "Scanning:", + "cmd.dev.no_git_repos": "No git repositories found.", + "cmd.dev.no_changes": "No uncommitted changes found.", + "cmd.dev.repos_with_changes": "{{.Count}} repo(s) with uncommitted changes:", + "cmd.dev.modified": "{{.Count}} modified", + "cmd.dev.untracked": "{{.Count}} untracked", + "cmd.dev.staged": "{{.Count}} staged", + "cmd.dev.confirm_claude_commit": "Have Claude commit these repos?", + "cmd.dev.committing": "Committing", + "cmd.dev.committed": "committed", + "cmd.dev.done_succeeded": "Done: {{.Count}} succeeded", + "cmd.dev.count_failed": "{{.Count}} failed", - "cmd.php.test.running": "Running tests", + "cmd.dev.push.all_up_to_date": "All repos up to date. Nothing to push.", + "cmd.dev.push.repos_with_unpushed": "{{.Count}} repo(s) with unpushed commits:", + "cmd.dev.push.commits_count": "{{.Count}} commit(s)", + "cmd.dev.push.confirm_push": "Push {{.Commits}} commit(s) to {{.Repos}} repo(s)?", + "cmd.dev.push.done_pushed": "Done: {{.Count}} pushed", + + "cmd.dev.pull.all_up_to_date": "All repos up to date. Nothing to pull.", + "cmd.dev.pull.pulling_repos": "Pulling {{.Count}} repo(s):", + "cmd.dev.pull.repos_behind": "{{.Count}} repo(s) behind upstream:", + "cmd.dev.pull.commits_behind": "{{.Count}} commit(s) behind", + "cmd.dev.pull.pulling": "Pulling", + "cmd.dev.pull.done_pulled": "Done: {{.Count}} pulled", + + "cmd.dev.health.dirty_label": "Dirty:", + "cmd.dev.health.ahead_label": "Ahead:", + "cmd.dev.health.behind_label": "Behind:", + "cmd.dev.health.errors_label": "Errors:", + "cmd.dev.health.repos": "repos", + "cmd.dev.health.dirty": "dirty", + "cmd.dev.health.to_push": "to push", + "cmd.dev.health.synced": "synced", + "cmd.dev.health.up_to_date": "up to date", + "cmd.dev.health.to_pull": "to pull", + "cmd.dev.health.errors": "errors", + "cmd.dev.health.more": "+{{.Count}} more", + + "cmd.dev.work.table_repo": "Repo", + "cmd.dev.work.table_modified": "Modified", + "cmd.dev.work.table_untracked": "Untracked", + "cmd.dev.work.table_staged": "Staged", + "cmd.dev.work.table_ahead": "Ahead", + "cmd.dev.work.error_prefix": "error:", + "cmd.dev.work.use_commit_flag": "Use --commit to have Claude create commits", + "cmd.dev.work.all_up_to_date": "All repos up to date.", + "cmd.dev.work.repos_with_unpushed": "{{.Count}} repo(s) with unpushed commits:", + "cmd.dev.work.commits_count": "{{.Count}} commit(s)", + + "cmd.dev.issues.no_issues": "No open issues found.", + "cmd.dev.issues.open_issues": "{{.Count}} open issue(s):", + "cmd.dev.issues.error_label": "Error:", + + "cmd.dev.reviews.no_prs": "No open PRs found.", + "cmd.dev.reviews.open_prs": "{{.Count}} open PR(s)", + "cmd.dev.reviews.pending": "{{.Count}} pending", + "cmd.dev.reviews.approved": "{{.Count}} approved", + "cmd.dev.reviews.changes_requested": "{{.Count}} changes requested", + "cmd.dev.reviews.status_approved": "v approved", + "cmd.dev.reviews.status_changes": "* changes requested", + "cmd.dev.reviews.status_pending": "o pending review", + "cmd.dev.reviews.draft": "[draft]", + + "cmd.dev.ci.repos_checked": "{{.Count}} repos checked", + "cmd.dev.ci.passing": "{{.Count}} passing", + "cmd.dev.ci.failing": "{{.Count}} failing", + "cmd.dev.ci.pending": "{{.Count}} pending", + "cmd.dev.ci.no_ci": "{{.Count}} no CI", + "cmd.dev.ci.error_label": "Error:", + + "cmd.dev.impact.analysis_for": "Impact analysis for", + "cmd.dev.impact.no_dependents": "No repos depend on {{.Name}}", + "cmd.dev.impact.direct_dependents": "{{.Count}} direct dependent(s):", + "cmd.dev.impact.transitive_dependents": "{{.Count}} transitive dependent(s):", + "cmd.dev.impact.summary": "Summary:", + "cmd.dev.impact.changes_affect": "Changes to {{.Repo}} affect {{.Affected}}/{{.Total}} repos", + "cmd.dev.impact.requires_registry": "impact analysis requires repos.yaml with dependency information", + + "cmd.dev.sync.success": "Public APIs synchronized successfully.", + "cmd.dev.sync.error_prefix": "Error:", + + "cmd.dev.vm.already_installed": "Dev environment already installed", + "cmd.dev.vm.check_updates": "Use {{.Command}} to check for updates", + "cmd.dev.vm.image_label": "Image:", + "cmd.dev.vm.downloading": "Downloading dev environment...", + "cmd.dev.vm.progress_label": "Progress:", + "cmd.dev.vm.installed_in": "Installed in {{.Duration}}", + "cmd.dev.vm.start_with": "Start with: {{.Command}}", + "cmd.dev.vm.not_installed": "dev environment not installed (run 'core dev install' first)", + "cmd.dev.vm.config_label": "Config:", + "cmd.dev.vm.config_value": "{{.Memory}}MB, {{.CPUs}} CPUs", + "cmd.dev.vm.booting": "Booting dev environment...", + "cmd.dev.vm.running": "Dev environment running", + "cmd.dev.vm.connect_with": "Connect with: {{.Command}}", + "cmd.dev.vm.ssh_port": "SSH port:", + "cmd.dev.vm.not_running": "Dev environment is not running", + "cmd.dev.vm.stopping": "Stopping dev environment...", + "cmd.dev.vm.stopped": "Stopped", + "cmd.dev.vm.status_title": "Dev Environment Status", + "cmd.dev.vm.installed_label": "Installed:", + "cmd.dev.vm.installed_yes": "Yes", + "cmd.dev.vm.installed_no": "No", + "cmd.dev.vm.version_label": "Version:", + "cmd.dev.vm.install_with": "Install with: {{.Command}}", + "cmd.dev.vm.status_label": "Status:", + "cmd.dev.vm.status_running": "Running", + "cmd.dev.vm.status_stopped": "Stopped", + "cmd.dev.vm.container_label": "Container:", + "cmd.dev.vm.memory_label": "Memory:", + "cmd.dev.vm.cpus_label": "CPUs:", + "cmd.dev.vm.uptime_label": "Uptime:", + "cmd.dev.vm.checking_updates": "Checking for updates...", + "cmd.dev.vm.current_label": "Current:", + "cmd.dev.vm.latest_label": "Latest:", + "cmd.dev.vm.up_to_date": "Already up to date", + "cmd.dev.vm.update_available": "Update available", + "cmd.dev.vm.run_to_update": "Run {{.Command}} to update", + "cmd.dev.vm.stopping_current": "Stopping current instance...", + "cmd.dev.vm.downloading_update": "Downloading update...", + "cmd.dev.vm.updated_in": "Updated in {{.Duration}}", + + "cmd.php.short": "Laravel/PHP development tools", + "cmd.php.long": "Manage Laravel development environment with FrankenPHP.\n\nServices orchestrated:\n - FrankenPHP/Octane (port 8000, HTTPS on 443)\n - Vite dev server (port 5173)\n - Laravel Horizon (queue workers)\n - Laravel Reverb (WebSocket, port 8080)\n - Redis (port 6379)", + + "cmd.php.dev.short": "Start Laravel development environment", + "cmd.php.dev.long": "Starts all detected Laravel services.\n\nAuto-detects:\n - Vite (vite.config.js/ts)\n - Horizon (config/horizon.php)\n - Reverb (config/reverb.php)\n - Redis (from .env)", + "cmd.php.dev.starting": "Starting {{.AppName}} development environment", + "cmd.php.dev.detected_services": "Detected services:", + "cmd.php.dev.services_started": "Services started:", + "cmd.php.dev.shutting_down": "Shutting down...", + "cmd.php.dev.all_stopped": "All services stopped", + "cmd.php.dev.press_ctrl_c": "Press Ctrl+C to stop all services", + "cmd.php.dev.logs_failed": "Failed to get logs: {{.Error}}", + "cmd.php.dev.stop_error": "Error stopping services: {{.Error}}", + + "cmd.php.logs.short": "View service logs", + "cmd.php.logs.long": "Stream logs from Laravel services.\n\nServices: frankenphp, vite, horizon, reverb, redis", + + "cmd.php.stop.short": "Stop all Laravel services", + "cmd.php.stop.stopping": "Stopping services...", + + "cmd.php.status.short": "Show service status", + "cmd.php.status.project": "Project:", + "cmd.php.status.detected_services": "Detected services:", + "cmd.php.status.package_manager": "Package manager:", + "cmd.php.status.octane_server": "Octane server:", + "cmd.php.status.ssl_certs": "SSL certificates:", + "cmd.php.status.ssl_installed": "installed", + "cmd.php.status.ssl_not_setup": "not setup", + "cmd.php.status.running": "running", + "cmd.php.status.stopped": "stopped", + "cmd.php.status.error": "error: {{.Error}}", + "cmd.php.status.port": "port {{.Port}}", + "cmd.php.status.pid": "pid {{.PID}}", + + "cmd.php.ssl.short": "Setup SSL certificates with mkcert", + "cmd.php.ssl.setting_up": "Setting up SSL for {{.Domain}}", + "cmd.php.ssl.mkcert_not_installed": "mkcert is not installed", + "cmd.php.ssl.install_with": "Install with:", + "cmd.php.ssl.install_macos": "macOS: brew install mkcert", + "cmd.php.ssl.install_linux": "Linux: see https://github.com/FiloSottile/mkcert", + "cmd.php.ssl.certs_exist": "Certificates already exist", + "cmd.php.ssl.certs_created": "SSL certificates created", + "cmd.php.ssl.cert_label": "Cert:", + "cmd.php.ssl.key_label": "Key:", + "cmd.php.ssl.flag.domain": "Domain for certificate (default: from APP_URL)", + + "cmd.php.dev.flag.no_vite": "Skip Vite dev server", + "cmd.php.dev.flag.no_horizon": "Skip Laravel Horizon", + "cmd.php.dev.flag.no_reverb": "Skip Laravel Reverb", + "cmd.php.dev.flag.no_redis": "Skip Redis server", + "cmd.php.dev.flag.https": "Enable HTTPS with mkcert", + "cmd.php.dev.flag.domain": "Domain for SSL certificate (default: from APP_URL or localhost)", + "cmd.php.dev.flag.port": "FrankenPHP port (default: 8000)", + + "cmd.php.logs.flag.follow": "Follow log output", + "cmd.php.logs.flag.service": "Specific service (default: all)", + + "cmd.php.build.short": "Build Docker or LinuxKit image", + "cmd.php.build.long": "Build a production-ready container image for the PHP project.\n\nBy default, builds a Docker image using FrankenPHP.\nUse --type linuxkit to build a LinuxKit VM image instead.", + "cmd.php.build.building_docker": "Building Docker image...", + "cmd.php.build.building_linuxkit": "Building LinuxKit image...", + "cmd.php.build.php_version": "PHP Version:", + "cmd.php.build.laravel": "Laravel:", + "cmd.php.build.octane": "Octane:", + "cmd.php.build.frontend": "Frontend:", + "cmd.php.build.extensions": "Extensions:", + "cmd.php.build.image": "Image:", + "cmd.php.build.platform": "Platform:", + "cmd.php.build.template": "Template:", + "cmd.php.build.format": "Format:", + "cmd.php.build.docker_success": "Docker image built successfully", + "cmd.php.build.docker_run_with": "Run with:", + "cmd.php.build.linuxkit_success": "LinuxKit image built successfully", + "cmd.php.build.flag.type": "Build type: docker (default) or linuxkit", + "cmd.php.build.flag.name": "Image name (default: project directory name)", + "cmd.php.build.flag.tag": "Image tag (default: latest)", + "cmd.php.build.flag.platform": "Target platform (e.g., linux/amd64, linux/arm64)", + "cmd.php.build.flag.dockerfile": "Path to custom Dockerfile", + "cmd.php.build.flag.output": "Output path for LinuxKit image", + "cmd.php.build.flag.format": "LinuxKit output format: qcow2 (default), iso, raw, vmdk", + "cmd.php.build.flag.template": "LinuxKit template name (default: server-php)", + "cmd.php.build.flag.no_cache": "Build without cache", + + "cmd.php.serve.short": "Run production container", + "cmd.php.serve.long": "Run a production PHP container.\n\nThis starts the built Docker image in production mode.", + "cmd.php.serve.running": "Running production container...", + "cmd.php.serve.ports": "Ports:", + "cmd.php.serve.stopped": "Container stopped", + "cmd.php.serve.name_required": "--name is required: specify the Docker image name", + "cmd.php.serve.flag.name": "Docker image name (required)", + "cmd.php.serve.flag.tag": "Image tag (default: latest)", + "cmd.php.serve.flag.container": "Container name", + "cmd.php.serve.flag.port": "HTTP port (default: 80)", + "cmd.php.serve.flag.https_port": "HTTPS port (default: 443)", + "cmd.php.serve.flag.detach": "Run in detached mode", + "cmd.php.serve.flag.env_file": "Path to environment file", + + "cmd.php.shell.short": "Open shell in running container", + "cmd.php.shell.long": "Open an interactive shell in a running PHP container.", + "cmd.php.shell.opening": "Opening shell in container {{.Container}}...", + + "cmd.php.packages.short": "Manage local PHP packages", + "cmd.php.packages.long": "Link and manage local PHP packages for development.\n\nSimilar to npm link, this adds path repositories to composer.json\nfor developing packages alongside your project.", + "cmd.php.packages.link.short": "Link local packages", + "cmd.php.packages.link.long": "Link local PHP packages for development.\n\nAdds path repositories to composer.json with symlink enabled.\nThe package name is auto-detected from each path's composer.json.", + "cmd.php.packages.link.linking": "Linking packages...", + "cmd.php.packages.link.done": "Packages linked. Run 'composer update' to install.", + "cmd.php.packages.unlink.short": "Unlink packages", + "cmd.php.packages.unlink.long": "Remove linked packages from composer.json.\n\nRemoves path repositories by package name.", + "cmd.php.packages.unlink.unlinking": "Unlinking packages...", + "cmd.php.packages.unlink.done": "Packages unlinked. Run 'composer update' to remove.", + "cmd.php.packages.update.short": "Update linked packages", + "cmd.php.packages.update.long": "Run composer update for linked packages.\n\nIf no packages specified, updates all packages.", + "cmd.php.packages.update.updating": "Updating packages...", + "cmd.php.packages.update.done": "Packages updated", + "cmd.php.packages.list.short": "List linked packages", + "cmd.php.packages.list.long": "List all locally linked packages.\n\nShows package name, path, and version for each linked package.", + "cmd.php.packages.list.none_found": "No linked packages found", + "cmd.php.packages.list.linked": "Linked packages:", + "cmd.php.packages.list.unknown": "(unknown)", + "cmd.php.packages.list.path": "Path:", + "cmd.php.packages.list.version": "Version:", + + "cmd.php.test.short": "Run PHP tests (PHPUnit/Pest)", + "cmd.php.test.long": "Run PHP tests using PHPUnit or Pest.\n\nAuto-detects Pest if tests/Pest.php exists, otherwise uses PHPUnit.", + "cmd.php.test.running": "Running tests with {{.Runner}}", "cmd.php.test.passed": "All tests passed", "cmd.php.test.failed": "Some tests failed", + "cmd.php.test.flag.parallel": "Run tests in parallel", + "cmd.php.test.flag.coverage": "Generate code coverage", + "cmd.php.test.flag.filter": "Filter tests by name pattern", + "cmd.php.test.flag.group": "Run only tests in specified group", + + "cmd.php.fmt.short": "Format PHP code with Laravel Pint", + "cmd.php.fmt.long": "Format PHP code using Laravel Pint.", + "cmd.php.fmt.checking": "Checking code with {{.Formatter}}", + "cmd.php.fmt.formatting": "Formatting code with {{.Formatter}}", + "cmd.php.fmt.no_formatter": "no formatter found (install Laravel Pint: composer require laravel/pint --dev)", + "cmd.php.fmt.success": "Code formatted successfully", + "cmd.php.fmt.no_issues": "No formatting issues found", + "cmd.php.fmt.flag.fix": "Auto-fix formatting issues", + "cmd.php.fmt.flag.diff": "Show diff of changes", + + "cmd.php.analyse.short": "Run PHPStan static analysis", + "cmd.php.analyse.long": "Run PHPStan or Larastan static analysis.\n\nAuto-detects Larastan if installed, otherwise uses PHPStan.", + "cmd.php.analyse.running": "Running static analysis with {{.Analyser}}", + "cmd.php.analyse.no_analyser": "no static analyser found (install PHPStan: composer require phpstan/phpstan --dev)", + "cmd.php.analyse.no_issues": "No issues found", + "cmd.php.analyse.flag.level": "PHPStan analysis level (0-9)", + "cmd.php.analyse.flag.memory": "Memory limit (e.g., 2G)", + + "cmd.php.psalm.short": "Run Psalm static analysis", + "cmd.php.psalm.long": "Run Psalm deep static analysis with Laravel plugin support.\n\nPsalm provides deeper type inference than PHPStan and catches\ndifferent classes of bugs. Both should be run for best coverage.", + "cmd.php.psalm.not_found": "Psalm not found", + "cmd.php.psalm.install": "composer require --dev vimeo/psalm", + "cmd.php.psalm.setup": "./vendor/bin/psalm --init", + "cmd.php.psalm.analysing": "Analysing code with Psalm", + "cmd.php.psalm.analysing_fixing": "Analysing and fixing code with Psalm", + "cmd.php.psalm.no_issues": "No issues found", + "cmd.php.psalm.flag.level": "Error level (1=strictest, 8=most lenient)", + "cmd.php.psalm.flag.fix": "Auto-fix issues where possible", + "cmd.php.psalm.flag.baseline": "Generate/update baseline file", + "cmd.php.psalm.flag.show_info": "Show info-level issues", + + "cmd.php.audit.short": "Security audit for dependencies", + "cmd.php.audit.long": "Check PHP and JavaScript dependencies for known vulnerabilities.\n\nRuns composer audit and npm audit (if package.json exists).", + "cmd.php.audit.scanning": "Scanning dependencies for vulnerabilities", + "cmd.php.audit.secure": "secure", + "cmd.php.audit.error": "error", + "cmd.php.audit.vulnerabilities": "{{.Count}} vulnerabilities", + "cmd.php.audit.found_vulns": "Found {{.Count}} vulnerabilities across dependencies", + "cmd.php.audit.fix_hint": "composer update && npm update", + "cmd.php.audit.completed_errors": "audit completed with errors", + "cmd.php.audit.all_secure": "All dependencies are secure", + "cmd.php.audit.flag.json": "Output in JSON format", + "cmd.php.audit.flag.fix": "Auto-fix vulnerabilities (npm only)", + + "cmd.php.security.short": "Security vulnerability scanning", + "cmd.php.security.long": "Scan for security vulnerabilities in configuration and code.\n\nChecks environment config, file permissions, code patterns,\nand runs security-focused static analysis.", + "cmd.php.security.running": "Running security checks", + "cmd.php.security.checks_suffix": " CHECKS:", + "cmd.php.security.summary": "Security scan complete", + "cmd.php.security.passed": "Passed:", + "cmd.php.security.critical": "Critical:", + "cmd.php.security.high": "High:", + "cmd.php.security.medium": "Medium:", + "cmd.php.security.low": "Low:", + "cmd.php.security.fix_label": "Fix:", + "cmd.php.security.flag.severity": "Minimum severity (critical, high, medium, low)", + "cmd.php.security.flag.json": "Output in JSON format", + "cmd.php.security.flag.sarif": "Output in SARIF format (for GitHub Security)", + "cmd.php.security.flag.url": "URL to check HTTP headers (optional)", + + "cmd.php.qa.short": "Run full QA pipeline", + "cmd.php.qa.long": "Run the complete quality assurance pipeline.\n\nStages:\n quick: Security audit, code style, PHPStan\n standard: Psalm, tests\n full: Rector dry-run, mutation testing (slow)", + "cmd.php.qa.running": "Running QA pipeline ({{.Stages}})", + "cmd.php.qa.no_checks": "No checks available", + "cmd.php.qa.stage_prefix": "═══ ", + "cmd.php.qa.stage_suffix": " STAGE ═══", + "cmd.php.qa.passed": "passed", + "cmd.php.qa.failed": "failed", + "cmd.php.qa.all_passed": "All checks passed ({{.Passed}}/{{.Total}})", + "cmd.php.qa.some_failed": "Some checks failed ({{.Passed}}/{{.Total}} passed)", + "cmd.php.qa.to_fix": "To fix:", + "cmd.php.qa.pipeline_failed": "QA pipeline failed", + "cmd.php.qa.fix_audit": "composer update && npm update", + "cmd.php.qa.fix_phpstan": "Fix PHPStan errors shown above", + "cmd.php.qa.fix_psalm": "Fix Psalm errors shown above", + "cmd.php.qa.fix_tests": "Fix failing tests shown above", + "cmd.php.qa.fix_infection": "Improve test coverage for mutated code", + "cmd.php.qa.issue_vulns": "vulnerabilities", + "cmd.php.qa.issue_style": "Code style issues found", + "cmd.php.qa.issue_analysis": "Static analysis errors", + "cmd.php.qa.issue_types": "Type errors found", + "cmd.php.qa.issue_tests": "Test failures", + "cmd.php.qa.issue_rector": "Code improvements available", + "cmd.php.qa.issue_mutation": "Mutation score below threshold", + "cmd.php.qa.flag.quick": "Only run quick checks", + "cmd.php.qa.flag.full": "Run all stages including slow checks", + "cmd.php.qa.flag.fix": "Auto-fix issues where possible", + + "cmd.php.rector.short": "Automated code refactoring", + "cmd.php.rector.long": "Run Rector for automated code improvements and PHP upgrades.\n\nRector can automatically upgrade PHP syntax, improve code quality,\nand apply framework-specific refactorings.", + "cmd.php.rector.not_found": "Rector not found", + "cmd.php.rector.install": "composer require --dev rector/rector", + "cmd.php.rector.setup": "./vendor/bin/rector init", + "cmd.php.rector.analysing": "Analysing code with Rector", + "cmd.php.rector.refactoring": "Refactoring code with Rector", + "cmd.php.rector.changes_suggested": "Changes suggested (use --fix to apply)", + "cmd.php.rector.refactored": "Code refactored successfully", + "cmd.php.rector.no_changes": "No changes needed", + "cmd.php.rector.flag.fix": "Apply changes (default is dry-run)", + "cmd.php.rector.flag.diff": "Show detailed diff of changes", + "cmd.php.rector.flag.clear_cache": "Clear Rector cache before running", + + "cmd.php.infection.short": "Mutation testing for test quality", + "cmd.php.infection.long": "Run Infection mutation testing to measure test suite quality.\n\nMutation testing modifies your code and checks if tests catch\nthe changes. High mutation score = high quality tests.\n\nWarning: This can be slow on large codebases.", + "cmd.php.infection.not_found": "Infection not found", + "cmd.php.infection.install": "composer require --dev infection/infection", + "cmd.php.infection.running": "Running mutation testing", + "cmd.php.infection.note": "This may take a while...", + "cmd.php.infection.complete": "Mutation testing complete", + "cmd.php.infection.flag.min_msi": "Minimum mutation score indicator (0-100, default: 50)", + "cmd.php.infection.flag.min_covered_msi": "Minimum covered mutation score (0-100, default: 70)", + "cmd.php.infection.flag.threads": "Number of parallel threads (default: 4)", + "cmd.php.infection.flag.filter": "Filter files by pattern", + "cmd.php.infection.flag.only_covered": "Only mutate covered code", + + "cmd.php.deploy.short": "Deploy to Coolify", + "cmd.php.deploy.long": "Deploy the PHP application to Coolify.\n\nRequires configuration in .env:\n COOLIFY_URL=https://coolify.example.com\n COOLIFY_TOKEN=your-api-token\n COOLIFY_APP_ID=production-app-id\n COOLIFY_STAGING_APP_ID=staging-app-id (optional)", + "cmd.php.deploy.deploying": "Deploying to {{.Environment}}...", + "cmd.php.deploy.success": "Deployment completed successfully", + "cmd.php.deploy.warning_status": "Deployment ended with status: {{.Status}}", + "cmd.php.deploy.triggered": "Deployment triggered. Use 'core php deploy:status' to check progress.", + "cmd.php.deploy.flag.staging": "Deploy to staging environment", + "cmd.php.deploy.flag.force": "Force deployment even if no changes detected", + "cmd.php.deploy.flag.wait": "Wait for deployment to complete", + + "cmd.php.deploy_status.short": "Show deployment status", + "cmd.php.deploy_status.long": "Show the status of a deployment.", + "cmd.php.deploy_status.checking": "Checking {{.Environment}} deployment status...", + "cmd.php.deploy_status.flag.staging": "Check staging environment", + "cmd.php.deploy_status.flag.id": "Specific deployment ID", + + "cmd.php.deploy_rollback.short": "Rollback to previous deployment", + "cmd.php.deploy_rollback.long": "Rollback to a previous deployment.\n\nIf no deployment ID is specified, rolls back to the most recent\nsuccessful deployment.", + "cmd.php.deploy_rollback.rolling_back": "Rolling back {{.Environment}}...", + "cmd.php.deploy_rollback.success": "Rollback completed successfully", + "cmd.php.deploy_rollback.warning_status": "Rollback ended with status: {{.Status}}", + "cmd.php.deploy_rollback.triggered": "Rollback triggered. Use 'core php deploy:status' to check progress.", + "cmd.php.deploy_rollback.flag.staging": "Rollback staging environment", + "cmd.php.deploy_rollback.flag.id": "Specific deployment ID to rollback to", + "cmd.php.deploy_rollback.flag.wait": "Wait for rollback to complete", + + "cmd.php.deploy_list.short": "List recent deployments", + "cmd.php.deploy_list.long": "List recent deployments.", + "cmd.php.deploy_list.recent": "Recent {{.Environment}} deployments:", + "cmd.php.deploy_list.none_found": "No deployments found", + "cmd.php.deploy_list.flag.staging": "List staging deployments", + "cmd.php.deploy_list.flag.limit": "Number of deployments to list (default: 10)", + + "cmd.php.label.php": "PHP:", + "cmd.php.label.services": "Services:", + "cmd.php.label.running": "Running:", + "cmd.php.label.app_url": "App URL:", + "cmd.php.label.vite": "Vite:", + "cmd.php.label.warning": "Warning:", + "cmd.php.label.error": "Error:", + "cmd.php.label.done": "Done:", + "cmd.php.label.skip": "Skip:", + "cmd.php.label.info": "Info:", + "cmd.php.label.install": "Install:", + "cmd.php.label.setup": "Setup:", + "cmd.php.label.fix": "Fix:", + "cmd.php.label.audit": "Audit:", + "cmd.php.label.security": "Security:", + "cmd.php.label.summary": "Summary:", + "cmd.php.label.qa": "QA:", + "cmd.php.label.psalm": "Psalm:", + "cmd.php.label.rector": "Rector:", + "cmd.php.label.infection": "Infection:", + "cmd.php.label.deploy": "Deploy:", + "cmd.php.label.status": "Status:", + "cmd.php.label.id": "ID:", + "cmd.php.label.url": "URL:", + "cmd.php.label.branch": "Branch:", + "cmd.php.label.commit": "Commit:", + "cmd.php.label.message": "Message:", + "cmd.php.label.started": "Started:", + "cmd.php.label.completed": "Completed:", + "cmd.php.label.duration": "Duration:", + + "cmd.php.error.not_laravel": "not a Laravel project (missing artisan or laravel/framework)", + "cmd.php.error.not_laravel_short": "not a Laravel project", + "cmd.php.error.not_php": "not a PHP project (missing composer.json)", + "cmd.php.error.working_dir": "failed to get working directory", + "cmd.php.error.start_services": "failed to start services", + "cmd.php.error.stop_services": "failed to stop services", + "cmd.php.error.get_logs": "failed to get logs", + "cmd.php.error.detect_config": "failed to detect project configuration", + "cmd.php.error.build_failed": "build failed", + "cmd.php.error.start_container": "failed to start container", + "cmd.php.error.open_shell": "failed to open shell", + "cmd.php.error.link_packages": "failed to link packages", + "cmd.php.error.unlink_packages": "failed to unlink packages", + "cmd.php.error.update_packages": "composer update failed", + "cmd.php.error.list_packages": "failed to list packages", + "cmd.php.error.tests_failed": "tests failed", + "cmd.php.error.fmt_failed": "formatting failed", + "cmd.php.error.fmt_issues": "formatting issues found", + "cmd.php.error.analysis_issues": "analysis found issues", + "cmd.php.error.psalm_issues": "psalm found issues", + "cmd.php.error.psalm_not_installed": "psalm not installed", + "cmd.php.error.audit_failed": "audit failed", + "cmd.php.error.vulns_found": "vulnerabilities found", + "cmd.php.error.security_failed": "security check failed", + "cmd.php.error.critical_high_issues": "critical or high severity issues found", + "cmd.php.error.rector_failed": "rector failed", + "cmd.php.error.rector_not_installed": "rector not installed", + "cmd.php.error.infection_failed": "mutation testing failed", + "cmd.php.error.infection_not_installed": "infection not installed", + "cmd.php.error.mkcert_not_installed": "mkcert not installed", + "cmd.php.error.ssl_setup": "failed to setup SSL", + "cmd.php.error.deploy_failed": "deployment failed", + "cmd.php.error.status_failed": "failed to get status", + "cmd.php.error.rollback_failed": "rollback failed", + "cmd.php.error.list_deployments": "failed to list deployments", "cmd.go.short": "Go development tools", - "cmd.go.test.short": "Run tests with coverage", - "cmd.go.fmt.short": "Format Go code", - "cmd.go.build.short": "Build Go binaries", - "cmd.go.cov.short": "Run tests with coverage report", + "cmd.go.long": "Go development tools with enhanced output and environment setup.\n\nCommands:\n test Run tests\n cov Run tests with coverage report\n fmt Format Go code\n lint Run golangci-lint\n install Install Go binary\n mod Module management (tidy, download, verify)\n work Workspace management", + "cmd.go.test.short": "Run tests with coverage", + "cmd.go.test.long": "Run Go tests with coverage reporting.\n\nSets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings.\nFilters noisy output and provides colour-coded coverage.\n\nExamples:\n core go test\n core go test --coverage\n core go test --pkg ./pkg/crypt\n core go test --run TestHash", + "cmd.go.test.label": "Test:", "cmd.go.test.running": "Running tests", - "cmd.go.test.passed": "All tests passed", - "cmd.go.test.failed": "Some tests failed", + "cmd.go.test.package_label": "Package:", + "cmd.go.test.passed": "{{.Count}} passed", + "cmd.go.test.passed_failed": "{{.Passed}} passed, {{.Failed}} failed", + "cmd.go.test.all_passed": "PASS All tests passed", + "cmd.go.test.some_failed": "FAIL Some tests failed", "cmd.go.test.coverage": "Coverage", + "cmd.go.test.flag.coverage": "Show detailed per-package coverage", + "cmd.go.test.flag.pkg": "Package to test (default: ./...)", + "cmd.go.test.flag.run": "Run only tests matching regexp", + "cmd.go.test.flag.short": "Run only short tests", + "cmd.go.test.flag.race": "Enable race detector", + "cmd.go.test.flag.json": "Output JSON results", + "cmd.go.test.flag.verbose": "Verbose output", + + "cmd.go.cov.short": "Run tests with coverage report", + "cmd.go.cov.long": "Run tests and generate coverage report.\n\nExamples:\n core go cov # Run with coverage summary\n core go cov --html # Generate HTML report\n core go cov --open # Generate and open HTML report\n core go cov --threshold 80 # Fail if coverage < 80%", + "cmd.go.cov.label": "Coverage:", + "cmd.go.cov.running": "Running tests with coverage", + "cmd.go.cov.html_label": "HTML:", + "cmd.go.cov.open_manually": "(open manually)", + "cmd.go.cov.below_threshold": "FAIL Coverage {{.Actual}}% is below threshold {{.Threshold}}%", + "cmd.go.cov.error.discover": "failed to discover test packages", + "cmd.go.cov.error.no_packages": "no test packages found", + "cmd.go.cov.error.create_file": "failed to create coverage file", + "cmd.go.cov.error.get_coverage": "failed to get coverage", + "cmd.go.cov.error.generate_html": "failed to generate HTML", + "cmd.go.cov.error.below_threshold": "coverage below threshold", + "cmd.go.cov.flag.pkg": "Package to test (default: ./...)", + "cmd.go.cov.flag.html": "Generate HTML coverage report", + "cmd.go.cov.flag.open": "Generate and open HTML report in browser", + "cmd.go.cov.flag.threshold": "Minimum coverage percentage (exit 1 if below)", + + "cmd.go.fmt.short": "Format Go code", + "cmd.go.fmt.long": "Format Go code using gofmt or goimports.\n\nExamples:\n core go fmt # Check formatting\n core go fmt --fix # Fix formatting\n core go fmt --diff # Show diff", + "cmd.go.fmt.flag.fix": "Fix formatting in place", + "cmd.go.fmt.flag.diff": "Show diff of changes", + "cmd.go.fmt.flag.check": "Check only, exit 1 if not formatted", + + "cmd.go.lint.short": "Run golangci-lint", + "cmd.go.lint.long": "Run golangci-lint on the codebase.\n\nExamples:\n core go lint\n core go lint --fix", + "cmd.go.lint.flag.fix": "Fix issues automatically", + + "cmd.go.install.short": "Install Go binary", + "cmd.go.install.long": "Install Go binary to $GOPATH/bin.\n\nExamples:\n core go install # Install current module\n core go install ./cmd/core # Install specific path\n core go install --no-cgo # Pure Go (no C dependencies)\n core go install -v # Verbose output", + "cmd.go.install.label": "Install:", + "cmd.go.install.installing": "Installing", + "cmd.go.install.path_label": "Path:", + "cmd.go.install.cgo_label": "CGO:", + "cmd.go.install.cgo_disabled": "disabled", + "cmd.go.install.failed": "FAIL Install failed", + "cmd.go.install.success": "OK", + "cmd.go.install.installed_to": "Installed to {{.Path}}", + "cmd.go.install.flag.verbose": "Verbose output", + "cmd.go.install.flag.no_cgo": "Disable CGO (CGO_ENABLED=0)", + + "cmd.go.mod.short": "Module management", + "cmd.go.mod.long": "Go module management commands.\n\nCommands:\n tidy Add missing and remove unused modules\n download Download modules to local cache\n verify Verify dependencies\n graph Print module dependency graph", + "cmd.go.mod.tidy.short": "Tidy go.mod", + "cmd.go.mod.download.short": "Download modules", + "cmd.go.mod.verify.short": "Verify dependencies", + "cmd.go.mod.graph.short": "Print dependency graph", + + "cmd.go.work.short": "Workspace management", + "cmd.go.work.long": "Go workspace management commands.\n\nCommands:\n sync Sync go.work with modules\n init Initialize go.work\n use Add module to workspace", + "cmd.go.work.sync.short": "Sync workspace", + "cmd.go.work.init.short": "Initialize workspace", + "cmd.go.work.use.short": "Add module to workspace", + "cmd.go.work.added": "Added {{.Module}}", + "cmd.go.work.error.no_modules": "no go.mod files found", + + "cmd.go.build.short": "Build Go binaries", "cmd.doctor.short": "Check development environment", + "cmd.doctor.long": "Checks that all required tools are installed and configured.\nRun this before 'core setup' to ensure your environment is ready.", "cmd.doctor.checking": "Checking development environment...", - "cmd.doctor.required": "Required", - "cmd.doctor.optional": "Optional", - "cmd.doctor.github": "GitHub Access", - "cmd.doctor.workspace": "Workspace", - "cmd.doctor.ready": "Environment ready", - "cmd.doctor.issues": "{{.Count}} issues found", + "cmd.doctor.required": "Required:", + "cmd.doctor.optional": "Optional:", + "cmd.doctor.github": "GitHub Access:", + "cmd.doctor.workspace": "Workspace:", + "cmd.doctor.ready": "Doctor: Environment ready", + "cmd.doctor.issues": "Doctor: {{.Count}} issues found", + "cmd.doctor.issues_error": "{{.Count}} required tools missing", + "cmd.doctor.install_missing": "Install missing tools:", + "cmd.doctor.verbose_flag": "Show detailed version information", + "cmd.doctor.ssh_found": "SSH key found", + "cmd.doctor.ssh_missing": "SSH key missing - run: ssh-keygen && gh ssh-key add", + "cmd.doctor.cli_auth": "CLI authenticated", + "cmd.doctor.cli_auth_missing": "CLI authentication - run: gh auth login", + "cmd.doctor.repos_yaml_found": "Found repos.yaml at {{.Path}}", + "cmd.doctor.repos_cloned": "{{.Cloned}}/{{.Total}} repos cloned", + "cmd.doctor.no_repos_yaml": "No repos.yaml found (run from workspace directory)", + "cmd.doctor.install_macos": "brew install git gh php composer node pnpm docker", + "cmd.doctor.install_macos_cask": "brew install --cask claude", + "cmd.doctor.install_linux_header": "# Install via your package manager or:", + "cmd.doctor.install_linux_git": "# Git: apt install git", + "cmd.doctor.install_linux_gh": "# GitHub CLI: https://cli.github.com/", + "cmd.doctor.install_linux_php": "# PHP: apt install php8.3-cli", + "cmd.doctor.install_linux_node": "# Node: https://nodejs.org/", + "cmd.doctor.install_linux_pnpm": "# pnpm: npm install -g pnpm", + "cmd.doctor.install_other": "See documentation for your OS", - "cmd.setup.short": "Set up development workspace", - "cmd.setup.cloning": "Cloning repositories...", + "cmd.doctor.check.git.name": "Git", + "cmd.doctor.check.git.description": "Version control", + "cmd.doctor.check.gh.name": "GitHub CLI", + "cmd.doctor.check.gh.description": "GitHub integration (issues, PRs, CI)", + "cmd.doctor.check.php.name": "PHP", + "cmd.doctor.check.php.description": "Laravel packages", + "cmd.doctor.check.composer.name": "Composer", + "cmd.doctor.check.composer.description": "PHP dependencies", + "cmd.doctor.check.node.name": "Node.js", + "cmd.doctor.check.node.description": "Frontend builds", + "cmd.doctor.check.pnpm.name": "pnpm", + "cmd.doctor.check.pnpm.description": "Fast package manager", + "cmd.doctor.check.claude.name": "Claude Code", + "cmd.doctor.check.claude.description": "AI-assisted development", + "cmd.doctor.check.docker.name": "Docker", + "cmd.doctor.check.docker.description": "Container runtime", + + "cmd.setup.short": "Bootstrap workspace or clone packages from registry", + "cmd.setup.long": "Sets up a development workspace.\n\nREGISTRY MODE (repos.yaml exists):\n Clones all repositories defined in repos.yaml into packages/.\n Skips repos that already exist. Use --only to filter by type.\n\nBOOTSTRAP MODE (no repos.yaml):\n 1. Clones core-devops to set up the workspace\n 2. Presents an interactive wizard to select packages\n 3. Clones selected packages\n\nUse --all to skip the wizard and clone everything.", + + "cmd.setup.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.setup.flag.only": "Only clone repos of these types (comma-separated: foundation,module,product)", + "cmd.setup.flag.dry_run": "Show what would be cloned without cloning", + "cmd.setup.flag.all": "Skip wizard, clone all packages (non-interactive)", + "cmd.setup.flag.name": "Project directory name for bootstrap mode", + "cmd.setup.flag.build": "Run build after cloning", + + "cmd.setup.bootstrap_mode": "Bootstrap mode (no repos.yaml found)", + "cmd.setup.cloning_current_dir": "Cloning into current directory", + "cmd.setup.creating_project_dir": "Creating project directory", + "cmd.setup.cloning": "Cloning", + "cmd.setup.cloned": "cloned", + "cmd.setup.would_clone": "Would clone", + "cmd.setup.already_exists": "already exists", + "cmd.setup.would_load_registry": "Would load registry from", + + "cmd.setup.registry_label": "Registry:", + "cmd.setup.org_label": "Org:", + "cmd.setup.target_label": "Target:", + "cmd.setup.filter_label": "Filter:", + + "cmd.setup.to_clone": "{{.Count}} to clone", + "cmd.setup.exist": "{{.Count}} exist", + "cmd.setup.skipped": "{{.Count}} skipped", + "cmd.setup.nothing_to_clone": "Nothing to clone.", + "cmd.setup.would_clone_list": "Would clone:", + "cmd.setup.cancelled": "Cancelled.", + "cmd.setup.done": "done", + "cmd.setup.done_label": "Done:", + "cmd.setup.cloned_count": "{{.Count}} cloned", + "cmd.setup.failed_count": "{{.Count}} failed", + "cmd.setup.already_exist_count": "{{.Count}} already exist", + "cmd.setup.running_build": "Running build...", "cmd.setup.complete": "Setup complete", - "cmd.build.short": "Build and release tools", - "cmd.build.building": "Building...", - "cmd.build.complete": "Build complete", + "cmd.setup.repo.setting_up": "Setting up repository", + "cmd.setup.repo.detected_type": "Detected project type", + "cmd.setup.repo.would_create": "Would create", + "cmd.setup.repo.created": "Created", - "cmd.ai.short": "AI task management", - "cmd.ai.tasks.short": "List tasks", - "cmd.ai.task.short": "View task details", + "cmd.setup.wizard.git_repo_title": "This directory is a git repository", + "cmd.setup.wizard.what_to_do": "What would you like to do?", + "cmd.setup.wizard.option_setup": "Setup Working Directory", + "cmd.setup.wizard.option_package": "Create Package (clone repos into subdirectory)", + "cmd.setup.wizard.project_name_title": "Project directory name", + "cmd.setup.wizard.project_name_desc": "Enter the name for your new workspace directory", + "cmd.setup.wizard.package_selection": "Package Selection", + "cmd.setup.wizard.selection_hint": "Use space to select/deselect, enter to confirm", + "cmd.setup.wizard.select_packages": "Select packages to clone", + "cmd.setup.wizard.confirm_clone": "Clone {{.Count}} packages to {{.Target}}?", + "cmd.setup.wizard.confirm_yes": "Yes, clone", + "cmd.setup.wizard.confirm_cancel": "Cancel", + + "cmd.setup.error.build_failed": "build failed", + + "cmd.build.short": "Build projects with auto-detection and cross-compilation", + "cmd.build.long": "Builds the current project with automatic type detection.\nSupports Go, Wails, Docker, LinuxKit, and Taskfile projects.\nConfiguration can be provided via .core/build.yaml or command-line flags.\n\nExamples:\n core build # Auto-detect and build\n core build --type docker # Build Docker image\n core build --type linuxkit # Build LinuxKit image\n core build --type linuxkit --config linuxkit.yml --format qcow2-bios", + + "cmd.build.from_path.short": "Build from a local directory", + "cmd.build.from_path.starting": "Starting build from path:", + "cmd.build.from_path.generating_template": "Generating application from template...", + "cmd.build.from_path.copying_files": "Copying application files...", + "cmd.build.from_path.compiling": "Compiling application...", + "cmd.build.from_path.success": "Build successful! Executable created at:", + "cmd.build.from_path.flag.path": "The path to the static web application files", + "cmd.build.from_path.error.invalid_path": "invalid path specified", + "cmd.build.from_path.error.must_be_directory": "path specified must be a directory", + "cmd.build.from_path.error.clean_build_dir": "failed to clean build directory", + "cmd.build.from_path.error.anchor_template": "failed to anchor template filesystem", + "cmd.build.from_path.error.create_sod": "failed to create new sod instance", + "cmd.build.from_path.error.extract_template": "failed to extract template", + "cmd.build.from_path.error.copy_files": "failed to copy application files", + "cmd.build.from_path.error.go_mod_tidy": "go mod tidy failed", + "cmd.build.from_path.error.go_build": "go build failed", + + "cmd.build.pwa.short": "Build from a live PWA URL", + "cmd.build.pwa.starting": "Starting PWA build from URL:", + "cmd.build.pwa.downloading_to": "Downloading PWA to temporary directory:", + "cmd.build.pwa.found_manifest": "Found manifest:", + "cmd.build.pwa.download_complete": "PWA download complete.", + "cmd.build.pwa.warning": "Warning:", + "cmd.build.pwa.no_manifest": "no manifest file found. Proceeding with basic site download.", + "cmd.build.pwa.asset_download_failed": "failed to download asset", + "cmd.build.pwa.flag.url": "The URL of the PWA to build", + "cmd.build.pwa.error.create_temp_dir": "failed to create temporary directory", + "cmd.build.pwa.error.download_failed": "failed to download PWA", + "cmd.build.pwa.error.fetch_url": "failed to fetch URL", + "cmd.build.pwa.error.read_response": "failed to read response body", + "cmd.build.pwa.error.write_index": "failed to write index.html", + "cmd.build.pwa.error.fetch_manifest": "failed to fetch or parse manifest", + "cmd.build.pwa.error.no_manifest_tag": "no tag found", + + "cmd.build.sdk.short": "Generate API SDKs from OpenAPI spec", + "cmd.build.sdk.long": "Generates typed API clients from OpenAPI specifications.\nSupports TypeScript, Python, Go, and PHP.\n\nExamples:\n core build sdk # Generate all configured SDKs\n core build sdk --lang typescript # Generate only TypeScript SDK\n core build sdk --spec api.yaml # Use specific OpenAPI spec", + "cmd.build.sdk.label": "Build SDK:", + "cmd.build.sdk.generating": "Generating SDKs", + "cmd.build.sdk.dry_run_mode": "(dry-run mode)", + "cmd.build.sdk.spec_label": "Spec:", + "cmd.build.sdk.language_label": "Language:", + "cmd.build.sdk.languages_label": "Languages:", + "cmd.build.sdk.generated_label": "Generated:", + "cmd.build.sdk.would_generate": "Would generate SDKs (dry-run)", + "cmd.build.sdk.complete": "SDK generation complete", + "cmd.build.sdk.flag.spec": "Path to OpenAPI spec file", + "cmd.build.sdk.flag.lang": "Generate only this language (typescript, python, go, php)", + "cmd.build.sdk.flag.version": "Version to embed in generated SDKs", + "cmd.build.sdk.flag.dry_run": "Show what would be generated without writing files", + + "cmd.build.flag.type": "Builder type (go, wails, docker, linuxkit, taskfile) - auto-detected if not specified", + "cmd.build.flag.ci": "CI mode - minimal output with JSON artifact list at the end", + "cmd.build.flag.targets": "Comma-separated OS/arch pairs (e.g., linux/amd64,darwin/arm64)", + "cmd.build.flag.output": "Output directory for artifacts (default: dist)", + "cmd.build.flag.archive": "Create archives (tar.gz for linux/darwin, zip for windows)", + "cmd.build.flag.checksum": "Generate SHA256 checksums and CHECKSUMS.txt", + "cmd.build.flag.config": "Config file path (for linuxkit: YAML config, for docker: Dockerfile)", + "cmd.build.flag.format": "Output format for linuxkit (iso-bios, qcow2-bios, raw, vmdk)", + "cmd.build.flag.push": "Push Docker image after build", + "cmd.build.flag.image": "Docker image name (e.g., host-uk/core-devops)", + "cmd.build.flag.no_sign": "Skip all code signing", + "cmd.build.flag.notarize": "Enable macOS notarization (requires Apple credentials)", + + "cmd.build.label.build": "Build:", + "cmd.build.label.type": "Type:", + "cmd.build.label.output": "Output:", + "cmd.build.label.binary": "Binary:", + "cmd.build.label.targets": "Targets:", + "cmd.build.label.error": "Error:", + "cmd.build.label.success": "Success:", + "cmd.build.label.sign": "Sign:", + "cmd.build.label.archive": "Archive:", + "cmd.build.label.checksum": "Checksum:", + "cmd.build.label.ok": "OK:", + + "cmd.build.building_project": "Building project", + "cmd.build.built_artifacts": "Built {{.Count}} artifact(s)", + "cmd.build.signing_binaries": "Signing binaries...", + "cmd.build.creating_archives": "Creating archives...", + "cmd.build.computing_checksums": "Computing checksums...", + + "cmd.build.error.working_dir": "failed to get working directory", + "cmd.build.error.load_config": "failed to load config", + "cmd.build.error.detect_type": "failed to detect project type", + "cmd.build.error.no_project_type": "no supported project type detected in {{.Dir}}\nSupported types: go (go.mod), wails (wails.json), node (package.json), php (composer.json)", + "cmd.build.error.build_failed": "build failed", + "cmd.build.error.signing_failed": "signing failed", + "cmd.build.error.notarization_failed": "notarization failed", + "cmd.build.error.archive_failed": "archive failed", + "cmd.build.error.marshal_artifacts": "failed to marshal artifacts", + "cmd.build.error.checksum_failed": "checksum failed", + "cmd.build.error.write_checksums": "failed to write CHECKSUMS.txt", + "cmd.build.error.gpg_signing_failed": "GPG signing failed", + "cmd.build.error.invalid_target": "invalid target format \"{{.Target}}\", expected OS/arch (e.g., linux/amd64)", + "cmd.build.error.no_targets": "no valid targets specified", + "cmd.build.error.node_not_implemented": "Node.js builder not yet implemented", + "cmd.build.error.php_not_implemented": "PHP builder not yet implemented", + "cmd.build.error.unsupported_type": "unsupported project type", + + "cmd.ai.short": "AI agent task management", + "cmd.ai.long": "Manage tasks from the core-agentic service for AI-assisted development.\n\nCommands:\n tasks List tasks (filterable by status, priority, labels)\n task View task details or auto-select highest priority\n task:update Update task status or progress\n task:complete Mark task as completed or failed\n task:commit Create git commit with task reference\n task:pr Create GitHub PR linked to task\n claude Claude Code integration\n\nWorkflow:\n core ai tasks # List pending tasks\n core ai task --auto --claim # Auto-select and claim a task\n core ai task:commit -m 'msg' # Commit with task reference\n core ai task:complete # Mark task done", + + "cmd.ai.claude.short": "Claude Code integration", + "cmd.ai.claude.long": "Tools for working with Claude Code.\n\nCommands:\n run Run Claude in the current directory\n config Manage Claude configuration", + "cmd.ai.claude.run.short": "Run Claude Code in the current directory", + "cmd.ai.claude.config.short": "Manage Claude configuration", + + "cmd.ai.tasks.short": "List available tasks from core-agentic", + "cmd.ai.tasks.long": "Lists tasks from the core-agentic service.\n\nConfiguration is loaded from:\n 1. Environment variables (AGENTIC_TOKEN, AGENTIC_BASE_URL)\n 2. .env file in current directory\n 3. ~/.core/agentic.yaml\n\nExamples:\n core ai tasks\n core ai tasks --status pending --priority high\n core ai tasks --labels bug,urgent", + "cmd.ai.tasks.none_found": "No tasks found.", + "cmd.ai.tasks.found": "{{.Count}} task(s) found:", + "cmd.ai.tasks.hint": "Use 'core ai task ' to view details", + "cmd.ai.tasks.flag.status": "Filter by status (pending, in_progress, completed, blocked)", + "cmd.ai.tasks.flag.priority": "Filter by priority (critical, high, medium, low)", + "cmd.ai.tasks.flag.labels": "Filter by labels (comma-separated)", + "cmd.ai.tasks.flag.limit": "Max number of tasks to return", + "cmd.ai.tasks.flag.project": "Filter by project", + + "cmd.ai.task.short": "Show task details or auto-select a task", + "cmd.ai.task.long": "Shows details of a specific task or auto-selects the highest priority task.\n\nExamples:\n core ai task abc123 # Show task details\n core ai task abc123 --claim # Show and claim the task\n core ai task abc123 --context # Show task with gathered context\n core ai task --auto # Auto-select highest priority pending task", + "cmd.ai.task.no_pending": "No pending tasks available.", + "cmd.ai.task.id_required": "task ID required (or use --auto)", + "cmd.ai.task.context_failed": "Failed to build context", + "cmd.ai.task.claiming": "Claiming task...", + "cmd.ai.task.claimed": "Task claimed successfully!", + "cmd.ai.task.flag.auto": "Auto-select highest priority pending task", + "cmd.ai.task.flag.claim": "Claim the task after showing details", + "cmd.ai.task.flag.context": "Show gathered context for AI collaboration", + + "cmd.ai.task_update.short": "Update task status or progress", + "cmd.ai.task_update.long": "Updates a task's status, progress, or adds notes.\n\nExamples:\n core ai task:update abc123 --status in_progress\n core ai task:update abc123 --progress 50 --notes 'Halfway done'", + "cmd.ai.task_update.flag_required": "at least one of --status, --progress, or --notes required", + "cmd.ai.task_update.success": "Task {{.ID}} updated successfully", + "cmd.ai.task_update.flag.status": "New status (pending, in_progress, completed, blocked)", + "cmd.ai.task_update.flag.progress": "Progress percentage (0-100)", + "cmd.ai.task_update.flag.notes": "Notes about the update", + + "cmd.ai.task_complete.short": "Mark a task as completed", + "cmd.ai.task_complete.long": "Marks a task as completed with optional output and artifacts.\n\nExamples:\n core ai task:complete abc123 --output 'Feature implemented'\n core ai task:complete abc123 --failed --error 'Build failed'", + "cmd.ai.task_complete.success": "Task {{.ID}} completed successfully", + "cmd.ai.task_complete.failed": "Task {{.ID}} marked as failed", + "cmd.ai.task_complete.flag.output": "Summary of the completed work", + "cmd.ai.task_complete.flag.failed": "Mark the task as failed", + "cmd.ai.task_complete.flag.error": "Error message if failed", + + "cmd.ai.task_commit.short": "Auto-commit changes with task reference", + "cmd.ai.task_commit.long": "Creates a git commit with a task reference and co-author attribution.\n\nCommit message format:\n feat(scope): description\n\n Task: #123\n Co-Authored-By: Claude \n\nExamples:\n core ai task:commit abc123 --message 'add user authentication'\n core ai task:commit abc123 -m 'fix login bug' --scope auth\n core ai task:commit abc123 -m 'update docs' --push", + "cmd.ai.task_commit.message_required": "commit message required (--message or -m)", + "cmd.ai.task_commit.no_changes": "No uncommitted changes to commit.", + "cmd.ai.task_commit.creating": "Creating commit for task {{.ID}}...", + "cmd.ai.task_commit.committed": "Committed:", + "cmd.ai.task_commit.pushing": "Pushing changes...", + "cmd.ai.task_commit.pushed": "Changes pushed successfully", + "cmd.ai.task_commit.flag.message": "Commit message (without task reference)", + "cmd.ai.task_commit.flag.scope": "Scope for the commit type (e.g., auth, api, ui)", + "cmd.ai.task_commit.flag.push": "Push changes after committing", + + "cmd.ai.task_pr.short": "Create a pull request for a task", + "cmd.ai.task_pr.long": "Creates a GitHub pull request linked to a task.\n\nRequires the GitHub CLI (gh) to be installed and authenticated.\n\nExamples:\n core ai task:pr abc123\n core ai task:pr abc123 --title 'Add authentication feature'\n core ai task:pr abc123 --draft --labels 'enhancement,needs-review'\n core ai task:pr abc123 --base develop", + "cmd.ai.task_pr.branch_error": "cannot create PR from {{.Branch}} branch; create a feature branch first", + "cmd.ai.task_pr.pushing_branch": "Pushing branch {{.Branch}}...", + "cmd.ai.task_pr.creating": "Creating pull request...", + "cmd.ai.task_pr.created": "Pull request created!", + "cmd.ai.task_pr.flag.title": "PR title (defaults to task title)", + "cmd.ai.task_pr.flag.draft": "Create as draft PR", + "cmd.ai.task_pr.flag.labels": "Labels to add (comma-separated)", + "cmd.ai.task_pr.flag.base": "Base branch (defaults to main)", + + "cmd.ai.priority.critical": "CRITICAL", + "cmd.ai.priority.high": "HIGH", + "cmd.ai.priority.medium": "MEDIUM", + "cmd.ai.priority.low": "LOW", + + "cmd.ai.status.pending": "pending", + "cmd.ai.status.in_progress": "in_progress", + "cmd.ai.status.completed": "completed", + "cmd.ai.status.blocked": "blocked", + + "cmd.ai.label.id": "ID:", + "cmd.ai.label.title": "Title:", + "cmd.ai.label.priority": "Priority:", + "cmd.ai.label.status": "Status:", + "cmd.ai.label.project": "Project:", + "cmd.ai.label.labels": "Labels:", + "cmd.ai.label.claimed_by": "Claimed by:", + "cmd.ai.label.created": "Created:", + "cmd.ai.label.description": "Description:", + "cmd.ai.label.related_files": "Related files:", + "cmd.ai.label.blocked_by": "Blocked by:", + "cmd.ai.label.url": "URL:", + + "cmd.ai.error.load_config": "failed to load config", + "cmd.ai.error.list_tasks": "failed to list tasks", + "cmd.ai.error.get_task": "failed to get task", + "cmd.ai.error.claim_task": "failed to claim task", + "cmd.ai.error.update_task": "failed to update task", + "cmd.ai.error.complete_task": "failed to complete task", + "cmd.ai.error.working_dir": "failed to get working directory", + "cmd.ai.error.git_status": "failed to check git status", + "cmd.ai.error.commit": "failed to commit", + "cmd.ai.error.push": "failed to push", + "cmd.ai.error.get_branch": "failed to get current branch", + "cmd.ai.error.push_branch": "failed to push branch", + "cmd.ai.error.create_pr": "failed to create PR", "cmd.vm.short": "LinuxKit VM management", - "cmd.vm.run.short": "Run a VM from image or template", + "cmd.vm.long": "Manage LinuxKit virtual machines.\n\nLinuxKit VMs are lightweight, immutable VMs built from YAML templates.\nThey run using qemu or hyperkit depending on your system.\n\nCommands:\n run Run a VM from image or template\n ps List running VMs\n stop Stop a running VM\n logs View VM logs\n exec Execute command in VM\n templates Manage LinuxKit templates", + + "cmd.vm.run.short": "Run a LinuxKit image or template", + "cmd.vm.run.long": "Runs a LinuxKit image as a VM using the available hypervisor.\n\nSupported image formats: .iso, .qcow2, .vmdk, .raw\n\nYou can also run from a template using --template, which will build and run\nthe image automatically. Use --var to set template variables.\n\nExamples:\n core vm run image.iso\n core vm run -d image.qcow2\n core vm run --name myvm --memory 2048 --cpus 4 image.iso\n core vm run --template core-dev --var SSH_KEY=\"ssh-rsa AAAA...\"\n core vm run --template server-php --var SSH_KEY=\"...\" --var DOMAIN=example.com", + "cmd.vm.run.flag.name": "Name for the container", + "cmd.vm.run.flag.detach": "Run in detached mode (background)", + "cmd.vm.run.flag.memory": "Memory in MB (default: 1024)", + "cmd.vm.run.flag.cpus": "Number of CPUs (default: 1)", + "cmd.vm.run.flag.ssh_port": "SSH port for exec commands (default: 2222)", + "cmd.vm.run.flag.template": "Run from a LinuxKit template (build + run)", + "cmd.vm.run.flag.var": "Template variable in KEY=VALUE format (can be repeated)", + "cmd.vm.run.error.image_required": "image path is required (or use --template)", + "cmd.vm.ps.short": "List running VMs", + "cmd.vm.ps.long": "Lists all VMs. By default, only shows running VMs.\n\nExamples:\n core vm ps\n core vm ps -a", + "cmd.vm.ps.flag.all": "Show all containers (including stopped)", + "cmd.vm.ps.header": "ID\tNAME\tIMAGE\tSTATUS\tSTARTED\tPID", + "cmd.vm.ps.no_containers": "No containers", + "cmd.vm.ps.no_running": "No running containers", + "cmd.vm.stop.short": "Stop a running VM", + "cmd.vm.stop.long": "Stops a running VM by ID.\n\nExamples:\n core vm stop abc12345\n core vm stop abc1", + "cmd.vm.stop.stopping": "Stopping:", + "cmd.vm.stop.stopped": "Stopped", + "cmd.vm.logs.short": "View VM logs", + "cmd.vm.logs.long": "View logs from a VM.\n\nExamples:\n core vm logs abc12345\n core vm logs -f abc1", + "cmd.vm.logs.flag.follow": "Follow log output", - "cmd.docs.short": "Documentation tools", - "cmd.docs.list.short": "List documentation files", - "cmd.docs.sync.short": "Sync docs to central location", + "cmd.vm.exec.short": "Execute a command in a VM", + "cmd.vm.exec.long": "Execute a command inside a running VM via SSH.\n\nExamples:\n core vm exec abc12345 ls -la\n core vm exec abc1 /bin/sh", - "cmd.pkg.short": "Package management", - "cmd.pkg.install.short": "Install packages", - "cmd.pkg.search.short": "Search packages", + "cmd.vm.templates.short": "Manage LinuxKit templates", + "cmd.vm.templates.long": "Manage LinuxKit YAML templates for building VMs.\n\nTemplates provide pre-configured LinuxKit configurations for common use cases.\nThey support variable substitution with ${VAR} and ${VAR:-default} syntax.\n\nExamples:\n core vm templates # List available templates\n core vm templates show core-dev # Show template content\n core vm templates vars server-php # Show template variables", + "cmd.vm.templates.title": "Available LinuxKit Templates", + "cmd.vm.templates.header": "NAME\tDESCRIPTION", + "cmd.vm.templates.no_templates": "No templates available.", + "cmd.vm.templates.hint.show": "Show template:", + "cmd.vm.templates.hint.vars": "Show variables:", + "cmd.vm.templates.hint.run": "Run from template:", + + "cmd.vm.templates.show.short": "Display template content", + "cmd.vm.templates.show.long": "Display the content of a LinuxKit template.\n\nExamples:\n core templates show core-dev\n core templates show server-php", + + "cmd.vm.templates.vars.short": "Show template variables", + "cmd.vm.templates.vars.long": "Display all variables used in a template.\n\nShows required variables (no default) and optional variables (with defaults).\n\nExamples:\n core templates vars core-dev\n core templates vars server-php", + "cmd.vm.templates.vars.required": "Required Variables (no default):", + "cmd.vm.templates.vars.optional": "Optional Variables (with defaults):", + "cmd.vm.templates.vars.none": "No variables in this template.", + + "cmd.vm.label.image": "Image:", + "cmd.vm.label.name": "Name:", + "cmd.vm.label.hypervisor": "Hypervisor:", + "cmd.vm.label.started": "Started:", + "cmd.vm.label.pid": "PID:", + "cmd.vm.label.container_stopped": "Container stopped:", + "cmd.vm.label.template": "Template:", + "cmd.vm.label.building": "Building:", + + "cmd.vm.hint.view_logs": "Use 'core vm logs {{.ID}}' to view output", + "cmd.vm.hint.stop": "Use 'core vm stop {{.ID}}' to stop", + + "cmd.vm.error.init_manager": "failed to initialize container manager", + "cmd.vm.error.run_container": "failed to run container", + "cmd.vm.error.list_containers": "failed to list containers", + "cmd.vm.error.stop_container": "failed to stop container", + "cmd.vm.error.get_logs": "failed to get logs", + "cmd.vm.error.id_required": "container ID is required", + "cmd.vm.error.id_and_cmd_required": "container ID and command are required", + "cmd.vm.error.no_match": "no container found matching: {{.ID}}", + "cmd.vm.error.multiple_match": "multiple containers match '{{.ID}}', be more specific", + "cmd.vm.error.template_required": "template name is required", + "cmd.vm.error.apply_template": "failed to apply template", + "cmd.vm.error.create_temp": "failed to create temp directory", + "cmd.vm.error.write_template": "failed to write template", + "cmd.vm.error.build_image": "failed to build image", + "cmd.vm.error.no_image_found": "no image found after build", + "cmd.vm.error.linuxkit_not_found": "linuxkit not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit", + + "cmd.docs.short": "Documentation management", + "cmd.docs.long": "Manage documentation across all repos.\nScan for docs, check coverage, and sync to core-php/docs/packages/.", + + "cmd.docs.list.short": "List documentation across repos", + "cmd.docs.list.long": "List documentation files for all repos in the workspace.\n\nShows README.md, CLAUDE.md, CHANGELOG.md, and docs/ directory status\nfor each repository.", + "cmd.docs.list.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.docs.list.header.repo": "Repo", + "cmd.docs.list.header.readme": "README", + "cmd.docs.list.header.claude": "CLAUDE", + "cmd.docs.list.header.changelog": "CHANGELOG", + "cmd.docs.list.header.docs": "docs/", + "cmd.docs.list.files_count": "{{.Count}} files", + "cmd.docs.list.coverage_label": "Coverage", + "cmd.docs.list.coverage_summary": "{{.WithDocs}} with docs, {{.WithoutDocs}} without", + + "cmd.docs.sync.short": "Sync documentation to core-php/docs/packages/", + "cmd.docs.sync.long": "Sync documentation from all repos to a central location.\n\nCopies docs/ directories from each package to core-php/docs/packages/\nfor unified documentation builds.", + "cmd.docs.sync.flag.registry": "Path to repos.yaml (auto-detected if not specified)", + "cmd.docs.sync.flag.dry_run": "Show what would be synced without copying", + "cmd.docs.sync.flag.output": "Output directory (default: core-php/docs/packages)", + "cmd.docs.sync.no_docs_found": "No documentation found in any repos.", + "cmd.docs.sync.found_label": "Found", + "cmd.docs.sync.repos_with_docs": "{{.Count}} repo(s) with docs/ directories", + "cmd.docs.sync.files_count": "({{.Count}} files)", + "cmd.docs.sync.total_label": "Total:", + "cmd.docs.sync.total_summary": "{{.Files}} files from {{.Repos}} repos → {{.Output}}", + "cmd.docs.sync.dry_run_notice": "Dry run - no files copied", + "cmd.docs.sync.confirm": "Sync?", + "cmd.docs.sync.done_label": "Done:", + "cmd.docs.sync.synced_packages": "Synced {{.Count}} packages", + + "cmd.docs.error.load_registry": "failed to load registry", + "cmd.docs.error.scan_directory": "failed to scan directory", + + "cmd.pkg.short": "Package management for core-* repos", + "cmd.pkg.long": "Manage host-uk/core-* packages and repositories.\n\nCommands:\n search Search GitHub for packages\n install Clone a package from GitHub\n list List installed packages\n update Update installed packages\n outdated Check for outdated packages", + "cmd.pkg.no_description": "(no description)", + + "cmd.pkg.search.short": "Search GitHub for packages", + "cmd.pkg.search.long": "Searches GitHub for repositories matching a pattern.\nUses gh CLI for authenticated search. Results are cached for 1 hour.\n\nExamples:\n core pkg search # List all host-uk repos\n core pkg search --pattern 'core-*' # Search for core-* repos\n core pkg search --org mycompany # Search different org\n core pkg search --refresh # Bypass cache", + "cmd.pkg.search.flag.org": "GitHub organization (default: host-uk)", + "cmd.pkg.search.flag.pattern": "Repo name pattern (* for wildcard)", + "cmd.pkg.search.flag.type": "Filter by type in name (mod, services, plug, website)", + "cmd.pkg.search.flag.limit": "Max results (default 50)", + "cmd.pkg.search.flag.refresh": "Bypass cache and fetch fresh data", + "cmd.pkg.search.cache_label": "Cache:", + "cmd.pkg.search.note_label": "Note:", + "cmd.pkg.search.fetching_label": "Fetching:", + "cmd.pkg.search.gh_token_warning": "GH_TOKEN env var is set - this may cause auth issues", + "cmd.pkg.search.gh_token_unset": "Unset it with: unset GH_TOKEN", + "cmd.pkg.search.no_repos_found": "No repositories found matching pattern.", + "cmd.pkg.search.found_repos": "Found {{.Count}} repositories:", + "cmd.pkg.search.private_label": "[private]", + "cmd.pkg.search.install_with": "Install with:", + + "cmd.pkg.install.short": "Clone a package from GitHub", + "cmd.pkg.install.long": "Clones a repository from GitHub.\n\nExamples:\n core pkg install host-uk/core-php\n core pkg install host-uk/core-tenant --dir ./packages\n core pkg install host-uk/core-admin --add", + "cmd.pkg.install.flag.dir": "Target directory (default: ./packages or current dir)", + "cmd.pkg.install.flag.add": "Add to repos.yaml registry", + "cmd.pkg.install.skip_label": "Skip:", + "cmd.pkg.install.already_exists": "{{.Name}} already exists at {{.Path}}", + "cmd.pkg.install.installing_label": "Installing:", + "cmd.pkg.install.target_label": "Target:", + "cmd.pkg.install.cloning": "Cloning", + "cmd.pkg.install.add_to_registry": "add to registry", + "cmd.pkg.install.added_to_registry": "added to repos.yaml", + "cmd.pkg.install.done_label": "Done:", + "cmd.pkg.install.installed": "Installed {{.Name}}", + + "cmd.pkg.list.short": "List installed packages", + "cmd.pkg.list.long": "Lists all packages in the current workspace.\n\nReads from repos.yaml or scans for git repositories.\n\nExamples:\n core pkg list", + "cmd.pkg.list.title": "Installed Packages", + "cmd.pkg.list.no_packages": "No packages in registry.", + "cmd.pkg.list.total_label": "Total:", + "cmd.pkg.list.summary": "{{.Installed}} installed, {{.Missing}} missing", + "cmd.pkg.list.install_missing": "Install missing:", + + "cmd.pkg.update.short": "Update installed packages", + "cmd.pkg.update.long": "Pulls latest changes for installed packages.\n\nExamples:\n core pkg update core-php # Update specific package\n core pkg update --all # Update all packages", + "cmd.pkg.update.flag.all": "Update all packages", + "cmd.pkg.update.update_label": "Update:", + "cmd.pkg.update.updating": "Updating {{.Count}} package(s)", + "cmd.pkg.update.not_installed": "not installed", + "cmd.pkg.update.up_to_date": "up to date", + "cmd.pkg.update.done_label": "Done:", + "cmd.pkg.update.summary": "{{.Updated}} updated, {{.Skipped}} skipped, {{.Failed}} failed", + + "cmd.pkg.outdated.short": "Check for outdated packages", + "cmd.pkg.outdated.long": "Checks which packages have unpulled commits.\n\nExamples:\n core pkg outdated", + "cmd.pkg.outdated.outdated_label": "Outdated:", + "cmd.pkg.outdated.checking": "Checking for updates...", + "cmd.pkg.outdated.commits_behind": "{{.Count}} commits behind", + "cmd.pkg.outdated.done_label": "Done:", + "cmd.pkg.outdated.all_up_to_date": "All packages up to date", + "cmd.pkg.outdated.summary_label": "Summary:", + "cmd.pkg.outdated.summary": "{{.Outdated}} outdated, {{.UpToDate}} up to date", + "cmd.pkg.outdated.update_with": "Update with:", + + "cmd.pkg.error.gh_not_authenticated": "gh CLI not authenticated. Run: gh auth login", + "cmd.pkg.error.auth_failed": "authentication failed - try: unset GH_TOKEN && gh auth login", + "cmd.pkg.error.search_failed": "search failed: %s", + "cmd.pkg.error.parse_results": "failed to parse results: %w", + "cmd.pkg.error.repo_required": "repository is required (e.g., core pkg install host-uk/core-php)", + "cmd.pkg.error.invalid_repo_format": "invalid repo format: use org/repo (e.g., host-uk/core-php)", + "cmd.pkg.error.create_directory": "failed to create directory: %w", + "cmd.pkg.error.no_repos_yaml": "no repos.yaml found", + "cmd.pkg.error.no_repos_yaml_workspace": "no repos.yaml found - run from workspace directory", + "cmd.pkg.error.load_registry": "failed to load registry: %w", + "cmd.pkg.error.specify_package": "specify package name or use --all", + + "cmd.ci.short": "Publish releases (dry-run by default)", + "cmd.ci.long": "Publishes pre-built artifacts from dist/ to configured targets.\nRun 'core build' first to create artifacts.\n\nSAFE BY DEFAULT: Runs in dry-run mode unless --we-are-go-for-launch is specified.\n\nConfiguration: .core/release.yaml", + + "cmd.ci.init.short": "Initialize release configuration", + "cmd.ci.init.long": "Creates a .core/release.yaml configuration file interactively.", + "cmd.ci.init.config_exists": "Configuration already exists at", + "cmd.ci.init.overwrite_prompt": "Overwrite? [y/N]: ", + "cmd.ci.init.creating": "Creating release configuration", + "cmd.ci.init.project_name": "Project name", + "cmd.ci.init.github_repo": "GitHub repository (owner/repo):", + "cmd.ci.init.config_written": "Configuration written to", + + "cmd.ci.changelog.short": "Generate changelog", + "cmd.ci.changelog.long": "Generates a changelog from conventional commits.", + "cmd.ci.changelog.flag.from": "Starting ref (default: previous tag)", + "cmd.ci.changelog.flag.to": "Ending ref (default: HEAD)", + + "cmd.ci.version.short": "Show or set version", + "cmd.ci.version.long": "Shows the determined version or validates a version string.", - "cmd.ci.short": "CI/CD tools", "cmd.ci.release.short": "Create a release", - "cmd.sdk.short": "SDK validation tools", - "cmd.sdk.diff.short": "Check for breaking API changes", - "cmd.sdk.validate.short": "Validate OpenAPI spec", + "cmd.ci.flag.go_for_launch": "Actually publish (default is dry-run for safety)", + "cmd.ci.flag.version": "Version to release (e.g., v1.2.3)", + "cmd.ci.flag.draft": "Create release as a draft", + "cmd.ci.flag.prerelease": "Mark release as a prerelease", - "cmd.test.short": "Test runner", - "cmd.test.running": "Running tests...", + "cmd.ci.publishing": "Publishing release", + "cmd.ci.dry_run_hint": "(dry-run) use --we-are-go-for-launch to publish", + "cmd.ci.go_for_launch": "GO FOR LAUNCH", + "cmd.ci.publish_completed": "Publish completed!", + + "cmd.ci.label.ci": "CI:", + "cmd.ci.label.init": "Init:", + "cmd.ci.label.note": "Note:", + "cmd.ci.label.error": "Error:", + "cmd.ci.label.success": "Success:", + "cmd.ci.label.version": "Version:", + "cmd.ci.label.artifacts": "Artifacts:", + "cmd.ci.label.published": "Published:", + + "cmd.ci.error.working_dir": "failed to get working directory", + "cmd.ci.error.load_config": "failed to load config", + "cmd.ci.error.write_config": "failed to write config", + "cmd.ci.error.no_publishers": "no publishers configured in .core/release.yaml", + "cmd.ci.error.generate_changelog": "failed to generate changelog", + "cmd.ci.error.determine_version": "failed to determine version", + + "cmd.sdk.short": "SDK validation and API compatibility tools", + "cmd.sdk.long": "Tools for validating OpenAPI specs and checking API compatibility.\nTo generate SDKs, use: core build sdk\n\nCommands:\n diff Check for breaking API changes\n validate Validate OpenAPI spec syntax", + + "cmd.sdk.diff.short": "Check for breaking API changes", + "cmd.sdk.diff.long": "Check for breaking API changes between spec versions.\n\nCompares a base spec (version tag or file) against the current spec\nand reports any breaking changes that would affect API consumers.", + "cmd.sdk.diff.flag.base": "Base spec (version tag or file)", + "cmd.sdk.diff.flag.spec": "Current spec file", + "cmd.sdk.diff.label": "SDK Diff:", + "cmd.sdk.diff.checking": "Checking for breaking changes", + "cmd.sdk.diff.base_label": "Base:", + "cmd.sdk.diff.current_label": "Current:", + "cmd.sdk.diff.breaking": "Breaking:", + "cmd.sdk.diff.error.base_required": "--base is required (version tag or file path)", + + "cmd.sdk.validate.short": "Validate OpenAPI spec", + "cmd.sdk.validate.long": "Validate OpenAPI spec syntax.\n\nChecks that the OpenAPI specification file is valid and well-formed.", + "cmd.sdk.validate.flag.spec": "Path to OpenAPI spec file", + "cmd.sdk.validate.validating": "Validating OpenAPI spec", + "cmd.sdk.validate.spec_label": "Spec:", + "cmd.sdk.validate.valid": "Spec is valid", + + "cmd.sdk.label.sdk": "SDK:", + "cmd.sdk.label.ok": "OK:", + "cmd.sdk.label.error": "Error:", + "cmd.sdk.error.working_dir": "failed to get working directory", + + "cmd.test.short": "Run tests with coverage", + "cmd.test.long": "Runs Go tests with coverage reporting.\n\nSets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings on macOS.\n\nExamples:\n core test # Run all tests with coverage summary\n core test --verbose # Show test output as it runs\n core test --coverage # Show detailed per-package coverage\n core test --pkg ./pkg/... # Test specific packages\n core test --run TestName # Run specific test by name\n core test --short # Skip long-running tests\n core test --race # Enable race detector\n core test --json # Output JSON for CI/agents", + "cmd.test.running": "Running tests", + "cmd.test.all_passed": "All tests passed", + "cmd.test.tests_failed": "Tests failed", + "cmd.test.passed": "{{.Count}} passed", + "cmd.test.failed": "{{.Count}} failed", + "cmd.test.skipped": "{{.Count}} skipped", + "cmd.test.failed_packages": "Failed packages:", + "cmd.test.coverage_by_package": "Coverage by package:", + + "cmd.test.flag.verbose": "Show test output as it runs (-v)", + "cmd.test.flag.coverage": "Show detailed per-package coverage", + "cmd.test.flag.short": "Skip long-running tests (-short)", + "cmd.test.flag.pkg": "Package pattern to test (default: ./...)", + "cmd.test.flag.run": "Run only tests matching this regex (-run)", + "cmd.test.flag.race": "Enable race detector (-race)", + "cmd.test.flag.json": "Output JSON for CI/agents", + + "cmd.test.label.test": "Test:", + "cmd.test.label.package": "Package:", + "cmd.test.label.filter": "Filter:", + "cmd.test.label.coverage": "Coverage:", + "cmd.test.label.average": "Average", + + "cmd.test.error.no_go_mod": "no go.mod found - run from a Go project directory", + "cmd.test.error.tests_failed": "tests failed", "error.not_found": "Not found: {{.Item}}", "error.invalid": "Invalid: {{.Item}}",