mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-04-24 06:56:59 -04:00
chore(test): separate and move around i18n testing (#10539)
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
Closes #10534 The primary code change is that `TestMissingTranslationHandling` was converted to a unit test. However translation unit tests were a mix of mostly unrelated old (INI) and new (JSON) code with unclear licensing. It was cleaned up with help of Git history. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10539 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
parent
b1adc7d931
commit
973ff28f44
7 changed files with 365 additions and 330 deletions
|
|
@ -302,7 +302,7 @@ func main() {
|
|||
if name == "docker" || name == ".git" || name == "node_modules" {
|
||||
return fs.SkipDir
|
||||
}
|
||||
} else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" {
|
||||
} else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" || fpath == "modules/translation/i18n/i18n_ini_test.go" {
|
||||
// skip false positives
|
||||
} else if strings.HasSuffix(name, ".go") {
|
||||
onError(HandleGoFile(handler, fpath, nil))
|
||||
|
|
|
|||
206
modules/translation/i18n/i18n_ini_test.go
Normal file
206
modules/translation/i18n/i18n_ini_test.go
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLocaleStoreINI(t *testing.T) {
|
||||
testData1 := []byte(`
|
||||
.dot.name = Dot Name
|
||||
fmt = %[1]s %[2]s
|
||||
|
||||
[section]
|
||||
sub = Sub String
|
||||
mixed = test value; <span style="color: red\; background: none;">%s</span>
|
||||
`)
|
||||
|
||||
testData2 := []byte(`
|
||||
fmt = %[2]s %[1]s
|
||||
|
||||
[section]
|
||||
sub = Changed Sub String
|
||||
`)
|
||||
|
||||
ls := NewLocaleStore()
|
||||
require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRuleEnglish, UsedPluralFormsEnglish, testData1, nil))
|
||||
require.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", MockPluralRule, UsedPluralFormsMock, testData2, nil))
|
||||
ls.SetDefaultLang("lang1")
|
||||
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
lang2, _ := ls.Locale("lang2")
|
||||
|
||||
result := lang1.TrString("fmt", "a", "b")
|
||||
assert.Equal(t, "a b", result)
|
||||
|
||||
result = lang2.TrString("fmt", "a", "b")
|
||||
assert.Equal(t, "b a", result)
|
||||
|
||||
result = lang1.TrString("section.sub")
|
||||
assert.Equal(t, "Sub String", result)
|
||||
|
||||
result = lang2.TrString("section.sub")
|
||||
assert.Equal(t, "Changed Sub String", result)
|
||||
|
||||
langNone, _ := ls.Locale("none")
|
||||
result = langNone.TrString(".dot.name")
|
||||
assert.Equal(t, "Dot Name", 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 := lang1.HasKey("no-such")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "no-such", lang1.TrString("no-such"))
|
||||
require.NoError(t, ls.Close())
|
||||
}
|
||||
|
||||
func TestLocaleStoreMoreSource(t *testing.T) {
|
||||
testData1 := []byte(`
|
||||
a=11
|
||||
b=12
|
||||
`)
|
||||
|
||||
testData2 := []byte(`
|
||||
b=21
|
||||
c=22
|
||||
`)
|
||||
|
||||
ls := NewLocaleStore()
|
||||
require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRule, UsedPluralFormsMock, testData1, testData2))
|
||||
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"))
|
||||
}
|
||||
|
||||
type stringerPointerReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (s *stringerPointerReceiver) String() string {
|
||||
return s.s
|
||||
}
|
||||
|
||||
type stringerStructReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (s stringerStructReceiver) String() string {
|
||||
return s.s
|
||||
}
|
||||
|
||||
type errorStructReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e errorStructReceiver) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
type errorPointerReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorPointerReceiver) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func TestLocaleWithTemplate(t *testing.T) {
|
||||
ls := NewLocaleStore()
|
||||
require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRule, UsedPluralFormsMock, []byte(`key=<a>%s</a>`), nil))
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
|
||||
tmpl := template.New("test").Funcs(template.FuncMap{"tr": lang1.TrHTML})
|
||||
tmpl = template.Must(tmpl.Parse(`{{tr "key" .var}}`))
|
||||
|
||||
cases := []struct {
|
||||
in any
|
||||
want string
|
||||
}{
|
||||
{"<str>", "<a><str></a>"},
|
||||
{[]byte("<bytes>"), "<a>[60 98 121 116 101 115 62]</a>"},
|
||||
{template.HTML("<html>"), "<a><html></a>"},
|
||||
{stringerPointerReceiver{"<stringerPointerReceiver>"}, "<a>{<stringerPointerReceiver>}</a>"},
|
||||
{&stringerPointerReceiver{"<stringerPointerReceiver ptr>"}, "<a><stringerPointerReceiver ptr></a>"},
|
||||
{stringerStructReceiver{"<stringerStructReceiver>"}, "<a><stringerStructReceiver></a>"},
|
||||
{&stringerStructReceiver{"<stringerStructReceiver ptr>"}, "<a><stringerStructReceiver ptr></a>"},
|
||||
{errorStructReceiver{"<errorStructReceiver>"}, "<a><errorStructReceiver></a>"},
|
||||
{&errorStructReceiver{"<errorStructReceiver ptr>"}, "<a><errorStructReceiver ptr></a>"},
|
||||
{errorPointerReceiver{"<errorPointerReceiver>"}, "<a>{<errorPointerReceiver>}</a>"},
|
||||
{&errorPointerReceiver{"<errorPointerReceiver ptr>"}, "<a><errorPointerReceiver ptr></a>"},
|
||||
}
|
||||
|
||||
buf := &strings.Builder{}
|
||||
for _, c := range cases {
|
||||
buf.Reset()
|
||||
require.NoError(t, tmpl.Execute(buf, map[string]any{"var": c.in}))
|
||||
assert.Equal(t, c.want, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocaleStoreQuirks(t *testing.T) {
|
||||
const nl = "\n"
|
||||
q := func(q1, s string, q2 ...string) string {
|
||||
return q1 + s + strings.Join(q2, "")
|
||||
}
|
||||
testDataList := []struct {
|
||||
in string
|
||||
out string
|
||||
hint string
|
||||
}{
|
||||
{` xx`, `xx`, "simple, no quote"},
|
||||
{`" xx"`, ` xx`, "simple, double-quote"},
|
||||
{`' xx'`, ` xx`, "simple, single-quote"},
|
||||
{"` xx`", ` xx`, "simple, back-quote"},
|
||||
|
||||
{`x\"y`, `x\"y`, "no unescape, simple"},
|
||||
{q(`"`, `x\"y`, `"`), `"x\"y"`, "unescape, double-quote"},
|
||||
{q(`'`, `x\"y`, `'`), `x\"y`, "no unescape, single-quote"},
|
||||
{q("`", `x\"y`, "`"), `x\"y`, "no unescape, back-quote"},
|
||||
|
||||
{q(`"`, `x\"y`) + nl + "b=", `"x\"y`, "half open, double-quote"},
|
||||
{q(`'`, `x\"y`) + nl + "b=", `'x\"y`, "half open, single-quote"},
|
||||
{q("`", `x\"y`) + nl + "b=`", `x\"y` + nl + "b=", "half open, back-quote, multi-line"},
|
||||
|
||||
{`x ; y`, `x ; y`, "inline comment (;)"},
|
||||
{`x # y`, `x # y`, "inline comment (#)"},
|
||||
{`x \; y`, `x ; y`, `inline comment (\;)`},
|
||||
{`x \# y`, `x # y`, `inline comment (\#)`},
|
||||
}
|
||||
|
||||
for _, testData := range testDataList {
|
||||
ls := NewLocaleStore()
|
||||
err := ls.AddLocaleByIni("lang1", "Lang1", nil, nil, []byte("a="+testData.in), nil)
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
require.NoError(t, err, testData.hint)
|
||||
assert.Equal(t, testData.out, lang1.TrString("a"), testData.hint)
|
||||
require.NoError(t, ls.Close())
|
||||
}
|
||||
|
||||
// TODO: Crowdin needs the strings to be quoted correctly and doesn't like incomplete quotes
|
||||
// and Crowdin always outputs quoted strings if there are quotes in the strings.
|
||||
// So, Gitea's `key="quoted" unquoted` content shouldn't be used on Crowdin directly,
|
||||
// it should be converted to `key="\"quoted\" unquoted"` first.
|
||||
// TODO: We can not use UnescapeValueDoubleQuotes=true, because there are a lot of back-quotes in en-US.ini,
|
||||
// then Crowdin will output:
|
||||
// > key = "`x \" y`"
|
||||
// Then Gitea will read a string with back-quotes, which is incorrect.
|
||||
// TODO: Crowdin might generate multi-line strings, quoted by double-quote, it's not supported by LocaleStore
|
||||
// LocaleStore uses back-quote for multi-line strings, it's not supported by Crowdin.
|
||||
// TODO: Crowdin doesn't support back-quote as string quoter, it mainly uses double-quote
|
||||
// so, the following line will be parsed as: value="`first", comment="second`" on Crowdin
|
||||
// > a = `first; second`
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright 2024-2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -37,24 +36,7 @@ var (
|
|||
UsedPluralFormsMock = []PluralFormIndex{PluralFormZero, PluralFormOne, PluralFormFew, PluralFormOther}
|
||||
)
|
||||
|
||||
func TestLocaleStore(t *testing.T) {
|
||||
testData1 := []byte(`
|
||||
.dot.name = Dot Name
|
||||
fmt = %[1]s %[2]s
|
||||
|
||||
[section]
|
||||
sub = Sub String
|
||||
mixed = test value; <span style="color: red\; background: none;">%s</span>
|
||||
`)
|
||||
|
||||
testData2 := []byte(`
|
||||
fmt = %[2]s %[1]s
|
||||
|
||||
[section]
|
||||
sub = Changed Sub String
|
||||
commits = fallback value for commits
|
||||
`)
|
||||
|
||||
func TestLocaleStoreJSON(t *testing.T) {
|
||||
testDataJSON2 := []byte(`
|
||||
{
|
||||
"section.json": "the JSON is %s",
|
||||
|
|
@ -90,35 +72,18 @@ commits = fallback value for commits
|
|||
`)
|
||||
|
||||
ls := NewLocaleStore()
|
||||
require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRuleEnglish, UsedPluralFormsEnglish, testData1, nil))
|
||||
require.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", MockPluralRule, UsedPluralFormsMock, testData2, nil))
|
||||
|
||||
// Currently LocaleStore has to be first populated with langcodes via AddLocaleByIni
|
||||
require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRuleEnglish, UsedPluralFormsEnglish, []byte(""), nil))
|
||||
require.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", MockPluralRule, UsedPluralFormsMock, []byte(""), nil))
|
||||
|
||||
require.NoError(t, ls.AddToLocaleFromJSON("lang1", testDataJSON1))
|
||||
require.NoError(t, ls.AddToLocaleFromJSON("lang2", testDataJSON2))
|
||||
ls.SetDefaultLang("lang1")
|
||||
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
ls.SetDefaultLang("lang1")
|
||||
lang2, _ := ls.Locale("lang2")
|
||||
|
||||
result := lang1.TrString("fmt", "a", "b")
|
||||
assert.Equal(t, "a b", result)
|
||||
|
||||
result = lang2.TrString("fmt", "a", "b")
|
||||
assert.Equal(t, "b a", result)
|
||||
|
||||
result = lang1.TrString("section.sub")
|
||||
assert.Equal(t, "Sub String", result)
|
||||
|
||||
result = lang2.TrString("section.sub")
|
||||
assert.Equal(t, "Changed Sub String", result)
|
||||
|
||||
langNone, _ := ls.Locale("none")
|
||||
result = langNone.TrString(".dot.name")
|
||||
assert.Equal(t, "Dot Name", result)
|
||||
|
||||
result2 := lang2.TrHTML("section.mixed", "a&b")
|
||||
assert.EqualValues(t, `test value; <span style="color: red; background: none;">a&b</span>`, result2)
|
||||
|
||||
result = lang2.TrString("section.json", "valid")
|
||||
result := lang2.TrString("section.json", "valid")
|
||||
assert.Equal(t, "the JSON is valid", result)
|
||||
|
||||
result = lang2.TrString("nested.outer.inner.json")
|
||||
|
|
@ -127,7 +92,7 @@ commits = fallback value for commits
|
|||
result = lang2.TrString("section.commits")
|
||||
assert.Equal(t, "lots of %d commits", result)
|
||||
|
||||
result2 = lang2.TrPluralString(1, "section.commits", 1)
|
||||
result2 := lang2.TrPluralString(1, "section.commits", 1)
|
||||
assert.EqualValues(t, "one 1 commit", result2)
|
||||
|
||||
result2 = lang2.TrPluralString(3, "section.commits", 3)
|
||||
|
|
@ -156,159 +121,34 @@ commits = fallback value for commits
|
|||
|
||||
result2 = lang2.TrPluralString(7, "section.incomplete", 7)
|
||||
assert.EqualValues(t, "[untranslated] some 7 objects", result2)
|
||||
}
|
||||
|
||||
langs, descs := ls.ListLangNameDesc()
|
||||
assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs)
|
||||
assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs)
|
||||
func TestMissingTranslationHandling(t *testing.T) {
|
||||
ls := NewLocaleStore()
|
||||
|
||||
// Test HasKey for JSON
|
||||
found := lang2.HasKey("section.json")
|
||||
// Currently LocaleStore has to be first populated with langcodes via AddLocaleByIni
|
||||
require.NoError(t, ls.AddLocaleByIni("en-US", "English", MockPluralRuleEnglish, UsedPluralFormsEnglish, []byte(""), nil))
|
||||
require.NoError(t, ls.AddLocaleByIni("fun", "Funlang", MockPluralRule, UsedPluralFormsMock, []byte(""), nil))
|
||||
|
||||
require.NoError(t, ls.AddToLocaleFromJSON("en-US", []byte(`
|
||||
{
|
||||
"incorrect_root_url": "This Forgejo instance...",
|
||||
"meta.last_line": "Hi there!"
|
||||
}`)))
|
||||
require.NoError(t, ls.AddToLocaleFromJSON("fun", []byte(`
|
||||
{
|
||||
"meta.last_line": "This language only has one line that is never used by the UI. It will never have a translation for incorrect_root_url"
|
||||
}`)))
|
||||
|
||||
ls.SetDefaultLang("en-US")
|
||||
|
||||
// Get "fun" locale, make sure it's available
|
||||
funLocale, found := ls.Locale("fun")
|
||||
assert.True(t, found)
|
||||
|
||||
// Test HasKey for INI
|
||||
found = lang2.HasKey("section.sub")
|
||||
assert.True(t, found)
|
||||
// Get translation for a string that this locale doesn't have
|
||||
s := funLocale.TrString("incorrect_root_url")
|
||||
|
||||
found = lang1.HasKey("no-such")
|
||||
assert.False(t, found)
|
||||
assert.Equal(t, "no-such", lang1.TrString("no-such"))
|
||||
require.NoError(t, ls.Close())
|
||||
}
|
||||
|
||||
func TestLocaleStoreMoreSource(t *testing.T) {
|
||||
testData1 := []byte(`
|
||||
a=11
|
||||
b=12
|
||||
`)
|
||||
|
||||
testData2 := []byte(`
|
||||
b=21
|
||||
c=22
|
||||
`)
|
||||
|
||||
ls := NewLocaleStore()
|
||||
require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRule, UsedPluralFormsMock, testData1, testData2))
|
||||
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"))
|
||||
}
|
||||
|
||||
type stringerPointerReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (s *stringerPointerReceiver) String() string {
|
||||
return s.s
|
||||
}
|
||||
|
||||
type stringerStructReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (s stringerStructReceiver) String() string {
|
||||
return s.s
|
||||
}
|
||||
|
||||
type errorStructReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e errorStructReceiver) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
type errorPointerReceiver struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorPointerReceiver) Error() string {
|
||||
return e.s
|
||||
}
|
||||
|
||||
func TestLocaleWithTemplate(t *testing.T) {
|
||||
ls := NewLocaleStore()
|
||||
require.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", MockPluralRule, UsedPluralFormsMock, []byte(`key=<a>%s</a>`), nil))
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
|
||||
tmpl := template.New("test").Funcs(template.FuncMap{"tr": lang1.TrHTML})
|
||||
tmpl = template.Must(tmpl.Parse(`{{tr "key" .var}}`))
|
||||
|
||||
cases := []struct {
|
||||
in any
|
||||
want string
|
||||
}{
|
||||
{"<str>", "<a><str></a>"},
|
||||
{[]byte("<bytes>"), "<a>[60 98 121 116 101 115 62]</a>"},
|
||||
{template.HTML("<html>"), "<a><html></a>"},
|
||||
{stringerPointerReceiver{"<stringerPointerReceiver>"}, "<a>{<stringerPointerReceiver>}</a>"},
|
||||
{&stringerPointerReceiver{"<stringerPointerReceiver ptr>"}, "<a><stringerPointerReceiver ptr></a>"},
|
||||
{stringerStructReceiver{"<stringerStructReceiver>"}, "<a><stringerStructReceiver></a>"},
|
||||
{&stringerStructReceiver{"<stringerStructReceiver ptr>"}, "<a><stringerStructReceiver ptr></a>"},
|
||||
{errorStructReceiver{"<errorStructReceiver>"}, "<a><errorStructReceiver></a>"},
|
||||
{&errorStructReceiver{"<errorStructReceiver ptr>"}, "<a><errorStructReceiver ptr></a>"},
|
||||
{errorPointerReceiver{"<errorPointerReceiver>"}, "<a>{<errorPointerReceiver>}</a>"},
|
||||
{&errorPointerReceiver{"<errorPointerReceiver ptr>"}, "<a><errorPointerReceiver ptr></a>"},
|
||||
}
|
||||
|
||||
buf := &strings.Builder{}
|
||||
for _, c := range cases {
|
||||
buf.Reset()
|
||||
require.NoError(t, tmpl.Execute(buf, map[string]any{"var": c.in}))
|
||||
assert.Equal(t, c.want, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocaleStoreQuirks(t *testing.T) {
|
||||
const nl = "\n"
|
||||
q := func(q1, s string, q2 ...string) string {
|
||||
return q1 + s + strings.Join(q2, "")
|
||||
}
|
||||
testDataList := []struct {
|
||||
in string
|
||||
out string
|
||||
hint string
|
||||
}{
|
||||
{` xx`, `xx`, "simple, no quote"},
|
||||
{`" xx"`, ` xx`, "simple, double-quote"},
|
||||
{`' xx'`, ` xx`, "simple, single-quote"},
|
||||
{"` xx`", ` xx`, "simple, back-quote"},
|
||||
|
||||
{`x\"y`, `x\"y`, "no unescape, simple"},
|
||||
{q(`"`, `x\"y`, `"`), `"x\"y"`, "unescape, double-quote"},
|
||||
{q(`'`, `x\"y`, `'`), `x\"y`, "no unescape, single-quote"},
|
||||
{q("`", `x\"y`, "`"), `x\"y`, "no unescape, back-quote"},
|
||||
|
||||
{q(`"`, `x\"y`) + nl + "b=", `"x\"y`, "half open, double-quote"},
|
||||
{q(`'`, `x\"y`) + nl + "b=", `'x\"y`, "half open, single-quote"},
|
||||
{q("`", `x\"y`) + nl + "b=`", `x\"y` + nl + "b=", "half open, back-quote, multi-line"},
|
||||
|
||||
{`x ; y`, `x ; y`, "inline comment (;)"},
|
||||
{`x # y`, `x # y`, "inline comment (#)"},
|
||||
{`x \; y`, `x ; y`, `inline comment (\;)`},
|
||||
{`x \# y`, `x # y`, `inline comment (\#)`},
|
||||
}
|
||||
|
||||
for _, testData := range testDataList {
|
||||
ls := NewLocaleStore()
|
||||
err := ls.AddLocaleByIni("lang1", "Lang1", nil, nil, []byte("a="+testData.in), nil)
|
||||
lang1, _ := ls.Locale("lang1")
|
||||
require.NoError(t, err, testData.hint)
|
||||
assert.Equal(t, testData.out, lang1.TrString("a"), testData.hint)
|
||||
require.NoError(t, ls.Close())
|
||||
}
|
||||
|
||||
// TODO: Crowdin needs the strings to be quoted correctly and doesn't like incomplete quotes
|
||||
// and Crowdin always outputs quoted strings if there are quotes in the strings.
|
||||
// So, Gitea's `key="quoted" unquoted` content shouldn't be used on Crowdin directly,
|
||||
// it should be converted to `key="\"quoted\" unquoted"` first.
|
||||
// TODO: We can not use UnescapeValueDoubleQuotes=true, because there are a lot of back-quotes in en-US.ini,
|
||||
// then Crowdin will output:
|
||||
// > key = "`x \" y`"
|
||||
// Then Gitea will read a string with back-quotes, which is incorrect.
|
||||
// TODO: Crowdin might generate multi-line strings, quoted by double-quote, it's not supported by LocaleStore
|
||||
// LocaleStore uses back-quote for multi-line strings, it's not supported by Crowdin.
|
||||
// TODO: Crowdin doesn't support back-quote as string quoter, it mainly uses double-quote
|
||||
// so, the following line will be parsed as: value="`first", comment="second`" on Crowdin
|
||||
// > a = `first; second`
|
||||
// Verify fallback to English
|
||||
assert.True(t, strings.HasPrefix(s, "This Forgejo instance..."))
|
||||
}
|
||||
|
|
|
|||
120
modules/translation/plural_rules_test.go
Normal file
120
modules/translation/plural_rules_test.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package translation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/translation/i18n"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetPluralRule(t *testing.T) {
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en"))
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en-US"))
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en_UK"))
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("nds"))
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("de-DE"))
|
||||
|
||||
assert.Equal(t, PluralRuleOneForm, GetPluralRuleImpl("zh"))
|
||||
assert.Equal(t, PluralRuleOneForm, GetPluralRuleImpl("ja"))
|
||||
|
||||
assert.Equal(t, PluralRuleBengali, GetPluralRuleImpl("bn"))
|
||||
|
||||
assert.Equal(t, PluralRuleIcelandic, GetPluralRuleImpl("is"))
|
||||
|
||||
assert.Equal(t, PluralRuleFilipino, GetPluralRuleImpl("fil"))
|
||||
|
||||
assert.Equal(t, PluralRuleCzech, GetPluralRuleImpl("cs"))
|
||||
|
||||
assert.Equal(t, PluralRuleRussian, GetPluralRuleImpl("ru"))
|
||||
|
||||
assert.Equal(t, PluralRulePolish, GetPluralRuleImpl("pl"))
|
||||
|
||||
assert.Equal(t, PluralRuleLatvian, GetPluralRuleImpl("lv"))
|
||||
|
||||
assert.Equal(t, PluralRuleLithuanian, GetPluralRuleImpl("lt"))
|
||||
|
||||
assert.Equal(t, PluralRuleFrench, GetPluralRuleImpl("fr"))
|
||||
|
||||
assert.Equal(t, PluralRuleCatalan, GetPluralRuleImpl("ca"))
|
||||
|
||||
assert.Equal(t, PluralRuleSlovenian, GetPluralRuleImpl("sl"))
|
||||
|
||||
assert.Equal(t, PluralRuleArabic, GetPluralRuleImpl("ar"))
|
||||
|
||||
assert.Equal(t, PluralRuleCatalan, GetPluralRuleImpl("pt-PT"))
|
||||
assert.Equal(t, PluralRuleFrench, GetPluralRuleImpl("pt-BR"))
|
||||
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("invalid"))
|
||||
}
|
||||
|
||||
func TestApplyPluralRule(t *testing.T) {
|
||||
testCases := []struct {
|
||||
expect i18n.PluralFormIndex
|
||||
pluralRule int
|
||||
values []int64
|
||||
}{
|
||||
{i18n.PluralFormOne, PluralRuleDefault, []int64{1}},
|
||||
{i18n.PluralFormOther, PluralRuleDefault, []int64{0, 2, 10, 256}},
|
||||
|
||||
{i18n.PluralFormOther, PluralRuleOneForm, []int64{0, 1, 2}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleBengali, []int64{0, 1}},
|
||||
{i18n.PluralFormOther, PluralRuleBengali, []int64{2, 10, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleIcelandic, []int64{1, 21, 31}},
|
||||
{i18n.PluralFormOther, PluralRuleIcelandic, []int64{0, 2, 11, 15, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleFilipino, []int64{0, 1, 2, 3, 5, 7, 8, 10, 11, 12, 257}},
|
||||
{i18n.PluralFormOther, PluralRuleFilipino, []int64{4, 6, 9, 14, 16, 19, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleCzech, []int64{1}},
|
||||
{i18n.PluralFormFew, PluralRuleCzech, []int64{2, 3, 4}},
|
||||
{i18n.PluralFormOther, PluralRuleCzech, []int64{5, 0, 12, 78, 254}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleRussian, []int64{1, 21, 31}},
|
||||
{i18n.PluralFormFew, PluralRuleRussian, []int64{2, 23, 34}},
|
||||
{i18n.PluralFormMany, PluralRuleRussian, []int64{0, 5, 11, 37, 111, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRulePolish, []int64{1}},
|
||||
{i18n.PluralFormFew, PluralRulePolish, []int64{2, 23, 34}},
|
||||
{i18n.PluralFormMany, PluralRulePolish, []int64{0, 5, 11, 21, 37, 256}},
|
||||
|
||||
{i18n.PluralFormZero, PluralRuleLatvian, []int64{0, 10, 11, 17}},
|
||||
{i18n.PluralFormOne, PluralRuleLatvian, []int64{1, 21, 71}},
|
||||
{i18n.PluralFormOther, PluralRuleLatvian, []int64{2, 7, 22, 23, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleLithuanian, []int64{1, 21, 31}},
|
||||
{i18n.PluralFormFew, PluralRuleLithuanian, []int64{2, 5, 9, 23, 34, 256}},
|
||||
{i18n.PluralFormMany, PluralRuleLithuanian, []int64{0, 10, 11, 18}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleFrench, []int64{0, 1}},
|
||||
{i18n.PluralFormMany, PluralRuleFrench, []int64{1000000, 2000000}},
|
||||
{i18n.PluralFormOther, PluralRuleFrench, []int64{2, 4, 10, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleCatalan, []int64{1}},
|
||||
{i18n.PluralFormMany, PluralRuleCatalan, []int64{1000000, 2000000}},
|
||||
{i18n.PluralFormOther, PluralRuleCatalan, []int64{0, 2, 4, 10, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleSlovenian, []int64{1, 101, 201, 501}},
|
||||
{i18n.PluralFormTwo, PluralRuleSlovenian, []int64{2, 102, 202, 502}},
|
||||
{i18n.PluralFormFew, PluralRuleSlovenian, []int64{3, 103, 203, 503, 4, 104, 204, 504}},
|
||||
{i18n.PluralFormOther, PluralRuleSlovenian, []int64{0, 5, 11, 12, 20, 256}},
|
||||
|
||||
{i18n.PluralFormZero, PluralRuleArabic, []int64{0}},
|
||||
{i18n.PluralFormOne, PluralRuleArabic, []int64{1}},
|
||||
{i18n.PluralFormTwo, PluralRuleArabic, []int64{2}},
|
||||
{i18n.PluralFormFew, PluralRuleArabic, []int64{3, 4, 9, 10, 103, 104}},
|
||||
{i18n.PluralFormMany, PluralRuleArabic, []int64{11, 12, 13, 14, 17, 111, 256}},
|
||||
{i18n.PluralFormOther, PluralRuleArabic, []int64{100, 101, 102}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
for _, n := range tc.values {
|
||||
assert.Equal(t, tc.expect, PluralRules[tc.pluralRule](n), "Testcase for plural rule %d, value %d", tc.pluralRule, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package translation
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package translation
|
||||
|
||||
// TODO: make this package friendly to testing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
|
|
@ -48,111 +47,3 @@ func TestPrettyNumber(t *testing.T) {
|
|||
assert.Equal(t, "1,000,000", l.PrettyNumber(1000000))
|
||||
assert.Equal(t, "1,000,000.1", l.PrettyNumber(1000000.1))
|
||||
}
|
||||
|
||||
func TestGetPluralRule(t *testing.T) {
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en"))
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en-US"))
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("en_UK"))
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("nds"))
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("de-DE"))
|
||||
|
||||
assert.Equal(t, PluralRuleOneForm, GetPluralRuleImpl("zh"))
|
||||
assert.Equal(t, PluralRuleOneForm, GetPluralRuleImpl("ja"))
|
||||
|
||||
assert.Equal(t, PluralRuleBengali, GetPluralRuleImpl("bn"))
|
||||
|
||||
assert.Equal(t, PluralRuleIcelandic, GetPluralRuleImpl("is"))
|
||||
|
||||
assert.Equal(t, PluralRuleFilipino, GetPluralRuleImpl("fil"))
|
||||
|
||||
assert.Equal(t, PluralRuleCzech, GetPluralRuleImpl("cs"))
|
||||
|
||||
assert.Equal(t, PluralRuleRussian, GetPluralRuleImpl("ru"))
|
||||
|
||||
assert.Equal(t, PluralRulePolish, GetPluralRuleImpl("pl"))
|
||||
|
||||
assert.Equal(t, PluralRuleLatvian, GetPluralRuleImpl("lv"))
|
||||
|
||||
assert.Equal(t, PluralRuleLithuanian, GetPluralRuleImpl("lt"))
|
||||
|
||||
assert.Equal(t, PluralRuleFrench, GetPluralRuleImpl("fr"))
|
||||
|
||||
assert.Equal(t, PluralRuleCatalan, GetPluralRuleImpl("ca"))
|
||||
|
||||
assert.Equal(t, PluralRuleSlovenian, GetPluralRuleImpl("sl"))
|
||||
|
||||
assert.Equal(t, PluralRuleArabic, GetPluralRuleImpl("ar"))
|
||||
|
||||
assert.Equal(t, PluralRuleCatalan, GetPluralRuleImpl("pt-PT"))
|
||||
assert.Equal(t, PluralRuleFrench, GetPluralRuleImpl("pt-BR"))
|
||||
|
||||
assert.Equal(t, PluralRuleDefault, GetPluralRuleImpl("invalid"))
|
||||
}
|
||||
|
||||
func TestApplyPluralRule(t *testing.T) {
|
||||
testCases := []struct {
|
||||
expect i18n.PluralFormIndex
|
||||
pluralRule int
|
||||
values []int64
|
||||
}{
|
||||
{i18n.PluralFormOne, PluralRuleDefault, []int64{1}},
|
||||
{i18n.PluralFormOther, PluralRuleDefault, []int64{0, 2, 10, 256}},
|
||||
|
||||
{i18n.PluralFormOther, PluralRuleOneForm, []int64{0, 1, 2}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleBengali, []int64{0, 1}},
|
||||
{i18n.PluralFormOther, PluralRuleBengali, []int64{2, 10, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleIcelandic, []int64{1, 21, 31}},
|
||||
{i18n.PluralFormOther, PluralRuleIcelandic, []int64{0, 2, 11, 15, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleFilipino, []int64{0, 1, 2, 3, 5, 7, 8, 10, 11, 12, 257}},
|
||||
{i18n.PluralFormOther, PluralRuleFilipino, []int64{4, 6, 9, 14, 16, 19, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleCzech, []int64{1}},
|
||||
{i18n.PluralFormFew, PluralRuleCzech, []int64{2, 3, 4}},
|
||||
{i18n.PluralFormOther, PluralRuleCzech, []int64{5, 0, 12, 78, 254}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleRussian, []int64{1, 21, 31}},
|
||||
{i18n.PluralFormFew, PluralRuleRussian, []int64{2, 23, 34}},
|
||||
{i18n.PluralFormMany, PluralRuleRussian, []int64{0, 5, 11, 37, 111, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRulePolish, []int64{1}},
|
||||
{i18n.PluralFormFew, PluralRulePolish, []int64{2, 23, 34}},
|
||||
{i18n.PluralFormMany, PluralRulePolish, []int64{0, 5, 11, 21, 37, 256}},
|
||||
|
||||
{i18n.PluralFormZero, PluralRuleLatvian, []int64{0, 10, 11, 17}},
|
||||
{i18n.PluralFormOne, PluralRuleLatvian, []int64{1, 21, 71}},
|
||||
{i18n.PluralFormOther, PluralRuleLatvian, []int64{2, 7, 22, 23, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleLithuanian, []int64{1, 21, 31}},
|
||||
{i18n.PluralFormFew, PluralRuleLithuanian, []int64{2, 5, 9, 23, 34, 256}},
|
||||
{i18n.PluralFormMany, PluralRuleLithuanian, []int64{0, 10, 11, 18}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleFrench, []int64{0, 1}},
|
||||
{i18n.PluralFormMany, PluralRuleFrench, []int64{1000000, 2000000}},
|
||||
{i18n.PluralFormOther, PluralRuleFrench, []int64{2, 4, 10, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleCatalan, []int64{1}},
|
||||
{i18n.PluralFormMany, PluralRuleCatalan, []int64{1000000, 2000000}},
|
||||
{i18n.PluralFormOther, PluralRuleCatalan, []int64{0, 2, 4, 10, 256}},
|
||||
|
||||
{i18n.PluralFormOne, PluralRuleSlovenian, []int64{1, 101, 201, 501}},
|
||||
{i18n.PluralFormTwo, PluralRuleSlovenian, []int64{2, 102, 202, 502}},
|
||||
{i18n.PluralFormFew, PluralRuleSlovenian, []int64{3, 103, 203, 503, 4, 104, 204, 504}},
|
||||
{i18n.PluralFormOther, PluralRuleSlovenian, []int64{0, 5, 11, 12, 20, 256}},
|
||||
|
||||
{i18n.PluralFormZero, PluralRuleArabic, []int64{0}},
|
||||
{i18n.PluralFormOne, PluralRuleArabic, []int64{1}},
|
||||
{i18n.PluralFormTwo, PluralRuleArabic, []int64{2}},
|
||||
{i18n.PluralFormFew, PluralRuleArabic, []int64{3, 4, 9, 10, 103, 104}},
|
||||
{i18n.PluralFormMany, PluralRuleArabic, []int64{11, 12, 13, 14, 17, 111, 256}},
|
||||
{i18n.PluralFormOther, PluralRuleArabic, []int64{100, 101, 102}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
for _, n := range tc.values {
|
||||
assert.Equal(t, tc.expect, PluralRules[tc.pluralRule](n), "Testcase for plural rule %d, value %d", tc.pluralRule, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
|
||||
"forgejo.org/models/unittest"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/translation/i18n"
|
||||
files_service "forgejo.org/services/repository/files"
|
||||
"forgejo.org/tests"
|
||||
|
||||
|
|
@ -20,28 +19,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMissingTranslationHandling(t *testing.T) {
|
||||
// Currently new languages can only be added to localestore via AddLocaleByIni
|
||||
// so this line is here to make the other one work. When INI locales are removed,
|
||||
// it will not be needed by this test.
|
||||
i18n.DefaultLocales.AddLocaleByIni("fun", "Funlang", nil, nil, []byte(""), nil)
|
||||
|
||||
// Add a testing locale to the store
|
||||
i18n.DefaultLocales.AddToLocaleFromJSON("fun", []byte(`{
|
||||
"meta.last_line": "This language only has one line that is never used by the UI. It will never have a translation for incorrect_root_url"
|
||||
}`))
|
||||
|
||||
// Get "fun" locale, make sure it's available
|
||||
funLocale, found := i18n.DefaultLocales.Locale("fun")
|
||||
assert.True(t, found)
|
||||
|
||||
// Get translation for a string that this locale doesn't have
|
||||
s := funLocale.TrString("incorrect_root_url")
|
||||
|
||||
// Verify fallback to English
|
||||
assert.True(t, strings.HasPrefix(s, "This Forgejo instance"))
|
||||
}
|
||||
|
||||
// TestDataSizeTranslation is a test for usage of TrSize in file size display
|
||||
func TestDataSizeTranslation(t *testing.T) {
|
||||
onApplicationRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue