Intl в JavaScript: форматуємо дати, числа та списки
Вітаю! Мене звати Олексій, я Frontend Engineer. За останні роки фронтенд-розробка та користувацькі інтерфейси не просто перейшли на інший рівень, а у цілому змінили підхід до вебу та користування браузером. Ми заходимо у браузер не тільки для того, щоб почитати новини на статичній вебсторінці чи подивитись картинки з котиками.
Браузер та складні вебзастосунки стали постійними компаньйонами в роботі, проведенні операцій з грошима, документами, спілкуванні, заняттях спортом та творчості для людей по всьому світу.
Нам, розробникам, все більше для користувачів додатків треба показувати числа в різних форматах, монетарні значення з валютами, списки, дати, час тощо. Для того, щоб показувати інформацію консистентно, ми пишемо util-функції та застосовуємо їх до різних частин нашого застосунку. І ось тут починаються складнощі, тому що
- треба врахувати локалізацію;
- треба врахувати доступність;
- треба врахувати спеціальні знаки та формат даних специфічний для деяких країн;
- ⬆️ усе вищеперераховане ще треба протестувати та підтримувати.
Як результат, ми маємо складні функції, які треба тестувати, підтримувати, які часом важко змінити та не поламати, та які роблять наш бандл важче. Ми хочемо відмовитись від цього, але як?
І ось тут вступає в гру Intl-обʼєкт, який дозволяє у більшості випадків відмовитись від складних util-функцій, форматувати дані як нам треба і навіть бере на себе локалізацію та доступність.
У цій статті я хочу розглянути з вами основні та найбільш популярні конструктори обʼєкта Intl та їхні методи. Я впевнений, ви побачите багато цікавого, і, мабуть, навіть замислитеся над тим, щоб покращити свої робочі та власні проєкти.
Що таке обʼєкт Intl
Intl — це вбудований об’єкт, який виступає в ролі простору імен для Internationalization API, який забезпечує мовне порівняння рядків, форматування чисел, списків, дати та часу.
На відміну від глобальних обʼєктів, Intl це не конструктор, ви не можете використовувати його з оператором new. Усі його методи та проперті статичні (як у Math).
Далі перейдімо до тих самих основних конструкторів та їхніх методів.
Intl.NumberFormat — числа та монетарні значення
Окей, перейдемо до форматування чисел з прикладами. Для того, щоб застосувати базове форматування числа з опціями за замовчуванням та локаллю користувача, треба просто викликати метод format з числом в обʼєкта Intl.NumberFormat():
const number = 42500; console.log(new Intl.NumberFormat().format(number)); // '42,500' в локалі en-US
Це вже непогано для доволі великого числа випадків, але подивімось на опції. Доволі популярна опція maximumFractionDigits для обмеження кількості цифр дробової частини числа:
const number = 42500.678
console.log(
new Intl.NumberFormat('en-US', {maximumFractionDigits: 2}) .format(num)
) // 42500.68 Цікаво, що ця опція також правильно округлює числа, якщо число було б 42500.674 — результат отримали б 42500.67.
Щобільше, дуже часто нам треба «відрізати» непотрібні нулі у кінці числа і ми це також можемо зробити за допомогою trailingZeroDisplay:
const number = 42500.600
console.log(
new Intl.NumberFormat('en-US', {
maximumFractionDigits: 2,
trailingZeroDisplay: "stripIfInteger"
}).format(num)
)
// 42500.6
Як бачимо, опції можна групувати, головне правило — щоб вони не суперечили одне іншому :)
Окей, з цим розібрались, а що якщо ми розробляємо софт для інженерів та науковців і нам треба використовувати «інженерний» чи «науковий» формати чисел? Так, це також можна:
const number = 105_900;
console.log(
new Intl.NumberFormat('en-US', {notation: 'scientific'}).format(number)
)
// 1.059E5
console.log(
new Intl.NumberFormat('en-US', {notation: 'engineering'}).format(number)
)
// 105.9E3
Якщо ми розробляємо соціальну мережу, де у нас є підписники, лайки, коментарі і ми знаємо, що їх буде багато — ми хочемо зробити числа коротше, замість 150 000 показати 150k, замість 109 000 000 показати просто 109М. Це можна зробити за допомогою {notation: "compact"} та compactDisplay:
console.log(
new Intl.NumberFormat('en-US', {notation: 'compact'}).format(45_600)
)
// 46k
console.log(
new Intl.NumberFormat('en-US', {
notation: 'compact',
minimumFractionDigits: 1
}).format(45_600)
)
// 45.6K
console.log(
new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'long'
}).format(105_800_900)
)
// 106 million
console.log(
new Intl.NumberFormat('uk-UA', {
notation: 'compact',
compactDisplay: 'long',
minimumFractionDigits: 1,
maximumFractionDigits: 2
}).format(105_800_900)
)
// 105,8 мільйона (так, українська локаль також підтримується :))
Також можна використовувати одиниці виміру (метри, кілометри, літри, кілограми тощо). Це буде корисно якщо ви розробляєте софт, наприклад, для мережі заправок:
console.log(
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'liter',
unitDisplay: 'short'
}).format(21)
)
// 21 L
console.log(
new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'liter',
unitDisplay: 'narrow'
}).format(1500)
)
// 1500L
console.log(
new Intl.NumberFormat('uk-UA', {
style: 'unit',
unit: 'liter',
unitDisplay: 'long'
}).format(1500)
)
// 1 500 літрів
Далі поговоримо про гроші та монетарні значення, кількість яких треба відображати у багатьох інтерфейсах, особливо коли ти працюєш у фінтеху (передаю привіт усім моїм колегам).
Одразу скажу. Опції, які ми розглянули вище, також можна застосувати і до монетарних значень, поєднувати та отримувати ДУЖЕ гнучкі результати для різних потреб.
І тут все доволі просто: достатньо додати {style: "currency"} до вашого конфігу в Intl.NumberFormat та вказати валюту, яку ви хочете відобразити:
console.log(
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(17450),
);
// $17,450.00
Хочу звернути вашу увагу на те, що залежно від локалі знак валюти може знаходитись у різних місцях через місцеві стандарти.
Ось приклад для американської та німецької локалей:
console.log(
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(19_398)
);
// €19,398.00
console.log(
new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR'
}).format(19_398)
);
// 19.398,00 €
Далі — більше. Ви також можете вибрати, як ви хочете показувати конкретну валюту: за допомогою ISO-коду, символу чи повним імʼям за допомогою currencyDisplay:
console.log(
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'JPY',
currencyDisplay: 'code'
}).format(75860)
);
// JPY 75,860
console.log(
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'JPY',
currencyDisplay: 'symbol'
}).format(75860)
);
// ¥75,860
console.log(
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'JPY',
currencyDisplay: 'name'
}).format(75860)
);
// 75,860 Japanese yen
На цьому з Intl.NumberFormat будемо потроху завершувати. Я розглянув усі основні конфігурації, які вам можуть бути потрібні для взаємодії з числами та монетарними значеннями.
Але насправді загалом це далеко не все :) Якщо вам треба сконфігурувати дійсно щось «екзотичне» або специфічне до вашого продукту — ось тут ви можете знайти перелік та опис УСІХ можливих опцій для трансформації чисел.
А ми йдемо далі, і на черзі у нас.... дати.
Intl.DateTimeFormat — відображення дат
Зараз ми підійшли до теми, яка була є і буде актуальною увесь час існування вебу — дати. Вони потрібні майже у всіх інтерфейсах. До того — майже у всіх інтерфейсах вони повинні бути показані по різному, залежно від ситуацій та місця знаходження на сторінці
Щобільше, як я і казав раніше — це все потребує локалізації та формату відповідно до локації.
Отже, перейдемо до базового використання Intl.DateTimeFormat:
const date = new Date(); console.log(new Intl.DateTimeFormat().format(date)) // 12/20/2020
Окей, вже непогано, але хочеться більше інформації про дату. Розберемо параметр dateStyle. Він має декілька популярних «пресетів» для відображення дати, такі як "full", "long", "medium", i "short". Давайте на них подивимось:
const date = new Date();
console.log(
new Intl.DateTimeFormat('en-GB', {
dateStyle: 'full',
}).format(date),
);
// Wednesday, 15 May 2024
console.log(
new Intl.DateTimeFormat('uk-UA', {
dateStyle: 'long',
}).format(date),
);
// 15 травня 2024 р.
console.log(
new Intl.DateTimeFormat('uk-UA', {
dateStyle: 'medium',
}).format(date),
);
// 15 трав. 2024 р.
console.log(
new Intl.DateTimeFormat('uk-UA', {
dateStyle: 'short',
}).format(date),
);
// 12/20/2020 Вже класно, додаймо ще час — за допомогою timeStyle. Він може приймати ті ж самі значення — "full", "long", "medium", i "short".
const date = new Date();
console.log(
new Intl.DateTimeFormat('uk-UA', {
dateStyle: 'full',
timeStyle: 'full',
}).format(date),
);
// середа, 15 травня 2024 р. о 00:21:42 за східноєвропейським літнім часом
console.log(
new Intl.DateTimeFormat('uk-UA', {
dateStyle: 'long',
timeStyle: 'long',
}).format(date),
);
// 15 травня 2024 р. о 00:22:24 GMT+3
console.log(
new Intl.DateTimeFormat('uk-UA', {
dateStyle: 'medium',
timeStyle: 'medium',
}).format(date),
);
// 15 трав. 2024 р., 00:22:52
console.log(
new Intl.DateTimeFormat('uk-UA', {
dateStyle: 'short',
timeStyle: 'short',
}).format(date),
);
// 15.05.24, 00:23 І цього буде достатньо для деяких ситуацій. До речі, якщо маєте змогу — ознайомте, будь ласка, з цими форматами ваших дизайнерів. Мабуть, для деяких випадків саме на вашому проєкті вони підійдуть, а реалізувати буде дуже просто.
Але... для багатьох ситуації усі вищенаведені формати можуть не підходити, і треба відображати дати дуже «по-особливому», чи додати/прибрати якісь додаткові деталі. І це дійсно можна законфігурувати за допомогою більш деталізованих опції, зараз все розкажу і покажу.
Усі дати можна поділити на компоненти: місяці, роки, дні, години, хвилини, секунди тощо. Intl.DateTimeFormat дає нам можливості додати конфіг для відображення кожного компонента окремо. Дні тижня, роки, місяці, ери та таймзони можна показувати у скорочених, розгорнутих чи напівскорочених варіантах. Усі опції-варіанти для кожного компонента дат можна знайти тут, а поєднань цих варіантів може бути тисячі.... Але я вам все ж покажу декілька гарних:
const date = new Date();
console.log(
new Intl.DateTimeFormat('en-EN', {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: false,
}).format(date)
) // May 16, 21:31
console.log(
new Intl.DateTimeFormat('en-EN', {
weekday: 'short',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
year: 'numeric',
hour12: false,
}).format(date);
)
// Thu, May 16, 2024, 21:34
console.log(
new Intl.DateTimeFormat('en-EN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true,
}).format(date);
)
// May 16, 2024 at 09:36:57 PM
console.log(
new Intl.DateTimeFormat('en-EN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short',
}).format(date); ) // May 16, 2024 at 09:39 PM GMT+3
console.log(
new Intl.DateTimeFormat('en-EN', {
year: 'numeric',
month: 'long',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
timeZone: 'UTC',
timeZoneName: 'short'
}).format(date);
) // May 16, 2024 at 06:41 PM UTC
console.log(
new Intl.DateTimeFormat('en-EN', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date);
)
// Thursday, May 16, 2024
console.log(
new Intl.DateTimeFormat('uk-UA', {
year: 'numeric',
month: 'short',
day: '2-digit',
era: 'long'
}).format(date);
)
// 16 трав. 2024 р. нашої ери

