diff --git a/docs/docs/reference/core-plugins/email-plugin/index.md b/docs/docs/reference/core-plugins/email-plugin/index.md
index b585099021..f1ba037257 100644
--- a/docs/docs/reference/core-plugins/email-plugin/index.md
+++ b/docs/docs/reference/core-plugins/email-plugin/index.md
@@ -108,7 +108,10 @@ Dynamic data such as the recipient's name or order items are specified using [Ha
The following helper functions are available for use in email templates:
-* `formatMoney`: Formats an amount of money (which are always stored as integers in Vendure) as a decimal, e.g. `123` => `1.23`
+* `formatMoney`: Formats an amount of money (which are always stored as integers in Vendure) as a decimal, e.g. `123` => `1.23`.
+ * Also accepts two additional parameters (`currency` and `locale`), which will format according to the given currency and locale. For example:
+ * `{{ formatMoney 123 'USD' 'en-US' }}` => `$1.23`
+ * `{{ formatMoney 123 'EUR' 'de-DE' }}` => `1,23 €`
* `formatDate`: Formats a Date value with the [dateformat](https://www.npmjs.com/package/dateformat) package.
## Extending the default email handlers
diff --git a/packages/email-plugin/src/handlebars-mjml-generator.ts b/packages/email-plugin/src/handlebars-mjml-generator.ts
index 99cb08a4c3..df42ef61e3 100644
--- a/packages/email-plugin/src/handlebars-mjml-generator.ts
+++ b/packages/email-plugin/src/handlebars-mjml-generator.ts
@@ -56,11 +56,24 @@ export class HandlebarsMjmlGenerator implements EmailGenerator {
return dateFormat(date, format);
});
- Handlebars.registerHelper('formatMoney', (amount?: number) => {
- if (amount == null) {
- return amount;
- }
- return (amount / 100).toFixed(2);
- });
+ Handlebars.registerHelper(
+ 'formatMoney',
+ (amount?: number, currencyCode?: string, locale?: string) => {
+ if (amount == null) {
+ return amount;
+ }
+ // Last parameter is a generic "options" object which is not used here.
+ // If it's supplied, it means the helper function did not receive the additional, optional parameters.
+ // See https://handlebarsjs.com/api-reference/helpers.html#the-options-parameter
+ if (!currencyCode || typeof currencyCode === 'object') {
+ return (amount / 100).toFixed(2);
+ }
+ // Same reasoning for `locale` as for `currencyCode` here.
+ return new Intl.NumberFormat(typeof locale === 'object' ? undefined : locale, {
+ style: 'currency',
+ currency: currencyCode,
+ }).format(amount / 100);
+ },
+ );
}
}
diff --git a/packages/email-plugin/src/plugin.spec.ts b/packages/email-plugin/src/plugin.spec.ts
index b8d7fc3ae2..c74174c14f 100644
--- a/packages/email-plugin/src/plugin.spec.ts
+++ b/packages/email-plugin/src/plugin.spec.ts
@@ -21,6 +21,7 @@ import { createReadStream, readFileSync } from 'fs';
import path from 'path';
import { Readable } from 'stream';
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
+
import { orderConfirmationHandler } from './default-email-handlers';
import { EmailProcessor } from './email-processor';
import { EmailSender } from './email-sender';
@@ -338,6 +339,8 @@ describe('EmailPlugin', () => {
eventBus.publish(new MockEvent(ctx, true));
await pause();
expect(onSend.mock.calls[0][0].body).toContain('Price: 1.23');
+ expect(onSend.mock.calls[0][0].body).toContain('Price: €1.23');
+ expect(onSend.mock.calls[0][0].body).toContain('Price: £1.23');
});
});
@@ -658,7 +661,7 @@ describe('EmailPlugin', () => {
await pause();
expect(testingLogger.warnSpy.mock.calls[0][0]).toContain(
- 'Email has a large \'content\' attachment (64k). Consider using the \'path\' instead for improved performance.',
+ "Email has a large 'content' attachment (64k). Consider using the 'path' instead for improved performance.",
);
});
});
@@ -881,8 +884,8 @@ describe('EmailPlugin', () => {
return {
type: 'testing',
onSend: () => {},
- }
- }
+ };
+ },
});
const ctx = RequestContext.deserialize({
_channel: { code: DEFAULT_CHANNEL_CODE },
@@ -891,7 +894,7 @@ describe('EmailPlugin', () => {
module!.get(EventBus).publish(new MockEvent(ctx, true));
await pause();
expect(module).toBeDefined();
- expect(typeof (module.get(EmailPlugin) as any).options.transport).toBe('function');
+ expect(typeof module.get(EmailPlugin).options.transport).toBe('function');
});
it('Passes injector and context to transport function', async () => {
diff --git a/packages/email-plugin/templates/order-confirmation/body.hbs b/packages/email-plugin/templates/order-confirmation/body.hbs
index a30bed98cf..1f898a2617 100644
--- a/packages/email-plugin/templates/order-confirmation/body.hbs
+++ b/packages/email-plugin/templates/order-confirmation/body.hbs
@@ -61,7 +61,7 @@