@leancodepl/mail-translation 10.5.0 → 10.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,10 +3,21 @@
3
3
  All notable changes to this project will be documented in this file. See
4
4
  [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- # [10.5.0](https://github.com/leancodepl/js_corelibrary/compare/v10.4.0...v10.5.0) (2026-07-01)
6
+ ## [10.5.2](https://github.com/leancodepl/js_corelibrary/compare/v10.5.1...v10.5.2) (2026-07-02)
7
+
8
+ **Note:** Version bump only for package @leancodepl/mail-translation
9
+
10
+ # Change Log
11
+
12
+ All notable changes to this project will be documented in this file. See
13
+ [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
14
+
15
+ ## [10.5.1](https://github.com/leancodepl/js_corelibrary/compare/v10.4.0...v10.5.1) (2026-07-01)
7
16
 
8
17
  ### Bug Fixes
9
18
 
19
+ - **mail-translation:** follow mjml include best practices + drop comment
20
+ ([f523436](https://github.com/leancodepl/js_corelibrary/commit/f523436d2df62553eb74d2be9804c534a22a7d02))
10
21
  - resolve pre-existing typecheck errors in logger affected set
11
22
  ([9bb40cf](https://github.com/leancodepl/js_corelibrary/commit/9bb40cf62ffd84b55f1bb20a19aef1fcf7a15dff))
12
23
 
package/README.md CHANGED
@@ -100,6 +100,18 @@ templates/
100
100
  └── footer.mjml
101
101
  ```
102
102
 
103
+ ### Includes
104
+
105
+ You can pull shared partials into a template with `mj-include`:
106
+
107
+ ```mjml
108
+ <mj-include path="./components/styles.mjml" />
109
+ ```
110
+
111
+ Any `.mjml` file inside your templates root can be included, referenced with a base-relative path such as
112
+ `./components/styles.mjml`. Files outside the templates root are not allowed: paths cannot escape the root with `..`,
113
+ and absolute paths outside it are rejected.
114
+
103
115
  ### Translation Files
104
116
 
105
117
  Create JSON translation files in your `translationsPath`:
package/dist/bin.cjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";const c=require("yargs/helpers"),u=require("yargs/yargs"),n=require("./saveOutputs-Dq_vvJdp.cjs"),g=require("lilconfig"),d=require("yaml"),t=require("zod/v4");function p(e){const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const i=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(a,r,i.get?i:{enumerable:!0,get:()=>e[r]})}}return a.default=e,Object.freeze(a)}const f=p(d),m=t.z.enum(["kratos","razor"]),h=t.z.object({translationsPath:t.z.string().optional().describe("Path to directory containing translation JSON files. When omitted, templates are compiled without translations. Each JSON file should be named with the language code (e.g., en.json, pl.json)."),mailsPath:t.z.string().describe("Path to directory containing MJML email templates. All .mjml files in this directory will be processed."),plaintextMailsPath:t.z.string().optional().describe("Path to directory containing plaintext templates. If not specified, defaults to the same value as mailsPath. Used for generating text-only versions of emails alongside HTML versions."),outputPath:t.z.string().describe("Directory where processed templates will be saved. The tool will create this directory if it doesn't exist."),outputMode:m.describe("Target templating system format: 'kratos' for Go template files compatible with Ory Kratos, 'razor' for C# Razor template files."),defaultLanguage:t.z.string().optional().describe("Default language code for templates with translations. Required when translationsPath is provided."),languages:t.z.array(t.z.string()).optional().describe("Array of language codes to process. When omitted, all languages found in translation files are automatically processed. Use this to limit output to specific languages."),kratosLanguageVariable:t.z.string().optional().describe("Variable path used for language detection in Kratos templates. Defaults to '.Identity.traits.lang'. This determines how the generated template will access the user's language preference. Only used in Kratos mode.")});function s(e,a){return f.parse(a)}const o="mail-translation",y={searchPlaces:[`.${o}rc`,`.${o}rc.json`,`.${o}rc.yaml`,`.${o}rc.yml`,`.${o}rc.js`,`.${o}rc.cjs`,`${o}.config.js`,`${o}.config.cjs`],loaders:{".yaml":s,".yml":s}};function b(e){const a=g.lilconfigSync("mail-translation",y),r=e?a.load(e):a.search();if(!r)throw new Error("No configuration file found");try{return h.parse(r.config)}catch(i){throw new Error(`Failed to load configuration: ${i}`)}}const w=u(c.hideBin(process.argv)).option("config",{alias:"c",type:"string",description:"Config file location"}).parseSync(),l=b(w.config);n.generate(l).then(e=>n.saveOutputs({processedTemplates:e,outputPath:l.outputPath}));
2
+ "use strict";const c=require("yargs/helpers"),u=require("yargs/yargs"),n=require("./saveOutputs-C8epdHxg.cjs"),g=require("lilconfig"),d=require("yaml"),t=require("zod/v4");function p(e){const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const i=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(a,r,i.get?i:{enumerable:!0,get:()=>e[r]})}}return a.default=e,Object.freeze(a)}const f=p(d),m=t.z.enum(["kratos","razor"]),h=t.z.object({translationsPath:t.z.string().optional().describe("Path to directory containing translation JSON files. When omitted, templates are compiled without translations. Each JSON file should be named with the language code (e.g., en.json, pl.json)."),mailsPath:t.z.string().describe("Path to directory containing MJML email templates. All .mjml files in this directory will be processed."),plaintextMailsPath:t.z.string().optional().describe("Path to directory containing plaintext templates. If not specified, defaults to the same value as mailsPath. Used for generating text-only versions of emails alongside HTML versions."),outputPath:t.z.string().describe("Directory where processed templates will be saved. The tool will create this directory if it doesn't exist."),outputMode:m.describe("Target templating system format: 'kratos' for Go template files compatible with Ory Kratos, 'razor' for C# Razor template files."),defaultLanguage:t.z.string().optional().describe("Default language code for templates with translations. Required when translationsPath is provided."),languages:t.z.array(t.z.string()).optional().describe("Array of language codes to process. When omitted, all languages found in translation files are automatically processed. Use this to limit output to specific languages."),kratosLanguageVariable:t.z.string().optional().describe("Variable path used for language detection in Kratos templates. Defaults to '.Identity.traits.lang'. This determines how the generated template will access the user's language preference. Only used in Kratos mode.")});function s(e,a){return f.parse(a)}const o="mail-translation",y={searchPlaces:[`.${o}rc`,`.${o}rc.json`,`.${o}rc.yaml`,`.${o}rc.yml`,`.${o}rc.js`,`.${o}rc.cjs`,`${o}.config.js`,`${o}.config.cjs`],loaders:{".yaml":s,".yml":s}};function b(e){const a=g.lilconfigSync("mail-translation",y),r=e?a.load(e):a.search();if(!r)throw new Error("No configuration file found");try{return h.parse(r.config)}catch(i){throw new Error(`Failed to load configuration: ${i}`)}}const w=u(c.hideBin(process.argv)).option("config",{alias:"c",type:"string",description:"Config file location"}).parseSync(),l=b(w.config);n.generate(l).then(e=>n.saveOutputs({processedTemplates:e,outputPath:l.outputPath}));
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { hideBin as l } from "yargs/helpers";
3
3
  import c from "yargs/yargs";
4
- import { g, s as d } from "./saveOutputs-UaR47fWW.js";
4
+ import { g, s as d } from "./saveOutputs-C5A3oaMr.js";
5
5
  import { lilconfigSync as m } from "lilconfig";
6
6
  import * as p from "yaml";
7
7
  import { z as t } from "zod/v4";
@@ -7,8 +7,8 @@ export interface MjmlCompileResult {
7
7
  html: string;
8
8
  mjmlParseErrors: MjmlParseError[];
9
9
  }
10
- export declare function compileMjml({ mjmlContent, filePath }: {
10
+ export declare function compileMjml({ mjmlContent, filePath, }: {
11
11
  mjmlContent: string;
12
12
  filePath: string;
13
- }): MjmlCompileResult;
13
+ }): Promise<MjmlCompileResult>;
14
14
  //# sourceMappingURL=compileMjml.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compileMjml.d.ts","sourceRoot":"","sources":["../src/compileMjml.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,cAAc,EAAE,CAAA;CAClC;AAED,wBAAgB,WAAW,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,iBAAiB,CAsBnH"}
1
+ {"version":3,"file":"compileMjml.d.ts","sourceRoot":"","sources":["../src/compileMjml.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,cAAc,EAAE,CAAA;CAClC;AAED,wBAAsB,WAAW,CAAC,EAChC,WAAW,EACX,QAAQ,GACT,EAAE;IACD,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA2B7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAKhD,wBAAsB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,YAAY,CAAC,4DAmB/E"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAKhD,wBAAsB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,YAAY,CAAC,4DAqB/E"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./saveOutputs-Dq_vvJdp.cjs");exports.generate=e.generate;exports.saveOutputs=e.saveOutputs;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./saveOutputs-C8epdHxg.cjs");exports.generate=e.generate;exports.saveOutputs=e.saveOutputs;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { g as a, s as t } from "./saveOutputs-UaR47fWW.js";
1
+ import { g as a, s as t } from "./saveOutputs-C5A3oaMr.js";
2
2
  export {
3
3
  a as generate,
4
4
  t as saveOutputs
@@ -25,5 +25,5 @@ export declare function processTemplate({ template, translationData, outputMode,
25
25
  defaultLanguage?: string;
26
26
  kratosLanguageVariable?: string;
27
27
  mailsPath: string;
28
- }): ProcessedTemplate;
28
+ }): Promise<ProcessedTemplate>;
29
29
  //# sourceMappingURL=processTemplate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"processTemplate.d.ts","sourceRoot":"","sources":["../src/processTemplate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,cAAc,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,EAA2B,cAAc,EAAE,MAAM,2BAA2B,CAAA;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAGpD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,cAAc,EAAE,CAAA;IACjC,eAAe,EAAE,cAAc,EAAE,CAAA;CAClC;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,eAAe,EACf,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,QAAQ,CAAA;IAClB,eAAe,EAAE,eAAe,CAAA;IAChC,UAAU,EAAE,UAAU,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,SAAS,EAAE,MAAM,CAAA;CAClB,GAAG,iBAAiB,CAmCpB"}
1
+ {"version":3,"file":"processTemplate.d.ts","sourceRoot":"","sources":["../src/processTemplate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,cAAc,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACrC,OAAO,EAA2B,cAAc,EAAE,MAAM,2BAA2B,CAAA;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAGpD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,cAAc,EAAE,CAAA;IACjC,eAAe,EAAE,cAAc,EAAE,CAAA;CAClC;AAED,wBAAsB,eAAe,CAAC,EACpC,QAAQ,EACR,eAAe,EACf,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,QAAQ,CAAA;IAClB,eAAe,EAAE,eAAe,CAAA;IAChC,UAAU,EAAE,UAAU,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,SAAS,EAAE,MAAM,CAAA;CAClB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmC7B"}
@@ -6,12 +6,12 @@ import F from "intl-messageformat";
6
6
  import { createCliLogger as O } from "@leancodepl/logger/cli";
7
7
  async function M(t) {
8
8
  try {
9
- const r = (await f(t)).filter((e) => g(e) === ".mjml");
9
+ const a = (await f(t)).filter((e) => g(e) === ".mjml");
10
10
  return Promise.all(
11
- r.map(async (e) => {
12
- const a = x(e, ".mjml"), o = p(t, e), i = await h(o, "utf8");
11
+ a.map(async (e) => {
12
+ const r = x(e, ".mjml"), s = p(t, e), i = await h(s, "utf8");
13
13
  return {
14
- name: a,
14
+ name: r,
15
15
  content: i,
16
16
  isPlaintext: !1
17
17
  };
@@ -26,19 +26,19 @@ async function k({
26
26
  outputMode: n
27
27
  }) {
28
28
  try {
29
- const r = await f(t), e = n === "kratos" ? r.filter((a) => a.endsWith(".plaintext.gotmpl")) : r.filter((a) => a.endsWith(".txt.cshtml"));
29
+ const a = await f(t), e = n === "kratos" ? a.filter((r) => r.endsWith(".plaintext.gotmpl")) : a.filter((r) => r.endsWith(".txt.cshtml"));
30
30
  return Promise.all(
31
- e.map(async (a) => {
32
- const o = n === "kratos" ? a.replace(/\.plaintext\.gotmpl$/, "") : a.replace(/\.txt\.cshtml$/, ""), i = p(t, a), s = await h(i, "utf8");
31
+ e.map(async (r) => {
32
+ const s = n === "kratos" ? r.replace(/\.plaintext\.gotmpl$/, "") : r.replace(/\.txt\.cshtml$/, ""), i = p(t, r), o = await h(i, "utf8");
33
33
  return {
34
- name: o,
35
- content: s,
34
+ name: s,
35
+ content: o,
36
36
  isPlaintext: !0
37
37
  };
38
38
  })
39
39
  );
40
- } catch (r) {
41
- throw new Error(`Failed to load plaintext templates: ${r}`);
40
+ } catch (a) {
41
+ throw new Error(`Failed to load plaintext templates: ${a}`);
42
42
  }
43
43
  }
44
44
  const c = O();
@@ -47,82 +47,90 @@ async function C(t) {
47
47
  if (!t)
48
48
  return n;
49
49
  try {
50
- const e = (await f(t)).filter((a) => g(a) === ".json");
50
+ const e = (await f(t)).filter((r) => g(r) === ".json");
51
51
  if (e.length === 0)
52
52
  return c.warn(`No translation files found in: ${t}. Continuing without translations.`), n;
53
- for (const a of e) {
54
- const o = x(a, ".json"), i = p(t, a);
53
+ for (const r of e) {
54
+ const s = x(r, ".json"), i = p(t, r);
55
55
  try {
56
- const s = await h(i, "utf8"), l = JSON.parse(s);
57
- n[o] = l;
58
- } catch (s) {
59
- c.warn(`Failed to load translation file ${a}:`, s);
56
+ const o = await h(i, "utf8"), l = JSON.parse(o);
57
+ n[s] = l;
58
+ } catch (o) {
59
+ c.warn(`Failed to load translation file ${r}:`, o);
60
60
  }
61
61
  }
62
62
  return n;
63
- } catch (r) {
64
- return c.warn(`Failed to load translations: ${r}. Continuing without translations.`), n;
63
+ } catch (a) {
64
+ return c.warn(`Failed to load translations: ${a}. Continuing without translations.`), n;
65
65
  }
66
66
  }
67
67
  const { html: b } = E;
68
- function N({ mjmlContent: t, filePath: n }) {
68
+ async function N({
69
+ mjmlContent: t,
70
+ filePath: n
71
+ }) {
69
72
  try {
70
- const r = v(t, {
73
+ const a = await v(t, {
71
74
  keepComments: !1,
72
75
  validationLevel: "soft",
73
- filePath: n
76
+ // `filePath` points at the templates root; templates use base-relative
77
+ // `mj-include` paths so partials resolve within it (mjml scopes includes to
78
+ // `filePath`/`includePath` and denies `..` escapes).
79
+ filePath: n,
80
+ // mjml 5 disables `mj-include` by default; re-enable it to resolve shared partials.
81
+ ignoreIncludes: !1
74
82
  });
75
83
  return {
76
- html: b(r.html, {
84
+ html: b(a.html, {
77
85
  indent_size: 2,
78
86
  preserve_newlines: !0,
79
87
  max_preserve_newlines: 1
80
88
  }),
81
- mjmlParseErrors: r.errors || []
89
+ mjmlParseErrors: a.errors || []
82
90
  };
83
- } catch (r) {
84
- throw new Error(`MJML compilation failed: ${r}`);
91
+ } catch (a) {
92
+ throw new Error(`MJML compilation failed: ${a}`);
85
93
  }
86
94
  }
87
95
  function R({
88
96
  translatedTemplates: t,
89
97
  defaultLanguage: n,
90
- kratosLanguageVariable: r
98
+ kratosLanguageVariable: a
91
99
  }) {
92
- const e = t.filter((s) => s.isPlaintext), a = t.filter((s) => !s.isPlaintext), o = a.map((s) => ({
93
- filename: `${s.name}.gotmpl`,
100
+ const e = t.filter((o) => o.isPlaintext), r = t.filter((o) => !o.isPlaintext), s = r.map((o) => ({
101
+ filename: `${o.name}.gotmpl`,
94
102
  content: d({
95
- templates: a,
103
+ templates: r,
96
104
  defaultLanguage: n,
97
- kratosLanguageVariable: r
105
+ kratosLanguageVariable: a
98
106
  })
99
- })), i = e.map((s) => ({
100
- filename: `${s.name}.plaintext.gotmpl`,
107
+ })), i = e.map((o) => ({
108
+ filename: `${o.name}.plaintext.gotmpl`,
101
109
  content: d({
102
110
  templates: e,
103
111
  defaultLanguage: n,
104
- kratosLanguageVariable: r
112
+ kratosLanguageVariable: a
105
113
  })
106
114
  }));
107
- return [...o, ...i];
115
+ return [...s, ...i];
108
116
  }
109
117
  function d({
110
118
  templates: t,
111
119
  defaultLanguage: n,
112
- kratosLanguageVariable: r = ".Identity.traits.lang"
120
+ kratosLanguageVariable: a = ".Identity.traits.lang"
113
121
  }) {
114
122
  if (t.length === 1 || !n)
115
123
  return t[0]?.content ?? "";
116
124
  let e = "";
117
- return t.forEach((o) => {
118
- e += `{{define "${o.language}"}}
119
- `, e += o.content, e += `
125
+ return t.forEach((s) => {
126
+ e += `{{define "${s.language}"}}
127
+ `, e += s.content, e += `
120
128
  {{end}}
121
129
 
122
130
  `;
123
- }), t.filter((o) => o.language !== n).map((o) => o.language).forEach((o, i) => {
124
- e += `{{- ${i === 0 ? "if" : "else if"} eq ${r} "${o}" -}}
125
- `, e += `{{ template "${o}" . }}
131
+ }), t.filter((s) => s.language !== n).map((s) => s.language).forEach((s, i) => {
132
+ e += `{{- ${i === 0 ? "if" : "else if"} eq ${a} "${s}" -}}
133
+ `, e += `{{ template "${s}" . }}
126
134
  `;
127
135
  }), e += `{{- else -}}
128
136
  `, e += `{{ template "${n}" . }}
@@ -133,9 +141,9 @@ function z({
133
141
  translatedTemplates: t,
134
142
  defaultLanguage: n
135
143
  }) {
136
- return t.map((r) => ({
137
- filename: L({ template: r, defaultLanguage: n }),
138
- content: J(r.content)
144
+ return t.map((a) => ({
145
+ filename: L({ template: a, defaultLanguage: n }),
146
+ content: J(a.content)
139
147
  }));
140
148
  }
141
149
  function J(t) {
@@ -173,10 +181,10 @@ function L({
173
181
  template: t,
174
182
  defaultLanguage: n
175
183
  }) {
176
- const r = !!n && !!t.language && t.language !== n;
184
+ const a = !!n && !!t.language && t.language !== n;
177
185
  return [
178
186
  t.name,
179
- ...r ? [t.language] : [],
187
+ ...a ? [t.language] : [],
180
188
  ...t.isPlaintext ? ["txt"] : [],
181
189
  "cshtml"
182
190
  ].join(".");
@@ -184,34 +192,34 @@ function L({
184
192
  function _({
185
193
  translatedTemplates: t,
186
194
  outputMode: n,
187
- defaultLanguage: r,
195
+ defaultLanguage: a,
188
196
  kratosLanguageVariable: e
189
197
  }) {
190
198
  switch (n) {
191
199
  case "kratos":
192
200
  return R({
193
201
  translatedTemplates: t,
194
- defaultLanguage: r,
202
+ defaultLanguage: a,
195
203
  kratosLanguageVariable: e
196
204
  });
197
205
  case "razor":
198
- return z({ translatedTemplates: t, defaultLanguage: r });
206
+ return z({ translatedTemplates: t, defaultLanguage: a });
199
207
  }
200
208
  }
201
209
  function A({
202
210
  template: t,
203
211
  translations: n,
204
- language: r
212
+ language: a
205
213
  }) {
206
214
  const e = /\(\(t\s+["']([^"']+)["']\s*(?:,\s*({.*?}))?\s*\)\)/g;
207
- return t.replaceAll(e, (o, i, s) => {
215
+ return t.replaceAll(e, (s, i, o) => {
208
216
  const l = n[i];
209
- if (!l || !r)
210
- return c.warn(`Translation is missing for key "${i}"` + (r ? ` for "${r}" language` : "")), i;
211
- if (s && r)
217
+ if (!l || !a)
218
+ return c.warn(`Translation is missing for key "${i}"` + (a ? ` for "${a}" language` : "")), i;
219
+ if (o && a)
212
220
  try {
213
- const m = JSON.parse(s);
214
- return new F(l, r).format(m);
221
+ const m = JSON.parse(o);
222
+ return new F(l, a).format(m);
215
223
  } catch (m) {
216
224
  return c.warn(`Error parsing JSON parameters or formatting message for key "${i}":`, m), i;
217
225
  }
@@ -219,27 +227,27 @@ function A({
219
227
  return l;
220
228
  });
221
229
  }
222
- function D({
230
+ async function D({
223
231
  template: t,
224
232
  translationData: n,
225
- outputMode: r,
233
+ outputMode: a,
226
234
  defaultLanguage: e,
227
- kratosLanguageVariable: a,
228
- mailsPath: o
235
+ kratosLanguageVariable: r,
236
+ mailsPath: s
229
237
  }) {
230
- const i = Object.keys(n), s = i.length > 0 ? i : [e], l = t.isPlaintext ? void 0 : N({ mjmlContent: t.content, filePath: o }), m = t.isPlaintext ? t.content : l?.html ?? "", w = s.map((u) => {
231
- const T = u ? n[u] ?? {} : {}, P = A({ template: m, translations: T, language: u });
238
+ const i = Object.keys(n), o = i.length > 0 ? i : [e], l = t.isPlaintext ? void 0 : await N({ mjmlContent: t.content, filePath: s }), m = t.isPlaintext ? t.content : l?.html ?? "", w = o.map((u) => {
239
+ const P = u ? n[u] ?? {} : {}, T = A({ template: m, translations: P, language: u });
232
240
  return {
233
241
  name: t.name,
234
- content: P,
242
+ content: T,
235
243
  isPlaintext: t.isPlaintext,
236
244
  language: u
237
245
  };
238
246
  }), y = _({
239
247
  translatedTemplates: w,
240
- outputMode: r,
248
+ outputMode: a,
241
249
  defaultLanguage: e,
242
- kratosLanguageVariable: a
250
+ kratosLanguageVariable: r
243
251
  });
244
252
  return {
245
253
  name: t.name,
@@ -248,19 +256,21 @@ function D({
248
256
  };
249
257
  }
250
258
  async function H(t) {
251
- const n = await C(t.translationsPath), r = await M(t.mailsPath), e = await k({
259
+ const n = await C(t.translationsPath), a = await M(t.mailsPath), e = await k({
252
260
  plaintextMailsPath: t.plaintextMailsPath ?? t.mailsPath,
253
261
  outputMode: t.outputMode
254
262
  });
255
- return [...r, ...e].map(
256
- (a) => D({
257
- template: a,
258
- translationData: n,
259
- outputMode: t.outputMode,
260
- defaultLanguage: t.defaultLanguage,
261
- kratosLanguageVariable: t.kratosLanguageVariable,
262
- mailsPath: t.mailsPath
263
- })
263
+ return Promise.all(
264
+ [...a, ...e].map(
265
+ (r) => D({
266
+ template: r,
267
+ translationData: n,
268
+ outputMode: t.outputMode,
269
+ defaultLanguage: t.defaultLanguage,
270
+ kratosLanguageVariable: t.kratosLanguageVariable,
271
+ mailsPath: t.mailsPath
272
+ })
273
+ )
264
274
  );
265
275
  }
266
276
  async function V({
@@ -268,9 +278,9 @@ async function V({
268
278
  outputPath: n
269
279
  }) {
270
280
  await $(n, { recursive: !0 });
271
- const r = t.flatMap((e) => e.outputTemplates);
281
+ const a = t.flatMap((e) => e.outputTemplates);
272
282
  await Promise.all(
273
- r.map((e) => j(p(n, e.filename), e.content, "utf8"))
283
+ a.map((e) => j(p(n, e.filename), e.content, "utf8"))
274
284
  );
275
285
  for (const e of t)
276
286
  e.mjmlParseErrors.length > 0 && c.warn(`Errors in ${e.name}:`, e.mjmlParseErrors);
@@ -0,0 +1,10 @@
1
+ "use strict";const m=require("node:fs/promises"),c=require("node:path"),y=require("js-beautify"),j=require("mjml"),P=require("intl-messageformat"),T=require("@leancodepl/logger/cli");async function $(t){try{const a=(await m.readdir(t)).filter(e=>c.extname(e)===".mjml");return Promise.all(a.map(async e=>{const r=c.basename(e,".mjml"),s=c.join(t,e),i=await m.readFile(s,"utf8");return{name:r,content:i,isPlaintext:!1}}))}catch(n){throw new Error(`Failed to load templates: ${n}`)}}async function v({plaintextMailsPath:t,outputMode:n}){try{const a=await m.readdir(t),e=n==="kratos"?a.filter(r=>r.endsWith(".plaintext.gotmpl")):a.filter(r=>r.endsWith(".txt.cshtml"));return Promise.all(e.map(async r=>{const s=n==="kratos"?r.replace(/\.plaintext\.gotmpl$/,""):r.replace(/\.txt\.cshtml$/,""),i=c.join(t,r),o=await m.readFile(i,"utf8");return{name:s,content:o,isPlaintext:!0}}))}catch(a){throw new Error(`Failed to load plaintext templates: ${a}`)}}const u=T.createCliLogger();async function E(t){const n={};if(!t)return n;try{const e=(await m.readdir(t)).filter(r=>c.extname(r)===".json");if(e.length===0)return u.warn(`No translation files found in: ${t}. Continuing without translations.`),n;for(const r of e){const s=c.basename(r,".json"),i=c.join(t,r);try{const o=await m.readFile(i,"utf8"),l=JSON.parse(o);n[s]=l}catch(o){u.warn(`Failed to load translation file ${r}:`,o)}}return n}catch(a){return u.warn(`Failed to load translations: ${a}. Continuing without translations.`),n}}const{html:F}=y;async function O({mjmlContent:t,filePath:n}){try{const a=await j(t,{keepComments:!1,validationLevel:"soft",filePath:n,ignoreIncludes:!1});return{html:F(a.html,{indent_size:2,preserve_newlines:!0,max_preserve_newlines:1}),mjmlParseErrors:a.errors||[]}}catch(a){throw new Error(`MJML compilation failed: ${a}`)}}function M({translatedTemplates:t,defaultLanguage:n,kratosLanguageVariable:a}){const e=t.filter(o=>o.isPlaintext),r=t.filter(o=>!o.isPlaintext),s=r.map(o=>({filename:`${o.name}.gotmpl`,content:d({templates:r,defaultLanguage:n,kratosLanguageVariable:a})})),i=e.map(o=>({filename:`${o.name}.plaintext.gotmpl`,content:d({templates:e,defaultLanguage:n,kratosLanguageVariable:a})}));return[...s,...i]}function d({templates:t,defaultLanguage:n,kratosLanguageVariable:a=".Identity.traits.lang"}){if(t.length===1||!n)return t[0]?.content??"";let e="";return t.forEach(s=>{e+=`{{define "${s.language}"}}
2
+ `,e+=s.content,e+=`
3
+ {{end}}
4
+
5
+ `}),t.filter(s=>s.language!==n).map(s=>s.language).forEach((s,i)=>{e+=`{{- ${i===0?"if":"else if"} eq ${a} "${s}" -}}
6
+ `,e+=`{{ template "${s}" . }}
7
+ `}),e+=`{{- else -}}
8
+ `,e+=`{{ template "${n}" . }}
9
+ `,e+=`{{- end -}}
10
+ `,e}function k({translatedTemplates:t,defaultLanguage:n}){return t.map(a=>({filename:b({template:a,defaultLanguage:n}),content:C(a.content)}))}function C(t){const n=["annotation","character-variant","charset","color-profile","container","counter-style","font-face","font-feature-values","font-palette-values","import","keyframes","-webkit-keyframes","layer","media","namespace","ornaments","page","position-try","property","scope","starting-style","styleset","stylistic","supports","swash","view-transition"].join("|");return t.replaceAll(new RegExp(`(?<!@)@(${n})`,"g"),"@@$1")}function b({template:t,defaultLanguage:n}){const a=!!n&&!!t.language&&t.language!==n;return[t.name,...a?[t.language]:[],...t.isPlaintext?["txt"]:[],"cshtml"].join(".")}function q({translatedTemplates:t,outputMode:n,defaultLanguage:a,kratosLanguageVariable:e}){switch(n){case"kratos":return M({translatedTemplates:t,defaultLanguage:a,kratosLanguageVariable:e});case"razor":return k({translatedTemplates:t,defaultLanguage:a})}}function N({template:t,translations:n,language:a}){const e=/\(\(t\s+["']([^"']+)["']\s*(?:,\s*({.*?}))?\s*\)\)/g;return t.replaceAll(e,(s,i,o)=>{const l=n[i];if(!l||!a)return u.warn(`Translation is missing for key "${i}"`+(a?` for "${a}" language`:"")),i;if(o&&a)try{const p=JSON.parse(o);return new P(l,a).format(p)}catch(p){return u.warn(`Error parsing JSON parameters or formatting message for key "${i}":`,p),i}else return l})}async function R({template:t,translationData:n,outputMode:a,defaultLanguage:e,kratosLanguageVariable:r,mailsPath:s}){const i=Object.keys(n),o=i.length>0?i:[e],l=t.isPlaintext?void 0:await O({mjmlContent:t.content,filePath:s}),p=t.isPlaintext?t.content:l?.html??"",h=o.map(f=>{const g=f?n[f]??{}:{},x=N({template:p,translations:g,language:f});return{name:t.name,content:x,isPlaintext:t.isPlaintext,language:f}}),w=q({translatedTemplates:h,outputMode:a,defaultLanguage:e,kratosLanguageVariable:r});return{name:t.name,mjmlParseErrors:l?.mjmlParseErrors??[],outputTemplates:w}}async function _(t){const n=await E(t.translationsPath),a=await $(t.mailsPath),e=await v({plaintextMailsPath:t.plaintextMailsPath??t.mailsPath,outputMode:t.outputMode});return Promise.all([...a,...e].map(r=>R({template:r,translationData:n,outputMode:t.outputMode,defaultLanguage:t.defaultLanguage,kratosLanguageVariable:t.kratosLanguageVariable,mailsPath:t.mailsPath})))}async function z({processedTemplates:t,outputPath:n}){await m.mkdir(n,{recursive:!0});const a=t.flatMap(e=>e.outputTemplates);await Promise.all(a.map(e=>m.writeFile(c.join(n,e.filename),e.content,"utf8")));for(const e of t)e.mjmlParseErrors.length>0&&u.warn(`Errors in ${e.name}:`,e.mjmlParseErrors)}exports.generate=_;exports.saveOutputs=z;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leancodepl/mail-translation",
3
- "version": "10.5.0",
3
+ "version": "10.5.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,11 +18,11 @@
18
18
  "mail-translation": "./dist/bin.js"
19
19
  },
20
20
  "dependencies": {
21
- "@leancodepl/logger": "10.5.0",
22
- "intl-messageformat": "^10.7.16",
21
+ "@leancodepl/logger": "10.5.2",
22
+ "intl-messageformat": "^11.2.9",
23
23
  "js-beautify": "^1.15.4",
24
24
  "lilconfig": "^3.1.3",
25
- "mjml": "^4.17.1",
25
+ "mjml": "^5.4.0",
26
26
  "tslib": "^2.8.1",
27
27
  "yaml": "^2.8.0",
28
28
  "yargs": "^18.0.0",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/js-beautify": "^1.14.3",
33
- "@types/mjml": "^4.7.4",
33
+ "@types/mjml": "^5.0.0",
34
34
  "@types/yaml": "^1.9.7",
35
35
  "@types/yargs": "^17.0.33"
36
36
  },
@@ -1,10 +0,0 @@
1
- "use strict";const m=require("node:fs/promises"),c=require("node:path"),j=require("js-beautify"),y=require("mjml"),T=require("intl-messageformat"),P=require("@leancodepl/logger/cli");async function $(t){try{const r=(await m.readdir(t)).filter(e=>c.extname(e)===".mjml");return Promise.all(r.map(async e=>{const a=c.basename(e,".mjml"),s=c.join(t,e),i=await m.readFile(s,"utf8");return{name:a,content:i,isPlaintext:!1}}))}catch(n){throw new Error(`Failed to load templates: ${n}`)}}async function v({plaintextMailsPath:t,outputMode:n}){try{const r=await m.readdir(t),e=n==="kratos"?r.filter(a=>a.endsWith(".plaintext.gotmpl")):r.filter(a=>a.endsWith(".txt.cshtml"));return Promise.all(e.map(async a=>{const s=n==="kratos"?a.replace(/\.plaintext\.gotmpl$/,""):a.replace(/\.txt\.cshtml$/,""),i=c.join(t,a),o=await m.readFile(i,"utf8");return{name:s,content:o,isPlaintext:!0}}))}catch(r){throw new Error(`Failed to load plaintext templates: ${r}`)}}const u=P.createCliLogger();async function E(t){const n={};if(!t)return n;try{const e=(await m.readdir(t)).filter(a=>c.extname(a)===".json");if(e.length===0)return u.warn(`No translation files found in: ${t}. Continuing without translations.`),n;for(const a of e){const s=c.basename(a,".json"),i=c.join(t,a);try{const o=await m.readFile(i,"utf8"),l=JSON.parse(o);n[s]=l}catch(o){u.warn(`Failed to load translation file ${a}:`,o)}}return n}catch(r){return u.warn(`Failed to load translations: ${r}. Continuing without translations.`),n}}const{html:F}=j;function O({mjmlContent:t,filePath:n}){try{const r=y(t,{keepComments:!1,validationLevel:"soft",filePath:n});return{html:F(r.html,{indent_size:2,preserve_newlines:!0,max_preserve_newlines:1}),mjmlParseErrors:r.errors||[]}}catch(r){throw new Error(`MJML compilation failed: ${r}`)}}function M({translatedTemplates:t,defaultLanguage:n,kratosLanguageVariable:r}){const e=t.filter(o=>o.isPlaintext),a=t.filter(o=>!o.isPlaintext),s=a.map(o=>({filename:`${o.name}.gotmpl`,content:d({templates:a,defaultLanguage:n,kratosLanguageVariable:r})})),i=e.map(o=>({filename:`${o.name}.plaintext.gotmpl`,content:d({templates:e,defaultLanguage:n,kratosLanguageVariable:r})}));return[...s,...i]}function d({templates:t,defaultLanguage:n,kratosLanguageVariable:r=".Identity.traits.lang"}){if(t.length===1||!n)return t[0]?.content??"";let e="";return t.forEach(s=>{e+=`{{define "${s.language}"}}
2
- `,e+=s.content,e+=`
3
- {{end}}
4
-
5
- `}),t.filter(s=>s.language!==n).map(s=>s.language).forEach((s,i)=>{e+=`{{- ${i===0?"if":"else if"} eq ${r} "${s}" -}}
6
- `,e+=`{{ template "${s}" . }}
7
- `}),e+=`{{- else -}}
8
- `,e+=`{{ template "${n}" . }}
9
- `,e+=`{{- end -}}
10
- `,e}function k({translatedTemplates:t,defaultLanguage:n}){return t.map(r=>({filename:b({template:r,defaultLanguage:n}),content:C(r.content)}))}function C(t){const n=["annotation","character-variant","charset","color-profile","container","counter-style","font-face","font-feature-values","font-palette-values","import","keyframes","-webkit-keyframes","layer","media","namespace","ornaments","page","position-try","property","scope","starting-style","styleset","stylistic","supports","swash","view-transition"].join("|");return t.replaceAll(new RegExp(`(?<!@)@(${n})`,"g"),"@@$1")}function b({template:t,defaultLanguage:n}){const r=!!n&&!!t.language&&t.language!==n;return[t.name,...r?[t.language]:[],...t.isPlaintext?["txt"]:[],"cshtml"].join(".")}function q({translatedTemplates:t,outputMode:n,defaultLanguage:r,kratosLanguageVariable:e}){switch(n){case"kratos":return M({translatedTemplates:t,defaultLanguage:r,kratosLanguageVariable:e});case"razor":return k({translatedTemplates:t,defaultLanguage:r})}}function N({template:t,translations:n,language:r}){const e=/\(\(t\s+["']([^"']+)["']\s*(?:,\s*({.*?}))?\s*\)\)/g;return t.replaceAll(e,(s,i,o)=>{const l=n[i];if(!l||!r)return u.warn(`Translation is missing for key "${i}"`+(r?` for "${r}" language`:"")),i;if(o&&r)try{const p=JSON.parse(o);return new T(l,r).format(p)}catch(p){return u.warn(`Error parsing JSON parameters or formatting message for key "${i}":`,p),i}else return l})}function R({template:t,translationData:n,outputMode:r,defaultLanguage:e,kratosLanguageVariable:a,mailsPath:s}){const i=Object.keys(n),o=i.length>0?i:[e],l=t.isPlaintext?void 0:O({mjmlContent:t.content,filePath:s}),p=t.isPlaintext?t.content:l?.html??"",h=o.map(f=>{const g=f?n[f]??{}:{},x=N({template:p,translations:g,language:f});return{name:t.name,content:x,isPlaintext:t.isPlaintext,language:f}}),w=q({translatedTemplates:h,outputMode:r,defaultLanguage:e,kratosLanguageVariable:a});return{name:t.name,mjmlParseErrors:l?.mjmlParseErrors??[],outputTemplates:w}}async function _(t){const n=await E(t.translationsPath),r=await $(t.mailsPath),e=await v({plaintextMailsPath:t.plaintextMailsPath??t.mailsPath,outputMode:t.outputMode});return[...r,...e].map(a=>R({template:a,translationData:n,outputMode:t.outputMode,defaultLanguage:t.defaultLanguage,kratosLanguageVariable:t.kratosLanguageVariable,mailsPath:t.mailsPath}))}async function z({processedTemplates:t,outputPath:n}){await m.mkdir(n,{recursive:!0});const r=t.flatMap(e=>e.outputTemplates);await Promise.all(r.map(e=>m.writeFile(c.join(n,e.filename),e.content,"utf8")));for(const e of t)e.mjmlParseErrors.length>0&&u.warn(`Errors in ${e.name}:`,e.mjmlParseErrors)}exports.generate=_;exports.saveOutputs=z;