Конфігурувати
Intl.DateTimeFormatможна за допомогою ChatGPT. Просто скопіюйте дату з вашого дизайну, а штучний інтелект видасть вам обʼєкт з потрібними опціями дляIntl.DateTimeFormat.
Як бачимо, потенціал просто величезний і я дуже сподіваюсь, що ви замислитесь над використанням саме Intl.DateTimeFormat для того, щоб показувати дати у вашому вебзастосунку.
Єдине, що хотів би додати — будь ласка, узгодьте з дизайнером та протестуйте, як один і той самий формат дати буде виглядати у різних локалях (як мінімум у тих, на які розрахований ваш застосунок/вебсайт), тому що довжина рядка з назвою місяців, днів може відрізнятись залежно від мови.
Окей, з датами та часом нібито розібрались, на черзі у нас відображення списків.
Intl.ListFormat зробить ваші списки зручними
Ймовірно, ви бачили у соціальних мережах під постами примітки, як-от «Сподобалось Марії, Миколаю та 45 іншим» чи на сайтах послуг під конкретною пропозицією «Компанія_1, Компанія_2 та 67 інших компаній» вже користуються нашими послугами. Чи, мабуть, примітку до select-інпуту на кшталт «Виберіть товар_1, товар_2 чи товар_3».
Зробити подібний перелік речей, послуг, варіантів вибору нам допоможе саме Intl.ListFormat. За допомогою цього конструктора ми зможемо відображати списки у більш зручній формі для користувачів зі всього світу. Щобільше, автоматично врахуємо місцеві правила оформлення списків та розділові знаки поміж частинами.
Окей, по традиції перейдемо до базового використання Intl.ListFormat:
const phones = ['iPhone', 'Samsung', 'Xiaomi'];
console.log(
new Intl.ListFormat('en-US').format(phones)
)
// iPhone, Samsung, and Xiaomi Як бачимо, буквально одним рядком коду наш список вже перетворився на щось дійсно user-friendly і це сміливо можна додавати на сторінку вебзастосунка.
Але це ще не все. Давайте більш детально розглянемо доступні опції і як ми можемо управляти відображенням нашого списку залежно від контексту та ситуації.
Перш за все подивимось на параметр style. Він відповідає за те, чи будуть у представленні списку сепаратори, союзні слова тощо. Перейдімо до прикладів:
const phones = ['iPhone', 'Samsung', 'Xiaomi'];
console.log(
new Intl.ListFormat('en-US', {
style: 'long'
}).format(phones)
)
// iPhone, Samsung, and Xiaomi
console.log(
new Intl.ListFormat('en-US', {
style: 'short'
}).format(phones)
)
// iPhone, Samsung, & Xiaomi
// (символ замість слова "and", більш "по-математичному")
console.log(
new Intl.ListFormat('en-US', {
style: 'narrow'
}).format(phones)
)
// iPhone, Samsung, Xiaomi (просте розділення комами) Добре, тепер ми знаємо, як форматувати всі ці значення, aле ще може трапитись, коли також потрібен тип групування обʼєктів. Цілком може бути, що ми хочемо, щоб в умовному списку користувач вибрав декілька елементів. А може бути й така ситуація, коли треба вибрати тільки щось одне — і тоді нам треба обирати різне групування та різні сполучники.
І ми це можемо зробити за допомогою параметра type:
const phones = ['iPhone', 'Samsung', 'Xiaomi'];
console.log(
new Intl.ListFormat('uk-UA', {
style: 'long',
type: 'conjunction'
}).format(phones)
)
// iPhone, Samsung і Xiaomi (і - єднальний сполучник)
console.log(
new Intl.ListFormat('uk-UA', {
style: 'long',
type: 'disjunction'
}).format(phones)
)
// iPhone, Samsung або Xiaomi (або - розділовий, сполучник дизʼюнкції) Загалом, все. Ось так, просто передавши пару полів в конфіг, ви можете показувати користувачам списки, робити ваші компоненти, форми та тултіпи більш зрозумілими та приємними для сприйняття.
Зараз я хочу відповісти на два запитання, які мені задають частіше всього після того, як я розповідаю про Intl. Я впевнений, що ці запитання будуть в коментарях і хочу відповісти на них завчасно :)
Я використовую Intl.NumberFormat/DateTimeFormat/ListFormat і не можу створити варіант, який мені підходить. Що робити?
Як на мене, тут є два основних варіанти розв’язання цієї проблеми.
- Поговорити з дизайнером/PM. Чи дійсно треба робити якийсь дуже кастомний формат відображення чисел, дат, списків і чи дійсно це буде корисно для користувача? Разом з цим, було б добре, якщо ви запропонуєте свої варіанти, базуючись на конфігурації
Intl, і підкріпите це аргументами про локалізацію, легку підтримку такого рішення тощо. - Якщо все ж таки треба щось кастомне — не обовʼязково писати все форматування з нуля. Усі
Intl-конструктори мають методformatToParts, який допоможе вам створити щось кастомне простішим шляхом. Давайте я це продемонструю:
const phones = ['iPhone', 'Samsung', 'Xiaomi'];
console.log(
new Intl.ListFormat('uk-UA', {
style: 'long',
type: 'conjunction'
}).formatToParts(phones)
)
/*
[
{ "type": "element", "value": "iPhone" },
{ "type": "literal", "value": ", " },
{ "type": "element", "value": "Samsung" },
{ "type": "literal", "value": " і " },
{ "type": "element", "value": "Xiaomi" }
]
*/Як результат, ми будемо мати не форматований рядок, а масив обʼєктів-складових елементів цього рядка. А ще помічені окремо саме значення, а також розділові знаки властивістю type. Далі ви можете пройтись по цьому обʼєкту, додати потрібну вам інформацію, а потім склеїти в рядок і відобразити у інтерфейсі.
Що там з підтримкою браузерів, чи підтримується Intl в сучасних і не дуже браузерах?
По цьому питанню можу дати три посилання:
Можете побачити, що підтримка дуже і дуже широка, а Intl.NumberFormat і Intl.DateTimeFormat навіть підтримуються в Internet Explorer.
Висновки
Ви тепер можете сміливо використовувати Intl у своїх проєктах. Впевнений, що в цій статті ви знайшли щось для себе, або, як мінімум, подумали над тим, як покращити свої та робочі проєкти. Бажаю наснаги та професійного розвитку!
Дуже запрошую завітати на мій YouTube-канал, де я розповідаю про фронтенд-розробку, а також розв’язую різні задачі для підготовки до співбесід. Буду радий кожному. Також ось мій LinkedIn.
Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.
4 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів