2022-08-28 10:43:25 +01:00
|
|
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
2025-04-22 03:49:37 +00:00
|
|
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
2022-11-27 13:20:29 -05:00
|
|
|
// SPDX-License-Identifier: MIT
|
2022-08-28 10:43:25 +01:00
|
|
|
|
|
|
|
|
package i18n
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2024-02-15 05:48:45 +08:00
|
|
|
"html/template"
|
|
|
|
|
"slices"
|
2022-08-28 10:43:25 +01:00
|
|
|
|
2025-03-27 19:40:14 +00:00
|
|
|
"forgejo.org/modules/log"
|
|
|
|
|
"forgejo.org/modules/setting"
|
2025-04-02 14:57:45 +00:00
|
|
|
"forgejo.org/modules/translation/localeiter"
|
2025-03-27 19:40:14 +00:00
|
|
|
"forgejo.org/modules/util"
|
2022-08-28 10:43:25 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// This file implements the static LocaleStore that will not watch for changes
|
|
|
|
|
|
|
|
|
|
type locale struct {
|
|
|
|
|
store *localeStore
|
|
|
|
|
langName string
|
|
|
|
|
idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap
|
2024-12-30 20:03:37 +01:00
|
|
|
|
|
|
|
|
newStyleMessages map[string]string
|
|
|
|
|
pluralRule PluralFormRule
|
2025-05-03 14:11:01 +00:00
|
|
|
usedPluralForms []PluralFormIndex
|
2022-08-28 10:43:25 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-15 05:48:45 +08:00
|
|
|
var _ Locale = (*locale)(nil)
|
|
|
|
|
|
2022-08-28 10:43:25 +01:00
|
|
|
type localeStore struct {
|
|
|
|
|
// After initializing has finished, these fields are read-only.
|
|
|
|
|
langNames []string
|
|
|
|
|
langDescs []string
|
|
|
|
|
|
|
|
|
|
localeMap map[string]*locale
|
|
|
|
|
trKeyToIdxMap map[string]int
|
|
|
|
|
|
|
|
|
|
defaultLang string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewLocaleStore creates a static locale store
|
|
|
|
|
func NewLocaleStore() LocaleStore {
|
|
|
|
|
return &localeStore{localeMap: make(map[string]*locale), trKeyToIdxMap: make(map[string]int)}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-30 20:03:37 +01:00
|
|
|
const (
|
|
|
|
|
PluralFormSeparator string = "\036"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// A note about pluralization rules.
|
|
|
|
|
// go-i18n supports plural rules in theory.
|
|
|
|
|
// In practice, it relies on another library that hardcodes a list of common languages
|
|
|
|
|
// and their plural rules, and does not support languages not hardcoded there.
|
|
|
|
|
// So we pretend that all languages are English and use our own function to extract
|
|
|
|
|
// the correct plural form for a given count and language.
|
|
|
|
|
|
2022-08-28 10:43:25 +01:00
|
|
|
// AddLocaleByIni adds locale by ini into the store
|
2025-05-03 14:11:01 +00:00
|
|
|
func (store *localeStore) AddLocaleByIni(langName, langDesc string, pluralRule PluralFormRule, usedPluralForms []PluralFormIndex, source, moreSource []byte) error {
|
2022-08-28 10:43:25 +01:00
|
|
|
if _, ok := store.localeMap[langName]; ok {
|
|
|
|
|
return ErrLocaleAlreadyExist
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
store.langNames = append(store.langNames, langName)
|
|
|
|
|
store.langDescs = append(store.langDescs, langDesc)
|
|
|
|
|
|
2025-05-03 14:11:01 +00:00
|
|
|
l := &locale{store: store, langName: langName, idxToMsgMap: make(map[int]string), pluralRule: pluralRule, usedPluralForms: usedPluralForms, newStyleMessages: make(map[string]string)}
|
2022-08-28 10:43:25 +01:00
|
|
|
store.localeMap[l.langName] = l
|
|
|
|
|
|
2023-06-02 17:27:30 +08:00
|
|
|
iniFile, err := setting.NewConfigProviderForLocale(source, moreSource)
|
2022-08-28 10:43:25 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to load ini: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, section := range iniFile.Sections() {
|
|
|
|
|
for _, key := range section.Keys() {
|
|
|
|
|
var trKey string
|
2024-01-21 19:35:58 +00:00
|
|
|
// see https://codeberg.org/forgejo/discussions/issues/104
|
|
|
|
|
// https://github.com/WeblateOrg/weblate/issues/10831
|
|
|
|
|
// for an explanation of why "common" is an alternative
|
|
|
|
|
if section.Name() == "" || section.Name() == "DEFAULT" || section.Name() == "common" {
|
2022-08-28 10:43:25 +01:00
|
|
|
trKey = key.Name()
|
|
|
|
|
} else {
|
|
|
|
|
trKey = section.Name() + "." + key.Name()
|
|
|
|
|
}
|
|
|
|
|
idx, ok := store.trKeyToIdxMap[trKey]
|
|
|
|
|
if !ok {
|
|
|
|
|
idx = len(store.trKeyToIdxMap)
|
|
|
|
|
store.trKeyToIdxMap[trKey] = idx
|
|
|
|
|
}
|
|
|
|
|
l.idxToMsgMap[idx] = key.Value()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-30 20:03:37 +01:00
|
|
|
func (store *localeStore) AddToLocaleFromJSON(langName string, source []byte) error {
|
|
|
|
|
locale, ok := store.localeMap[langName]
|
|
|
|
|
if !ok {
|
|
|
|
|
return ErrLocaleDoesNotExist
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-02 14:57:45 +00:00
|
|
|
return localeiter.IterateMessagesNextContent(source, func(key, pluralForm, value string) error {
|
|
|
|
|
msgKey := key
|
|
|
|
|
if pluralForm != "" {
|
|
|
|
|
msgKey = key + PluralFormSeparator + pluralForm
|
|
|
|
|
}
|
|
|
|
|
locale.newStyleMessages[msgKey] = value
|
|
|
|
|
return nil
|
|
|
|
|
})
|
2024-12-30 20:03:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *locale) LookupNewStyleMessage(trKey string) string {
|
|
|
|
|
if msg, ok := l.newStyleMessages[trKey]; ok {
|
|
|
|
|
return msg
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
[v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183
Followup to https://codeberg.org/forgejo/forgejo/pulls/6203
Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.
Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.
However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.
The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.
I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
|
|
|
func (l *locale) LookupPluralByCount(trKey string, count any, isDefaultLang bool) string {
|
2024-12-30 20:03:37 +01:00
|
|
|
n, err := util.ToInt64(count)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error("Invalid plural count '%s'", count)
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pluralForm := l.pluralRule(n)
|
[v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183
Followup to https://codeberg.org/forgejo/forgejo/pulls/6203
Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.
Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.
However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.
The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.
I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
|
|
|
return l.LookupPluralByForm(trKey, pluralForm, isDefaultLang)
|
2025-05-03 14:11:01 +00:00
|
|
|
}
|
|
|
|
|
|
[v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183
Followup to https://codeberg.org/forgejo/forgejo/pulls/6203
Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.
Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.
However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.
The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.
I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
|
|
|
func (l *locale) LookupPluralByForm(trKey string, pluralForm PluralFormIndex, isDefaultLang bool) string {
|
2024-12-30 20:03:37 +01:00
|
|
|
suffix := ""
|
|
|
|
|
switch pluralForm {
|
|
|
|
|
case PluralFormZero:
|
|
|
|
|
suffix = PluralFormSeparator + "zero"
|
|
|
|
|
case PluralFormOne:
|
|
|
|
|
suffix = PluralFormSeparator + "one"
|
|
|
|
|
case PluralFormTwo:
|
|
|
|
|
suffix = PluralFormSeparator + "two"
|
|
|
|
|
case PluralFormFew:
|
|
|
|
|
suffix = PluralFormSeparator + "few"
|
|
|
|
|
case PluralFormMany:
|
|
|
|
|
suffix = PluralFormSeparator + "many"
|
|
|
|
|
case PluralFormOther:
|
|
|
|
|
// No suffix for the "other" string.
|
ci: detect and prevent empty `case` statements in Go code (#11593)
One of the security patches released 2026-03-09 [fixed a vulnerability](https://codeberg.org/forgejo/forgejo/pulls/11513/commits/d1c7b04d09f6a13896eaa1322ac690b2021539da) caused by a misapplication of Go `case` statements, where the implementation would have been correct if Go `case` statements automatically fall through to the next case block, but they do not. This PR adds a semgrep rule which detects any empty `case` statement and raises an error, in order to prevent this coding mistake in the future.
For example, code like this will now trigger a build error:
```go
switch setting.Protocol {
case setting.HTTPUnix:
case setting.FCGI:
case setting.FCGIUnix:
default:
defaultLocalURL := string(setting.Protocol) + "://"
}
```
Example error:
```
cmd/web.go
❯❯❱ semgrep.config.forgejo-switch-empty-case
switch has a case block with no content. This is treated as "break" by Go, but developers may
confuse it for "fallthrough". To fix this error, disambiguate by using "break" or
"fallthrough".
279┆ switch setting.Protocol {
280┆ case setting.HTTPUnix:
281┆ case setting.FCGI:
282┆ case setting.FCGIUnix:
283┆ default:
284┆ defaultLocalURL := string(setting.Protocol) + "://"
285┆ if setting.HTTPAddr == "0.0.0.0" {
286┆ defaultLocalURL += "localhost"
287┆ } else {
288┆ defaultLocalURL += setting.HTTPAddr
```
As described in the error output, this error can be fixed by explicitly listing `break` (the real Go behaviour, to do nothing in the block), or by listing `fallthrough` (if the intent was to fall through).
All existing code triggering this detection has been changed to `break` (or, rarely, irrelevant cases have been removed), which should maintain the same code functionality. While performing this fixup, a light analysis was performed on each case and they *appeared* correct, but with ~65 cases I haven't gone into extreme depth.
Tests are present for the semgrep rule in `.semgrep/tests/go.go`.
## Checklist
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
### Documentation
- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.
### Release notes
- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11593
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
2026-03-10 02:50:28 +01:00
|
|
|
break
|
2024-12-30 20:03:37 +01:00
|
|
|
default:
|
2025-05-03 14:11:01 +00:00
|
|
|
log.Error("Invalid plural form index %d", pluralForm)
|
2024-12-30 20:03:37 +01:00
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if result, ok := l.newStyleMessages[trKey+suffix]; ok {
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
[v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183
Followup to https://codeberg.org/forgejo/forgejo/pulls/6203
Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.
Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.
However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.
The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.
I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
|
|
|
// Severify depends on the lang. A missing string in default lang will affect
|
|
|
|
|
// all translations, while community translations may just be incomplete
|
|
|
|
|
logFunc := log.Debug
|
|
|
|
|
if isDefaultLang {
|
|
|
|
|
logFunc = log.Error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logFunc("Missing translation for key `%[1]s`, plural form `%[2]s`", trKey, suffix)
|
2024-12-30 20:03:37 +01:00
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 10:43:25 +01:00
|
|
|
func (store *localeStore) HasLang(langName string) bool {
|
|
|
|
|
_, ok := store.localeMap[langName]
|
|
|
|
|
return ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (store *localeStore) ListLangNameDesc() (names, desc []string) {
|
|
|
|
|
return store.langNames, store.langDescs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetDefaultLang sets default language as a fallback
|
|
|
|
|
func (store *localeStore) SetDefaultLang(lang string) {
|
|
|
|
|
store.defaultLang = lang
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-03 14:11:01 +00:00
|
|
|
func (store *localeStore) GetDefaultLang() string {
|
|
|
|
|
return store.defaultLang
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 10:43:25 +01:00
|
|
|
// Locale returns the locale for the lang or the default language
|
|
|
|
|
func (store *localeStore) Locale(lang string) (Locale, bool) {
|
|
|
|
|
l, found := store.localeMap[lang]
|
|
|
|
|
if !found {
|
|
|
|
|
var ok bool
|
|
|
|
|
l, ok = store.localeMap[store.defaultLang]
|
|
|
|
|
if !ok {
|
|
|
|
|
// no default - return an empty locale
|
|
|
|
|
l = &locale{store: store, idxToMsgMap: make(map[int]string)}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return l, found
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (store *localeStore) Close() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-03 14:11:01 +00:00
|
|
|
func (l *locale) Language() string {
|
|
|
|
|
return l.langName
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-15 05:48:45 +08:00
|
|
|
func (l *locale) TrString(trKey string, trArgs ...any) string {
|
2022-08-28 10:43:25 +01:00
|
|
|
format := trKey
|
|
|
|
|
|
2024-12-30 20:03:37 +01:00
|
|
|
if msg := l.LookupNewStyleMessage(trKey); msg != "" {
|
|
|
|
|
format = msg
|
|
|
|
|
} else {
|
|
|
|
|
// First fallback: old-style translation
|
2025-01-28 15:03:38 +01:00
|
|
|
idx, foundIndex := l.store.trKeyToIdxMap[trKey]
|
2024-12-30 20:03:37 +01:00
|
|
|
found := false
|
2025-01-28 15:03:38 +01:00
|
|
|
if foundIndex {
|
2024-12-30 20:03:37 +01:00
|
|
|
if msg, ok := l.idxToMsgMap[idx]; ok {
|
|
|
|
|
format = msg // use the found translation
|
2024-03-18 12:41:31 +01:00
|
|
|
found = true
|
2022-08-28 10:43:25 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-12-30 20:03:37 +01:00
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
|
// Second fallback: new-style default language
|
|
|
|
|
if defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok {
|
|
|
|
|
if msg := defaultLang.LookupNewStyleMessage(trKey); msg != "" {
|
|
|
|
|
format = msg
|
2025-04-22 03:49:37 +00:00
|
|
|
found = true
|
2025-01-28 15:03:38 +01:00
|
|
|
} else if foundIndex {
|
2024-12-30 20:03:37 +01:00
|
|
|
// Third fallback: old-style default language
|
|
|
|
|
if msg, ok := defaultLang.idxToMsgMap[idx]; ok {
|
|
|
|
|
format = msg
|
|
|
|
|
found = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
|
log.Error("Missing translation %q", trKey)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-18 12:41:31 +01:00
|
|
|
}
|
2022-08-28 10:43:25 +01:00
|
|
|
|
|
|
|
|
msg, err := Format(format, trArgs...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error("Error whilst formatting %q in %s: %v", trKey, l.langName, err)
|
|
|
|
|
}
|
|
|
|
|
return msg
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-30 20:03:37 +01:00
|
|
|
func PrepareArgsForHTML(trArgs ...any) []any {
|
2024-02-15 05:48:45 +08:00
|
|
|
args := slices.Clone(trArgs)
|
|
|
|
|
for i, v := range args {
|
|
|
|
|
switch v := v.(type) {
|
2024-02-18 20:15:24 +08:00
|
|
|
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
|
|
|
|
|
// for most basic types (including template.HTML which is safe), just do nothing and use it
|
ci: detect and prevent empty `case` statements in Go code (#11593)
One of the security patches released 2026-03-09 [fixed a vulnerability](https://codeberg.org/forgejo/forgejo/pulls/11513/commits/d1c7b04d09f6a13896eaa1322ac690b2021539da) caused by a misapplication of Go `case` statements, where the implementation would have been correct if Go `case` statements automatically fall through to the next case block, but they do not. This PR adds a semgrep rule which detects any empty `case` statement and raises an error, in order to prevent this coding mistake in the future.
For example, code like this will now trigger a build error:
```go
switch setting.Protocol {
case setting.HTTPUnix:
case setting.FCGI:
case setting.FCGIUnix:
default:
defaultLocalURL := string(setting.Protocol) + "://"
}
```
Example error:
```
cmd/web.go
❯❯❱ semgrep.config.forgejo-switch-empty-case
switch has a case block with no content. This is treated as "break" by Go, but developers may
confuse it for "fallthrough". To fix this error, disambiguate by using "break" or
"fallthrough".
279┆ switch setting.Protocol {
280┆ case setting.HTTPUnix:
281┆ case setting.FCGI:
282┆ case setting.FCGIUnix:
283┆ default:
284┆ defaultLocalURL := string(setting.Protocol) + "://"
285┆ if setting.HTTPAddr == "0.0.0.0" {
286┆ defaultLocalURL += "localhost"
287┆ } else {
288┆ defaultLocalURL += setting.HTTPAddr
```
As described in the error output, this error can be fixed by explicitly listing `break` (the real Go behaviour, to do nothing in the block), or by listing `fallthrough` (if the intent was to fall through).
All existing code triggering this detection has been changed to `break` (or, rarely, irrelevant cases have been removed), which should maintain the same code functionality. While performing this fixup, a light analysis was performed on each case and they *appeared* correct, but with ~65 cases I haven't gone into extreme depth.
Tests are present for the semgrep rule in `.semgrep/tests/go.go`.
## Checklist
The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
### Documentation
- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.
### Release notes
- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [x] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11593
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
2026-03-10 02:50:28 +01:00
|
|
|
break
|
2024-02-15 05:48:45 +08:00
|
|
|
case string:
|
2024-02-18 20:15:24 +08:00
|
|
|
args[i] = template.HTMLEscapeString(v)
|
2024-02-15 05:48:45 +08:00
|
|
|
case fmt.Stringer:
|
|
|
|
|
args[i] = template.HTMLEscapeString(v.String())
|
2024-02-18 20:15:24 +08:00
|
|
|
default:
|
|
|
|
|
args[i] = template.HTMLEscapeString(fmt.Sprint(v))
|
2024-02-15 05:48:45 +08:00
|
|
|
}
|
|
|
|
|
}
|
2024-12-30 20:03:37 +01:00
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *locale) TrHTML(trKey string, trArgs ...any) template.HTML {
|
|
|
|
|
return template.HTML(l.TrString(trKey, PrepareArgsForHTML(trArgs...)...))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (l *locale) TrPluralString(count any, trKey string, trArgs ...any) template.HTML {
|
[v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183
Followup to https://codeberg.org/forgejo/forgejo/pulls/6203
Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.
Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.
However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.
The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.
I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
|
|
|
message := l.LookupPluralByCount(trKey, count, false)
|
2024-12-30 20:03:37 +01:00
|
|
|
|
|
|
|
|
if message == "" {
|
|
|
|
|
if defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok {
|
[v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183
Followup to https://codeberg.org/forgejo/forgejo/pulls/6203
Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.
Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.
However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.
The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.
I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
|
|
|
message = defaultLang.LookupPluralByCount(trKey, count, true)
|
2024-12-30 20:03:37 +01:00
|
|
|
}
|
|
|
|
|
if message == "" {
|
|
|
|
|
message = trKey
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
message, err := Format(message, PrepareArgsForHTML(trArgs...)...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Error("Error whilst formatting %q in %s: %v", trKey, l.langName, err)
|
|
|
|
|
}
|
|
|
|
|
return template.HTML(message)
|
2024-02-15 05:48:45 +08:00
|
|
|
}
|
|
|
|
|
|
2025-05-03 14:11:01 +00:00
|
|
|
func (l *locale) TrPluralStringAllForms(trKey string) ([]string, []string) {
|
|
|
|
|
defaultLang, hasDefaultLang := l.store.localeMap[l.store.defaultLang]
|
|
|
|
|
|
|
|
|
|
var fallback []string
|
|
|
|
|
fallback = nil
|
|
|
|
|
|
|
|
|
|
result := make([]string, len(l.usedPluralForms))
|
|
|
|
|
allPresent := true
|
|
|
|
|
|
|
|
|
|
for i, form := range l.usedPluralForms {
|
[v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183
Followup to https://codeberg.org/forgejo/forgejo/pulls/6203
Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.
Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.
However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.
The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.
I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
|
|
|
result[i] = l.LookupPluralByForm(trKey, form, false)
|
2025-05-03 14:11:01 +00:00
|
|
|
if result[i] == "" {
|
|
|
|
|
allPresent = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !allPresent {
|
|
|
|
|
if hasDefaultLang {
|
|
|
|
|
fallback = make([]string, len(defaultLang.usedPluralForms))
|
|
|
|
|
for i, form := range defaultLang.usedPluralForms {
|
[v15.0/forgejo] fix(i18n): don't log harmless missing translations as errors (#12185)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/12183
Followup to https://codeberg.org/forgejo/forgejo/pulls/6203
Currently it is logging an error wherever a template is rendered in language that doesn't have all plural strings covered. For example, Esperanto isn't well maintained.
Since more plural strings were migrated in v15 to new format, these errors became much more common. However, for all languages but the base one (English) they are completely harmless and just indicate an incomplete translation.
However, for base (English) they indicate a bug in either template or en-US.json, which should be still logged as an error.
The error is being logged by `LookupPluralByForm`, which is called by `TrPluralStringAllForms` and (`TrPluralString` through `LookupPluralByCount`). I originally intended to just pass log func directly to `LookupPluralByForm` from both, but since `TrPluralString` isn't calling `LookupPluralByForm` directly, it didn't look clean, so I went with passing a flag around instead and implemented logging logic in `LookupPluralByForm` itself.
I little concern is with that the so-called "default lang" is configurable, and if it is configured to something with less than 100% completion, it will cause fallback bugs, as well as a lot of logging of this as an error. But this is why changing "default lang" is a bad idea in the first place, and broken fallbacks should be greater concern than junk in the logs.
Co-authored-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12185
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
2026-04-19 01:46:40 +02:00
|
|
|
fallback[i] = defaultLang.LookupPluralByForm(trKey, form, true)
|
2025-05-03 14:11:01 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
log.Error("Plural set for '%s' is incomplete and no fallback language is set.", trKey)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result, fallback
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-15 05:48:45 +08:00
|
|
|
// HasKey returns whether a key is present in this locale or not
|
|
|
|
|
func (l *locale) HasKey(trKey string) bool {
|
2025-03-08 14:52:32 +00:00
|
|
|
_, ok := l.newStyleMessages[trKey]
|
|
|
|
|
if ok {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2022-08-28 10:43:25 +01:00
|
|
|
idx, ok := l.store.trKeyToIdxMap[trKey]
|
|
|
|
|
if !ok {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
_, ok = l.idxToMsgMap[idx]
|
|
|
|
|
return ok
|
|
|
|
|
}
|