@nxgt/mail-presets 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Tsala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # @nxgt/mail-presets
2
+
3
+ Nine ready transactional e-mails for a [Maizzle](https://maizzle.com) 6
4
+ project: `verify-email`, `reset-password`, `password-changed`,
5
+ `email-changed`, `sign-in-code`, `magic-link`, `new-sign-in`, `welcome` and
6
+ `invitation`, written with `@nxgt/mail-ui`'s components and translated in
7
+ `en` and `fr`. Your project builds them with its own brand and theme, next to
8
+ its own templates, and replaces any of them — a whole template, or one
9
+ message.
10
+
11
+ ```ts
12
+ // maizzle.config.ts
13
+ import { defineMailConfig } from '@nxgt/mail-config';
14
+ import { i18n } from '@nxgt/mail-i18n';
15
+ import { presets } from '@nxgt/mail-presets';
16
+ import { ui, uiCatalogues } from '@nxgt/mail-ui';
17
+
18
+ const mails = presets();
19
+
20
+ export default defineMailConfig({
21
+ plugins: [
22
+ ui({ brand: { name: 'Acme', url: 'https://acme.example' } }),
23
+ i18n({
24
+ locales: ['en', 'fr'],
25
+ catalogues: [uiCatalogues, mails.catalogues],
26
+ templates: [mails.templates],
27
+ }),
28
+ ],
29
+ });
30
+ ```
31
+
32
+ `maizzle build` then writes `dist/en/verify-email.html`,
33
+ `dist/fr/verify-email.html`… each with its `.txt`, and every e-mail's
34
+ placeholders and subjects in `dist/mail-manifest.json`. See what they look
35
+ like in the [built samples](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-presets/samples/README.md).
36
+
37
+ Each preset as it arrives, with the brand `Acme`, the default theme and example
38
+ values in its placeholders. Click one for full size; `fr` is the French build.
39
+
40
+ <table>
41
+ <tr><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/verify-email.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/verify-email.png" width="260" alt="The verify-email e-mail, in English"></a><br><code>verify-email</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/verify-email.png">fr</a></td><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/reset-password.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/reset-password.png" width="260" alt="The reset-password e-mail, in English"></a><br><code>reset-password</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/reset-password.png">fr</a></td><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/password-changed.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/password-changed.png" width="260" alt="The password-changed e-mail, in English"></a><br><code>password-changed</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/password-changed.png">fr</a></td></tr>
42
+ <tr><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/email-changed.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/email-changed.png" width="260" alt="The email-changed e-mail, in English"></a><br><code>email-changed</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/email-changed.png">fr</a></td><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/sign-in-code.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/sign-in-code.png" width="260" alt="The sign-in-code e-mail, in English"></a><br><code>sign-in-code</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/sign-in-code.png">fr</a></td><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/magic-link.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/magic-link.png" width="260" alt="The magic-link e-mail, in English"></a><br><code>magic-link</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/magic-link.png">fr</a></td></tr>
43
+ <tr><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/new-sign-in.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/new-sign-in.png" width="260" alt="The new-sign-in e-mail, in English"></a><br><code>new-sign-in</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/new-sign-in.png">fr</a></td><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/welcome.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/welcome.png" width="260" alt="The welcome e-mail, in English"></a><br><code>welcome</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/welcome.png">fr</a></td><td valign="top"><a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/invitation.png"><img src="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/en/invitation.png" width="260" alt="The invitation e-mail, in English"></a><br><code>invitation</code> · <a href="https://raw.githubusercontent.com/softistx/nxgt-mail/refs/tags/@nxgt/mail-presets@0.1.0/packages/mail-presets/previews/fr/invitation.png">fr</a></td></tr>
44
+ </table>
45
+
46
+ > **0.x.** A minor version may still change the surface; the changelog says how.
47
+
48
+ ## Install
49
+
50
+ ```sh
51
+ bun add @nxgt/mail-presets @nxgt/mail-i18n @nxgt/mail-ui @nxgt/mail-config @maizzle/framework @maizzle/tailwindcss vue
52
+ ```
53
+
54
+ Peers, all required:
55
+
56
+ - `@nxgt/mail-i18n` — builds the templates once per locale, from the folder
57
+ `presets()` names, with the messages it gives.
58
+ - `@nxgt/mail-ui` — the templates are written with its `Nx*` components, and
59
+ use its shared `common.*` messages.
60
+ - `@maizzle/framework` (`^6.1.7`) — Maizzle itself. `@nxgt/mail-config` and
61
+ `@maizzle/tailwindcss` (`^1.5.6`) are what `@nxgt/mail-ui` needs, the
62
+ latter as a direct dependency of your project (see its
63
+ [Setup](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-ui/README.md#setup)).
64
+ - `vue` (`^3.5`) — the templates are Vue single-file components.
65
+ - `typescript` (6). Bundler resolution (`"moduleResolution": "bundler"`) is
66
+ what is supported and tested; `nodenext` is out of contract.
67
+
68
+ Your project still has a `locales/<locale>.json` per locale, `{}` if it
69
+ overrides nothing: `@nxgt/mail-i18n` requires it.
70
+
71
+ ## Exports
72
+
73
+ | Export | What it is |
74
+ | --- | --- |
75
+ | `presets(options?)` | `{ templates, catalogues }` for `i18n({ templates, catalogues })`: every preset, or the ones in `only` |
76
+ | `PRESETS` | The nine names, `['verify-email', 'reset-password', …]`, as a readonly tuple |
77
+ | `PresetName` | One of them, as a type |
78
+ | `presetCatalogues` | Every preset's messages, `{ en, fr }`, before `presets()` keeps the ones asked for; frozen, every group in it |
79
+ | `TEMPLATES_DIR` | The absolute path of the package's `emails/` folder |
80
+ | `Presets`, `PresetsOptions` | What `presets()` answers, and its options |
81
+
82
+ ## Usage
83
+
84
+ ### A few of them — `only`
85
+
86
+ ```ts
87
+ import { presets } from '@nxgt/mail-presets';
88
+
89
+ const mails = presets({ only: ['verify-email', 'reset-password', 'magic-link'] });
90
+ // mails.templates → { dir: '/…/@nxgt/mail-presets/emails', emails: ['verify-email', 'reset-password', 'magic-link'] }
91
+ // mails.catalogues → { en, fr }, with presets.*, verifyEmail.*, resetPassword.*, magicLink.* only
92
+ ```
93
+
94
+ Pass both to `i18n()` as above: only those three are built, beside your own
95
+ templates. A wrong `only` is a bare `TypeError` when the config loads:
96
+ `presets: only holds something that is not a preset — name one of
97
+ verify-email, reset-password, …`. See [Wiring the presets](docs/guide/presets.md).
98
+
99
+ ### The e-mails
100
+
101
+ Each one takes its values at send time as placeholders, listed in the
102
+ manifest:
103
+
104
+ | E-mail | Placeholders | Subject (`en`) |
105
+ | --- | --- | --- |
106
+ | `verify-email` | `link`, `name` | Confirm your e-mail address |
107
+ | `reset-password` | `link`, `name` | Reset your password |
108
+ | `password-changed` | `link`, `name` | Your password was changed |
109
+ | `email-changed` | `link`, `name`, `newEmail` | Your e-mail address was changed |
110
+ | `sign-in-code` | `code` | Your sign-in code: `{{ code }}` |
111
+ | `magic-link` | `link` | Your sign-in link |
112
+ | `new-sign-in` | `device`, `link`, `location`, `name`, `time` | New sign-in to your account |
113
+ | `welcome` | `link`, `name` | Welcome, `{{ name }}` |
114
+ | `invitation` | `inviter`, `link`, `organization` | `{{ inviter }}` invited you to join `{{ organization }}` |
115
+
116
+ `link` is a URL in each of them: the sender fills it with an `http:` or `https:` URL.
117
+ See [The e-mails](docs/guide/emails.md) for what each one says, in both
118
+ locales, and every message key.
119
+
120
+ ### Replacing a template
121
+
122
+ ```vue
123
+ <!-- emails/welcome.vue — built instead of the package's welcome -->
124
+ <template>
125
+ <NxLayout :preheader="t('welcome.preheader')">
126
+ <NxTypography variant="headline-small">{{ t('welcome.title', { brand: brand.name }) }}</NxTypography>
127
+ <NxTypography>{{ t('common.greeting', { name: placeholder('name') }) }}</NxTypography>
128
+ <NxButton :href="placeholder('link')">{{ t('welcome.action') }}</NxButton>
129
+ <NxTypography variant="caption">{{ t('welcome.help') }}</NxTypography>
130
+ </NxLayout>
131
+ </template>
132
+ ```
133
+
134
+ A template in your `emails/` with the name of a preset replaces it. It keeps
135
+ the preset's messages, so it only writes the keys it adds (`welcome.help`
136
+ here) in your `locales/<locale>.json`. To start from the package's, copy it
137
+ from `TEMPLATES_DIR`.
138
+
139
+ ### Overriding a message
140
+
141
+ ```json
142
+ // locales/en.json — every other message stays the package's
143
+ { "verifyEmail": { "action": "Yes, this is my address" } }
144
+ ```
145
+
146
+ Your catalogues go over the package's, key by key, in each locale. The
147
+ `fr` button stays `Confirmer mon adresse`. See
148
+ [Wiring the presets](docs/guide/presets.md#overriding-a-message).
149
+
150
+ ## Traps
151
+
152
+ **Give `i18n()` both catalogues.** The templates use `@nxgt/mail-ui`'s
153
+ `common.*` messages: without `uiCatalogues` the build fails with
154
+ `calls t('common.footer.why'), which is not a key of the catalogues`, and
155
+ without `mails.catalogues`, with `calls t('verifyEmail.preheader'), …`.
156
+
157
+ ```ts
158
+ catalogues: [uiCatalogues, mails.catalogues],
159
+ ```
160
+
161
+ **List `ui()` in `plugins`.** The templates are made of its `Nx*` components,
162
+ installed under `node_modules`, where Maizzle resolves no tag: `ui()` resolves
163
+ them, and without it the build fails with `i18n: en/verify-email.html is
164
+ empty — a tag of its template resolved to no component; list the plugin that
165
+ brings it, as ui()`.
166
+
167
+ **Another locale writes the keys itself.** The presets have `en` and `fr`
168
+ only: a project in `de` writes `common.*`, `presets.*` and the group of each
169
+ preset it builds in `locales/de.json`, or the build fails with
170
+ `i18n: de: presets.linkFallback is missing — en, the fallback locale, has it`.
171
+ See [Another locale](docs/guide/presets.md#another-locale).
172
+
173
+ **An `en` override keeps the template's arguments.** `en` is the fallback
174
+ locale, and the template still passes `{brand}` to `verifyEmail.body`: an
175
+ override without it fails with `passes {brand} to verifyEmail.body, which
176
+ does not use it`. Replace the template to change what it passes.
177
+
178
+ **`only` keeps only those presets' messages.** A template of yours named
179
+ like a preset left out of `only` writes all of its messages itself.
180
+
181
+ **Three subjects hold placeholders.** `welcome`, `sign-in-code` and
182
+ `invitation` put `{{ name }}`, `{{ code }}`, `{{ inviter }}` and
183
+ `{{ organization }}` in the subject: take the subject from the manifest and
184
+ fill it like the body.
185
+
186
+ **The samples are not in the tarball.** They are on GitHub, in
187
+ [`samples/`](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-presets/samples/README.md).
188
+
189
+ ## Type safety, counted
190
+
191
+ **5 plausible mistakes, 5 refused** at compile time, each measured by a
192
+ `@ts-expect-error` in
193
+ [`test/types/refusals.ts`](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-presets/test/types/refusals.ts)
194
+ that fails the typecheck the moment it stops holding:
195
+
196
+ 1. A name in `only` that is not a preset (`'sign-in'`).
197
+ 2. `only` given one name rather than a list.
198
+ 3. `only` given an empty list, rather than left out for every preset.
199
+ 4. The list given as the options (`presets(['welcome'])`) rather than in
200
+ `only`.
201
+ 5. `i18n({ templates })` given the folder alone (`mails.templates.dir`)
202
+ rather than the whole `mails.templates`.
203
+
204
+ The same file holds the calls that must keep compiling: a refusal that refuses
205
+ the correct call is a bug.
206
+
207
+ ## Documentation
208
+
209
+ - [The guides](docs/README.md) — one page per area, with every option,
210
+ e-mail, message and error.
211
+ - [Troubleshooting](docs/troubleshooting.md) — an error message, its cause and
212
+ its fix.
213
+ - [Roadmap](docs/roadmap.md) — what is next, and what is deliberately not
214
+ planned.
215
+ - [Samples](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-presets/samples/README.md)
216
+ — every preset built in `en` and `fr`, with the brand `Acme`.
217
+ - [Vocabulary](https://github.com/softistx/nxgt-mail/blob/develop/docs/vocabulary.md)
218
+ — the words these pages use, defined once.
219
+
220
+ ## Licence
221
+
222
+ MIT
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `@nxgt/mail-presets` — ready transactional e-mails for a Maizzle project
3
+ * built with `@nxgt/mail-ui` and `@nxgt/mail-i18n`: verify-email,
4
+ * reset-password, password-changed, email-changed, sign-in-code, magic-link,
5
+ * new-sign-in, welcome and invitation, in `en` and `fr`.
6
+ *
7
+ * ```ts
8
+ * // maizzle.config.ts
9
+ * import { defineMailConfig } from '@nxgt/mail-config';
10
+ * import { i18n } from '@nxgt/mail-i18n';
11
+ * import { presets } from '@nxgt/mail-presets';
12
+ * import { ui, uiCatalogues } from '@nxgt/mail-ui';
13
+ *
14
+ * const mails = presets();
15
+ *
16
+ * export default defineMailConfig({
17
+ * plugins: [
18
+ * ui({ brand: { name: 'Acme', url: 'https://acme.example' } }),
19
+ * i18n({
20
+ * locales: ['en', 'fr'],
21
+ * catalogues: [uiCatalogues, mails.catalogues],
22
+ * templates: [mails.templates],
23
+ * }),
24
+ * ],
25
+ * });
26
+ * ```
27
+ *
28
+ * The built samples are in the package's `samples/` folder, on GitHub.
29
+ */
30
+ export { PRESETS, type PresetName, type Presets, type PresetsOptions, presetCatalogues, presets, TEMPLATES_DIR, } from './presets';
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EACN,OAAO,EACP,KAAK,UAAU,EACf,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,gBAAgB,EAChB,OAAO,EACP,aAAa,GACb,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,221 @@
1
+ // src/presets.ts
2
+ import { fileURLToPath } from "node:url";
3
+ import {
4
+ emailKey
5
+ } from "@nxgt/mail-i18n";
6
+ // src/locales/en.json
7
+ var en_default = {
8
+ presets: {
9
+ linkFallback: "If the button does not work, open this link:",
10
+ notYou: "If this was not you, secure your account now."
11
+ },
12
+ verifyEmail: {
13
+ subject: "Confirm your e-mail address",
14
+ preheader: "One click to confirm your address.",
15
+ title: "Confirm your e-mail address",
16
+ body: "Confirm that this address is yours to finish setting up your {brand} account.",
17
+ action: "Confirm my address"
18
+ },
19
+ resetPassword: {
20
+ subject: "Reset your password",
21
+ preheader: "Choose a new password for your account.",
22
+ title: "Reset your password",
23
+ body: "Someone asked to reset the password of your {brand} account. Choose a new one with the button below.",
24
+ action: "Choose a new password"
25
+ },
26
+ passwordChanged: {
27
+ subject: "Your password was changed",
28
+ preheader: "The password of your account was just changed.",
29
+ title: "Your password was changed",
30
+ body: "The password of your {brand} account was just changed.",
31
+ action: "Secure my account"
32
+ },
33
+ emailChanged: {
34
+ subject: "Your e-mail address was changed",
35
+ preheader: "Your account now signs in with a new address.",
36
+ title: "Your e-mail address was changed",
37
+ body: "Your {brand} account now uses {newEmail}. We send this notice to your former address.",
38
+ action: "Undo this change"
39
+ },
40
+ signInCode: {
41
+ subject: "Your sign-in code: {code}",
42
+ preheader: "Enter this code to sign in.",
43
+ title: "Your sign-in code",
44
+ body: "Enter this code to sign in to {brand}. It works once.",
45
+ ignore: "If you did not try to sign in, you can ignore this e-mail: no one can sign in without the code."
46
+ },
47
+ magicLink: {
48
+ subject: "Your sign-in link",
49
+ preheader: "One click to sign in.",
50
+ title: "Sign in to {brand}",
51
+ body: "Click the button below to sign in. The link works once.",
52
+ action: "Sign in"
53
+ },
54
+ newSignIn: {
55
+ subject: "New sign-in to your account",
56
+ preheader: "Your account was signed in from a new device.",
57
+ title: "New sign-in to your account",
58
+ banner: "Your account was signed in from a device we had not seen.",
59
+ device: "Device",
60
+ location: "Location",
61
+ time: "Time",
62
+ body: "If this was you, there is nothing to do.",
63
+ action: "Secure my account"
64
+ },
65
+ welcome: {
66
+ subject: "Welcome, {name}",
67
+ preheader: "Your account is ready.",
68
+ title: "Welcome to {brand}",
69
+ body: "Your account is ready. Everything you need is one click away.",
70
+ action: "Get started"
71
+ },
72
+ invitation: {
73
+ subject: "{inviter} invited you to join {organization}",
74
+ preheader: "Join {organization} on {brand}.",
75
+ title: "Join {organization}",
76
+ body: "{inviter} invited you to join {organization} on {brand}.",
77
+ action: "Accept the invitation"
78
+ }
79
+ };
80
+ // src/locales/fr.json
81
+ var fr_default = {
82
+ presets: {
83
+ linkFallback: "Si le bouton ne fonctionne pas, ouvrez ce lien :",
84
+ notYou: "Si ce n'était pas vous, sécurisez votre compte dès maintenant."
85
+ },
86
+ verifyEmail: {
87
+ subject: "Confirmez votre adresse e-mail",
88
+ preheader: "Un clic pour confirmer votre adresse.",
89
+ title: "Confirmez votre adresse e-mail",
90
+ body: "Confirmez que cette adresse est bien la vôtre pour terminer la création de votre compte {brand}.",
91
+ action: "Confirmer mon adresse"
92
+ },
93
+ resetPassword: {
94
+ subject: "Réinitialisez votre mot de passe",
95
+ preheader: "Choisissez un nouveau mot de passe pour votre compte.",
96
+ title: "Réinitialisez votre mot de passe",
97
+ body: "Quelqu'un a demandé à réinitialiser le mot de passe de votre compte {brand}. Choisissez-en un nouveau avec le bouton ci-dessous.",
98
+ action: "Choisir un nouveau mot de passe"
99
+ },
100
+ passwordChanged: {
101
+ subject: "Votre mot de passe a été modifié",
102
+ preheader: "Le mot de passe de votre compte vient d'être modifié.",
103
+ title: "Votre mot de passe a été modifié",
104
+ body: "Le mot de passe de votre compte {brand} vient d'être modifié.",
105
+ action: "Sécuriser mon compte"
106
+ },
107
+ emailChanged: {
108
+ subject: "Votre adresse e-mail a été modifiée",
109
+ preheader: "Votre compte utilise désormais une nouvelle adresse.",
110
+ title: "Votre adresse e-mail a été modifiée",
111
+ body: "Votre compte {brand} utilise désormais {newEmail}. Nous envoyons cet avis à votre ancienne adresse.",
112
+ action: "Annuler ce changement"
113
+ },
114
+ signInCode: {
115
+ subject: "Votre code de connexion : {code}",
116
+ preheader: "Saisissez ce code pour vous connecter.",
117
+ title: "Votre code de connexion",
118
+ body: "Saisissez ce code pour vous connecter à {brand}. Il ne fonctionne qu'une fois.",
119
+ ignore: "Si vous n'avez pas essayé de vous connecter, vous pouvez ignorer cet e-mail : personne ne peut se connecter sans ce code."
120
+ },
121
+ magicLink: {
122
+ subject: "Votre lien de connexion",
123
+ preheader: "Un clic pour vous connecter.",
124
+ title: "Connectez-vous à {brand}",
125
+ body: "Cliquez sur le bouton ci-dessous pour vous connecter. Le lien ne fonctionne qu'une fois.",
126
+ action: "Me connecter"
127
+ },
128
+ newSignIn: {
129
+ subject: "Nouvelle connexion à votre compte",
130
+ preheader: "Votre compte a été utilisé depuis un nouvel appareil.",
131
+ title: "Nouvelle connexion à votre compte",
132
+ banner: "Votre compte a été utilisé depuis un appareil que nous ne connaissions pas.",
133
+ device: "Appareil",
134
+ location: "Lieu",
135
+ time: "Heure",
136
+ body: "Si c'était vous, vous n'avez rien à faire.",
137
+ action: "Sécuriser mon compte"
138
+ },
139
+ welcome: {
140
+ subject: "Bienvenue, {name}",
141
+ preheader: "Votre compte est prêt.",
142
+ title: "Bienvenue chez {brand}",
143
+ body: "Votre compte est prêt. Tout ce dont vous avez besoin est à portée de clic.",
144
+ action: "Commencer"
145
+ },
146
+ invitation: {
147
+ subject: "{inviter} vous invite à rejoindre {organization}",
148
+ preheader: "Rejoignez {organization} sur {brand}.",
149
+ title: "Rejoignez {organization}",
150
+ body: "{inviter} vous invite à rejoindre {organization} sur {brand}.",
151
+ action: "Accepter l'invitation"
152
+ }
153
+ };
154
+
155
+ // src/presets.ts
156
+ var PRESETS = [
157
+ "verify-email",
158
+ "reset-password",
159
+ "password-changed",
160
+ "email-changed",
161
+ "sign-in-code",
162
+ "magic-link",
163
+ "new-sign-in",
164
+ "welcome",
165
+ "invitation"
166
+ ];
167
+ var TEMPLATES_DIR = fileURLToPath(new URL("../emails", import.meta.url));
168
+ function freeze(catalogue) {
169
+ for (const value of Object.values(catalogue)) {
170
+ if (typeof value === "object" && value !== null)
171
+ freeze(value);
172
+ }
173
+ return Object.freeze(catalogue);
174
+ }
175
+ var presetCatalogues = Object.freeze({
176
+ en: freeze(en_default),
177
+ fr: freeze(fr_default)
178
+ });
179
+ function checkOnly(only) {
180
+ if (!Array.isArray(only) || only.length === 0) {
181
+ throw new TypeError("presets: only must list at least one preset, as ['verify-email']");
182
+ }
183
+ for (const name of only) {
184
+ if (!PRESETS.includes(name)) {
185
+ throw new TypeError(`presets: only holds something that is not a preset — name one of ${PRESETS.join(", ")}`);
186
+ }
187
+ }
188
+ if (new Set(only).size !== only.length) {
189
+ throw new TypeError("presets: only holds the same preset twice");
190
+ }
191
+ }
192
+ function pick(catalogue, keys) {
193
+ return Object.fromEntries(Object.entries(catalogue).filter(([key]) => keys.has(key)));
194
+ }
195
+ function presets(options = {}) {
196
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
197
+ throw new TypeError("presets: options must be an object, as { only: ['verify-email'] }");
198
+ }
199
+ if (options.only === undefined) {
200
+ return { templates: { dir: TEMPLATES_DIR }, catalogues: presetCatalogues };
201
+ }
202
+ checkOnly(options.only);
203
+ const [first, ...rest] = options.only;
204
+ const keys = new Set(["presets", ...options.only.map(emailKey)]);
205
+ return {
206
+ templates: { dir: TEMPLATES_DIR, emails: [first, ...rest] },
207
+ catalogues: Object.fromEntries(Object.entries(presetCatalogues).map(([locale, catalogue]) => [
208
+ locale,
209
+ pick(catalogue, keys)
210
+ ]))
211
+ };
212
+ }
213
+ export {
214
+ PRESETS,
215
+ TEMPLATES_DIR,
216
+ presetCatalogues,
217
+ presets
218
+ };
219
+
220
+ //# debugId=ECFD552A0FD494CB64756E2164756E21
221
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/presets.ts"],
4
+ "sourcesContent": [
5
+ "import { fileURLToPath } from 'node:url';\nimport {\n\ttype Catalogue,\n\ttype Catalogues,\n\temailKey,\n\ttype TemplateSource,\n} from '@nxgt/mail-i18n';\nimport en from './locales/en.json';\nimport fr from './locales/fr.json';\n\n/** Every preset, by the name of its template and of its built file. */\nexport const PRESETS = [\n\t'verify-email',\n\t'reset-password',\n\t'password-changed',\n\t'email-changed',\n\t'sign-in-code',\n\t'magic-link',\n\t'new-sign-in',\n\t'welcome',\n\t'invitation',\n] as const;\n\nexport type PresetName = (typeof PRESETS)[number];\n\n/** The templates, beside `src/` and `dist/` in the package. */\nexport const TEMPLATES_DIR = fileURLToPath(\n\tnew URL('../emails', import.meta.url),\n);\n\n/** `catalogue` and every group in it, frozen: a project reads them, never changes them. */\nfunction freeze(catalogue: Catalogue): Catalogue {\n\tfor (const value of Object.values(catalogue)) {\n\t\tif (typeof value === 'object' && value !== null) freeze(value as Catalogue);\n\t}\n\treturn Object.freeze(catalogue);\n}\n\n/** The messages of every preset, in `en` and `fr`, before {@link presets} keeps the ones asked for. */\nexport const presetCatalogues: Catalogues = Object.freeze({\n\ten: freeze(en),\n\tfr: freeze(fr),\n});\n\nexport interface PresetsOptions {\n\t/** The presets to build, as `['verify-email', 'reset-password']`. Default every one. */\n\treadonly only?: readonly [PresetName, ...PresetName[]];\n}\n\n/** What {@link presets} answers, for `@nxgt/mail-i18n`. */\nexport interface Presets {\n\t/** For `i18n({ templates })`. */\n\treadonly templates: TemplateSource;\n\t/** For `i18n({ catalogues })`: the messages of the presets kept, and the shared `presets.*`. */\n\treadonly catalogues: Catalogues;\n}\n\nfunction checkOnly(\n\tonly: unknown,\n): asserts only is readonly [PresetName, ...PresetName[]] {\n\tif (!Array.isArray(only) || only.length === 0) {\n\t\tthrow new TypeError(\n\t\t\t\"presets: only must list at least one preset, as ['verify-email']\",\n\t\t);\n\t}\n\tfor (const name of only) {\n\t\tif (!(PRESETS as readonly unknown[]).includes(name)) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`presets: only holds something that is not a preset — name one of ${PRESETS.join(', ')}`,\n\t\t\t);\n\t\t}\n\t}\n\tif (new Set(only).size !== only.length) {\n\t\tthrow new TypeError('presets: only holds the same preset twice');\n\t}\n}\n\n/** `catalogue` with only the groups named in `keys`. */\nfunction pick(catalogue: Catalogue, keys: ReadonlySet<string>): Catalogue {\n\treturn Object.fromEntries(\n\t\tObject.entries(catalogue).filter(([key]) => keys.has(key)),\n\t);\n}\n\n/**\n * The preset e-mails of `@nxgt/mail-presets`, for `@nxgt/mail-i18n`:\n *\n * ```ts\n * const mails = presets({ only: ['verify-email', 'reset-password'] });\n * i18n({\n * locales: ['en', 'fr'],\n * catalogues: [uiCatalogues, mails.catalogues],\n * templates: [mails.templates],\n * });\n * ```\n *\n * The project builds them with its own `ui({ brand, theme })`. A template of\n * the same name in its `emails/` replaces a preset, and its catalogue\n * overrides any message key by key.\n */\nexport function presets(options: PresetsOptions = {}): Presets {\n\tif (\n\t\ttypeof options !== 'object' ||\n\t\toptions === null ||\n\t\tArray.isArray(options)\n\t) {\n\t\tthrow new TypeError(\n\t\t\t\"presets: options must be an object, as { only: ['verify-email'] }\",\n\t\t);\n\t}\n\tif (options.only === undefined) {\n\t\treturn { templates: { dir: TEMPLATES_DIR }, catalogues: presetCatalogues };\n\t}\n\tcheckOnly(options.only);\n\tconst [first, ...rest] = options.only;\n\tconst keys = new Set(['presets', ...options.only.map(emailKey)]);\n\treturn {\n\t\ttemplates: { dir: TEMPLATES_DIR, emails: [first, ...rest] },\n\t\tcatalogues: Object.fromEntries(\n\t\t\tObject.entries(presetCatalogues).map(([locale, catalogue]) => [\n\t\t\t\tlocale,\n\t\t\t\tpick(catalogue, keys),\n\t\t\t]),\n\t\t),\n\t};\n}\n"
6
+ ],
7
+ "mappings": ";AAAA;AACA;AAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUO,IAAM,UAAU;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAKO,IAAM,gBAAgB,cAC5B,IAAI,IAAI,aAAa,YAAY,GAAG,CACrC;AAGA,SAAS,MAAM,CAAC,WAAiC;AAAA,EAChD,WAAW,SAAS,OAAO,OAAO,SAAS,GAAG;AAAA,IAC7C,IAAI,OAAO,UAAU,YAAY,UAAU;AAAA,MAAM,OAAO,KAAkB;AAAA,EAC3E;AAAA,EACA,OAAO,OAAO,OAAO,SAAS;AAAA;AAIxB,IAAM,mBAA+B,OAAO,OAAO;AAAA,EACzD,IAAI,OAAO,UAAE;AAAA,EACb,IAAI,OAAO,UAAE;AACd,CAAC;AAeD,SAAS,SAAS,CACjB,MACyD;AAAA,EACzD,IAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW,GAAG;AAAA,IAC9C,MAAM,IAAI,UACT,kEACD;AAAA,EACD;AAAA,EACA,WAAW,QAAQ,MAAM;AAAA,IACxB,IAAI,CAAE,QAA+B,SAAS,IAAI,GAAG;AAAA,MACpD,MAAM,IAAI,UACT,oEAAoE,QAAQ,KAAK,IAAI,GACtF;AAAA,IACD;AAAA,EACD;AAAA,EACA,IAAI,IAAI,IAAI,IAAI,EAAE,SAAS,KAAK,QAAQ;AAAA,IACvC,MAAM,IAAI,UAAU,2CAA2C;AAAA,EAChE;AAAA;AAID,SAAS,IAAI,CAAC,WAAsB,MAAsC;AAAA,EACzE,OAAO,OAAO,YACb,OAAO,QAAQ,SAAS,EAAE,OAAO,EAAE,SAAS,KAAK,IAAI,GAAG,CAAC,CAC1D;AAAA;AAmBM,SAAS,OAAO,CAAC,UAA0B,CAAC,GAAY;AAAA,EAC9D,IACC,OAAO,YAAY,YACnB,YAAY,QACZ,MAAM,QAAQ,OAAO,GACpB;AAAA,IACD,MAAM,IAAI,UACT,mEACD;AAAA,EACD;AAAA,EACA,IAAI,QAAQ,SAAS,WAAW;AAAA,IAC/B,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,GAAG,YAAY,iBAAiB;AAAA,EAC1E;AAAA,EACA,UAAU,QAAQ,IAAI;AAAA,EACtB,OAAO,UAAU,QAAQ,QAAQ;AAAA,EACjC,MAAM,OAAO,IAAI,IAAI,CAAC,WAAW,GAAG,QAAQ,KAAK,IAAI,QAAQ,CAAC,CAAC;AAAA,EAC/D,OAAO;AAAA,IACN,WAAW,EAAE,KAAK,eAAe,QAAQ,CAAC,OAAO,GAAG,IAAI,EAAE;AAAA,IAC1D,YAAY,OAAO,YAClB,OAAO,QAAQ,gBAAgB,EAAE,IAAI,EAAE,QAAQ,eAAe;AAAA,MAC7D;AAAA,MACA,KAAK,WAAW,IAAI;AAAA,IACrB,CAAC,CACF;AAAA,EACD;AAAA;",
8
+ "debugId": "ECFD552A0FD494CB64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,37 @@
1
+ import { type Catalogues, type TemplateSource } from '@nxgt/mail-i18n';
2
+ /** Every preset, by the name of its template and of its built file. */
3
+ export declare const PRESETS: readonly ["verify-email", "reset-password", "password-changed", "email-changed", "sign-in-code", "magic-link", "new-sign-in", "welcome", "invitation"];
4
+ export type PresetName = (typeof PRESETS)[number];
5
+ /** The templates, beside `src/` and `dist/` in the package. */
6
+ export declare const TEMPLATES_DIR: string;
7
+ /** The messages of every preset, in `en` and `fr`, before {@link presets} keeps the ones asked for. */
8
+ export declare const presetCatalogues: Catalogues;
9
+ export interface PresetsOptions {
10
+ /** The presets to build, as `['verify-email', 'reset-password']`. Default every one. */
11
+ readonly only?: readonly [PresetName, ...PresetName[]];
12
+ }
13
+ /** What {@link presets} answers, for `@nxgt/mail-i18n`. */
14
+ export interface Presets {
15
+ /** For `i18n({ templates })`. */
16
+ readonly templates: TemplateSource;
17
+ /** For `i18n({ catalogues })`: the messages of the presets kept, and the shared `presets.*`. */
18
+ readonly catalogues: Catalogues;
19
+ }
20
+ /**
21
+ * The preset e-mails of `@nxgt/mail-presets`, for `@nxgt/mail-i18n`:
22
+ *
23
+ * ```ts
24
+ * const mails = presets({ only: ['verify-email', 'reset-password'] });
25
+ * i18n({
26
+ * locales: ['en', 'fr'],
27
+ * catalogues: [uiCatalogues, mails.catalogues],
28
+ * templates: [mails.templates],
29
+ * });
30
+ * ```
31
+ *
32
+ * The project builds them with its own `ui({ brand, theme })`. A template of
33
+ * the same name in its `emails/` replaces a preset, and its catalogue
34
+ * overrides any message key by key.
35
+ */
36
+ export declare function presets(options?: PresetsOptions): Presets;
37
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,KAAK,UAAU,EAEf,KAAK,cAAc,EACnB,MAAM,iBAAiB,CAAC;AAIzB,uEAAuE;AACvE,eAAO,MAAM,OAAO,wJAUV,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AAElD,+DAA+D;AAC/D,eAAO,MAAM,aAAa,QAEzB,CAAC;AAUF,uGAAuG;AACvG,eAAO,MAAM,gBAAgB,EAAE,UAG7B,CAAC;AAEH,MAAM,WAAW,cAAc;IAC9B,wFAAwF;IACxF,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,UAAU,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;CACvD;AAED,2DAA2D;AAC3D,MAAM,WAAW,OAAO;IACvB,iCAAiC;IACjC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,gGAAgG;IAChG,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CAChC;AA6BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAyB7D"}
package/docs/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @nxgt/mail-presets — documentation
2
+
3
+ The [README](../README.md) shows that it works. These pages show how, one area
4
+ at a time, with an example for every rule. A *preset* is one of the package's
5
+ nine e-mails: its template and its messages. The other words they use
6
+ (project, template, catalogue, message, placeholder, variable, subject) are
7
+ defined once, in the
8
+ [vocabulary](https://github.com/softistx/nxgt-mail/blob/develop/docs/vocabulary.md).
9
+
10
+ | Page | Read it when |
11
+ | --- | --- |
12
+ | [Wiring the presets](guide/presets.md) | You are adding `presets()` to `maizzle.config.ts`: its `only` option, what it answers and how `i18n()` builds it, replacing a template, overriding a message, a locale other than `en` and `fr`, and every `TypeError` it throws |
13
+ | [The e-mails](guide/emails.md) | You are sending one of the nine: what it is for, what it shows, its placeholders and which of them is a URL, its subject, and each of its messages in `en` and `fr` |
14
+ | [Troubleshooting](troubleshooting.md) | You have an error message and want its cause and its fix |
15
+ | [Roadmap](roadmap.md) | You want to know what is coming, what shipped, and what is deliberately not planned |
16
+
17
+ The built HTML of every preset, in both locales, is in
18
+ [`samples/`](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-presets/samples/README.md),
19
+ on GitHub. The package's specs check it against a build made the way a
20
+ project makes it: `@nxgt/mail-ui` and `@nxgt/mail-presets` installed in a
21
+ `node_modules`, with the configuration of [The e-mails](guide/emails.md).