diff --git a/.changeset/sixty-walls-rest.md b/.changeset/sixty-walls-rest.md new file mode 100644 index 000000000..61b681ddb --- /dev/null +++ b/.changeset/sixty-walls-rest.md @@ -0,0 +1,6 @@ +--- +"svelte-ux": minor +--- + +- Add locale settings to settings object, and allow dynamically changing the current locale. +- Move format function to be a store on the settings object, which updates when the locale changes. diff --git a/packages/svelte-ux/src/lib/components/settings.ts b/packages/svelte-ux/src/lib/components/settings.ts index 2780296bb..f65e63ee6 100644 --- a/packages/svelte-ux/src/lib/components/settings.ts +++ b/packages/svelte-ux/src/lib/components/settings.ts @@ -8,7 +8,7 @@ import { type LocaleStore, type LocaleSettingsInput, } from '$lib/utils/locale'; -import { buildFormatters, type FormatFunctions } from '$lib/utils'; +import { buildFormatters, type FormatFunctions } from '$lib/utils/format'; import { writable, type Readable, type Writable, derived } from 'svelte/store'; export type SettingsInput = { diff --git a/packages/svelte-ux/src/lib/utils/date.test.ts b/packages/svelte-ux/src/lib/utils/date.test.ts index f21cd3692..a40019d7f 100644 --- a/packages/svelte-ux/src/lib/utils/date.test.ts +++ b/packages/svelte-ux/src/lib/utils/date.test.ts @@ -10,9 +10,11 @@ import { type CustomIntlDateTimeFormatOptions, type FormatDateOptions, DateToken, + formatDateWithLocale, } from './date'; import { getSettings } from '$lib/components'; -import { format } from '.'; +import { format, formatWithLocale } from '.'; +import { createLocaleSettings, defaultLocale } from './locale'; const DATE = '2023-11-21'; // "good" default date as the day (21) is bigger than 12 (number of months). And november is a good month1 (because why not?) const dt_2M_2d = new Date(2023, 10, 21); @@ -22,41 +24,43 @@ const dt_1M_1d = new Date(2023, 2, 7); const dt_1M_1d_time_pm = new Date(2023, 2, 7, 14, 2, 3, 4); const dt_1M_1d_time_am = new Date(2023, 2, 7, 1, 2, 3, 4); -const fr: FormatDateOptions = { - locales: 'fr', - ordinalSuffixes: { - fr: { - one: 'er', - two: '', - few: '', - other: '', +const fr = createLocaleSettings({ + locale: 'fr', + formats: { + dates: { + ordinalSuffixes: { + one: 'er', + two: '', + few: '', + other: '', + }, }, }, -}; +}); describe('formatDate()', () => { it('should return empty string for null or undefined date', () => { - expect(formatDate(getSettings(), null)).equal(''); - expect(formatDate(getSettings(), undefined)).equal(''); + expect(formatDate(null)).equal(''); + expect(formatDate(undefined)).equal(''); }); it('should return empty string for invalid date', () => { - expect(formatDate(getSettings(), 'invalid date')).equal(''); + expect(formatDate('invalid date')).equal(''); }); describe('should format date for PeriodType.Day', () => { const localDate = new Date(2023, 10, 21); const combi = [ - ['short', undefined, '11/21'], - ['short', 'fr', '21/11'], - ['long', undefined, 'Nov 21, 2023'], - ['long', 'fr', '21 nov. 2023'], + ['short', defaultLocale, '11/21'], + ['short', fr, '21/11'], + ['long', defaultLocale, 'Nov 21, 2023'], + ['long', fr, '21 nov. 2023'], ] as const; for (const c of combi) { const [variant, locales, expected] = c; it(c.toString(), () => { - expect(formatDate(getSettings(), localDate, PeriodType.Day, { variant, locales })).equal( + expect(formatDateWithLocale(locales, localDate, PeriodType.Day, { variant })).equal( expected ); }); @@ -65,18 +69,16 @@ describe('formatDate()', () => { describe('should format date string for PeriodType.Day', () => { const combi = [ - ['short', undefined, '11/21'], - ['short', 'fr', '21/11'], - ['long', undefined, 'Nov 21, 2023'], - ['long', 'fr', '21 nov. 2023'], + ['short', defaultLocale, '11/21'], + ['short', fr, '21/11'], + ['long', defaultLocale, 'Nov 21, 2023'], + ['long', fr, '21 nov. 2023'], ] as const; for (const c of combi) { const [variant, locales, expected] = c; it(c.toString(), () => { - expect(formatDate(getSettings(), DATE, PeriodType.Day, { variant, locales })).equal( - expected - ); + expect(formatDateWithLocale(locales, DATE, PeriodType.Day, { variant })).equal(expected); }); } }); @@ -114,86 +116,86 @@ describe('formatDate()', () => { for (const c of combi) { const [date, periodType, options, [expected_default, expected_fr]] = c; it(c.toString(), () => { - expect(format(getSettings(), date, periodType, options)).equal(expected_default); + expect(formatWithLocale(defaultLocale, date, periodType, options)).equal(expected_default); }); it(c.toString() + 'fr', () => { - expect(format(getSettings(), date, periodType, { ...options, ...fr })).equal(expected_fr); + expect(formatWithLocale(fr, date, periodType, options)).equal(expected_fr); }); } }); describe('should format date for PeriodType.WeekSun / Mon', () => { const combi = [ - [PeriodType.WeekSun, 'short', undefined, '11/19 - 11/25'], - [PeriodType.WeekSun, 'short', 'fr', '19/11 - 25/11'], - [PeriodType.WeekSun, 'long', undefined, '11/19/2023 - 11/25/2023'], - [PeriodType.WeekSun, 'long', 'fr', '19/11/2023 - 25/11/2023'], - [PeriodType.WeekMon, 'long', undefined, '11/20/2023 - 11/26/2023'], - [PeriodType.WeekMon, 'long', 'fr', '20/11/2023 - 26/11/2023'], + [PeriodType.WeekSun, 'short', defaultLocale, '11/19 - 11/25'], + [PeriodType.WeekSun, 'short', fr, '19/11 - 25/11'], + [PeriodType.WeekSun, 'long', defaultLocale, '11/19/2023 - 11/25/2023'], + [PeriodType.WeekSun, 'long', fr, '19/11/2023 - 25/11/2023'], + [PeriodType.WeekMon, 'long', defaultLocale, '11/20/2023 - 11/26/2023'], + [PeriodType.WeekMon, 'long', fr, '20/11/2023 - 26/11/2023'], ] as const; for (const c of combi) { const [periodType, variant, locales, expected] = c; it(c.toString(), () => { - expect(formatDate(getSettings(), DATE, periodType, { variant, locales })).equal(expected); + expect(formatDateWithLocale(locales, DATE, periodType, { variant, locales })).equal( + expected + ); }); } }); describe('should format date for PeriodType.Week', () => { const combi = [ - [PeriodType.Week, 'short', undefined, DayOfWeek.Sunday, '11/19 - 11/25'], - [PeriodType.Week, 'short', 'fr', DayOfWeek.Sunday, '19/11 - 25/11'], - [PeriodType.Week, 'long', undefined, DayOfWeek.Sunday, '11/19/2023 - 11/25/2023'], - [PeriodType.Week, 'long', 'fr', DayOfWeek.Sunday, '19/11/2023 - 25/11/2023'], - - [PeriodType.Week, 'short', undefined, DayOfWeek.Monday, '11/20 - 11/26'], - [PeriodType.Week, 'short', 'fr', DayOfWeek.Monday, '20/11 - 26/11'], - [PeriodType.Week, 'long', undefined, DayOfWeek.Monday, '11/20/2023 - 11/26/2023'], - [PeriodType.Week, 'long', 'fr', DayOfWeek.Monday, '20/11/2023 - 26/11/2023'], + [PeriodType.Week, 'short', defaultLocale, DayOfWeek.Sunday, '11/19 - 11/25'], + [PeriodType.Week, 'short', fr, DayOfWeek.Sunday, '19/11 - 25/11'], + [PeriodType.Week, 'long', defaultLocale, DayOfWeek.Sunday, '11/19/2023 - 11/25/2023'], + [PeriodType.Week, 'long', fr, DayOfWeek.Sunday, '19/11/2023 - 25/11/2023'], + + [PeriodType.Week, 'short', defaultLocale, DayOfWeek.Monday, '11/20 - 11/26'], + [PeriodType.Week, 'short', fr, DayOfWeek.Monday, '20/11 - 26/11'], + [PeriodType.Week, 'long', defaultLocale, DayOfWeek.Monday, '11/20/2023 - 11/26/2023'], + [PeriodType.Week, 'long', fr, DayOfWeek.Monday, '20/11/2023 - 26/11/2023'], ] as const; for (const c of combi) { const [periodType, variant, locales, weekStartsOn, expected] = c; it(c.toString(), () => { - expect( - formatDate(getSettings(), DATE, periodType, { variant, locales, weekStartsOn }) - ).equal(expected); + expect(formatDateWithLocale(locales, DATE, periodType, { variant, weekStartsOn })).equal( + expected + ); }); } }); describe('should format date for PeriodType.Month', () => { const combi = [ - ['short', undefined, 'Nov'], - ['short', 'fr', 'nov.'], - ['long', undefined, 'November'], - ['long', 'fr', 'novembre'], + ['short', defaultLocale, 'Nov'], + ['short', fr, 'nov.'], + ['long', defaultLocale, 'November'], + ['long', fr, 'novembre'], ] as const; for (const c of combi) { const [variant, locales, expected] = c; it(c.toString(), () => { - expect(formatDate(getSettings(), DATE, PeriodType.Month, { variant, locales })).equal( - expected - ); + expect(formatDateWithLocale(locales, DATE, PeriodType.Month, { variant })).equal(expected); }); } }); describe('should format date for PeriodType.MonthYear', () => { const combi = [ - ['short', undefined, 'Nov 23'], - ['short', 'fr', 'nov. 23'], - ['long', undefined, 'November 2023'], - ['long', 'fr', 'novembre 2023'], + ['short', defaultLocale, 'Nov 23'], + ['short', fr, 'nov. 23'], + ['long', defaultLocale, 'November 2023'], + ['long', fr, 'novembre 2023'], ] as const; for (const c of combi) { const [variant, locales, expected] = c; it(c.toString(), () => { - expect(formatDate(getSettings(), DATE, PeriodType.MonthYear, { variant, locales })).equal( + expect(formatDateWithLocale(locales, DATE, PeriodType.MonthYear, { variant })).equal( expected ); }); @@ -202,16 +204,16 @@ describe('formatDate()', () => { describe('should format date for PeriodType.Quarter', () => { const combi = [ - ['short', undefined, 'Oct - Dec 23'], - ['short', 'fr', 'oct. - déc. 23'], - ['long', undefined, 'October - December 2023'], - ['long', 'fr', 'octobre - décembre 2023'], + ['short', defaultLocale, 'Oct - Dec 23'], + ['short', fr, 'oct. - déc. 23'], + ['long', defaultLocale, 'October - December 2023'], + ['long', fr, 'octobre - décembre 2023'], ] as const; for (const c of combi) { const [variant, locales, expected] = c; it(c.toString(), () => { - expect(formatDate(getSettings(), DATE, PeriodType.Quarter, { variant, locales })).equal( + expect(formatDateWithLocale(locales, DATE, PeriodType.Quarter, { variant })).equal( expected ); }); @@ -220,35 +222,35 @@ describe('formatDate()', () => { describe('should format date for PeriodType.CalendarYear', () => { const combi = [ - ['short', undefined, '23'], - ['short', 'fr', '23'], - ['long', undefined, '2023'], - ['long', 'fr', '2023'], + ['short', defaultLocale, '23'], + ['short', fr, '23'], + ['long', defaultLocale, '2023'], + ['long', fr, '2023'], ] as const; for (const c of combi) { const [variant, locales, expected] = c; it(c.toString(), () => { - expect( - formatDate(getSettings(), DATE, PeriodType.CalendarYear, { variant, locales }) - ).equal(expected); + expect(formatDateWithLocale(locales, DATE, PeriodType.CalendarYear, { variant })).equal( + expected + ); }); } }); describe('should format date for PeriodType.FiscalYearOctober', () => { const combi = [ - ['short', undefined, '24'], - ['short', 'fr', '24'], - ['long', undefined, '2024'], - ['long', 'fr', '2024'], + ['short', defaultLocale, '24'], + ['short', fr, '24'], + ['long', defaultLocale, '2024'], + ['long', fr, '2024'], ] as const; for (const c of combi) { const [variant, locales, expected] = c; it(c.toString(), () => { expect( - formatDate(getSettings(), DATE, PeriodType.FiscalYearOctober, { variant, locales }) + formatDateWithLocale(locales, DATE, PeriodType.FiscalYearOctober, { variant }) ).equal(expected); }); } @@ -256,16 +258,16 @@ describe('formatDate()', () => { describe('should format date for PeriodType.BiWeek1Sun', () => { const combi = [ - ['short', undefined, '11/12 - 11/25'], - ['short', 'fr', '12/11 - 25/11'], - ['long', undefined, '11/12/2023 - 11/25/2023'], - ['long', 'fr', '12/11/2023 - 25/11/2023'], + ['short', defaultLocale, '11/12 - 11/25'], + ['short', fr, '12/11 - 25/11'], + ['long', defaultLocale, '11/12/2023 - 11/25/2023'], + ['long', fr, '12/11/2023 - 25/11/2023'], ] as const; for (const c of combi) { const [variant, locales, expected] = c; it(c.toString(), () => { - expect(formatDate(getSettings(), DATE, PeriodType.BiWeek1Sun, { variant, locales })).equal( + expect(formatDateWithLocale(locales, DATE, PeriodType.BiWeek1Sun, { variant })).equal( expected ); }); @@ -275,16 +277,16 @@ describe('formatDate()', () => { describe('should format date for PeriodType.undefined', () => { const expected = '2023-11-21T00:00:00-04:00'; const combi = [ - ['short', undefined], - ['short', 'fr'], - ['long', undefined], - ['long', 'fr'], + ['short', defaultLocale], + ['short', fr], + ['long', defaultLocale], + ['long', fr], ] as const; for (const c of combi) { const [variant, locales] = c; it(c.toString(), () => { - expect(formatDate(getSettings(), DATE, undefined, { variant, locales })).equal(expected); + expect(formatDateWithLocale(locales, DATE, undefined, { variant })).equal(expected); }); } }); @@ -348,11 +350,11 @@ describe('formatIntl() tokens', () => { for (const c of combi) { const [date, tokens, [expected_default, expected_fr]] = c; it(c.toString(), () => { - expect(formatIntl(getSettings(), date, tokens)).equal(expected_default); + expect(formatIntl(defaultLocale, date, tokens)).equal(expected_default); }); it(c.toString() + 'fr', () => { - expect(formatIntl(getSettings(), date, tokens, fr)).equal(expected_fr); + expect(formatIntl(fr, date, tokens)).equal(expected_fr); }); } }); diff --git a/packages/svelte-ux/src/lib/utils/date.ts b/packages/svelte-ux/src/lib/utils/date.ts index b7abe2963..673acf6fd 100644 --- a/packages/svelte-ux/src/lib/utils/date.ts +++ b/packages/svelte-ux/src/lib/utils/date.ts @@ -33,66 +33,10 @@ import { import { hasKeyOf } from '../types/typeGuards'; import { chunk } from './array'; import type { DateRange } from './dateRange'; -import { knownLocales, type LocaleSettings } from './locale'; - -export type SelectedDate = Date | Date[] | DateRange | null; - -export type Period = { - start: Date; - end: Date; - periodTypeId: PeriodType; -}; - -export enum PeriodType { - Custom = 1, - - Day = 10, - DayTime = 11, - TimeOnly = 15, - - WeekSun = 20, - WeekMon = 21, - WeekTue = 22, - WeekWed = 23, - WeekThu = 24, - WeekFri = 25, - WeekSat = 26, - Week = 27, // will be replaced by WeekSun, WeekMon, etc depending on weekStartsOn - - Month = 30, - MonthYear = 31, - Quarter = 40, - CalendarYear = 50, - FiscalYearOctober = 60, - - BiWeek1Sun = 70, - BiWeek1Mon = 71, - BiWeek1Tue = 72, - BiWeek1Wed = 73, - BiWeek1Thu = 74, - BiWeek1Fri = 75, - BiWeek1Sat = 76, - BiWeek1 = 77, // will be replaced by BiWeek1Sun, BiWeek1Mon, etc depending on weekStartsOn - - BiWeek2Sun = 80, - BiWeek2Mon = 81, - BiWeek2Tue = 82, - BiWeek2Wed = 83, - BiWeek2Thu = 84, - BiWeek2Fri = 85, - BiWeek2Sat = 86, - BiWeek2 = 87, // will be replaced by BiWeek2Sun, BiWeek2Mon, etc depending on weekStartsOn -} +import { PeriodType, DayOfWeek, DateToken } from './date_types'; +import { defaultLocale, type LocaleSettings } from './locale'; -export enum DayOfWeek { - Sunday = 0, - Monday = 1, - Tuesday = 2, - Wednesday = 3, - Thursday = 4, - Friday = 5, - Saturday = 6, -} +export * from './date_types'; export function getDayOfWeekName(weekStartsOn: DayOfWeek, locales: string) { // Create a date object for a specific day (0 = Sunday, 1 = Monday, etc.) @@ -103,7 +47,7 @@ export function getDayOfWeekName(weekStartsOn: DayOfWeek, locales: string) { } export function getPeriodTypeName(periodType: PeriodType) { - return getPeriodTypeNameWithLocale(knownLocales.en, periodType); + return getPeriodTypeNameWithLocale(defaultLocale, periodType); } export function getPeriodTypeNameWithLocale(settings: LocaleSettings, periodType: PeriodType) { @@ -584,58 +528,6 @@ export function formatISODate( return formatISO(date, { representation }); } -export enum DateToken { - /** `1982, 1986, 2024` */ - Year_numeric = 'yyy', - /** `82, 86, 24` */ - Year_2Digit = 'yy', - - /** `January, February, ..., December` */ - Month_long = 'MMMM', - /** `Jan, Feb, ..., Dec` */ - Month_short = 'MMM', - /** `01, 02, ..., 12` */ - Month_2Digit = 'MM', - /** `1, 2, ..., 12` */ - Month_numeric = 'M', - - /** `1, 2, ..., 11, 12` */ - Hour_numeric = 'h', - /** `01, 02, ..., 11, 12` */ - Hour_2Digit = 'hh', - /** You should probably not use this. Force with AM/PM (and the good locale), not specifying this will automatically take the good local */ - Hour_wAMPM = 'a', - /** You should probably not use this. Force without AM/PM (and the good locale), not specifying this will automatically take the good local */ - Hour_woAMPM = 'aaaaaa', - - /** `0, 1, ..., 59` */ - Minute_numeric = 'm', - /** `00, 01, ..., 59` */ - Minute_2Digit = 'mm', - - /** `0, 1, ..., 59` */ - Second_numeric = 's', - /** `00, 01, ..., 59` */ - Second_2Digit = 'ss', - - /** `000, 001, ..., 999` */ - MiliSecond_3 = 'SSS', - - /** Minimize digit: `1, 2, 11, ...` */ - DayOfMonth_numeric = 'd', - /** `01, 02, 11, ...` */ - DayOfMonth_2Digit = 'dd', - /** `1st, 2nd, 11th, ...` You can have your local ordinal by passing `ordinalSuffixes` in options / settings */ - DayOfMonth_withOrdinal = 'do', - - /** `M, T, W, T, F, S, S` */ - DayOfWeek_narrow = 'eeeee', - /** `Monday, Tuesday, ..., Sunday` */ - DayOfWeek_long = 'eeee', - /** `Mon, Tue, Wed, ..., Sun` */ - DayOfWeek_short = 'eee', -} - export function formatIntl( settings: LocaleSettings, dt: Date, @@ -759,54 +651,12 @@ function range( return formatIntl(settings, start, formatToUse) + ' - ' + formatIntl(settings, end, formatToUse); } -export type OrdinalSuffixes = { - one: string; - two: string; - few: string; - other: string; - zero?: string; - many?: string; -}; -export type DateFormatVariant = 'short' | 'default' | 'long'; -type DateFormatVariantPreset = { - short?: CustomIntlDateTimeFormatOptions; - default?: CustomIntlDateTimeFormatOptions; - long?: CustomIntlDateTimeFormatOptions; -}; -export type CustomIntlDateTimeFormatOptions = - | string - | string[] - | (Intl.DateTimeFormatOptions & { withOrdinal?: boolean }); - -export type FormatDateOptions = { - weekStartsOn?: DayOfWeek; - variant?: DateFormatVariant | 'custom'; - custom?: CustomIntlDateTimeFormatOptions; -}; - -export interface FormatDateLocaleOptions { - weekStartsOn?: DayOfWeek; - baseParsing?: string; - presets?: { - day?: DateFormatVariantPreset; - dayTime?: DateFormatVariantPreset; - timeOnly?: DateFormatVariantPreset; - week?: DateFormatVariantPreset; - month?: DateFormatVariantPreset; - monthsYear?: DateFormatVariantPreset; - year?: DateFormatVariantPreset; - }; - ordinalSuffixes?: OrdinalSuffixes; -} - -export type FormatDateLocalePresets = Required; - export function formatDate( date: Date | string | null | undefined, periodType: PeriodType, options: FormatDateOptions = {} ): string { - return formatDateWithLocale(knownLocales.en, date, periodType, options); + return formatDateWithLocale(defaultLocale, date, periodType, options); } export function formatDateWithLocale( diff --git a/packages/svelte-ux/src/lib/utils/date_types.ts b/packages/svelte-ux/src/lib/utils/date_types.ts new file mode 100644 index 000000000..969362816 --- /dev/null +++ b/packages/svelte-ux/src/lib/utils/date_types.ts @@ -0,0 +1,152 @@ +export type SelectedDate = Date | Date[] | DateRange | null; + +export type Period = { + start: Date; + end: Date; + periodTypeId: PeriodType; +}; + +export enum PeriodType { + Custom = 1, + + Day = 10, + DayTime = 11, + TimeOnly = 15, + + WeekSun = 20, + WeekMon = 21, + WeekTue = 22, + WeekWed = 23, + WeekThu = 24, + WeekFri = 25, + WeekSat = 26, + Week = 27, // will be replaced by WeekSun, WeekMon, etc depending on weekStartsOn + + Month = 30, + MonthYear = 31, + Quarter = 40, + CalendarYear = 50, + FiscalYearOctober = 60, + + BiWeek1Sun = 70, + BiWeek1Mon = 71, + BiWeek1Tue = 72, + BiWeek1Wed = 73, + BiWeek1Thu = 74, + BiWeek1Fri = 75, + BiWeek1Sat = 76, + BiWeek1 = 77, // will be replaced by BiWeek1Sun, BiWeek1Mon, etc depending on weekStartsOn + + BiWeek2Sun = 80, + BiWeek2Mon = 81, + BiWeek2Tue = 82, + BiWeek2Wed = 83, + BiWeek2Thu = 84, + BiWeek2Fri = 85, + BiWeek2Sat = 86, + BiWeek2 = 87, // will be replaced by BiWeek2Sun, BiWeek2Mon, etc depending on weekStartsOn +} + +export enum DayOfWeek { + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6, +} + +export enum DateToken { + /** `1982, 1986, 2024` */ + Year_numeric = 'yyy', + /** `82, 86, 24` */ + Year_2Digit = 'yy', + + /** `January, February, ..., December` */ + Month_long = 'MMMM', + /** `Jan, Feb, ..., Dec` */ + Month_short = 'MMM', + /** `01, 02, ..., 12` */ + Month_2Digit = 'MM', + /** `1, 2, ..., 12` */ + Month_numeric = 'M', + + /** `1, 2, ..., 11, 12` */ + Hour_numeric = 'h', + /** `01, 02, ..., 11, 12` */ + Hour_2Digit = 'hh', + /** You should probably not use this. Force with AM/PM (and the good locale), not specifying this will automatically take the good local */ + Hour_wAMPM = 'a', + /** You should probably not use this. Force without AM/PM (and the good locale), not specifying this will automatically take the good local */ + Hour_woAMPM = 'aaaaaa', + + /** `0, 1, ..., 59` */ + Minute_numeric = 'm', + /** `00, 01, ..., 59` */ + Minute_2Digit = 'mm', + + /** `0, 1, ..., 59` */ + Second_numeric = 's', + /** `00, 01, ..., 59` */ + Second_2Digit = 'ss', + + /** `000, 001, ..., 999` */ + MiliSecond_3 = 'SSS', + + /** Minimize digit: `1, 2, 11, ...` */ + DayOfMonth_numeric = 'd', + /** `01, 02, 11, ...` */ + DayOfMonth_2Digit = 'dd', + /** `1st, 2nd, 11th, ...` You can have your local ordinal by passing `ordinalSuffixes` in options / settings */ + DayOfMonth_withOrdinal = 'do', + + /** `M, T, W, T, F, S, S` */ + DayOfWeek_narrow = 'eeeee', + /** `Monday, Tuesday, ..., Sunday` */ + DayOfWeek_long = 'eeee', + /** `Mon, Tue, Wed, ..., Sun` */ + DayOfWeek_short = 'eee', +} + +export type OrdinalSuffixes = { + one: string; + two: string; + few: string; + other: string; + zero?: string; + many?: string; +}; +export type DateFormatVariant = 'short' | 'default' | 'long'; +type DateFormatVariantPreset = { + short?: CustomIntlDateTimeFormatOptions; + default?: CustomIntlDateTimeFormatOptions; + long?: CustomIntlDateTimeFormatOptions; +}; +export type CustomIntlDateTimeFormatOptions = + | string + | string[] + | (Intl.DateTimeFormatOptions & { withOrdinal?: boolean }); + +export type FormatDateOptions = { + weekStartsOn?: DayOfWeek; + variant?: DateFormatVariant | 'custom'; + custom?: CustomIntlDateTimeFormatOptions; +}; + +export interface FormatDateLocaleOptions { + weekStartsOn?: DayOfWeek; + baseParsing?: string; + presets?: { + day?: DateFormatVariantPreset; + dayTime?: DateFormatVariantPreset; + timeOnly?: DateFormatVariantPreset; + week?: DateFormatVariantPreset; + month?: DateFormatVariantPreset; + monthsYear?: DateFormatVariantPreset; + year?: DateFormatVariantPreset; + }; + ordinalSuffixes?: OrdinalSuffixes; +} + +export type FormatDateLocalePresets = Required; diff --git a/packages/svelte-ux/src/lib/utils/format.ts b/packages/svelte-ux/src/lib/utils/format.ts index 8d7b36be3..db4ffa270 100644 --- a/packages/svelte-ux/src/lib/utils/format.ts +++ b/packages/svelte-ux/src/lib/utils/format.ts @@ -10,7 +10,7 @@ import { } from './date'; import { formatNumberWithLocale } from './number'; import type { FormatNumberOptions, FormatNumberStyle } from './number'; -import { knownLocales, type LocaleSettings } from './locale'; +import { defaultLocale, type LocaleSettings } from './locale'; export type FormatType = FormatNumberStyle | PeriodType; @@ -33,7 +33,7 @@ export function format( format?: FormatType, options?: FormatNumberOptions | FormatDateOptions ): any { - return formatWithLocale(knownLocales.en, value, format, options); + return formatWithLocale(defaultLocale, value, format, options); } export function formatWithLocale( diff --git a/packages/svelte-ux/src/lib/utils/index.ts b/packages/svelte-ux/src/lib/utils/index.ts index 6429b6577..f78febf69 100644 --- a/packages/svelte-ux/src/lib/utils/index.ts +++ b/packages/svelte-ux/src/lib/utils/index.ts @@ -2,7 +2,13 @@ export { formatDate, PeriodType, DayOfWeek, DateToken } from './date'; export * from './duration'; export * from './file'; -export * from './format'; +export { + format, + formatWithLocale, + type FormatFunction, + type FormatFunctionProperties, + type FormatFunctions, +} from './format'; export * from './json'; export * from './logger'; export { round, clamp } from './number'; @@ -17,6 +23,15 @@ export * as date from './date'; export * as dateRange from './dateRange'; export * as dom from './dom'; export * as env from './env'; +export { + defaultLocale, + createLocaleSettings, + type LocaleStore, + type LocaleSettings, + type LocaleSettingsInput, + type NumberPresets, + type NumberPresetsOptions, +} from './locale'; // export * as excel from './excel'; // Remove until `await import('exceljs')` works externally export * as map from './map'; export * as number from './number'; diff --git a/packages/svelte-ux/src/lib/utils/locale.ts b/packages/svelte-ux/src/lib/utils/locale.ts index 63b69c73b..e55f01dde 100644 --- a/packages/svelte-ux/src/lib/utils/locale.ts +++ b/packages/svelte-ux/src/lib/utils/locale.ts @@ -6,7 +6,7 @@ import { DayOfWeek, type FormatDateLocaleOptions, type FormatDateLocalePresets, -} from './date'; +} from './date_types'; import type { DictionaryMessages, DictionaryMessagesOptions } from './dictionary'; import type { FormatNumberOptions, FormatNumberStyle } from './number'; @@ -186,11 +186,7 @@ export function createLocaleSettings( return defaultsDeep(localeSettings, base); } -export const knownLocales: Record = { - en: createLocaleSettings({ locale: 'en' }), - // TODO fill in the real values for 'fr' - fr: createLocaleSettings({ locale: 'fr' }), -}; +export const defaultLocale = createLocaleSettings({ locale: 'en' }); export function getAllKnownLocales( additionalLocales?: Record @@ -198,5 +194,5 @@ export function getAllKnownLocales( const additional = additionalLocales ? Object.entries(additionalLocales).map(([key, value]) => [key, createLocaleSettings(value)]) : []; - return { ...knownLocales, ...Object.fromEntries(additional) }; + return { en: defaultLocale, ...Object.fromEntries(additional) }; } diff --git a/packages/svelte-ux/src/lib/utils/number.test.ts b/packages/svelte-ux/src/lib/utils/number.test.ts index b90fd92fe..0cc29872c 100644 --- a/packages/svelte-ux/src/lib/utils/number.test.ts +++ b/packages/svelte-ux/src/lib/utils/number.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { clamp, formatNumber, formatNumberWithLocale, round } from './number'; -import { createLocaleSettings, knownLocales } from './locale'; +import { defaultLocale, createLocaleSettings } from './locale'; describe('clamp()', () => { it('no change', () => { @@ -126,9 +126,14 @@ describe('formatNumber()', () => { }); it('formats number with currency EUR with right local', () => { - const actual = formatNumberWithLocale(knownLocales.fr, 1234.5678, 'currency', { - currency: 'EUR', - }); + const actual = formatNumberWithLocale( + createLocaleSettings({ locale: 'fr' }), + 1234.5678, + 'currency', + { + currency: 'EUR', + } + ); expect(actual).equal('1 234,57 €'); }); diff --git a/packages/svelte-ux/src/lib/utils/number.ts b/packages/svelte-ux/src/lib/utils/number.ts index 9ad8f0f7d..45b51e3a1 100644 --- a/packages/svelte-ux/src/lib/utils/number.ts +++ b/packages/svelte-ux/src/lib/utils/number.ts @@ -1,5 +1,5 @@ import type { Settings } from '../components/settings'; -import { knownLocales, type LocaleSettings } from './locale'; +import { defaultLocale, createLocaleSettings, type LocaleSettings } from './locale'; export type FormatNumberStyle = | 'decimal' // from Intl.NumberFormat options.style NumberFormatOptions @@ -36,7 +36,7 @@ export function formatNumber( style?: FormatNumberStyle, options?: FormatNumberOptions ) { - return formatNumberWithLocale(knownLocales.en, number, style, options); + return formatNumberWithLocale(defaultLocale, number, style, options); } // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat