From d2d577c012a5926f6774b1e4741d61d09b26e2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Wed, 27 Sep 2023 15:53:13 +0200 Subject: [PATCH] feat: date-fns --- frontend/app/src/app/app.module.ts | 2 +- .../data/chips/edit-event-selection.html | 4 +- .../app/src/app/modules/data/data.module.ts | 8 +- .../data/list/food-data-list.component.ts | 2 +- .../place/special/mensa/place-mensa.html | 4 +- .../app/modules/profile/page/my-courses.html | 6 +- .../src/app/modules/profile/profile.module.ts | 4 +- .../app/translation/common-string-pipes.ts | 73 --------- .../date-time/format-frequency.pipe.ts | 68 +-------- .../translation/date-time/format-frequency.ts | 22 +++ .../date-time/format-relative-date.pipe.ts | 66 +------- .../date-time/format-relative-date.ts | 23 +++ .../date-time/format-relative-live.pipe.ts | 57 ------- .../date-time/opening-hours.pipe.ts | 141 ------------------ .../app/src/app/translation/dfns-locale.ts | 35 ++++- .../translation/thing-translate.service.ts | 2 +- frontend/app/src/assets/i18n/de.json | 14 ++ frontend/app/src/assets/i18n/en.json | 14 ++ pnpm-lock.yaml | 22 +-- 19 files changed, 139 insertions(+), 428 deletions(-) create mode 100644 frontend/app/src/app/translation/date-time/format-frequency.ts create mode 100644 frontend/app/src/app/translation/date-time/format-relative-date.ts delete mode 100644 frontend/app/src/app/translation/date-time/format-relative-live.pipe.ts delete mode 100644 frontend/app/src/app/translation/date-time/opening-hours.pipe.ts diff --git a/frontend/app/src/app/app.module.ts b/frontend/app/src/app/app.module.ts index e1b3c561..e5adcaf5 100644 --- a/frontend/app/src/app/app.module.ts +++ b/frontend/app/src/app/app.module.ts @@ -101,7 +101,7 @@ export function initializerFactory( // this language will be used as a fallback when a translation isn't found in the current language translateService.setDefaultLang('en'); translateService.use(languageCode); - const dateFnsLocale = await getDateFnsLocale(languageCode as SCLanguageCode); + const dateFnsLocale = await getDateFnsLocale(languageCode as SCLanguageCode, translateService); setDefaultOptions({locale: dateFnsLocale}); dateFnsConfigurationService.setLocale(dateFnsLocale); diff --git a/frontend/app/src/app/modules/data/chips/edit-event-selection.html b/frontend/app/src/app/modules/data/chips/edit-event-selection.html index bac998d2..c9851109 100644 --- a/frontend/app/src/app/modules/data/chips/edit-event-selection.html +++ b/frontend/app/src/app/modules/data/chips/edit-event-selection.html @@ -32,8 +32,8 @@ > {{ frequency.children[0].item.repeatFrequency ? (frequency.children[0].item.repeatFrequency | - dfnsParseDuration | dfnsFormatFrequency | sentencecase) : ('data.chips.add_events.popover.SINGLE' | - translate | titlecase) }} + dfnsParseDuration | dfnsFormatFrequencyPure | sentencecase) : + ('data.chips.add_events.popover.SINGLE' | translate | titlecase) }} diff --git a/frontend/app/src/app/modules/data/data.module.ts b/frontend/app/src/app/modules/data/data.module.ts index cb993fc7..7106f0c7 100644 --- a/frontend/app/src/app/modules/data/data.module.ts +++ b/frontend/app/src/app/modules/data/data.module.ts @@ -111,8 +111,8 @@ import { ParseIsoPipeModule, } from 'ngx-date-fns'; import {ParseDurationPipe} from '../../translation/date-time/parse-duration.pipe'; -import {DfnsFormatFrequencyPipe} from '../../translation/date-time/format-frequency.pipe'; -import {FormatRelativeDatePipe} from '../../translation/date-time/format-relative-date.pipe'; +import {DfnsFormatFrequencyPurePipe} from '../../translation/date-time/format-frequency.pipe'; +import {DfnsFormatRelativeDatePurePipe} from '../../translation/date-time/format-relative-date.pipe'; /** * Module for handling data @@ -207,9 +207,9 @@ import {FormatRelativeDatePipe} from '../../translation/date-time/format-relativ ParseDurationPipe, FormatDurationPipeModule, FormatRelativeToNowPipeModule, - DfnsFormatFrequencyPipe, + DfnsFormatFrequencyPurePipe, FormatDistanceToNowPipeModule, - FormatRelativeDatePipe, + DfnsFormatRelativeDatePurePipe, ], providers: [ CoordinatedSearchProvider, diff --git a/frontend/app/src/app/modules/data/list/food-data-list.component.ts b/frontend/app/src/app/modules/data/list/food-data-list.component.ts index 6379d815..3728e33c 100644 --- a/frontend/app/src/app/modules/data/list/food-data-list.component.ts +++ b/frontend/app/src/app/modules/data/list/food-data-list.component.ts @@ -16,7 +16,7 @@ import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MapPosition, PositionService} from '../../map/position.service'; import {Geolocation} from '@capacitor/geolocation'; import {BehaviorSubject, from} from 'rxjs'; -import {pauseWhen} from '../../../util/pause-when'; +import {pauseWhen} from '../../../util/rxjs/pause-when'; import {map, retry, startWith, take} from 'rxjs/operators'; import {SCSearchFilter, SCSearchSort} from '@openstapps/core'; diff --git a/frontend/app/src/app/modules/data/types/place/special/mensa/place-mensa.html b/frontend/app/src/app/modules/data/types/place/special/mensa/place-mensa.html index 31beccb8..3464d5eb 100644 --- a/frontend/app/src/app/modules/data/types/place/special/mensa/place-mensa.html +++ b/frontend/app/src/app/modules/data/types/place/special/mensa/place-mensa.html @@ -19,10 +19,10 @@ {{ day.key | dfnsParseIso | dfnsFormatRelativeDate | sentencecase }}{{ day.key | dfnsParseIso | dfnsFormatRelativeDatePure | sentencecase }} {{ day.key | dfnsParseIso | dfnsFormatRelativeDate | sentencecase }}{{ day.key | dfnsParseIso | dfnsFormatRelativeDatePure | sentencecase }} diff --git a/frontend/app/src/app/modules/profile/page/my-courses.html b/frontend/app/src/app/modules/profile/page/my-courses.html index 0daa793a..e811324e 100644 --- a/frontend/app/src/app/modules/profile/page/my-courses.html +++ b/frontend/app/src/app/modules/profile/page/my-courses.html @@ -7,9 +7,9 @@ {{ myCoursesDay[0] | dfnsParseIso | dfnsFormatRelativeDate | titlecase }} - {{ ('profile.courses.' + - (myCoursesDay[1].length === 0 ? 'NO' : myCoursesDay[1].length === 1 ? 'ONE' : 'MANY' ) + '_EVENT') | - translate: {count: myCoursesDay[1].length} }}{{ myCoursesDay[0] | dfnsParseIso | dfnsFormatRelativeDatePure | titlecase }} - {{ + ('profile.courses.' + (myCoursesDay[1].length === 0 ? 'NO' : myCoursesDay[1].length === 1 ? 'ONE' : + 'MANY' ) + '_EVENT') | translate: {count: myCoursesDay[1].length} }} diff --git a/frontend/app/src/app/modules/profile/profile.module.ts b/frontend/app/src/app/modules/profile/profile.module.ts index b6121a65..5de080e5 100644 --- a/frontend/app/src/app/modules/profile/profile.module.ts +++ b/frontend/app/src/app/modules/profile/profile.module.ts @@ -27,7 +27,7 @@ import {ThingTranslateModule} from '../../translation/thing-translate.module'; import {DataModule} from '../data/data.module'; import {MyCoursesComponent} from './page/my-courses.component'; import {FormatPipeModule, ParseIsoPipeModule} from 'ngx-date-fns'; -import {FormatRelativeDatePipe} from '../../translation/date-time/format-relative-date.pipe'; +import {DfnsFormatRelativeDatePurePipe} from '../../translation/date-time/format-relative-date.pipe'; const routes: Routes = [ { @@ -50,7 +50,7 @@ const routes: Routes = [ ThingTranslateModule, DataModule, FormatPipeModule, - FormatRelativeDatePipe, + DfnsFormatRelativeDatePurePipe, ParseIsoPipeModule, ], }) diff --git a/frontend/app/src/app/translation/common-string-pipes.ts b/frontend/app/src/app/translation/common-string-pipes.ts index 4ef5dcd5..529e80e4 100644 --- a/frontend/app/src/app/translation/common-string-pipes.ts +++ b/frontend/app/src/app/translation/common-string-pipes.ts @@ -96,79 +96,6 @@ export class StringSplitPipe implements PipeTransform { return this.value as never; } } -@Injectable() -@Pipe({ - name: 'durationLocalized', - pure: true, -}) -export class DurationLocalizedPipe implements PipeTransform, OnDestroy { - locale: string; - - onLangChange?: Subscription; - - value: string; - - frequencyPrefixes: {[iso6391Code: string]: string} = { - de: 'alle', - en: 'every', - es: 'cada', - pt: 'a cada', - fr: 'tous les', - cn: '每', - ru: 'kаждые', - }; - - constructor(private readonly translate: TranslateService) { - this.locale = translate.currentLang; - } - - private _dispose(): void { - if (this.onLangChange?.closed === false) { - this.onLangChange?.unsubscribe(); - } - } - - ngOnDestroy(): void { - this._dispose(); - } - - /** - * @param value An ISO 8601 duration string - * @param isFrequency Boolean indicating if this duration is to be interpreted as repeat frequency - */ - transform(value: string | unknown, isFrequency = false): string { - this.updateValue(value, isFrequency); - this._dispose(); - if (this.onLangChange?.closed === true) { - this.onLangChange = this.translate.onLangChange.subscribe((event: LangChangeEvent) => { - this.locale = event.lang; - this.updateValue(value, isFrequency); - }); - } - - return this.value; - } - - updateValue(value: string | unknown, isFrequency = false): void { - if (typeof value !== 'string') { - logger.warn(`durationLocalized pipe unable to parse input: ${value}`); - - return; - } - - if (isFrequency) { - const fequencyPrefix = Object.keys(this.frequencyPrefixes).filter(element => - this.locale.includes(element), - ); - this.value = [ - fequencyPrefix.length > 0 ? this.frequencyPrefixes[fequencyPrefix[0]] : this.frequencyPrefixes.en, - moment.duration(value).humanize(), - ].join(' '); - } else { - this.value = moment.duration(value).humanize(); - } - } -} @Injectable() @Pipe({ diff --git a/frontend/app/src/app/translation/date-time/format-frequency.pipe.ts b/frontend/app/src/app/translation/date-time/format-frequency.pipe.ts index 471cee0c..00ac5429 100644 --- a/frontend/app/src/app/translation/date-time/format-frequency.pipe.ts +++ b/frontend/app/src/app/translation/date-time/format-frequency.pipe.ts @@ -1,68 +1,14 @@ -import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core'; -import {Subscription} from 'rxjs'; -import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; -import {formatDuration} from 'date-fns'; +import {Injectable, Pipe, PipeTransform} from '@angular/core'; +import {formatFrequency} from './format-frequency'; @Injectable() @Pipe({ - name: 'dfnsFormatFrequency', + name: 'dfnsFormatFrequencyPure', standalone: true, - pure: false, + pure: true, }) -export class DfnsFormatFrequencyPipe implements PipeTransform, OnDestroy { - locale: string; - - onLangChange?: Subscription; - - value: string; - - frequencyPrefixes: {[iso6391Code: string]: string} = { - de: 'alle', - en: 'every', - es: 'cada', - pt: 'a cada', - fr: 'tous les', - cn: '每', - ru: 'kаждые', - }; - - constructor(private readonly translate: TranslateService) { - this.locale = translate.currentLang; - } - - private _dispose(): void { - if (this.onLangChange?.closed === false) { - this.onLangChange?.unsubscribe(); - } - } - - ngOnDestroy(): void { - this._dispose(); - } - - /** - * @param value An ISO 8601 duration string - */ - transform(value: Duration): string { - this.updateValue(value); - this._dispose(); - if (this.onLangChange?.closed === true) { - this.onLangChange = this.translate.onLangChange.subscribe((event: LangChangeEvent) => { - this.locale = event.lang; - this.updateValue(value); - }); - } - - return this.value; - } - - updateValue(value: Duration): void { - const fequencyPrefix = Object.keys(this.frequencyPrefixes).filter(element => - this.locale.includes(element), - ); - this.value = [ - fequencyPrefix.length > 0 ? this.frequencyPrefixes[fequencyPrefix[0]] : this.frequencyPrefixes.en, - formatDuration(value), - ].join(' '); +export class DfnsFormatFrequencyPurePipe implements PipeTransform { + transform(duration: Duration): string { + return formatFrequency(duration); } } diff --git a/frontend/app/src/app/translation/date-time/format-frequency.ts b/frontend/app/src/app/translation/date-time/format-frequency.ts new file mode 100644 index 00000000..c24a1684 --- /dev/null +++ b/frontend/app/src/app/translation/date-time/format-frequency.ts @@ -0,0 +1,22 @@ +import {Duration, formatDuration, getDefaultOptions} from 'date-fns'; +import {LocaleExtension} from '../dfns-locale'; + +/** + * Formats a duration to a frequency + * @param duration the duration to format as a frequency + * @example "alle 3 Tage" + */ +export function formatFrequency(duration: Duration): string { + const {locale} = getDefaultOptions() as {locale: LocaleExtension}; + locale.formatFrequencyOptions; + + // This will break... + const formatted = formatDuration(duration); + const plural = new Intl.PluralRules(locale.code).select(Number(/\d+/.exec(formatted)?.[0])); + const formatString = locale.formatFrequencyOptions[plural] ?? locale.formatFrequencyOptions['many']!; + + return formatString + .replaceAll(/(['"](?=\w))|((?<=\w)['"])/g, '') + .replaceAll(/{{\s*duration\s*}}/g, formatted) + .replaceAll(/{{\s*suffix\s*}}/g, formatted.replace(/^\d+\s*/, '')); +} diff --git a/frontend/app/src/app/translation/date-time/format-relative-date.pipe.ts b/frontend/app/src/app/translation/date-time/format-relative-date.pipe.ts index 71118e07..5d0d310e 100644 --- a/frontend/app/src/app/translation/date-time/format-relative-date.pipe.ts +++ b/frontend/app/src/app/translation/date-time/format-relative-date.pipe.ts @@ -1,67 +1,13 @@ import {Pipe, PipeTransform} from '@angular/core'; -import {DateFnsConfigurationService} from 'ngx-date-fns'; -import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; -import {formatRelative} from 'date-fns'; - -const formatRelativeLocaleEn = { - lastWeek: "'last' eeee", - yesterday: "'yesterday'", - today: "'today'", - tomorrow: "'tomorrow'", - nextWeek: "'next' eeee", - other: 'pp', -}; - -const formatRelativeLocaleDe = { - lastWeek: "'letzten' eeee", - yesterday: "'gestern'", - today: "'heute'", - tomorrow: "'morgen'", - nextWeek: 'eeee', - other: 'pp', -}; +import {formatRelativeDate} from './format-relative-date'; @Pipe({ - name: 'dfnsFormatRelativeDate', - pure: false, + name: 'dfnsFormatRelativeDatePure', + pure: true, standalone: true, }) -export class FormatRelativeDatePipe implements PipeTransform { - locale: Locale; - - value: string; - - input: Date | number; - - base: Date | number; - - constructor(private dfnsConfig: DateFnsConfigurationService) { - this.updateLocale(); - dfnsConfig.localeChanged.pipe(takeUntilDestroyed()).subscribe(this.updateLocale.bind(this)); - } - - private updateLocale() { - this.locale = {...this.dfnsConfig.locale()!}; - const format = { - ['de']: formatRelativeLocaleDe, - ['en-US']: formatRelativeLocaleEn, - }[this.locale.code!]; - this.locale.formatRelative = function (token) { - return format![token as keyof typeof format] as string; - }; - this.updateValue(); - } - - transform(value: Date | number, other: Date | number = Date.now()): string { - this.input = value; - this.base = other; - this.updateValue(); - - return this.value; - } - - updateValue() { - if (!this.input || !this.base) return; - this.value = formatRelative(this.input, this.base, {locale: this.locale}); +export class DfnsFormatRelativeDatePurePipe implements PipeTransform { + transform(date: Date | number, baseDate: Date | number = Date.now()): string { + return formatRelativeDate(date, baseDate); } } diff --git a/frontend/app/src/app/translation/date-time/format-relative-date.ts b/frontend/app/src/app/translation/date-time/format-relative-date.ts new file mode 100644 index 00000000..6709880f --- /dev/null +++ b/frontend/app/src/app/translation/date-time/format-relative-date.ts @@ -0,0 +1,23 @@ +import {formatRelative, getDefaultOptions} from 'date-fns'; +import {LocaleExtension} from '../dfns-locale'; + +/** + * @see {formatRelative} + */ +export function formatRelativeDate( + date: Date | number, + baseDate: Date | number, + options?: { + locale?: LocaleExtension; + weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; + }, +): string { + const locale = options?.locale ?? (getDefaultOptions() as {locale: LocaleExtension}).locale; + const customLocale: LocaleExtension = { + ...locale, + formatRelative(token: keyof LocaleExtension['formatRelativeDateOptions']) { + return locale.formatRelativeDateOptions![token]; + }, + }; + return formatRelative(date, baseDate, {locale: customLocale, weekStartsOn: options?.weekStartsOn}); +} diff --git a/frontend/app/src/app/translation/date-time/format-relative-live.pipe.ts b/frontend/app/src/app/translation/date-time/format-relative-live.pipe.ts deleted file mode 100644 index d4924f49..00000000 --- a/frontend/app/src/app/translation/date-time/format-relative-live.pipe.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {OnDestroy, Pipe, PipeTransform} from '@angular/core'; -import {Subscription} from 'rxjs'; -import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; -import {formatDuration} from 'date-fns'; - -@Pipe({ - name: 'dfnsFormatRelativeLive', - pure: false, - standalone: true, -}) -export class FormatRelativeLivePipe implements PipeTransform, OnDestroy { - locale: string; - - onLangChange?: Subscription; - - value: string; - - constructor(private readonly translate: TranslateService) { - this.locale = translate.currentLang; - } - - private _dispose(): void { - if (this.onLangChange?.closed === false) { - this.onLangChange?.unsubscribe(); - } - } - - ngOnDestroy(): void { - this._dispose(); - } - - /** - * @param value An ISO 8601 duration string - */ - transform(value: Duration): string { - this.updateValue(value); - this._dispose(); - if (this.onLangChange?.closed === true) { - this.onLangChange = this.translate.onLangChange.subscribe((event: LangChangeEvent) => { - this.locale = event.lang; - this.updateValue(value); - }); - } - - return this.value; - } - - updateValue(value: Duration): void { - const fequencyPrefix = Object.keys(this.frequencyPrefixes).filter(element => - this.locale.includes(element), - ); - this.value = [ - fequencyPrefix.length > 0 ? this.frequencyPrefixes[fequencyPrefix[0]] : this.frequencyPrefixes.en, - formatDuration(value), - ].join(' '); - } -} diff --git a/frontend/app/src/app/translation/date-time/opening-hours.pipe.ts b/frontend/app/src/app/translation/date-time/opening-hours.pipe.ts deleted file mode 100644 index 4f946ea0..00000000 --- a/frontend/app/src/app/translation/date-time/opening-hours.pipe.ts +++ /dev/null @@ -1,141 +0,0 @@ -import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core'; -import {Subscription} from 'rxjs'; -import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; -import {logger} from '../../_helpers/ts-logger'; -import opening_hours from 'opening_hours'; -import {addHours, formatRelative, isBefore, isToday} from 'date-fns'; - -@Injectable() -@Pipe({ - name: 'openingHours', - standalone: true, - pure: false, -}) -export class OpeningHoursPipe implements PipeTransform, OnDestroy { - locale: string; - - onLangChange?: Subscription; - - value: string[] = []; - - constructor(private readonly translate: TranslateService) { - this.locale = translate.currentLang; - } - - private _dispose(): void { - if (this.onLangChange?.closed === false) { - this.onLangChange?.unsubscribe(); - } - } - - ngOnDestroy(): void { - this._dispose(); - } - - transform(aString: string | unknown): string[] { - this.updateValue(aString); - this._dispose(); - if (this.onLangChange?.closed === true) { - this.onLangChange = this.translate.onLangChange.subscribe((event: LangChangeEvent) => { - this.locale = event.lang; - this.updateValue(aString); - }); - } - - return this.value; - } - - updateValue(aString: string | unknown) { - if (typeof aString !== 'string') { - logger.warn(`openingHours pipe unable to parse input: ${aString}`); - - return; - } - let openingHours; - - try { - openingHours = new opening_hours(aString, { - address: { - country_code: 'de', - state: 'Hessen', - }, - lon: 8.667_97, - lat: 50.129_16, - }); - } catch (error) { - logger.warn(error); - this.value = []; - - return; - } - - const isOpen: boolean = openingHours.getState(); - const isUnknown: boolean = openingHours.getUnknown(); - - const nextChange = openingHours.getNextChange(); - const nextChangeIsOpen: boolean = openingHours.getState(nextChange); - const nextChangeUnknown: boolean = openingHours.getUnknown(nextChange); - const nextChangeIsToday: boolean = isToday(nextChange!); - - let stateKey = isOpen ? 'common.openingHours.state_open' : 'common.openingHours.state_closed'; - - stateKey = isUnknown ? 'common.openingHours.state_maybe' : stateKey; - - this.value = [isOpen ? 'success' : 'danger', `${this.translate.instant(stateKey)}`]; - - if (isUnknown) { - const comment = openingHours.getComment(); - this.value = ['light', `${this.translate.instant(stateKey)}`]; - if (typeof comment === 'string') { - this.value.push(comment); - } - return; - } - - if (nextChangeUnknown) { - return; - } - - let nextChangeKey: string | undefined; - - let formattedCalender = formatRelative(nextChange!, Date.now()); - - if (isBefore(nextChange!, addHours(Date.now(), 1))) { - this.value[0] = 'warning'; - nextChangeKey = nextChangeIsOpen - ? 'common.openingHours.opening_soon_warning' - : 'common.openingHours.closing_soon_warning'; - this.value.push( - `${this.translate.instant(nextChangeKey, { - time: new Intl.DateTimeFormat(this.locale, { - timeStyle: 'short', - }).format(nextChange), - })}`, - ); - return; - } - - if (nextChangeIsToday) { - nextChangeKey = nextChangeIsOpen - ? 'common.openingHours.opening_today' - : 'common.openingHours.closing_today'; - this.value.push( - `${this.translate.instant(nextChangeKey, { - time: new Intl.DateTimeFormat(this.locale, { - timeStyle: 'short', - }).format(nextChange), - })}`, - ); - return; - } - - nextChangeKey = nextChangeIsOpen ? 'common.openingHours.opening' : 'common.openingHours.closing'; - formattedCalender = formattedCalender.slice(0, 1).toUpperCase() + formattedCalender.slice(1); - this.value.push( - `${this.translate.instant(nextChangeKey, { - relativeDateTime: formattedCalender, - })}`, - ); - return; - } -} diff --git a/frontend/app/src/app/translation/dfns-locale.ts b/frontend/app/src/app/translation/dfns-locale.ts index 65f2bc2e..28ed9a49 100644 --- a/frontend/app/src/app/translation/dfns-locale.ts +++ b/frontend/app/src/app/translation/dfns-locale.ts @@ -14,6 +14,19 @@ */ import {SCLanguageCode} from '@openstapps/core'; import type {Locale} from 'date-fns'; +import {TranslateService} from '@ngx-translate/core'; + +export interface LocaleExtension extends Locale { + formatFrequencyOptions: Partial>; + formatRelativeDateOptions: { + lastWeek: string; + yesterday: string; + today: string; + tomorrow: string; + nextWeek: string; + other: string; + }; +} type LocalesMap = Record Promise<{default: Locale}>>; @@ -25,11 +38,23 @@ const LOCALES = { /** * Get a Date Fns Locale */ -export async function getDateFnsLocale(code: SCLanguageCode): Promise { - if (code in LOCALES) { - return LOCALES[code as keyof typeof LOCALES]().then(it => it.default); - } else { +export async function getDateFnsLocale( + code: SCLanguageCode, + translator: TranslateService, +): Promise { + if (!(code in LOCALES)) { console.warn(`Unknown Locale "${code}" for Date Fns. Falling back to English.`); - return LOCALES.en().then(it => it.default); } + const key = code in LOCALES ? (code as keyof typeof LOCALES) : 'en'; + const translations = translator.translations[translator.currentLang]; + const frequencyExtension = translations['dateFns']['FORMAT_FREQUENCY']; + const relativeDateExtension = translations['dateFns']['FORMAT_RELATIVE_DATE']; + + return LOCALES[key]().then(it => { + const locale = it.default as LocaleExtension; + locale.formatFrequencyOptions = {...frequencyExtension}; + locale.formatRelativeDateOptions = {...relativeDateExtension}; + + return locale; + }); } diff --git a/frontend/app/src/app/translation/thing-translate.service.ts b/frontend/app/src/app/translation/thing-translate.service.ts index 77d5d89c..a27cee09 100644 --- a/frontend/app/src/app/translation/thing-translate.service.ts +++ b/frontend/app/src/app/translation/thing-translate.service.ts @@ -54,7 +54,7 @@ export class ThingTranslateService { /** set the default language from configuration */ this.translateService.onLangChange.pipe(takeUntilDestroyed()).subscribe((event: LangChangeEvent) => { this.translator.language = event.lang as keyof SCTranslations; - getDateFnsLocale(event.lang as SCLanguageCode).then(locale => { + getDateFnsLocale(event.lang as SCLanguageCode, translateService).then(locale => { setDefaultOptions({locale}); this.dfnsConfiguration.setLocale(locale); }); diff --git a/frontend/app/src/assets/i18n/de.json b/frontend/app/src/assets/i18n/de.json index a49a6a89..74efa6e3 100644 --- a/frontend/app/src/assets/i18n/de.json +++ b/frontend/app/src/assets/i18n/de.json @@ -8,6 +8,20 @@ "export": "Exportieren", "share": "Teilen", "timeSuffix": "Uhr", + "dateFns": { + "FORMAT_FREQUENCY": { + "one": "'jede' {{suffix}}", + "many": "'alle' {{duration}}" + }, + "FORMAT_RELATIVE_DATE": { + "lastWeek": "'letzten' eeee", + "yesterday": "'gestern'", + "today": "'heute'", + "tomorrow": "'morgen'", + "nextWeek": "'nächsten' eeee", + "other": "pp" + } + }, "ratings": { "thank_you": "Vielen Dank für die Bewertung!" }, diff --git a/frontend/app/src/assets/i18n/en.json b/frontend/app/src/assets/i18n/en.json index 115792f7..b8c950a3 100644 --- a/frontend/app/src/assets/i18n/en.json +++ b/frontend/app/src/assets/i18n/en.json @@ -8,6 +8,20 @@ "export": "Export", "share": "Share", "timeSuffix": "", + "dateFns": { + "FORMAT_FREQUENCY": { + "one": "'every' {{suffix}}", + "many": "'every' {{duration}}" + }, + "FORMAT_RELATIVE_DATE": { + "lastWeek": "'last' eeee", + "yesterday": "'yesterday'", + "today": "'today'", + "tomorrow": "'tomorrow'", + "nextWeek": "'next' eeee", + "other": "pp" + } + }, "ratings": { "thank_you": "Thank you for your feedback!" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09f728a4..ee0130b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -842,6 +842,9 @@ importers: deepmerge: specifier: 4.3.1 version: 4.3.1 + duration-fns: + specifier: 3.0.2 + version: 3.0.2 form-data: specifier: 4.0.0 version: 4.0.0 @@ -863,9 +866,6 @@ importers: material-symbols: specifier: 0.10.0 version: 0.10.0 - moment: - specifier: 2.29.4 - version: 2.29.4 ngx-date-fns: specifier: 10.0.1 version: 10.0.1(@angular/common@16.1.4)(@angular/core@16.1.4)(date-fns@2.30.0) @@ -875,9 +875,6 @@ importers: ngx-markdown: specifier: 16.0.0 version: 16.0.0(@angular/common@16.1.4)(@angular/core@16.1.4)(@angular/platform-browser@16.1.4)(@types/marked@4.3.1)(marked@4.3.0)(rxjs@7.8.1)(zone.js@0.13.1) - ngx-moment: - specifier: 6.0.2 - version: 6.0.2(moment@2.29.4) opening_hours: specifier: 3.8.0 version: 3.8.0 @@ -10328,6 +10325,10 @@ packages: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: true + /duration-fns@3.0.2: + resolution: {integrity: sha512-w82IXh/6aWNHFA0qlQazJYJrZTWieTItuuGTE7YX4cxPaZTWhmVImbsBBiMK1/OhGDgiinuCpJoSFILYLDSKDg==} + dev: false + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -14333,15 +14334,6 @@ packages: prismjs: 1.29.0 dev: false - /ngx-moment@6.0.2(moment@2.29.4): - resolution: {integrity: sha512-HUvDyoJPZKLA3tc+GMQqDpVyCVT2SPfEiV7/CGj2Dwwsn//JhhQ8eTr+RzKqBzLysrXkCwlzulVVJaJ5A0FJEA==} - peerDependencies: - moment: ^2.19.3 - dependencies: - moment: 2.29.4 - tslib: 2.4.1 - dev: false - /nice-napi@1.0.2: resolution: {integrity: sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==} os: ['!win32']