Files
summercms/.planning/phases/04-cli-scaffolding-i18n-and-mail/04-02-SUMMARY.md
Jakub Zych c203bdfc54 docs(04-02): complete i18n phrasebook plan
Tasks completed: 3/3
- Load a namespaced translation from an embedded plugin
- Select CLDR and Laravel plural forms with PHP parameters
- Apply configured locale fallback and missing-key behavior

SUMMARY: .planning/phases/04-cli-scaffolding-i18n-and-mail/04-02-SUMMARY.md
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-18 13:53:31 +02:00

7.2 KiB
Raw Blame History

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
04-cli-scaffolding-i18n-and-mail 02 i18n
phrasebook
i18n
cldr
go-i18n
yaml
wintercms
phase provides
04-cli-scaffolding-i18n-and-mail pact.HasLang, party.Activate Register-then-Boot order
phase provides
01-framework-kernel-foundation backpack.Publish/Lookup, towel.Locale, compass app.locale
phrasebook catalog loader for lang/<locale>/<group>.yaml
app-scoped Translator with Get/Choice and explicit-locale variants
HasLang activation before plugin Boot
hello plugin en/pl embedded catalog
04-03-mail
04-04-tests
plugin-porting
I18N-02
added patterns
github.com/nicksnyder/go-i18n/v2@v2.6.1
go-i18n selects only a CLDR category label via IdentityParser
phrasebook owns YAML source text and :name/:Name/:NAME substitution
HasLang catalogs load after Register and publish Translator before Boot
created modified
phrasebook/loader.go
phrasebook/translator.go
phrasebook/translator_test.go
examples/hello/plugins/base/lang/en/lang.yaml
examples/hello/plugins/base/lang/pl/lang.yaml
party/registry.go
party/registry_test.go
examples/hello/plugins/base/plugin.go
examples/hello/hello_test.go
go.mod
go.sum
go-i18n is used only to choose a CLDR category label; YAML message text never enters its template engine
A per-locale i18n.Bundle is cached so matcher/plural rules follow the requested tag, not the English default bundle
Fallback order is requested, parent, app.fallback_locale, then the raw key; framework defaults remain en/en
LangFS paths must be lang/<locale>/<group>.yaml; malformed paths, non-string leaves, duplicate keys and duplicate namespace owners fail boot with plugin/file/key context
YAML maps whose keys are all CLDR categories are plural maps requiring other; otherwise they flatten as nested namespaces
Missing keys log once per translator outside production and never include parameters
I18N-01
10min 2026-09-18

Phase 4 Plan 02: i18n phrasebook Summary

Plugin-owned English and Polish YAML catalogs resolve vendor.plugin::group.key with CLDR and Laravel plurals, :name case variants, and pl-PL→pl→fallback→raw lookup

Performance

  • Duration: 10 min
  • Started: 2026-09-18T11:43:00Z
  • Completed: 2026-09-18T11:52:55Z
  • Tasks: 3
  • Files modified: 11

Accomplishments

  • phrasebook loads embedded lang/<locale>/<group>.yaml, flattens nested maps to vendor.plugin::group.dot.path, and rejects malformed catalogs with plugin/file/key context
  • party.Activate publishes one *phrasebook.Translator after Register and before Boot, using app.locale / app.fallback_locale with framework defaults en/en
  • Polish and English plural maps and Laravel pipe strings select the specified forms; :name, :Name, and :NAME substitute after selection without sending YAML through go-i18n templates
  • Requested locale walks parent then configured fallback then returns the raw key; missing keys log once outside production

Task Commits

Each task was committed atomically:

  1. Task 1: Load a namespaced translation from an embedded plugin - f824e55 (feat)
  2. Task 2: Select CLDR and Laravel plural forms with PHP parameters - 6d8786a (feat)
  3. Task 3: Apply configured locale fallback and missing-key behavior - 8a4e0ef (feat)

Plan metadata: (this commit)

Files Created/Modified

  • phrasebook/loader.go - WalkDir catalog load, YAML flatten, plural-map detection, pipe parse
  • phrasebook/translator.go - Get/Choice, CLDR category selection, fallback chain, missing-key log
  • phrasebook/translator_test.go - translation, plural, and locale-fallback smokes
  • party/registry.go - HasLang scan and translator publish before Boot
  • party/registry_test.go - translator available at Boot; malformed lang fails with plugin id
  • examples/hello/plugins/base/plugin.go - embeds LangFS and implements HasLang
  • examples/hello/plugins/base/lang/en/lang.yaml - English nested keys, plurals, pipes, parameters
  • examples/hello/plugins/base/lang/pl/lang.yaml - Polish nested keys, CLDR map and pipe forms
  • examples/hello/hello_test.go - Activate + en/pl/pl-PL/fallback/raw-key smoke
  • go.mod / go.sum - pin go-i18n/v2 v2.6.1

Decisions Made

  • go-i18n receives only category-name message variants and IdentityParser, so PHP :name placeholders and {{.Count}} YAML stay literal
  • Each locale gets its own i18n.Bundle default language so Polish few/many are real categories, not English other
  • Fallback stops at configured app.fallback_locale then the raw key; there is no extra core-locale step beyond that default

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] phrasebook tests cannot import party

  • Found during: Task 1 (TestTranslationSmoke Activate)
  • Issue: phrasebook_test → party → phrasebook is an import cycle in tests
  • Fix: Call phrasebook.Activate from phrasebook tests; keep party.Activate HasLang wiring tests in party/registry_test.go
  • Files modified: phrasebook/translator_test.go, party/registry_test.go
  • Verification: TestTranslationSmoke and TestActivatePublishesTranslatorBeforeBoot pass
  • Committed in: f824e55 (Task 1 commit)

2. [Rule 1 - Bug] go-i18n rejects float PluralCount

  • Found during: Task 2 (fractional plural samples)
  • Issue: plural.NewOperands requires integers or decimal strings, not float64
  • Fix: Coerce float counts to decimal strings before category selection
  • Files modified: phrasebook/translator.go
  • Verification: TestPluralSmoke covers 1.5 for pl/en
  • Committed in: 6d8786a (Task 2 commit)

3. [Rule 1 - Bug] English default bundle hid Polish CLDR forms

  • Found during: Task 2 (Polish few rejected as invalid for pl)
  • Issue: A single NewBundle(language.English) made Localizer match English plural rules
  • Fix: Cache one bundle per locale tag with that tag as default language
  • Files modified: phrasebook/translator.go
  • Verification: Polish counts 1/2/5/22 select one/few/many/few
  • Committed in: 6d8786a (Task 2 commit)

Total deviations: 3 auto-fixed (1 blocking, 2 bugs) Impact on plan: No behavior divergence from D-01–D-05. Fixes were required for catalog tests to compile and for Polish CLDR selection to be real.

Issues Encountered

None

User Setup Required

None - no external service configuration required.

Next Phase Readiness

Ready for 04-03 (postcard / mail). Translator is app-scoped and available at plugin Boot. Unit-test coverage remains the last plan (04-04). Per-request preferred-locale lookup stays Phase 7.

Verification

  • go test ./phrasebook -run 'TestTranslationSmoke|TestPluralSmoke|TestLocaleFallbackSmoke' -short -count=1 — PASS
  • go -C examples/hello test ./... — PASS
  • go vet ./... && go test ./... — PASS
  • No second context key; phrasebook reads towel.Locale only
  • No package-level current-locale variable

Self-Check: PASSED


Phase: 04-cli-scaffolding-i18n-and-mail Completed: 2026-09-18