Обработка специальных значений

При интернационализации приложений недостаточно корректно форматировать только строки, даты и числа. В реальных системах регулярно встречаются специальные значения:

  • null
  • undefined
  • NaN
  • Infinity
  • -Infinity
  • пустые строки
  • отсутствующие переводы
  • некорректные валюты
  • неподдерживаемые локали
  • невалидные даты

Библиотека Globalize предоставляет инструменты для обработки большинства подобных ситуаций, однако часть логики необходимо реализовывать самостоятельно.


Особенности обработки специальных значений

При локализации данные проходят несколько этапов:

  1. Получение значения.
  2. Преобразование.
  3. Форматирование.
  4. Вывод в пользовательский интерфейс.

На любом этапе могут появляться специальные значения, нарушающие корректную работу приложения.

Например:

const price = NaN;
const date = null;
const amount = Infinity;

Без дополнительной обработки форматтеры могут:

  • выбросить исключение;
  • вернуть неожиданный результат;
  • сформировать некорректную строку;
  • нарушить отображение интерфейса.

Проверка значений перед форматированием

Базовая стратегия защиты

Перед использованием форматтеров рекомендуется выполнять предварительную валидацию.

function safeNumber(value) {
    if (value === null || value === undefined) {
        return "Нет данных";
    }

    if (Number.isNaN(value)) {
        return "Некорректное число";
    }

    if (!Number.isFinite(value)) {
        return "Бесконечность";
    }

    return value;
}

Использование:

const formatter = Globalize.numberFormatter();

console.log(
    formatter(safeNumber(1500))
);

Обработка null и undefined

Проблема отсутствующих данных

Часто сервер возвращает:

{
    amount: null
}

или:

{
    amount: undefined
}

Попытка форматирования может привести к ошибке:

const formatter = Globalize.numberFormatter();

formatter(null);

Результат зависит от конфигурации и версии библиотек CLDR, но подобное поведение считается небезопасным.


Создание безопасной обёртки

Практический подход — создание универсальной функции.

function formatNullableNumber(value, locale = "en") {
    const globalize = new Globalize(locale);
    const formatter = globalize.numberFormatter();

    if (value == null) {
        return "—";
    }

    return formatter(value);
}

Пример:

console.log(formatNullableNumber(null));
console.log(formatNullableNumber(undefined));
console.log(formatNullableNumber(15000));

Использование значений по умолчанию

Иногда вместо заглушки требуется использовать дефолтное значение.

function formatPrice(price) {
    const formatter = Globalize.currencyFormatter("USD");

    return formatter(price ?? 0);
}

Оператор ?? особенно удобен, так как не заменяет:

  • 0
  • false
  • ""

Обработка NaN

Причины появления NaN

Значение NaN возникает при ошибочных вычислениях:

const result = 0 / 0;

или:

const value = Number("abc");

Проверка через Number.isNaN

Наиболее безопасный вариант:

function safeFormatNumber(value) {
    if (Number.isNaN(value)) {
        return "Ошибка вычисления";
    }

    return Globalize.formatNumber(value);
}

Локализованные сообщения об ошибках

Специальные значения желательно локализовывать.

const messages = {
    en: {
        invalidNumber: "Invalid number"
    },
    ru: {
        invalidNumber: "Некорректное число"
    }
};

function localizedNaN(value, locale) {
    if (Number.isNaN(value)) {
        return messages[locale].invalidNumber;
    }

    return Globalize(locale).formatNumber(value);
}

Обработка бесконечности

Значения Infinity и -Infinity

В JavaScript существуют специальные числовые значения:

Infinity
-Infinity

Они появляются:

  • при делении на ноль;
  • при переполнении;
  • при математических ошибках.

Проверка через Number.isFinite

function formatFiniteNumber(value) {
    if (!Number.isFinite(value)) {
        return "∞";
    }

    return Globalize.formatNumber(value);
}

Разделение положительной и отрицательной бесконечности

function formatInfinity(value) {
    if (value === Infinity) {
        return "+∞";
    }

    if (value === -Infinity) {
        return "-∞";
    }

    return Globalize.formatNumber(value);
}

Обработка пустых строк

