i18n: backend-localized lookups, suggestions, and static data (10 languages)
- suggestion_service: fix scorer (stringList unmarshal accepts scalar|array; anchor scoring on base universal score so bool matches no longer tie); add localizeReasons for human-readable, Accept-Language-localized match reasons - lookup_i18n: localize lookup display names, home-profile options, document types/categories via internal/i18n - static_data_handler: per-locale seeded-data response (display_name, home profile options, document types/categories) with per-locale cache + ETag - settings_handler: invalidate per-locale seeded-data cache on lookup change instead of pre-warming a single non-localized blob - cache_service: per-locale seeded-data keys + ETag - DTOs: add DisplayName fields (task/residence/contractor) - translations: add suggestion.reason.* and lookup.* keys across all 10 langs - cmd/api: extract startup helpers + tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/treytartt/honeydue-api/internal/dto/responses"
|
||||
"github.com/treytartt/honeydue-api/internal/i18n"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Load the embedded translation bundle for the localization tests.
|
||||
_ = i18n.Init()
|
||||
}
|
||||
|
||||
func TestLookupSlug(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"Pest Control": "pest_control",
|
||||
"Bi-Weekly": "bi_weekly",
|
||||
"Semi-Annually": "semi_annually",
|
||||
"Mobile Home": "mobile_home",
|
||||
"HVAC": "hvac",
|
||||
"tank_gas": "tank_gas",
|
||||
"Tankless (Gas)": "tankless_gas",
|
||||
" Trailing/Punct ": "trailing_punct",
|
||||
}
|
||||
for in, want := range cases {
|
||||
assert.Equalf(t, want, lookupSlug(in), "slug(%q)", in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalizeLookup_EnglishAndSpanish(t *testing.T) {
|
||||
en := i18n.NewLocalizer("en")
|
||||
es := i18n.NewLocalizer("es")
|
||||
|
||||
// Known value: localizes per locale.
|
||||
assert.Equal(t, "Plumbing", localizeLookup(en, lookupKindTaskCategory, "Plumbing"))
|
||||
assert.Equal(t, "Fontanería", localizeLookup(es, lookupKindTaskCategory, "Plumbing"))
|
||||
|
||||
// Frequency with separators (regression on the bi_weekly/semi_annually slug).
|
||||
assert.Equal(t, "Bi-Weekly", localizeLookup(en, lookupKindTaskFrequency, "Bi-Weekly"))
|
||||
assert.Equal(t, "Quincenal", localizeLookup(es, lookupKindTaskFrequency, "Bi-Weekly"))
|
||||
}
|
||||
|
||||
func TestLocalizeLookup_NilLocalizerFallsBackToEnglish(t *testing.T) {
|
||||
assert.Equal(t, "House", localizeLookup(nil, lookupKindResidenceType, "House"))
|
||||
}
|
||||
|
||||
func TestLocalizeLookup_UnknownValueFallsBackToName(t *testing.T) {
|
||||
// No translation key exists -> return the stable English name, never a key.
|
||||
got := localizeLookup(i18n.NewLocalizer("es"), lookupKindTaskCategory, "Totally Unknown Value")
|
||||
assert.Equal(t, "Totally Unknown Value", got)
|
||||
}
|
||||
|
||||
func TestBuildHomeProfileOptions(t *testing.T) {
|
||||
opts := BuildHomeProfileOptions(i18n.NewLocalizer("es"))
|
||||
|
||||
// All 7 fields present.
|
||||
for _, field := range []string{"heating_type", "cooling_type", "water_heater_type", "roof_type", "exterior_type", "flooring_primary", "landscaping_type"} {
|
||||
require.Containsf(t, opts, field, "missing field %s", field)
|
||||
require.NotEmpty(t, opts[field])
|
||||
}
|
||||
|
||||
// Values are stable; display_name is localized.
|
||||
heating := opts["heating_type"]
|
||||
assert.Equal(t, "gas_furnace", heating[0].Value)
|
||||
assert.Equal(t, "Calefactor de gas", heating[0].DisplayName)
|
||||
}
|
||||
|
||||
func TestLocalizeLookups_FillsDisplayName(t *testing.T) {
|
||||
cats := []responses.TaskCategoryResponse{{Name: "Plumbing"}, {Name: "HVAC"}}
|
||||
prios := []responses.TaskPriorityResponse{{Name: "High"}}
|
||||
freqs := []responses.TaskFrequencyResponse{{Name: "Monthly"}}
|
||||
specs := []responses.ContractorSpecialtyResponse{{Name: "Plumber"}}
|
||||
types := []responses.ResidenceTypeResponse{{Name: "House"}}
|
||||
|
||||
LocalizeLookups(i18n.NewLocalizer("es"), types, cats, prios, freqs, specs)
|
||||
|
||||
assert.Equal(t, "Fontanería", cats[0].DisplayName)
|
||||
assert.Equal(t, "Plumbing", cats[0].Name) // name stays stable
|
||||
assert.Equal(t, "Climatización", cats[1].DisplayName)
|
||||
assert.Equal(t, "Alta", prios[0].DisplayName)
|
||||
assert.Equal(t, "Mensual", freqs[0].DisplayName)
|
||||
assert.Equal(t, "Fontanero", specs[0].DisplayName)
|
||||
assert.Equal(t, "Casa", types[0].DisplayName)
|
||||
}
|
||||
Reference in New Issue
Block a user