Changelog
View SourceAll notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
2.0.1 - 2026-10-15
Added
- Suwałki (Poland) in
Cities. It ranks outside the ten largest, so it is returned byCities.get/1,Cities.all/0andCities.for_country("PL", limit: :all), not by the defaultfor_country("PL")
2.0.0 - 2026-10-15
Contains several behaviour changes (marked Breaking).
Added
BeamLabCountries.Cities/City: 2,621 major cities across 244 countries, with population, coordinates, time zone, native name and translations in the 23Translationslocales. Each country contributes its 10 largest places plus the capital. The United States (501 places) and Canada (131) also include at least 10 populated places per state, province or territory, linked throughsubdivision. Canada uses the full GeoNames country dump, including small populated localities.for_country/2returns the 10 largest and still includes the capital when it ranks lower (limit: :allreturns the rest). API:all/0,count/0,get/1,for_country/2,for_subdivision/3,capital/1,get_name/2. Data from GeoNames (CC BY 4.0), generated bydev_docs/cities/build_cities.py- Currency records for Caribbean guilder (
XCG) and Zimbabwe Gold (ZWG), bringing the catalog to 157 entries. Historical currencies remain available (CBCS, Reserve Bank of Zimbabwe) Country.timezones— IANA time zone identifiers for every country (from tzdb 2026bzone.tab), filterable viafilter_by(:timezones, "Europe/Tallinn")- 7 country-name locales for
Translations:cs,hi,id,ro,th,tr,vi(CLDR) — 23 locales in total - 16 unions:
brics,cis,oic,pacific_alliance,commonwealth,arab_league,gcc,schengen,eurozone,sco,caricom,ecowas,sadc,eac,council_of_europe,cptpp— 29 in total Translations.get_name/3with a:fallbacklocale option; regional locales ("de-AT","pt_BR") are reduced to their language- Netherlands Antilles (
AN) is complete: modern data format, languages, geo, week start, translations in all locales
Fixed
- Cayman Islands (
KY) and Tajikistan (TJ) had no subdivisions: their data files were namedKy.yaml/Tj.yaml. The build now fails for subdivision files not named after an uppercase alpha2 code - 1,069 subdivisions had
name: nil(e.g. all ofAL,CZ,LV);namenow falls back to the English translation Subdivision.unofficial_nameswas sometimes a string and sometimes a list; it is now always a list- Union data:
aseangainedTL;opeclostAO(left 2024) andAE(left 2026-05-01);eeano longer listsCH;african_uniongainedEH;mercosurgainedBO Currencies.format/3printed"$-1,234.56"and"$-0.00"; the sign now precedes the symbol ("-$1,234.56") and is dropped when the amount rounds to zero. Integer amounts keep their full precision, and float fractions are no longer built by subtracting the integer part- Switzerland's country-level EEA flag now agrees with the union data (Swiss government)
- Bulgaria and Croatia use
EUR, matching their euro area membership (ECB: Bulgaria, ECB: Croatia). Curaçao and Sint Maarten useXCG(CBCS) - City name selection prefers a flagged short name over an unpreferred alternative, rejects preferred names in the wrong script, and tries fallback language tags when an earlier tag has no usable name. Nantes has a Ukrainian name (
Нант). English lookups return the canonicalname - City generation fails before writing output when alternate-name archives are missing, and writes its report inside the output directory
- Invalid city
:limitvalues raiseArgumentError. The District of Columbia is not padded to ten entries, sofor_subdivision("US", "DC")is Washington D.C. rather than a list of neighbourhoods - New locale files, subdivision files or city files are picked up by a plain
mix compile(previously only edits to existing files triggered recompilation)
Changed
- Breaking:
eu_memberandeea_memberarefalse(notnil) for non-members - Breaking:
national_prefix/international_prefixarenilinstead of the string"None"when a country has none - Breaking:
filter_by/1,2,get_by/2andexists?/2raiseArgumentErrorfor attributes that are notCountryfields instead of silently matching nothing - Breaking:
Currencies.format/3raisesArgumentErrorfor an invalid:symbol_position, and the sign placement changed (see above) - Lookup functions across
BeamLabCountries,Unions,Currencies,Languages,TranslationsandSubdivisionsreturnnil/false/[]for non-string input instead of raisingFunctionClauseError filter_by,get_byandexists?compare against attribute values normalized at compile time (about 150x faster);Languagesstructs and the language-to-countries index are also built at compile time- Docs: corrected field counts and the English-speaking-countries count, documented time zones and the new error behaviour
1.2.1 - 2026-09-13
Fixed
Currencies.for_country/1raisedFunctionClauseErrorfor countries without acurrency_code(AN); it now returnsnilSubdivisions.all/1returned subdivisions in arbitrary order for countries with more than 32 entries (e.g.US); results are now sorted by subdivision ID- The Hex package included the local dialyzer PLT (
priv/plts/, ~3 MB); it is now excluded viaexclude_patterns international_prefixwas parsed as the integer9forGL,NGandZA(unquoted009/09in YAML); it is now always a string, andCountry.treflects that- Country data:
ANgainedcurrency_code: ANG;SLusesSLE(replacedSLLin 2022) andSTusesSTN(replacedSTDin 2018);IM/JEuseGBPandTVusesAUDinstead of the non-ISOIMP/JEP/TVD, soCurrencies.for_country/1resolves for every country;NPlistsurinstead of the ISO 639-3urd .gitignoreignoredcountries-*.tarbut the Hex tarball is namedbeamlab_countries-*.tar
Changed
Languages.all_codes/0andLanguages.all_locale_codes/0now return sorted lists- Tests guard the data invariants above (every
currency_codeis a known currency, every language code is known,international_prefixis a string, subdivisions are sorted) CHANGELOG.mdis included in the generated docs
1.2.0 - 2026-08-14
Added
- Estonian (
et) country name translations — 16th locale forTranslations.get_name/2andget_all_names/1, sourced from CLDR (et/territories) AN(Netherlands Antilles) translations forde,en,es,et,fr,ja,nl, andru. CLDR dropped the retired code, so these are taken frompriv/data/countries/AN.yaml(and Wikipedia titles foret/ru)
Changed
Translations.supported_locales/0now returns 16 locales (etinserted in sort order)
1.1.0 - 2026-07-17
Fixed
Currencies.format/3produced malformed output for negative amounts (e.g."$-1,234..56") and dropped the sign for amounts between -1 and 0Subdivisions.get/2/Subdivisions.all/1allowed path traversal via unsanitized country codes and could crash withBadMapErroron non-map YAML filesSubdivisionslookups are now case-insensitive for both country codes and subdivision IDs, consistent withBeamLabCountries.get/1Languages.get_locale/1andLanguages.valid_locale?/1now normalize locale codes (e.g."EN-us")Languages.parse_locale/1now upcases the region part (e.g."EN-us"->{"en", "US"})- Country, union, and subdivision data files are now tracked via
@external_resource, so editing them triggers recompilation
Changed
- Subdivision data is loaded at compile time instead of being read and parsed from disk on every call;
Subdivisions.all/1now also accepts an alpha2 code string yaml_elixiris now a compile-time-only dependency (runtime: false)
Added
@specannotations for all public functions;Subdivision.ttype; requiredalpha2/alpha3/nameinCountry.t- Doctest coverage for
Translations(new test module),Subdivisions,Language, andLocale - GitHub Actions CI running
mix testandmix precommit
Removed
- Legacy
.travis.yml(pinned Elixir 1.9.1, incompatible with the required~> 1.18)
1.0.8 - 2026-04-10
Added
short_namefield — simplified country names for display (e.g. "United Kingdom" instead of "United Kingdom of Great Britain and Northern Ireland")phone_prefixfield — international calling prefix with "+" (e.g. "+372" for Estonia)
Changed
- Updated deps (credo, ex_doc, yaml_elixir)
[1.0.7] - 2026-02-16
- Complete locale and language_locales coverage across all countries
Added
- 54 new locale entries in
locales.json(86 -> 140 total) for spoken languages that previously had no locale mapping (Greek, Hausa, Zulu, Swahili, Norwegian Bokmal/Nynorsk, Afrikaans, and 48 others) language_localespopulated for 247 countries -- maps every spoken language that has a locale entry to its correct regional variant- Multi-variant languages (en, fr, de, es, pt, zh) resolve to the appropriate regional variant based on geography (e.g.,
en: en-GBfor Kenya,en: en-USfor Jamaica) - Single-variant languages map to themselves (e.g.,
el: elfor Greek,sw: swfor Swahili) - Only 3 countries remain without
language_locales(AN, AQ, BV -- no spoken languages defined)
- Multi-variant languages (en, fr, de, es, pt, zh) resolve to the appropriate regional variant based on geography (e.g.,
- 5 new tests validating
language_localesmappings, including a test that all locale references are valid
Fixed
- Languages like Greek (
el), Hausa (ha), Norwegian Bokmal (nb) were unreachable viaLanguages.get_locale/1-- now return proper Locale structs - Countries speaking only single-variant languages (e.g., Afghanistan, Mongolia, Iran, Georgia) previously had nil
language_locales-- now fully mapped
[1.0.6] - 2025-12-29
- Subdivisons added to countries
[1.0.5] - 2025-12-17
- Update VAT rates for Estonia, Finland, Slovakia (2025)
- Fix language_locales not being loaded from YAML files
[1.0.4] - 2025-12-17
- Add language_locales field for dialect/locale specification
Added new language_locales field to Country struct and YAML files that maps base language codes to their specific regional variants. This allows applications to know which dialect is used in each country (e.g., Estonia uses en-GB, not en-US).
[1.0.3] - 2025-12-16
- Added continent, region & subregion
- Expand languages_spoken to include widely used languages
Updated ~70 country YAML files to include commonly spoken languages beyond just official languages. Added languages used in business, education, tourism, and by significant minority populations.
Europe
Baltic States
- Estonia (EE): Russian (ru), English (en)
- Latvia (LV): Russian (ru), English (en)
- Lithuania (LT): Russian (ru), English (en), Polish (pl)
Eastern Europe
- Ukraine (UA): Russian (ru), English (en)
- Belarus (BY): English (en)
- Moldova (MD): Russian (ru), Ukrainian (uk), English (en)
- Poland (PL): English (en), German (de)
- Czech Republic (CZ): English (en), German (de)
- Slovakia (SK): Czech (cs), Hungarian (hu), English (en)
- Hungary (HU): English (en), German (de)
- Romania (RO): Hungarian (hu), English (en), French (fr)
- Bulgaria (BG): Turkish (tr), English (en), Russian (ru)
- Russia (RU): English (en), Tatar (tt), Ukrainian (uk)
Nordic Countries
- Sweden (SE): English (en), Finnish (fi)
- Norway (NO): English (en)
- Denmark (DK): English (en), German (de)
- Finland (FI): English (en), Russian (ru)
- Iceland (IS): English (en), Danish (da)
- Faroe Islands (FO): Danish (da), English (en)
- Greenland (GL): Danish (da), English (en)
Western Europe
- Germany (DE): English (en), Turkish (tr), Russian (ru)
- France (FR): English (en), Arabic (ar), German (de)
- Netherlands (NL): English (en), German (de), French (fr)
- Belgium (BE): English (en)
- Austria (AT): English (en), Turkish (tr), Croatian (hr)
- Switzerland (CH): Romansh (rm), English (en)
- Luxembourg (LU): English (en), Portuguese (pt)
- Liechtenstein (LI): English (en)
- Monaco (MC): Italian (it), English (en)
Southern Europe
- Spain (ES): Catalan (ca), Galician (gl), Basque (eu), English (en)
- Italy (IT): English (en), German (de), French (fr)
- Portugal (PT): English (en), Spanish (es), French (fr)
- Greece (GR): English (en), French (fr), German (de)
- Croatia (HR): English (en), German (de), Italian (it)
- Slovenia (SI): English (en), German (de), Croatian (hr), Italian (it)
- Malta (MT): Italian (it)
- Andorra (AD): Spanish (es), French (fr), Portuguese (pt)
- San Marino (SM): English (en)
- Gibraltar (GI): Spanish (es)
Balkans
- Serbia (RS): Hungarian (hu), English (en), Romanian (ro)
- Bosnia and Herzegovina (BA): English (en)
- Montenegro (ME): English (en)
- North Macedonia (MK): Albanian (sq), Turkish (tr), English (en)
- Albania (AL): English (en), Italian (it), Greek (el)
British Isles & Channel Islands
- United Kingdom (GB): Welsh (cy), Scottish Gaelic (gd), Polish (pl)
- Ireland (IE): Polish (pl)
- Jersey (JE): Portuguese (pt)
- Guernsey (GG): Portuguese (pt)
Other European
- Turkey (TR): Kurdish (ku), English (en), Arabic (ar)
- Cyprus (CY): English (en)
- Vatican (VA): English (en), German (de), French (fr)
Asia
East Asia
- Japan (JP): English (en)
- China (CN): English (en)
- South Korea (KR): English (en)
- Taiwan (TW): English (en)
Southeast Asia
- Singapore (SG): Chinese (zh)
- Malaysia (MY): Chinese (zh), Tamil (ta)
- Indonesia (ID): Javanese (jv), English (en)
- Philippines (PH): Cebuano (ceb)
- Thailand (TH): English (en), Chinese (zh)
- Vietnam (VN): English (en), Chinese (zh)
- Cambodia (KH): English (en), French (fr)
- Laos (LA): English (en), French (fr)
- Myanmar (MM): English (en)
South Asia
- India (IN): Bengali (bn), Telugu (te), Tamil (ta), Marathi (mr)
- Pakistan (PK): Punjabi (pa), Sindhi (sd)
- Bangladesh (BD): English (en)
- Nepal (NP): English (en)
- Sri Lanka (LK): English (en)
Central Asia
- Kazakhstan (KZ): English (en)
- Uzbekistan (UZ): English (en)
Middle East
- United Arab Emirates (AE): English (en), Hindi (hi), Urdu (ur)
- Saudi Arabia (SA): English (en)
- Israel (IL): English (en), Russian (ru)
- Qatar (QA): English (en)
- Kuwait (KW): English (en)
- Bahrain (BH): English (en)
- Oman (OM): English (en)
- Jordan (JO): English (en)
- Lebanon (LB): English (en)
Americas
North America
- United States (US): Spanish (es), Chinese (zh)
- Canada (CA): Chinese (zh), Punjabi (pa)
- Mexico (MX): English (en)
South America
- Brazil (BR): English (en), Spanish (es)
- Argentina (AR): English (en)
- Colombia (CO): English (en)
- Chile (CL): English (en)
- Peru (PE): Quechua (qu), Aymara (ay), English (en)
Oceania
- Australia (AU): Chinese (zh), Arabic (ar), Vietnamese (vi)
- New Zealand (NZ): Maori (mi), Chinese (zh)
Africa
- Egypt (EG): English (en), French (fr)
- Nigeria (NG): Hausa (ha), Yoruba (yo), Igbo (ig)
[1.0.2] - 2025-12-15
- Fix VAT rates being displayed as charlists instead of integer lists when single-digit reduced rates are parsed from YAML.
1.0.1 - 2025-12-12
Added
- Unions module - Query international organizations (EU, NATO, G7, G20, ASEAN, OPEC, OECD, APEC, Mercosur, USMCA, African Union, EEA, EFTA)
BeamLabCountries.Unionswith functions:all/0,get/1,get!/1,for_country/1,codes_for_country/1,member?/2,member_countries/1,filter_by/2,exists?/1BeamLabCountries.Unionstruct with 8 fields (code, name, type, founded, headquarters, website, wikipedia, members)
- Locales support - Regional language variants (e.g., "en-US", "es-MX", "pt-BR")
BeamLabCountries.Localestruct with 7 fields (code, base_code, region_code, name, native_name, flag, country_name)- New
Languagesfunctions:get_locale/1,all_locales/0,all_locale_codes/0,locale_count/0,locales_for_language/1,valid_locale?/1,parse_locale/1 - 85 locales included
- Country-language associations - Find countries by spoken language
- New
Languagesfunctions:countries_for_language/1,country_names_for_language/1,flags_for_language/1
- New
- Language struct -
BeamLabCountries.Languagestruct with 4 fields (code, name, native_name, family) - New Languages functions -
all/0returns all languages asLanguagestructs eea_memberfield added toCountrystruct for EEA membership status- Documentation section in README with correct HexDocs links
Fixed
- Wikipedia URL in package metadata
1.0.0 - 2025-12-10
Added
- Initial release as
beamlab_countries(renamed frompk_countries) - Country data - 250 countries with 39 fields per country
BeamLabCountriesmodule with functions:all/0,count/0,get/1,get!/1,get_by/2,get_by_alpha3/1,filter_by/2,exists?/2BeamLabCountries.Countrystruct with fields including alpha2, alpha3, name, region, currency, eu_member, languages_official, languages_spoken, and more
- Subdivisions - States/provinces for countries
BeamLabCountries.Subdivisionsmodule withall/1BeamLabCountries.Subdivisionstruct with 5 fields (id, name, unofficial_names, translations, geo)
- Languages - ISO 639-1 language lookup (184 languages)
BeamLabCountries.Languagesmodule with functions:get_name/1,get_native_name/1,get/1,all_codes/0,count/0,valid?/1
- Translations - Country names in 15 languages (ar, de, en, es, fr, it, ja, ko, nl, pl, pt, ru, sv, uk, zh)
BeamLabCountries.Translationsmodule with functions:get_name/2,get_all_names/1,supported_locales/0,locale_supported?/1
- Compile-time data loading for fast runtime lookups
- O(1) lookups for alpha2 and alpha3 codes via pre-built maps
- Requires Elixir 1.18+
Changed
- Migrated from
yamerltoyaml_elixirfor YAML parsing get/1now returnsnilinstead of raising (useget!/1for raising behavior)