From b1e8f534eff7569dc2e8dab4dee851d1b709f71b Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Sat, 22 Sep 2018 15:04:55 -0700 Subject: Simplify locale package usage (refactoring) --- locale/catalog.go | 36 +++++++++ locale/catalog_test.go | 34 +++++++++ locale/language.go | 50 ------------- locale/locale.go | 16 +--- locale/locale_test.go | 105 ++++---------------------- locale/parser.go | 21 ------ locale/parser_test.go | 34 --------- locale/plural.go | 103 ++++++++++++++++++++++++++ locale/plural_test.go | 63 ++++++++++++++++ locale/plurals.go | 101 ------------------------- locale/printer.go | 67 +++++++++++++++++ locale/printer_test.go | 174 ++++++++++++++++++++++++++++++++++++++++++++ locale/translations_test.go | 12 +-- locale/translator.go | 31 -------- 14 files changed, 497 insertions(+), 350 deletions(-) create mode 100644 locale/catalog.go create mode 100644 locale/catalog_test.go delete mode 100644 locale/language.go delete mode 100644 locale/parser.go delete mode 100644 locale/parser_test.go create mode 100644 locale/plural.go create mode 100644 locale/plural_test.go delete mode 100644 locale/plurals.go create mode 100644 locale/printer.go create mode 100644 locale/printer_test.go delete mode 100644 locale/translator.go (limited to 'locale') diff --git a/locale/catalog.go b/locale/catalog.go new file mode 100644 index 0000000..926298c --- /dev/null +++ b/locale/catalog.go @@ -0,0 +1,36 @@ +// 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 ( + "encoding/json" + "fmt" +) + +type translationDict map[string]interface{} +type catalog map[string]translationDict + +var defaultCatalog catalog + +func init() { + defaultCatalog = make(catalog) + + for language, data := range translations { + messages, err := parseTranslationDict(data) + if err != nil { + panic(err) + } + + defaultCatalog[language] = messages + } +} + +func parseTranslationDict(data string) (translationDict, error) { + var translations translationDict + if err := json.Unmarshal([]byte(data), &translations); err != nil { + return nil, fmt.Errorf("invalid translation file: %v", err) + } + return translations, nil +} diff --git a/locale/catalog_test.go b/locale/catalog_test.go new file mode 100644 index 0000000..0be9995 --- /dev/null +++ b/locale/catalog_test.go @@ -0,0 +1,34 @@ +// 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 TestParserWithInvalidData(t *testing.T) { + _, err := parseTranslationDict(`{`) + if err == nil { + t.Fatal(`An error should be returned when parsing invalid data`) + } +} + +func TestParser(t *testing.T) { + translations, err := parseTranslationDict(`{"k": "v"}`) + if err != nil { + t.Fatalf(`Unexpected parsing error: %v`, err) + } + + if translations == nil { + t.Fatal(`Translations should not be nil`) + } + + value, found := translations["k"] + if !found { + t.Fatal(`The translation should contains the defined key`) + } + + if value.(string) != "v" { + t.Fatal(`The translation key should contains the defined value`) + } +} diff --git a/locale/language.go b/locale/language.go deleted file mode 100644 index 5738163..0000000 --- a/locale/language.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2017 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 "fmt" - -// Language represents a language in the system. -type Language struct { - language string - translations catalogMessages -} - -// Get fetch the translation for the given key. -func (l *Language) Get(key string, args ...interface{}) string { - var translation string - - str, found := l.translations[key] - if !found { - translation = key - } else { - translation = str.(string) - } - - return fmt.Sprintf(translation, args...) -} - -// Plural returns the translation of the given key by using the language plural form. -func (l *Language) Plural(key string, n int, args ...interface{}) string { - translation := key - slices, found := l.translations[key] - - if found { - pluralForm, found := pluralForms[l.language] - if !found { - pluralForm = pluralForms["default"] - } - - index := pluralForm(n) - translations := slices.([]interface{}) - translation = key - - if len(translations) > index { - translation = translations[index].(string) - } - } - - return fmt.Sprintf(translation, args...) -} diff --git a/locale/locale.go b/locale/locale.go index 59174b0..a59d4c4 100755 --- a/locale/locale.go +++ b/locale/locale.go @@ -1,23 +1,9 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. +// 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 "miniflux.app/logger" - -// Load loads all translations. -func Load() *Translator { - translator := NewTranslator() - - for language, tr := range translations { - logger.Debug("Loading translation: %s", language) - translator.AddLanguage(language, tr) - } - - return translator -} - // AvailableLanguages returns the list of available languages. func AvailableLanguages() map[string]string { return map[string]string{ diff --git a/locale/locale_test.go b/locale/locale_test.go index 28e2a80..9f49b60 100644 --- a/locale/locale_test.go +++ b/locale/locale_test.go @@ -1,103 +1,24 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. +// 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 TestTranslateWithMissingLanguage(t *testing.T) { - translator := NewTranslator() - translation := translator.GetLanguage("en_US").Get("auth.username") - - if translation != "auth.username" { - t.Errorf("Wrong translation, got %s", translation) - } -} - -func TestTranslateWithExistingKey(t *testing.T) { - data := `{"auth.username": "Username"}` - translator := NewTranslator() - translator.AddLanguage("en_US", data) - translation := translator.GetLanguage("en_US").Get("auth.username") +func TestAvailableLanguages(t *testing.T) { + results := AvailableLanguages() + for k, v := range results { + if k == "" { + t.Errorf(`Empty language key detected`) + } - if translation != "Username" { - t.Errorf("Wrong translation, got %s", translation) + if v == "" { + t.Errorf(`Empty language value detected`) + } } -} - -func TestTranslateWithMissingKey(t *testing.T) { - data := `{"auth.username": "Username"}` - translator := NewTranslator() - translator.AddLanguage("en_US", data) - translation := translator.GetLanguage("en_US").Get("auth.password") - - if translation != "auth.password" { - t.Errorf("Wrong translation, got %s", translation) - } -} - -func TestTranslateWithMissingKeyAndPlaceholder(t *testing.T) { - translator := NewTranslator() - translator.AddLanguage("fr_FR", "") - translation := translator.GetLanguage("fr_FR").Get("Status: %s", "ok") - - if translation != "Status: ok" { - t.Errorf("Wrong translation, got %s", translation) - } -} - -func TestTranslatePluralWithDefaultRule(t *testing.T) { - data := `{"number_of_users": ["Il y a %d utilisateur (%s)", "Il y a %d utilisateurs (%s)"]}` - translator := NewTranslator() - translator.AddLanguage("fr_FR", data) - language := translator.GetLanguage("fr_FR") - - translation := language.Plural("number_of_users", 1, 1, "some text") - expected := "Il y a 1 utilisateur (some text)" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } - - translation = language.Plural("number_of_users", 2, 2, "some text") - expected = "Il y a 2 utilisateurs (some text)" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } -} - -func TestTranslatePluralWithRussianRule(t *testing.T) { - data := `{"key": ["из %d книги за %d день", "из %d книг за %d дня", "из %d книг за %d дней"]}` - translator := NewTranslator() - translator.AddLanguage("ru_RU", data) - language := translator.GetLanguage("ru_RU") - - translation := language.Plural("key", 1, 1, 1) - expected := "из 1 книги за 1 день" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } - - translation = language.Plural("key", 2, 2, 2) - expected = "из 2 книг за 2 дня" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } - - translation = language.Plural("key", 5, 5, 5) - expected = "из 5 книг за 5 дней" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } -} - -func TestTranslatePluralWithMissingTranslation(t *testing.T) { - translator := NewTranslator() - translator.AddLanguage("fr_FR", "") - language := translator.GetLanguage("fr_FR") - translation := language.Plural("number_of_users", 2) - expected := "number_of_users" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) + if _, found := results["en_US"]; !found { + t.Errorf(`We must have at least the default language (en_US)`) } } diff --git a/locale/parser.go b/locale/parser.go deleted file mode 100644 index 03591c1..0000000 --- a/locale/parser.go +++ /dev/null @@ -1,21 +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 ( - "encoding/json" - "fmt" -) - -type catalogMessages map[string]interface{} -type catalog map[string]catalogMessages - -func parseCatalogMessages(data string) (catalogMessages, error) { - var translations catalogMessages - if err := json.Unmarshal([]byte(data), &translations); err != nil { - return nil, fmt.Errorf("invalid translation file: %v", err) - } - return translations, nil -} diff --git a/locale/parser_test.go b/locale/parser_test.go deleted file mode 100644 index 619d8db..0000000 --- a/locale/parser_test.go +++ /dev/null @@ -1,34 +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 TestParserWithInvalidData(t *testing.T) { - _, err := parseCatalogMessages(`{`) - if err == nil { - t.Fatal(`An error should be returned when parsing invalid data`) - } -} - -func TestParser(t *testing.T) { - translations, err := parseCatalogMessages(`{"k": "v"}`) - if err != nil { - t.Fatalf(`Unexpected parsing error: %v`, err) - } - - if translations == nil { - t.Fatal(`Translations should not be nil`) - } - - value, found := translations["k"] - if !found { - t.Fatal(`The translation should contains the defined key`) - } - - if value.(string) != "v" { - t.Fatal(`The translation key should contains the defined value`) - } -} diff --git a/locale/plural.go b/locale/plural.go new file mode 100644 index 0000000..3b14c38 --- /dev/null +++ b/locale/plural.go @@ -0,0 +1,103 @@ +// 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" + +type pluralFormFunc func(n int) int + +// See https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html +// And http://www.unicode.org/cldr/charts/29/supplemental/language_plural_rules.html +var pluralForms = map[string]pluralFormFunc{ + // nplurals=2; plural=(n != 1); + "default": func(n int) int { + if n != 1 { + return 1 + } + + return 0 + }, + // nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); + "ar_AR": func(n int) int { + if n == 0 { + return 0 + } + + if n == 1 { + return 1 + } + + if n == 2 { + return 2 + } + + if n%100 >= 3 && n%100 <= 10 { + return 3 + } + + if n%100 >= 11 { + return 4 + } + + return 5 + }, + // nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; + "cs_CZ": func(n int) int { + if n == 1 { + return 0 + } + + if n >= 2 && n <= 4 { + return 1 + } + + return 2 + }, + // nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + "pl_PL": func(n int) int { + if n == 1 { + return 0 + } + + if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { + return 1 + } + + return 2 + }, + // nplurals=2; plural=(n > 1); + "pt_BR": func(n int) int { + if n > 1 { + return 1 + } + return 0 + }, + // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + "ru_RU": func(n int) int { + if n%10 == 1 && n%100 != 11 { + return 0 + } + + if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { + return 1 + } + + return 2 + }, + // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + "sr_RS": func(n int) int { + if n%10 == 1 && n%100 != 11 { + return 0 + } + + if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { + return 1 + } + + return 2 + }, + // nplurals=1; plural=0; + "zh_CN": func(n int) int { + return 0 + }, +} diff --git a/locale/plural_test.go b/locale/plural_test.go new file mode 100644 index 0000000..e7694bd --- /dev/null +++ b/locale/plural_test.go @@ -0,0 +1,63 @@ +// 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 TestPluralRules(t *testing.T) { + scenarios := map[string]map[int]int{ + "default": map[int]int{ + 1: 0, + 2: 1, + 5: 1, + }, + "ar_AR": map[int]int{ + 0: 0, + 1: 1, + 2: 2, + 5: 3, + 11: 4, + 200: 5, + }, + "cs_CZ": map[int]int{ + 1: 0, + 2: 1, + 5: 2, + }, + "pl_PL": map[int]int{ + 1: 0, + 2: 1, + 5: 2, + }, + "pt_BR": map[int]int{ + 1: 0, + 2: 1, + 5: 1, + }, + "ru_RU": map[int]int{ + 1: 0, + 2: 1, + 5: 2, + }, + "sr_RS": map[int]int{ + 1: 0, + 2: 1, + 5: 2, + }, + "zh_CN": map[int]int{ + 1: 0, + 5: 0, + }, + } + + for rule, values := range scenarios { + for input, expected := range values { + result := pluralForms[rule](input) + if result != expected { + t.Errorf(`Unexpected result for %q rule, got %d instead of %d for %d as input`, rule, result, expected, input) + } + } + } +} diff --git a/locale/plurals.go b/locale/plurals.go deleted file mode 100644 index c4e47cf..0000000 --- a/locale/plurals.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017 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" - -// See https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html -// And http://www.unicode.org/cldr/charts/29/supplemental/language_plural_rules.html -var pluralForms = map[string]func(n int) int{ - // nplurals=2; plural=(n != 1); - "default": func(n int) int { - if n != 1 { - return 1 - } - - return 0 - }, - // nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); - "ar_AR": func(n int) int { - if n == 0 { - return 0 - } - - if n == 1 { - return 1 - } - - if n == 2 { - return 2 - } - - if n%100 >= 3 && n%100 <= 10 { - return 3 - } - - if n%100 >= 11 { - return 4 - } - - return 5 - }, - // nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; - "cs_CZ": func(n int) int { - if n == 1 { - return 0 - } - - if n >= 2 && n <= 4 { - return 1 - } - - return 2 - }, - // nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); - "pl_PL": func(n int) int { - if n == 1 { - return 0 - } - - if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { - return 1 - } - - return 2 - }, - // nplurals=2; plural=(n > 1); - "pt_BR": func(n int) int { - if n > 1 { - return 1 - } - return 0 - }, - // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); - "ru_RU": func(n int) int { - if n%10 == 1 && n%100 != 11 { - return 0 - } - - if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { - return 1 - } - - return 2 - }, - // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); - "sr_RS": func(n int) int { - if n%10 == 1 && n%100 != 11 { - return 0 - } - - if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { - return 1 - } - - return 2 - }, - // nplurals=1; plural=0; - "zh_CN": func(n int) int { - return 0 - }, -} diff --git a/locale/printer.go b/locale/printer.go new file mode 100644 index 0000000..ef04e05 --- /dev/null +++ b/locale/printer.go @@ -0,0 +1,67 @@ +// 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 "fmt" + +// Printer converts translation keys to language-specific strings. +type Printer struct { + language string +} + +// Printf is like fmt.Printf, but using language-specific formatting. +func (p *Printer) Printf(key string, args ...interface{}) string { + var translation string + + str, found := defaultCatalog[p.language][key] + if !found { + translation = key + } else { + var valid bool + translation, valid = str.(string) + if !valid { + translation = key + } + } + + return fmt.Sprintf(translation, args...) +} + +// Plural returns the translation of the given key by using the language plural form. +func (p *Printer) Plural(key string, n int, args ...interface{}) string { + choices, found := defaultCatalog[p.language][key] + + if found { + var plurals []string + + switch v := choices.(type) { + case []interface{}: + for _, v := range v { + plurals = append(plurals, fmt.Sprint(v)) + } + case []string: + plurals = v + default: + return key + } + + pluralForm, found := pluralForms[p.language] + if !found { + pluralForm = pluralForms["default"] + } + + index := pluralForm(n) + if len(plurals) > index { + return fmt.Sprintf(plurals[index], args...) + } + } + + return key +} + +// NewPrinter creates a new Printer. +func NewPrinter(language string) *Printer { + return &Printer{language} +} diff --git a/locale/printer_test.go b/locale/printer_test.go new file mode 100644 index 0000000..1d8f58d --- /dev/null +++ b/locale/printer_test.go @@ -0,0 +1,174 @@ +// 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 TestTranslateWithMissingLanguage(t *testing.T) { + defaultCatalog = catalog{} + translation := NewPrinter("invalid").Printf("missing.key") + + if translation != "missing.key" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithMissingKey(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "k": "v", + }, + } + + translation := NewPrinter("en_US").Printf("missing.key") + if translation != "missing.key" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithExistingKey(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "auth.username": "Login", + }, + } + + translation := NewPrinter("en_US").Printf("auth.username") + if translation != "Login" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithExistingKeyAndPlaceholder(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "key": "Test: %s", + }, + "fr_FR": translationDict{ + "key": "Test : %s", + }, + } + + translation := NewPrinter("fr_FR").Printf("key", "ok") + if translation != "Test : ok" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithMissingKeyAndPlaceholder(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "auth.username": "Login", + }, + "fr_FR": translationDict{ + "auth.username": "Identifiant", + }, + } + + translation := NewPrinter("fr_FR").Printf("Status: %s", "ok") + if translation != "Status: ok" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithInvalidValue(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "auth.username": "Login", + }, + "fr_FR": translationDict{ + "auth.username": true, + }, + } + + translation := NewPrinter("fr_FR").Printf("auth.username") + if translation != "auth.username" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslatePluralWithDefaultRule(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "number_of_users": []string{"%d user (%s)", "%d users (%s)"}, + }, + "fr_FR": translationDict{ + "number_of_users": []string{"%d utilisateur (%s)", "%d utilisateurs (%s)"}, + }, + } + + printer := NewPrinter("fr_FR") + translation := printer.Plural("number_of_users", 1, 1, "some text") + expected := "1 utilisateur (some text)" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } + + translation = printer.Plural("number_of_users", 2, 2, "some text") + expected = "2 utilisateurs (some text)" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } +} + +func TestTranslatePluralWithRussianRule(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "time_elapsed.years": []string{"%d year", "%d years"}, + }, + "ru_RU": translationDict{ + "time_elapsed.years": []string{"%d год назад", "%d года назад", "%d лет назад"}, + }, + } + + printer := NewPrinter("ru_RU") + + translation := printer.Plural("time_elapsed.years", 1, 1) + expected := "1 год назад" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } + + translation = printer.Plural("time_elapsed.years", 2, 2) + expected = "2 года назад" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } + + translation = printer.Plural("time_elapsed.years", 5, 5) + expected = "5 лет назад" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } +} + +func TestTranslatePluralWithMissingTranslation(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "number_of_users": []string{"%d user (%s)", "%d users (%s)"}, + }, + "fr_FR": translationDict{}, + } + translation := NewPrinter("fr_FR").Plural("number_of_users", 2) + expected := "number_of_users" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } +} + +func TestTranslatePluralWithInvalidValues(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "number_of_users": []string{"%d user (%s)", "%d users (%s)"}, + }, + "fr_FR": translationDict{ + "number_of_users": "must be a slice", + }, + } + translation := NewPrinter("fr_FR").Plural("number_of_users", 2) + expected := "number_of_users" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } +} diff --git a/locale/translations_test.go b/locale/translations_test.go index f196a28..55775ba 100644 --- a/locale/translations_test.go +++ b/locale/translations_test.go @@ -9,16 +9,16 @@ 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: %s`, language) + t.Fatalf(`This language do not have a catalog: %q`, language) } } } func TestAllKeysHaveValue(t *testing.T) { for language := range AvailableLanguages() { - messages, err := parseCatalogMessages(translations[language]) + messages, err := parseTranslationDict(translations[language]) if err != nil { - t.Fatalf(`Parsing error language %s`, language) + t.Fatalf(`Parsing error for language %q`, language) } if len(messages) == 0 { @@ -42,7 +42,7 @@ func TestAllKeysHaveValue(t *testing.T) { func TestMissingTranslations(t *testing.T) { refLang := "en_US" - references, err := parseCatalogMessages(translations[refLang]) + references, err := parseTranslationDict(translations[refLang]) if err != nil { t.Fatal(`Unable to parse reference language`) } @@ -52,9 +52,9 @@ func TestMissingTranslations(t *testing.T) { continue } - messages, err := parseCatalogMessages(translations[language]) + messages, err := parseTranslationDict(translations[language]) if err != nil { - t.Fatalf(`Parsing error language %s`, language) + t.Fatalf(`Parsing error for language %q`, language) } for key := range references { diff --git a/locale/translator.go b/locale/translator.go deleted file mode 100644 index 0c38c56..0000000 --- a/locale/translator.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2017 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" - -// Translator manage supported locales. -type Translator struct { - locales catalog -} - -// AddLanguage loads a new language into the system. -func (t *Translator) AddLanguage(language, data string) (err error) { - t.locales[language], err = parseCatalogMessages(data) - return err -} - -// GetLanguage returns the given language handler. -func (t *Translator) GetLanguage(language string) *Language { - translations, found := t.locales[language] - if !found { - return &Language{language: language} - } - - return &Language{language: language, translations: translations} -} - -// NewTranslator creates a new Translator. -func NewTranslator() *Translator { - return &Translator{locales: make(catalog)} -} -- cgit v1.2.3