mirror of
https://codeberg.org/davrot/forgejo.git
synced 2025-05-28 03:00:03 +02:00
Refactor locale&string&template related code (#29165)
Clarify when "string" should be used (and be escaped), and when "template.HTML" should be used (no need to escape) And help PRs like #29059 , to render the error messages correctly. (cherry picked from commit f3eb835886031df7a562abc123c3f6011c81eca8) Conflicts: modules/web/middleware/binding.go routers/web/feed/convert.go tests/integration/branches_test.go tests/integration/repo_branch_test.go trivial context conflicts
This commit is contained in:
parent
d565d85160
commit
65248945c9
78 changed files with 359 additions and 286 deletions
|
@ -4,26 +4,25 @@
|
|||
package i18n
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
)
|
||||
|
||||
var DefaultLocales = NewLocaleStore()
|
||||
|
||||
type Locale interface {
|
||||
// Tr translates a given key and arguments for a language
|
||||
Tr(trKey string, trArgs ...any) string
|
||||
// Has reports if a locale has a translation for a given key
|
||||
Has(trKey string) bool
|
||||
// TrString translates a given key and arguments for a language
|
||||
TrString(trKey string, trArgs ...any) string
|
||||
// TrHTML translates a given key and arguments for a language, string arguments are escaped to HTML
|
||||
TrHTML(trKey string, trArgs ...any) template.HTML
|
||||
// HasKey reports if a locale has a translation for a given key
|
||||
HasKey(trKey string) bool
|
||||
}
|
||||
|
||||
// LocaleStore provides the functions common to all locale stores
|
||||
type LocaleStore interface {
|
||||
io.Closer
|
||||
|
||||
// Tr translates a given key and arguments for a language
|
||||
Tr(lang, trKey string, trArgs ...any) string
|
||||
// Has reports if a locale has a translation for a given key
|
||||
Has(lang, trKey string) bool
|
||||
// SetDefaultLang sets the default language to fall back to
|
||||
SetDefaultLang(lang string)
|
||||
// ListLangNameDesc provides paired slices of language names to descriptors
|
||||
|
@ -45,7 +44,7 @@ func ResetDefaultLocales() {
|
|||
DefaultLocales = NewLocaleStore()
|
||||
}
|
||||
|
||||
// GetLocales returns the locale from the default locales
|
||||
// GetLocale returns the locale from the default locales
|
||||
func GetLocale(lang string) (Locale, bool) {
|
||||
return DefaultLocales.Locale(lang)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ fmt = %[1]s %[2]s
|
|||
|
||||
[section]
|
||||
sub = Sub String
|
||||
mixed = test value; <span style="color: red\; background: none;">more text</span>
|
||||
mixed = test value; <span style="color: red\; background: none;">%s</span>
|
||||
`)
|
||||
|
||||
testData2 := []byte(`
|
||||
|
@ -32,29 +32,33 @@ sub = Changed Sub String
|
|||
assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2, nil))
|
||||
ls.SetDefaultLang("lang1")
|
||||
|
||||
result := ls.Tr("lang1", "fmt", "a", "b")
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
lang2, _ := ls.Locale("lang2")
|
||||
|
||||
result := lang1.TrString("fmt", "a", "b")
|
||||
assert.Equal(t, "a b", result)
|
||||
|
||||
result = ls.Tr("lang2", "fmt", "a", "b")
|
||||
result = lang2.TrString("fmt", "a", "b")
|
||||
assert.Equal(t, "b a", result)
|
||||
|
||||
result = ls.Tr("lang1", "section.sub")
|
||||
result = lang1.TrString("section.sub")
|
||||
assert.Equal(t, "Sub String", result)
|
||||
|
||||
result = ls.Tr("lang2", "section.sub")
|
||||
result = lang2.TrString("section.sub")
|
||||
assert.Equal(t, "Changed Sub String", result)
|
||||
|
||||
result = ls.Tr("", ".dot.name")
|
||||
langNone, _ := ls.Locale("none")
|
||||
result = langNone.TrString(".dot.name")
|
||||
assert.Equal(t, "Dot Name", result)
|
||||
|
||||
result = ls.Tr("lang2", "section.mixed")
|
||||
assert.Equal(t, `test value; <span style="color: red; background: none;">more text</span>`, result)
|
||||
result2 := lang2.TrHTML("section.mixed", "a&b")
|
||||
assert.EqualValues(t, `test value; <span style="color: red; background: none;">a&b</span>`, result2)
|
||||
|
||||
langs, descs := ls.ListLangNameDesc()
|
||||
assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs)
|
||||
assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs)
|
||||
|
||||
found := ls.Has("lang1", "no-such")
|
||||
found := lang1.HasKey("no-such")
|
||||
assert.False(t, found)
|
||||
assert.NoError(t, ls.Close())
|
||||
}
|
||||
|
@ -72,9 +76,10 @@ c=22
|
|||
|
||||
ls := NewLocaleStore()
|
||||
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1, testData2))
|
||||
assert.Equal(t, "11", ls.Tr("lang1", "a"))
|
||||
assert.Equal(t, "21", ls.Tr("lang1", "b"))
|
||||
assert.Equal(t, "22", ls.Tr("lang1", "c"))
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
assert.Equal(t, "11", lang1.TrString("a"))
|
||||
assert.Equal(t, "21", lang1.TrString("b"))
|
||||
assert.Equal(t, "22", lang1.TrString("c"))
|
||||
}
|
||||
|
||||
func TestLocaleStoreQuirks(t *testing.T) {
|
||||
|
@ -110,8 +115,9 @@ func TestLocaleStoreQuirks(t *testing.T) {
|
|||
for _, testData := range testDataList {
|
||||
ls := NewLocaleStore()
|
||||
err := ls.AddLocaleByIni("lang1", "Lang1", []byte("a="+testData.in), nil)
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
assert.NoError(t, err, testData.hint)
|
||||
assert.Equal(t, testData.out, ls.Tr("lang1", "a"), testData.hint)
|
||||
assert.Equal(t, testData.out, lang1.TrString("a"), testData.hint)
|
||||
assert.NoError(t, ls.Close())
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ package i18n
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"slices"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -18,6 +20,8 @@ type locale struct {
|
|||
idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap
|
||||
}
|
||||
|
||||
var _ Locale = (*locale)(nil)
|
||||
|
||||
type localeStore struct {
|
||||
// After initializing has finished, these fields are read-only.
|
||||
langNames []string
|
||||
|
@ -88,20 +92,6 @@ func (store *localeStore) SetDefaultLang(lang string) {
|
|||
store.defaultLang = lang
|
||||
}
|
||||
|
||||
// Tr translates content to target language. fall back to default language.
|
||||
func (store *localeStore) Tr(lang, trKey string, trArgs ...any) string {
|
||||
l, _ := store.Locale(lang)
|
||||
|
||||
return l.Tr(trKey, trArgs...)
|
||||
}
|
||||
|
||||
// Has returns whether the given language has a translation for the provided key
|
||||
func (store *localeStore) Has(lang, trKey string) bool {
|
||||
l, _ := store.Locale(lang)
|
||||
|
||||
return l.Has(trKey)
|
||||
}
|
||||
|
||||
// Locale returns the locale for the lang or the default language
|
||||
func (store *localeStore) Locale(lang string) (Locale, bool) {
|
||||
l, found := store.localeMap[lang]
|
||||
|
@ -116,13 +106,11 @@ func (store *localeStore) Locale(lang string) (Locale, bool) {
|
|||
return l, found
|
||||
}
|
||||
|
||||
// Close implements io.Closer
|
||||
func (store *localeStore) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tr translates content to locale language. fall back to default language.
|
||||
func (l *locale) Tr(trKey string, trArgs ...any) string {
|
||||
func (l *locale) TrString(trKey string, trArgs ...any) string {
|
||||
format := trKey
|
||||
|
||||
idx, ok := l.store.trKeyToIdxMap[trKey]
|
||||
|
@ -144,8 +132,23 @@ func (l *locale) Tr(trKey string, trArgs ...any) string {
|
|||
return msg
|
||||
}
|
||||
|
||||
// Has returns whether a key is present in this locale or not
|
||||
func (l *locale) Has(trKey string) bool {
|
||||
func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML {
|
||||
args := slices.Clone(trArgs)
|
||||
for i, v := range args {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
args[i] = template.HTML(template.HTMLEscapeString(v))
|
||||
case fmt.Stringer:
|
||||
args[i] = template.HTMLEscapeString(v.String())
|
||||
default: // int, float, include template.HTML
|
||||
// do nothing, just use it
|
||||
}
|
||||
}
|
||||
return template.HTML(l.TrString(trKey, args...))
|
||||
}
|
||||
|
||||
// HasKey returns whether a key is present in this locale or not
|
||||
func (l *locale) HasKey(trKey string) bool {
|
||||
idx, ok := l.store.trKeyToIdxMap[trKey]
|
||||
if !ok {
|
||||
return false
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
|
||||
package translation
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// MockLocale provides a mocked locale without any translations
|
||||
type MockLocale struct{}
|
||||
|
@ -14,12 +17,16 @@ func (l MockLocale) Language() string {
|
|||
return "en"
|
||||
}
|
||||
|
||||
func (l MockLocale) Tr(s string, _ ...any) string {
|
||||
func (l MockLocale) TrString(s string, _ ...any) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func (l MockLocale) TrN(_cnt any, key1, _keyN string, _args ...any) string {
|
||||
return key1
|
||||
func (l MockLocale) Tr(s string, a ...any) template.HTML {
|
||||
return template.HTML(s)
|
||||
}
|
||||
|
||||
func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
|
||||
return template.HTML(key1)
|
||||
}
|
||||
|
||||
func (l MockLocale) PrettyNumber(v any) string {
|
||||
|
|
|
@ -5,6 +5,7 @@ package translation
|
|||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -27,8 +28,11 @@ var ContextKey any = &contextKey{}
|
|||
// Locale represents an interface to translation
|
||||
type Locale interface {
|
||||
Language() string
|
||||
Tr(string, ...any) string
|
||||
TrN(cnt any, key1, keyN string, args ...any) string
|
||||
TrString(string, ...any) string
|
||||
|
||||
Tr(key string, args ...any) template.HTML
|
||||
TrN(cnt any, key1, keyN string, args ...any) template.HTML
|
||||
|
||||
PrettyNumber(v any) string
|
||||
}
|
||||
|
||||
|
@ -144,6 +148,8 @@ type locale struct {
|
|||
msgPrinter *message.Printer
|
||||
}
|
||||
|
||||
var _ Locale = (*locale)(nil)
|
||||
|
||||
// NewLocale return a locale
|
||||
func NewLocale(lang string) Locale {
|
||||
if lock != nil {
|
||||
|
@ -216,8 +222,12 @@ var trNLangRules = map[string]func(int64) int{
|
|||
},
|
||||
}
|
||||
|
||||
func (l *locale) Tr(s string, args ...any) template.HTML {
|
||||
return l.TrHTML(s, args...)
|
||||
}
|
||||
|
||||
// TrN returns translated message for plural text translation
|
||||
func (l *locale) TrN(cnt any, key1, keyN string, args ...any) string {
|
||||
func (l *locale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
|
||||
var c int64
|
||||
if t, ok := cnt.(int); ok {
|
||||
c = int64(t)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue