1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-08-11 17:51:01 +00:00
miniflux-v2/internal/locale/error_test.go
Julien Voisin 181e1341e1
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.
2025-07-31 19:10:14 -07:00

299 lines
8.4 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 (
"errors"
"testing"
)
func TestNewLocalizedErrorWrapper(t *testing.T) {
originalErr := errors.New("original error message")
translationKey := "error.test_key"
args := []any{"arg1", 42}
wrapper := NewLocalizedErrorWrapper(originalErr, translationKey, args...)
if wrapper.originalErr != originalErr {
t.Errorf("Expected original error to be %v, got %v", originalErr, wrapper.originalErr)
}
if wrapper.translationKey != translationKey {
t.Errorf("Expected translation key to be %q, got %q", translationKey, wrapper.translationKey)
}
if len(wrapper.translationArgs) != 2 {
t.Errorf("Expected 2 translation args, got %d", len(wrapper.translationArgs))
}
if wrapper.translationArgs[0] != "arg1" || wrapper.translationArgs[1] != 42 {
t.Errorf("Expected translation args [arg1, 42], got %v", wrapper.translationArgs)
}
}
func TestLocalizedErrorWrapper_Error(t *testing.T) {
originalErr := errors.New("original error message")
wrapper := NewLocalizedErrorWrapper(originalErr, "error.test_key")
result := wrapper.Error()
if result != originalErr {
t.Errorf("Expected Error() to return original error %v, got %v", originalErr, result)
}
}
func TestLocalizedErrorWrapper_Translate(t *testing.T) {
// Set up test catalog
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.test_key": "Error: %s (code: %d)",
},
},
"fr_FR": translationDict{
singulars: map[string]string{
"error.test_key": "Erreur : %s (code : %d)",
},
},
}
originalErr := errors.New("original error")
wrapper := NewLocalizedErrorWrapper(originalErr, "error.test_key", "test message", 404)
// Test English translation
result := wrapper.Translate("en_US")
expected := "Error: test message (code: 404)"
if result != expected {
t.Errorf("Expected English translation %q, got %q", expected, result)
}
// Test French translation
result = wrapper.Translate("fr_FR")
expected = "Erreur : test message (code : 404)"
if result != expected {
t.Errorf("Expected French translation %q, got %q", expected, result)
}
// Test with missing language (should use key as fallback with args applied)
result = wrapper.Translate("invalid_lang")
expected = "error.test_key%!(EXTRA string=test message, int=404)"
if result != expected {
t.Errorf("Expected fallback translation %q, got %q", expected, result)
}
}
func TestLocalizedErrorWrapper_TranslateWithEmptyKey(t *testing.T) {
originalErr := errors.New("original error message")
wrapper := NewLocalizedErrorWrapper(originalErr, "")
result := wrapper.Translate("en_US")
expected := "original error message"
if result != expected {
t.Errorf("Expected original error message %q, got %q", expected, result)
}
}
func TestLocalizedErrorWrapper_TranslateWithNoArgs(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.simple": "Simple error message",
},
},
}
originalErr := errors.New("original error")
wrapper := NewLocalizedErrorWrapper(originalErr, "error.simple")
result := wrapper.Translate("en_US")
expected := "Simple error message"
if result != expected {
t.Errorf("Expected translation %q, got %q", expected, result)
}
}
func TestNewLocalizedError(t *testing.T) {
translationKey := "error.validation"
args := []any{"field1", "invalid"}
localizedErr := NewLocalizedError(translationKey, args...)
if localizedErr.translationKey != translationKey {
t.Errorf("Expected translation key to be %q, got %q", translationKey, localizedErr.translationKey)
}
if len(localizedErr.translationArgs) != 2 {
t.Errorf("Expected 2 translation args, got %d", len(localizedErr.translationArgs))
}
if localizedErr.translationArgs[0] != "field1" || localizedErr.translationArgs[1] != "invalid" {
t.Errorf("Expected translation args [field1, invalid], got %v", localizedErr.translationArgs)
}
}
func TestLocalizedError_String(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.validation": "Validation failed for %s: %s",
},
},
}
localizedErr := NewLocalizedError("error.validation", "username", "too short")
result := localizedErr.String()
expected := "Validation failed for username: too short"
if result != expected {
t.Errorf("Expected String() result %q, got %q", expected, result)
}
}
func TestLocalizedError_StringWithMissingTranslation(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{},
}
localizedErr := NewLocalizedError("error.missing", "arg1")
result := localizedErr.String()
expected := "error.missing%!(EXTRA string=arg1)"
if result != expected {
t.Errorf("Expected String() result %q, got %q", expected, result)
}
}
func TestLocalizedError_Error(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.database": "Database connection failed: %s",
},
},
}
localizedErr := NewLocalizedError("error.database", "timeout")
result := localizedErr.Error()
if result == nil {
t.Error("Expected Error() to return a non-nil error")
}
expected := "Database connection failed: timeout"
if result.Error() != expected {
t.Errorf("Expected Error() message %q, got %q", expected, result.Error())
}
}
func TestLocalizedError_Translate(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.permission": "Permission denied for %s",
},
},
"es_ES": translationDict{
singulars: map[string]string{
"error.permission": "Permiso denegado para %s",
},
},
}
localizedErr := NewLocalizedError("error.permission", "admin panel")
// Test English translation
result := localizedErr.Translate("en_US")
expected := "Permission denied for admin panel"
if result != expected {
t.Errorf("Expected English translation %q, got %q", expected, result)
}
// Test Spanish translation
result = localizedErr.Translate("es_ES")
expected = "Permiso denegado para admin panel"
if result != expected {
t.Errorf("Expected Spanish translation %q, got %q", expected, result)
}
// Test with missing language
result = localizedErr.Translate("invalid_lang")
expected = "error.permission%!(EXTRA string=admin panel)"
if result != expected {
t.Errorf("Expected fallback translation %q, got %q", expected, result)
}
}
func TestLocalizedError_TranslateWithNoArgs(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.generic": "An error occurred",
},
},
"de_DE": translationDict{
singulars: map[string]string{
"error.generic": "Ein Fehler ist aufgetreten",
},
},
}
localizedErr := NewLocalizedError("error.generic")
// Test English
result := localizedErr.Translate("en_US")
expected := "An error occurred"
if result != expected {
t.Errorf("Expected English translation %q, got %q", expected, result)
}
// Test German
result = localizedErr.Translate("de_DE")
expected = "Ein Fehler ist aufgetreten"
if result != expected {
t.Errorf("Expected German translation %q, got %q", expected, result)
}
}
func TestLocalizedError_TranslateWithComplexArgs(t *testing.T) {
defaultCatalog = catalog{
"en_US": translationDict{
singulars: map[string]string{
"error.complex": "Error %d: %s occurred at %s with severity %s",
},
},
}
localizedErr := NewLocalizedError("error.complex", 500, "Internal Server Error", "2024-01-01", "high")
result := localizedErr.Translate("en_US")
expected := "Error 500: Internal Server Error occurred at 2024-01-01 with severity high"
if result != expected {
t.Errorf("Expected complex translation %q, got %q", expected, result)
}
}
func TestLocalizedErrorWrapper_WithNilError(t *testing.T) {
// This tests edge case behavior - what happens with nil error
wrapper := NewLocalizedErrorWrapper(nil, "error.test")
// Error() should return nil
result := wrapper.Error()
if result != nil {
t.Errorf("Expected Error() to return nil, got %v", result)
}
}
func TestLocalizedError_EmptyKey(t *testing.T) {
localizedErr := NewLocalizedError("")
result := localizedErr.String()
expected := ""
if result != expected {
t.Errorf("Expected empty string for empty key, got %q", result)
}
result = localizedErr.Translate("en_US")
if result != expected {
t.Errorf("Expected empty string for empty key translation, got %q", result)
}
}