← JavaScript/Intl: интернационализация в JS#138 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Intl: интернационализация в JS

Интернет-магазин выходит на рынки России, Германии и США. Цена 1500 должна отображаться как "1 500 ₽", "1.500 €" и "$1,500" — в зависимости от локали. Дата "следующая неделя" по-русски звучит "через 7 дней", а по-немецки — "in 7 Tagen". Всё это решает Intl API — без сторонних библиотек.

Что решает Intl

Intl (Internationalization API) — встроенный набор объектов для форматирования чисел, валют, дат и строк с учётом локали. До Intl для этого нужны были библиотеки вроде moment.js или numeral.js весом по 100+ KB.

На основе предыдущих уроков

  • Date: объект даты, который форматирует Intl.DateTimeFormat
  • числа: числа, которые форматирует Intl.NumberFormat
  • строки: Intl.Collator для правильной сортировки строк
  • Intl.NumberFormat — числа, валюты, проценты

    // Простое форматирование числа
    const num = new Intl.NumberFormat('ru-RU').format(1234567.89)
    console.log(num)  // '1 234 567,89'
    
    // Валюта
    const price = new Intl.NumberFormat('ru-RU', {
      style: 'currency',
      currency: 'RUB',
    }).format(89990)
    console.log(price)  // '89 990,00 ₽'
    
    // Доллары
    const usd = new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD',
    }).format(89990)
    console.log(usd)  // '$89,990.00'
    
    // Проценты
    const pct = new Intl.NumberFormat('ru-RU', {
      style: 'percent',
      maximumFractionDigits: 1,
    }).format(0.1567)
    console.log(pct)  // '15,7%'
    
    // Компактный формат
    const compact = new Intl.NumberFormat('ru-RU', {
      notation: 'compact',
    }).format(1500000)
    console.log(compact)  // '1,5 млн'

    Intl.DateTimeFormat — форматирование дат

    const date = new Date('2026-03-04T15:30:00')
    
    // Полная дата по-русски
    const full = new Intl.DateTimeFormat('ru-RU', {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      weekday: 'long',
    }).format(date)
    console.log(full)  // 'среда, 4 марта 2026 г.'
    
    // Краткий формат
    const short = new Intl.DateTimeFormat('ru-RU').format(date)
    console.log(short)  // '04.03.2026'
    
    // Дата и время
    const datetime = new Intl.DateTimeFormat('ru-RU', {
      dateStyle: 'short',
      timeStyle: 'short',
    }).format(date)
    console.log(datetime)  // '04.03.2026, 15:30'
    
    // Английский формат
    const enDate = new Intl.DateTimeFormat('en-US', {
      dateStyle: 'full',
    }).format(date)
    console.log(enDate)  // 'Wednesday, March 4, 2026'

    Intl.RelativeTimeFormat — "5 минут назад"

    const rtf = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
    
    console.log(rtf.format(-5, 'minute'))   // '5 минут назад'
    console.log(rtf.format(-1, 'day'))      // 'вчера'
    console.log(rtf.format(3, 'day'))       // 'через 3 дня'
    console.log(rtf.format(-2, 'week'))     // '2 недели назад'
    console.log(rtf.format(1, 'month'))     // 'в следующем месяце'
    console.log(rtf.format(-1, 'year'))     // 'в прошлом году'
    
    // Функция "время назад"
    function timeAgo(date) {
      const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
      const rtf = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
    
      if (Math.abs(seconds) < 60)   return rtf.format(-seconds, 'second')
      if (Math.abs(seconds) < 3600) return rtf.format(-Math.floor(seconds / 60), 'minute')
      if (Math.abs(seconds) < 86400) return rtf.format(-Math.floor(seconds / 3600), 'hour')
      return rtf.format(-Math.floor(seconds / 86400), 'day')
    }

    Intl.Collator — сортировка с учётом локали

    // Без Collator — неправильная сортировка кириллицы
    const names = ['Яков', 'Алексей', 'Борис', 'Анна', 'Ёж']
    console.log(names.sort())
    // ['Алексей', 'Анна', 'Борис', 'Ёж', 'Яков'] — Ё может попасть не туда!
    
    // С Collator — корректно
    const collator = new Intl.Collator('ru-RU')
    console.log(names.sort(collator.compare))
    // ['Алексей', 'Анна', 'Борис', 'Ёж', 'Яков'] — правильный порядок
    
    // Сортировка нечувствительная к регистру
    const ci = new Intl.Collator('ru-RU', { sensitivity: 'base' })
    console.log(['Борис', 'анна', 'Алексей'].sort(ci.compare))
    // ['Алексей', 'анна', 'Борис']

    Intl.PluralRules — правила множественного числа

    const pr = new Intl.PluralRules('ru-RU')
    
    // Определяет форму слова
    function formatItems(count) {
      const rule = pr.select(count)
      const forms = {
        one: 'товар',    // 1 товар
        few: 'товара',   // 2, 3, 4 товара
        many: 'товаров', // 5, 6, 11 товаров
        other: 'товаров',
      }
      return `${count} ${forms[rule]}`
    }
    
    console.log(formatItems(1))   // '1 товар'
    console.log(formatItems(3))   // '3 товара'
    console.log(formatItems(5))   // '5 товаров'
    console.log(formatItems(11))  // '11 товаров'
    console.log(formatItems(21))  // '21 товар'

    Типичные ошибки

    Ошибка 1: создают новый Intl-объект при каждом вызове

    // Сломано: создание Intl.NumberFormat дорогостоящая операция
    function formatPrice(price) {
      return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB' }).format(price)
      // ^ каждый раз создаётся новый объект
    }
    
    // Исправлено: создать один раз
    const priceFormatter = new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB' })
    const formatPrice = (price) => priceFormatter.format(price)

    Ошибка 2: сортировка строк без Collator

    // Сломано: стандартный sort некорректно сортирует кириллицу
    const names = ['Яша', 'Анна', 'Ёж', 'Борис']
    names.sort()  // ['Анна', 'Борис', 'Ёж', 'Яша'] — Ё может попасть не туда!
    
    // Исправлено:
    const collator = new Intl.Collator('ru-RU')
    names.sort(collator.compare)  // корректный порядок

    Ошибка 3: используют toLocaleDateString() вместо Intl.DateTimeFormat

    // Нестабильно: поведение зависит от системных настроек браузера
    date.toLocaleDateString()  // разный результат на разных машинах
    
    // Исправлено: явная локаль
    new Intl.DateTimeFormat('ru-RU', { dateStyle: 'short' }).format(date)
    // '04.03.2026' — одинаково везде

    В реальных проектах

  • E-commerce: цены форматируются через Intl.NumberFormat с валютой страны пользователя
  • Соцсети: "5 минут назад" — Intl.RelativeTimeFormat с автоматическим обновлением
  • Таблицы и отчёты: сортировка имён через Intl.Collator с учётом диакритики
  • Уведомления: "У вас 1 сообщение / 5 сообщений" — Intl.PluralRules для правильных окончаний
  • Примеры

    Форматирование российских цен, дат и относительного времени через Intl API

    // ===== Intl.NumberFormat — цены =====
    
    function formatPrice(amount, currency = 'RUB', locale = 'ru-RU') {
      return new Intl.NumberFormat(locale, {
        style: 'currency',
        currency,
        minimumFractionDigits: 0,
        maximumFractionDigits: 0,
      }).format(amount)
    }
    
    console.log('=== Форматирование цен ===')
    console.log(formatPrice(1500))              // '1 500 ₽'
    console.log(formatPrice(89990))             // '89 990 ₽'
    console.log(formatPrice(1234567))           // '1 234 567 ₽'
    console.log(formatPrice(99, 'USD', 'en-US')) // '$99'
    console.log(formatPrice(50, 'EUR', 'de-DE')) // '50 €'
    
    // ===== Intl.DateTimeFormat — даты =====
    
    function formatDate(date, style = 'short') {
      return new Intl.DateTimeFormat('ru-RU', { dateStyle: style }).format(date)
    }
    
    const today = new Date('2026-03-04T12:00:00')
    
    console.log('\n=== Форматирование дат ===')
    console.log(formatDate(today, 'short'))  // '04.03.2026'
    console.log(formatDate(today, 'medium')) // '4 мар. 2026 г.'
    console.log(formatDate(today, 'long'))   // '4 марта 2026 г.'
    console.log(formatDate(today, 'full'))   // 'среда, 4 марта 2026 г.'
    
    // Дата + время
    const withTime = new Intl.DateTimeFormat('ru-RU', {
      dateStyle: 'short',
      timeStyle: 'short',
    }).format(today)
    console.log('Дата и время:', withTime)   // '04.03.2026, 12:00'
    
    // ===== Intl.RelativeTimeFormat — "N дней назад" =====
    
    function timeAgo(date) {
      const now = Date.now()
      const diffMs = now - date.getTime()
      const diffSec = Math.round(diffMs / 1000)
      const diffMin = Math.round(diffSec / 60)
      const diffHrs = Math.round(diffMin / 60)
      const diffDays = Math.round(diffHrs / 24)
    
      const rtf = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
    
      if (Math.abs(diffSec) < 60)   return rtf.format(-diffSec, 'second')
      if (Math.abs(diffMin) < 60)   return rtf.format(-diffMin, 'minute')
      if (Math.abs(diffHrs) < 24)   return rtf.format(-diffHrs, 'hour')
      return rtf.format(-diffDays, 'day')
    }
    
    console.log('\n=== Относительное время ===')
    const rtf = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
    console.log(rtf.format(-2, 'minute'))   // '2 минуты назад'
    console.log(rtf.format(-1, 'hour'))     // 'час назад'
    console.log(rtf.format(-1, 'day'))      // 'вчера'
    console.log(rtf.format(-5, 'day'))      // '5 дней назад'
    console.log(rtf.format(3, 'day'))       // 'через 3 дня'
    console.log(rtf.format(1, 'week'))      // 'через 1 неделю'
    
    // ===== Intl.PluralRules — правила множественного числа =====
    
    console.log('\n=== Множественное число ===')
    const pr = new Intl.PluralRules('ru-RU')
    
    function items(n) {
      const rule = pr.select(n)
      const forms = { one: 'товар', few: 'товара', many: 'товаров', other: 'товаров' }
      return `${n} ${forms[rule]}`
    }
    
    ;[1, 2, 5, 11, 21, 100, 101].forEach(n => console.log(items(n)))
    // '1 товар', '2 товара', '5 товаров', '11 товаров', '21 товар', '100 товаров', '101 товар'

    Intl: интернационализация в JS

    Интернет-магазин выходит на рынки России, Германии и США. Цена 1500 должна отображаться как "1 500 ₽", "1.500 €" и "$1,500" — в зависимости от локали. Дата "следующая неделя" по-русски звучит "через 7 дней", а по-немецки — "in 7 Tagen". Всё это решает Intl API — без сторонних библиотек.

    Что решает Intl

    Intl (Internationalization API) — встроенный набор объектов для форматирования чисел, валют, дат и строк с учётом локали. До Intl для этого нужны были библиотеки вроде moment.js или numeral.js весом по 100+ KB.

    На основе предыдущих уроков

  • Date: объект даты, который форматирует Intl.DateTimeFormat
  • числа: числа, которые форматирует Intl.NumberFormat
  • строки: Intl.Collator для правильной сортировки строк
  • Intl.NumberFormat — числа, валюты, проценты

    // Простое форматирование числа
    const num = new Intl.NumberFormat('ru-RU').format(1234567.89)
    console.log(num)  // '1 234 567,89'
    
    // Валюта
    const price = new Intl.NumberFormat('ru-RU', {
      style: 'currency',
      currency: 'RUB',
    }).format(89990)
    console.log(price)  // '89 990,00 ₽'
    
    // Доллары
    const usd = new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD',
    }).format(89990)
    console.log(usd)  // '$89,990.00'
    
    // Проценты
    const pct = new Intl.NumberFormat('ru-RU', {
      style: 'percent',
      maximumFractionDigits: 1,
    }).format(0.1567)
    console.log(pct)  // '15,7%'
    
    // Компактный формат
    const compact = new Intl.NumberFormat('ru-RU', {
      notation: 'compact',
    }).format(1500000)
    console.log(compact)  // '1,5 млн'

    Intl.DateTimeFormat — форматирование дат

    const date = new Date('2026-03-04T15:30:00')
    
    // Полная дата по-русски
    const full = new Intl.DateTimeFormat('ru-RU', {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      weekday: 'long',
    }).format(date)
    console.log(full)  // 'среда, 4 марта 2026 г.'
    
    // Краткий формат
    const short = new Intl.DateTimeFormat('ru-RU').format(date)
    console.log(short)  // '04.03.2026'
    
    // Дата и время
    const datetime = new Intl.DateTimeFormat('ru-RU', {
      dateStyle: 'short',
      timeStyle: 'short',
    }).format(date)
    console.log(datetime)  // '04.03.2026, 15:30'
    
    // Английский формат
    const enDate = new Intl.DateTimeFormat('en-US', {
      dateStyle: 'full',
    }).format(date)
    console.log(enDate)  // 'Wednesday, March 4, 2026'

    Intl.RelativeTimeFormat — "5 минут назад"

    const rtf = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
    
    console.log(rtf.format(-5, 'minute'))   // '5 минут назад'
    console.log(rtf.format(-1, 'day'))      // 'вчера'
    console.log(rtf.format(3, 'day'))       // 'через 3 дня'
    console.log(rtf.format(-2, 'week'))     // '2 недели назад'
    console.log(rtf.format(1, 'month'))     // 'в следующем месяце'
    console.log(rtf.format(-1, 'year'))     // 'в прошлом году'
    
    // Функция "время назад"
    function timeAgo(date) {
      const seconds = Math.floor((Date.now() - date.getTime()) / 1000)
      const rtf = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
    
      if (Math.abs(seconds) < 60)   return rtf.format(-seconds, 'second')
      if (Math.abs(seconds) < 3600) return rtf.format(-Math.floor(seconds / 60), 'minute')
      if (Math.abs(seconds) < 86400) return rtf.format(-Math.floor(seconds / 3600), 'hour')
      return rtf.format(-Math.floor(seconds / 86400), 'day')
    }

    Intl.Collator — сортировка с учётом локали

    // Без Collator — неправильная сортировка кириллицы
    const names = ['Яков', 'Алексей', 'Борис', 'Анна', 'Ёж']
    console.log(names.sort())
    // ['Алексей', 'Анна', 'Борис', 'Ёж', 'Яков'] — Ё может попасть не туда!
    
    // С Collator — корректно
    const collator = new Intl.Collator('ru-RU')
    console.log(names.sort(collator.compare))
    // ['Алексей', 'Анна', 'Борис', 'Ёж', 'Яков'] — правильный порядок
    
    // Сортировка нечувствительная к регистру
    const ci = new Intl.Collator('ru-RU', { sensitivity: 'base' })
    console.log(['Борис', 'анна', 'Алексей'].sort(ci.compare))
    // ['Алексей', 'анна', 'Борис']

    Intl.PluralRules — правила множественного числа

    const pr = new Intl.PluralRules('ru-RU')
    
    // Определяет форму слова
    function formatItems(count) {
      const rule = pr.select(count)
      const forms = {
        one: 'товар',    // 1 товар
        few: 'товара',   // 2, 3, 4 товара
        many: 'товаров', // 5, 6, 11 товаров
        other: 'товаров',
      }
      return `${count} ${forms[rule]}`
    }
    
    console.log(formatItems(1))   // '1 товар'
    console.log(formatItems(3))   // '3 товара'
    console.log(formatItems(5))   // '5 товаров'
    console.log(formatItems(11))  // '11 товаров'
    console.log(formatItems(21))  // '21 товар'

    Типичные ошибки

    Ошибка 1: создают новый Intl-объект при каждом вызове

    // Сломано: создание Intl.NumberFormat дорогостоящая операция
    function formatPrice(price) {
      return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB' }).format(price)
      // ^ каждый раз создаётся новый объект
    }
    
    // Исправлено: создать один раз
    const priceFormatter = new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB' })
    const formatPrice = (price) => priceFormatter.format(price)

    Ошибка 2: сортировка строк без Collator

    // Сломано: стандартный sort некорректно сортирует кириллицу
    const names = ['Яша', 'Анна', 'Ёж', 'Борис']
    names.sort()  // ['Анна', 'Борис', 'Ёж', 'Яша'] — Ё может попасть не туда!
    
    // Исправлено:
    const collator = new Intl.Collator('ru-RU')
    names.sort(collator.compare)  // корректный порядок

    Ошибка 3: используют toLocaleDateString() вместо Intl.DateTimeFormat

    // Нестабильно: поведение зависит от системных настроек браузера
    date.toLocaleDateString()  // разный результат на разных машинах
    
    // Исправлено: явная локаль
    new Intl.DateTimeFormat('ru-RU', { dateStyle: 'short' }).format(date)
    // '04.03.2026' — одинаково везде

    В реальных проектах

  • E-commerce: цены форматируются через Intl.NumberFormat с валютой страны пользователя
  • Соцсети: "5 минут назад" — Intl.RelativeTimeFormat с автоматическим обновлением
  • Таблицы и отчёты: сортировка имён через Intl.Collator с учётом диакритики
  • Уведомления: "У вас 1 сообщение / 5 сообщений" — Intl.PluralRules для правильных окончаний
  • Примеры

    Форматирование российских цен, дат и относительного времени через Intl API

    // ===== Intl.NumberFormat — цены =====
    
    function formatPrice(amount, currency = 'RUB', locale = 'ru-RU') {
      return new Intl.NumberFormat(locale, {
        style: 'currency',
        currency,
        minimumFractionDigits: 0,
        maximumFractionDigits: 0,
      }).format(amount)
    }
    
    console.log('=== Форматирование цен ===')
    console.log(formatPrice(1500))              // '1 500 ₽'
    console.log(formatPrice(89990))             // '89 990 ₽'
    console.log(formatPrice(1234567))           // '1 234 567 ₽'
    console.log(formatPrice(99, 'USD', 'en-US')) // '$99'
    console.log(formatPrice(50, 'EUR', 'de-DE')) // '50 €'
    
    // ===== Intl.DateTimeFormat — даты =====
    
    function formatDate(date, style = 'short') {
      return new Intl.DateTimeFormat('ru-RU', { dateStyle: style }).format(date)
    }
    
    const today = new Date('2026-03-04T12:00:00')
    
    console.log('\n=== Форматирование дат ===')
    console.log(formatDate(today, 'short'))  // '04.03.2026'
    console.log(formatDate(today, 'medium')) // '4 мар. 2026 г.'
    console.log(formatDate(today, 'long'))   // '4 марта 2026 г.'
    console.log(formatDate(today, 'full'))   // 'среда, 4 марта 2026 г.'
    
    // Дата + время
    const withTime = new Intl.DateTimeFormat('ru-RU', {
      dateStyle: 'short',
      timeStyle: 'short',
    }).format(today)
    console.log('Дата и время:', withTime)   // '04.03.2026, 12:00'
    
    // ===== Intl.RelativeTimeFormat — "N дней назад" =====
    
    function timeAgo(date) {
      const now = Date.now()
      const diffMs = now - date.getTime()
      const diffSec = Math.round(diffMs / 1000)
      const diffMin = Math.round(diffSec / 60)
      const diffHrs = Math.round(diffMin / 60)
      const diffDays = Math.round(diffHrs / 24)
    
      const rtf = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
    
      if (Math.abs(diffSec) < 60)   return rtf.format(-diffSec, 'second')
      if (Math.abs(diffMin) < 60)   return rtf.format(-diffMin, 'minute')
      if (Math.abs(diffHrs) < 24)   return rtf.format(-diffHrs, 'hour')
      return rtf.format(-diffDays, 'day')
    }
    
    console.log('\n=== Относительное время ===')
    const rtf = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
    console.log(rtf.format(-2, 'minute'))   // '2 минуты назад'
    console.log(rtf.format(-1, 'hour'))     // 'час назад'
    console.log(rtf.format(-1, 'day'))      // 'вчера'
    console.log(rtf.format(-5, 'day'))      // '5 дней назад'
    console.log(rtf.format(3, 'day'))       // 'через 3 дня'
    console.log(rtf.format(1, 'week'))      // 'через 1 неделю'
    
    // ===== Intl.PluralRules — правила множественного числа =====
    
    console.log('\n=== Множественное число ===')
    const pr = new Intl.PluralRules('ru-RU')
    
    function items(n) {
      const rule = pr.select(n)
      const forms = { one: 'товар', few: 'товара', many: 'товаров', other: 'товаров' }
      return `${n} ${forms[rule]}`
    }
    
    ;[1, 2, 5, 11, 21, 100, 101].forEach(n => console.log(items(n)))
    // '1 товар', '2 товара', '5 товаров', '11 товаров', '21 товар', '100 товаров', '101 товар'

    Задание

    Напиши функцию formatProduct(price, currency, date), которая принимает цену (число), валюту (строку, например "RUB") и дату добавления (объект Date), и возвращает строку вида "1 500 ₽ · добавлен 04.03.2026". Для форматирования используй Intl.NumberFormat и Intl.DateTimeFormat с локалью ru-RU.

    Подсказка

    Intl.NumberFormat('ru-RU', { style: 'currency', currency }).format(price) + ' · добавлен ' + Intl.DateTimeFormat('ru-RU', { dateStyle: 'short' }).format(date)

    Загружаем среду выполнения...
    Загружаем AI-помощника...