different selector types
MODIFY
src/assets/i18n/en.json
src/assets/i18n/en.json
"userID": "User ID"
},
"email": {
- "common": {
- "username" : "Hello ! {{user.firstName}}"
- },
+ "username" : ["Hello ! {{user.firstName}}","Hello again!"],
"new-registered-user": {
- "title":"Reset Password",
+ "title":"New Registered User",
"buttonText":"E-Mobility",
- "text":"You have just requested to reset your password.\nClick on the link below to generate and receive a new one."
+ "text":"You have registered to SAP. Welcome abroad!"
},
"request-password": {
"title":"Reset Password",

RENAME
src/notification/email/ComponentsManager/ComponentsManager.ts
src/notification/email/ComponentsManager/EmailComponentsManager.ts
@@ -2,25 +2,23 @@ import { promises as fs } from 'fs';
type Component = string;
-export default class ComponentsManager {
+export default class EmailComponentsManager {
private static components = new Map<string, Component>();
// Get the component from the cache or load it
public static async getComponent(componentName: Component): Promise<Component> {
- let cachedComponent = ComponentsManager.components.get(componentName);
+ let cachedComponent = EmailComponentsManager.components.get(componentName);
if (!cachedComponent) {
- console.log(ComponentsManager.components.size);
- cachedComponent = await ComponentsManager.loadComponent(componentName);
- ComponentsManager.components.set(componentName, cachedComponent);
+ cachedComponent = await EmailComponentsManager.loadComponent(componentName);
+ EmailComponentsManager.components.set(componentName, cachedComponent);
}
return cachedComponent;
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
public static async loadComponent(componentName: string): Promise<Component> {
try {
- const fileName = 'src/notification/email/mjmlComponents/' + componentName;
+ const fileName = 'src/notification/email/MjmlComponents/' + componentName;
const content = await fs.readFile(fileName, 'utf8');
return content;
} catch (error) {

MODIFY
src/notification/email/ComponentsManager/index.ts
src/notification/email/ComponentsManager/index.ts
-import ComponentsManager from './ComponentsManager';
+import EmailComponentsManager from './EmailComponentsManager';
-export default ComponentsManager;
+export default EmailComponentsManager;

MODIFY
src/notification/email/EMailNotificationTask.ts
src/notification/email/EMailNotificationTask.ts
/* eslint-disable max-len */
import { AccountVerificationNotification, AdminAccountVerificationNotification, BaseNotification, BillingAccountActivationNotification, BillingAccountCreationLinkNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EmailNotificationMessage, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../../types/UserNotifications';
-import { BUTTON, CONFIG, FOOTER, HEADER, TEXT1, TEXT2, TITLE } from './mjmlComponents';
import FeatureToggles, { Feature } from '../../utils/FeatureToggles';
import { Message, SMTPClient, SMTPError } from 'emailjs';
@@ -20,7 +19,7 @@ import Utils from '../../utils/Utils';
import ejs from 'ejs';
import fs from 'fs';
import global from '../../types/GlobalType';
-import mjmlBuilder from './mjmlBuilder';
+import mjmlBuilder from './MjmlBuilder';
import rfc2047 from 'rfc2047';
const MODULE_NAME = 'EMailNotificationTask';
@@ -306,7 +305,7 @@ export default class EMailNotificationTask implements NotificationTask {
}
}
- private async sendSmartEmail(prefix: string, context: BaseNotification, user: User, tenant: Tenant, severity: NotificationSeverity, useSmtpClientBackup = false): Promise<void> {
+ private async sendSmartEmail(prefix: string, context: any, user: User, tenant: Tenant, severity: NotificationSeverity, useSmtpClientBackup = false): Promise<void> {
let startTime: number;
let emailContent = {} as EmailNotificationMessage;
try {
@@ -320,21 +319,21 @@ export default class EMailNotificationTask implements NotificationTask {
}
// Create the template
const template = new mjmlBuilder()
- .addConfig(await ComponentsManager.getComponent(CONFIG))
- .addHeader(await ComponentsManager.getComponent(HEADER))
- .addToBody(await ComponentsManager.getComponent(TITLE))
- .addToBody(await ComponentsManager.getComponent(TEXT1))
- .addToBody(await ComponentsManager.getComponent(BUTTON))
- .addToBody(await ComponentsManager.getComponent(TEXT2))
- .addFooter(await ComponentsManager.getComponent(FOOTER))
+ .addConfig(await ComponentsManager.getComponent(Constants.CONFIG))
+ .addHeader(await ComponentsManager.getComponent(Constants.HEADER))
+ .addToBody(await ComponentsManager.getComponent(Constants.TITLE))
+ .addToBody(await ComponentsManager.getComponent(Constants.TEXT1))
+ .addToBody(await ComponentsManager.getComponent(Constants.BUTTON))
+ .addToBody(await ComponentsManager.getComponent(Constants.TEXT2))
+ .addFooter(await ComponentsManager.getComponent(Constants.FOOTER))
.buildTemplate();
// Resolve
const i18nInstance = I18nManager.getInstanceForLocale(user.locale);
- template.resolve(i18nInstance, context);
+ template.resolve(i18nInstance, context,prefix);
const html = template.getHtml();
emailContent = {
to: user.email,
- subject: i18nInstance.translate(`email.${prefix}.title`, context),
+ subject: i18nInstance.translate(`email.${prefix}.title`, context as Record<string,unknown>),
text: html,
html: html
};
@@ -353,7 +352,7 @@ export default class EMailNotificationTask implements NotificationTask {
detailedMessages: { error: error.stack }
});
} finally {
- await Logging.traceNotificationEnd(tenant, MODULE_NAME, 'prepareAndSendEmail', startTime, templateName, emailContent, user.id);
+ await Logging.traceNotificationEnd(tenant, MODULE_NAME, 'prepareAndSendEmail', startTime, prefix, emailContent, user.id);
}
}

MODIFY
src/notification/email/mjmlBuilder/index.ts
src/notification/email/mjmlBuilder/index.ts
-import mjmlBuilder from './mjmlBuilder';
+import MjmlBuilder from './MjmlBuilder';
-export default mjmlBuilder;
+export default MjmlBuilder;

MODIFY
src/notification/email/mjmlBuilder/mjmlBuilder.ts
src/notification/email/mjmlBuilder/mjmlBuilder.ts
-import mjmlTemplate from '../mjmlTemplate/mjmlTemplate';
+import mjmlTemplate from '../MjmlTemplate/MjmlTemplate';
-export default class mjmlBuilder {
+export default class MjmlBuilder {
private header: string;
private body: string[];
private footer: string;

MODIFY
src/notification/email/mjmlComponents/button.mjml
src/notification/email/mjmlComponents/button.mjml
<mj-section>
<mj-column>
<mj-button font-size="16px" href="#">
- {{email.buttonText}}
+ {{i18n:buttonText}}
</mj-button>
</mj-column>
</mj-section>

MODIFY
src/notification/email/mjmlComponents/config.mjml
src/notification/email/mjmlComponents/config.mjml
<mj-head>
- <mj-title>{{email.title}}</mj-title>
+ <mj-title>{{i18n:title}}</mj-title>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
<mj-attributes>
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>

DELETE
src/notification/email/mjmlComponents/index.ts
-const FOOTER = 'footer.mjml';
-const HEADER = 'header.mjml';
-const CONFIG = 'config.mjml';
-
-const BUTTON = 'button.mjml';
-const TITLE = 'title.mjml';
-const TEXT1 = 'text1.mjml';
-const TEXT2 = 'text2.mjml';
-
-export { BUTTON, TITLE, TEXT2, TEXT1, FOOTER, HEADER, CONFIG };

MODIFY
src/notification/email/mjmlComponents/text1.mjml
src/notification/email/mjmlComponents/text1.mjml
<mj-section padding-top="20px">
<mj-column>
<mj-text>
- <p>{{email.user.username}},</p>
- <p>{{email.text}}</p>
+ <p>{{user.mobile}}</p>
+ <p>{{i18n:common.username}},</p>
+ <p>{{i18n:text}}</p>
</mj-text>
</mj-column>
</mj-section>

MODIFY
src/notification/email/mjmlComponents/title.mjml
src/notification/email/mjmlComponents/title.mjml
<mj-section>
<mj-column>
- <mj-text font-weight="500" font-size="20px">{{email.title}}</mj-text>
+ <mj-text font-weight="500" font-size="20px">{{i18n:title}}</mj-text>
</mj-column>
</mj-section>

MODIFY
src/notification/email/mjmlTemplate/mjmlTemplate.ts
src/notification/email/mjmlTemplate/mjmlTemplate.ts
@@ -2,9 +2,8 @@ import I18nManager from '../../../utils/I18nManager';
/* eslint-disable @typescript-eslint/member-ordering */
import { flatten } from './utils';
import mjml2html from 'mjml';
-import mjmlContext from '../mjmlContext/mjmlContext';
-export default class mjmlTemplate {
+export default class MjmlTemplate {
private template: string;
public constructor(template: string) {
@@ -23,7 +22,7 @@ export default class mjmlTemplate {
this.template = this.template.replace('{{' + selector + '}}', value);
}
- private getSelectors(): string[] {
+ private getContextSelectors(): string[] {
const regex = new RegExp(/\{\{(.*)\}\}/, 'g');
const matches = this.template.matchAll(regex);
const selectors = [];
@@ -33,6 +32,24 @@ export default class mjmlTemplate {
return selectors;
}
+ private geti18nSelectors(): string[] {
+ const regex = new RegExp(/\{\{(email..*)\}\}/, 'g');
+ const matches = this.template.matchAll(regex);
+ const selectors = [];
+ for (const match of matches) {
+ selectors.push(match[1]);
+ }
+ return selectors;
+ }
+
+ private preparei18nSelectors(prefix:string): void {
+ const i18nRegexCommon = new RegExp(/\{\{i18n:common.(.*)\}\}/, 'g');
+ this.template = this.template.replace(i18nRegexCommon, '{{email.$1}}');
+
+ const i18nRegex = new RegExp(/\{\{i18n:(.*)\}\}/, 'g');
+ this.template = this.template.replace(i18nRegex, '{{email.' + prefix + '.$1}}');
+ }
+
private splitSelector(selector: string): string[] {
return selector.split('.');
}
@@ -45,24 +62,22 @@ export default class mjmlTemplate {
return value;
}
- public resolve(i18nManager: I18nManager, context?: Record<string, unknown>): void {
- // flatten
+ public resolve(i18nManager: I18nManager, context: any, prefix: string): void {
const data = {};
flatten(context,data);
- // replace i18n selectors
- const selectors = this.getSelectors();
- for (const selector of selectors) {
+ this.preparei18nSelectors(prefix);
+
+ const i18nSelectors = this.geti18nSelectors();
+ for (const selector of i18nSelectors) {
const value = i18nManager.translate(selector,data);
this.replace(selector, value);
}
- // replace context selectors
- const translatedSelectors = this.getSelectors();
- for (const selector of translatedSelectors) {
+ const contextSelectors = this.getContextSelectors();
+ for (const selector of contextSelectors) {
const keys = this.splitSelector(selector);
const value = this.getValue(keys, context);
- // const value = i18nManager.translate(selector, context);
this.replace(selector,value);
}
console.log(this.template);

MODIFY
src/notification/email/mjmlTester/mjmlTester.ts
src/notification/email/mjmlTester/mjmlTester.ts
import { BUTTON, CONFIG, FOOTER, HEADER, TEXT1, TEXT2, TITLE } from '../mjmlComponents';
import express from 'express';
-import mjmlBuilder from '../mjmlBuilder/mjmlBuilder';
-import mjmlContext from '../mjmlContext/mjmlContext';
+import mjmlBuilder from '../MjmlBuilder/MjmlBuilder';
+import mjmlContext from '../MjmlContext/MjmlContext';
const app = express();
const port = 3000;

MODIFY
src/utils/Constants.ts
src/utils/Constants.ts
@@ -468,4 +468,14 @@ export default class Constants {
public static readonly AFIREV_MINIMAL_DURATION_THRESHOLD = 120; // Minimal duration - 2 minutes
public static readonly AFIREV_MINIMAL_CONSUMPTION_THRESHOLD = 500; // Minimal consumption - 0.5 kW.h
+
+ public static readonly FOOTER = 'footer.mjml';
+ public static readonly HEADER = 'header.mjml';
+ public static readonly CONFIG = 'config.mjml';
+
+ public static readonly BUTTON = 'button.mjml';
+ public static readonly TITLE = 'title.mjml';
+ public static readonly TEXT1 = 'text1.mjml';
+ public static readonly TEXT2 = 'text2.mjml';
+
}

different selector types