aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/i18n/i18n.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/i18n/i18n.go')
-rw-r--r--src/i18n/i18n.go406
1 files changed, 113 insertions, 293 deletions
diff --git a/src/i18n/i18n.go b/src/i18n/i18n.go
index ebd849a..93b8d63 100644
--- a/src/i18n/i18n.go
+++ b/src/i18n/i18n.go
@@ -1,14 +1,23 @@
+//go:generate ./gen.py
+
package i18n
import (
"errors"
"fmt"
+ "io/fs"
+ "log"
"maps"
+ "slices"
"strings"
"time"
"unicode/utf8"
+ "git.thomasvoss.com/euro-cash.eu/pkg/atexit"
+ "git.thomasvoss.com/euro-cash.eu/pkg/watch"
"github.com/leonelquinteros/gotext"
+
+ "git.thomasvoss.com/euro-cash.eu/src/wikipedia"
)
type Printer struct {
@@ -16,15 +25,6 @@ type Printer struct {
inner *gotext.Locale
}
-type LocaleInfo struct {
- Bcp, Name string
- Eurozone, Enabled bool
- DateFormat string
- GroupSeparator, DecimalSeparator rune
- MonetaryPre [2]string
- MonetaryPost string
-}
-
type number interface {
int | float64
}
@@ -34,293 +34,82 @@ type sprintfFunc func(LocaleInfo, *strings.Builder, any) error
var (
handlers map[rune]sprintfFunc = map[rune]sprintfFunc{
-1: sprintfGeneric,
- 'e': sprintfe,
'E': sprintfE,
- 'l': sprintfl,
'L': sprintfL,
+ 'e': sprintfe,
+ 'l': sprintfl,
'm': sprintfm,
+ 'p': sprintfp,
'r': sprintfr,
}
- /* To determine the correct currency-, date-, and number formats to
- use, use the ‘getfmt’ script in the repository root */
- locales = [...]LocaleInfo{
- {
- Bcp: "ca",
- Name: "Català",
- DateFormat: "2/1/2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "de",
- Name: "Deutsch",
- DateFormat: "2.1.2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "el",
- Name: "Ελληνικά",
- DateFormat: "2/1/2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "en",
- Name: "English",
- DateFormat: "02/01/2006",
- Eurozone: true,
- Enabled: true,
- GroupSeparator: ',',
- DecimalSeparator: '.',
- MonetaryPre: [2]string{"€", "-€"},
- },
- {
- Bcp: "es",
- Name: "Español",
- DateFormat: "2/1/2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "et",
- Name: "Eesti",
- DateFormat: "2.1.2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "fi",
- Name: "Suomi",
- DateFormat: "2.1.2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "fr",
- Name: "Français",
- DateFormat: "02/01/2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "ga",
- Name: "Gaeilge",
- DateFormat: "02/01/2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: ',',
- DecimalSeparator: '.',
- MonetaryPre: [2]string{"€", "-€"},
- },
- {
- Bcp: "hr",
- Name: "Hrvatski",
- DateFormat: "02. 01. 2006.",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "it",
- Name: "Italiano",
- DateFormat: "02/01/2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "lb",
- Name: "Lëtzebuergesch",
- DateFormat: "2.1.2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "lt",
- Name: "Lietuvių",
- DateFormat: "2006-01-02",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "lv",
- Name: "Latviešu",
- DateFormat: "2.01.2006.",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "mt",
- Name: "Malti",
- DateFormat: "2/1/2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: ',',
- DecimalSeparator: '.',
- MonetaryPre: [2]string{"€", "-€"},
- },
- {
- Bcp: "nl",
- Name: "Nederlands",
- DateFormat: "2-1-2006",
- Eurozone: true,
- Enabled: true,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"€ ", "€ -"},
- },
- {
- Bcp: "pt",
- Name: "Português",
- DateFormat: "02/01/2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"€ ", "€ -"},
- },
- {
- Bcp: "sk",
- Name: "Slovenčina",
- DateFormat: "2. 1. 2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "sl",
- Name: "Slovenščina",
- DateFormat: "2. 1. 2006",
- Eurozone: true,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "sv",
- Name: "Svenska",
- DateFormat: "2006-01-02",
- Eurozone: true,
- Enabled: true,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- /* Non-Eurozone locales */
- {
- Bcp: "bg",
- Name: "Български",
- DateFormat: "2.01.2006 г.",
- Eurozone: false, /* TODO(2026): Set to true */
- Enabled: false,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "ro",
- Name: "Română",
- DateFormat: "02.01.2006",
- Eurozone: false,
- Enabled: false,
- GroupSeparator: '.',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- {
- Bcp: "uk",
- Name: "Yкраїнська",
- DateFormat: "02.01.2006",
- Eurozone: false,
- Enabled: false,
- GroupSeparator: ' ',
- DecimalSeparator: ',',
- MonetaryPre: [2]string{"", "-"},
- MonetaryPost: " €",
- },
- }
- Printers map[string]Printer = make(map[string]Printer, len(locales))
+ Printers = make(map[string]Printer, len(locales))
DefaultPrinter Printer
)
-func Init() {
- for _, li := range locales {
- if !li.Enabled {
- continue
- }
- gl := gotext.NewLocale("po", li.Bcp)
- gl.AddDomain("messages")
- Printers[li.Bcp] = Printer{li, gl}
+func Init(dir fs.FS, debugp bool) {
+ gotext.FallbackLocale = "en"
+ i := slices.IndexFunc(locales[:], func(li LocaleInfo) bool {
+ return li.Bcp == gotext.FallbackLocale
+ })
+ if i == -1 {
+ atexit.Exec()
+ log.Fatalf("No translation file default locale ‘%s’\n",
+ gotext.FallbackLocale)
+ }
+ if !locales[i].Enabledp {
+ atexit.Exec()
+ log.Fatalf("Default locale ‘%s’ is not enabled\n",
+ locales[i].Name)
}
- gotext.FallbackLocale = "en"
+ initLocale(dir, locales[i], locales[i].Name, debugp)
DefaultPrinter = Printers[gotext.FallbackLocale]
+
+ for j, li := range locales {
+ if li.Enabledp && i != j {
+ name := DefaultPrinter.GetC(li.Name, "Language Name")
+ initLocale(dir, li, name, debugp)
+ }
+ }
+}
+
+func initLocale(dir fs.FS, li LocaleInfo, name string, debugp bool) {
+ gl := gotext.NewLocaleFS(li.Bcp, dir)
+ gl.AddDomain("messages")
+ Printers[li.Bcp] = Printer{li, gl}
+
+ if debugp {
+ subdir, err := fs.Sub(dir, li.Bcp)
+ if err != nil {
+ log.Printf("No translations directory for ‘%s’\n", name)
+ return
+ }
+ go watch.FileFS(subdir, "messages.po", func() {
+ Printers[li.Bcp].inner.AddDomain("messages")
+ log.Printf("Translations for ‘%s’ updated\n", name)
+ })
+ }
+
+ log.Printf("Initialized printer for ‘%s’\n", name)
}
func Locales() []LocaleInfo {
return locales[:]
}
+func (p Printer) Wikipedia(title string) string {
+ return wikipedia.Url(title, p.Bcp)
+}
+
func (p Printer) Get(fmt string, args ...map[string]any) string {
return p.Sprintf(p.inner.Get(fmt), args...)
}
+func (p Printer) GetC(fmt, ctx string, args ...map[string]any) string {
+ return p.Sprintf(p.inner.GetC(fmt, ctx), args...)
+}
+
func (p Printer) GetN(fmtS, fmtP string, n int, args ...map[string]any) string {
return p.Sprintf(p.inner.GetN(fmtS, fmtP, n), args...)
}
@@ -342,13 +131,25 @@ func (p Printer) Ftoa(n float64) string {
return bob.String()
}
-func (p Printer) Mitoa(n int) string {
+func (p Printer) Itop(n int) string {
+ var bob strings.Builder
+ sprintfp(p.LocaleInfo, &bob, n)
+ return bob.String()
+}
+
+func (p Printer) Ftop(n float64) string {
+ var bob strings.Builder
+ sprintfp(p.LocaleInfo, &bob, n)
+ return bob.String()
+}
+
+func (p Printer) Itom(n int) string {
var bob strings.Builder
sprintfm(p.LocaleInfo, &bob, n)
return bob.String()
}
-func (p Printer) Mftoa(n float64) string {
+func (p Printer) Ftom(n float64) string {
var bob strings.Builder
sprintfm(p.LocaleInfo, &bob, n)
return bob.String()
@@ -373,11 +174,6 @@ func (p Printer) Sprintf(format string, args ...map[string]any) string {
htmlesc(&bob, format[:i])
format = format[i+1:]
- if len(format) == 0 {
- /* TODO: Handle error: trailing percent */
- break
- }
-
i = strings.IndexRune(format, '}')
if i == -1 {
/* TODO: Handle error: unterminated { */
@@ -485,20 +281,44 @@ func sprintfL(li LocaleInfo, bob *strings.Builder, v any) error {
}
func sprintfm(li LocaleInfo, bob *strings.Builder, v any) error {
+ var (
+ fmt string
+ negp bool
+ )
switch v.(type) {
case int:
- n := v.(int)
- htmlesc(bob, li.MonetaryPre[btoi(n >= 0)])
- writeInt(bob, abs(n), li)
- htmlesc(bob, li.MonetaryPost)
+ negp = v.(int) < 0
case float64:
- n := v.(float64)
- htmlesc(bob, li.MonetaryPre[btoi(n >= 0)])
- writeFloat(bob, abs(n), li)
- htmlesc(bob, li.MonetaryPost)
- default:
- return errors.New("TODO")
+ negp = v.(float64) < 0
+ }
+
+ fmt = li.MonetaryFormats[btoi(negp)]
+ pre, suf, _ := strings.Cut(fmt, "123")
+ htmlesc(bob, pre)
+
+ switch v.(type) {
+ case int:
+ writeInt(bob, abs(v.(int)), li)
+ case float64:
+ writeFloat(bob, abs(v.(float64)), li)
+ }
+
+ htmlesc(bob, suf)
+ return nil
+}
+
+func sprintfp(li LocaleInfo, bob *strings.Builder, v any) error {
+ pre, suf, _ := strings.Cut(li.PercentFormat, "123")
+ htmlesc(bob, pre)
+
+ switch v.(type) {
+ case int:
+ writeInt(bob, v.(int), li)
+ case float64:
+ writeFloat(bob, v.(float64), li)
}
+
+ htmlesc(bob, suf)
return nil
}
@@ -566,9 +386,9 @@ func abs[T number](x T) T {
func btoi(b bool) int {
if b {
- return 0
+ return 1
}
- return 1
+ return 0
}
func htmlesc(bob *strings.Builder, s string) {