stefangabos / world_countries

Constantly updated lists of world countries and their associated alpha-2, alpha-3 and numeric country codes as defined by the ISO 3166 standard, available in CSV, JSON , PHP, SQL and XML formats, in multiple languages and with national flags included; also available are the ISO 3166-2 codes of provinces/ states associated with the countries
http://stefangabos.github.io/world_countries/
Other
1.34k stars 374 forks source link

Add ts types #70

Closed bfischer1121 closed 2 years ago

bfischer1121 commented 2 years ago

Why

Add typings for TypeScript users

How

stefangabos commented 2 years ago

this looks deceivingly simple and beautiful! but i am not familiar with all of this (i am old-school, yet to catch up with the world of react and its friends) :) can you please make me an example on how would one use all of this as what is happening at line 41 in types.d.ts is evil magic

bfischer1121 commented 2 years ago

Ha! Well if you like technical puzzles and rabbit holes then it's an interesting path to go down...

Country describes the shape of data in data/countries/*/* TranslatedCountry describes the data in data/countries/_combined/*

In javascript-speak, the file is doing this (where Omit and Record are built-in TS functions called generics):

function Omit(object, key) {
  delete object[key]
  return object
}

function Record(keys, value) {
  const record = {}

  keys.forEach(key => {
    record[key] = value
  })

  return record
}

export const LanguageCode = ['ar', 'bg']

export const Country = {
  id: 'number',
  alpha2: 'string',
  alpha3: 'string',
  name: 'string'
}

// { id: 'number', alpha2: 'string', alpha3: 'string', 'ar': 'string', 'bg': 'string' }
export const TranslatedCountry = { ...Omit(Country, 'name'), ...Record(LanguageCode, 'string') }

Someone using TypeScript can then validate the data they load (be it from JSON, CSV, XML, etc) like so:

import countries from 'world_countries_lists/data/countries/_combined/countries.json'
import { TranslatedCountry } from 'world_countries_lists'

countries.forEach((country: TranslatedCountry) => {
  // succeeds
  console.log(country.alpha2)
  console.log(country.ar)

  // fails in editor and during build time
  console.log(country.ca)
})
stefangabos commented 2 years ago

ok, i think i got it. but

why do you need to describe the shape of data in data/countries/*/* when you are only using the combined version? isn't import countries from 'world_countries_lists/data/countries/_combined/countries.json' enough to be able to access all data already and not having to have LanguageCode and also not needing to do any filtering on it?

also, in the pull request, you have export const TranslatedCountry = { ...Omit(Country, 'name'), ...Record(LanguageCode, 'string') } whereas in the example you have export const TranslatedCountry = { ...Omit(Country, 'name') & ...Record(LanguageCode, 'string') }

bfischer1121 commented 2 years ago

Ahh yea the first example is just trying to translate the TS syntax to JS equivalent to make sense of what line 41 is doing... TS's interface & interface syntax is analogous to JS's { ...object, ...object }, where they're combining 2 entities into 1. Sorry for confusion.

For Country and LanguageCode, I tried to be liberal in what's exposed to outside devs. I'm personally using the combined file but if devs import individual language files they'll need Country. Language is useful for validating the list of languages this library supports. For example, an outside app/lib might want to validate mapping between this library's languages and their own, where if a new version adds or removes a language it lets them know to update their code with a break in the contract/build.