117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
package preview
|
|
|
|
import (
|
|
"fmt"
|
|
"gitea-mail-templates/tools/upstream"
|
|
"html/template"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// Locale is a lightweight adapter for the translation implementation pinned in
|
|
// upstream/modules/translation. Catalogs are always official snapshot data.
|
|
type Locale struct {
|
|
Lang string
|
|
Catalog map[string]string
|
|
English map[string]string
|
|
Problems *[]error
|
|
Warnings *[]string
|
|
}
|
|
|
|
func (l Locale) Language() string { return l.Lang }
|
|
func (l Locale) HasKey(key string) bool { _, ok := l.Catalog[key]; return ok }
|
|
|
|
func (l Locale) problem(err error) {
|
|
if l.Problems != nil {
|
|
*l.Problems = append(*l.Problems, err)
|
|
}
|
|
}
|
|
|
|
func (l Locale) TrString(key string, args ...any) string {
|
|
format := l.Catalog[key]
|
|
if format == "" {
|
|
format = l.English[key]
|
|
}
|
|
if format == "" {
|
|
l.problem(fmt.Errorf("missing translation key %s", key))
|
|
format = template.HTMLEscapeString(key)
|
|
}
|
|
if len(args) == 0 {
|
|
return format
|
|
}
|
|
values := make([]any, 0, len(args))
|
|
for _, a := range args {
|
|
v := reflect.ValueOf(a)
|
|
if v.IsValid() && v.Kind() == reflect.Slice {
|
|
if len(args) != 1 {
|
|
l.problem(fmt.Errorf("translation %s has uncertain slice arguments", key))
|
|
break
|
|
}
|
|
for i := 0; i < v.Len(); i++ {
|
|
values = append(values, v.Index(i).Interface())
|
|
}
|
|
} else {
|
|
values = append(values, a)
|
|
}
|
|
}
|
|
msg := fmt.Sprintf(format, values...)
|
|
// Upstream logs formatting failures; a fixture preview must surface them.
|
|
if strings.Contains(msg, "%!") && upstream.ReviewedFormattingIssue(l.Lang, key, format) {
|
|
if l.Warnings != nil {
|
|
*l.Warnings = append(*l.Warnings, "[UPSTREAM-WARN] "+l.Lang+" "+key+": official placeholder %[1]s is malformed; output matches Gitea")
|
|
}
|
|
} else if strings.Contains(msg, "%!") {
|
|
l.problem(fmt.Errorf("translation %s formatting failed: %s", key, msg))
|
|
}
|
|
return msg
|
|
}
|
|
|
|
func escapedArgs(args []any) []any {
|
|
out := append([]any(nil), args...)
|
|
for i, a := range out {
|
|
switch a := a.(type) {
|
|
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
|
|
case string:
|
|
out[i] = template.HTMLEscapeString(a)
|
|
case fmt.Stringer:
|
|
out[i] = template.HTMLEscapeString(a.String())
|
|
default:
|
|
out[i] = template.HTMLEscapeString(fmt.Sprint(a))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (l Locale) Tr(key string, args ...any) template.HTML {
|
|
return template.HTML(l.TrString(key, escapedArgs(args)...))
|
|
}
|
|
|
|
func (l Locale) TrN(count any, one, many string, args ...any) template.HTML {
|
|
var n int64
|
|
switch count := count.(type) {
|
|
case int:
|
|
n = int64(count)
|
|
case int16:
|
|
n = int64(count)
|
|
case int32:
|
|
n = int64(count)
|
|
case int64:
|
|
n = count
|
|
default:
|
|
return l.Tr(many, args...)
|
|
}
|
|
singular := n == 1
|
|
switch l.Lang {
|
|
case "zh-CN", "zh-HK", "zh-TW":
|
|
singular = true
|
|
case "lv-LV", "ru-RU":
|
|
singular = n%10 == 1 && n%100 != 11
|
|
case "fr-FR":
|
|
singular = n > -2 && n < 2
|
|
}
|
|
if singular {
|
|
return l.Tr(one, args...)
|
|
}
|
|
return l.Tr(many, args...)
|
|
}
|