mirror of
https://github.com/karust/openserp.git
synced 2026-08-05 16:53:54 +08:00
140 lines
4.1 KiB
Go
140 lines
4.1 KiB
Go
package core
|
|
|
|
import "strings"
|
|
|
|
// Locale is a parsed language/region pair derived from a BCP47-style code.
|
|
// Language is the lowercase 2-letter language subtag (e.g. "en", "de").
|
|
// Country is the uppercase 2-letter region subtag (e.g. "US", "DE"); it may be
|
|
// empty when the input had no region and the caller did not request a default.
|
|
type Locale struct {
|
|
Language string
|
|
Country string
|
|
}
|
|
|
|
var defaultLocaleCountryByLanguage = map[string]string{
|
|
"en": "US",
|
|
"de": "DE",
|
|
"ru": "RU",
|
|
"fr": "FR",
|
|
"es": "ES",
|
|
"it": "IT",
|
|
"pt": "BR",
|
|
"zh": "CN",
|
|
"ja": "JP",
|
|
"ko": "KR",
|
|
"nl": "NL",
|
|
"pl": "PL",
|
|
"tr": "TR",
|
|
"ar": "SA",
|
|
}
|
|
|
|
// ParseLocale parses a language code such as "en", "EN-us", or "de_AT" into a
|
|
// Locale. Returns the zero value when the input is empty or has no language
|
|
// subtag. Country is uppercased; Language is lowercased.
|
|
func ParseLocale(code string) Locale {
|
|
code = strings.TrimSpace(code)
|
|
if code == "" {
|
|
return Locale{}
|
|
}
|
|
code = strings.ReplaceAll(code, "_", "-")
|
|
|
|
parts := strings.Split(code, "-")
|
|
language := strings.ToLower(strings.TrimSpace(parts[0]))
|
|
if language == "" {
|
|
return Locale{}
|
|
}
|
|
|
|
country := ""
|
|
if len(parts) > 1 {
|
|
country = strings.ToUpper(strings.TrimSpace(parts[1]))
|
|
}
|
|
return Locale{Language: language, Country: country}
|
|
}
|
|
|
|
// CountryFromRegion extracts a two-letter country/market code from a region
|
|
// hint. It accepts "ru", "RU", "en-RU", and "en_RU"; engine-native numeric
|
|
// region IDs intentionally return empty here.
|
|
func CountryFromRegion(region string) string {
|
|
region = strings.TrimSpace(region)
|
|
if region == "" {
|
|
return ""
|
|
}
|
|
region = strings.ReplaceAll(region, "_", "-")
|
|
if len(region) == 2 && isAlphaASCII(region[0]) && isAlphaASCII(region[1]) {
|
|
return strings.ToUpper(region)
|
|
}
|
|
|
|
locale := ParseLocale(region)
|
|
if len(locale.Country) == 2 && isAlphaASCII(locale.Country[0]) && isAlphaASCII(locale.Country[1]) {
|
|
return locale.Country
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func isAlphaASCII(ch byte) bool {
|
|
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|
|
}
|
|
|
|
// PrimaryLanguageTag returns the BCP47 primary tag for a lang code, filling in
|
|
// a default country for bare languages (e.g. "de" -> "de-DE"). Returns "" when
|
|
// the input has no language subtag.
|
|
func PrimaryLanguageTag(langCode string) string {
|
|
locale := ParseLocale(langCode)
|
|
if locale.Language == "" {
|
|
return ""
|
|
}
|
|
country := locale.Country
|
|
if country == "" {
|
|
country = defaultLocaleCountryByLanguage[locale.Language]
|
|
}
|
|
if country == "" {
|
|
return locale.Language
|
|
}
|
|
return locale.Language + "-" + country
|
|
}
|
|
|
|
var defaultTimezoneByCountry = map[string]string{
|
|
"US": "America/New_York", "GB": "Europe/London", "DE": "Europe/Berlin",
|
|
"FR": "Europe/Paris", "ES": "Europe/Madrid", "IT": "Europe/Rome",
|
|
"RU": "Europe/Moscow", "BR": "America/Sao_Paulo", "JP": "Asia/Tokyo",
|
|
"CN": "Asia/Shanghai", "KR": "Asia/Seoul", "IN": "Asia/Kolkata",
|
|
"AU": "Australia/Sydney", "CA": "America/Toronto",
|
|
"MX": "America/Mexico_City", "PL": "Europe/Warsaw",
|
|
"NL": "Europe/Amsterdam", "TR": "Europe/Istanbul",
|
|
"AR": "America/Argentina/Buenos_Aires", "SA": "Asia/Riyadh",
|
|
"BE": "Europe/Brussels", "KZ": "Asia/Almaty", "UA": "Europe/Kyiv",
|
|
}
|
|
|
|
// TimezoneForLocale returns an IANA timezone for a locale's country, or for
|
|
// the default country of the language when country is empty. Returns "" for
|
|
// unknown locales — caller should retain the existing profile timezone.
|
|
func TimezoneForLocale(loc Locale) string {
|
|
if loc.Country != "" {
|
|
if tz, ok := defaultTimezoneByCountry[loc.Country]; ok {
|
|
return tz
|
|
}
|
|
}
|
|
if loc.Language != "" {
|
|
if country, ok := defaultLocaleCountryByLanguage[loc.Language]; ok {
|
|
if tz, ok2 := defaultTimezoneByCountry[country]; ok2 {
|
|
return tz
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// BuildAcceptLanguageHeader formats an Accept-Language value from a lang code.
|
|
// Example: "de" -> "de-DE,de;q=0.9", "en-GB" -> "en-GB,en;q=0.9", "sw" -> "sw".
|
|
func BuildAcceptLanguageHeader(langCode string) string {
|
|
primary := PrimaryLanguageTag(langCode)
|
|
if primary == "" {
|
|
return ""
|
|
}
|
|
language := ParseLocale(langCode).Language
|
|
if primary == language {
|
|
return language
|
|
}
|
|
return primary + "," + language + ";q=0.9"
|
|
}
|