"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DEFAULT_NOTIFICATION_LOCALE = exports.SUPPORTED_NOTIFICATION_LOCALES = void 0; exports.coerceNotificationLocale = coerceNotificationLocale; exports.getNotificationTextDirection = getNotificationTextDirection; exports.formatLocalizedDate = formatLocalizedDate; exports.formatLocalizedCurrency = formatLocalizedCurrency; exports.localizeSubscriptionStatus = localizeSubscriptionStatus; exports.localizeInvoiceStatus = localizeInvoiceStatus; exports.localizeBillingPeriod = localizeBillingPeriod; exports.localizePlanName = localizePlanName; exports.resolveNotificationLocale = resolveNotificationLocale; exports.resolveNotificationTemplate = resolveNotificationTemplate; exports.renderNotificationText = renderNotificationText; exports.renderLocalizedEmailHtml = renderLocalizedEmailHtml; const prisma_1 = require("../lib/prisma"); exports.SUPPORTED_NOTIFICATION_LOCALES = ['en', 'fr', 'ar']; exports.DEFAULT_NOTIFICATION_LOCALE = 'en'; const localeConfig = { en: { intl: 'en-US', direction: 'ltr' }, fr: { intl: 'fr-FR', direction: 'ltr' }, ar: { intl: 'ar-MA', direction: 'rtl' }, }; const subscriptionStatusLabels = { en: { trialing: 'Trial active', active: 'Active', payment_pending: 'Payment pending', past_due: 'Payment overdue', suspended: 'Suspended', cancelled: 'Canceled', canceled: 'Canceled', expired: 'Expired', }, fr: { trialing: 'Essai actif', active: 'Actif', payment_pending: 'Paiement en attente', past_due: 'Paiement en retard', suspended: 'Suspendu', cancelled: 'Annule', canceled: 'Annule', expired: 'Expire', }, ar: { trialing: 'الفترة التجريبية نشطة', active: 'نشط', payment_pending: 'الدفع قيد الانتظار', past_due: 'الدفع متأخر', suspended: 'معلّق', cancelled: 'ملغى', canceled: 'ملغى', expired: 'منتهي', }, }; const invoiceStatusLabels = { en: { draft: 'Preparing', open: 'Due', payment_pending: 'Payment processing', paid: 'Paid', partially_paid: 'Partially paid', past_due: 'Past due', void: 'Canceled', uncollectible: 'Contact support', refunded: 'Refunded', partially_refunded: 'Partially refunded', }, fr: { draft: 'En preparation', open: 'A payer', payment_pending: 'Paiement en cours', paid: 'Payee', partially_paid: 'Partiellement payee', past_due: 'En retard', void: 'Annulee', uncollectible: 'Contacter le support', refunded: 'Remboursee', partially_refunded: 'Partiellement remboursee', }, ar: { draft: 'قيد الإعداد', open: 'مستحقة الدفع', payment_pending: 'الدفع قيد المعالجة', paid: 'مدفوعة', partially_paid: 'مدفوعة جزئياً', past_due: 'متأخرة', void: 'ملغاة', uncollectible: 'تواصل مع الدعم', refunded: 'مستردة', partially_refunded: 'مستردة جزئياً', }, }; const billingPeriodLabels = { en: { monthly: 'monthly', annual: 'annual', }, fr: { monthly: 'mensuel', annual: 'annuel', }, ar: { monthly: 'شهري', annual: 'سنوي', }, }; const planLabels = { en: { starter: 'Starter', growth: 'Growth', pro: 'Pro', }, fr: { starter: 'Starter', growth: 'Growth', pro: 'Pro', }, ar: { starter: 'Starter', growth: 'Growth', pro: 'Pro', }, }; function coerceNotificationLocale(locale) { if (locale && exports.SUPPORTED_NOTIFICATION_LOCALES.includes(locale)) { return locale; } return exports.DEFAULT_NOTIFICATION_LOCALE; } function getNotificationTextDirection(locale) { return localeConfig[locale].direction; } function formatLocalizedDate(value, locale, options) { const date = value instanceof Date ? value : new Date(value); return new Intl.DateTimeFormat(localeConfig[locale].intl, options ?? { year: 'numeric', month: 'long', day: 'numeric', }).format(date); } function formatLocalizedCurrency(amountMinor, currency, locale) { return new Intl.NumberFormat(localeConfig[locale].intl, { style: 'currency', currency, }).format(amountMinor / 100); } function localizeSubscriptionStatus(status, locale) { const normalized = status.toLowerCase(); return subscriptionStatusLabels[locale][normalized] ?? status; } function localizeInvoiceStatus(status, locale) { const normalized = status.toLowerCase(); return invoiceStatusLabels[locale][normalized] ?? status; } function localizeBillingPeriod(period, locale) { const normalized = period.toLowerCase(); return billingPeriodLabels[locale][normalized] ?? period; } function localizePlanName(plan, locale) { const normalized = plan.toLowerCase(); return planLabels[locale][normalized] ?? plan; } function isTemplateDateValue(value) { return typeof value === 'object' && value !== null && 'type' in value && value.type === 'date'; } function isTemplateCurrencyValue(value) { return typeof value === 'object' && value !== null && 'type' in value && value.type === 'currency'; } function isTemplateSubscriptionStatusValue(value) { return typeof value === 'object' && value !== null && 'type' in value && value.type === 'subscription_status'; } function isTemplateInvoiceStatusValue(value) { return typeof value === 'object' && value !== null && 'type' in value && value.type === 'invoice_status'; } function formatTemplateValue(value, locale) { if (value instanceof Date) return formatLocalizedDate(value, locale); if (isTemplateDateValue(value)) return formatLocalizedDate(value.value, locale, value.options); if (isTemplateCurrencyValue(value)) return formatLocalizedCurrency(value.amountMinor, value.currency, locale); if (isTemplateSubscriptionStatusValue(value)) return localizeSubscriptionStatus(value.status, locale); if (isTemplateInvoiceStatusValue(value)) return localizeInvoiceStatus(value.status, locale); if (value === null || value === undefined) return ''; return String(value); } function normalizeTemplateVariableList(value) { if (!Array.isArray(value)) return []; return value.filter((item) => typeof item === 'string'); } function escapeHtml(value) { return value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } async function findTemplate(db, templateKey, channel, locale) { return db.notificationTemplate.findFirst({ where: { templateKey, channel, locale, isActive: true }, orderBy: { version: 'desc' }, select: { templateKey: true, channel: true, locale: true, subject: true, body: true, requiredVariables: true, optionalVariables: true, version: true, }, }); } async function resolveNotificationLocale(input, db = prisma_1.prisma) { if (input.locale) return coerceNotificationLocale(input.locale); const employeePromise = input.employeeId ? db.employee.findUnique({ where: { id: input.employeeId }, select: { preferredLanguage: true }, }) : Promise.resolve(null); const renterPromise = input.renterId ? db.renter.findUnique({ where: { id: input.renterId }, select: { preferredLocale: true }, }) : Promise.resolve(null); const workspacePromise = input.companyId ? db.brandSettings.findUnique({ where: { companyId: input.companyId }, select: { defaultLocale: true }, }) : Promise.resolve(null); const billingPromise = input.billingAccountId ? db.billingAccount.findUnique({ where: { id: input.billingAccountId }, select: { preferredLanguage: true }, }) : input.companyId ? db.billingAccount.findFirst({ where: { companyId: input.companyId, isPrimary: true }, select: { preferredLanguage: true }, orderBy: { createdAt: 'asc' }, }) : Promise.resolve(null); const [employee, renter, workspace, billingAccount] = await Promise.all([ employeePromise, renterPromise, workspacePromise, billingPromise, ]); return coerceNotificationLocale(employee?.preferredLanguage ?? renter?.preferredLocale ?? workspace?.defaultLocale ?? billingAccount?.preferredLanguage ?? exports.DEFAULT_NOTIFICATION_LOCALE); } async function resolveNotificationTemplate(input, db = prisma_1.prisma) { const requested = await findTemplate(db, input.templateKey, input.channel, input.locale); if (requested) { return { locale: coerceNotificationLocale(requested.locale), subject: renderNotificationText(requested.subject, input.variables, normalizeTemplateVariableList(requested.requiredVariables), input.locale), body: renderNotificationText(requested.body, input.variables, normalizeTemplateVariableList(requested.requiredVariables), input.locale), templateKey: requested.templateKey, usedFallback: false, }; } if (input.locale !== exports.DEFAULT_NOTIFICATION_LOCALE) { console.warn(`[Notifications] Missing ${input.locale} template for ${input.templateKey}/${input.channel}; falling back to ${exports.DEFAULT_NOTIFICATION_LOCALE}.`); } const fallback = await findTemplate(db, input.templateKey, input.channel, exports.DEFAULT_NOTIFICATION_LOCALE); if (!fallback) { throw new Error(`Missing notification template for ${input.templateKey}/${input.channel} in ${input.locale} and ${exports.DEFAULT_NOTIFICATION_LOCALE}`); } return { locale: exports.DEFAULT_NOTIFICATION_LOCALE, subject: renderNotificationText(fallback.subject, input.variables, normalizeTemplateVariableList(fallback.requiredVariables), exports.DEFAULT_NOTIFICATION_LOCALE), body: renderNotificationText(fallback.body, input.variables, normalizeTemplateVariableList(fallback.requiredVariables), exports.DEFAULT_NOTIFICATION_LOCALE), templateKey: fallback.templateKey, usedFallback: true, }; } function renderNotificationText(template, variables = {}, requiredVariables = [], locale) { if (!template) return null; for (const variableName of requiredVariables) { if (variables[variableName] === undefined || variables[variableName] === null) { throw new Error(`Missing notification template variable: ${variableName}`); } } return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_match, variableName) => formatTemplateValue(variables[variableName], locale)); } function renderLocalizedEmailHtml(body, locale) { const direction = getNotificationTextDirection(locale); const textAlign = direction === 'rtl' ? 'right' : 'left'; const paragraphs = body .split(/\n{2,}/) .map((paragraph) => `

${escapeHtml(paragraph).replace(/\n/g, '
')}

`) .join(''); return [ ``, ``, paragraphs, '', '', ].join(''); } //# sourceMappingURL=notificationLocalizationService.js.map