diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-28 19:03:15 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-28 19:03:15 +0200 |
| commit | 9100973576f87adcadaf6c8b331df7e729aeb117 (patch) | |
| tree | b67377705bb50c30004c52abc423573d1b335ca8 /internal/i18n/gen_test.go | |
| parent | 771ec96f07499d6efbcb587f94e30ffcbc4e0e58 (diff) | |
| download | lectio-9100973576f87adcadaf6c8b331df7e729aeb117.tar.gz lectio-9100973576f87adcadaf6c8b331df7e729aeb117.zip | |
i18n: make all UI chrome user-translatable via ui/<code>.ini
The interface chrome (version labels, TUI keybar/messages, CLI banner words,
web control labels, section-heading words) was English/Polish hardcoded in Go.
It is now data: the English and Polish tables ship as embedded lang/en.ini and
lang/pl.ini, and any language is user-overridable at <config>/ui/<code>.ini,
resolved per key with an English fallback. With ui_language = <code> plus a
names/<code>.ini, the WHOLE interface -- chrome, day names, saint names -- is
translatable without a rebuild.
- i18n.Get loads the embedded English baseline, overlays the embedded/user
language file, caches per lang; field <-> INI-key mapping is by reflection
(lang.go) so the tables and files stay in sync automatically. Edge-space
values (e.g. "error: ") are double-quoted so the INI trim keeps them. The
Go tables move to golden_test.go; TestGoldenMatchesEmbedded asserts the
embedded files still parse back to them, and TestGenerateLangFiles
regenerates them (LECTIO_GEN=1).
- config.UIDir(); wire i18n.SetUserDir in the CLI, TUI and web entry points.
- README/config: document ui/<code>.ini alongside names/<code>.ini.
Diffstat (limited to 'internal/i18n/gen_test.go')
| -rw-r--r-- | internal/i18n/gen_test.go | 69 |
1 files changed, 69 insertions, 0 deletions
diff --git a/internal/i18n/gen_test.go b/internal/i18n/gen_test.go new file mode 100644 index 0000000..82dfd83 --- /dev/null +++ b/internal/i18n/gen_test.go @@ -0,0 +1,69 @@ +package i18n + +import ( + "os" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" +) + +// quote wraps a value in double quotes when it has meaningful leading/trailing +// whitespace, so the INI reader's trim does not eat it. +func quote(s string) string { + if s != strings.TrimSpace(s) { + return `"` + s + `"` + } + return s +} + +// marshalUI renders a UI table as an INI file, the inverse of applyUI. It is the +// generator for the embedded lang/<code>.ini files; the mapping is by reflection +// so adding a UI field needs no generator change. +func marshalUI(u UI) []byte { + var b strings.Builder + v := reflect.ValueOf(u) + t := v.Type() + for i := 0; i < t.NumField(); i++ { + key := iniKey(t.Field(i).Name) + fv := v.Field(i) + switch fv.Kind() { + case reflect.String: + b.WriteString(key + " = " + quote(fv.String()) + "\n") + case reflect.Map: + ks := make([]string, 0, fv.Len()) + for _, k := range fv.MapKeys() { + ks = append(ks, k.String()) + } + sort.Strings(ks) + for _, mk := range ks { + b.WriteString(key + "." + mk + " = " + quote(fv.MapIndex(reflect.ValueOf(mk)).String()) + "\n") + } + case reflect.Array: + parts := make([]string, fv.Len()) + for j := 0; j < fv.Len(); j++ { + parts[j] = fv.Index(j).String() + } + b.WriteString(key + " = " + strings.Join(parts, ",") + "\n") + } + } + return []byte(b.String()) +} + +// TestGenerateLangFiles (re)writes the embedded English and Polish chrome files +// from the Go golden tables. It is a generator, not an assertion: run it with +// LECTIO_GEN=1 after changing enUI/plUI to refresh lang/en.ini and lang/pl.ini. +func TestGenerateLangFiles(t *testing.T) { + if os.Getenv("LECTIO_GEN") == "" { + t.Skip("set LECTIO_GEN=1 to regenerate lang/*.ini from the Go tables") + } + if err := os.MkdirAll("lang", 0o755); err != nil { + t.Fatal(err) + } + for code, u := range map[string]UI{"en": enUI, "pl": plUI} { + if err := os.WriteFile(filepath.Join("lang", code+".ini"), marshalUI(u), 0o644); err != nil { + t.Fatalf("write %s: %v", code, err) + } + } +} |
