diff options
Diffstat (limited to 'src/i18n/i18n.go')
-rw-r--r-- | src/i18n/i18n.go | 196 |
1 files changed, 101 insertions, 95 deletions
diff --git a/src/i18n/i18n.go b/src/i18n/i18n.go index 62d836f..3bc490f 100644 --- a/src/i18n/i18n.go +++ b/src/i18n/i18n.go @@ -3,6 +3,7 @@ package i18n import ( "errors" "fmt" + "maps" "strings" "time" "unicode/utf8" @@ -264,19 +265,11 @@ func Locales() []LocaleInfo { } func (p Printer) Get(fmt string, args ...map[string]any) string { - /* TODO: Warning if you pass more than 1 arg? */ - var m map[string]any - if len(args) == 0 { - m = make(map[string]any) - } else { - m = args[0] - } - - return p.Sprintf(p.inner.Get(fmt), m) + return p.Sprintf(p.inner.Get(fmt), 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) +func (p Printer) GetN(fmtS, fmtP string, n int, args ...map[string]any) string { + return p.Sprintf(p.inner.GetN(fmtS, fmtP, n), args...) } /* Transform ‘en-US’ to ‘en’ */ @@ -284,17 +277,22 @@ func (l LocaleInfo) Language() string { return l.Bcp[:2] } -func (p Printer) Sprintf(format string, args map[string]any) string { +func (p Printer) Sprintf(format string, args ...map[string]any) string { var bob strings.Builder - args["-"] = "" + vars := map[string]any{ + "-": "", + } + for _, arg := range args { + maps.Copy(vars, arg) + } for { - i := strings.IndexByte(format, '%') + i := strings.IndexByte(format, '{') if i == -1 { - bob.WriteString(format) + htmlesc(&bob, format) break } - bob.WriteString(format[:i]) + htmlesc(&bob, format[:i]) format = format[i+1:] if len(format) == 0 { @@ -302,54 +300,43 @@ func (p Printer) Sprintf(format string, args map[string]any) string { break } - b := format[0] - format = format[1:] - - switch b { - case '%': - bob.WriteByte(b) - case '(': - i = strings.IndexRune(format, ')') - if i == -1 { - /* TODO: Handle error: unterminated %( */ - return "unterminated %(" - } - - parts := strings.Split(format[:i], ":") - format = format[i+1:] - - var flag rune - switch len(parts) { - case 1: - flag = -1 - case 2: - f, n := utf8.DecodeRune([]byte(parts[1])) - if n != len(parts[1]) { - /* TODO: Handle error: flag too long or empty */ - return "flag too long or empty" - } - flag = f - default: - /* TODO: Handle error: too many colons */ - return "too many colons" - } + i = strings.IndexRune(format, '}') + if i == -1 { + /* TODO: Handle error: unterminated { */ + return "unterminated {" + } - h, ok := handlers[flag] - if !ok { - /* TODO: Handle error: no such handler */ - return "no such handler" - } + parts := strings.Split(format[:i], ":") + format = format[i+1:] - v, ok := args[parts[0]] - if !ok { - /* TODO: Handle error: no such key */ - return "no such key" + var flag rune + switch len(parts) { + case 1: + flag = -1 + case 2: + f, n := utf8.DecodeRune([]byte(parts[1])) + if n != len(parts[1]) { + /* TODO: Handle error: flag too long or empty */ + return "flag too long or empty" } - h(p.LocaleInfo, &bob, v) + flag = f default: - /* TODO: Handle error: invalid escape */ - bob.WriteByte(b) + /* TODO: Handle error: too many colons */ + return "too many colons" } + + h, ok := handlers[flag] + if !ok { + /* TODO: Handle error: no such handler */ + return "no such handler" + } + + v, ok := vars[parts[0]] + if !ok { + /* TODO: Handle error: no such key */ + return "no such key" + } + h(p.LocaleInfo, &bob, v) } return bob.String() @@ -358,15 +345,15 @@ func (p Printer) Sprintf(format string, args map[string]any) string { func sprintfGeneric(li LocaleInfo, bob *strings.Builder, v any) error { switch v.(type) { case time.Time: - bob.WriteString(v.(time.Time).Format(li.DateFormat)) + htmlesc(bob, v.(time.Time).Format(li.DateFormat)) case int: writeInt(bob, v.(int), li.ThousandsSeparator) case float64: writeFloat(bob, v.(float64), li.ThousandsSeparator, li.DecimalSeparator) case string: - bob.WriteString(v.(string)) + htmlesc(bob, v.(string)) default: - bob.WriteString(fmt.Sprint(v)) + htmlesc(bob, fmt.Sprint(v)) } return nil } @@ -376,16 +363,16 @@ func sprintfe(li LocaleInfo, bob *strings.Builder, v any) error { if !ok { return errors.New("TODO") } - bob.WriteString("\u2068<a href=\"mailto:") - attrEscape(bob, s) - bob.WriteString("\">\u2069") - bob.WriteString(s) - bob.WriteString("\u2068</a>\u2069") + bob.WriteString("<a href=\"mailto:") + htmlesc(bob, s) + bob.WriteString("\">") + htmlesc(bob, s) + bob.WriteString("</a>") return nil } func sprintfE(li LocaleInfo, bob *strings.Builder, _ any) error { - bob.WriteString("\u2068</a>\u2069") + bob.WriteString("</a>") return nil } @@ -394,9 +381,9 @@ func sprintfl(li LocaleInfo, bob *strings.Builder, v any) error { if !ok { return errors.New("TODO") } - bob.WriteString("\u2068<a href=\"") - attrEscape(bob, s) - bob.WriteString("\">\u2069") + bob.WriteString("<a href=\"") + htmlesc(bob, s) + bob.WriteString("\">") return nil } @@ -405,9 +392,9 @@ func sprintfL(li LocaleInfo, bob *strings.Builder, v any) error { if !ok { return errors.New("TODO") } - bob.WriteString("\u2068<a href=\"") - attrEscape(bob, s) - bob.WriteString("\" target=\"_blank\">\u2069") + bob.WriteString("<a href=\"") + htmlesc(bob, s) + bob.WriteString("\" target=\"_blank\">") return nil } @@ -416,15 +403,15 @@ func sprintfm(li LocaleInfo, bob *strings.Builder, v any) error { case int: n := v.(int) i := btoi(n >= 0) - bob.WriteString(li.MonetaryPre[i]) + htmlesc(bob, li.MonetaryPre[i]) writeInt(bob, abs(n), li.ThousandsSeparator) - bob.WriteString(li.MonetaryPost[i]) + htmlesc(bob, li.MonetaryPost[i]) case float64: n := v.(float64) i := btoi(n >= 0) - bob.WriteString(li.MonetaryPre[i]) + htmlesc(bob, li.MonetaryPre[i]) writeFloat(bob, abs(n), li.ThousandsSeparator, li.DecimalSeparator) - bob.WriteString(li.MonetaryPost[i]) + htmlesc(bob, li.MonetaryPost[i]) default: return errors.New("TODO") } @@ -436,27 +423,10 @@ func sprintfr(li LocaleInfo, bob *strings.Builder, v any) error { if !ok { return errors.New("TODO") } - bob.WriteRune('\u2068') bob.WriteString(s) - bob.WriteRune('\u2069') return nil } -func attrEscape(bob *strings.Builder, s string) { - for _, r := range s { - switch r { - case '<': - bob.WriteString("<") - case '&': - bob.WriteString("&") - case '"': - bob.WriteString(""") - default: - bob.WriteRune(r) - } - } -} - func writeInt(bob *strings.Builder, num int, sep rune) { s := fmt.Sprintf("%d", num) if s[0] == '-' { @@ -516,3 +486,39 @@ func btoi(b bool) int { } return 1 } + +func htmlesc(bob *strings.Builder, s string) { + for _, r := range s { + switch r { + case '<': + bob.WriteString("<") + case '>': + bob.WriteString(">") + case '&': + bob.WriteString("&") + case '"': + bob.WriteString(""") + case '\'': + bob.WriteString("'") + default: + bob.WriteRune(r) + } + } +} + +func htmlescByte(bob *strings.Builder, b byte) { + switch b { + case '<': + bob.WriteString("<") + case '>': + bob.WriteString(">") + case '&': + bob.WriteString("&") + case '"': + bob.WriteString(""") + case '\'': + bob.WriteString("'") + default: + bob.WriteByte(b) + } +} |