Changelog

View Source

All 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 by Cities.get/1, Cities.all/0 and Cities.for_country("PL", limit: :all), not by the default for_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 23 Translations locales. 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 through subdivision. Canada uses the full GeoNames country dump, including small populated localities. for_country/2 returns the 10 largest and still includes the capital when it ranks lower (limit: :all returns 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 by dev_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 2026b zone.tab), filterable via filter_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/3 with a :fallback locale 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 named Ky.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 of AL, CZ, LV); name now falls back to the English translation
  • Subdivision.unofficial_names was sometimes a string and sometimes a list; it is now always a list
  • Union data: asean gained TL; opec lost AO (left 2024) and AE (left 2026-05-01); eea no longer lists CH; african_union gained EH; mercosur gained BO
  • Currencies.format/3 printed "$-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 use XCG (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 canonical name
  • City generation fails before writing output when alternate-name archives are missing, and writes its report inside the output directory
  • Invalid city :limit values raise ArgumentError. The District of Columbia is not padded to ten entries, so for_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_member and eea_member are false (not nil) for non-members
  • Breaking: national_prefix / international_prefix are nil instead of the string "None" when a country has none
  • Breaking: filter_by/1,2, get_by/2 and exists?/2 raise ArgumentError for attributes that are not Country fields instead of silently matching nothing
  • Breaking: Currencies.format/3 raises ArgumentError for an invalid :symbol_position, and the sign placement changed (see above)
  • Lookup functions across BeamLabCountries, Unions, Currencies, Languages, Translations and Subdivisions return nil / false / [] for non-string input instead of raising FunctionClauseError
  • filter_by, get_by and exists? compare against attribute values normalized at compile time (about 150x faster); Languages structs 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/1 raised FunctionClauseError for countries without a currency_code (AN); it now returns nil
  • Subdivisions.all/1 returned 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 via exclude_patterns
  • international_prefix was parsed as the integer 9 for GL, NG and ZA (unquoted 009 / 09 in YAML); it is now always a string, and Country.t reflects that
  • Country data: AN gained currency_code: ANG; SL uses SLE (replaced SLL in 2022) and ST uses STN (replaced STD in 2018); IM / JE use GBP and TV uses AUD instead of the non-ISO IMP / JEP / TVD, so Currencies.for_country/1 resolves for every country; NP lists ur instead of the ISO 639-3 urd
  • .gitignore ignored countries-*.tar but the Hex tarball is named beamlab_countries-*.tar

Changed

  • Languages.all_codes/0 and Languages.all_locale_codes/0 now return sorted lists
  • Tests guard the data invariants above (every currency_code is a known currency, every language code is known, international_prefix is a string, subdivisions are sorted)
  • CHANGELOG.md is included in the generated docs

1.2.0 - 2026-08-14

Added

  • Estonian (et) country name translations — 16th locale for Translations.get_name/2 and get_all_names/1, sourced from CLDR (et/territories)
  • AN (Netherlands Antilles) translations for de, en, es, et, fr, ja, nl, and ru. CLDR dropped the retired code, so these are taken from priv/data/countries/AN.yaml (and Wikipedia titles for et / ru)

Changed

  • Translations.supported_locales/0 now returns 16 locales (et inserted in sort order)

1.1.0 - 2026-07-17

Fixed

  • Currencies.format/3 produced malformed output for negative amounts (e.g. "$-1,234..56") and dropped the sign for amounts between -1 and 0
  • Subdivisions.get/2 / Subdivisions.all/1 allowed path traversal via unsanitized country codes and could crash with BadMapError on non-map YAML files
  • Subdivisions lookups are now case-insensitive for both country codes and subdivision IDs, consistent with BeamLabCountries.get/1
  • Languages.get_locale/1 and Languages.valid_locale?/1 now normalize locale codes (e.g. "EN-us")
  • Languages.parse_locale/1 now 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/1 now also accepts an alpha2 code string
  • yaml_elixir is now a compile-time-only dependency (runtime: false)

Added

  • @spec annotations for all public functions; Subdivision.t type; required alpha2/alpha3/name in Country.t
  • Doctest coverage for Translations (new test module), Subdivisions, Language, and Locale
  • GitHub Actions CI running mix test and mix precommit

Removed

  • Legacy .travis.yml (pinned Elixir 1.9.1, incompatible with the required ~> 1.18)

1.0.8 - 2026-04-10

Added

  • short_name field — simplified country names for display (e.g. "United Kingdom" instead of "United Kingdom of Great Britain and Northern Ireland")
  • phone_prefix field — 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_locales populated 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-GB for Kenya, en: en-US for Jamaica)
    • Single-variant languages map to themselves (e.g., el: el for Greek, sw: sw for Swahili)
    • Only 3 countries remain without language_locales (AN, AQ, BV -- no spoken languages defined)
  • 5 new tests validating language_locales mappings, including a test that all locale references are valid

Fixed

  • Languages like Greek (el), Hausa (ha), Norwegian Bokmal (nb) were unreachable via Languages.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.Unions with functions: all/0, get/1, get!/1, for_country/1, codes_for_country/1, member?/2, member_countries/1, filter_by/2, exists?/1
    • BeamLabCountries.Union struct 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.Locale struct with 7 fields (code, base_code, region_code, name, native_name, flag, country_name)
    • New Languages functions: 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 Languages functions: countries_for_language/1, country_names_for_language/1, flags_for_language/1
  • Language struct - BeamLabCountries.Language struct with 4 fields (code, name, native_name, family)
  • New Languages functions - all/0 returns all languages as Language structs
  • eea_member field added to Country struct 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 from pk_countries)
  • Country data - 250 countries with 39 fields per country
    • BeamLabCountries module with functions: all/0, count/0, get/1, get!/1, get_by/2, get_by_alpha3/1, filter_by/2, exists?/2
    • BeamLabCountries.Country struct with fields including alpha2, alpha3, name, region, currency, eu_member, languages_official, languages_spoken, and more
  • Subdivisions - States/provinces for countries
  • Languages - ISO 639-1 language lookup (184 languages)
  • Translations - Country names in 15 languages (ar, de, en, es, fr, it, ja, ko, nl, pl, pt, ru, sv, uk, zh)
  • 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 yamerl to yaml_elixir for YAML parsing
  • get/1 now returns nil instead of raising (use get!/1 for raising behavior)