2026-01-30 11:56:29 +00:00
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
1. **Simple cases stay simple** - `_("key")` works as expected
|
|
|
|
|
2. **Complex cases become declarative** - Intent drives output, not caller logic
|
|
|
|
|
3. **Translators have power** - Grammar rules live in translations, not code
|
|
|
|
|
4. **CLI integration** - Questions, confirmations, choices are first-class
|
|
|
|
|
|
|
|
|
|
## API Design
|
|
|
|
|
|
2026-01-30 12:00:53 +00:00
|
|
|
### 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.
|
2026-01-30 11:56:29 +00:00
|
|
|
|
|
|
|
|
```go
|
2026-01-30 12:00:53 +00:00
|
|
|
i18n._("cli.success") // "Success"
|
|
|
|
|
i18n._("common.label.error") // "Error:"
|
|
|
|
|
i18n._("common.error.failed", map[string]any{"Action": "load"}) // "Failed to load"
|
|
|
|
|
```
|
2026-01-30 11:56:29 +00:00
|
|
|
|
2026-01-30 12:00:53 +00:00
|
|
|
### Compose: `T()` / `C()`
|
2026-01-30 11:56:29 +00:00
|
|
|
|
2026-01-30 12:00:53 +00:00
|
|
|
Semantic intent resolution. Takes an intent key from `core.*` namespace and returns a `Composed` result with multiple output forms.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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"
|
|
|
|
|
}
|
2026-01-30 11:56:29 +00:00
|
|
|
```
|
|
|
|
|
|
2026-01-30 12:00:53 +00:00
|
|
|
## CLI Integration
|
|
|
|
|
|
|
|
|
|
The CLI package uses `T()` internally for prompts:
|
2026-01-30 11:56:29 +00:00
|
|
|
|
|
|
|
|
```go
|
2026-01-30 12:00:53 +00:00
|
|
|
// 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]
|
2026-01-30 11:56:29 +00:00
|
|
|
// Returns: bool
|
|
|
|
|
|
2026-01-30 12:00:53 +00:00
|
|
|
// Question with options
|
|
|
|
|
choice := cli.Question("core.save", i18n.S("changes", 3).Count(3), cli.Options{
|
2026-01-30 11:56:29 +00:00
|
|
|
Default: "yes",
|
2026-01-30 12:00:53 +00:00
|
|
|
Extra: []string{"all"},
|
2026-01-30 11:56:29 +00:00
|
|
|
})
|
|
|
|
|
// 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
|
|
|
|
|
```
|
|
|
|
|
|
2026-01-30 12:00:53 +00:00
|
|
|
### cli.Confirm()
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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
|
|
|
|
|
```
|
|
|
|
|
|
2026-01-30 11:56:29 +00:00
|
|
|
## Reserved Namespaces
|
|
|
|
|
|
|
|
|
|
### `common.*` - Reusable Fragments
|
|
|
|
|
|
|
|
|
|
Atomic translation units that can be composed:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"common": {
|
|
|
|
|
"verb": {
|
|
|
|
|
"edit": "edit",
|
|
|
|
|
"delete": "delete",
|
|
|
|
|
"create": "create",
|
|
|
|
|
"save": "save",
|
2026-01-30 12:00:53 +00:00
|
|
|
"update": "update",
|
|
|
|
|
"commit": "commit"
|
2026-01-30 11:56:29 +00:00
|
|
|
},
|
|
|
|
|
"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:
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"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"
|
2026-01-30 12:00:53 +00:00
|
|
|
},
|
|
|
|
|
"commit": {
|
|
|
|
|
"_meta": {
|
|
|
|
|
"type": "action",
|
|
|
|
|
"verb": "common.verb.commit",
|
|
|
|
|
"dangerous": false
|
|
|
|
|
},
|
|
|
|
|
"question": "Commit {{.Subject}}?",
|
|
|
|
|
"success": "{{.Subject | title}} committed",
|
|
|
|
|
"failure": "Failed to commit {{.Subject}}"
|
2026-01-30 11:56:29 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 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
|
2026-01-30 12:00:53 +00:00
|
|
|
1. Define `Composed` and `Subject` types
|
|
|
|
|
2. Add `S()` / `Subject()` builder
|
|
|
|
|
3. Add `T()` / `C()` with intent resolution
|
|
|
|
|
4. Parse `_meta` from JSON
|
|
|
|
|
5. Add template functions (title, lower, past, etc.)
|
2026-01-30 11:56:29 +00:00
|
|
|
|
|
|
|
|
### Phase 2: CLI Integration
|
|
|
|
|
1. Implement `cli.Confirm()` using intents
|
|
|
|
|
2. Implement `cli.Question()` with options
|
|
|
|
|
3. Implement `cli.Choose()` for lists
|
2026-01-30 12:00:53 +00:00
|
|
|
4. Localize prompt characters [y/N] → [j/N] etc.
|
2026-01-30 11:56:29 +00:00
|
|
|
|
|
|
|
|
### Phase 3: Grammar Engine
|
|
|
|
|
1. Verb conjugation (past tense, etc.)
|
|
|
|
|
2. Noun plurality with irregular forms
|
|
|
|
|
3. Article selection (a/an, gender)
|
|
|
|
|
4. Language-specific rules
|
|
|
|
|
|
|
|
|
|
### Phase 4: Extended Languages
|
|
|
|
|
1. Gender agreement (French, German, etc.)
|
|
|
|
|
2. Formality levels (Japanese, Korean, etc.)
|
|
|
|
|
3. Right-to-left support
|
|
|
|
|
4. Plural forms beyond one/other (Russian, Arabic, etc.)
|
|
|
|
|
|
|
|
|
|
## Example: Full Flow
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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)
|
2026-01-30 12:00:53 +00:00
|
|
|
if !cli.Confirm("core.commit", i18n.S("file", path).Count(len(files))) {
|
2026-01-30 11:56:29 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"$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" }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
docs(i18n): add grammar fundamentals and DX improvements
- Parts of speech: verb, noun, article, adjective, preposition
- Verb conjugation structure (base/past/gerund)
- Noun with plurality and gender
- Article selection (a/an, gender agreement)
- DX: compile-time validation, IDE support, debug mode
- DX: short subject syntax, fluent chaining, fallback chain
- Notes for future: pluralize lib, CLDR, sync.Map caching
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 12:03:54 +00:00
|
|
|
## Grammar Fundamentals
|
|
|
|
|
|
|
|
|
|
Parts of speech we need to handle:
|
|
|
|
|
|
|
|
|
|
| Part | Role | Example | Transforms |
|
|
|
|
|
|------|------|---------|------------|
|
|
|
|
|
| **Verb** | Action | delete, save, commit | tense (past/present), mood (imperative) |
|
|
|
|
|
| **Noun** | Subject/Object | file, commit, user | plurality, gender, case |
|
|
|
|
|
| **Article** | Determiner | a/an, the | vowel-awareness, gender agreement |
|
|
|
|
|
| **Adjective** | Describes noun | modified, new, deleted | gender/number agreement |
|
|
|
|
|
| **Preposition** | Relation | in, from, to | - |
|
|
|
|
|
|
|
|
|
|
### Verb Conjugation
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"common": {
|
|
|
|
|
"verb": {
|
|
|
|
|
"delete": {
|
|
|
|
|
"base": "delete",
|
|
|
|
|
"past": "deleted",
|
|
|
|
|
"gerund": "deleting",
|
|
|
|
|
"imperative": "delete"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
For most English verbs, derive automatically:
|
|
|
|
|
- `past`: base + "ed" (or irregular lookup)
|
|
|
|
|
- `gerund`: base + "ing"
|
|
|
|
|
|
|
|
|
|
### Noun Handling
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"common": {
|
|
|
|
|
"noun": {
|
|
|
|
|
"file": {
|
|
|
|
|
"one": "file",
|
|
|
|
|
"other": "files",
|
|
|
|
|
"gender": "neuter"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Article Selection
|
|
|
|
|
|
|
|
|
|
English: a/an based on next word's sound (not letter)
|
|
|
|
|
- "a file", "an item", "a user", "an hour"
|
|
|
|
|
|
|
|
|
|
Other languages: gender agreement (der/die/das, le/la, etc.)
|
|
|
|
|
|
|
|
|
|
## DX Improvements
|
|
|
|
|
|
|
|
|
|
### 1. Compile-Time Validation
|
|
|
|
|
- `go generate` checks all `T("core.X")` calls have matching JSON keys
|
|
|
|
|
- Warns on missing `_meta` fields
|
|
|
|
|
- Type-checks template variables
|
|
|
|
|
|
|
|
|
|
### 2. IDE Support
|
|
|
|
|
- JSON schema for autocomplete in translation files
|
|
|
|
|
- Go constants generated from JSON keys: `i18n.CoreDelete` instead of `"core.delete"`
|
|
|
|
|
|
|
|
|
|
### 3. Fallback Chain
|
|
|
|
|
```
|
|
|
|
|
T("core.delete", subject)
|
|
|
|
|
→ try core.delete.question
|
|
|
|
|
→ try core.delete (plain string)
|
|
|
|
|
→ try common.action.delete
|
|
|
|
|
→ return "Delete {{.Subject}}?" (hardcoded fallback)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4. Debug Mode
|
|
|
|
|
```go
|
|
|
|
|
i18n.Debug(true) // Shows: [core.delete] Delete file.txt?
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 5. Short Subject Syntax
|
|
|
|
|
```go
|
|
|
|
|
// Instead of:
|
|
|
|
|
i18n.T("core.delete", i18n.S("file", path))
|
|
|
|
|
|
|
|
|
|
// Allow:
|
|
|
|
|
i18n.T("core.delete", path) // Infers subject type from intent's expected noun
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 6. Fluent Chaining
|
|
|
|
|
```go
|
|
|
|
|
i18n.T("core.delete").
|
|
|
|
|
Subject("file", path).
|
|
|
|
|
Count(3).
|
|
|
|
|
Question() // Returns just the question string
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Notes for Future Implementation
|
|
|
|
|
|
|
|
|
|
- Use `github.com/gertd/go-pluralize` for English plurality
|
|
|
|
|
- Consider `github.com/nicksnyder/go-i18n` patterns for CLDR plural rules
|
|
|
|
|
- Store compiled templates in sync.Map for caching
|
|
|
|
|
- `_meta` parsing happens once at load time, not per-call
|
|
|
|
|
- CLI prompt chars from `common.prompt.*` - allows `[j/N]` for German
|
|
|
|
|
|
2026-01-30 11:56:29 +00:00
|
|
|
## Open Questions
|
|
|
|
|
|
|
|
|
|
1. **Verb conjugation library** - Use existing Go library or build custom?
|
|
|
|
|
2. **Gender detection** - How to infer gender for subjects in gendered languages?
|
|
|
|
|
3. **Fallback behavior** - What happens when intent metadata is missing?
|
|
|
|
|
4. **Caching** - Should compiled templates be cached?
|
|
|
|
|
5. **Validation** - How to validate intent definitions at build time?
|