mirror of
https://github.com/miniflux/v2.git
synced 2025-06-27 16:36:00 +00:00
Use embed package for translations instead of generated files
Replace "go generate" with the new embed package.
This commit is contained in:
parent
a352aff93b
commit
5d65a85bdb
8 changed files with 103 additions and 3996 deletions
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"miniflux.app/config"
|
"miniflux.app/config"
|
||||||
"miniflux.app/database"
|
"miniflux.app/database"
|
||||||
|
"miniflux.app/locale"
|
||||||
"miniflux.app/logger"
|
"miniflux.app/logger"
|
||||||
"miniflux.app/storage"
|
"miniflux.app/storage"
|
||||||
"miniflux.app/version"
|
"miniflux.app/version"
|
||||||
|
@ -100,6 +101,11 @@ func Parse() {
|
||||||
logger.Info("The default value for DATABASE_URL is used")
|
logger.Info("The default value for DATABASE_URL is used")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Debug("Loading translations...")
|
||||||
|
if err := locale.LoadCatalogMessages(); err != nil {
|
||||||
|
logger.Fatal("Unable to load translations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
db, err := database.NewConnectionPool(
|
db, err := database.NewConnectionPool(
|
||||||
config.Opts.DatabaseURL(),
|
config.Opts.DatabaseURL(),
|
||||||
config.Opts.DatabaseMinConns(),
|
config.Opts.DatabaseMinConns(),
|
||||||
|
|
|
@ -216,5 +216,4 @@ func main() {
|
||||||
|
|
||||||
generateBundle("template/views.go", "template", "templateViewsMap", glob("template/html/*.html"))
|
generateBundle("template/views.go", "template", "templateViewsMap", glob("template/html/*.html"))
|
||||||
generateBundle("template/common.go", "template", "templateCommonMap", glob("template/html/common/*.html"))
|
generateBundle("template/common.go", "template", "templateCommonMap", glob("template/html/common/*.html"))
|
||||||
generateBundle("locale/translations.go", "locale", "translations", glob("locale/translations/*.json"))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package locale // import "miniflux.app/locale"
|
package locale // import "miniflux.app/locale"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
@ -14,23 +15,41 @@ type catalog map[string]translationDict
|
||||||
|
|
||||||
var defaultCatalog catalog
|
var defaultCatalog catalog
|
||||||
|
|
||||||
func init() {
|
//go:embed translations/*.json
|
||||||
|
var translationFiles embed.FS
|
||||||
|
|
||||||
|
// LoadCatalogMessages loads and parses all translations encoded in JSON.
|
||||||
|
func LoadCatalogMessages() error {
|
||||||
|
var err error
|
||||||
defaultCatalog = make(catalog)
|
defaultCatalog = make(catalog)
|
||||||
|
|
||||||
for language, data := range translations {
|
for language := range AvailableLanguages() {
|
||||||
messages, err := parseTranslationDict(data)
|
defaultCatalog[language], err = loadTranslationFile(language)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultCatalog[language] = messages
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTranslationDict(data string) (translationDict, error) {
|
func loadTranslationFile(language string) (translationDict, error) {
|
||||||
var translations translationDict
|
translationFileData, err := translationFiles.ReadFile(fmt.Sprintf("translations/%s.json", language))
|
||||||
if err := json.Unmarshal([]byte(data), &translations); err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid translation file: %v", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return translations, nil
|
|
||||||
|
translationMessages, err := parseTranslationMessages(translationFileData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return translationMessages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTranslationMessages(data []byte) (translationDict, error) {
|
||||||
|
var translationMessages translationDict
|
||||||
|
if err := json.Unmarshal(data, &translationMessages); err != nil {
|
||||||
|
return nil, fmt.Errorf(`invalid translation file: %w`, err)
|
||||||
|
}
|
||||||
|
return translationMessages, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,14 @@ package locale // import "miniflux.app/locale"
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestParserWithInvalidData(t *testing.T) {
|
func TestParserWithInvalidData(t *testing.T) {
|
||||||
_, err := parseTranslationDict(`{`)
|
_, err := parseTranslationMessages([]byte(`{`))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal(`An error should be returned when parsing invalid data`)
|
t.Fatal(`An error should be returned when parsing invalid data`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParser(t *testing.T) {
|
func TestParser(t *testing.T) {
|
||||||
translations, err := parseTranslationDict(`{"k": "v"}`)
|
translations, err := parseTranslationMessages([]byte(`{"k": "v"}`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(`Unexpected parsing error: %v`, err)
|
t.Fatalf(`Unexpected parsing error: %v`, err)
|
||||||
}
|
}
|
||||||
|
@ -32,3 +32,60 @@ func TestParser(t *testing.T) {
|
||||||
t.Fatal(`The translation key should contains the defined value`)
|
t.Fatal(`The translation key should contains the defined value`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadCatalog(t *testing.T) {
|
||||||
|
if err := LoadCatalogMessages(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllKeysHaveValue(t *testing.T) {
|
||||||
|
for language := range AvailableLanguages() {
|
||||||
|
messages, err := loadTranslationFile(language)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Unable to load translation messages for language %q`, language)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) == 0 {
|
||||||
|
t.Fatalf(`The language %q doesn't have any messages`, language)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range messages {
|
||||||
|
switch value := v.(type) {
|
||||||
|
case string:
|
||||||
|
if value == "" {
|
||||||
|
t.Errorf(`The key %q for the language %q have an empty string as value`, k, language)
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
if len(value) == 0 {
|
||||||
|
t.Errorf(`The key %q for the language %q have an empty list as value`, k, language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMissingTranslations(t *testing.T) {
|
||||||
|
refLang := "en_US"
|
||||||
|
references, err := loadTranslationFile(refLang)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(`Unable to parse reference language`)
|
||||||
|
}
|
||||||
|
|
||||||
|
for language := range AvailableLanguages() {
|
||||||
|
if language == refLang {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
messages, err := loadTranslationFile(language)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Parsing error for language %q`, language)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key := range references {
|
||||||
|
if _, found := messages[key]; !found {
|
||||||
|
t.Fatalf(`Translation key %q not found in language %q`, key, language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,12 +8,12 @@ import "testing"
|
||||||
|
|
||||||
func TestPluralRules(t *testing.T) {
|
func TestPluralRules(t *testing.T) {
|
||||||
scenarios := map[string]map[int]int{
|
scenarios := map[string]map[int]int{
|
||||||
"default": map[int]int{
|
"default": {
|
||||||
1: 0,
|
1: 0,
|
||||||
2: 1,
|
2: 1,
|
||||||
5: 1,
|
5: 1,
|
||||||
},
|
},
|
||||||
"ar_AR": map[int]int{
|
"ar_AR": {
|
||||||
0: 0,
|
0: 0,
|
||||||
1: 1,
|
1: 1,
|
||||||
2: 2,
|
2: 2,
|
||||||
|
@ -21,32 +21,32 @@ func TestPluralRules(t *testing.T) {
|
||||||
11: 4,
|
11: 4,
|
||||||
200: 5,
|
200: 5,
|
||||||
},
|
},
|
||||||
"cs_CZ": map[int]int{
|
"cs_CZ": {
|
||||||
1: 0,
|
1: 0,
|
||||||
2: 1,
|
2: 1,
|
||||||
5: 2,
|
5: 2,
|
||||||
},
|
},
|
||||||
"pl_PL": map[int]int{
|
"pl_PL": {
|
||||||
1: 0,
|
1: 0,
|
||||||
2: 1,
|
2: 1,
|
||||||
5: 2,
|
5: 2,
|
||||||
},
|
},
|
||||||
"pt_BR": map[int]int{
|
"pt_BR": {
|
||||||
1: 0,
|
1: 0,
|
||||||
2: 1,
|
2: 1,
|
||||||
5: 1,
|
5: 1,
|
||||||
},
|
},
|
||||||
"ru_RU": map[int]int{
|
"ru_RU": {
|
||||||
1: 0,
|
1: 0,
|
||||||
2: 1,
|
2: 1,
|
||||||
5: 2,
|
5: 2,
|
||||||
},
|
},
|
||||||
"sr_RS": map[int]int{
|
"sr_RS": {
|
||||||
1: 0,
|
1: 0,
|
||||||
2: 1,
|
2: 1,
|
||||||
5: 2,
|
5: 2,
|
||||||
},
|
},
|
||||||
"zh_CN": map[int]int{
|
"zh_CN": {
|
||||||
1: 0,
|
1: 0,
|
||||||
5: 0,
|
5: 0,
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,66 +0,0 @@
|
||||||
// Copyright 2018 Frédéric Guillot. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package locale // import "miniflux.app/locale"
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestAllLanguagesHaveCatalog(t *testing.T) {
|
|
||||||
for language := range AvailableLanguages() {
|
|
||||||
if _, found := translations[language]; !found {
|
|
||||||
t.Fatalf(`This language do not have a catalog: %q`, language)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAllKeysHaveValue(t *testing.T) {
|
|
||||||
for language := range AvailableLanguages() {
|
|
||||||
messages, err := parseTranslationDict(translations[language])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf(`Parsing error for language %q`, language)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(messages) == 0 {
|
|
||||||
t.Fatalf(`The language %q doesn't have any messages`, language)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range messages {
|
|
||||||
switch value := v.(type) {
|
|
||||||
case string:
|
|
||||||
if value == "" {
|
|
||||||
t.Errorf(`The key %q for the language %q have an empty string as value`, k, language)
|
|
||||||
}
|
|
||||||
case []string:
|
|
||||||
if len(value) == 0 {
|
|
||||||
t.Errorf(`The key %q for the language %q have an empty list as value`, k, language)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMissingTranslations(t *testing.T) {
|
|
||||||
refLang := "en_US"
|
|
||||||
references, err := parseTranslationDict(translations[refLang])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(`Unable to parse reference language`)
|
|
||||||
}
|
|
||||||
|
|
||||||
for language := range AvailableLanguages() {
|
|
||||||
if language == refLang {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
messages, err := parseTranslationDict(translations[language])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf(`Parsing error for language %q`, language)
|
|
||||||
}
|
|
||||||
|
|
||||||
for key := range references {
|
|
||||||
if _, found := messages[key]; !found {
|
|
||||||
t.Fatalf(`Translation key %q not found in language %q`, key, language)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
1
main.go
1
main.go
|
@ -10,7 +10,6 @@ package main // import "miniflux.app"
|
||||||
//go:generate gofmt -s -w ui/static/js.go
|
//go:generate gofmt -s -w ui/static/js.go
|
||||||
//go:generate gofmt -s -w template/views.go
|
//go:generate gofmt -s -w template/views.go
|
||||||
//go:generate gofmt -s -w template/common.go
|
//go:generate gofmt -s -w template/common.go
|
||||||
//go:generate gofmt -s -w locale/translations.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"miniflux.app/cli"
|
"miniflux.app/cli"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue