package services import ( "strings" goi18n "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/treytartt/honeydue-api/internal/dto/responses" "github.com/treytartt/honeydue-api/internal/i18n" ) // lookup kinds — the message-key namespace for each localizable lookup type. const ( lookupKindResidenceType = "residence_type" lookupKindTaskCategory = "task_category" lookupKindTaskPriority = "task_priority" lookupKindTaskFrequency = "task_frequency" lookupKindSpecialty = "contractor_specialty" lookupKindHomeProfile = "home_profile" lookupKindDocumentType = "document_type" lookupKindDocumentCat = "document_category" ) // documentTypeValues / documentCategoryValues are the stable client enum codes // (see iOS DocumentType/DocumentCategory). Display labels are localized at // request time. Order is presentation order. var documentTypeValues = []string{ "warranty", "manual", "receipt", "inspection", "permit", "deed", "insurance", "contract", "photo", "other", } var documentCategoryValues = []string{ "appliance", "hvac", "plumbing", "electrical", "roofing", "structural", "landscaping", "general", "other", } // localizedList maps a list of stable values to {value, localized display}. func localizedList(localizer *goi18n.Localizer, kind string, values []string) []HomeProfileOption { out := make([]HomeProfileOption, 0, len(values)) for _, v := range values { out = append(out, HomeProfileOption{ Value: v, DisplayName: localizeLookup(localizer, kind, v), }) } return out } // BuildDocumentTypes returns localized document-type options. func BuildDocumentTypes(localizer *goi18n.Localizer) []HomeProfileOption { return localizedList(localizer, lookupKindDocumentType, documentTypeValues) } // BuildDocumentCategories returns localized document-category options. func BuildDocumentCategories(localizer *goi18n.Localizer) []HomeProfileOption { return localizedList(localizer, lookupKindDocumentCat, documentCategoryValues) } // lookupSlug normalizes a stable English lookup name (or option value) into a // message-key slug: lowercased, non-alphanumeric runs collapsed to "_". // "Pest Control" -> "pest_control", "Bi-Weekly" -> "bi_weekly", "tank_gas" -> // "tank_gas". func lookupSlug(name string) string { var b strings.Builder prevUnderscore := false for _, r := range strings.ToLower(strings.TrimSpace(name)) { switch { case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'): b.WriteRune(r) prevUnderscore = false default: if !prevUnderscore && b.Len() > 0 { b.WriteByte('_') prevUnderscore = true } } } return strings.Trim(b.String(), "_") } // localizeLookup returns the localized display label for a lookup value. // Keys follow "lookup..". If the locale lacks the key it falls back // to English, and ultimately to the original name so a raw key never surfaces. func localizeLookup(localizer *goi18n.Localizer, kind, name string) string { key := "lookup." + kind + "." + lookupSlug(name) msg := i18n.T(localizer, key, nil) if msg == key { msg = i18n.T(i18n.NewLocalizer(i18n.DefaultLanguage), key, nil) } if msg == key { // No translation anywhere — fall back to the stable English name. return name } return msg } // HomeProfileOption is a single selectable value for a home-profile field. type HomeProfileOption struct { Value string `json:"value"` DisplayName string `json:"display_name"` } // homeProfileCatalog is the canonical set of home-profile field options, // mirroring the (previously hardcoded) iOS dropdowns. Order is presentation // order. Display labels are localized at request time via localizeLookup. var homeProfileCatalog = []struct { Field string Values []string }{ {"heating_type", []string{"gas_furnace", "electric_furnace", "heat_pump", "boiler", "radiant", "other"}}, {"cooling_type", []string{"central_ac", "window_ac", "heat_pump", "evaporative", "none", "other"}}, {"water_heater_type", []string{"tank_gas", "tank_electric", "tankless_gas", "tankless_electric", "heat_pump", "solar", "other"}}, {"roof_type", []string{"asphalt_shingle", "metal", "tile", "slate", "wood_shake", "flat", "other"}}, {"exterior_type", []string{"brick", "vinyl_siding", "wood_siding", "stucco", "stone", "fiber_cement", "other"}}, {"flooring_primary", []string{"hardwood", "laminate", "tile", "carpet", "vinyl", "concrete", "other"}}, {"landscaping_type", []string{"lawn", "desert", "xeriscape", "garden", "mixed", "none", "other"}}, } // BuildHomeProfileOptions returns the home-profile field options with display // labels localized for the supplied localizer (nil falls back to English). func BuildHomeProfileOptions(localizer *goi18n.Localizer) map[string][]HomeProfileOption { out := make(map[string][]HomeProfileOption, len(homeProfileCatalog)) for _, f := range homeProfileCatalog { opts := make([]HomeProfileOption, 0, len(f.Values)) for _, v := range f.Values { opts = append(opts, HomeProfileOption{ Value: v, DisplayName: localizeLookup(localizer, lookupKindHomeProfile, v), }) } out[f.Field] = opts } return out } // LocalizeLookups fills the DisplayName of each lookup slice in place, using the // supplied localizer. Mutates the passed slices. func LocalizeLookups( localizer *goi18n.Localizer, residenceTypes []responses.ResidenceTypeResponse, categories []responses.TaskCategoryResponse, priorities []responses.TaskPriorityResponse, frequencies []responses.TaskFrequencyResponse, specialties []responses.ContractorSpecialtyResponse, ) { for i := range residenceTypes { residenceTypes[i].DisplayName = localizeLookup(localizer, lookupKindResidenceType, residenceTypes[i].Name) } for i := range categories { categories[i].DisplayName = localizeLookup(localizer, lookupKindTaskCategory, categories[i].Name) } for i := range priorities { priorities[i].DisplayName = localizeLookup(localizer, lookupKindTaskPriority, priorities[i].Name) } for i := range frequencies { frequencies[i].DisplayName = localizeLookup(localizer, lookupKindTaskFrequency, frequencies[i].Name) } for i := range specialties { specialties[i].DisplayName = localizeLookup(localizer, lookupKindSpecialty, specialties[i].Name) } }