1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-08-06 17:41:00 +00:00

refactor(locale): introspect the translation files at load time

Since Go doesn't support unions, and because the translation format is a bit
wacky with the same field having multiple types, we must resort to
introspection to switch between single-item translation (for singular), and
multi-items ones (for plurals).

Previously, introspection was done at runtime, which is not only slow, but will
also only catch typing errors while trying to use the translations. The current
approach is to use a struct with a different field per possible type, and
implement a custom unmarshaller to dispatch the translations to the right one.
This should marginally reduce the memory consumption since interface-boxing
doesn't happen anymore, speed up the translations matching, and enforce proper
typing earlier. This also allows us to remove a bunch of now-useless tests.
This commit is contained in:
Julien Voisin 2025-08-01 04:10:14 +02:00 committed by GitHub
parent f3052eb8ed
commit 181e1341e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 180 additions and 205 deletions

View file

@ -9,7 +9,10 @@ import (
"fmt" "fmt"
) )
type translationDict map[string]any type translationDict struct {
singulars map[string]string
plurals map[string][]string
}
type catalog map[string]translationDict type catalog map[string]translationDict
var defaultCatalog = make(catalog, len(AvailableLanguages)) var defaultCatalog = make(catalog, len(AvailableLanguages))
@ -21,7 +24,7 @@ func getTranslationDict(language string) (translationDict, error) {
if _, ok := defaultCatalog[language]; !ok { if _, ok := defaultCatalog[language]; !ok {
var err error var err error
if defaultCatalog[language], err = loadTranslationFile(language); err != nil { if defaultCatalog[language], err = loadTranslationFile(language); err != nil {
return nil, err return translationDict{}, err
} }
} }
return defaultCatalog[language], nil return defaultCatalog[language], nil
@ -30,21 +33,55 @@ func getTranslationDict(language string) (translationDict, error) {
func loadTranslationFile(language string) (translationDict, error) { func loadTranslationFile(language string) (translationDict, error) {
translationFileData, err := translationFiles.ReadFile("translations/" + language + ".json") translationFileData, err := translationFiles.ReadFile("translations/" + language + ".json")
if err != nil { if err != nil {
return nil, err return translationDict{}, err
} }
translationMessages, err := parseTranslationMessages(translationFileData) translationMessages, err := parseTranslationMessages(translationFileData)
if err != nil { if err != nil {
return nil, err return translationDict{}, err
} }
return translationMessages, nil return translationMessages, nil
} }
func (t *translationDict) UnmarshalJSON(data []byte) error {
var tmpMap map[string]any
err := json.Unmarshal(data, &tmpMap)
if err != nil {
return err
}
m := translationDict{
singulars: make(map[string]string),
plurals: make(map[string][]string),
}
for key, value := range tmpMap {
switch vtype := value.(type) {
case string:
m.singulars[key] = vtype
case []any:
for _, translation := range vtype {
if translationStr, ok := translation.(string); ok {
m.plurals[key] = append(m.plurals[key], translationStr)
} else {
return fmt.Errorf("invalid type for translation in an array: %v", translation)
}
}
default:
return fmt.Errorf("invalid type (%T) for translation: %v", vtype, value)
}
}
*t = m
return nil
}
func parseTranslationMessages(data []byte) (translationDict, error) { func parseTranslationMessages(data []byte) (translationDict, error) {
var translationMessages translationDict var translationMessages translationDict
if err := json.Unmarshal(data, &translationMessages); err != nil { if err := json.Unmarshal(data, &translationMessages); err != nil {
return nil, fmt.Errorf(`invalid translation file: %w`, err) return translationDict{}, fmt.Errorf(`invalid translation file: %w`, err)
} }
return translationMessages, nil return translationMessages, nil
} }

View file

@ -3,7 +3,9 @@
package locale // import "miniflux.app/v2/internal/locale" package locale // import "miniflux.app/v2/internal/locale"
import "testing" import (
"testing"
)
func TestParserWithInvalidData(t *testing.T) { func TestParserWithInvalidData(t *testing.T) {
_, err := parseTranslationMessages([]byte(`{`)) _, err := parseTranslationMessages([]byte(`{`))
@ -18,16 +20,12 @@ func TestParser(t *testing.T) {
t.Fatalf(`Unexpected parsing error: %v`, err) t.Fatalf(`Unexpected parsing error: %v`, err)
} }
if translations == nil { value, found := translations.singulars["k"]
t.Fatal(`Translations should not be nil`)
}
value, found := translations["k"]
if !found { if !found {
t.Fatal(`The translation should contains the defined key`) t.Fatalf(`The translation %v should contains the defined key`, translations.singulars)
} }
if value.(string) != "v" { if value != "v" {
t.Fatal(`The translation key should contains the defined value`) t.Fatal(`The translation key should contains the defined value`)
} }
} }
@ -48,20 +46,22 @@ func TestAllKeysHaveValue(t *testing.T) {
t.Fatalf(`Unable to load translation messages for language %q`, language) t.Fatalf(`Unable to load translation messages for language %q`, language)
} }
if len(messages) == 0 { if len(messages.singulars) == 0 {
t.Fatalf(`The language %q doesn't have any messages`, language) t.Fatalf(`The language %q doesn't have any messages for singulars`, language)
} }
for k, v := range messages { if len(messages.plurals) == 0 {
switch value := v.(type) { t.Fatalf(`The language %q doesn't have any messages for plurals`, language)
case string: }
if value == "" {
t.Errorf(`The key %q for the language %q has an empty string as value`, k, language) for k, v := range messages.singulars {
} if len(v) == 0 {
case []any: t.Errorf(`The key %q for singulars for the language %q has an empty list as value`, k, language)
if len(value) == 0 { }
t.Errorf(`The key %q for the language %q has an empty list as value`, k, language) }
} for k, v := range messages.plurals {
if len(v) == 0 {
t.Errorf(`The key %q for plurals for the language %q has an empty list as value`, k, language)
} }
} }
} }
@ -84,9 +84,14 @@ func TestMissingTranslations(t *testing.T) {
t.Fatalf(`Parsing error for language %q`, language) t.Fatalf(`Parsing error for language %q`, language)
} }
for key := range references { for key := range references.singulars {
if _, found := messages[key]; !found { if _, found := messages.singulars[key]; !found {
t.Errorf(`Translation key %q not found in language %q`, key, language) t.Errorf(`Translation key %q not found in language %q singulars`, key, language)
}
}
for key := range references.plurals {
if _, found := messages.plurals[key]; !found {
t.Errorf(`Translation key %q not found in language %q plurals`, key, language)
} }
} }
} }
@ -121,11 +126,9 @@ func TestTranslationFilePluralForms(t *testing.T) {
t.Fatalf(`Unable to load translation messages for language %q`, language) t.Fatalf(`Unable to load translation messages for language %q`, language)
} }
for k, v := range messages { for k, v := range messages.plurals {
if value, ok := v.([]any); ok { if len(v) != numberOfPluralFormsPerLanguage[language] {
if len(value) != numberOfPluralFormsPerLanguage[language] { t.Errorf(`The key %q for the language %q does not have the expected number of plurals, got %d instead of %d`, k, language, len(v), numberOfPluralFormsPerLanguage[language])
t.Errorf(`The key %q for the language %q does not have the expected number of plurals, got %d instead of %d`, k, language, len(value), numberOfPluralFormsPerLanguage[language])
}
} }
} }
} }

