@nxgt/mail-i18n 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,470 @@
1
+ # @nxgt/mail-i18n
2
+
3
+ i18n for a normal [Maizzle](https://maizzle.com) 6 project of transactional
4
+ e-mails: write **one template per e-mail**, its text as keys into
5
+ [ICU](https://unicode-org.github.io/icu/userguide/format_parse/messages/)
6
+ catalogues, and one `maizzle build` writes it in every locale. The build also
7
+ writes a manifest of each e-mail's placeholders and subjects. A catalogue or a
8
+ template that cannot be right **fails the build**, and the error names the
9
+ locale and the key.
10
+
11
+ ```ts
12
+ // maizzle.config.ts
13
+ import { defineMailConfig } from '@nxgt/mail-config';
14
+ import { i18n } from '@nxgt/mail-i18n';
15
+
16
+ export default defineMailConfig({
17
+ plugins: [i18n({ locales: ['en', 'fr'], fallbackLocale: 'en' })],
18
+ });
19
+ ```
20
+
21
+ Your project stays a Maizzle project: `emails/`, `components/`, `public/`,
22
+ `maizzle serve`, `maizzle build`. The plugin adds `locales/`, and gives each
23
+ template `t`, `locale` and `placeholder`.
24
+
25
+ > **0.x.** A minor version may still change the surface; the changelog says how.
26
+
27
+ ## Install
28
+
29
+ ```sh
30
+ bun add @nxgt/mail-i18n @nxgt/mail-config @maizzle/framework @maizzle/tailwindcss
31
+ ```
32
+
33
+ Peers:
34
+
35
+ - `@maizzle/framework` (`^6.1.7`), required — Maizzle itself.
36
+ - `@nxgt/mail-config`, required — the i18n plugin is a plugin for its
37
+ `defineMailConfig`. It needs `@maizzle/tailwindcss` (`^1.5.6`) **as a direct
38
+ dependency of your project**; see [Setup](#setup).
39
+ - `typescript` (6), required. Bundler resolution
40
+ (`"moduleResolution": "bundler"`) is what is supported and tested; `nodenext`
41
+ is out of contract.
42
+ - `vue` (`^3.5`), **optional** — Maizzle already brings it. List it in your
43
+ own `package.json` when you want `t`, `locale` and `placeholder` typed in
44
+ templates ([Editor and type checking](#editor-and-type-checking)).
45
+
46
+ ## Setup
47
+
48
+ ```gitignore
49
+ # .gitignore — the official starter already has it
50
+ .maizzle/
51
+ ```
52
+
53
+ The plugin writes one generated file per template and locale under
54
+ `.maizzle/i18n/`. They are rewritten on every run, never edited, and never
55
+ committed.
56
+
57
+ ```css
58
+ /* in the <style> your layout imports Tailwind from */
59
+ @import "@maizzle/tailwindcss";
60
+ ```
61
+
62
+ That import is resolved from your project. Under an isolated install (Bun
63
+ workspaces, pnpm), it fails **silently** unless `@maizzle/tailwindcss` is in
64
+ your own `package.json`, as the `bun add` above makes it: the build succeeds,
65
+ and no Tailwind utility is generated.
66
+
67
+ ```jsonc
68
+ // tsconfig.json — the official starter's include; keep .maizzle/*.d.ts in it
69
+ { "include": ["**/*.vue", ".maizzle/*.d.ts"] }
70
+ ```
71
+
72
+ ```jsonc
73
+ // package.json — the starter's postinstall
74
+ { "scripts": { "postinstall": "maizzle prepare" } }
75
+ ```
76
+
77
+ Each time the config loads (`maizzle prepare`, `serve`, `build`), the plugin
78
+ writes `.maizzle/nxgt-mail-i18n.d.ts`: the types of `t`, from your catalogues.
79
+ The starter's `tsconfig.json` does not include `maizzle.config.ts`, so this
80
+ file is how the editor learns them. See
81
+ [Editor and type checking](#editor-and-type-checking).
82
+
83
+ ## Exports
84
+
85
+ | Export | What it is |
86
+ | --- | --- |
87
+ | `i18n(options)` | The plugin, for `defineMailConfig({ plugins })` |
88
+ | `createTranslator(catalogues, getLanguage)` | `t(key, args?, language?)` outside templates, shaped like `@nxgt/i18n`, but it throws on a missing key |
89
+ | `emailKey(email)` | Where an e-mail's messages live: `auth/reset-password` → `auth.resetPassword` |
90
+ | `MANIFEST_FILE` | `'mail-manifest.json'`, the manifest's name in the output folder |
91
+ | `WRAPPERS_DIR` | `'.maizzle/i18n'`, where the generated files go |
92
+ | `I18nOptions`, `Layout`, `TemplateSource` | The plugin's options, `'nested' \| 'flat'`, and a package's folder of templates for `templates` |
93
+ | `Catalogue`, `Catalogues`, `ArgumentKind` | A catalogue as written, catalogues by locale, and `'string' \| 'number' \| 'date'` |
94
+ | `Translate`, `LanguageProvider`, `MessageArgs` | What `createTranslator` answers and takes |
95
+ | `Manifest`, `ManifestEmail` | The shape of `dist/mail-manifest.json` |
96
+ | `TemplateMessages`, `TemplateKey`, `TemplateArgs` | The keys of `t` in templates and their arguments, filled from your catalogues by the generated `.maizzle/nxgt-mail-i18n.d.ts` |
97
+
98
+ ## Usage
99
+
100
+ ### A project in two languages
101
+
102
+ A catalogue per locale, nested, `camelCase` keys:
103
+
104
+ ```json
105
+ // locales/en.json — locales/fr.json has the same keys
106
+ {
107
+ "verifyEmail": {
108
+ "subject": "Confirm your e-mail address, {name}",
109
+ "title": "Confirm your e-mail address",
110
+ "greeting": "Hello {name},",
111
+ "expires": "The link expires in {minutes, plural, one {# minute} other {# minutes}}.",
112
+ "action": "Confirm my address"
113
+ }
114
+ }
115
+ ```
116
+
117
+ One template, every language:
118
+
119
+ ```vue
120
+ <!-- emails/verify-email.vue -->
121
+ <template>
122
+ <Html :lang="locale">
123
+ <Body>
124
+ <Container>
125
+ <Heading>{{ t('verifyEmail.title') }}</Heading>
126
+ <Text>{{ t('verifyEmail.greeting', { name: placeholder('name') }) }}</Text>
127
+ <Text>{{ t('verifyEmail.expires', { minutes: 15 }) }}</Text>
128
+ <Button :href="placeholder('link')">{{ t('verifyEmail.action') }}</Button>
129
+ </Container>
130
+ </Body>
131
+ </Html>
132
+ </template>
133
+ ```
134
+
135
+ - `t(key, args?)` formats the message in the locale being built.
136
+ - `locale` is that locale, `'en'` or `'fr'`.
137
+ - `placeholder('name')` writes `{{ name }}` in the built file, for a value that
138
+ is only known at send time. Write it in text, in an attribute, or pass it as
139
+ an argument.
140
+
141
+ `maizzle serve` lists the template once per locale. `maizzle build` writes:
142
+
143
+ ```text
144
+ dist/
145
+ en/verify-email.html <p>Hello {{ name }},</p> … <a href="{{ link }}">
146
+ en/verify-email.txt
147
+ fr/verify-email.html <p>Bonjour {{ name }},</p> … Le lien expire dans 15 minutes.
148
+ fr/verify-email.txt
149
+ mail-manifest.json
150
+ ```
151
+
152
+ The `.txt` files come from `@nxgt/mail-config`'s base config. The e-mail is
153
+ named after its template's path, without `.vue`: `emails/auth/reset-password.vue`
154
+ is `auth/reset-password`, written to `dist/<locale>/auth/reset-password.html`.
155
+ See [Templates](docs/guide/templates.md).
156
+
157
+ ### Options
158
+
159
+ | Option | Type | Default | Effect |
160
+ | --- | --- | --- | --- |
161
+ | `locales` | `readonly string[]` | — (required) | Every locale to build, as BCP 47 tags: `['en', 'fr', 'pt-BR']` |
162
+ | `fallbackLocale` | `string` | the first of `locales` | The reference catalogue: every other locale must have its keys, and may not add an argument |
163
+ | `dir` | `string` | `'locales'` | The folder of `<locale>.json` catalogues, relative to where `maizzle` runs |
164
+ | `emails` | `string` | `'emails'` | The folder of templates |
165
+ | `layout` | `'nested' \| 'flat'` | `'nested'` | `nested` writes `dist/en/verify-email.html`; `flat` writes `dist/verify-email.en.html` |
166
+ | `catalogues` | `readonly Catalogues[]` | `[]` | Catalogues a package ships, merged in order **under** your `<locale>.json`, key by key |
167
+ | `templates` | `readonly TemplateSource[]` | `[]` | Folders of templates a package ships, built with yours; your template of the same name wins |
168
+ | `rendererTypes` | `string \| false` | `'generated/mail.ts'` | The `.ts` module, relative to where `maizzle` runs, that each build writes `MailEmails` to, for `createMailRenderer<MailEmails>`. `false` writes none |
169
+
170
+ ```ts
171
+ // maizzle.config.ts — every file of one e-mail side by side
172
+ export default defineMailConfig({
173
+ plugins: [i18n({ locales: ['en', 'fr'], layout: 'flat' })],
174
+ });
175
+ ```
176
+
177
+ A wrong option is a bare `TypeError` when the config loads:
178
+ `i18n: fallbackLocale must be one of locales`.
179
+
180
+ ### Catalogues from a package
181
+
182
+ A package can ship messages your templates share, such as `@nxgt/mail-ui`'s
183
+ `common.greeting`. List them in `catalogues`. Your own
184
+ `locales/<locale>.json` is still required for every locale, and wins key by
185
+ key:
186
+
187
+ ```ts
188
+ // maizzle.config.ts
189
+ import { defineMailConfig } from '@nxgt/mail-config';
190
+ import { i18n } from '@nxgt/mail-i18n';
191
+ import { uiCatalogues } from '@nxgt/mail-ui';
192
+
193
+ export default defineMailConfig({
194
+ plugins: [i18n({ locales: ['en', 'fr'], catalogues: [uiCatalogues] })],
195
+ });
196
+ ```
197
+
198
+ ```json
199
+ // locales/en.json — common.footer.why and common.footer.ignore stay the package's
200
+ { "common": { "greeting": "Hi {name}," } }
201
+ ```
202
+
203
+ The merged catalogues are checked like your own. A source's locale your
204
+ project does not build is left out. See
205
+ [Catalogues](docs/guide/catalogues.md#catalogues-from-a-package).
206
+
207
+ ### Templates from a package
208
+
209
+ A package can ship whole e-mails, such as `@nxgt/mail-presets`. List its
210
+ folders in `templates`, and its messages in `catalogues`:
211
+
212
+ ```ts
213
+ // maizzle.config.ts
214
+ import { defineMailConfig } from '@nxgt/mail-config';
215
+ import { i18n } from '@nxgt/mail-i18n';
216
+ import { presets } from '@nxgt/mail-presets';
217
+ import { ui, uiCatalogues } from '@nxgt/mail-ui';
218
+
219
+ const mails = presets({ only: ['verify-email', 'reset-password'] });
220
+ // mails.templates → { dir: '/…/node_modules/@nxgt/mail-presets/emails', emails: ['verify-email', 'reset-password'] }
221
+
222
+ export default defineMailConfig({
223
+ plugins: [
224
+ ui({ brand: { name: 'Acme' } }),
225
+ i18n({
226
+ locales: ['en', 'fr'],
227
+ catalogues: [uiCatalogues, mails.catalogues],
228
+ templates: [mails.templates],
229
+ }),
230
+ ],
231
+ });
232
+ ```
233
+
234
+ A `TemplateSource` is `{ dir, emails? }`: `dir` an absolute folder that holds
235
+ templates, `emails` a non-empty list of the ones to build (every one when left
236
+ out). Your `emails/` is read first: `emails/verify-email.vue` replaces the
237
+ package's `verify-email`. Two packages that ship the same e-mail are refused
238
+ when the config loads. See
239
+ [Templates](docs/guide/templates.md#templates-from-a-package).
240
+
241
+ ### The subject
242
+
243
+ Every e-mail needs a subject, in every locale: the message
244
+ `<emailKey>.subject`. `emailKey` turns the template's path into a key:
245
+
246
+ ```ts
247
+ import { emailKey } from '@nxgt/mail-i18n';
248
+
249
+ emailKey('verify-email'); // 'verifyEmail' → verifyEmail.subject
250
+ emailKey('auth/reset-password'); // 'auth.resetPassword' → auth.resetPassword.subject
251
+ ```
252
+
253
+ The template does not write the subject. The build formats it once per locale
254
+ into the manifest, and turns each argument into a placeholder:
255
+ `"Confirm your e-mail address, {name}"` becomes
256
+ `"Confirm your e-mail address, {{ name }}"`. A template with no subject fails
257
+ the build: `i18n: welcome has no subject — add welcome.subject to the
258
+ catalogues`. See [Catalogues](docs/guide/catalogues.md#the-subject).
259
+
260
+ ### The manifest
261
+
262
+ `dist/mail-manifest.json` is written by `maizzle build`, for the code that
263
+ sends. For each e-mail, it records its placeholders, the ones a link or an
264
+ image URL starts with, its subject in each locale, and its files:
265
+
266
+ ```json
267
+ {
268
+ "locales": ["en", "fr"],
269
+ "fallbackLocale": "en",
270
+ "emails": {
271
+ "verify-email": {
272
+ "variables": ["link", "name"],
273
+ "urlVariables": ["link"],
274
+ "subject": {
275
+ "en": "Confirm your e-mail address, {{ name }}",
276
+ "fr": "Confirmez votre adresse e-mail, {{ name }}"
277
+ },
278
+ "files": {
279
+ "en": { "html": "en/verify-email.html", "text": "en/verify-email.txt" },
280
+ "fr": { "html": "fr/verify-email.html", "text": "fr/verify-email.txt" }
281
+ }
282
+ }
283
+ }
284
+ }
285
+ ```
286
+
287
+ `createMailRenderer` from `@nxgt/mail/renderer` reads it at send time:
288
+ `createMailRenderer({ dir: 'dist' }).render('verify-email', { name, link })`
289
+ answers the subject, HTML and text, every value escaped.
290
+
291
+ After the manifest, each build writes `generated/mail.ts`, in the project — the manifest's e-mails
292
+ and variables as a type, rewritten only when they change. **Commit it**: the
293
+ code that sends then type-checks without running a build.
294
+
295
+ ```ts
296
+ // generated/mail.ts — never edited
297
+ export interface MailEmails {
298
+ "verify-email": { readonly link: string; readonly name: string | number };
299
+ }
300
+ ```
301
+
302
+ ```ts
303
+ // in the code that sends
304
+ import { createMailRenderer } from '@nxgt/mail/renderer';
305
+ import type { MailEmails } from './generated/mail';
306
+
307
+ const mails = createMailRenderer<MailEmails>({ dir: 'dist' });
308
+ mails.render('verify-email', { name: 'Ada', link: 'https://app.example.com/v' });
309
+ // mails.render('verify-emial', …) and a missing `link` no longer compile
310
+ ```
311
+
312
+ A URL variable is `string`, any other `string | number`. Set
313
+ `rendererTypes` to write it elsewhere, or `false` for none. See
314
+ [The manifest](docs/guide/manifest.md) and its
315
+ [renderer's types](docs/guide/manifest.md#the-renderers-types--generatedmailts).
316
+
317
+ ### Outside templates — `createTranslator`
318
+
319
+ The same catalogues, from your application's code:
320
+
321
+ ```ts
322
+ import { pickLocale } from '@nxgt/mail';
323
+ import { createTranslator } from '@nxgt/mail-i18n';
324
+ import en from './locales/en.json';
325
+ import fr from './locales/fr.json';
326
+
327
+ declare const user: { locale: string | null };
328
+
329
+ const t = createTranslator({ en, fr }, () => pickLocale(user.locale, ['en', 'fr'], 'en'));
330
+
331
+ t('verifyEmail.expires', { minutes: 15 }); // in the user's locale
332
+ t('verifyEmail.expires', { minutes: 15 }, 'fr'); // 'Le lien expire dans 15 minutes.'
333
+ ```
334
+
335
+ `pickLocale` comes from `@nxgt/mail` (`bun add @nxgt/mail`); `getLanguage`
336
+ can be any function that returns one of the catalogues' locales.
337
+
338
+ `createTranslator(catalogues, getLanguage)` and `t(key, args, language?)`
339
+ have the shape of `@nxgt/i18n`, with one difference: this `t` **throws**
340
+ where `@nxgt/i18n` would answer the key. It throws on a key the catalogue
341
+ does not have, on a language with no catalogue, and on a message that does
342
+ not format. An e-mail never goes out with `verifyEmail.title` or `{link}` in
343
+ it. See [Translating outside templates](docs/guide/translator.md).
344
+
345
+ ### Editor and type checking
346
+
347
+ With the [Setup](#setup) above, Vue's language tools (the **Vue - Official**
348
+ extension in the editor, `vue-tsc` in CI) complete the keys of `t` and flag a
349
+ call the build would refuse:
350
+
351
+ ```vue
352
+ <!-- emails/verify-email.vue, with the catalogue above -->
353
+ <template>
354
+ <p>{{ t('verifyEmail.titel') }}</p>
355
+ <!-- Argument of type '"verifyEmail.titel"' is not assignable to parameter of type 'keyof TemplateMessages'. -->
356
+ <p>{{ t('verifyEmail.greeting') }}</p>
357
+ <!-- Expected 2 arguments, but got 1. -->
358
+ <p>{{ t('verifyEmail.expires', { minutes: placeholder('minutes') }) }}</p>
359
+ <!-- Type 'string' is not assignable to type 'number'. -->
360
+ </template>
361
+ ```
362
+
363
+ ```sh
364
+ bun add -d vue-tsc vue
365
+ bunx vue-tsc --noEmit # after maizzle prepare, as in CI
366
+ ```
367
+
368
+ The types come from the fallback locale's catalogue, merged with the
369
+ `catalogues` option, and refresh on the next config load: saving a catalogue
370
+ under `maizzle serve`, or running `maizzle prepare` or `maizzle build`.
371
+ Without the generated file, the template checker does not know `t`,
372
+ `locale` or `placeholder` at all (`Property 't' does not exist`); the build is
373
+ not affected. See [Editor and type checking](docs/guide/editor.md).
374
+
375
+ ## Traps
376
+
377
+ **Leave `content` to the plugin.** It points Maizzle at the generated files. A
378
+ `content` in your config replaces that, and each template then fails with
379
+ `… is not built through the i18n plugin`.
380
+
381
+ **Run `maizzle` from the project root.** `dir`, `emails` and `.maizzle/i18n`
382
+ are resolved against the directory `maizzle` runs in.
383
+
384
+ **Nest keys; never dot them.** `{ "verifyEmail": { "title": "…" } }`, not
385
+ `{ "verifyEmail.title": "…" }`, which fails the build as not camelCase.
386
+
387
+ **Never write `{{ name }}` by hand in a template.** Vue would evaluate it.
388
+ Write `{{ placeholder('name') }}`.
389
+
390
+ **Never branch on a placeholder.** The template is built before the value
391
+ exists: `v-if="link.startsWith('https:')"` would test the string `{{ link }}`.
392
+
393
+ **Keep `url.base` off for links.** It would prefix `{{ link }}` in every
394
+ `href`.
395
+
396
+ **A subject's arguments are plain `{name}`.** They become placeholders, filled
397
+ as strings at send time. `{count, number}` or a `select` in a subject fails
398
+ the build.
399
+
400
+ **List the plugin that brings a template's components.** A tag that
401
+ resolves to no component renders nothing, and the build fails with
402
+ `i18n: fr/welcome.html is empty — a tag of its template resolved to no
403
+ component; list the plugin that brings it, as ui()`.
404
+
405
+ **Leave the output paths to the plugin.** An `output.path` set in a
406
+ template, or a `plaintext.destination`, moves a file out of the plugin's
407
+ layout, and the build fails when the manifest is written.
408
+
409
+ ## Type safety, counted
410
+
411
+ **18 plausible mistakes, 18 refused** at compile time. Each one is measured by
412
+ a `@ts-expect-error` in
413
+ [`test/types/refusals.ts`](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-i18n/test/types/refusals.ts),
414
+ which fails the typecheck the moment it stops holding:
415
+
416
+ 1. The plugin called without `locales`.
417
+ 2. `locales` given one locale as a string rather than a list.
418
+ 3. A `layout` other than `'nested'` or `'flat'`.
419
+ 4. A catalogue with a leaf that is not a message (`expires: 15`).
420
+ 5. `createTranslator` given a language that is neither a locale nor a
421
+ function that answers one.
422
+ 6. An argument that is an object: only a string, a number or a `Date`.
423
+ 7. `placeholder` given something other than a name.
424
+ 8. A template that assigns `locale`.
425
+ 9. A `fallbackLocale` that is not a string.
426
+ 10. A folder option (`dir`, `emails`) that is not a string.
427
+ 11. `t` called in a template with a key that is not a string.
428
+ 12. `createTranslator`'s `t` given a language, for one call, that is neither
429
+ a locale nor a function that answers one.
430
+ 13. `catalogues` given one catalogue by locale rather than a list of them.
431
+ 14. `catalogues` holding a locale whose value is a message, not a catalogue.
432
+ 15. `templates` given one folder rather than a list of them.
433
+ 16. A template folder's `emails` given as one name rather than a list.
434
+ 17. A template folder's `emails` given as an empty list.
435
+ 18. `rendererTypes` given as `true` rather than a path.
436
+
437
+ The same file holds the calls that must keep compiling: a refusal that refuses
438
+ the correct call is a bug.
439
+
440
+ **7 template mistakes, 7 refused** by the types generated from the
441
+ catalogues, each measured by a `@vue-expect-error` in
442
+ [`test/fixture/types/refusals.vue`](https://github.com/softistx/nxgt-mail/blob/develop/packages/mail-i18n/test/fixture/types/refusals.vue),
443
+ checked by `vue-tsc` after `maizzle prepare` (`bun run typecheck:templates`):
444
+
445
+ 1. A key the catalogues do not have (`t('verifyEmail.titel')`).
446
+ 2. A message's argument left out.
447
+ 3. An argument the message does not use.
448
+ 4. A plural's count given as a placeholder, which is text.
449
+ 5. A key written in `snake_case` (`t('verify_email.title')`).
450
+ 6. A key that may be a message without arguments or one with
451
+ (`t(ok ? 'verifyEmail.title' : 'verifyEmail.expires')`).
452
+ 7. The same, given the arguments of only one of them.
453
+
454
+ The same file holds the template calls that must keep compiling. The build
455
+ still checks every call against every locale's catalogue; the types report
456
+ the same mistakes earlier.
457
+
458
+ ## Documentation
459
+
460
+ - [The guides](docs/README.md) — one page per area, with every option and error.
461
+ - [Troubleshooting](docs/troubleshooting.md) — an error message, its cause and
462
+ its fix.
463
+ - [Roadmap](docs/roadmap.md) — what is next, and what is deliberately not
464
+ planned.
465
+ - [Vocabulary](https://github.com/softistx/nxgt-mail/blob/develop/docs/vocabulary.md)
466
+ — the words these pages use, defined once.
467
+
468
+ ## Licence
469
+
470
+ MIT
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A catalogue as written: nested objects whose leaves are ICU messages, the
3
+ * conventions of `@nxgt/i18n`.
4
+ *
5
+ * ```json
6
+ * { "verifyEmail": { "subject": "Confirm your e-mail address" } }
7
+ * ```
8
+ */
9
+ export interface Catalogue {
10
+ readonly [key: string]: string | Catalogue;
11
+ }
12
+ /** A catalogue per locale: `{ en: {...}, fr: {...} }`. */
13
+ export type Catalogues = Readonly<Record<string, Catalogue>>;
14
+ /**
15
+ * What an argument is, from the way a message uses it: `{n, number}` and
16
+ * `{n, plural, …}` a number, `{at, date}` a date, anything else a string.
17
+ */
18
+ export type ArgumentKind = 'string' | 'number' | 'date';
19
+ /** One message, checked, with the kind of each argument it uses. */
20
+ export interface Message {
21
+ readonly text: string;
22
+ readonly args: ReadonlyMap<string, ArgumentKind>;
23
+ /** The arguments a `{x, select, …}` chooses on: a placeholder would always choose `other`. */
24
+ readonly selects: ReadonlySet<string>;
25
+ }
26
+ /** Every message of one locale, by dotted key: `verifyEmail.subject`. */
27
+ export type Messages = ReadonlyMap<string, Message>;
28
+ /**
29
+ * Each of `project`'s locales, with `sources` merged under it in order: a
30
+ * source's locale the project does not have is left out.
31
+ */
32
+ export declare function layerCatalogues(sources: readonly Catalogues[], project: Record<string, Catalogue>): Record<string, Catalogue>;
33
+ /**
34
+ * Checks the catalogues of every locale and answers their messages. A
35
+ * catalogue that is not objects of camelCase keys, a message that does not
36
+ * parse, and a locale that differs from the fallback locale in its keys or
37
+ * its arguments **throw**, naming the locale and the key.
38
+ */
39
+ export declare function checkCatalogues(catalogues: Catalogues, locales: readonly string[], fallbackLocale: string): ReadonlyMap<string, Messages>;
40
+ //# sourceMappingURL=catalogues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogues.d.ts","sourceRoot":"","sources":["../src/catalogues.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,MAAM,WAAW,SAAS;IACzB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAE7D;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExD,oEAAoE;AACpE,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjD,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACtC;AAED,yEAAyE;AACzE,MAAM,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA2BpD;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,OAAO,EAAE,SAAS,UAAU,EAAE,EAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAQ3B;AAsID;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,EAAE,MAAM,GACpB,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAmB/B"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `@nxgt/mail-i18n` — i18n for a Maizzle project, shaped like `@nxgt/i18n`.
3
+ *
4
+ * ```ts
5
+ * // maizzle.config.ts
6
+ * import { defineMailConfig } from '@nxgt/mail-config';
7
+ * import { i18n } from '@nxgt/mail-i18n';
8
+ *
9
+ * export default defineMailConfig({
10
+ * plugins: [i18n({ locales: ['en', 'fr'] })],
11
+ * });
12
+ * ```
13
+ *
14
+ * A template writes `{{ t('verifyEmail.title') }}`; `maizzle build` writes
15
+ * `dist/en/verify-email.html`, `dist/fr/verify-email.html` and
16
+ * `dist/mail-manifest.json`.
17
+ */
18
+ import './vue';
19
+ export type { ArgumentKind, Catalogue, Catalogues, } from './catalogues';
20
+ export { emailKey, type Manifest, type ManifestEmail, } from './manifest';
21
+ export { type I18nOptions, i18n, MANIFEST_FILE, WRAPPERS_DIR, } from './plugin';
22
+ export type { TemplateSource } from './sources';
23
+ export { createTranslator, type LanguageProvider, type MessageArgs, type Translate, } from './translator';
24
+ export type { TemplateArgs, TemplateKey, TemplateMessages } from './vue';
25
+ export type { Layout } from './wrappers';
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,OAAO,CAAC;AAEf,YAAY,EACX,YAAY,EACZ,SAAS,EACT,UAAU,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,aAAa,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,KAAK,WAAW,EAChB,IAAI,EACJ,aAAa,EACb,YAAY,GACZ,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACN,gBAAgB,EAChB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,SAAS,GACd,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzE,YAAY,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC"}