@leancodepl/mail-translation 10.5.0 → 10.5.1
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 +3 -1
- package/README.md +12 -0
- package/dist/bin.cjs +1 -1
- package/dist/bin.js +1 -1
- package/dist/compileMjml.d.ts +2 -2
- package/dist/compileMjml.d.ts.map +1 -1
- package/dist/generate.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/processTemplate.d.ts +1 -1
- package/dist/processTemplate.d.ts.map +1 -1
- package/dist/{saveOutputs-UaR47fWW.js → saveOutputs-C5A3oaMr.js} +89 -79
- package/dist/saveOutputs-C8epdHxg.cjs +10 -0
- package/package.json +5 -5
- package/dist/saveOutputs-Dq_vvJdp.cjs +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
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
|
-
|
|
6
|
+
## [10.5.1](https://github.com/leancodepl/js_corelibrary/compare/v10.4.0...v10.5.1) (2026-07-01)
|
|
7
7
|
|
|
8
8
|
### Bug Fixes
|
|
9
9
|
|
|
10
|
+
- **mail-translation:** follow mjml include best practices + drop comment
|
|
11
|
+
([f523436](https://github.com/leancodepl/js_corelibrary/commit/f523436d2df62553eb74d2be9804c534a22a7d02))
|
|
10
12
|
- resolve pre-existing typecheck errors in logger affected set
|
|
11
13
|
([9bb40cf](https://github.com/leancodepl/js_corelibrary/commit/9bb40cf62ffd84b55f1bb20a19aef1fcf7a15dff))
|
|
12
14
|
|
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-
|
|
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-
|
|
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";
|
package/dist/compileMjml.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/generate.d.ts.map
CHANGED
|
@@ -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,
|
|
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-
|
|
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
|
@@ -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,
|
|
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
|
|
9
|
+
const a = (await f(t)).filter((e) => g(e) === ".mjml");
|
|
10
10
|
return Promise.all(
|
|
11
|
-
|
|
12
|
-
const
|
|
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:
|
|
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
|
|
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 (
|
|
32
|
-
const
|
|
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:
|
|
35
|
-
content:
|
|
34
|
+
name: s,
|
|
35
|
+
content: o,
|
|
36
36
|
isPlaintext: !0
|
|
37
37
|
};
|
|
38
38
|
})
|
|
39
39
|
);
|
|
40
|
-
} catch (
|
|
41
|
-
throw new Error(`Failed to load plaintext templates: ${
|
|
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((
|
|
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
|
|
54
|
-
const
|
|
53
|
+
for (const r of e) {
|
|
54
|
+
const s = x(r, ".json"), i = p(t, r);
|
|
55
55
|
try {
|
|
56
|
-
const
|
|
57
|
-
n[
|
|
58
|
-
} catch (
|
|
59
|
-
c.warn(`Failed to load translation file ${
|
|
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 (
|
|
64
|
-
return c.warn(`Failed to load translations: ${
|
|
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({
|
|
68
|
+
async function N({
|
|
69
|
+
mjmlContent: t,
|
|
70
|
+
filePath: n
|
|
71
|
+
}) {
|
|
69
72
|
try {
|
|
70
|
-
const
|
|
73
|
+
const a = await v(t, {
|
|
71
74
|
keepComments: !1,
|
|
72
75
|
validationLevel: "soft",
|
|
73
|
-
filePath
|
|
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(
|
|
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:
|
|
89
|
+
mjmlParseErrors: a.errors || []
|
|
82
90
|
};
|
|
83
|
-
} catch (
|
|
84
|
-
throw new Error(`MJML compilation failed: ${
|
|
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:
|
|
98
|
+
kratosLanguageVariable: a
|
|
91
99
|
}) {
|
|
92
|
-
const e = t.filter((
|
|
93
|
-
filename: `${
|
|
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:
|
|
103
|
+
templates: r,
|
|
96
104
|
defaultLanguage: n,
|
|
97
|
-
kratosLanguageVariable:
|
|
105
|
+
kratosLanguageVariable: a
|
|
98
106
|
})
|
|
99
|
-
})), i = e.map((
|
|
100
|
-
filename: `${
|
|
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:
|
|
112
|
+
kratosLanguageVariable: a
|
|
105
113
|
})
|
|
106
114
|
}));
|
|
107
|
-
return [...
|
|
115
|
+
return [...s, ...i];
|
|
108
116
|
}
|
|
109
117
|
function d({
|
|
110
118
|
templates: t,
|
|
111
119
|
defaultLanguage: n,
|
|
112
|
-
kratosLanguageVariable:
|
|
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((
|
|
118
|
-
e += `{{define "${
|
|
119
|
-
`, 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((
|
|
124
|
-
e += `{{- ${i === 0 ? "if" : "else if"} eq ${
|
|
125
|
-
`, e += `{{ template "${
|
|
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((
|
|
137
|
-
filename: L({ template:
|
|
138
|
-
content: J(
|
|
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
|
|
184
|
+
const a = !!n && !!t.language && t.language !== n;
|
|
177
185
|
return [
|
|
178
186
|
t.name,
|
|
179
|
-
...
|
|
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:
|
|
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:
|
|
202
|
+
defaultLanguage: a,
|
|
195
203
|
kratosLanguageVariable: e
|
|
196
204
|
});
|
|
197
205
|
case "razor":
|
|
198
|
-
return z({ translatedTemplates: t, defaultLanguage:
|
|
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:
|
|
212
|
+
language: a
|
|
205
213
|
}) {
|
|
206
214
|
const e = /\(\(t\s+["']([^"']+)["']\s*(?:,\s*({.*?}))?\s*\)\)/g;
|
|
207
|
-
return t.replaceAll(e, (
|
|
215
|
+
return t.replaceAll(e, (s, i, o) => {
|
|
208
216
|
const l = n[i];
|
|
209
|
-
if (!l || !
|
|
210
|
-
return c.warn(`Translation is missing for key "${i}"` + (
|
|
211
|
-
if (
|
|
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(
|
|
214
|
-
return new F(l,
|
|
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:
|
|
233
|
+
outputMode: a,
|
|
226
234
|
defaultLanguage: e,
|
|
227
|
-
kratosLanguageVariable:
|
|
228
|
-
mailsPath:
|
|
235
|
+
kratosLanguageVariable: r,
|
|
236
|
+
mailsPath: s
|
|
229
237
|
}) {
|
|
230
|
-
const i = Object.keys(n),
|
|
231
|
-
const
|
|
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:
|
|
242
|
+
content: T,
|
|
235
243
|
isPlaintext: t.isPlaintext,
|
|
236
244
|
language: u
|
|
237
245
|
};
|
|
238
246
|
}), y = _({
|
|
239
247
|
translatedTemplates: w,
|
|
240
|
-
outputMode:
|
|
248
|
+
outputMode: a,
|
|
241
249
|
defaultLanguage: e,
|
|
242
|
-
kratosLanguageVariable:
|
|
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),
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
281
|
+
const a = t.flatMap((e) => e.outputTemplates);
|
|
272
282
|
await Promise.all(
|
|
273
|
-
|
|
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.
|
|
3
|
+
"version": "10.5.1",
|
|
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.
|
|
22
|
-
"intl-messageformat": "^
|
|
21
|
+
"@leancodepl/logger": "10.5.1",
|
|
22
|
+
"intl-messageformat": "^11.2.9",
|
|
23
23
|
"js-beautify": "^1.15.4",
|
|
24
24
|
"lilconfig": "^3.1.3",
|
|
25
|
-
"mjml": "^4.
|
|
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": "^
|
|
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;
|