View file

@ -46,10 +46,14 @@ func TestLocalizedErrorWrapper_Translate(t *testing.T) {
// Set up test catalog // Set up test catalog
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"error.test_key": "Error: %s (code: %d)", singulars: map[string]string{
"error.test_key": "Error: %s (code: %d)",
},
}, },
"fr_FR": translationDict{ "fr_FR": translationDict{
"error.test_key": "Erreur : %s (code : %d)", singulars: map[string]string{
"error.test_key": "Erreur : %s (code : %d)",
},
}, },
} }
@ -92,7 +96,9 @@ func TestLocalizedErrorWrapper_TranslateWithEmptyKey(t *testing.T) {
func TestLocalizedErrorWrapper_TranslateWithNoArgs(t *testing.T) { func TestLocalizedErrorWrapper_TranslateWithNoArgs(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"error.simple": "Simple error message", singulars: map[string]string{
"error.simple": "Simple error message",
},
}, },
} }
@ -128,7 +134,9 @@ func TestNewLocalizedError(t *testing.T) {
func TestLocalizedError_String(t *testing.T) { func TestLocalizedError_String(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"error.validation": "Validation failed for %s: %s", singulars: map[string]string{
"error.validation": "Validation failed for %s: %s",
},
}, },
} }
@ -158,7 +166,9 @@ func TestLocalizedError_StringWithMissingTranslation(t *testing.T) {
func TestLocalizedError_Error(t *testing.T) { func TestLocalizedError_Error(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"error.database": "Database connection failed: %s", singulars: map[string]string{
"error.database": "Database connection failed: %s",
},
}, },
} }
@ -178,10 +188,14 @@ func TestLocalizedError_Error(t *testing.T) {
func TestLocalizedError_Translate(t *testing.T) { func TestLocalizedError_Translate(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"error.permission": "Permission denied for %s", singulars: map[string]string{
"error.permission": "Permission denied for %s",
},
}, },
"es_ES": translationDict{ "es_ES": translationDict{
"error.permission": "Permiso denegado para %s", singulars: map[string]string{
"error.permission": "Permiso denegado para %s",
},
}, },
} }
@ -212,10 +226,14 @@ func TestLocalizedError_Translate(t *testing.T) {
func TestLocalizedError_TranslateWithNoArgs(t *testing.T) { func TestLocalizedError_TranslateWithNoArgs(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"error.generic": "An error occurred", singulars: map[string]string{
"error.generic": "An error occurred",
},
}, },
"de_DE": translationDict{ "de_DE": translationDict{
"error.generic": "Ein Fehler ist aufgetreten", singulars: map[string]string{
"error.generic": "Ein Fehler ist aufgetreten",
},
}, },
} }
@ -239,7 +257,9 @@ func TestLocalizedError_TranslateWithNoArgs(t *testing.T) {
func TestLocalizedError_TranslateWithComplexArgs(t *testing.T) { func TestLocalizedError_TranslateWithComplexArgs(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"error.complex": "Error %d: %s occurred at %s with severity %s", singulars: map[string]string{
"error.complex": "Error %d: %s occurred at %s with severity %s",
},
}, },
} }

View file

@ -17,10 +17,8 @@ func NewPrinter(language string) *Printer {
func (p *Printer) Print(key string) string { func (p *Printer) Print(key string) string {
if dict, err := getTranslationDict(p.language); err == nil { if dict, err := getTranslationDict(p.language); err == nil {
if str, ok := dict[key]; ok { if str, ok := dict.singulars[key]; ok {
if translation, ok := str.(string); ok { return str
return translation
}
} }
} }
return key return key
@ -31,10 +29,8 @@ func (p *Printer) Printf(key string, args ...any) string {
translation := key translation := key
if dict, err := getTranslationDict(p.language); err == nil { if dict, err := getTranslationDict(p.language); err == nil {
if str, ok := dict[key]; ok { if str, ok := dict.singulars[key]; ok {
if translation, ok = str.(string); !ok { translation = str
translation = key
}
} }
} }
@ -42,29 +38,16 @@ func (p *Printer) Printf(key string, args ...any) string {
} }
// Plural returns the translation of the given key by using the language plural form. // 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 { func (p *Printer) Plural(key string, n int, args ...any) string {
dict, err := getTranslationDict(p.language) dict, err := getTranslationDict(p.language)
if err != nil { if err != nil {
return key return key
} }
if choices, found := dict[key]; found { if choices, found := dict.plurals[key]; found {
var plurals []string
switch v := choices.(type) {
case []string:
plurals = v
case []any:
for _, v := range v {
plurals = append(plurals, fmt.Sprint(v))
}
default:
return key
}
index := getPluralForm(p.language, n) index := getPluralForm(p.language, n)
if len(plurals) > index { if len(choices) > index {
return fmt.Sprintf(plurals[index], args...) return fmt.Sprintf(choices[index], args...)
} }
} }

View file

@ -17,7 +17,9 @@ func TestPrintfWithMissingLanguage(t *testing.T) {
func TestPrintfWithMissingKey(t *testing.T) { func TestPrintfWithMissingKey(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"k": "v", singulars: map[string]string{
"k": "v",
},
}, },
} }
@ -30,7 +32,9 @@ func TestPrintfWithMissingKey(t *testing.T) {
func TestPrintfWithExistingKey(t *testing.T) { func TestPrintfWithExistingKey(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"auth.username": "Login", singulars: map[string]string{
"auth.username": "Login",
},
}, },
} }
@ -43,10 +47,14 @@ func TestPrintfWithExistingKey(t *testing.T) {
func TestPrintfWithExistingKeyAndPlaceholder(t *testing.T) { func TestPrintfWithExistingKeyAndPlaceholder(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"key": "Test: %s", singulars: map[string]string{
"key": "Test: %s",
},
}, },
"fr_FR": translationDict{ "fr_FR": translationDict{
"key": "Test : %s", singulars: map[string]string{
"key": "Test : %s",
},
}, },
} }
@ -59,10 +67,14 @@ func TestPrintfWithExistingKeyAndPlaceholder(t *testing.T) {
func TestPrintfWithMissingKeyAndPlaceholder(t *testing.T) { func TestPrintfWithMissingKeyAndPlaceholder(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"auth.username": "Login", singulars: map[string]string{
"auth.username": "Login",
},
}, },
"fr_FR": translationDict{ "fr_FR": translationDict{
"auth.username": "Identifiant", singulars: map[string]string{
"auth.username": "Identifiant",
},
}, },
} }
@ -72,22 +84,6 @@ func TestPrintfWithMissingKeyAndPlaceholder(t *testing.T) {
} }
} }
func TestPrintfWithInvalidValue(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 TestPrintWithMissingLanguage(t *testing.T) { func TestPrintWithMissingLanguage(t *testing.T) {
defaultCatalog = catalog{} defaultCatalog = catalog{}
translation := NewPrinter("invalid").Print("missing.key") translation := NewPrinter("invalid").Print("missing.key")
@ -100,7 +96,9 @@ func TestPrintWithMissingLanguage(t *testing.T) {
func TestPrintWithMissingKey(t *testing.T) { func TestPrintWithMissingKey(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"existing.key": "value", singulars: map[string]string{
"existing.key": "value",
},
}, },
} }
@ -113,7 +111,9 @@ func TestPrintWithMissingKey(t *testing.T) {
func TestPrintWithExistingKey(t *testing.T) { func TestPrintWithExistingKey(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"auth.username": "Login", singulars: map[string]string{
"auth.username": "Login",
},
}, },
} }
@ -126,13 +126,19 @@ func TestPrintWithExistingKey(t *testing.T) {
func TestPrintWithDifferentLanguages(t *testing.T) { func TestPrintWithDifferentLanguages(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"greeting": "Hello", singulars: map[string]string{
"greeting": "Hello",
},
}, },
"fr_FR": translationDict{ "fr_FR": translationDict{
"greeting": "Bonjour", singulars: map[string]string{
"greeting": "Bonjour",
},
}, },
"es_ES": translationDict{ "es_ES": translationDict{
"greeting": "Hola", singulars: map[string]string{
"greeting": "Hola",
},
}, },
} }
@ -153,46 +159,12 @@ func TestPrintWithDifferentLanguages(t *testing.T) {
} }
} }
func TestPrintWithInvalidTranslationType(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
"valid.key": "valid string",
"invalid.key": 12345, // not a string
},
}
printer := NewPrinter("en_US")
// Valid string should work
translation := printer.Print("valid.key")
if translation != "valid string" {
t.Errorf(`Wrong translation for valid key, got %q`, translation)
}
// Invalid type should return the key itself
translation = printer.Print("invalid.key")
if translation != "invalid.key" {
t.Errorf(`Wrong translation for invalid key, got %q`, translation)
}
}
func TestPrintWithNilTranslation(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
"nil.key": nil,
},
}
translation := NewPrinter("en_US").Print("nil.key")
if translation != "nil.key" {
t.Errorf(`Wrong translation for nil value, got %q`, translation)
}
}
func TestPrintWithEmptyKey(t *testing.T) { func TestPrintWithEmptyKey(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"": "empty key translation", singulars: map[string]string{
"": "empty key translation",
},
}, },
} }
@ -205,7 +177,9 @@ func TestPrintWithEmptyKey(t *testing.T) {
func TestPrintWithEmptyTranslation(t *testing.T) { func TestPrintWithEmptyTranslation(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"empty.value": "", singulars: map[string]string{
"empty.value": "",
},
}, },
} }
@ -218,10 +192,14 @@ func TestPrintWithEmptyTranslation(t *testing.T) {
func TestPluralWithDefaultRule(t *testing.T) { func TestPluralWithDefaultRule(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"number_of_users": []string{"%d user (%s)", "%d users (%s)"}, plurals: map[string][]string{
"number_of_users": {"%d user (%s)", "%d users (%s)"},
},
}, },
"fr_FR": translationDict{ "fr_FR": translationDict{
"number_of_users": []string{"%d utilisateur (%s)", "%d utilisateurs (%s)"}, plurals: map[string][]string{
"number_of_users": {"%d utilisateur (%s)", "%d utilisateurs (%s)"},
},
}, },
} }
@ -242,10 +220,14 @@ func TestPluralWithDefaultRule(t *testing.T) {
func TestPluralWithRussianRule(t *testing.T) { func TestPluralWithRussianRule(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"time_elapsed.years": []string{"%d year", "%d years"}, plurals: map[string][]string{
"time_elapsed.years": {"%d year", "%d years"},
},
}, },
"ru_RU": translationDict{ "ru_RU": translationDict{
"time_elapsed.years": []string{"%d год назад", "%d года назад", "%d лет назад"}, plurals: map[string][]string{
"time_elapsed.years": {"%d год назад", "%d года назад", "%d лет назад"},
},
}, },
} }
@ -273,7 +255,9 @@ func TestPluralWithRussianRule(t *testing.T) {
func TestPluralWithMissingTranslation(t *testing.T) { func TestPluralWithMissingTranslation(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"en_US": translationDict{ "en_US": translationDict{
"number_of_users": []string{"%d user (%s)", "%d users (%s)"}, plurals: map[string][]string{
"number_of_users": {"%d user (%s)", "%d users (%s)"},
},
}, },
"fr_FR": translationDict{}, "fr_FR": translationDict{},
} }
@ -284,22 +268,6 @@ func TestPluralWithMissingTranslation(t *testing.T) {
} }
} }
func TestPluralWithInvalidValues(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)
}
}
func TestPluralWithMissingLanguage(t *testing.T) { func TestPluralWithMissingLanguage(t *testing.T) {
defaultCatalog = catalog{} defaultCatalog = catalog{}
translation := NewPrinter("invalid_language").Plural("test.key", 2) translation := NewPrinter("invalid_language").Plural("test.key", 2)
@ -309,63 +277,21 @@ func TestPluralWithMissingLanguage(t *testing.T) {
} }
} }
func TestPluralWithAnySliceType(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
"test.key": []any{"%d item", "%d items"},
},
}
printer := NewPrinter("en_US")
translation := printer.Plural("test.key", 1, 1)
expected := "1 item"
if translation != expected {
t.Errorf(`Wrong translation for singular, got %q instead of %q`, translation, expected)
}
translation = printer.Plural("test.key", 2, 2)
expected = "2 items"
if translation != expected {
t.Errorf(`Wrong translation for plural, got %q instead of %q`, translation, expected)
}
}
func TestPluralWithMixedAnySliceTypes(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
"mixed.key": []any{"single: %s", "multiple: %s", "many: %s"},
},
}
printer := NewPrinter("en_US")
// Test first element (should convert first any element to string)
translation := printer.Plural("mixed.key", 0, "test") // n=0 uses index 0
expected := "single: test"
if translation != expected {
t.Errorf(`Wrong translation for index 0, got %q instead of %q`, translation, expected)
}
// Test second element (should use plural form)
translation = printer.Plural("mixed.key", 2, "items") // plural form for default language
expected = "multiple: items"
if translation != expected {
t.Errorf(`Wrong translation for index 1, got %q instead of %q`, translation, expected)
}
}
func TestPluralWithIndexOutOfBounds(t *testing.T) { func TestPluralWithIndexOutOfBounds(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"test_lang": translationDict{ "test_lang": translationDict{
"limited.key": []string{"only one form"}, plurals: map[string][]string{
"limited.key": {"only one form"},
},
}, },
} }
// Force a scenario where getPluralForm might return an index >= len(plurals) // Force a scenario where getPluralForm might return an index >= len(plurals)
// We'll create a scenario with Czech language rules // We'll create a scenario with Czech language rules
defaultCatalog["cs_CZ"] = translationDict{ defaultCatalog["cs_CZ"] = translationDict{
"limited.key": []string{"one form only"}, // Only one form, but Czech has 3 plural forms plurals: map[string][]string{
"limited.key": {"one form only"}, // Only one form, but Czech has 3 plural forms
},
} }
printer := NewPrinter("cs_CZ") printer := NewPrinter("cs_CZ")
@ -380,13 +306,19 @@ func TestPluralWithIndexOutOfBounds(t *testing.T) {
func TestPluralWithVariousLanguageRules(t *testing.T) { func TestPluralWithVariousLanguageRules(t *testing.T) {
defaultCatalog = catalog{ defaultCatalog = catalog{
"ar_AR": translationDict{ "ar_AR": translationDict{
"items": []string{"no items", "one item", "two items", "few items", "many items", "other items"}, plurals: map[string][]string{
"items": {"no items", "one item", "two items", "few items", "many items", "other items"},
},
}, },
"pl_PL": translationDict{ "pl_PL": translationDict{
"files": []string{"one file", "few files", "many files"}, plurals: map[string][]string{
"files": {"one file", "few files", "many files"},
},
}, },
"ja_JP": translationDict{ "ja_JP": translationDict{
"photos": []string{"photos"}, plurals: map[string][]string{
"photos": {"photos"},
},
}, },
} }