Дані країн, мов та континентів у форматах ISO (столиці, валюти, назви рідною мовою, телефонні коди).
bun add countries-list Випадний список країн — найнудніше, що можна зробити, і у 2014-му я не міг знайти даних, щоб його нормально заповнити. В одних списках були англійські назви, але не було назв рідною мовою, в інших — коди ISO, але без телефонних; а де було все одразу, там воно лежало в багатомегабайтному файлі, зібраному для зовсім іншої задачі.
Тож я зібрав свій, і відтоді ідея не змінилася: один типізований запис на країну — код, назва, назва рідною мовою, столиця, валюта, телефонний код, континент — і нічого зайвого.
Приклади
import { continents, countries, getCountryCode, getEmojiFlag, languages } from 'countries-list'
countries.UA
// { name: 'Ukraine', native: 'Україна', phone: [380], continent: 'EU',
// capital: 'Kyiv', currency: ['UAH'], languages: ['uk'] }
getCountryCode('Ukraine') // 'UA'
getCountryCode('Україна') // 'UA' — рідні назви теж працюють
getEmojiFlag('UA') // '🇺🇦'
continents.EU // 'Europe'
languages.uk // { name: 'Ukrainian', native: 'Українська' }
Кожен код ISO 3166-1 і навіть трохи більше, жодних залежностей, типи в комплекті. countries. автодоповнюється в редакторі, а одрук у коді країни стає помилкою компіляції, а не undefined під час виконання — коди з форми чи API все одно варто перевіряти окремо.
Навіщо це
Проблема ніколи не була в назвах країн — їх вистачає всюди. Проблема в тому, що полю країни одночасно потрібні чотири різні речі: код, щоб зберегти, назва, щоб показати, прапор, щоб намалювати, і телефонний код для поля з номером. Зібрати це з трьох пакетів означало три набори ключів країн, які не збігаються між собою, плюс шар мапінгу, щоб їх помирити.
Коли всі поля лежать в одному записі, цілий клас таких помилок стає неможливим. Ідея не геніальна — мабуть, тому й досі корисна.
Чим відрізняється
У цій ніші є хороші набори даних, і вони розв’язують різні задачі:
| Пакет | Про що він |
|---|---|
countries-list | один типізований запис на країну — коди, рідні назви, столиця, валюта, телефон, континент |
i18n-iso-countries | назви країн, перекладені багатьма мовами |
world-countries | великий сирий набір даних — кордони, координати, альтернативні написання |
iso-3166-1 | конвертація кодів і принципово нічого більше |
Беріть i18n-iso-countries, якщо вам потрібна «Німеччина» японською — переклад назв за локалями це його основна робота, і робить він її краще. Беріть world-countries, якщо потрібна географія. Цей пакет покриває поля, які питає форма чи екран налаштувань, і на цьому все.
Чого немає в інших: ті самі дані потрапляють у PHP через Packagist і генеруються з того самого джерела.
composer require annexare/countries-list
Що всередині
Валюти — окремим імпортом
Повний ISO 4217 — назви, назви рідною мовою, символи, числові коди, розмінні одиниці — живе за власним підшляхом:
import { currencies, getCurrency, getCurrencyByNumeric } from 'countries-list/currencies'
currencies.UAH
// { name: 'Ukrainian Hryvnia', native: 'українська гривня', symbol: '₴',
// symbolNative: '₴', numeric: '980', decimals: 2 }
getCurrency('JPY') // decimals: 0 — у єни немає розмінної одиниці
getCurrencyByNumeric('840') // USD, для банківських запитів з числовими кодами
Набір валют перегенеровується з офіційного списку ISO 4217 та CLDR скриптом, а не правиться руками, тож оновити його після зміни валюти — це команда, а не вечір роботи.
Мінімальні мапи для простих випадків
Іноді потрібні два кілобайти, а не набір даних:
import countries2to3 from 'countries-list/minimal/countries.2to3.min.json'
import languageNames from 'countries-list/minimal/languages.native.min.json'
Формати поза JavaScript
Ті самі дані генеруються як JSON, CSV і SQL, тож щоб заповнити базу, не треба спершу писати скрипт імпорту. Файли лежать усередині npm-пакета — нічого додатково завантажувати не треба:
mysql app_db < node_modules/countries-list/data.sql
head -2 node_modules/countries-list/countries.csv
# "Code","Name","Native","Phone","Continent","Capital","Currency","Languages"
# "AC","Ascension Island","Ascension Island","247","Africa","Georgetown","SHP","en"
Нотатки про рішення
Валюти винесені окремо не випадково. Повна таблиця ISO 4217 з рідними назвами й символами — це відчутна кількість байтів, і більшість застосунків, яким потрібен список країн, ніколи її не торкається. Тримати її за countries-list/currencies, а не в кореневому експорті, залишає типовий випадок легким. Експорти через підшляхи — найдешевше рішення для розміру бандла з усіх можливих, і саме те, яке більшість наборів даних пропускає.
ISO 4217 взагалі не визначає символів. Він стандартизує коди, числові коди та розмінні одиниці; ₴ і ¥ беруться з Unicode CLDR — окремого джерела зі своїм циклом оновлень. Якщо у валюти немає власного латинського знаку, symbol відкочується до коду ISO, а локальний знак іде в symbolNative: CHF лишається CHF, а JPY дає ¥ для латинського інтерфейсу і ¥ для японського. Об’єднати symbol і symbolNative в одне поле було б простіше — і неправильно, бо інтерфейсу потрібні обидва варіанти.
getCountryCode шукає не лише за англійською назвою. Він знаходить країну і за рідною назвою («Україна» → UA), і за поширеними альтернативними написаннями на кшталт USA чи UK. Це один додатковий індекс у даних, який робить набір придатним для форм будь-якою мовою — і тихо прибирає цілу категорію костилів.
Третю версію я повністю переписав на TypeScript, ESM і монорепозиторій на Bun workspaces. Усе в dist генерується з типізованих джерел, включно з SQL, тож виправлення даних — це одна правка в одному місці, а не чотири файли, які треба тримати синхронними вручну.
Статус
Проєкт живий, розвивається та підтримується з 2014-го — у тому числі користувачами, адже код відкритий.