Skip to content

Commit

Permalink
feat : [I18PP-249] i18nify-go geo changes (#182)
Browse files Browse the repository at this point in the history
  • Loading branch information
rzp-Piyush authored Dec 12, 2024
1 parent 5ba8f6d commit e43a8cb
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 6 deletions.
26 changes: 21 additions & 5 deletions packages/i18nify-go/country.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package i18nify_go
import (
metadata "github.com/razorpay/i18nify/packages/i18nify-go/modules/country_metadata"
subdivisions "github.com/razorpay/i18nify/packages/i18nify-go/modules/country_subdivisions"
"github.com/razorpay/i18nify/packages/i18nify-go/modules/country_subdivisions/zipcode"
currency "github.com/razorpay/i18nify/packages/i18nify-go/modules/currency"
phonenumber "github.com/razorpay/i18nify/packages/i18nify-go/modules/phonenumber"
)
Expand All @@ -14,22 +15,22 @@ type Country struct {
}

// GetCountryMetadata retrieves metadata information for the country.
func (c Country) GetCountryMetadata() metadata.MetadataInformation {
func (c *Country) GetCountryMetadata() metadata.MetadataInformation {
return metadata.GetMetadataInformation(c.Code)
}

// GetCountryPhoneNumber retrieves telephone number information for the country.
func (c Country) GetCountryPhoneNumber() phonenumber.CountryTeleInformation {
func (c *Country) GetCountryPhoneNumber() phonenumber.CountryTeleInformation {
return phonenumber.GetCountryTeleInformation(c.Code)
}

// GetCountrySubDivisions retrieves subdivision information for the country.
func (c Country) GetCountrySubDivisions() subdivisions.CountrySubdivisions {
func (c *Country) GetCountrySubDivisions() subdivisions.CountrySubdivisions {
return subdivisions.GetCountrySubdivisions(c.Code)
}

// GetCountryCurrency retrieves currency information for the country.
func (c Country) GetCountryCurrency() []currency.CurrencyInformation {
func (c *Country) GetCountryCurrency() []currency.CurrencyInformation {
// Retrieve metadata information for the country.
countryMetadata := c.GetCountryMetadata()

Expand All @@ -42,9 +43,24 @@ func (c Country) GetCountryCurrency() []currency.CurrencyInformation {
return curInfoList
}

// GetStatesByZipCode retrieves the states with zipcode for the country.
func (c *Country) GetStatesByZipCode(zipCode string) []subdivisions.State {
return zipcode.GetDetailsFromZipCode(zipCode, c.Code)
}

// IsValidZipCode returns whether a zipCode is valid for the country or not.
func (c *Country) IsValidZipCode(zipCode string) bool {
return zipcode.IsValidZipCode(zipCode, c.Code)
}

// GetZipCodesFromCity returns all the zipcodes belonging to that city.
func (c *Country) GetZipCodesFromCity(cityName string) []string {
return zipcode.GetZipCodesFromCity(cityName, c.Code)
}

// NewCountry creates a new Country instance with the given country code.
func NewCountry(code string) ICountry {
return Country{
return &Country{
Code: code,
}
}
19 changes: 19 additions & 0 deletions packages/i18nify-go/example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ func main() {
fmt.Println(state.GetCities()[0]) //{Yellāpur nan Asia/Kolkata [581337 581337 ...}
fmt.Println(len(state.GetCities())) //58

// Get States by zipcode
// As suggested online there are some cases in which one zipcode can be
madhyaPradesh := countryIN.GetStatesByZipCode("452010")[0]
fmt.Printf("For pincode 452010 state : %s\n", madhyaPradesh) // {[{Wārāseonī nan Asia/Kolkata [481331 ...}
fmt.Printf("State name %s\n", madhyaPradesh.GetName())

// Get Cities
cityList := madhyaPradesh.Cities
fmt.Println("Cities :")
for _, city := range cityList {
fmt.Println(city.Name)
}

// Get zipcodes by city
zipcodes := countryIN.GetZipCodesFromCity("indore")
for _, val := range zipcodes {
fmt.Println(val)
}

//USD
currencyUS := currency.GetCurrencyInformation("USD")
fmt.Println(currencyUS.Name) //US Dollar
Expand Down
6 changes: 6 additions & 0 deletions packages/i18nify-go/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,10 @@ type ICountry interface {
GetCountryPhoneNumber() phonenumber.CountryTeleInformation
// GetCountryCurrency retrieves currency information for the country.
GetCountryCurrency() []currency.CurrencyInformation
// GetStatesByZipCode retrieves the states with zipcode for the country.
GetStatesByZipCode(zipcode string) []country_subdivisions.State
// IsValidZipCode returns whether a pinCode is valid for the country or not.
IsValidZipCode(zipcode string) bool
// GetZipCodesFromCity returns all the zipcodes belonging to that city.
GetZipCodesFromCity(cityName string) []string
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import (
//go:embed data
var subDivJsonDir embed.FS

// Cache to avoid duplicate reads for same countryCode
var countrySubDivisionStore = make(map[string]CountrySubdivisions)

// DataFile is the directory where JSON files containing country subdivision data are stored. "

// UnmarshalCountrySubdivisions parses JSON data into a CountrySubdivisions struct.
Expand Down Expand Up @@ -46,9 +49,18 @@ func (r *CountrySubdivisions) GetCountryName() string {
func (r *CountrySubdivisions) GetStates() map[string]State {
return r.States
}
func (r *CountrySubdivisions) GetStateByStateCode(code string) (State, bool) {
if _, exists := r.States[code]; exists {
return r.States[code], true
}
return State{}, false
}

// GetCountrySubdivisions retrieves subdivision information for a specific country code.
func GetCountrySubdivisions(code string) CountrySubdivisions {
if _, present := countrySubDivisionStore[code]; present {
return countrySubDivisionStore[code]
}
// Read JSON data file containing country subdivision information.
completePath := filepath.Join("data/", code+".json")
subDivJsonData, err := subDivJsonDir.ReadFile(completePath)
Expand All @@ -58,6 +70,8 @@ func GetCountrySubdivisions(code string) CountrySubdivisions {
}
// Unmarshal JSON data into CountrySubdivisions struct.
allSubDivData, _ := UnmarshalCountrySubdivisions(subDivJsonData)
// Store calculated CountrySubDivisions into the cache
countrySubDivisionStore[code] = allSubDivData
return allSubDivData
}

Expand Down Expand Up @@ -125,6 +139,16 @@ func (r *City) GetZipcodes() []string {
return r.Zipcodes
}

// IsValidZipCode returns whether the input zipcode is valid for the city.
func (r *City) IsValidZipCode(zipcode string) bool {
for _, code := range r.Zipcodes {
if code == zipcode {
return true
}
}
return false
}

// NewCity creates a new City instance.
func NewCity(name string, regionName string, timezone string, zipcodes []string) *City {
return &City{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"testing"
)

var testJSONData = []byte(`{"country_name": "India", "states": {"KA": {"name": "Karnataka", "cities": [{"name": "Bengaluru", "timezone": "Asia/Kolkata", "zipcodes": ["560018", "560116", "560500"], "region_name/district_name": "nan"}]}}}`)
//var testJSONData = []byte(`{"country_name": "India", "states": {"KA": {"name": "Karnataka", "cities": [{"name": "Bengaluru", "timezone": "Asia/Kolkata", "zipcodes": ["560018", "560116", "560500"], "region_name/district_name": "nan"}]}}}`)

func TestUnmarshalCountrySubdivisions(t *testing.T) {
jsonData, err := subDivJsonDir.ReadFile("data/IN.json")
Expand Down Expand Up @@ -83,3 +83,22 @@ func assertIsArray(t *testing.T, value interface{}) {
t.Errorf("Expected an array or slice, but got %T", value)
}
}
func TestGetStateByStateCode(t *testing.T) {
data := CountrySubdivisions{
CountryName: "India",
States: map[string]State{
"KA": {Name: "Karnataka"},
"MH": {Name: "Maharashtra"},
},
}

// Test: Valid state code
state, exists := data.GetStateByStateCode("KA")
assert.True(t, exists, "State should exist for valid state code")
assert.Equal(t, "Karnataka", state.GetName())

// Test: Invalid state code
state, exists = data.GetStateByStateCode("TN")
assert.False(t, exists, "State should not exist for invalid state code")
assert.Equal(t, State{}, state, "State should be empty for invalid state code")
}
132 changes: 132 additions & 0 deletions packages/i18nify-go/modules/country_subdivisions/zipcode/zipcode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Package zipcode manages lookup for all the details related to zipcode (city, state) for a country
package zipcode

import (
"github.com/razorpay/i18nify/packages/i18nify-go/modules/country_subdivisions"
"strings"
)

type ZipCodeDetails struct {
Details []Details
}

type ZipCodeData struct {
zipCodeToDetails map[string]ZipCodeDetails
cityToZipCodes map[string][]string
}
type Details struct {
cityNames []string
stateCode string
}

var zipCodeStore = make(map[string]ZipCodeData)

func GetCountryZipCodeDetails(countryCode string) ZipCodeData {
if _, exists := zipCodeStore[countryCode]; !exists {
subdivision := country_subdivisions.GetCountrySubdivisions(countryCode)
zipCodeData := initializeZipCodeMap(subdivision)
zipCodeStore[countryCode] = zipCodeData
}
return zipCodeStore[countryCode]
}
func GetDetailsFromZipCode(zipCode string, countryCode string) []country_subdivisions.State {
// Validate inputs
if zipCode == "" || countryCode == "" {
return nil
}

// Get zip code data and subdivisions
zipCodeData := GetCountryZipCodeDetails(countryCode)
subdivisions := country_subdivisions.GetCountrySubdivisions(countryCode)

var states []country_subdivisions.State

// Check if zipcode exists in the data
zipDetails, exists := zipCodeData.zipCodeToDetails[zipCode]
if !exists {
return states
}

// Iterate through details for this zipcode
for _, detail := range zipDetails.Details {
stateCode := detail.stateCode
// Check if state exists
state, stateExists := subdivisions.States[stateCode]
if !stateExists {
continue
}
// Create a copy of the state to modify
stateCopy := state
stateCopy.Cities = map[string]country_subdivisions.City{}
for _, cityName := range detail.cityNames {
stateCopy.Cities[cityName] = state.Cities[cityName]
}
states = append(states, stateCopy)
}

return states
}

func IsValidZipCode(zipCode string, countryCode string) bool {
zipCodeData := GetCountryZipCodeDetails(countryCode)
_, exists := zipCodeData.zipCodeToDetails[zipCode]
return exists
}
func GetZipCodesFromCity(city string, countryCode string) []string {
zipCodeData := GetCountryZipCodeDetails(countryCode)
return zipCodeData.cityToZipCodes[strings.ToLower(city)]
}

// initializeZipCodeMap builds the zip code maps for the given CountrySubdivisions.
func initializeZipCodeMap(subdivisions country_subdivisions.CountrySubdivisions) ZipCodeData {
cityToZipCode := make(map[string][]string)
zipCodeToDetails := make(map[string]ZipCodeDetails)

for stateCode, state := range subdivisions.States {
for _, city := range state.Cities {
// Lowercase city name once
cityKey := strings.ToLower(city.Name)
cityToZipCode[cityKey] = city.Zipcodes

for _, zipcode := range city.Zipcodes {
details, exists := zipCodeToDetails[zipcode]
if !exists {
details = ZipCodeDetails{}
}
// Check if state already exists to avoid duplicates
stateExists := false
for _, existingDetail := range details.Details {
if existingDetail.stateCode == stateCode {
stateExists = true
// Ensure unique city names
existingDetail.cityNames = appendUnique(existingDetail.cityNames, city.Name)
break
}
}
if !stateExists {
details.Details = append(details.Details, Details{
stateCode: stateCode,
cityNames: []string{city.Name},
})
}

zipCodeToDetails[zipcode] = details
}
}
}

return ZipCodeData{
cityToZipCodes: cityToZipCode,
zipCodeToDetails: zipCodeToDetails,
}
}

// Helper function to append unique strings
func appendUnique(slice []string, item string) []string {
for _, existing := range slice {
if existing == item {
return slice
}
}
return append(slice, item)
}
Loading

0 comments on commit e43a8cb

Please sign in to comment.