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...) }