Строгая типизация локалей позволяет поймать опечатки на этапе
компиляции, предоставить автодополнение в IDE и сделать конфигурацию
приложения явной. Базовый тип locale?: string слишком широк
для production-кода.
По умолчанию функция format принимает
locale?: string. Это означает, что следующий код
скомпилируется без ошибок:
import { format } from 'timeago.js';
format(new Date(), 'ruu'); // опечатка — нет ошибки
format(new Date(), 'english'); // неверное имя — нет ошибки
format(new Date(), ''); // пустая строка — нет ошибки
type Locale =
| 'af' // Африкаанс
| 'ar' // Арабский
| 'be' // Белорусский
| 'bg' // Болгарский
| 'ca' // Каталанский
| 'da' // Датский
| 'de' // Немецкий
| 'el' // Греческий
| 'en_US' // Английский (США)
| 'es' // Испанский
| 'eu' // Баскский
| 'fa' // Персидский
| 'fi' // Финский
| 'fr' // Французский
| 'he' // Иврит
| 'hr' // Хорватский
| 'hu' // Венгерский
| 'hy_AM' // Армянский
| 'id_ID' // Индонезийский
| 'it' // Итальянский
| 'ja' // Японский
| 'ka' // Грузинский
| 'ko' // Корейский
| 'ml' // Малаялам
| 'my' // Бирманский
| 'nb_NO' // Норвежский
| 'nl' // Нидерландский
| 'nn_NO' // Нюнорск
| 'pl' // Польский
| 'pt_BR' // Португальский (Бразилия)
| 'ro' // Румынский
| 'ru' // Русский
| 'sk' // Словацкий
| 'sl' // Словенский
| 'sr' // Сербский
| 'sv' // Шведский
| 'ta' // Тамильский
| 'th' // Тайский
| 'tr' // Турецкий
| 'uk' // Украинский
| 'uz' // Узбекский
| 'vi' // Вьетнамский
| 'zh_CN' // Китайский (упрощённый)
| 'zh_TW' // Китайский (традиционный)
| 'zt' // Канtonsky (кантонский);
import { format } from 'timeago.js';
type Locale = 'ru' | 'en_US' | 'de' | 'fr' | 'zh_CN';
function typedFormat(
date: Date | string | number,
locale: Locale = 'ru'
): string {
return format(date, locale);
}
// typedFormat(new Date(), 'ruu'); // Ошибка TypeScript
// typedFormat(new Date(), 'ru'); // OK
В реальных приложениях часто поддерживается не весь список, а только несколько языков:
// Полный список из библиотеки
type TimeagoLocale = 'ru' | 'en_US' | 'de' | 'fr' | /* ... */ string;
// Поддерживаемые приложением
type AppLocale = 'ru' | 'en_US' | 'de';
// Проверка совместимости на уровне типов
type ValidAppLocale = AppLocale extends TimeagoLocale ? AppLocale : never;
// Если AppLocale выходит за рамки TimeagoLocale — ошибка типов при использовании
const LOCALES = ['ru', 'en_US', 'de', 'fr', 'es'] as const;
type SupportedLocale = (typeof LOCALES)[number];
// 'ru' | 'en_US' | 'de' | 'fr' | 'es'
function isValidLocale(locale: string): locale is SupportedLocale {
return (LOCALES as readonly string[]).includes(locale);
}
function safeFormat(date: Date, locale: string): string {
const resolvedLocale: SupportedLocale = isValidLocale(locale) ? locale : 'ru';
return format(date, resolvedLocale);
}
type AppLocale = 'ru' | 'en_US' | 'de';
interface LocaleConfig {
code: AppLocale;
name: string;
direction: 'ltr' | 'rtl';
}
const LOCALE_CONFIGS: Record<AppLocale, LocaleConfig> = {
ru: { code: 'ru', name: 'Русский', direction: 'ltr' },
en_US: { code: 'en_US', name: 'English', direction: 'ltr' },
de: { code: 'de', name: 'Deutsch', direction: 'ltr' },
};
// Функция, принимающая только ключ из конфига
function formatWithConfig(date: Date, localeCode: AppLocale): string {
const config = LOCALE_CONFIGS[localeCode];
return format(date, config.code);
}
import { format } from 'timeago.js';
type AppLocale = 'ru' | 'en_US' | 'de';
type LocalizedResult =
| { locale: 'ru'; value: string; direction: 'ltr' }
| { locale: 'en_US'; value: string; direction: 'ltr' }
| { locale: 'de'; value: string; direction: 'ltr' };
function formatLocalized(
date: Date | string | number,
locale: AppLocale
): LocalizedResult {
const value = format(date, locale);
// TypeScript требует exhaustive return
switch (locale) {
case 'ru': return { locale, value, direction: 'ltr' };
case 'en_US': return { locale, value, direction: 'ltr' };
case 'de': return { locale, value, direction: 'ltr' };
}
}
type AppLocale = 'ru' | 'en_US' | 'de';
const LOCALE_MAP: Record<string, AppLocale> = {
'ru': 'ru',
'ru-RU': 'ru',
'en': 'en_US',
'en-US': 'en_US',
'de': 'de',
'de-DE': 'de',
};
function detectLocale(fallback: AppLocale = 'ru'): AppLocale {
const browserLocale = navigator.language;
return LOCALE_MAP[browserLocale] ?? fallback;
}
// Тип результата — AppLocale, не string
const locale = detectLocale(); // AppLocale
import { format } from 'timeago.js';
type RTLLocale = 'ar' | 'he' | 'fa';
type LTRLocale = 'ru' | 'en_US' | 'de';
type AnyLocale = RTLLocale | LTRLocale;
// Перегрузки с разными возвращаемыми типами
function formatWithDirection(date: Date, locale: RTLLocale): { value: string; dir: 'rtl' };
function formatWithDirection(date: Date, locale: LTRLocale): { value: string; dir: 'ltr' };
function formatWithDirection(date: Date, locale: AnyLocale): { value: string; dir: 'rtl' | 'ltr' } {
const rtlSet = new Set<string>(['ar', 'he', 'fa']);
return {
value: format(date, locale),
dir: rtlSet.has(locale) ? 'rtl' : 'ltr',
};
}
const ar = formatWithDirection(new Date(), 'ar');
// ar.dir — тип 'rtl', не 'rtl' | 'ltr'
declare module 'timeago.js' {
type Locale = 'ru' | 'en_US' | 'de' | 'fr' | 'es' | 'zh_CN' | 'ja' | 'ko';
export function format(
date: Date | string | number,
locale?: Locale,
opts?: FormatOptions
): string;
export function render(
nodes: Element | NodeList | Element[],
locale?: Locale
): void;
}
Единое место определения — все вызовы format и
render во всём проекте начнут проверяться
автоматически.