Files
openstapps/src/app/translation/common-string-pipes.ts
2021-09-21 06:51:33 +00:00

487 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2020-2021 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import moment from 'moment';
import {Subscription} from 'rxjs';
import {logger} from '../_helpers/ts-logger';
// TODO: use import for opening_hours when the change is published with a new update
// see https://github.com/opening-hours/opening_hours.js/pull/407
// eslint-disable-next-line @typescript-eslint/no-var-requires, unicorn/prefer-module
const ohFunction = require('opening_hours');
@Injectable()
@Pipe({
name: 'join',
pure: true,
})
export class ArrayJoinPipe implements PipeTransform {
value = '';
transform(anArray: unknown[] | unknown, separator: string | unknown): string {
if (typeof separator !== 'string' || separator.length <= 0) {
return this.value;
}
if (!Array.isArray(anArray)) {
throw new SyntaxError(
`Wrong parameter in ArrayJoinPipe. Expected a valid Array, received: ${anArray}`,
);
}
this.value = anArray.join(separator);
return this.value;
}
}
@Injectable()
@Pipe({
name: 'sentencecase',
pure: true,
})
export class SentenceCasePipe implements PipeTransform {
value = '';
transform(aString: string | unknown): string {
if (typeof aString !== 'string') {
throw new SyntaxError(
`Wrong parameter in StringSplitPipe. Expected a valid String, received: ${aString}`,
);
}
this.value = aString.slice(0, 1).toUpperCase() + aString.slice(1);
return this.value;
}
}
@Injectable()
@Pipe({
name: 'split',
pure: true,
})
export class StringSplitPipe implements PipeTransform {
value = new Array<unknown>();
transform(aString: string | unknown, splitter: string | unknown): unknown[] {
if (typeof splitter !== 'string' || splitter.length <= 0) {
return this.value as never;
}
if (typeof aString !== 'string') {
throw new SyntaxError(
`Wrong parameter in StringSplitPipe. Expected a valid String, received: ${aString}`,
);
}
this.value = aString.split(splitter);
return this.value as never;
}
}
@Injectable()
@Pipe({
name: 'openingHours',
pure: true,
})
export class OpeningHoursPipe implements PipeTransform, OnDestroy {
locale: string;
onLangChange?: Subscription;
value = '';
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 ohFunction(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 nextChange: Date = openingHours.getNextChange();
let prefixKey = isOpen
? 'common.openingHours.open_until'
: 'common.openingHours.closed_until';
let formattedCalender = moment(nextChange).calendar();
if (moment(nextChange).isBefore(moment().add(1, 'hours'))) {
prefixKey = isOpen
? 'common.openingHours.closing_soon'
: 'common.openingHours.opening_soon';
formattedCalender =
formattedCalender.slice(0, 1).toUpperCase() +
formattedCalender.slice(1);
}
this.value = `${this.translate.instant(prefixKey)} ${formattedCalender}`;
}
}
@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({
name: 'metersLocalized',
pure: false,
})
export class MetersLocalizedPipe implements PipeTransform, OnDestroy {
locale: string;
onLangChange?: Subscription;
value = '';
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(value: string | number | unknown): 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: string | number | unknown) {
if (typeof value !== 'string' && typeof value !== 'number') {
logger.warn(`metersLocalized pipe unable to parse input: ${value}`);
return;
}
const imperialLocale = ['US', 'UK', 'LR', 'MM'].some(term =>
this.locale.includes(term),
);
const meters =
typeof value === 'string' ? Number.parseFloat(value) : (value as number);
if (imperialLocale) {
const yards = meters * 1.0936;
const options = {
style: 'unit',
unit: yards >= 1760 ? 'mile' : 'yard',
maximumFractionDigits: yards >= 1760 ? 1 : 0,
} as unknown as Intl.NumberFormatOptions;
this.value = new Intl.NumberFormat(this.locale, options).format(
yards >= 1760 ? yards / 1760 : yards,
);
} else {
const options = {
style: 'unit',
unit: meters >= 1000 ? 'kilometer' : 'meter',
maximumFractionDigits: meters >= 1000 ? 1 : 0,
} as unknown as Intl.NumberFormatOptions;
this.value = new Intl.NumberFormat(this.locale, options).format(
meters >= 1000 ? meters / 1000 : meters,
);
}
}
}
@Injectable()
@Pipe({
name: 'numberLocalized',
pure: true,
})
export class NumberLocalizedPipe 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 The number to be formatted
* @param formatOptions Formatting options to include.
* As specified by Intl.NumberFormatOptions as comma seperated key:value pairs.
*/
transform(value: string | number | unknown, formatOptions?: string): string {
this.updateValue(value, formatOptions);
this._dispose();
if (this.onLangChange?.closed === true) {
this.onLangChange = this.translate.onLangChange.subscribe(
(event: LangChangeEvent) => {
this.locale = event.lang;
this.updateValue(value, formatOptions);
},
);
}
return this.value;
}
updateValue(value: string | number | unknown, formatOptions?: string): void {
if (typeof value !== 'string' && typeof value !== 'number') {
logger.warn(`numberLocalized pipe unable to parse input: ${value}`);
return;
}
const options = formatOptions
?.split(',')
.map(element => element.split(':'))
// eslint-disable-next-line unicorn/no-array-reduce
.reduce(
(accumulator, [key, value_]) => ({
...accumulator,
[key.trim()]: value_.trim(),
}),
{},
) as Intl.NumberFormatOptions;
const float =
typeof value === 'string' ? Number.parseFloat(value) : (value as number);
this.value = new Intl.NumberFormat(this.locale, options).format(float);
}
}
@Injectable()
@Pipe({
name: 'dateFormat',
pure: true,
})
export class DateLocalizedFormatPipe 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 The date to be formatted
* @param formatOptions Dateformat options to include.
* As specified by Intl.DateTimeFormatOptions as comma seperated key:value pairs
* Default is year,month,day,hour and minute in numeric representation e.g. (en-US) "8/6/2021, 10:35"
*/
transform(value: string | unknown, formatOptions?: string): string {
this.updateValue(value, formatOptions);
this._dispose();
if (this.onLangChange?.closed === true) {
this.onLangChange = this.translate.onLangChange.subscribe(
(event: LangChangeEvent) => {
this.locale = event.lang;
this.updateValue(value, formatOptions);
},
);
}
return this.value;
}
updateValue(value: string | Date | unknown, formatOptions?: string): void {
if (
typeof value !== 'string' &&
Object.prototype.toString.call(value) !== '[object Date]'
) {
logger.warn(`dateFormat pipe unable to parse input: ${value}`);
return;
}
const options = formatOptions
?.split(',')
.map(element => element.split(':'))
// eslint-disable-next-line unicorn/no-array-reduce
.reduce(
(accumulator, [key, value_]) => ({
...accumulator,
[key.trim()]: value_.trim(),
}),
{},
) as Intl.DateTimeFormatOptions;
const date =
typeof value === 'string' ? Date.parse(value) : (value as Date);
this.value = new Intl.DateTimeFormat(
this.locale,
options ?? {
day: 'numeric',
month: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
},
).format(date);
}
}