102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
// 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"
|
|
}
|