Библиотека Globalize изначально ориентирована на работу в JavaScript-окружении без строгой типизации, однако при использовании в современных проектах она почти всегда оборачивается TypeScript-слоем. В этом контексте интерфейсы и дженерики становятся ключевым инструментом описания контрактов между частями системы интернационализации: форматированием, парсингом и локализационными данными CLDR.
Типизация здесь выполняет не декоративную роль, а фиксирует структуру данных, с которыми работает библиотека:
В основе типизации лежит описание локали как строго структурированного объекта или строкового идентификатора.
interface Locale {
name: string;
language: string;
script?: string;
region?: string;
}
Такой интерфейс позволяет отделить синтаксическое представление
локали ("en-US") от её разложенной формы. В типизированной
модели это упрощает работу с частичными локалями и
fallback-механизмами.
Дополнительно вводится обобщённый тип для поддержки различных представлений локали:
type LocaleInput = string | Locale;
Это позволяет API оставаться совместимым с нативным форматом Globalize и одновременно поддерживать расширенные структуры.
CLDR (Common Locale Data Repository) представляет собой фундаментальную зависимость. Типизация CLDR-структур критична, поскольку данные глубоко вложенные и часто неоднородные.
interface CldrNumberSymbols {
decimal: string;
group: string;
plusSign: string;
minusSign: string;
}
interface CldrNumberFormats {
decimal: string;
scientific: string;
percent: string;
currency: string;
}
Объединение этих интерфейсов формирует базовую модель числовой локализации:
interface CldrNumbers {
symbols: CldrNumberSymbols;
formats: CldrNumberFormats;
}
Такое разделение позволяет строго контролировать структуру входных данных, предотвращая некорректную конфигурацию форматирования.
Форматирование в Globalize является одним из наиболее типичных сценариев применения дженериков. Основная идея — параметризация результата форматирования.
interface Formatter<TInput, TOutput> {
format(value: TInput): TOutput;
}
Этот контракт описывает универсальный форматтер, где:
TInput — тип входного значения (число, дата, единица
измерения);TOutput — тип результата (обычно строка, но не
обязательно).type NumberFormatter = Formatter<number, string>;
Числовой форматтер в рамках Globalize работает с CLDR-данными и локалью, но интерфейс скрывает эти детали, оставляя только вход и выход.
type DateFormatter = Formatter<Date, string>;
Использование дженерика позволяет сохранить единообразие API для разных доменов локализации.
Типизация конфигураций форматирования требует более сложных дженериков, особенно при работе с параметрами локали.
interface FormatOptions<T> {
locale: LocaleInput;
raw?: boolean;
pattern?: string;
transform?: (value: T) => T;
}
Такой интерфейс связывает параметры конфигурации с типом данных, над которыми производится операция.
Пример специализации:
type NumberFormatOptions = FormatOptions<number>;
type DateFormatOptions = FormatOptions<Date>;
Это позволяет компилятору контролировать корректность применения опций в зависимости от типа данных.
В обратную сторону от форматирования работает парсинг строк в типизированные значения.
interface Parser<TInput, TOutput> {
parse(value: TInput): TOutput;
}
В контексте Globalize это критично для обработки локализованных чисел и дат.
Пример числового парсера:
type NumberParser = Parser<string, number>;
Парсер становится зависимым от локали через внешнюю конфигурацию, но интерфейс фиксирует только трансформацию типов.
Дженерики также используются при создании фабрик, которые генерируют специализированные форматтеры на основе конфигурации.
interface FormatterFactory<TInput, TOutput, TOptions> {
create(options: TOptions): Formatter<TInput, TOutput>;
}
Такой подход позволяет абстрагировать процесс создания форматтеров от конкретных реализаций.
Пример для чисел:
interface NumberFormatFactory
extends FormatterFactory<number, string, NumberFormatOptions> {}
Это обеспечивает строгую типизацию цепочки:
конфигурация → фабрика → форматтер → результат.
В сложных системах интернационализации используются условные типы для управления fallback-локалями.
type FallbackLocale<T extends string> =
T extends `${infer Lang}-${infer Region}`
? Lang
: T;
Такой механизм позволяет автоматически извлекать базовый язык из составной локали.
Применение в API:
type NormalizedLocale<T extends LocaleInput> =
T extends string ? FallbackLocale<T> : T;
Это повышает гибкость работы с локалями без потери типобезопасности.
При работе с несколькими локалями одновременно используется параметризация коллекций.
interface LocaleMap<T> {
[locale: string]: T;
}
Пример:
type DateFormatMap = LocaleMap<string>;
Это позволяет описывать структуры, где каждому языку соответствует собственный формат.
Хотя интерфейсы и дженерики существуют только на этапе компиляции, их структура напрямую отражает архитектуру Globalize:
Типизация здесь формализует те же связи, которые в runtime реализуются через композицию функций и конфигурационных объектов.
В сложных сценариях форматирования используется композиция обобщённых типов:
type Pipeline<TInput, TMiddle, TOutput> = {
first: Formatter<TInput, TMiddle>;
second: Formatter<TMiddle, TOutput>;
};
Такая модель позволяет описывать цепочки трансформации данных:
входное значение → промежуточное представление → финальный результат
без потери типовой информации на каждом этапе.
Интерфейсная модель обеспечивает расширение функциональности без изменения базового контракта:
interface ExtendedNumberFormatter extends NumberFormatter {
formatToParts(value: number): Array<{ type: string; value: string }>;
}
Такой подход позволяет вводить новые возможности, сохраняя совместимость с существующими типами.
В рамках Globalize это особенно важно, поскольку экосистема CLDR и локалей постоянно расширяется, а API должен оставаться стабильным.