// Package region provides a first-class region configuration for the commerce // platform. It maps country codes to their associated currency, locale, and // other market-specific settings, replacing the hardcoded getCurrency/getLocale // helpers that were duplicated across services. // // Usage: // // cfg, err := region.LoadConfig("data/regions.json") // currency := cfg.CurrencyOf("no") // "NOK" // locale := cfg.LocaleOf("no") // "nb-no" // country := cfg.CountryFromCurrency("NOK") // "no" package region import ( "encoding/json" "fmt" "os" "strings" ) // Region defines the market configuration for a single country. type Region struct { CountryCode string `json:"countryCode"` // ISO 3166-1 alpha-2 lowercase, e.g. "se", "no", "dk", "fi" Currency string `json:"currency"` // ISO 4217, e.g. "SEK", "NOK", "DKK", "EUR" Locale string `json:"locale"` // IETF BCP 47, e.g. "sv-se", "nb-no", "da-dk", "fi-fi" } // Config holds the full set of region definitions. type Config struct { Regions []Region `json:"regions"` } // LoadConfig reads a region configuration from a JSON file. An absent file // returns a default Config with only SE configured, matching the legacy // hardcoded behaviour. func LoadConfig(path string) (*Config, error) { f, err := os.Open(path) if err != nil { if os.IsNotExist(err) { return DefaultConfig(), nil } return nil, fmt.Errorf("region: open %s: %w", path, err) } defer f.Close() var cfg Config if err := json.NewDecoder(f).Decode(&cfg); err != nil { return nil, fmt.Errorf("region: decode %s: %w", path, err) } // Normalise country codes to lowercase. for i := range cfg.Regions { cfg.Regions[i].CountryCode = strings.ToLower(cfg.Regions[i].CountryCode) } return &cfg, nil } // DefaultConfig returns the built-in default with SE and NO configured. func DefaultConfig() *Config { return &Config{ Regions: []Region{ {CountryCode: "se", Currency: "SEK", Locale: "sv-se"}, {CountryCode: "no", Currency: "NOK", Locale: "nb-no"}, }, } } // CurrencyOf returns the ISO 4217 currency code for the given country. // Returns "SEK" if the country is not configured. func (c *Config) CurrencyOf(country string) string { cc := strings.ToLower(country) for _, r := range c.Regions { if r.CountryCode == cc { return r.Currency } } return "SEK" } // LocaleOf returns the IETF BCP 47 locale tag for the given country. // Returns "sv-se" if the country is not configured. func (c *Config) LocaleOf(country string) string { cc := strings.ToLower(country) for _, r := range c.Regions { if r.CountryCode == cc { return r.Locale } } return "sv-se" } // CountryFromCurrency is the reverse of CurrencyOf — it returns the country // code that uses the given currency. Returns "se" for unknown currencies. func (c *Config) CountryFromCurrency(currency string) string { curr := strings.ToUpper(currency) for _, r := range c.Regions { if strings.EqualFold(r.Currency, curr) { return r.CountryCode } } return "se" }