refactor(i18n): slim locale files using composable grammar

Reduce locale files from ~4600 to 338 total lines (93% reduction):
- en_GB.json: 1520 → 262 lines (83% reduction)
- en_US.json: ~1500 → 10 lines (US spelling overrides only)
- en_AU.json: ~1500 → 2 lines (inherits from en_GB)
- de.json: 120 → 64 lines

Removed redundant entries that can now be composed via core.* patterns:
- Labels: T("core.label.status") → "Status:"
- Counts: T("core.count.item", 5) → "5 items"
- Progress: T("core.progress.build") → "Building..."
- Done/Fail: T("core.done.build", subj) → "Build completed"

Updated tests to use new key patterns.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-30 14:24:23 +00:00
parent 621540433b
commit 39de3c2836
7 changed files with 263 additions and 4124 deletions

View file

@ -23,8 +23,8 @@ func TestTranslate(t *testing.T) {
require.NoError(t, err)
// Basic translation
result := svc.T("cli.success")
assert.Equal(t, "Success", result)
result := svc.T("cmd.dev.short")
assert.Equal(t, "Multi-repo development workflow", result)
// Missing key returns the key
result = svc.T("nonexistent.key")
@ -36,11 +36,11 @@ func TestTranslateWithArgs(t *testing.T) {
require.NoError(t, err)
// Translation with template data
result := svc.T("error.not_found", map[string]string{"Item": "config.yaml"})
assert.Equal(t, "Not found: config.yaml", result)
result := svc.T("error.repo_not_found", map[string]string{"Name": "config.yaml"})
assert.Equal(t, "Repository 'config.yaml' not found", result)
result = svc.T("cli.time.minutes_ago", map[string]int{"Count": 5})
assert.Equal(t, "5 minutes ago", result)
result = svc.T("cmd.ai.task_pr.branch_error", map[string]string{"Branch": "main"})
assert.Equal(t, "cannot create PR from main branch; create a feature branch first", result)
}
func TestSetLanguage(t *testing.T) {
@ -71,8 +71,8 @@ func TestDefaultService(t *testing.T) {
require.NotNil(t, svc)
// Global T function should work
result := T("cli.success")
assert.Equal(t, "Success", result)
result := T("cmd.dev.short")
assert.Equal(t, "Multi-repo development workflow", result)
}
func TestAddMessages(t *testing.T) {
@ -138,17 +138,18 @@ func TestDetectLanguage(t *testing.T) {
func TestPluralization(t *testing.T) {
svc, err := New()
require.NoError(t, err)
SetDefault(svc)
// Singular
result := svc.T("cli.count.items", map[string]any{"Count": 1})
// Singular - uses core.count.* magic
result := svc.T("core.count.item", 1)
assert.Equal(t, "1 item", result)
// Plural
result = svc.T("cli.count.items", map[string]any{"Count": 5})
result = svc.T("core.count.item", 5)
assert.Equal(t, "5 items", result)
// Zero uses plural
result = svc.T("cli.count.items", map[string]any{"Count": 0})
result = svc.T("core.count.item", 0)
assert.Equal(t, "0 items", result)
}
@ -156,13 +157,13 @@ func TestNestedKeys(t *testing.T) {
svc, err := New()
require.NoError(t, err)
// Deeply nested key
result := svc.T("cmd.dev.work.short")
assert.Equal(t, "Multi-repo git operations", result)
// Nested key
result := svc.T("cmd.dev.short")
assert.Equal(t, "Multi-repo development workflow", result)
// Nested with flag
result = svc.T("cmd.dev.work.flag.status")
assert.Equal(t, "Show status only, don't push", result)
// Deeper nested key (flat key with dots)
result = svc.T("cmd.dev.push.short")
assert.Equal(t, "Push commits across all repos", result)
}
func TestMessage_ForCategory(t *testing.T) {
@ -260,21 +261,21 @@ func TestDebugMode(t *testing.T) {
require.NoError(t, err)
// Without debug
result := svc.T("cli.success")
assert.Equal(t, "Success", result)
result := svc.T("cmd.dev.short")
assert.Equal(t, "Multi-repo development workflow", result)
// Enable debug
svc.SetDebug(true)
assert.True(t, svc.Debug())
// With debug - shows key prefix
result = svc.T("cli.success")
assert.Equal(t, "[cli.success] Success", result)
result = svc.T("cmd.dev.short")
assert.Equal(t, "[cmd.dev.short] Multi-repo development workflow", result)
// Disable debug
svc.SetDebug(false)
result = svc.T("cli.success")
assert.Equal(t, "Success", result)
result = svc.T("cmd.dev.short")
assert.Equal(t, "Multi-repo development workflow", result)
})
t.Run("C with debug mode", func(t *testing.T) {
@ -311,8 +312,8 @@ func TestDebugMode(t *testing.T) {
assert.True(t, Default().Debug())
// Translate
result := T("cli.success")
assert.Equal(t, "[cli.success] Success", result)
result := T("cmd.dev.short")
assert.Equal(t, "[cmd.dev.short] Multi-repo development workflow", result)
// Cleanup
SetDebug(false)

View file

@ -270,8 +270,8 @@ func TestIntentT_Integration(t *testing.T) {
assert.Equal(t, "Delete config.yaml?", result)
// Using T with regular key should work normally
result = svc.T("cli.success")
assert.Equal(t, "Success", result)
result = svc.T("cmd.dev.short")
assert.Equal(t, "Multi-repo development workflow", result)
}
func TestIntent_EmptyTemplates(t *testing.T) {

View file

@ -19,7 +19,7 @@ func TestServiceImplementsTranslator(t *testing.T) {
translator = svc
// Test interface methods
assert.Equal(t, "Success", translator.T("cli.success"))
assert.Equal(t, "Multi-repo development workflow", translator.T("cmd.dev.short"))
assert.NotEmpty(t, translator.Language())
assert.NotNil(t, translator.Direction())
assert.NotNil(t, translator.Formality())

View file

@ -1,120 +1,64 @@
{
"gram.verb.delete": { "base": "löschen", "past": "gelöscht", "gerund": "löschend" },
"gram.verb.save": { "base": "speichern", "past": "gespeichert", "gerund": "speichernd" },
"gram.verb.create": { "base": "erstellen", "past": "erstellt", "gerund": "erstellend" },
"gram.verb.update": { "base": "aktualisieren", "past": "aktualisiert", "gerund": "aktualisierend" },
"gram.verb.build": { "base": "bauen", "past": "gebaut", "gerund": "bauend" },
"gram.verb.run": { "base": "laufen", "past": "gelaufen", "gerund": "laufend" },
"gram.verb.check": { "base": "prüfen", "past": "geprüft", "gerund": "prüfend" },
"gram.verb.install": { "base": "installieren", "past": "installiert", "gerund": "installierend" },
"gram.verb.push": { "base": "pushen", "past": "gepusht", "gerund": "pushend" },
"gram.verb.pull": { "base": "pullen", "past": "gepullt", "gerund": "pullend" },
"gram.verb.commit": { "base": "committen", "past": "committet", "gerund": "committend" },
"gram.noun.file": { "one": "Datei", "other": "Dateien", "gender": "feminine" },
"gram.noun.repo": { "one": "Repository", "other": "Repositories", "gender": "neuter" },
"gram.noun.commit": { "one": "Commit", "other": "Commits", "gender": "masculine" },
"gram.noun.branch": { "one": "Branch", "other": "Branches", "gender": "masculine" },
"gram.noun.change": { "one": "Änderung", "other": "Änderungen", "gender": "feminine" },
"gram.noun.item": { "one": "Element", "other": "Elemente", "gender": "neuter" },
"gram.article.indefinite.masculine": "ein",
"gram.article.indefinite.feminine": "eine",
"gram.article.indefinite.neuter": "ein",
"gram.article.definite.masculine": "der",
"gram.article.definite.feminine": "die",
"gram.article.definite.neuter": "das",
"cli.success": "Erfolg",
"cli.error": "Fehler",
"cli.warning": "Warnung",
"cli.info": "Info",
"cli.done": "Fertig",
"cli.failed": "Fehlgeschlagen",
"cli.pass": "BESTANDEN",
"cli.fail": "FEHLGESCHLAGEN",
"cli.ok": "OK",
"cli.skip": "Übersprungen",
"cli.pending": "Ausstehend",
"cli.running": "Läuft",
"cli.completed": "Abgeschlossen",
"cli.cancelled": "Abgebrochen",
"cli.aborted": "Abgebrochen",
"cli.confirm.yes": "Ja",
"cli.confirm.no": "Nein",
"cli.confirm.proceed": "Fortfahren?",
"cli.confirm.continue": "Weiter?",
"cli.confirm.abort": "Vorgang abgebrochen",
"cli.progress.checking": "Prüfe",
"cli.progress.fetching": "Lade",
"cli.progress.loading": "Lade",
"cli.progress.processing": "Verarbeite",
"cli.progress.installing": "Installiere",
"cli.progress.building": "Baue",
"cli.progress.deploying": "Deploye",
"cli.progress.testing": "Teste",
"cli.time.just_now": "gerade eben",
"cli.time.seconds_ago": "vor {{.Count}} Sekunden",
"cli.time.minute_ago": "vor 1 Minute",
"cli.time.minutes_ago": "vor {{.Count}} Minuten",
"cli.time.hour_ago": "vor 1 Stunde",
"cli.time.hours_ago": "vor {{.Count}} Stunden",
"cli.time.day_ago": "vor 1 Tag",
"cli.time.days_ago": "vor {{.Count}} Tagen",
"cli.time.week_ago": "vor 1 Woche",
"cli.time.weeks_ago": "vor {{.Count}} Wochen",
"cli.count.items": {
"one": "{{.Count}} Element",
"other": "{{.Count}} Elemente"
"gram": {
"verb": {
"delete": { "base": "löschen", "past": "gelöscht", "gerund": "löschend" },
"save": { "base": "speichern", "past": "gespeichert", "gerund": "speichernd" },
"create": { "base": "erstellen", "past": "erstellt", "gerund": "erstellend" },
"update": { "base": "aktualisieren", "past": "aktualisiert", "gerund": "aktualisierend" },
"build": { "base": "bauen", "past": "gebaut", "gerund": "bauend" },
"run": { "base": "laufen", "past": "gelaufen", "gerund": "laufend" },
"check": { "base": "prüfen", "past": "geprüft", "gerund": "prüfend" },
"install": { "base": "installieren", "past": "installiert", "gerund": "installierend" },
"push": { "base": "pushen", "past": "gepusht", "gerund": "pushend" },
"pull": { "base": "pullen", "past": "gepullt", "gerund": "pullend" },
"commit": { "base": "committen", "past": "committet", "gerund": "committend" }
},
"noun": {
"file": { "one": "Datei", "other": "Dateien", "gender": "feminine" },
"repo": { "one": "Repository", "other": "Repositories", "gender": "neuter" },
"commit": { "one": "Commit", "other": "Commits", "gender": "masculine" },
"branch": { "one": "Branch", "other": "Branches", "gender": "masculine" },
"change": { "one": "Änderung", "other": "Änderungen", "gender": "feminine" },
"item": { "one": "Element", "other": "Elemente", "gender": "neuter" }
},
"article": {
"indefinite": { "masculine": "ein", "feminine": "eine", "neuter": "ein" },
"definite": { "masculine": "der", "feminine": "die", "neuter": "das" }
},
"punct": {
"label": ":",
"progress": "..."
}
},
"cli.count.files": {
"one": "{{.Count}} Datei",
"other": "{{.Count}} Dateien"
"prompt": {
"yes": "j",
"no": "n",
"continue": "Weiter?",
"proceed": "Fortfahren?",
"confirm": "Sind Sie sicher?"
},
"cli.count.repos": {
"one": "{{.Count}} Repository",
"other": "{{.Count}} Repositories"
"time": {
"just_now": "gerade eben",
"ago": {
"second": { "one": "vor {{.Count}} Sekunde", "other": "vor {{.Count}} Sekunden" },
"minute": { "one": "vor {{.Count}} Minute", "other": "vor {{.Count}} Minuten" },
"hour": { "one": "vor {{.Count}} Stunde", "other": "vor {{.Count}} Stunden" },
"day": { "one": "vor {{.Count}} Tag", "other": "vor {{.Count}} Tagen" },
"week": { "one": "vor {{.Count}} Woche", "other": "vor {{.Count}} Wochen" }
}
},
"cli.count.commits": {
"one": "{{.Count}} Commit",
"other": "{{.Count}} Commits"
"cmd": {
"dev.short": "Multi-Repository-Entwicklung",
"doctor.short": "Entwicklungsumgebung prüfen"
},
"cmd.dev.short": "Multi-Repository-Entwicklung",
"cmd.dev.status.dirty": "geändert",
"cmd.dev.status.clean": "sauber",
"cmd.dev.status.ahead": "voraus",
"cmd.dev.status.behind": "zurück",
"cmd.dev.status.synced": "synchronisiert",
"cmd.dev.push.confirm": "Alle pushen?",
"cmd.dev.commit.committing": "Committe geänderte Repos mit Claude...",
"cmd.doctor.short": "Entwicklungsumgebung prüfen",
"cmd.doctor.checking": "Prüfe Entwicklungsumgebung...",
"cmd.doctor.required": "Erforderlich",
"cmd.doctor.optional": "Optional",
"cmd.doctor.ready": "Umgebung bereit",
"error.not_found": "Nicht gefunden: {{.Item}}",
"error.invalid": "Ungültig: {{.Item}}",
"error.permission": "Zugriff verweigert: {{.Item}}",
"error.timeout": "Zeitüberschreitung",
"error.gh_not_found": "'gh' CLI nicht gefunden. Installieren von https://cli.github.com/",
"label.status": "Status",
"label.branch": "Branch",
"label.commit": "Commit",
"label.coverage": "Abdeckung",
"label.total": "Gesamt",
"lang.en": "Englisch",
"lang.de": "Deutsch",
"lang.es": "Spanisch",
"lang.fr": "Französisch",
"lang.zh": "Chinesisch"
"error": {
"gh_not_found": "'gh' CLI nicht gefunden. Installieren von https://cli.github.com/"
},
"lang": {
"de": "Deutsch",
"en": "Englisch",
"es": "Spanisch",
"fr": "Französisch",
"zh": "Chinesisch"
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff