mirror of
https://github.com/miniflux/v2.git
synced 2025-08-11 17:51:01 +00:00
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.
87 lines
2.2 KiB
Go
87 lines
2.2 KiB
Go
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package locale // import "miniflux.app/v2/internal/locale"
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
type translationDict struct {
|
|
singulars map[string]string
|
|
plurals map[string][]string
|
|
}
|
|
type catalog map[string]translationDict
|
|
|
|
var defaultCatalog = make(catalog, len(AvailableLanguages))
|
|
|
|
//go:embed translations/*.json
|
|
var translationFiles embed.FS
|
|
|
|
func getTranslationDict(language string) (translationDict, error) {
|
|
if _, ok := defaultCatalog[language]; !ok {
|
|
var err error
|
|
if defaultCatalog[language], err = loadTranslationFile(language); err != nil {
|
|
return translationDict{}, err
|
|
}
|
|
}
|
|
return defaultCatalog[language], nil
|
|
}
|
|
|
|
func loadTranslationFile(language string) (translationDict, error) {
|
|
translationFileData, err := translationFiles.ReadFile("translations/" + language + ".json")
|
|
if err != nil {
|
|
return translationDict{}, err
|
|
}
|
|
|
|
translationMessages, err := parseTranslationMessages(translationFileData)
|
|
if err != nil {
|
|
return translationDict{}, err
|
|
}
|
|
|
|
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) {
|
|
var translationMessages translationDict
|
|
if err := json.Unmarshal(data, &translationMessages); err != nil {
|
|
return translationDict{}, fmt.Errorf(`invalid translation file: %w`, err)
|
|
}
|
|
return translationMessages, nil
|
|
}
|