Особенности строковых данных

Пустая строка:

""

не является отсутствующим значением.

Однако в формах и API она часто эквивалентна отсутствию данных.


Нормализация значений

function normalizeValue(value) {
    if (value === "") {
        return null;
    }

    return value;
}

Использование:

const value = normalizeValue("");

if (value === null) {
    console.log("Значение отсутствует");
}

Работа с отсутствующими переводами

Ошибки локализации сообщений

Globalize активно использует словари переводов.

Пример:

Globalize.loadMessages({
    en: {
        hello: "Hello"
    }
});

Если перевод отсутствует:

Globalize.formatMessage("welcome");

может возникнуть исключение.


Защитная обёртка для сообщений

function safeMessage(key, locale = "en") {
    try {
        return Globalize(locale).formatMessage(key);
    } catch (e) {
        return `[missing:${key}]`;
    }
}

Использование fallback-локали

Распространённый подход:

  1. попытка получить перевод;
  2. переключение на резервную локаль.
function messageWithFallback(key, locale) {
    try {
        return Globalize(locale).formatMessage(key);
    } catch {
        return Globalize("en").formatMessage(key);
    }
}

Обработка неподдерживаемых локалей

Ошибки инициализации

Проблема:

new Globalize("xx-UNKNOWN");

Если CLDR-данные отсутствуют, форматирование завершится ошибкой.


Проверка списка поддерживаемых локалей

const supportedLocales = ["en", "ru", "de"];

function resolveLocale(locale) {
    if (!supportedLocales.includes(locale)) {
        return "en";
    }

    return locale;
}

Автоматическое резервирование

function createGlobalize(locale) {
    const safeLocale = resolveLocale(locale);

    return new Globalize(safeLocale);
}

Обработка невалидных дат

Некорректные объекты Date

JavaScript позволяет создавать невалидные даты:

const date = new Date("invalid");

Проверка даты

function isValidDate(date) {
    return date instanceof Date &&
           !isNaN(date.getTime());
}

Безопасное форматирование даты

function safeFormatDate(date, locale = "ru") {
    if (!isValidDate(date)) {
        return "Неверная дата";
    }

    return Globalize(locale).formatDate(date);
}

Безопасное форматирование валют

Ошибки валютных кодов

Globalize использует ISO-коды валют:

Globalize.currencyFormatter("USD");

Некорректный код:

Globalize.currencyFormatter("INVALID");

может привести к исключению.


Проверка валюты

const allowedCurrencies = [
    "USD",
    "EUR",
    "KZT",
    "RUB"
];

function safeCurrencyFormatter(currency) {
    if (!allowedCurrencies.includes(currency)) {
        currency = "USD";
    }

    return Globalize.currencyFormatter(currency);
}

Обработка исключений Globalize

Использование try/catch

Некоторые методы Globalize выбрасывают исключения:

  • отсутствие CLDR;
  • неизвестная локаль;
  • ошибки сообщений;
  • неверные параметры.

Пример безопасного вызова:

function safeExecute(callback) {
    try {
        return callback();
    } catch (error) {
        console.error(error);

        return null;
    }
}

Использование:

const result = safeExecute(() => {
    return Globalize("ru").formatNumber(5000);
});

Централизованный слой форматирования

Архитектурный подход

В крупных приложениях не рекомендуется вызывать Globalize напрямую из компонентов.

Лучше создать отдельный слой:

class FormatterService {
    constructor(locale = "en") {
        this.globalize = new Globalize(locale);
    }

    formatNumber(value) {
        if (value == null) {
            return "—";
        }

        if (!Number.isFinite(value)) {
            return "∞";
        }

        return this.globalize.formatNumber(value);
    }

    formatDate(date) {
        if (!isValidDate(date)) {
            return "Неверная дата";
        }

        return this.globalize.formatDate(date);
    }
}

Преимущества централизованной обработки

Такой подход обеспечивает:

  • единообразие;
  • контроль ошибок;
  • централизованную локализацию;
  • упрощение поддержки;
  • повторное использование;
  • удобство тестирования.

Нормализация данных перед Globalize

Подготовка входных значений

Globalize не должен получать “грязные” данные напрямую.

