import { format } from 'date-fns'; import { ru } from 'date-fns/locale'; import { incline, inclineFirstname, inclineLastname, inclineMiddlename } from 'lvovich'; import numberToWordsRuPkg from 'number-to-words-ru'; import { ToWords } from 'to-words'; const convertRu = (numberToWordsRuPkg as any).convert || numberToWordsRuPkg; export interface TransformConfig { type: string; params?: Record; } export type TransformType = | 'none' | 'date_long' | 'date_short' | 'currency_text' | 'currency_number' | 'uppercase' | 'lowercase' | 'initials' | 'declension' | 'name_part' | 'number_to_words'; const toWordsEn = new ToWords({ localeCode: 'en-US' }); export class TransformService { applyTransform( value: string | null | undefined, transform?: TransformConfig | TransformType | string | null, ): string { if (value === null || value === undefined || value === '') return ''; const config = this.normalizeTransform(transform); if (!config || config.type === 'none') return String(value); switch (config.type) { case 'date_long': return this.toDateLong(value); case 'date_short': return this.toDateShort(value); case 'currency_text': return this.toCurrencyText(value, config.params); case 'currency_number': return this.toCurrencyNumber(value); case 'uppercase': return String(value).toUpperCase(); case 'lowercase': return String(value).toLowerCase(); case 'initials': return this.toInitials(value); case 'declension': return this.toDeclension(value, config.params); case 'name_part': case 'namePart': return this.toNamePart(value, config.params); case 'number_to_words': case 'numberToWords': return this.toNumberToWords(value, config.params); default: return String(value); } } private normalizeTransform( transform?: TransformConfig | TransformType | string | null, ): TransformConfig | null { if (!transform) return null; if (typeof transform === 'string') { return { type: transform }; } return transform; } private toDateLong(value: string): string { try { const date = new Date(value); if (isNaN(date.getTime())) return value; return format(date, '«dd» MMMM yyyy\'г.\'', { locale: ru }); } catch { return value; } } private toDateShort(value: string): string { try { const date = new Date(value); if (isNaN(date.getTime())) return value; return format(date, 'dd.MM.yyyy'); } catch { return value; } } private parseNumber(value: string): number | null { const cleaned = String(value) .replace(/\s/g, '') .replace(',', '.'); const num = parseFloat(cleaned); return Number.isFinite(num) ? num : null; } private toCurrencyText(value: string, params?: Record): string { const num = this.parseNumber(value); if (num === null) return value; const currency = params?.currency || 'rub'; try { return convertRu(String(num), { currency }); } catch { return value; } } private toCurrencyNumber(value: string): string { const num = this.parseNumber(value); if (num === null) return value; return num.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } private toInitials(value: string): string { const parts = String(value).trim().split(/\s+/); if (parts.length >= 3) { const [last, first, middle] = parts; return `${last} ${first[0]}.${middle[0]}.`; } if (parts.length === 2) { const [last, first] = parts; return `${last} ${first[0]}.`; } return value; } private toDeclension(value: string, params?: Record): string { const raw = String(value).trim(); if (!raw) return value; const parts = raw.split(/\s+/); const declension = params?.case || 'dative'; if (parts.length >= 3) { const [last, first, middle] = parts; const result = incline({ last, first, middle }, declension); return [result.last, result.first, result.middle].filter(Boolean).join(' '); } if (parts.length === 2) { const [last, first] = parts; const result = incline({ last, first }, declension); return [result.last, result.first].filter(Boolean).join(' '); } if (parts.length === 1) { const [last] = parts; return inclineLastname(last, declension) || value; } return value; } private toNamePart(value: string, params?: Record): string { const raw = String(value).trim(); if (!raw) return value; const parts = raw.split(/\s+/); const part = params?.part || 'firstNameWithMiddleName'; switch (part) { case 'lastName': return parts[0] || ''; case 'firstName': return parts[1] || ''; case 'middleName': return parts[2] || ''; case 'firstNameWithMiddleName': return parts.slice(1, 3).join(' '); case 'shortName': return this.toInitials(value); case 'fullName': default: return value; } } private toNumberToWords(value: string, params?: Record): string { const num = this.parseNumber(value); if (num === null) return value; const currency = params?.currency || 'rub'; try { return convertRu(String(num), { currency }); } catch { return value; } } numberToWords(amount: number, locale: string = 'ru'): string { if (locale === 'ru') { try { return convertRu(String(amount), { currency: 'rub' }); } catch { return String(amount); } } try { return toWordsEn.convert(amount); } catch { return String(amount); } } }