diff --git a/cmd/ci/ci_init.go b/cmd/ci/ci_init.go index d2dc62c4..445c45b3 100644 --- a/cmd/ci/ci_init.go +++ b/cmd/ci/ci_init.go @@ -30,7 +30,7 @@ func runCIReleaseInit() error { response, _ := reader.ReadString('\n') response = strings.TrimSpace(strings.ToLower(response)) if response != "y" && response != "yes" { - fmt.Println(i18n.T("cli.confirm.abort")) + fmt.Println(i18n.T("common.prompt.abort")) return nil } } diff --git a/cmd/dev/dev_ci.go b/cmd/dev/dev_ci.go index 75f350a1..220168ed 100644 --- a/cmd/dev/dev_ci.go +++ b/cmd/dev/dev_ci.go @@ -104,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(i18n.T("cli.progress.checking")), i+1, len(repoList), repo.Name) + fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.P("check")), i+1, len(repoList), repo.Name) runs, err := fetchWorkflowRuns(repoFullName, repo.Name, branch) if err != nil { diff --git a/cmd/dev/dev_issues.go b/cmd/dev/dev_issues.go index d42c9809..fc0e2afd 100644 --- a/cmd/dev/dev_issues.go +++ b/cmd/dev/dev_issues.go @@ -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(i18n.T("cli.progress.fetching")), i+1, len(repoList), repo.Name) + fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.P("fetch")), i+1, len(repoList), repo.Name) issues, err := fetchIssues(repoFullName, repo.Name, limit, assignee) if err != nil { diff --git a/cmd/dev/dev_reviews.go b/cmd/dev/dev_reviews.go index 8e7cad82..38b80f85 100644 --- a/cmd/dev/dev_reviews.go +++ b/cmd/dev/dev_reviews.go @@ -115,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(i18n.T("cli.progress.fetching")), i+1, len(repoList), repo.Name) + fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.P("fetch")), i+1, len(repoList), repo.Name) prs, err := fetchPRs(repoFullName, repo.Name, author) if err != nil { diff --git a/cmd/docs/sync.go b/cmd/docs/sync.go index bb505623..27428dc0 100644 --- a/cmd/docs/sync.go +++ b/cmd/docs/sync.go @@ -116,7 +116,7 @@ func runDocsSync(registryPath string, outputDir string, dryRun bool) error { // Confirm fmt.Println() if !confirm(i18n.T("cmd.docs.sync.confirm")) { - fmt.Println(i18n.T("cli.confirm.abort")) + fmt.Println(i18n.T("common.prompt.abort")) return nil } diff --git a/pkg/i18n/grammar.go b/pkg/i18n/grammar.go index e5c9b7cd..4b4224d3 100644 --- a/pkg/i18n/grammar.go +++ b/pkg/i18n/grammar.go @@ -492,14 +492,82 @@ func Quote(s string) string { // tmpl := template.New("").Funcs(i18n.TemplateFuncs()) func TemplateFuncs() template.FuncMap { return template.FuncMap{ - "title": Title, - "lower": strings.ToLower, - "upper": strings.ToUpper, - "past": PastTense, - "gerund": Gerund, - "plural": Pluralize, + "title": Title, + "lower": strings.ToLower, + "upper": strings.ToUpper, + "past": PastTense, + "gerund": Gerund, + "plural": Pluralize, "pluralForm": PluralForm, - "article": Article, - "quote": Quote, + "article": Article, + "quote": Quote, } } + +// Progress returns a progress message for a verb. +// Generates "Verbing..." form. +// +// Progress("build") // "Building..." +// Progress("check") // "Checking..." +// Progress("fetch") // "Fetching..." +func Progress(verb string) string { + g := Gerund(verb) + if g == "" { + return "" + } + return Title(g) + "..." +} + +// ProgressSubject returns a progress message with a subject. +// Generates "Verbing subject..." form. +// +// ProgressSubject("build", "project") // "Building project..." +// ProgressSubject("check", "config.yaml") // "Checking config.yaml..." +func ProgressSubject(verb, subject string) string { + g := Gerund(verb) + if g == "" { + return "" + } + return Title(g) + " " + subject + "..." +} + +// ActionResult returns a result message for a completed action. +// Generates "Subject verbed" form. +// +// ActionResult("delete", "file") // "File deleted" +// ActionResult("commit", "changes") // "Changes committed" +func ActionResult(verb, subject string) string { + p := PastTense(verb) + if p == "" || subject == "" { + return "" + } + return Title(subject) + " " + p +} + +// ActionFailed returns a failure message for an action. +// Generates "Failed to verb subject" form. +// +// ActionFailed("delete", "file") // "Failed to delete file" +// ActionFailed("push", "commits") // "Failed to push commits" +func ActionFailed(verb, subject string) string { + if verb == "" { + return "" + } + if subject == "" { + return "Failed to " + verb + } + return "Failed to " + verb + " " + subject +} + +// Label returns a label with a colon suffix. +// Generates "Word:" form with title case. +// +// Label("status") // "Status:" +// Label("version") // "Version:" +// Label("app url") // "App Url:" +func Label(word string) string { + if word == "" { + return "" + } + return Title(word) + ":" +} diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index cca04288..3d99f51d 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -270,6 +270,35 @@ func C(intent string, subject *Subject) *Composed { } } +// --- Grammar convenience functions (package-level) --- +// These provide direct access to grammar functions without needing a service instance. + +// P returns a progress message for a verb: "Building...", "Checking..." +// Use this instead of T("cli.progress.building") for dynamic progress messages. +// +// P("build") // "Building..." +// P("fetch") // "Fetching..." +func P(verb string) string { + return Progress(verb) +} + +// PS returns a progress message with a subject: "Building project...", "Checking config..." +// +// PS("build", "project") // "Building project..." +// PS("check", "config.yaml") // "Checking config.yaml..." +func PS(verb, subject string) string { + return ProgressSubject(verb, subject) +} + +// L returns a label with colon: "Status:", "Version:" +// Use this instead of T("common.label.status") for simple labels. +// +// L("status") // "Status:" +// L("version") // "Version:" +func L(word string) string { + return Label(word) +} + // _ is the standard gettext-style translation helper. // Alias for T() - use whichever you prefer. // diff --git a/pkg/i18n/intents.go b/pkg/i18n/intents.go index e5ddd3f1..68d61c53 100644 --- a/pkg/i18n/intents.go +++ b/pkg/i18n/intents.go @@ -429,6 +429,140 @@ var coreIntents = map[string]Intent{ Success: "Confirmed", Failure: "Cancelled", }, + + // --- Additional Actions --- + + "core.sync": { + Meta: IntentMeta{ + Type: "action", + Verb: "sync", + Default: "yes", + }, + Question: "Sync {{.Subject}}?", + Confirm: "Sync {{.Subject}}?", + Success: "{{.Subject | title}} synced", + Failure: "Failed to sync {{.Subject}}", + }, + + "core.boot": { + Meta: IntentMeta{ + Type: "action", + Verb: "boot", + Default: "yes", + }, + Question: "Boot {{.Subject}}?", + Confirm: "Boot {{.Subject}}?", + Success: "{{.Subject | title}} booted", + Failure: "Failed to boot {{.Subject}}", + }, + + "core.format": { + Meta: IntentMeta{ + Type: "action", + Verb: "format", + Default: "yes", + }, + Question: "Format {{.Subject}}?", + Confirm: "Format {{.Subject}}?", + Success: "{{.Subject | title}} formatted", + Failure: "Failed to format {{.Subject}}", + }, + + "core.analyse": { + Meta: IntentMeta{ + Type: "action", + Verb: "analyse", + Default: "yes", + }, + Question: "Analyse {{.Subject}}?", + Confirm: "Analyse {{.Subject}}?", + Success: "{{.Subject | title}} analysed", + Failure: "Failed to analyse {{.Subject}}", + }, + + "core.link": { + Meta: IntentMeta{ + Type: "action", + Verb: "link", + Default: "yes", + }, + Question: "Link {{.Subject}}?", + Confirm: "Link {{.Subject}}?", + Success: "{{.Subject | title}} linked", + Failure: "Failed to link {{.Subject}}", + }, + + "core.unlink": { + Meta: IntentMeta{ + Type: "action", + Verb: "unlink", + Default: "yes", + }, + Question: "Unlink {{.Subject}}?", + Confirm: "Unlink {{.Subject}}?", + Success: "{{.Subject | title}} unlinked", + Failure: "Failed to unlink {{.Subject}}", + }, + + "core.fetch": { + Meta: IntentMeta{ + Type: "action", + Verb: "fetch", + Default: "yes", + }, + Question: "Fetch {{.Subject}}?", + Confirm: "Fetch {{.Subject}}?", + Success: "{{.Subject | title}} fetched", + Failure: "Failed to fetch {{.Subject}}", + }, + + "core.generate": { + Meta: IntentMeta{ + Type: "action", + Verb: "generate", + Default: "yes", + }, + Question: "Generate {{.Subject}}?", + Confirm: "Generate {{.Subject}}?", + Success: "{{.Subject | title}} generated", + Failure: "Failed to generate {{.Subject}}", + }, + + "core.validate": { + Meta: IntentMeta{ + Type: "action", + Verb: "validate", + Default: "yes", + }, + Question: "Validate {{.Subject}}?", + Confirm: "Validate {{.Subject}}?", + Success: "{{.Subject | title}} valid", + Failure: "{{.Subject | title}} invalid", + }, + + "core.check": { + Meta: IntentMeta{ + Type: "action", + Verb: "check", + Default: "yes", + }, + Question: "Check {{.Subject}}?", + Confirm: "Check {{.Subject}}?", + Success: "{{.Subject | title}} OK", + Failure: "{{.Subject | title}} failed", + }, + + "core.scan": { + Meta: IntentMeta{ + Type: "action", + Verb: "scan", + Default: "yes", + }, + Question: "Scan {{.Subject}}?", + Confirm: "Scan {{.Subject}}?", + Success: "{{.Subject | title}} scanned", + Failure: "Failed to scan {{.Subject}}", + }, } // getIntent retrieves an intent by its key from the core intents. diff --git a/pkg/i18n/locales/en_GB.json b/pkg/i18n/locales/en_GB.json index f3213117..dfc1e94f 100644 --- a/pkg/i18n/locales/en_GB.json +++ b/pkg/i18n/locales/en_GB.json @@ -1,5 +1,20 @@ { "common": { + "prompt": { + "yes": "y", + "no": "n", + "yes_full": "yes", + "no_full": "no", + "abort": "Operation aborted", + "cancel": "Cancelled", + "continue": "Continue?", + "proceed": "Proceed?", + "confirm": "Are you sure?", + "overwrite": "Overwrite?", + "delete": "Delete?", + "save": "Save?", + "discard": "Discard changes?" + }, "label": { "error": "Error:", "done": "Done:", @@ -87,13 +102,6 @@ "aborted": "Aborted", "cancelled": "Cancelled", "completed": "Completed", - "confirm": { - "abort": "Operation aborted", - "continue": "Continue?", - "no": "No", - "proceed": "Proceed?", - "yes": "Yes" - }, "count": { "commits": { "one": "{{.Count}} commit", @@ -116,15 +124,6 @@ "ok": "OK", "pass": "PASS", "pending": "Pending", - "progress": { - "building": "Building", - "checking": "Checking", - "deploying": "Deploying", - "fetching": "Fetching", - "loading": "Loading", - "processing": "Processing", - "testing": "Testing" - }, "skip": "Skipped", "success": "Success", "time": {