Часто используется слой нормализации:

function normalizeApiData(data) {
    return {
        amount: Number(data.amount) || 0,
        name: data.name ?? "Unknown",
        createdAt: new Date(data.createdAt)
    };
}

Разделение ответственности

Рекомендуемая архитектура:

Слой Ответственность
API Получение данных
Normalizer Очистка и приведение
Formatter Локализация
UI Отображение

Специальные значения в числовом форматировании

Поведение formatNumber

Globalize.formatNumber(NaN);
Globalize.formatNumber(Infinity);

Поведение зависит от:

  • версии Globalize;
  • CLDR;
  • окружения JavaScript.

Поэтому проверка значений должна выполняться заранее.


Форматирование отрицательного нуля

JavaScript содержит специальное значение:

-0

Проверка:

Object.is(value, -0)

Нормализация отрицательного нуля

function normalizeNegativeZero(value) {
    if (Object.is(value, -0)) {
        return 0;
    }

    return value;
}

Обработка пустых коллекций

Пустые массивы

При локализации списков возможны ситуации:

[]

Защитное форматирование списков

function formatItems(items) {
    if (!Array.isArray(items) || items.length === 0) {
        return "Список пуст";
    }

    return items.join(", ");
}

Локализация специальных сообщений

Хранение сообщений

Желательно выносить специальные строки в словари:

Globalize.loadMessages({
    ru: {
        errors: {
            invalidDate: "Неверная дата",
            invalidNumber: "Некорректное число",
            noData: "Нет данных"
        }
    }
});

Использование сообщений Globalize

function getErrorMessage(key, locale = "ru") {
    return Globalize(locale).formatMessage(
        `errors/${key}`
    );
}

Универсальная функция безопасного форматирования

Реализация

function safeFormat(value, formatter, options = {}) {
    const {
        nullValue = "—",
        nanValue = "NaN",
        infinityValue = "∞"
    } = options;

    if (value == null) {
        return nullValue;
    }

    if (Number.isNaN(value)) {
        return nanValue;
    }

    if (!Number.isFinite(value)) {
        return infinityValue;
    }

    try {
        return formatter(value);
    } catch {
        return nullValue;
    }
}

Использование

const formatter = Globalize("ru")
    .numberFormatter();

console.log(
    safeFormat(1500, formatter)
);

console.log(
    safeFormat(NaN, formatter)
);

console.log(
    safeFormat(Infinity, formatter)
);

Типичные ошибки при обработке специальных значений

Отсутствие проверки входных данных

Ошибка:

Globalize.formatDate(data.date);

Без проверки data.date.


Использование глобальных fallback-значений

Плохой пример:

value || 0

Проблема:

  • 0
  • false
  • ""

тоже будут заменены.


Игнорирование ошибок локализации

Нельзя предполагать, что:

  • перевод существует;
  • локаль загружена;
  • CLDR доступен;
  • форматтер всегда валиден.

Форматирование необработанных API-данных

Опасный код:

formatter(apiResponse.total);

Без:

  • нормализации;
  • проверки типов;
  • проверки диапазонов;
  • обработки ошибок.

Практическая схема обработки специальных значений

Полный цикл

function formatUserBalance(balance, locale) {
    if (balance == null) {
        return "Нет данных";
    }

    if (Number.isNaN(balance)) {
        return "Ошибка";
    }

    if (!Number.isFinite(balance)) {
        return "∞";
    }

    try {
        return Globalize(locale)
            .currencyFormatter("USD")(balance);
    } catch {
        return "Ошибка форматирования";
    }
}

Рекомендации по промышленной разработке

Основные принципы

Проверка до форматирования

Globalize не должен получать неподготовленные данные.


Централизация форматтеров

Форматирование должно находиться в сервисах, а не в UI-компонентах.


Использование fallback-локалей

Минимальный набор:

  • основная локаль;
  • резервная локаль;
  • дефолтные сообщения.

Изоляция ошибок

Ошибки форматирования не должны ломать интерфейс.


Локализация специальных сообщений

Строки:

  • «Нет данных»
  • «Ошибка»
  • «Неверная дата»

должны быть переведены так же, как и остальные сообщения приложения.