package web import ( "fmt" "runtime" "strings" "sync" "gitgud.io/mike/mpv-manager/internal/assets" "gitgud.io/mike/mpv-manager/pkg/locale" ) type LocaleCache struct { entries []locale.LocaleEntry err error once sync.Once } var localeCache = &LocaleCache{} func LoadLocales() ([]locale.LocaleEntry, error) { localeCache.once.Do(func() { jsonData, err := assets.ReadLocales() if err != nil { localeCache.err = fmt.Errorf("failed to read locales.json: %w", err) return } localeCache.entries = cloneLocaleEntries(jsonData) }) return cloneLocaleEntries(localeCache.entries), localeCache.err } func cloneLocaleEntries(entries []locale.LocaleEntry) []locale.LocaleEntry { cloned := make([]locale.LocaleEntry, len(entries)) for i := range entries { cloned[i] = entries[i] cloned[i].Regions = append([]locale.RegionEntry(nil), entries[i].Regions...) } return cloned } func GetFlagDisplay(localeStr string) string { if localeStr == "" { return "" } if runtime.GOOS == "windows" { entries, _ := LoadLocales() _, region := locale.FindByLocale(localeStr, entries) if region != nil { return fmt.Sprintf("/static/flags/%s.png", region.CountryCode) } return "" } parts := strings.Split(localeStr, "-") if len(parts) > 0 { return locale.GetFlagForLanguageCode(strings.ToLower(parts[0])) } return "🏳️" } // GetLanguageEntry returns the locale entry for a language code func GetLanguageEntry(localeStr string) *locale.LocaleEntry { if localeStr == "" { return nil } entries, _ := LoadLocales() parts := strings.Split(localeStr, "-") langCode := strings.ToLower(parts[0]) entry := locale.FindByLanguageCode(langCode, entries) if entry == nil { return nil } return entry } // Default country codes for common languages when no region specified var defaultCountryCodes = map[string]string{ "en": "US", // English "es": "ES", // Spanish "ar": "SA", // Arabic "ja": "JP", // Japanese "zh": "CN", // Chinese "fr": "FR", // French "de": "DE", // German "ru": "RU", // Russian "pt": "BR", // Portuguese "it": "IT", // Italian "ko": "KR", // Korean "hi": "IN", // Hindi "nl": "NL", // Dutch "pl": "PL", // Polish "tr": "TR", // Turkish "vi": "VN", // Vietnamese "th": "TH", // Thai "id": "ID", // Indonesian "ms": "MY", // Malay "sv": "SE", // Swedish "da": "DK", // Danish "no": "NO", // Norwegian "fi": "FI", // Finnish "cs": "CZ", // Czech "el": "GR", // Greek "he": "IL", // Hebrew "ro": "RO", // Romanian "hu": "HU", // Hungarian "sk": "SK", // Slovak } // GetLanguageCountryCode returns the country code for a locale string (for flag display) func GetLanguageCountryCode(localeStr string) string { if localeStr == "" { return "" } entries, _ := LoadLocales() // Try to find exact locale match first (for codes like "en-US", "es-419", "ru-BY") _, region := locale.FindByLocale(localeStr, entries) if region != nil && region.CountryCode != "" { return region.CountryCode } // For single-part locales (e.g., "en", "ja", "ar"), find language and use default country code parts := strings.Split(localeStr, "-") if len(parts) == 1 { langCode := strings.ToLower(parts[0]) // Check default country codes first if countryCode, ok := defaultCountryCodes[langCode]; ok { return countryCode } // Fallback to first region's country code from locale data entry := locale.FindByLanguageCode(langCode, entries) if entry != nil && len(entry.Regions) > 0 { return entry.Regions[0].CountryCode } } // Fallback: return empty string (will show UN flag) return "" } // GetRegionalLanguageName returns "Language - Region" or just "Language" for display func GetRegionalLanguageName(localeStr string) string { if localeStr == "" { return "" } entries, _ := LoadLocales() // Try to find exact locale match first entry, region := locale.FindByLocale(localeStr, entries) if entry != nil && region != nil { // Regional locale: return "Language - Region" if region.CountryName != "" { // Check if language name already contains region name in parentheses // e.g., "Spanish (Latin America)" already contains "Latin America" regionInParens := fmt.Sprintf("(%s)", region.CountryName) if strings.Contains(entry.LanguageName, regionInParens) { // Language name already includes region, return as-is return entry.LanguageName } return fmt.Sprintf("%s - %s", entry.LanguageName, region.CountryName) } } // Single-part locale or no region match: just return language name parts := strings.Split(localeStr, "-") if len(parts) >= 1 { langCode := strings.ToLower(parts[0]) entry = locale.FindByLanguageCode(langCode, entries) if entry != nil { return entry.LanguageName } } // Fallback: return locale string as-is return localeStr } // GetRegionalNativeName returns "NativeLanguage - NativeRegion" or just "NativeLanguage" for display func GetRegionalNativeName(localeStr string) string { if localeStr == "" { return "" } entries, _ := LoadLocales() // Try to find exact locale match first entry, region := locale.FindByLocale(localeStr, entries) if entry != nil && region != nil { // Regional locale: return "NativeLanguage - NativeRegion" if region.CountryLocal != "" { // Check if native language name already contains region name in parentheses // e.g., "Español (América Latina)" already contains "América Latina" regionInParens := fmt.Sprintf("(%s)", region.CountryLocal) if strings.Contains(entry.LanguageLocal, regionInParens) { // Native language name already includes region, return as-is return entry.LanguageLocal } // Only append region if different from language name if entry.LanguageLocal != region.CountryLocal { return fmt.Sprintf("%s - %s", entry.LanguageLocal, region.CountryLocal) } } } // Single-part locale or no region match: just return native language name parts := strings.Split(localeStr, "-") if len(parts) >= 1 { langCode := strings.ToLower(parts[0]) entry = locale.FindByLanguageCode(langCode, entries) if entry != nil { return entry.LanguageLocal } } // Fallback: return locale string as-is return localeStr } func FormatLanguageDisplay(localeStr string) string { // Return "Not set" for empty strings if localeStr == "" { return "Not set" } parts := strings.Split(localeStr, "-") langCode := strings.ToLower(parts[0]) entries, _ := LoadLocales() langEntry := locale.FindByLanguageCode(langCode, entries) if langEntry == nil { return localeStr } // Handle single-part locale (e.g., "en") - Major/General language if len(parts) == 1 { flag := locale.GetFlagForLanguageCode(langCode) // Return native name only (e.g., "Português" not "Português - Portugal") return fmt.Sprintf("%s - %s", flag, langEntry.LanguageLocal) } // Handle two-part locale (e.g., "en-US") - Regional variant _, region := locale.FindByLocale(localeStr, entries) if region == nil { flag := locale.GetFlagForLanguageCode(langCode) // Fallback for missing region data return fmt.Sprintf("%s - %s", flag, langEntry.LanguageLocal) } // For regional variants, use native region name if available displayName := region.CountryLocal if displayName == langEntry.LanguageName && region.CountryLocal != region.CountryName { // If native region name equals language name (e.g., "Français" = "Français"), // use country name instead (e.g., "Canada") displayName = region.CountryName } // Return native name only (e.g., "Português - Brasil" not "Português - Brazil") flag := GetFlagDisplay(localeStr) return fmt.Sprintf("%s - %s", flag, displayName) }