package web import ( "fmt" "runtime" "sort" "strings" "gitgud.io/mike/mpv-manager/internal/assets" "gitgud.io/mike/mpv-manager/pkg/locale" ) type LocaleCache struct { entries []locale.LocaleEntry loaded bool } var localeCache = &LocaleCache{} func LoadLocales() ([]locale.LocaleEntry, error) { if localeCache.loaded { return localeCache.entries, nil } jsonData, err := assets.ReadLocales() if err != nil { return nil, fmt.Errorf("failed to read locales.json: %w", err) } localeCache.entries = jsonData localeCache.loaded = true return jsonData, nil } func GetFlagDisplay(localeStr string) string { if localeStr == "" { return "" } if runtime.GOOS == "windows" { _, region := locale.FindByLocale(localeStr, localeCache.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 } // Ensure locale cache is loaded if !localeCache.loaded { LoadLocales() } parts := strings.Split(localeStr, "-") langCode := strings.ToLower(parts[0]) entry := locale.FindByLanguageCode(langCode, localeCache.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 "" } // Ensure locale cache is loaded if !localeCache.loaded { LoadLocales() } // Try to find exact locale match first (for codes like "en-US", "es-419", "ru-BY") _, region := locale.FindByLocale(localeStr, localeCache.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, localeCache.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 "" } // Ensure locale cache is loaded if !localeCache.loaded { LoadLocales() } // Try to find exact locale match first entry, region := locale.FindByLocale(localeStr, localeCache.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, localeCache.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 "" } // Ensure locale cache is loaded if !localeCache.loaded { LoadLocales() } // Try to find exact locale match first entry, region := locale.FindByLocale(localeStr, localeCache.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, localeCache.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]) langEntry := locale.FindByLanguageCode(langCode, localeCache.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, localeCache.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) } func GetCommonLanguages() ([]locale.LocaleEntry, []string) { entries, err := LoadLocales() if err != nil { return []locale.LocaleEntry{}, []string{} } commonCodes := []string{ "en", "ja", "es", "fr", "de", "zh", "ko", "it", "pt", "ru", } var result []locale.LocaleEntry var locales []string seen := make(map[string]bool) for _, code := range commonCodes { langEntry := locale.FindByLanguageCode(code, entries) if langEntry != nil && !seen[code] { result = append(result, *langEntry) seen[code] = true if len(langEntry.Regions) > 0 { locales = append(locales, langEntry.Regions[0].Locale) } } } return result, locales } func GetAllLanguages() ([]locale.LocaleEntry, []string) { entries, err := LoadLocales() if err != nil { return []locale.LocaleEntry{}, []string{} } sort.Slice(entries, func(i, j int) bool { return entries[i].LanguageName < entries[j].LanguageName }) var locales []string for _, entry := range entries { if len(entry.Regions) > 0 { locales = append(locales, entry.Regions[0].Locale) } } return entries, locales } // For more advanced locale utilities, see locale_utils.go which provides: // - LanguageOption struct with comprehensive display data // - SearchLanguages() with flexible filtering (common only, search by term, limit) // - GetCommonLanguageOptions() and GetAllLanguageOptions() returning LanguageOption // - GetLanguagesByCountry() to get languages for a specific country // - FindLanguageByLocale() to find a specific language option // - GetFlagImageURL() to get flag image URL paths for web UI // - FormatLanguageDisplayEntry() and FormatLanguageDisplayBasic() for formatted display