Stable API (permanent, cannot change): - _() - simple gettext-style lookup - T() / C() - compose (semantic intent resolution) - S() / Subject() - typed subject with metadata Renamed Transmute() to Compose() for clarity. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
10 KiB
10 KiB
Semantic i18n System Design
Overview
Extend the i18n system beyond simple key-value translation to support semantic intents that encode meaning, enabling:
- Composite translations from reusable fragments
- Grammatical awareness (gender, plurality, formality)
- CLI prompt integration with localized options
- Reduced calling code complexity
Goals
- Simple cases stay simple -
_("key")works as expected - Complex cases become declarative - Intent drives output, not caller logic
- Translators have power - Grammar rules live in translations, not code
- CLI integration - Questions, confirmations, choices are first-class
API Design
Function Reference (Stable API)
These function names are permanent - choose carefully, they cannot change.
| Function | Alias | Purpose |
|---|---|---|
_() |
- | Simple gettext-style lookup |
T() |
C() |
Compose - semantic intent resolution |
S() |
Subject() |
Create typed subject with metadata |
Simple Translation: _()
Standard gettext-style lookup. No magic, just key → value.
i18n._("cli.success") // "Success"
i18n._("common.label.error") // "Error:"
i18n._("common.error.failed", map[string]any{"Action": "load"}) // "Failed to load"
Compose: T() / C()
Semantic intent resolution. Takes an intent key from core.* namespace and returns a Composed result with multiple output forms.
// Full form
result := i18n.T("core.delete", i18n.S("file", path))
result := i18n.C("core.delete", i18n.S("file", path)) // Alias
// Result contains all forms
result.Question // "Delete /path/to/file.txt?"
result.Confirm // "Really delete /path/to/file.txt?"
result.Success // "File deleted"
result.Failure // "Failed to delete file"
result.Meta // IntentMeta{Dangerous: true, Default: "no", ...}
Subject: S() / Subject()
Creates a typed subject with optional metadata for grammar rules.
// Simple
i18n.S("file", "/path/to/file.txt")
// With count (plurality)
i18n.S("commit", commits).Count(len(commits))
// With gender (for gendered languages)
i18n.S("user", name).Gender("female")
// Chained
i18n.S("file", path).Count(3).In("/project")
Type Signatures
// Simple lookup
func _(key string, args ...any) string
// Compose (T and C are aliases)
func T(intent string, subject *Subject) *Composed
func C(intent string, subject *Subject) *Composed
// Subject builder
func S(noun string, value any) *Subject
func Subject(noun string, value any) *Subject
// Composed result
type Composed struct {
Question string
Confirm string
Success string
Failure string
Meta IntentMeta
}
// Subject with metadata
type Subject struct {
Noun string
Value any
count int
gender string
// ... other metadata
}
func (s *Subject) Count(n int) *Subject
func (s *Subject) Gender(g string) *Subject
func (s *Subject) In(location string) *Subject
// Intent metadata
type IntentMeta struct {
Type string // "action", "question", "info"
Verb string // Reference to common.verb.*
Dangerous bool // Requires confirmation
Default string // "yes" or "no"
Supports []string // Extra options like "all", "skip"
}
CLI Integration
The CLI package uses T() internally for prompts:
// Confirm uses T() internally
confirmed := cli.Confirm("core.delete", i18n.S("file", path))
// Internally: result := i18n.T("core.delete", subject)
// Displays: result.Question + localized [y/N]
// Returns: bool
// Question with options
choice := cli.Question("core.save", i18n.S("changes", 3).Count(3), cli.Options{
Default: "yes",
Extra: []string{"all"},
})
// Displays: "Save 3 changes? [a/y/N]"
// Returns: "yes" | "no" | "all"
// Choice from list
selected := cli.Choose("core.select.branch", branches)
// Displays localized prompt with arrow selection
cli.Confirm()
func Confirm(intent string, subject *i18n.Subject, opts ...ConfirmOption) bool
// Options
cli.DefaultYes() // Default to yes instead of no
cli.DefaultNo() // Explicit default no
cli.Required() // No default, must choose
cli.Timeout(30*time.Second) // Auto-select default after timeout
cli.Question()
func Question(intent string, subject *i18n.Subject, opts ...QuestionOption) string
// Options
cli.Extra("all", "skip") // Extra options beyond y/n
cli.Default("yes") // Which option is default
cli.Validate(func(s string) bool) // Custom validation
cli.Choose()
func Choose[T any](intent string, items []T, opts ...ChooseOption) T
// Options
cli.Display(func(T) string) // How to display each item
cli.Filter() // Enable fuzzy filtering
cli.Multi() // Allow multiple selection
Reserved Namespaces
common.* - Reusable Fragments
Atomic translation units that can be composed:
{
"common": {
"verb": {
"edit": "edit",
"delete": "delete",
"create": "create",
"save": "save",
"update": "update",
"commit": "commit"
},
"noun": {
"file": { "one": "file", "other": "files" },
"commit": { "one": "commit", "other": "commits" },
"change": { "one": "change", "other": "changes" }
},
"article": {
"the": "the",
"a": { "one": "a", "vowel": "an" }
},
"prompt": {
"yes": "y",
"no": "n",
"all": "a",
"skip": "s",
"quit": "q"
}
}
}
core.* - Semantic Intents
Intents encode meaning and behavior:
{
"core": {
"edit": {
"_meta": {
"type": "action",
"verb": "common.verb.edit",
"dangerous": false
},
"question": "Should I {{.Verb}} {{.Subject}}?",
"confirm": "{{.Verb | title}} {{.Subject}}?",
"success": "{{.Subject | title}} {{.Verb | past}}",
"failure": "Failed to {{.Verb}} {{.Subject}}"
},
"delete": {
"_meta": {
"type": "action",
"verb": "common.verb.delete",
"dangerous": true,
"default": "no"
},
"question": "Delete {{.Subject}}? This cannot be undone.",
"confirm": "Really delete {{.Subject}}?",
"success": "{{.Subject | title}} deleted",
"failure": "Failed to delete {{.Subject}}"
},
"save": {
"_meta": {
"type": "action",
"verb": "common.verb.save",
"supports": ["all", "skip"]
},
"question": "Save {{.Subject}}?",
"success": "{{.Subject | title}} saved"
},
"commit": {
"_meta": {
"type": "action",
"verb": "common.verb.commit",
"dangerous": false
},
"question": "Commit {{.Subject}}?",
"success": "{{.Subject | title}} committed",
"failure": "Failed to commit {{.Subject}}"
}
}
}
Template Functions
Available in translation templates:
| Function | Description | Example |
|---|---|---|
title |
Title case | {{.Name | title}} → "Hello World" |
lower |
Lower case | {{.Name | lower}} → "hello world" |
upper |
Upper case | {{.Name | upper}} → "HELLO WORLD" |
past |
Past tense verb | {{.Verb | past}} → "edited" |
plural |
Pluralize noun | {{.Noun | plural .Count}} → "files" |
article |
Add article | {{.Noun | article}} → "a file" |
quote |
Wrap in quotes | {{.Path | quote}} → "/path/to/file" |
Implementation Plan
Phase 1: Foundation
- Define
ComposedandSubjecttypes - Add
S()/Subject()builder - Add
T()/C()with intent resolution - Parse
_metafrom JSON - Add template functions (title, lower, past, etc.)
Phase 2: CLI Integration
- Implement
cli.Confirm()using intents - Implement
cli.Question()with options - Implement
cli.Choose()for lists - Localize prompt characters [y/N] → [j/N] etc.
Phase 3: Grammar Engine
- Verb conjugation (past tense, etc.)
- Noun plurality with irregular forms
- Article selection (a/an, gender)
- Language-specific rules
Phase 4: Extended Languages
- Gender agreement (French, German, etc.)
- Formality levels (Japanese, Korean, etc.)
- Right-to-left support
- Plural forms beyond one/other (Russian, Arabic, etc.)
Example: Full Flow
// In cmd/dev/dev_commit.go
path := "/Users/dev/project"
files := []string{"main.go", "config.yaml"}
// Old way (hardcoded English, manual prompt handling)
fmt.Printf("Commit %d files in %s? [y/N] ", len(files), path)
var response string
fmt.Scanln(&response)
if response != "y" && response != "Y" {
return
}
// New way (semantic, localized, integrated)
if !cli.Confirm("core.commit", i18n.S("file", path).Count(len(files))) {
return
}
// For German user, displays:
// "2 Dateien in /Users/dev/project committen? [j/N]"
// (note: "j" for "ja" instead of "y" for "yes")
JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"common": {
"description": "Reusable translation fragments",
"type": "object"
},
"core": {
"description": "Semantic intents with metadata",
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"_meta": {
"type": "object",
"properties": {
"type": { "enum": ["action", "question", "info"] },
"verb": { "type": "string" },
"dangerous": { "type": "boolean" },
"default": { "enum": ["yes", "no"] },
"supports": { "type": "array", "items": { "type": "string" } }
}
},
"question": { "type": "string" },
"confirm": { "type": "string" },
"success": { "type": "string" },
"failure": { "type": "string" }
}
}
}
}
}
Open Questions
- Verb conjugation library - Use existing Go library or build custom?
- Gender detection - How to infer gender for subjects in gendered languages?
- Fallback behavior - What happens when intent metadata is missing?
- Caching - Should compiled templates be cached?
- Validation - How to validate intent definitions at build time?