SkateSpots/src/lib/dateFnsI18n.ts
SkateSpots 828684cf15 feat(i18n): add Hindi (24 dictionaries, 25 codes, all 100%)
- Full 8,703-key Hindi dictionary in src/i18n/hi (7 namespace batches),
  placeholders verified, only 2% identical strings (brand/protocol names).
- Register hi in Language/LANGUAGE_LOADERS, detectLanguage (hi-IN),
  LanguageToggle (🇮🇳, हिन्दी) and dateFnsI18n (hi-IN).
- Lazy-load Noto Sans Devanagari for hi via lib/localeFonts.ts (renamed from
  cjkFonts.ts) and add it to the CSS font stack; other locales don't download it.
- check-i18n covers hi: 24 dictionaries / 25 codes at 100%; tsc/tests/build green.
2026-10-26 16:35:53 +07:00

172 lines
5.2 KiB
TypeScript

/**
* Locale-aware date/number formatting.
*
* The app ships English + Spanish, but `date-fns` calls `formatDistanceToNow`
* with the default (English) locale and native `Date.toLocaleDateString()`
* falls back to the *runtime* locale, not the app language. This module keeps a
* single module-level locale in sync with `LanguageProvider` and re-exports all
* of `date-fns` with `formatDistanceToNow` / `formatDistance` / `format`
* overridden to apply it. Import from here instead of `date-fns` directly.
*
* `dateLocaleCode()` feeds native `toLocaleDateString/TimeString` calls so a
* Spanish user sees "5 ene" rather than "Jan 5".
*/
export * from 'date-fns';
import {
formatDistanceToNow as _formatDistanceToNow,
formatDistanceToNowStrict as _formatDistanceToNowStrict,
formatDistance as _formatDistance,
format as _format,
} from 'date-fns';
import {
enUS,
es as esLocale,
ca as caLocale,
eu as euLocale,
gl as glLocale,
hi as hiLocale,
th as thLocale,
ja as jaLocale,
ptBR as ptLocale,
pt as ptPTLocale,
fr as frLocale,
de as deLocale,
it as itLocale,
nl as nlLocale,
pl as plLocale,
ru as ruLocale,
ko as koLocale,
zhCN as zhCNLocale,
zhTW as zhTWLocale,
uk as ukLocale,
tr as trLocale,
vi as viLocale,
id as idLocale,
sv as svLocale,
cs as csLocale,
ro as roLocale,
el as elLocale,
} from 'date-fns/locale';
import type { Locale } from 'date-fns';
import type { Language } from '@/contexts/language';
let activeLocale: Locale = enUS;
let activeCode = 'en-US';
/** Called by LanguageProvider (during render) so children format in-language. */
export function setDateLocale(language: Language): void {
if (language === 'es') {
activeLocale = esLocale;
activeCode = 'es-ES';
} else if (language === 'ca') {
activeLocale = caLocale;
activeCode = 'ca-ES';
} else if (language === 'eu') {
activeLocale = euLocale;
activeCode = 'eu-ES';
} else if (language === 'gl') {
activeLocale = glLocale;
activeCode = 'gl-ES';
} else if (language === 'hi') {
activeLocale = hiLocale;
activeCode = 'hi-IN';
} else if (language === 'th') {
activeLocale = thLocale;
activeCode = 'th-TH';
} else if (language === 'ja') {
activeLocale = jaLocale;
activeCode = 'ja-JP';
} else if (language === 'pt') {
activeLocale = ptLocale;
activeCode = 'pt-BR';
} else if (language === 'pt-PT') {
activeLocale = ptPTLocale;
activeCode = 'pt-PT';
} else if (language === 'fr') {
activeLocale = frLocale;
activeCode = 'fr-FR';
} else if (language === 'de') {
activeLocale = deLocale;
activeCode = 'de-DE';
} else if (language === 'it') {
activeLocale = itLocale;
activeCode = 'it-IT';
} else if (language === 'nl') {
activeLocale = nlLocale;
activeCode = 'nl-NL';
} else if (language === 'pl') {
activeLocale = plLocale;
activeCode = 'pl-PL';
} else if (language === 'ru') {
activeLocale = ruLocale;
activeCode = 'ru-RU';
} else if (language === 'ko' || language === 'ko-KP') {
activeLocale = koLocale;
activeCode = 'ko-KR';
} else if (language === 'zh-CN') {
activeLocale = zhCNLocale;
activeCode = 'zh-CN';
} else if (language === 'zh-TW') {
activeLocale = zhTWLocale;
activeCode = 'zh-TW';
} else if (language === 'uk') {
activeLocale = ukLocale;
activeCode = 'uk-UA';
} else if (language === 'tr') {
activeLocale = trLocale;
activeCode = 'tr-TR';
} else if (language === 'vi') {
activeLocale = viLocale;
activeCode = 'vi-VN';
} else if (language === 'id') {
activeLocale = idLocale;
activeCode = 'id-ID';
} else if (language === 'sv') {
activeLocale = svLocale;
activeCode = 'sv-SE';
} else if (language === 'cs') {
activeLocale = csLocale;
activeCode = 'cs-CZ';
} else if (language === 'ro') {
activeLocale = roLocale;
activeCode = 'ro-RO';
} else if (language === 'el') {
activeLocale = elLocale;
activeCode = 'el-GR';
} else {
activeLocale = enUS;
activeCode = 'en-US';
}
}
/** The active date-fns locale. */
export function dateLocale(): Locale {
return activeLocale;
}
/** BCP-47 code for native `toLocale*` / `Intl` APIs. */
export function dateLocaleCode(): string {
return activeCode;
}
type DistOptions = Parameters<typeof _formatDistanceToNow>[1];
type DistStrictOptions = Parameters<typeof _formatDistanceToNowStrict>[1];
type FormatDistanceOptions = Parameters<typeof _formatDistance>[2];
type FormatOptions = Parameters<typeof _format>[2];
export function formatDistanceToNow(date: Date | number, options?: DistOptions) {
return _formatDistanceToNow(date, { ...options, locale: options?.locale ?? activeLocale });
}
export function formatDistanceToNowStrict(date: Date | number, options?: DistStrictOptions) {
return _formatDistanceToNowStrict(date, { ...options, locale: options?.locale ?? activeLocale });
}
export function formatDistance(date: Date | number, baseDate: Date | number, options?: FormatDistanceOptions) {
return _formatDistance(date, baseDate, { ...options, locale: options?.locale ?? activeLocale });
}
export function format(date: Date | number, formatStr: string, options?: FormatOptions) {
return _format(date, formatStr, { ...options, locale: options?.locale ?? activeLocale });
}