diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index fb81622bd6945..12e84ff6f993d 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -214,6 +214,16 @@ jobs: GOOS: linux GOARCH: 386 + i18n-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true + - run: make i18n-check + docs: if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 52e4aefb6b729..c64d91a7ebbed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -183,7 +183,7 @@ Here's how to run the test suite: ## Translation All translation work happens on [Crowdin](https://translate.gitea.com). -The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini). +The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.json). It is synced regularly with Crowdin. \ Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \ Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version. diff --git a/Makefile b/Makefile index 605461158077c..299eb1c164f97 100644 --- a/Makefile +++ b/Makefile @@ -887,6 +887,16 @@ lockfile-check: exit 1; \ fi +.PHONY: i18n-backport +i18n-backport: + @echo "Backport translations ..." + $(GO) run tools/i18n/backport.go + +.PHONY: i18n-check +i18n-check: + @echo "Checking unused translations..." + $(GO) run tools/i18n/check.go + .PHONY: generate-gitignore generate-gitignore: ## update gitignore files $(GO) run build/generate-gitignores.go diff --git a/build/backport-locales.go b/build/backport-locales.go deleted file mode 100644 index d112dd72bd56e..0000000000000 --- a/build/backport-locales.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build ignore - -package main - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/setting" -) - -func main() { - if len(os.Args) != 2 { - println("usage: backport-locales ") - println("eg: backport-locales release/v1.19") - os.Exit(1) - } - - mustNoErr := func(err error) { - if err != nil { - panic(err) - } - } - collectInis := func(ref string) map[string]setting.ConfigProvider { - inis := map[string]setting.ConfigProvider{} - err := filepath.WalkDir("options/locale", func(path string, d os.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() || !strings.HasSuffix(d.Name(), ".ini") { - return nil - } - cfg, err := setting.NewConfigProviderForLocale(path) - mustNoErr(err) - inis[path] = cfg - fmt.Printf("collecting: %s @ %s\n", path, ref) - return nil - }) - mustNoErr(err) - return inis - } - - // collect new locales from current working directory - inisNew := collectInis("HEAD") - - // switch to the target ref, and collect the old locales - cmd := exec.Command("git", "checkout", os.Args[1]) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - mustNoErr(cmd.Run()) - inisOld := collectInis(os.Args[1]) - - // use old en-US as the base, and copy the new translations to the old locales - enUsOld := inisOld["options/locale/locale_en-US.ini"] - brokenWarned := make(container.Set[string]) - for path, iniOld := range inisOld { - if iniOld == enUsOld { - continue - } - iniNew := inisNew[path] - if iniNew == nil { - continue - } - for _, secEnUS := range enUsOld.Sections() { - secOld := iniOld.Section(secEnUS.Name()) - secNew := iniNew.Section(secEnUS.Name()) - for _, keyEnUs := range secEnUS.Keys() { - if secNew.HasKey(keyEnUs.Name()) { - oldStr := secOld.Key(keyEnUs.Name()).String() - newStr := secNew.Key(keyEnUs.Name()).String() - broken := oldStr != "" && strings.Count(oldStr, "%") != strings.Count(newStr, "%") - broken = broken || strings.Contains(oldStr, "\n") || strings.Contains(oldStr, "\n") - if broken { - brokenWarned.Add(secOld.Name() + "." + keyEnUs.Name()) - fmt.Println("----") - fmt.Printf("WARNING: skip broken locale: %s , [%s] %s\n", path, secEnUS.Name(), keyEnUs.Name()) - fmt.Printf("\told: %s\n", strings.ReplaceAll(oldStr, "\n", "\\n")) - fmt.Printf("\tnew: %s\n", strings.ReplaceAll(newStr, "\n", "\\n")) - continue - } - secOld.Key(keyEnUs.Name()).SetValue(newStr) - } - } - } - mustNoErr(iniOld.SaveTo(path)) - } - - fmt.Println("========") - - for path, iniNew := range inisNew { - for _, sec := range iniNew.Sections() { - for _, key := range sec.Keys() { - str := sec.Key(key.Name()).String() - broken := strings.Contains(str, "\n") - broken = broken || strings.HasPrefix(str, "`") != strings.HasSuffix(str, "`") - broken = broken || strings.HasPrefix(str, "\"`") - broken = broken || strings.HasPrefix(str, "`\"") - broken = broken || strings.Count(str, `"`)%2 == 1 - broken = broken || strings.Count(str, "`")%2 == 1 - if broken && !brokenWarned.Contains(sec.Name()+"."+key.Name()) { - fmt.Printf("WARNING: found broken locale: %s , [%s] %s\n", path, sec.Name(), key.Name()) - fmt.Printf("\tstr: %s\n", strings.ReplaceAll(str, "\n", "\\n")) - fmt.Println("----") - } - } - } - } -} diff --git a/go.mod b/go.mod index 26a2b818efcea..d0d43b7aa13c5 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/caddyserver/certmagic v0.24.0 github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20251013092601-6327009efd21 github.com/chi-middleware/proxy v1.1.1 + github.com/deckarep/golang-set/v2 v2.8.0 github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 github.com/dustin/go-humanize v1.0.1 diff --git a/go.sum b/go.sum index 7490b3923c489..e8c74623986c4 100644 --- a/go.sum +++ b/go.sum @@ -256,6 +256,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= +github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA= diff --git a/models/actions/runner.go b/models/actions/runner.go index 84398b143b8cf..5e08c94d121d3 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -115,6 +115,7 @@ func (r *ActionRunner) StatusName() string { } func (r *ActionRunner) StatusLocaleName(lang translation.Locale) string { + // i18n-check: actions.runners.status.* return lang.TrString("actions.runners.status." + r.StatusName()) } diff --git a/models/actions/status.go b/models/actions/status.go index 2b1d70613c71b..895fd52fb36de 100644 --- a/models/actions/status.go +++ b/models/actions/status.go @@ -43,6 +43,7 @@ func (s Status) String() string { // LocaleString returns the locale string name of the Status func (s Status) LocaleString(lang translation.Locale) string { + // i18n-check: actions.status.* return lang.TrString("actions.status." + s.String()) } diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 2ae5937a3d8ff..6aad2af1c0896 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -210,6 +210,7 @@ func (status *CommitStatus) APIURL(ctx context.Context) string { // LocaleString returns the locale string name of the Status func (status *CommitStatus) LocaleString(lang translation.Locale) string { + // i18n-check: repo.commitstatus.* return lang.TrString("repo.commitstatus." + status.State.String()) } diff --git a/models/issues/comment.go b/models/issues/comment.go index f15618bf50088..1fc0adb126074 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -226,11 +226,13 @@ const ( // LocaleString returns the locale string name of the role func (r RoleInRepo) LocaleString(lang translation.Locale) string { + // i18n-check: repo.issues.role.* return lang.TrString("repo.issues.role." + string(r)) } // LocaleHelper returns the locale tooltip of the role func (r RoleInRepo) LocaleHelper(lang translation.Locale) string { + // i18n-check: repo.issues.role.*_helper return lang.TrString("repo.issues.role." + string(r) + "_helper") } diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go index a1e101dd621cb..e0781cbf96f6e 100644 --- a/modules/auth/password/password.go +++ b/modules/auth/password/password.go @@ -128,6 +128,7 @@ func BuildComplexityError(locale translation.Locale) template.HTML { buffer.WriteString("