@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 +21 -0
- package/README.md +470 -0
- package/dist/catalogues.d.ts +40 -0
- package/dist/catalogues.d.ts.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +675 -0
- package/dist/index.js.map +17 -0
- package/dist/manifest.d.ts +45 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/plugin.d.ts +55 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/sources.d.ts +19 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/template.d.ts +22 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/translator.d.ts +31 -0
- package/dist/translator.d.ts.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/vue.d.ts +44 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/wrappers.d.ts +62 -0
- package/dist/wrappers.d.ts.map +1 -0
- package/docs/README.md +17 -0
- package/docs/guide/catalogues.md +354 -0
- package/docs/guide/editor.md +215 -0
- package/docs/guide/manifest.md +343 -0
- package/docs/guide/templates.md +421 -0
- package/docs/guide/translator.md +170 -0
- package/docs/roadmap.md +91 -0
- package/docs/troubleshooting.md +1096 -0
- package/package.json +71 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/manifest.ts", "../src/translator.ts", "../src/wrappers.ts", "../src/plugin.ts", "../src/catalogues.ts", "../src/sources.ts", "../src/template.ts", "../src/types.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { readFileSync } from 'node:fs';\nimport { isAbsolute, join, relative, sep } from 'node:path';\nimport type { Messages } from './catalogues';\nimport { createFormatter } from './translator';\nimport { type Entry, entryPath, type Layout, parseEntry } from './wrappers';\n\n/** One e-mail, as the renderer reads it. */\nexport interface ManifestEmail {\n\t/** Every placeholder of the e-mail, in any locale, sorted. */\n\treadonly variables: readonly string[];\n\t/**\n\t * The placeholders a URL attribute starts with — `href`, `src`,\n\t * `background`, `poster`, `action`: they decide the scheme, so a URL fills\n\t * them. One later in the value, as `?token={{ token }}`, is not one.\n\t */\n\treadonly urlVariables: readonly string[];\n\t/** The subject per locale, its placeholders kept as `{{ name }}`. */\n\treadonly subject: Readonly<Record<string, string>>;\n\t/** The built files per locale, relative to the output folder. */\n\treadonly files: Readonly<\n\t\tRecord<string, { readonly html: string; readonly text: string | null }>\n\t>;\n}\n\n/** `dist/mail-manifest.json`: what the build wrote, for the renderer. */\nexport interface Manifest {\n\treadonly locales: readonly string[];\n\treadonly fallbackLocale: string;\n\treadonly emails: Readonly<Record<string, ManifestEmail>>;\n}\n\n// Copied in packages/mail/src/renderer.ts, which reads this manifest: change both.\nconst PLACEHOLDER = /\\{\\{\\s*([a-z][a-zA-Z0-9]*)\\s*\\}\\}/g;\n/** An attribute that holds a URL, its value starting with a placeholder: that placeholder is the whole scheme. */\nconst URL_ATTRIBUTE =\n\t/\\s(?:href|src|background|poster|action)\\s*=\\s*(?:\"\\s*(\\{\\{[^\"]*)\"|'\\s*(\\{\\{[^']*)')/gi;\n\n/** `{{ name }}` — what `placeholder('name')` writes. */\nexport const placeholderMark = (name: string) => `{{ ${name} }}`;\n\nconst placeholdersIn = (text: string) =>\n\t[...text.matchAll(PLACEHOLDER)].map((match) => match[1] as string);\n\n/** `auth/reset-password` → `auth.resetPassword`: where its messages live. */\nexport const emailKey = (email: string) =>\n\temail\n\t\t.split('/')\n\t\t.map((segment) =>\n\t\t\tsegment.replace(/-([a-z0-9])/g, (_, char: string) => char.toUpperCase()),\n\t\t)\n\t\t.join('.');\n\n/**\n * The subject of `email` in `locale`: the message `<emailKey>.subject`, each\n * argument a placeholder. A missing subject, or one with an argument that is\n * not a string, **throws**.\n */\nfunction subjectOf(\n\temail: string,\n\tlocale: string,\n\tmessages: Messages,\n\tformat: ReturnType<typeof createFormatter>,\n): string {\n\tconst key = `${emailKey(email)}.subject`;\n\tconst message = messages.get(key);\n\tif (message === undefined) {\n\t\tthrow new Error(\n\t\t\t`i18n: ${email} has no subject — add ${key} to the catalogues`,\n\t\t);\n\t}\n\tconst args: Record<string, string> = {};\n\tfor (const [name, kind] of message.args) {\n\t\tif (message.selects.has(name)) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${locale}: ${key} chooses on {${name}} with a select — a subject's arguments are placeholders, which always choose other`,\n\t\t\t);\n\t\t}\n\t\tif (kind !== 'string') {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${locale}: ${key} uses {${name}} as a ${kind} — a subject's arguments are placeholders, filled at send time as strings`,\n\t\t\t);\n\t\t}\n\t\targs[name] = placeholderMark(name);\n\t}\n\treturn format(locale, key, message.text, args);\n}\n\n/** One e-mail in one locale, as found in the build's files. */\ninterface Built {\n\treadonly entry: Entry;\n\thtml: string | null;\n\ttext: string | null;\n}\n\nconst sorted = (values: Iterable<string>) => [...new Set(values)].sort();\n\n/**\n * Reads what the build wrote — `files`, absolute, under `outputDir` — and\n * answers the manifest: for each e-mail its placeholders, those in a URL, its\n * subject per locale and its files.\n */\nexport function buildManifest(options: {\n\treadonly files: readonly string[];\n\treadonly outputDir: string;\n\treadonly htmlExtension: string;\n\treadonly layout: Layout;\n\treadonly locales: readonly string[];\n\treadonly fallbackLocale: string;\n\treadonly messages: ReadonlyMap<string, Messages>;\n}): Manifest {\n\tconst { layout, locales } = options;\n\tconst format = createFormatter('i18n');\n\tconst found = new Map<string, Built>();\n\tfor (const file of options.files) {\n\t\tconst path = relative(options.outputDir, file).split(sep).join('/');\n\t\tif (path.startsWith('../') || isAbsolute(path)) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${path} was written outside the output folder — the i18n plugin lays out every e-mail; set no plaintext.destination and no output path in a template`,\n\t\t\t);\n\t\t}\n\t\tconst dot = path.lastIndexOf('.');\n\t\tconst base = path.slice(0, Math.max(dot, 0));\n\t\tconst entry = dot > 0 ? parseEntry(base, layout, locales) : null;\n\t\tif (entry === null) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${path} is not where the i18n plugin puts an e-mail — set no output path in a template`,\n\t\t\t);\n\t\t}\n\t\tconst seen = found.get(base) ?? { entry, html: null, text: null };\n\t\tif (path.slice(dot + 1) === options.htmlExtension) seen.html = path;\n\t\telse seen.text = path;\n\t\tfound.set(base, seen);\n\t}\n\n\tconst emails: Record<string, ManifestEmail> = {};\n\tfor (const email of sorted(\n\t\t[...found.values()].map((seen) => seen.entry.email),\n\t)) {\n\t\tconst variables: string[] = [];\n\t\tconst urlVariables: string[] = [];\n\t\tconst subject: Record<string, string> = {};\n\t\tconst files: Record<string, { html: string; text: string | null }> = {};\n\t\tfor (const locale of locales) {\n\t\t\tconst seen = found.get(entryPath({ email, locale }, layout));\n\t\t\tif (seen?.html == null) {\n\t\t\t\tthrow new Error(`i18n: ${email} was not built in ${locale}`);\n\t\t\t}\n\t\t\tconst html = readFileSync(join(options.outputDir, seen.html), 'utf8');\n\t\t\t// Vue renders an unresolved component as nothing, and Maizzle still\n\t\t\t// writes the doctype: the build would pass with an empty e-mail.\n\t\t\tif (html.replace(/<!doctype[^>]*>/i, '').trim() === '') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`i18n: ${seen.html} is empty — a tag of its template resolved to no component; list the plugin that brings it, as ui()`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tvariables.push(...placeholdersIn(html));\n\t\t\tfor (const match of html.matchAll(URL_ATTRIBUTE)) {\n\t\t\t\turlVariables.push(\n\t\t\t\t\t...placeholdersIn(match[1] ?? match[2] ?? '').slice(0, 1),\n\t\t\t\t);\n\t\t\t}\n\t\t\t// A <Plaintext> block is in the text part only.\n\t\t\tif (seen.text !== null) {\n\t\t\t\tconst text = readFileSync(join(options.outputDir, seen.text), 'utf8');\n\t\t\t\tvariables.push(...placeholdersIn(text));\n\t\t\t}\n\t\t\tsubject[locale] = subjectOf(\n\t\t\t\temail,\n\t\t\t\tlocale,\n\t\t\t\toptions.messages.get(locale) as Messages,\n\t\t\t\tformat,\n\t\t\t);\n\t\t\tvariables.push(...placeholdersIn(subject[locale]));\n\t\t\tfiles[locale] = { html: seen.html, text: seen.text };\n\t\t}\n\t\temails[email] = {\n\t\t\tvariables: sorted(variables),\n\t\t\turlVariables: sorted(urlVariables),\n\t\t\tsubject,\n\t\t\tfiles,\n\t\t};\n\t}\n\treturn {\n\t\tlocales: [...locales],\n\t\tfallbackLocale: options.fallbackLocale,\n\t\temails,\n\t};\n}\n",
|
|
6
|
+
"import { IntlMessageFormat } from 'intl-messageformat';\nimport type { Catalogue, Catalogues } from './catalogues';\n\n/** The values a message's arguments take: `{ name: 'Ada', count: 3 }`. */\nexport type MessageArgs = Readonly<Record<string, string | number | Date>>;\n\n/** A locale, or a function that answers it at each call — as in `@nxgt/i18n`. */\nexport type LanguageProvider = string | (() => string);\n\n/** `t(key, args?, language?)`: the message `key`, formatted in the language. */\nexport type Translate = (\n\tkey: string,\n\targs?: MessageArgs,\n\tlanguage?: LanguageProvider,\n) => string;\n\nfunction lookup(catalogue: Catalogue, key: string): string | null {\n\tlet node: string | Catalogue | undefined = catalogue;\n\tfor (const segment of key.split('.')) {\n\t\tif (typeof node !== 'object' || !Object.hasOwn(node, segment)) return null;\n\t\tnode = node[segment];\n\t}\n\treturn typeof node === 'string' ? node : null;\n}\n\nconst resolveLanguage = (language: LanguageProvider): unknown =>\n\ttypeof language === 'function' ? language() : language;\n\n/**\n * Formats with a cache of compiled messages, one per locale and key. A\n * message that does not format **throws**, the formatter's error as the\n * cause.\n */\nexport function createFormatter(prefix: string) {\n\tconst compiled = new Map<string, IntlMessageFormat>();\n\treturn (locale: string, key: string, text: string, args?: MessageArgs) => {\n\t\tconst id = `${locale}\\u0000${key}`;\n\t\ttry {\n\t\t\tlet format = compiled.get(id);\n\t\t\tif (format === undefined) {\n\t\t\t\tformat = new IntlMessageFormat(text, locale, undefined, {\n\t\t\t\t\tignoreTag: true,\n\t\t\t\t});\n\t\t\t\tcompiled.set(id, format);\n\t\t\t}\n\t\t\treturn String(format.format(args));\n\t\t} catch (cause) {\n\t\t\tthrow new Error(`${prefix}: ${locale}: ${key} could not be formatted`, {\n\t\t\t\tcause,\n\t\t\t});\n\t\t}\n\t};\n}\n\n/**\n * The translator of `@nxgt/i18n`, for mail: `createTranslator(catalogues,\n * getLanguage)` answers `t(key, args?, language?)`.\n *\n * Where `@nxgt/i18n` answers the key, this **throws**: a key the language's\n * catalogue does not have, a language with no catalogue, and a message that\n * does not format. An e-mail is not sent with a key in it.\n *\n * ```ts\n * import en from './locales/en.json';\n * import fr from './locales/fr.json';\n *\n * const t = createTranslator({ en, fr }, () => pickLocale(user.locale, ['en', 'fr'], 'en'));\n * t('verifyEmail.subject');\n * ```\n */\nexport function createTranslator(\n\tcatalogues: Catalogues,\n\tgetLanguage: LanguageProvider,\n): Translate {\n\tif (typeof catalogues !== 'object' || catalogues === null) {\n\t\tthrow new TypeError(\n\t\t\t'createTranslator: catalogues must be an object of catalogues by locale, as { en, fr }',\n\t\t);\n\t}\n\tif (typeof getLanguage !== 'string' && typeof getLanguage !== 'function') {\n\t\tthrow new TypeError(\n\t\t\t'createTranslator: getLanguage must be a locale or a function that answers one',\n\t\t);\n\t}\n\tconst format = createFormatter('t');\n\treturn (key, args, language = getLanguage) => {\n\t\tconst locale = resolveLanguage(language);\n\t\tif (typeof locale !== 'string' || !Object.hasOwn(catalogues, locale)) {\n\t\t\tthrow new Error(\n\t\t\t\t't: the language is not a locale of the catalogues — pick one with pickLocale',\n\t\t\t);\n\t\t}\n\t\tconst text = lookup(catalogues[locale] as Catalogue, key);\n\t\tif (text === null) throw new Error(`t: ${locale}: ${key} is not a key`);\n\t\treturn format(locale, key, text, args);\n\t};\n}\n",
|
|
7
|
+
"import {\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trmSync,\n\twriteFileSync,\n} from 'node:fs';\nimport { dirname, join, relative, sep } from 'node:path';\n\n/**\n * Where each locale's output goes: `nested` writes `en/verify-email.html`,\n * `flat` writes `verify-email.en.html`.\n */\nexport type Layout = 'nested' | 'flat';\n\n/** One e-mail in one locale: `{ email: 'auth/reset-password', locale: 'fr' }`. */\nexport interface Entry {\n\treadonly email: string;\n\treadonly locale: string;\n}\n\nconst NAME = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;\n\nconst posix = (path: string) => path.split(sep).join('/');\n\n/** Every `.vue` file under `dir`, as a slash-separated path without `.vue`. */\nfunction listEmails(dir: string, label: string): string[] {\n\tif (!existsSync(dir)) return [];\n\treturn readdirSync(dir, { recursive: true, encoding: 'utf8' })\n\t\t.filter((file) => file.endsWith('.vue'))\n\t\t.map((file) => {\n\t\t\tconst email = posix(file).slice(0, -'.vue'.length);\n\t\t\tfor (const segment of email.split('/')) {\n\t\t\t\tif (!NAME.test(segment)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`i18n: ${label}/${email}.vue is not a kebab-case name — name a template as verify-email.vue`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn email;\n\t\t})\n\t\t.sort();\n}\n\n/** The path of `entry`'s file under a folder laid out as `layout`, without an extension. */\nexport function entryPath(entry: Entry, layout: Layout): string {\n\treturn layout === 'nested'\n\t\t? `${entry.locale}/${entry.email}`\n\t\t: `${entry.email}.${entry.locale}`;\n}\n\n/**\n * Reads back what {@link entryPath} wrote — `path` relative to its folder,\n * slash-separated, without an extension — or `null` for a path that is not\n * one e-mail in one of `locales`.\n */\nexport function parseEntry(\n\tpath: string,\n\tlayout: Layout,\n\tlocales: readonly string[],\n): Entry | null {\n\tif (layout === 'nested') {\n\t\tconst [locale, ...rest] = path.split('/');\n\t\tif (locale === undefined || rest.length === 0) return null;\n\t\treturn locales.includes(locale) ? { email: rest.join('/'), locale } : null;\n\t}\n\tconst dot = path.lastIndexOf('.');\n\tconst locale = path.slice(dot + 1);\n\treturn dot > 0 && locales.includes(locale)\n\t\t? { email: path.slice(0, dot), locale }\n\t\t: null;\n}\n\nfunction wrapperSource(wrapper: string, template: string): string {\n\tconst from = posix(relative(dirname(wrapper), template));\n\treturn [\n\t\t'<!-- Generated by @nxgt/mail-i18n: one per template and locale. Never edited, never committed. -->',\n\t\t'<script setup>',\n\t\t`import Email from '${from.startsWith('.') ? from : `./${from}`}';`,\n\t\t'</script>',\n\t\t'<template><Email /></template>',\n\t\t'',\n\t].join('\\n');\n}\n\n/** Every file under `dir`, absolute. */\nfunction listFiles(dir: string): string[] {\n\tif (!existsSync(dir)) return [];\n\treturn readdirSync(dir, { recursive: true, encoding: 'utf8' })\n\t\t.map((file) => join(dir, file))\n\t\t.filter((file) => file.endsWith('.vue'));\n}\n\n/**\n * A folder of templates. `label` names it in an error: `emails`, or\n * `templates[0]` for a package's. `only`, when given, keeps those e-mails of\n * the folder and no other. A `packaged` folder — a package's — must hold\n * templates, and shares no name with another package's.\n */\nexport interface TemplateFolder {\n\treadonly dir: string;\n\treadonly label: string;\n\treadonly only?: readonly string[];\n\treadonly packaged?: boolean;\n}\n\n/** The e-mails of `folder`, the ones it keeps, checked. */\nfunction folderEmails({\n\tdir,\n\tlabel,\n\tonly,\n\tpackaged,\n}: TemplateFolder): string[] {\n\tconst emails = listEmails(dir, label);\n\tif (packaged && emails.length === 0) {\n\t\tthrow new Error(\n\t\t\t`i18n: ${label} holds no template — is ${dir} the folder of a package's e-mails?`,\n\t\t);\n\t}\n\tfor (const email of only ?? []) {\n\t\tif (!emails.includes(email)) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${label} has no template ${email}.vue — name one of its e-mails`,\n\t\t\t);\n\t\t}\n\t}\n\treturn [...(only ?? emails)];\n}\n\n/**\n * Each e-mail of `folders` and its file. The project's folder, first, wins\n * over a package's; two packages with the same e-mail **throw**, since\n * neither would be the obvious one.\n */\nfunction collectTemplates(\n\tfolders: readonly TemplateFolder[],\n): Map<string, string> {\n\tconst templates = new Map<string, string>();\n\tconst owners = new Map<string, TemplateFolder>();\n\tfor (const folder of folders) {\n\t\tfor (const email of folderEmails(folder)) {\n\t\t\tconst owner = owners.get(email);\n\t\t\tif (owner === undefined) {\n\t\t\t\towners.set(email, folder);\n\t\t\t\ttemplates.set(email, join(folder.dir, `${email}.vue`));\n\t\t\t} else if (owner.packaged) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`i18n: ${owner.label} and ${folder.label} both have ${email}.vue — keep one with emails: [...], or write the project's own in its folder`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\treturn templates;\n}\n\n/**\n * Writes one wrapper per template of `folders` and locale under\n * `wrappersDir` — the project's folder first, so its template replaces a\n * package's of the same name — each only when its text changed, and removes\n * the wrappers of templates that are gone, so a running dev server sees a\n * change only where there is one. Answers the e-mails, sorted.\n */\nexport function writeWrappers(options: {\n\treadonly folders: readonly TemplateFolder[];\n\treadonly wrappersDir: string;\n\treadonly locales: readonly string[];\n\treadonly layout: Layout;\n}): string[] {\n\tconst { wrappersDir, locales, layout } = options;\n\tconst templates = collectTemplates(options.folders);\n\tconst wanted = new Set<string>();\n\tfor (const [email, template] of templates) {\n\t\tfor (const locale of locales) {\n\t\t\tconst wrapper = join(\n\t\t\t\twrappersDir,\n\t\t\t\t`${entryPath({ email, locale }, layout)}.vue`,\n\t\t\t);\n\t\t\twanted.add(wrapper);\n\t\t\tconst source = wrapperSource(wrapper, template);\n\t\t\tif (existsSync(wrapper) && readFileSync(wrapper, 'utf8') === source) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tmkdirSync(dirname(wrapper), { recursive: true });\n\t\t\twriteFileSync(wrapper, source);\n\t\t}\n\t}\n\tfor (const file of listFiles(wrappersDir)) {\n\t\tif (!wanted.has(file)) rmSync(file);\n\t}\n\treturn [...templates.keys()].sort();\n}\n\n/** The part of Vite's dev server the watcher uses. */\ninterface WatchedServer {\n\treadonly watcher: {\n\t\tadd(path: string): void;\n\t\ton(event: 'add' | 'unlink', listener: (file: string) => void): void;\n\t};\n}\n\n/**\n * A Vite plugin for Maizzle's renderer: under `maizzle serve`, a template\n * added to or removed from `emailsDir` gets its wrappers at once, so the\n * preview lists it without a restart. A template the build would refuse is\n * reported, never thrown: an error in a watcher would stop the server.\n */\nexport function watchTemplates(emailsDir: string, regenerate: () => void) {\n\treturn {\n\t\tname: 'nxgt-mail-i18n:wrappers',\n\t\tconfigureServer(server: WatchedServer) {\n\t\t\tserver.watcher.add(emailsDir);\n\t\t\tconst onTemplate = (file: string) => {\n\t\t\t\tif (!file.startsWith(emailsDir + sep) || !file.endsWith('.vue')) return;\n\t\t\t\ttry {\n\t\t\t\t\tregenerate();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\t\t\t}\n\t\t\t};\n\t\t\tserver.watcher.on('add', onTemplate);\n\t\t\tserver.watcher.on('unlink', onTemplate);\n\t\t},\n\t};\n}\n",
|
|
8
|
+
"import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join, relative, resolve, sep } from 'node:path';\nimport { isMainThread } from 'node:worker_threads';\nimport { defineMailPlugin, type MailPlugin } from '@nxgt/mail-config';\nimport {\n\ttype Catalogue,\n\ttype Catalogues,\n\tcheckCatalogues,\n\tlayerCatalogues,\n\ttype Messages,\n} from './catalogues';\nimport { buildManifest } from './manifest';\nimport {\n\tcheckTemplates,\n\ttype TemplateSource,\n\ttemplateFolders,\n} from './sources';\nimport { templateProperties } from './template';\nimport { createFormatter } from './translator';\nimport {\n\tRENDERER_TYPES_FILE,\n\trendererTypes,\n\tTYPES_FILE,\n\ttemplateTypes,\n\twriteIfChanged,\n\twriteRendererTypes,\n} from './types';\nimport {\n\ttype Layout,\n\tparseEntry,\n\twatchTemplates,\n\twriteWrappers,\n} from './wrappers';\n\nexport interface I18nOptions {\n\t/** Every locale the project writes, as BCP 47 tags: `['en', 'fr']`. */\n\treadonly locales: readonly string[];\n\t/** The reference every other locale is checked against. Default the first locale. */\n\treadonly fallbackLocale?: string;\n\t/** The folder of `<locale>.json` catalogues. Default `locales`. */\n\treadonly dir?: string;\n\t/** The folder of templates. Default `emails`. */\n\treadonly emails?: string;\n\t/** `nested` writes `dist/en/verify-email.html`; `flat` writes `dist/verify-email.en.html`. Default `nested`. */\n\treadonly layout?: Layout;\n\t/**\n\t * Catalogues under the project's own, as a package ships them —\n\t * `[uiCatalogues]` from `@nxgt/mail-ui`. Each is merged key by key under\n\t * the next, and the project's `<locale>.json` over all of them.\n\t */\n\treadonly catalogues?: readonly Catalogues[];\n\t/**\n\t * Folders of templates under the project's own, as a package ships them —\n\t * `presets().templates` from `@nxgt/mail-presets`. A template in the\n\t * project's `emails/` replaces a package's of the same name.\n\t */\n\treadonly templates?: readonly TemplateSource[];\n\t/**\n\t * The module typing the renderer, written after each build: `MailEmails`,\n\t * for `createMailRenderer<MailEmails>(…)` in the code that sends. A path\n\t * from where `maizzle` runs — outside the project too, as\n\t * `../api/src/generated/mail.ts` — or `false` to write none. Default\n\t * `generated/mail.ts`.\n\t */\n\treadonly rendererTypes?: string | false;\n}\n\n/** Where the wrappers go, under the project. */\nexport const WRAPPERS_DIR = '.maizzle/i18n';\n\n/** The manifest's name, in the output folder. */\nexport const MANIFEST_FILE = 'mail-manifest.json';\n\nconst LOCALE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction checkOptions(options: I18nOptions): void {\n\tif (typeof options !== 'object' || options === null) {\n\t\tthrow new TypeError(\n\t\t\t\"i18n: options must be an object, as { locales: ['en', 'fr'] }\",\n\t\t);\n\t}\n\tconst { locales } = options;\n\tif (!Array.isArray(locales) || locales.length === 0) {\n\t\tthrow new TypeError(\n\t\t\t\"i18n: locales must hold at least one locale, as ['en', 'fr']\",\n\t\t);\n\t}\n\tfor (const locale of locales) {\n\t\tif (typeof locale !== 'string' || !LOCALE.test(locale)) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'i18n: locales holds something that is not a locale — write each as a BCP 47 tag, as en or pt-BR',\n\t\t\t);\n\t\t}\n\t}\n\tif (new Set(locales).size !== locales.length) {\n\t\tthrow new TypeError('i18n: locales holds the same locale twice');\n\t}\n\tif (\n\t\toptions.fallbackLocale !== undefined &&\n\t\t!locales.includes(options.fallbackLocale)\n\t) {\n\t\tthrow new TypeError('i18n: fallbackLocale must be one of locales');\n\t}\n\tfor (const key of ['dir', 'emails'] as const) {\n\t\tconst value = options[key];\n\t\tif (value !== undefined && (typeof value !== 'string' || value === '')) {\n\t\t\tthrow new TypeError(`i18n: ${key} must be a folder of the project`);\n\t\t}\n\t}\n\tif (\n\t\toptions.layout !== undefined &&\n\t\toptions.layout !== 'nested' &&\n\t\toptions.layout !== 'flat'\n\t) {\n\t\tthrow new TypeError(\"i18n: layout must be 'nested' or 'flat'\");\n\t}\n\tconst { catalogues } = options;\n\tif (\n\t\tcatalogues !== undefined &&\n\t\t(!Array.isArray(catalogues) ||\n\t\t\t!catalogues.every(\n\t\t\t\t(source) => isObject(source) && Object.values(source).every(isObject),\n\t\t\t))\n\t) {\n\t\tthrow new TypeError(\n\t\t\t'i18n: catalogues must be a list of catalogues by locale, as [{ en: {...}, fr: {...} }]',\n\t\t);\n\t}\n\tconst typesPath = options.rendererTypes;\n\tif (\n\t\ttypesPath !== undefined &&\n\t\ttypesPath !== false &&\n\t\t(typeof typesPath !== 'string' || !typesPath.endsWith('.ts'))\n\t) {\n\t\tthrow new TypeError(\n\t\t\t'i18n: rendererTypes must be the path of a .ts file, as generated/mail.ts, or false',\n\t\t);\n\t}\n\tcheckTemplates(options.templates);\n}\n\n/** Reads `<dir>/<locale>.json` for each locale. A missing or broken file **throws**. */\nfunction readCatalogues(\n\tdir: string,\n\tdirName: string,\n\tlocales: readonly string[],\n): Record<string, Catalogue> {\n\tconst out: Record<string, Catalogue> = {};\n\tfor (const locale of locales) {\n\t\tconst file = join(dir, `${locale}.json`);\n\t\tconst name = `${dirName}/${locale}.json`;\n\t\tif (!existsSync(file)) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${name} is missing — every locale has a catalogue`,\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\tout[locale] = JSON.parse(readFileSync(file, 'utf8'));\n\t\t} catch {\n\t\t\tthrow new Error(`i18n: ${name} is not valid JSON`);\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * The i18n plugin, for `defineMailConfig`:\n *\n * ```ts\n * defineMailConfig({ plugins: [i18n({ locales: ['en', 'fr'] })] });\n * ```\n *\n * It checks `locales/<locale>.json`, writes one wrapper per template and\n * locale under `.maizzle/i18n/` so one build writes every locale, gives each\n * template `t`, `locale` and `placeholder`, and writes\n * `dist/mail-manifest.json`. A catalogue or a template that cannot be right\n * **fails the build**, naming the locale and the key.\n */\nexport function i18n(options: I18nOptions): MailPlugin {\n\tcheckOptions(options);\n\tconst { locales } = options;\n\tconst fallbackLocale = options.fallbackLocale ?? (locales[0] as string);\n\tconst layout = options.layout ?? 'nested';\n\tconst dirName = options.dir ?? 'locales';\n\tconst emailsName = options.emails ?? 'emails';\n\tconst cwd = process.cwd();\n\tconst emailsDir = resolve(cwd, emailsName);\n\tconst wrappersDir = resolve(cwd, WRAPPERS_DIR);\n\n\tconst messages = checkCatalogues(\n\t\tlayerCatalogues(\n\t\t\toptions.catalogues ?? [],\n\t\t\treadCatalogues(resolve(cwd, dirName), dirName, locales),\n\t\t),\n\t\tlocales,\n\t\tfallbackLocale,\n\t);\n\tconst reference = messages.get(fallbackLocale) as Messages;\n\tconst format = createFormatter('i18n');\n\tconst regenerate = () =>\n\t\twriteWrappers({\n\t\t\tfolders: templateFolders(\n\t\t\t\t{ dir: emailsDir, name: emailsName },\n\t\t\t\toptions.templates ?? [],\n\t\t\t),\n\t\t\twrappersDir,\n\t\t\tlocales,\n\t\t\tlayout,\n\t\t});\n\t// A parallel build loads the config again in each worker: only the main\n\t// thread writes, so two workers never write the same file.\n\tif (isMainThread) {\n\t\tregenerate();\n\t\twriteIfChanged(\n\t\t\tresolve(cwd, TYPES_FILE),\n\t\t\ttemplateTypes(reference, fallbackLocale),\n\t\t);\n\t}\n\n\treturn defineMailPlugin({\n\t\tname: 'i18n',\n\t\tcontent: [`${wrappersDir}/**/*.vue`],\n\t\t// `maizzle serve` watches locales/ already; another folder is added.\n\t\t...(dirName === 'locales' ? {} : { server: { watch: [`${dirName}/**`] } }),\n\t\tvite: { plugins: [watchTemplates(emailsDir, regenerate)] },\n\t\tbeforeRender({ config, template }) {\n\t\t\tconst path = relative(\n\t\t\t\twrappersDir,\n\t\t\t\tjoin(template.path.dir, template.path.name),\n\t\t\t)\n\t\t\t\t.split(sep)\n\t\t\t\t.join('/');\n\t\t\tconst entry = parseEntry(path, layout, locales);\n\t\t\tif (entry === null) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`i18n: ${relative(cwd, join(template.path.dir, template.path.base))} is not built through the i18n plugin — leave content to it, and put templates in ${emailsName}/`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconfig.vue ??= {};\n\t\t\tconfig.vue.globalProperties = {\n\t\t\t\t...config.vue.globalProperties,\n\t\t\t\t...templateProperties({\n\t\t\t\t\t...entry,\n\t\t\t\t\tmessages: messages.get(entry.locale) as Messages,\n\t\t\t\t\treference,\n\t\t\t\t\tformat,\n\t\t\t\t}),\n\t\t\t};\n\t\t},\n\t\tafterBuild({ files, config }) {\n\t\t\tconst outputDir = resolve(cwd, config.output?.path ?? 'dist');\n\t\t\tconst manifest = buildManifest({\n\t\t\t\tfiles,\n\t\t\t\toutputDir,\n\t\t\t\thtmlExtension: config.output?.extension ?? 'html',\n\t\t\t\tlayout,\n\t\t\t\tlocales,\n\t\t\t\tfallbackLocale,\n\t\t\t\tmessages,\n\t\t\t});\n\t\t\twriteFileSync(\n\t\t\t\tjoin(outputDir, MANIFEST_FILE),\n\t\t\t\t`${JSON.stringify(manifest, null, '\\t')}\\n`,\n\t\t\t);\n\t\t\tconst typesFile = options.rendererTypes ?? RENDERER_TYPES_FILE;\n\t\t\tif (typesFile !== false) {\n\t\t\t\twriteRendererTypes(\n\t\t\t\t\tresolve(cwd, typesFile),\n\t\t\t\t\ttypesFile,\n\t\t\t\t\trendererTypes(manifest),\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t});\n}\n",
|
|
9
|
+
"import {\n\ttype MessageFormatElement,\n\tparse,\n\tTYPE,\n} from '@formatjs/icu-messageformat-parser';\n\n/**\n * A catalogue as written: nested objects whose leaves are ICU messages, the\n * conventions of `@nxgt/i18n`.\n *\n * ```json\n * { \"verifyEmail\": { \"subject\": \"Confirm your e-mail address\" } }\n * ```\n */\nexport interface Catalogue {\n\treadonly [key: string]: string | Catalogue;\n}\n\n/** A catalogue per locale: `{ en: {...}, fr: {...} }`. */\nexport type Catalogues = Readonly<Record<string, Catalogue>>;\n\n/**\n * What an argument is, from the way a message uses it: `{n, number}` and\n * `{n, plural, …}` a number, `{at, date}` a date, anything else a string.\n */\nexport type ArgumentKind = 'string' | 'number' | 'date';\n\n/** One message, checked, with the kind of each argument it uses. */\nexport interface Message {\n\treadonly text: string;\n\treadonly args: ReadonlyMap<string, ArgumentKind>;\n\t/** The arguments a `{x, select, …}` chooses on: a placeholder would always choose `other`. */\n\treadonly selects: ReadonlySet<string>;\n}\n\n/** Every message of one locale, by dotted key: `verifyEmail.subject`. */\nexport type Messages = ReadonlyMap<string, Message>;\n\nconst SEGMENT = /^[a-z][a-zA-Z0-9]*$/;\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** `over` merged into `under` key by key: an object is merged, anything else replaces. */\nfunction mergeCatalogue(under: Catalogue, over: Catalogue): Catalogue {\n\tconst out: Record<string, string | Catalogue> = { ...under };\n\tfor (const [key, value] of Object.entries(over)) {\n\t\tconst below = Object.hasOwn(out, key) ? out[key] : undefined;\n\t\t// Defined, not assigned: `out.__proto__ = …` would set the prototype and\n\t\t// hide the key from the check that refuses it.\n\t\tObject.defineProperty(out, key, {\n\t\t\tvalue:\n\t\t\t\tisObject(below) && isObject(value)\n\t\t\t\t\t? mergeCatalogue(below, value)\n\t\t\t\t\t: value,\n\t\t\tenumerable: true,\n\t\t\twritable: true,\n\t\t\tconfigurable: true,\n\t\t});\n\t}\n\treturn out;\n}\n\n/**\n * Each of `project`'s locales, with `sources` merged under it in order: a\n * source's locale the project does not have is left out.\n */\nexport function layerCatalogues(\n\tsources: readonly Catalogues[],\n\tproject: Record<string, Catalogue>,\n): Record<string, Catalogue> {\n\tconst out: Record<string, Catalogue> = {};\n\tfor (const [locale, catalogue] of Object.entries(project)) {\n\t\tout[locale] = [...sources.map((source) => source[locale]), catalogue]\n\t\t\t.filter((layer): layer is Catalogue => layer !== undefined)\n\t\t\t.reduce(mergeCatalogue, {});\n\t}\n\treturn out;\n}\n\nfunction flatten(\n\tcatalogue: unknown,\n\tlocale: string,\n\tprefix: string,\n\tinto: Map<string, string>,\n): void {\n\tif (!isObject(catalogue)) {\n\t\tthrow new Error(\n\t\t\t`i18n: ${locale}: ${prefix || 'the catalogue'} must be an object of messages`,\n\t\t);\n\t}\n\tfor (const [segment, value] of Object.entries(catalogue)) {\n\t\tconst key = prefix === '' ? segment : `${prefix}.${segment}`;\n\t\tif (!SEGMENT.test(segment)) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${locale}: ${key} is not camelCase — every segment of a key is camelCase, and nested rather than dotted, as verifyEmail.title`,\n\t\t\t);\n\t\t}\n\t\tif (typeof value === 'string') into.set(key, value);\n\t\telse if (isObject(value)) flatten(value, locale, key, into);\n\t\telse {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${locale}: ${key} must be a message (a string) or an object of messages`,\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction collect(\n\telements: readonly MessageFormatElement[],\n\tuses: Map<string, Set<ArgumentKind | 'plain' | 'select'>>,\n): void {\n\tconst use = (name: string, kind: ArgumentKind | 'plain' | 'select') =>\n\t\tuses.set(name, (uses.get(name) ?? new Set()).add(kind));\n\tfor (const element of elements) {\n\t\tif (element.type === TYPE.argument) use(element.value, 'plain');\n\t\telse if (element.type === TYPE.number) use(element.value, 'number');\n\t\telse if (element.type === TYPE.date || element.type === TYPE.time) {\n\t\t\tuse(element.value, 'date');\n\t\t} else if (element.type === TYPE.select || element.type === TYPE.plural) {\n\t\t\tuse(element.value, element.type === TYPE.plural ? 'number' : 'select');\n\t\t\tfor (const option of Object.values(element.options)) {\n\t\t\t\tcollect(option.value, uses);\n\t\t\t}\n\t\t} else if (element.type === TYPE.tag) collect(element.children, uses);\n\t}\n}\n\n/**\n * Parses a message and types its arguments. The parser's own error is not\n * kept as the cause: it carries the text of the message, and a build failure\n * names a key, never a text.\n */\nfunction analyse(text: string, locale: string, key: string): Message {\n\tlet ast: MessageFormatElement[];\n\ttry {\n\t\tast = parse(text, { ignoreTag: true, shouldParseSkeletons: true });\n\t} catch (cause) {\n\t\tconst reason = cause instanceof Error ? cause.message : 'refused';\n\t\tthrow new Error(\n\t\t\t`i18n: ${locale}: ${key} is not a valid ICU message (${reason})`,\n\t\t);\n\t}\n\tconst uses = new Map<string, Set<ArgumentKind | 'plain' | 'select'>>();\n\tcollect(ast, uses);\n\tconst args = new Map<string, ArgumentKind>();\n\tconst selects = new Set<string>();\n\tfor (const [name, kinds] of uses) {\n\t\tif (!SEGMENT.test(name)) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${locale}: ${key} uses {${name}}, which is not camelCase — an argument is a camelCase name, as {firstName}`,\n\t\t\t);\n\t\t}\n\t\tif (kinds.has('select')) selects.add(name);\n\t\tconst typed = [\n\t\t\t...new Set(\n\t\t\t\t[...kinds]\n\t\t\t\t\t.filter((kind) => kind !== 'plain')\n\t\t\t\t\t.map((kind) => (kind === 'select' ? 'string' : kind)),\n\t\t\t),\n\t\t];\n\t\tif (typed.length > 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${locale}: ${key} uses {${name}} as ${typed.join(' and as ')}`,\n\t\t\t);\n\t\t}\n\t\targs.set(name, (typed[0] as ArgumentKind | undefined) ?? 'string');\n\t}\n\treturn { text, args, selects };\n}\n\n/**\n * Checks `locale` against the fallback locale: the same keys, and no\n * argument the fallback does not declare, or declares as another kind. A\n * translation may leave an argument out.\n */\nfunction compare(\n\tlocale: string,\n\tmessages: Messages,\n\tfallbackLocale: string,\n\treference: Messages,\n): void {\n\tfor (const key of [...reference.keys()].sort()) {\n\t\tif (!messages.has(key)) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${locale}: ${key} is missing — ${fallbackLocale}, the fallback locale, has it`,\n\t\t\t);\n\t\t}\n\t}\n\tfor (const key of [...messages.keys()].sort()) {\n\t\tconst declared = reference.get(key);\n\t\tif (declared === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t`i18n: ${locale}: ${key} is not a key of ${fallbackLocale}, the fallback locale`,\n\t\t\t);\n\t\t}\n\t\tfor (const [name, kind] of (messages.get(key) as Message).args) {\n\t\t\tconst expected = declared.args.get(name);\n\t\t\tif (expected === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`i18n: ${locale}: ${key} uses {${name}}, which ${fallbackLocale} does not declare`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (expected !== kind) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`i18n: ${locale}: ${key} uses {${name}} as ${kind}, and ${fallbackLocale} declares it as ${expected}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Checks the catalogues of every locale and answers their messages. A\n * catalogue that is not objects of camelCase keys, a message that does not\n * parse, and a locale that differs from the fallback locale in its keys or\n * its arguments **throw**, naming the locale and the key.\n */\nexport function checkCatalogues(\n\tcatalogues: Catalogues,\n\tlocales: readonly string[],\n\tfallbackLocale: string,\n): ReadonlyMap<string, Messages> {\n\tconst out = new Map<string, Messages>();\n\tfor (const locale of locales) {\n\t\tconst flat = new Map<string, string>();\n\t\tflatten(catalogues[locale], locale, '', flat);\n\t\tout.set(\n\t\t\tlocale,\n\t\t\tnew Map(\n\t\t\t\t[...flat].map(([key, text]) => [key, analyse(text, locale, key)]),\n\t\t\t),\n\t\t);\n\t}\n\tconst reference = out.get(fallbackLocale) as Messages;\n\tfor (const locale of locales) {\n\t\tif (locale !== fallbackLocale) {\n\t\t\tcompare(locale, out.get(locale) as Messages, fallbackLocale, reference);\n\t\t}\n\t}\n\treturn out;\n}\n",
|
|
10
|
+
"import { isAbsolute } from 'node:path';\nimport type { TemplateFolder } from './wrappers';\n\n/** A package's folder of templates, for `i18n({ templates })`. */\nexport interface TemplateSource {\n\t/** The folder, absolute. */\n\treadonly dir: string;\n\t/** The e-mails of the folder to build, as `['verify-email']`. Default every one. */\n\treadonly emails?: readonly [string, ...string[]];\n}\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === 'object' && value !== null && !Array.isArray(value);\n\nconst isSource = (source: unknown): source is TemplateSource =>\n\tisObject(source) &&\n\ttypeof source.dir === 'string' &&\n\tisAbsolute(source.dir) &&\n\t(source.emails === undefined ||\n\t\t(Array.isArray(source.emails) &&\n\t\t\tsource.emails.length > 0 &&\n\t\t\tsource.emails.every((email) => typeof email === 'string') &&\n\t\t\tnew Set(source.emails).size === source.emails.length));\n\n/** Refuses a `templates` option that is not a list of {@link TemplateSource}. */\nexport function checkTemplates(\n\ttemplates: unknown,\n): asserts templates is readonly TemplateSource[] | undefined {\n\tif (\n\t\ttemplates !== undefined &&\n\t\t(!Array.isArray(templates) || !templates.every(isSource))\n\t) {\n\t\tthrow new TypeError(\n\t\t\t\"i18n: templates must be a list of template folders, as [{ dir: '/abs/path/emails' }] — emails, when given, names at least one, each once\",\n\t\t);\n\t}\n}\n\n/**\n * The folders the wrappers are written from: the project's `emails/` first,\n * so its template replaces a package's of the same name, then each package's.\n */\nexport function templateFolders(\n\temails: { readonly dir: string; readonly name: string },\n\ttemplates: readonly TemplateSource[],\n): TemplateFolder[] {\n\treturn [\n\t\t{ dir: emails.dir, label: emails.name },\n\t\t...templates.map((source, index) => ({\n\t\t\tdir: source.dir,\n\t\t\tlabel: `templates[${index}]`,\n\t\t\tpackaged: true,\n\t\t\t...(source.emails && { only: source.emails }),\n\t\t})),\n\t];\n}\n",
|
|
11
|
+
"import type { ArgumentKind, Messages } from './catalogues';\nimport { placeholderMark } from './manifest';\nimport type { createFormatter, MessageArgs } from './translator';\n\nconst NAME = /^[a-z][a-zA-Z0-9]*$/;\n\nconst KINDS: Record<ArgumentKind, (value: unknown) => boolean> = {\n\tstring: (value) => typeof value === 'string' || typeof value === 'number',\n\tnumber: (value) => typeof value === 'number',\n\tdate: (value) => value instanceof Date || typeof value === 'number',\n};\n\nconst isPlaceholder = (value: unknown) =>\n\ttypeof value === 'string' && /^\\{\\{ [a-z][a-zA-Z0-9]* \\}\\}$/.test(value);\n\nconst kindOf = (value: unknown) =>\n\tvalue === null ? 'null' : value instanceof Date ? 'date' : typeof value;\n\n/**\n * What one template gets in one locale: `t`, `locale` and `placeholder`.\n *\n * `t` is checked against the fallback locale's message, which declares every\n * argument: an unknown key, an argument left out, one the message does not\n * use, or one of the wrong kind **fails the build**, naming the locale, the\n * template and the key.\n */\nexport function templateProperties(options: {\n\treadonly email: string;\n\treadonly locale: string;\n\treadonly messages: Messages;\n\treadonly reference: Messages;\n\treadonly format: ReturnType<typeof createFormatter>;\n}) {\n\tconst { email, locale, messages, reference, format } = options;\n\tconst where = `i18n: ${locale}: ${email}`;\n\treturn {\n\t\tlocale,\n\t\tt(key: string, args: MessageArgs = {}): string {\n\t\t\tconst message = typeof key === 'string' ? messages.get(key) : undefined;\n\t\t\tconst declared = typeof key === 'string' ? reference.get(key) : undefined;\n\t\t\tif (message === undefined || declared === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${where} calls t('${String(key)}'), which is not a key of the catalogues`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (typeof args !== 'object' || args === null) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${where} calls t('${key}') with arguments that are not an object, as { name: placeholder('name') }`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tfor (const [name, kind] of declared.args) {\n\t\t\t\tif (!Object.hasOwn(args, name)) {\n\t\t\t\t\tthrow new Error(`${where} calls t('${key}') without {${name}}`);\n\t\t\t\t}\n\t\t\t\tif (declared.selects.has(name) && isPlaceholder(args[name])) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`${where} passes a placeholder to {${name}}, which ${key} chooses on with a select — a placeholder always chooses other`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!KINDS[kind](args[name])) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`${where} passes {${name}} to ${key} as a ${kindOf(args[name])} — the message uses it as a ${kind}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const name of Object.keys(args)) {\n\t\t\t\tif (!declared.args.has(name)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`${where} passes {${name}} to ${key}, which does not use it`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn format(locale, key, message.text, args);\n\t\t},\n\t\tplaceholder(name: string): string {\n\t\t\tif (typeof name !== 'string' || !NAME.test(name)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${where} calls placeholder() with a name that is not camelCase — as placeholder('firstName')`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn placeholderMark(name);\n\t\t},\n\t};\n}\n",
|
|
12
|
+
"import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type { ArgumentKind, Messages } from './catalogues';\nimport type { Manifest } from './manifest';\n\n/** Where the template types go, under the project: `.maizzle/*.d.ts` is in the starter's `tsconfig.json`. */\nexport const TYPES_FILE = '.maizzle/nxgt-mail-i18n.d.ts';\n\n/** What `t` accepts for each kind — as the build checks it. */\nconst TYPE_OF: Readonly<Record<ArgumentKind, string>> = {\n\tstring: 'string | number',\n\tnumber: 'number',\n\tdate: 'Date | number',\n};\n\n/**\n * The declaration file that types `t` in the templates: each key of the\n * `reference` catalogue, with its arguments, so an editor completes a key and\n * flags an unknown one or a missing argument. Keys and names are checked\n * before this runs, so they are safe to write quoted.\n */\nexport function templateTypes(\n\treference: Messages,\n\tfallbackLocale: string,\n): string {\n\tconst entries = [...reference.entries()]\n\t\t.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n\t\t.map(([key, message]) => {\n\t\t\tconst args = [...message.args.entries()]\n\t\t\t\t.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n\t\t\t\t.map(([name, kind]) => `${name}: ${TYPE_OF[kind]}`);\n\t\t\treturn `\\t\\t'${key}': { ${args.join('; ')}${args.length > 0 ? ' ' : ''}};`;\n\t\t});\n\treturn [\n\t\t`// Generated by @nxgt/mail-i18n from the ${fallbackLocale} catalogue, each time the config loads.`,\n\t\t'// Never edited, never committed: it types t() in the templates.',\n\t\t\"import type {} from '@nxgt/mail-i18n';\",\n\t\t'',\n\t\t\"declare module '@nxgt/mail-i18n' {\",\n\t\t'\\tinterface TemplateMessages {',\n\t\t...entries,\n\t\t'\\t}',\n\t\t'}',\n\t\t'',\n\t].join('\\n');\n}\n\n/** The first line of the renderer's types: a file without it is not the plugin's to replace. */\nconst RENDERER_TYPES_HEADER = '// Generated by @nxgt/mail-i18n from the build';\n\n/** Where the renderer's types go, under the project, by default. */\nexport const RENDERER_TYPES_FILE = 'generated/mail.ts';\n\n/**\n * The module that types the renderer: `MailEmails`, each e-mail of the build\n * with the variables it takes at send time, for\n * `createMailRenderer<MailEmails>(…)`. A URL variable takes a string; any\n * other a string or a number, as `render` writes it. `@nxgt/mail`'s\n * `RenderArguments` reads `Readonly<Record<string, never>>` as an e-mail\n * without variables, and its type specs copy this output: change them\n * together.\n */\nexport function rendererTypes(manifest: Manifest): string {\n\tconst entries = Object.entries(manifest.emails)\n\t\t.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n\t\t.map(([email, { variables, urlVariables }]) => {\n\t\t\tconst fields = variables.map(\n\t\t\t\t(name) =>\n\t\t\t\t\t`readonly ${name}: ${urlVariables.includes(name) ? 'string' : 'string | number'}`,\n\t\t\t);\n\t\t\tconst type =\n\t\t\t\tfields.length > 0\n\t\t\t\t\t? `{ ${fields.join('; ')} }`\n\t\t\t\t\t: 'Readonly<Record<string, never>>';\n\t\t\t// A name is a file's path, quoted as JSON: valid TypeScript whatever it holds.\n\t\t\treturn `\\t${JSON.stringify(email)}: ${type};`;\n\t\t});\n\treturn [\n\t\t`${RENDERER_TYPES_HEADER}, after each maizzle build.`,\n\t\t'// Never edited; committed, so the code that sends type-checks without a build.',\n\t\t'',\n\t\t'/** The e-mails of the build, each with the variables it takes when it is sent. */',\n\t\t'export interface MailEmails {',\n\t\t...entries,\n\t\t'}',\n\t\t'',\n\t].join('\\n');\n}\n\n/**\n * Writes the renderer's types to `file`, shown as `path` — unless `file` holds\n * something the plugin did not write: that **throws**, and the file is kept.\n */\nexport function writeRendererTypes(\n\tfile: string,\n\tpath: string,\n\tsource: string,\n): void {\n\tif (\n\t\texistsSync(file) &&\n\t\t!readFileSync(file, 'utf8').startsWith(RENDERER_TYPES_HEADER)\n\t) {\n\t\tthrow new Error(\n\t\t\t`i18n: ${path} was not written by i18n() — point rendererTypes at a file of its own`,\n\t\t);\n\t}\n\twriteIfChanged(file, source);\n}\n\n/** Writes `source` to `file` only when it changed, so an editor or a watcher sees a change only where there is one. */\nexport function writeIfChanged(file: string, source: string): void {\n\tif (existsSync(file) && readFileSync(file, 'utf8') === source) return;\n\tmkdirSync(dirname(file), { recursive: true });\n\twriteFileSync(file, source);\n}\n"
|
|
13
|
+
],
|
|
14
|
+
"mappings": "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",
|
|
15
|
+
"debugId": "874EDA850D792EF364756E2164756E21",
|
|
16
|
+
"names": []
|
|
17
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Messages } from './catalogues';
|
|
2
|
+
import { type Layout } from './wrappers';
|
|
3
|
+
/** One e-mail, as the renderer reads it. */
|
|
4
|
+
export interface ManifestEmail {
|
|
5
|
+
/** Every placeholder of the e-mail, in any locale, sorted. */
|
|
6
|
+
readonly variables: readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* The placeholders a URL attribute starts with — `href`, `src`,
|
|
9
|
+
* `background`, `poster`, `action`: they decide the scheme, so a URL fills
|
|
10
|
+
* them. One later in the value, as `?token={{ token }}`, is not one.
|
|
11
|
+
*/
|
|
12
|
+
readonly urlVariables: readonly string[];
|
|
13
|
+
/** The subject per locale, its placeholders kept as `{{ name }}`. */
|
|
14
|
+
readonly subject: Readonly<Record<string, string>>;
|
|
15
|
+
/** The built files per locale, relative to the output folder. */
|
|
16
|
+
readonly files: Readonly<Record<string, {
|
|
17
|
+
readonly html: string;
|
|
18
|
+
readonly text: string | null;
|
|
19
|
+
}>>;
|
|
20
|
+
}
|
|
21
|
+
/** `dist/mail-manifest.json`: what the build wrote, for the renderer. */
|
|
22
|
+
export interface Manifest {
|
|
23
|
+
readonly locales: readonly string[];
|
|
24
|
+
readonly fallbackLocale: string;
|
|
25
|
+
readonly emails: Readonly<Record<string, ManifestEmail>>;
|
|
26
|
+
}
|
|
27
|
+
/** `{{ name }}` — what `placeholder('name')` writes. */
|
|
28
|
+
export declare const placeholderMark: (name: string) => string;
|
|
29
|
+
/** `auth/reset-password` → `auth.resetPassword`: where its messages live. */
|
|
30
|
+
export declare const emailKey: (email: string) => string;
|
|
31
|
+
/**
|
|
32
|
+
* Reads what the build wrote — `files`, absolute, under `outputDir` — and
|
|
33
|
+
* answers the manifest: for each e-mail its placeholders, those in a URL, its
|
|
34
|
+
* subject per locale and its files.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildManifest(options: {
|
|
37
|
+
readonly files: readonly string[];
|
|
38
|
+
readonly outputDir: string;
|
|
39
|
+
readonly htmlExtension: string;
|
|
40
|
+
readonly layout: Layout;
|
|
41
|
+
readonly locales: readonly string[];
|
|
42
|
+
readonly fallbackLocale: string;
|
|
43
|
+
readonly messages: ReadonlyMap<string, Messages>;
|
|
44
|
+
}): Manifest;
|
|
45
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAyB,KAAK,MAAM,EAAc,MAAM,YAAY,CAAC;AAE5E,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CACvB,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CACvE,CAAC;CACF;AAED,yEAAyE;AACzE,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;CACzD;AAQD,wDAAwD;AACxD,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,WAAoB,CAAC;AAKjE,6EAA6E;AAC7E,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,WAM1B,CAAC;AA8Cb;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;CACjD,GAAG,QAAQ,CA8EX"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type MailPlugin } from '@nxgt/mail-config';
|
|
2
|
+
import { type Catalogues } from './catalogues';
|
|
3
|
+
import { type TemplateSource } from './sources';
|
|
4
|
+
import { type Layout } from './wrappers';
|
|
5
|
+
export interface I18nOptions {
|
|
6
|
+
/** Every locale the project writes, as BCP 47 tags: `['en', 'fr']`. */
|
|
7
|
+
readonly locales: readonly string[];
|
|
8
|
+
/** The reference every other locale is checked against. Default the first locale. */
|
|
9
|
+
readonly fallbackLocale?: string;
|
|
10
|
+
/** The folder of `<locale>.json` catalogues. Default `locales`. */
|
|
11
|
+
readonly dir?: string;
|
|
12
|
+
/** The folder of templates. Default `emails`. */
|
|
13
|
+
readonly emails?: string;
|
|
14
|
+
/** `nested` writes `dist/en/verify-email.html`; `flat` writes `dist/verify-email.en.html`. Default `nested`. */
|
|
15
|
+
readonly layout?: Layout;
|
|
16
|
+
/**
|
|
17
|
+
* Catalogues under the project's own, as a package ships them —
|
|
18
|
+
* `[uiCatalogues]` from `@nxgt/mail-ui`. Each is merged key by key under
|
|
19
|
+
* the next, and the project's `<locale>.json` over all of them.
|
|
20
|
+
*/
|
|
21
|
+
readonly catalogues?: readonly Catalogues[];
|
|
22
|
+
/**
|
|
23
|
+
* Folders of templates under the project's own, as a package ships them —
|
|
24
|
+
* `presets().templates` from `@nxgt/mail-presets`. A template in the
|
|
25
|
+
* project's `emails/` replaces a package's of the same name.
|
|
26
|
+
*/
|
|
27
|
+
readonly templates?: readonly TemplateSource[];
|
|
28
|
+
/**
|
|
29
|
+
* The module typing the renderer, written after each build: `MailEmails`,
|
|
30
|
+
* for `createMailRenderer<MailEmails>(…)` in the code that sends. A path
|
|
31
|
+
* from where `maizzle` runs — outside the project too, as
|
|
32
|
+
* `../api/src/generated/mail.ts` — or `false` to write none. Default
|
|
33
|
+
* `generated/mail.ts`.
|
|
34
|
+
*/
|
|
35
|
+
readonly rendererTypes?: string | false;
|
|
36
|
+
}
|
|
37
|
+
/** Where the wrappers go, under the project. */
|
|
38
|
+
export declare const WRAPPERS_DIR = ".maizzle/i18n";
|
|
39
|
+
/** The manifest's name, in the output folder. */
|
|
40
|
+
export declare const MANIFEST_FILE = "mail-manifest.json";
|
|
41
|
+
/**
|
|
42
|
+
* The i18n plugin, for `defineMailConfig`:
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* defineMailConfig({ plugins: [i18n({ locales: ['en', 'fr'] })] });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* It checks `locales/<locale>.json`, writes one wrapper per template and
|
|
49
|
+
* locale under `.maizzle/i18n/` so one build writes every locale, gives each
|
|
50
|
+
* template `t`, `locale` and `placeholder`, and writes
|
|
51
|
+
* `dist/mail-manifest.json`. A catalogue or a template that cannot be right
|
|
52
|
+
* **fails the build**, naming the locale and the key.
|
|
53
|
+
*/
|
|
54
|
+
export declare function i18n(options: I18nOptions): MailPlugin;
|
|
55
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAEN,KAAK,UAAU,EAIf,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEN,KAAK,cAAc,EAEnB,MAAM,WAAW,CAAC;AAWnB,OAAO,EACN,KAAK,MAAM,EAIX,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,WAAW;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,qFAAqF;IACrF,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,gHAAgH;IAChH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC/C;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACxC;AAED,gDAAgD;AAChD,eAAO,MAAM,YAAY,kBAAkB,CAAC;AAE5C,iDAAiD;AACjD,eAAO,MAAM,aAAa,uBAAuB,CAAC;AAiGlD;;;;;;;;;;;;GAYG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,UAAU,CAgGrD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TemplateFolder } from './wrappers';
|
|
2
|
+
/** A package's folder of templates, for `i18n({ templates })`. */
|
|
3
|
+
export interface TemplateSource {
|
|
4
|
+
/** The folder, absolute. */
|
|
5
|
+
readonly dir: string;
|
|
6
|
+
/** The e-mails of the folder to build, as `['verify-email']`. Default every one. */
|
|
7
|
+
readonly emails?: readonly [string, ...string[]];
|
|
8
|
+
}
|
|
9
|
+
/** Refuses a `templates` option that is not a list of {@link TemplateSource}. */
|
|
10
|
+
export declare function checkTemplates(templates: unknown): asserts templates is readonly TemplateSource[] | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* The folders the wrappers are written from: the project's `emails/` first,
|
|
13
|
+
* so its template replaces a package's of the same name, then each package's.
|
|
14
|
+
*/
|
|
15
|
+
export declare function templateFolders(emails: {
|
|
16
|
+
readonly dir: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
}, templates: readonly TemplateSource[]): TemplateFolder[];
|
|
19
|
+
//# sourceMappingURL=sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC9B,4BAA4B;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,oFAAoF;IACpF,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;CACjD;AAeD,iFAAiF;AACjF,wBAAgB,cAAc,CAC7B,SAAS,EAAE,OAAO,GAChB,OAAO,CAAC,SAAS,IAAI,SAAS,cAAc,EAAE,GAAG,SAAS,CAS5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,MAAM,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACvD,SAAS,EAAE,SAAS,cAAc,EAAE,GAClC,cAAc,EAAE,CAUlB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Messages } from './catalogues';
|
|
2
|
+
import type { createFormatter, MessageArgs } from './translator';
|
|
3
|
+
/**
|
|
4
|
+
* What one template gets in one locale: `t`, `locale` and `placeholder`.
|
|
5
|
+
*
|
|
6
|
+
* `t` is checked against the fallback locale's message, which declares every
|
|
7
|
+
* argument: an unknown key, an argument left out, one the message does not
|
|
8
|
+
* use, or one of the wrong kind **fails the build**, naming the locale, the
|
|
9
|
+
* template and the key.
|
|
10
|
+
*/
|
|
11
|
+
export declare function templateProperties(options: {
|
|
12
|
+
readonly email: string;
|
|
13
|
+
readonly locale: string;
|
|
14
|
+
readonly messages: Messages;
|
|
15
|
+
readonly reference: Messages;
|
|
16
|
+
readonly format: ReturnType<typeof createFormatter>;
|
|
17
|
+
}): {
|
|
18
|
+
locale: string;
|
|
19
|
+
t(key: string, args?: MessageArgs): string;
|
|
20
|
+
placeholder(name: string): string;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAgBjE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;CACpD;;WAKQ,MAAM,SAAQ,WAAW,GAAQ,MAAM;sBAqC5B,MAAM,GAAG,MAAM;EASlC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Catalogues } from './catalogues';
|
|
2
|
+
/** The values a message's arguments take: `{ name: 'Ada', count: 3 }`. */
|
|
3
|
+
export type MessageArgs = Readonly<Record<string, string | number | Date>>;
|
|
4
|
+
/** A locale, or a function that answers it at each call — as in `@nxgt/i18n`. */
|
|
5
|
+
export type LanguageProvider = string | (() => string);
|
|
6
|
+
/** `t(key, args?, language?)`: the message `key`, formatted in the language. */
|
|
7
|
+
export type Translate = (key: string, args?: MessageArgs, language?: LanguageProvider) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Formats with a cache of compiled messages, one per locale and key. A
|
|
10
|
+
* message that does not format **throws**, the formatter's error as the
|
|
11
|
+
* cause.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createFormatter(prefix: string): (locale: string, key: string, text: string, args?: MessageArgs) => string;
|
|
14
|
+
/**
|
|
15
|
+
* The translator of `@nxgt/i18n`, for mail: `createTranslator(catalogues,
|
|
16
|
+
* getLanguage)` answers `t(key, args?, language?)`.
|
|
17
|
+
*
|
|
18
|
+
* Where `@nxgt/i18n` answers the key, this **throws**: a key the language's
|
|
19
|
+
* catalogue does not have, a language with no catalogue, and a message that
|
|
20
|
+
* does not format. An e-mail is not sent with a key in it.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* import en from './locales/en.json';
|
|
24
|
+
* import fr from './locales/fr.json';
|
|
25
|
+
*
|
|
26
|
+
* const t = createTranslator({ en, fr }, () => pickLocale(user.locale, ['en', 'fr'], 'en'));
|
|
27
|
+
* t('verifyEmail.subject');
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function createTranslator(catalogues: Catalogues, getLanguage: LanguageProvider): Translate;
|
|
31
|
+
//# sourceMappingURL=translator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../src/translator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1D,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;AAE3E,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC;AAEvD,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,CACvB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,WAAW,EAClB,QAAQ,CAAC,EAAE,gBAAgB,KACvB,MAAM,CAAC;AAcZ;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,IAErC,QAAQ,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,MAAM,EAAE,OAAO,WAAW,YAiBrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC/B,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,gBAAgB,GAC3B,SAAS,CAuBX"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Messages } from './catalogues';
|
|
2
|
+
import type { Manifest } from './manifest';
|
|
3
|
+
/** Where the template types go, under the project: `.maizzle/*.d.ts` is in the starter's `tsconfig.json`. */
|
|
4
|
+
export declare const TYPES_FILE = ".maizzle/nxgt-mail-i18n.d.ts";
|
|
5
|
+
/**
|
|
6
|
+
* The declaration file that types `t` in the templates: each key of the
|
|
7
|
+
* `reference` catalogue, with its arguments, so an editor completes a key and
|
|
8
|
+
* flags an unknown one or a missing argument. Keys and names are checked
|
|
9
|
+
* before this runs, so they are safe to write quoted.
|
|
10
|
+
*/
|
|
11
|
+
export declare function templateTypes(reference: Messages, fallbackLocale: string): string;
|
|
12
|
+
/** Where the renderer's types go, under the project, by default. */
|
|
13
|
+
export declare const RENDERER_TYPES_FILE = "generated/mail.ts";
|
|
14
|
+
/**
|
|
15
|
+
* The module that types the renderer: `MailEmails`, each e-mail of the build
|
|
16
|
+
* with the variables it takes at send time, for
|
|
17
|
+
* `createMailRenderer<MailEmails>(…)`. A URL variable takes a string; any
|
|
18
|
+
* other a string or a number, as `render` writes it. `@nxgt/mail`'s
|
|
19
|
+
* `RenderArguments` reads `Readonly<Record<string, never>>` as an e-mail
|
|
20
|
+
* without variables, and its type specs copy this output: change them
|
|
21
|
+
* together.
|
|
22
|
+
*/
|
|
23
|
+
export declare function rendererTypes(manifest: Manifest): string;
|
|
24
|
+
/**
|
|
25
|
+
* Writes the renderer's types to `file`, shown as `path` — unless `file` holds
|
|
26
|
+
* something the plugin did not write: that **throws**, and the file is kept.
|
|
27
|
+
*/
|
|
28
|
+
export declare function writeRendererTypes(file: string, path: string, source: string): void;
|
|
29
|
+
/** Writes `source` to `file` only when it changed, so an editor or a watcher sees a change only where there is one. */
|
|
30
|
+
export declare function writeIfChanged(file: string, source: string): void;
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,6GAA6G;AAC7G,eAAO,MAAM,UAAU,iCAAiC,CAAC;AASzD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,SAAS,EAAE,QAAQ,EACnB,cAAc,EAAE,MAAM,GACpB,MAAM,CAqBR;AAKD,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,sBAAsB,CAAC;AAEvD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAyBxD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACZ,IAAI,CAUN;AAED,uHAAuH;AACvH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAIjE"}
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MessageArgs } from './translator';
|
|
2
|
+
/**
|
|
3
|
+
* Every key a template may pass to `t`, with its arguments. Empty here; the
|
|
4
|
+
* plugin fills it in `.maizzle/nxgt-mail-i18n.d.ts` from the project's
|
|
5
|
+
* catalogues each time the config loads, so an editor completes a key and
|
|
6
|
+
* flags an unknown one. With the file but no key, `t` takes any string.
|
|
7
|
+
*/
|
|
8
|
+
export interface TemplateMessages {
|
|
9
|
+
}
|
|
10
|
+
type Declared = keyof TemplateMessages;
|
|
11
|
+
/** A key of the catalogues once the types are generated; any string while none is. */
|
|
12
|
+
export type TemplateKey = [Declared] extends [never] ? string : Declared;
|
|
13
|
+
type Names<K> = K extends Declared ? keyof TemplateMessages[K] : never;
|
|
14
|
+
/** The keys of `K` whose argument names are not all of `All`'s. */
|
|
15
|
+
type Uneven<K, All = K> = K extends Declared ? [Names<All>] extends [keyof TemplateMessages[K]] ? never : K : never;
|
|
16
|
+
type Both<U> = (U extends unknown ? (u: U) => void : never) extends (i: infer I) => void ? I : never;
|
|
17
|
+
/** Whether `K` is every declared key, when there are several. */
|
|
18
|
+
type Every<K> = [Declared] extends [K] ? [Declared] extends [Both<Declared>] ? false : true : false;
|
|
19
|
+
/**
|
|
20
|
+
* The arguments of `key`: none, its declared ones, or any while no key is
|
|
21
|
+
* declared. A key that may be one of several messages (`ok ? 'a' : 'b'`)
|
|
22
|
+
* takes arguments every one of them accepts; the build refuses an argument a
|
|
23
|
+
* message does not use, so they must all use the same names. A key that may
|
|
24
|
+
* be any message takes any arguments: TypeScript reads an unknown key as
|
|
25
|
+
* every key, and the key is then what it reports.
|
|
26
|
+
*/
|
|
27
|
+
export type TemplateArgs<K> = [K] extends [Declared] ? Every<K> extends true ? [args?: MessageArgs] : [Uneven<K>] extends [never] ? [Names<K>] extends [never] ? [args?: Readonly<Record<string, never>>] : [args: Readonly<Both<TemplateMessages[K]>>] : [args: never] : [args?: MessageArgs];
|
|
28
|
+
/**
|
|
29
|
+
* What a template gets from the i18n plugin, typed for Vue's template
|
|
30
|
+
* checker: `{{ t('verifyEmail.title') }}`, `:lang="locale"`,
|
|
31
|
+
* `:href="placeholder('link')"`.
|
|
32
|
+
*/
|
|
33
|
+
declare module 'vue' {
|
|
34
|
+
interface ComponentCustomProperties {
|
|
35
|
+
/** The message `key` in the template's locale. A key or an argument that cannot be right fails the build. */
|
|
36
|
+
t<K extends TemplateKey>(key: K, ...args: TemplateArgs<K>): string;
|
|
37
|
+
/** The locale this build of the template is in: `'fr'`. */
|
|
38
|
+
readonly locale: string;
|
|
39
|
+
/** `{{ name }}` in the built file, filled at send time. `name` is camelCase. */
|
|
40
|
+
placeholder(name: string): string;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=vue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.d.ts","sourceRoot":"","sources":["../src/vue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB;CAAG;AAEpC,KAAK,QAAQ,GAAG,MAAM,gBAAgB,CAAC;AAEvC,sFAAsF;AACtF,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEzE,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,GAAG,MAAM,gBAAgB,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAEvE,mEAAmE;AACnE,KAAK,MAAM,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,QAAQ,GACzC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAC/C,KAAK,GACL,CAAC,GACF,KAAK,CAAC;AAET,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,GAAG,KAAK,CAAC,SAAS,CACnE,CAAC,EAAE,MAAM,CAAC,KACN,IAAI,GACN,CAAC,GACD,KAAK,CAAC;AAET,iEAAiE;AACjE,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,GACnC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAClC,KAAK,GACL,IAAI,GACL,KAAK,CAAC;AAET;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,GACjD,KAAK,CAAC,CAAC,CAAC,SAAS,IAAI,GACpB,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,GACpB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC1B,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACzB,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GACxC,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC5C,CAAC,IAAI,EAAE,KAAK,CAAC,GACf,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;AAExB;;;;GAIG;AACH,OAAO,QAAQ,KAAK,CAAC;IACpB,UAAU,yBAAyB;QAClC,6GAA6G;QAC7G,CAAC,CAAC,CAAC,SAAS,WAAW,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;QACnE,2DAA2D;QAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,gFAAgF;QAChF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;KAClC;CACD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each locale's output goes: `nested` writes `en/verify-email.html`,
|
|
3
|
+
* `flat` writes `verify-email.en.html`.
|
|
4
|
+
*/
|
|
5
|
+
export type Layout = 'nested' | 'flat';
|
|
6
|
+
/** One e-mail in one locale: `{ email: 'auth/reset-password', locale: 'fr' }`. */
|
|
7
|
+
export interface Entry {
|
|
8
|
+
readonly email: string;
|
|
9
|
+
readonly locale: string;
|
|
10
|
+
}
|
|
11
|
+
/** The path of `entry`'s file under a folder laid out as `layout`, without an extension. */
|
|
12
|
+
export declare function entryPath(entry: Entry, layout: Layout): string;
|
|
13
|
+
/**
|
|
14
|
+
* Reads back what {@link entryPath} wrote — `path` relative to its folder,
|
|
15
|
+
* slash-separated, without an extension — or `null` for a path that is not
|
|
16
|
+
* one e-mail in one of `locales`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseEntry(path: string, layout: Layout, locales: readonly string[]): Entry | null;
|
|
19
|
+
/**
|
|
20
|
+
* A folder of templates. `label` names it in an error: `emails`, or
|
|
21
|
+
* `templates[0]` for a package's. `only`, when given, keeps those e-mails of
|
|
22
|
+
* the folder and no other. A `packaged` folder — a package's — must hold
|
|
23
|
+
* templates, and shares no name with another package's.
|
|
24
|
+
*/
|
|
25
|
+
export interface TemplateFolder {
|
|
26
|
+
readonly dir: string;
|
|
27
|
+
readonly label: string;
|
|
28
|
+
readonly only?: readonly string[];
|
|
29
|
+
readonly packaged?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Writes one wrapper per template of `folders` and locale under
|
|
33
|
+
* `wrappersDir` — the project's folder first, so its template replaces a
|
|
34
|
+
* package's of the same name — each only when its text changed, and removes
|
|
35
|
+
* the wrappers of templates that are gone, so a running dev server sees a
|
|
36
|
+
* change only where there is one. Answers the e-mails, sorted.
|
|
37
|
+
*/
|
|
38
|
+
export declare function writeWrappers(options: {
|
|
39
|
+
readonly folders: readonly TemplateFolder[];
|
|
40
|
+
readonly wrappersDir: string;
|
|
41
|
+
readonly locales: readonly string[];
|
|
42
|
+
readonly layout: Layout;
|
|
43
|
+
}): string[];
|
|
44
|
+
/** The part of Vite's dev server the watcher uses. */
|
|
45
|
+
interface WatchedServer {
|
|
46
|
+
readonly watcher: {
|
|
47
|
+
add(path: string): void;
|
|
48
|
+
on(event: 'add' | 'unlink', listener: (file: string) => void): void;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A Vite plugin for Maizzle's renderer: under `maizzle serve`, a template
|
|
53
|
+
* added to or removed from `emailsDir` gets its wrappers at once, so the
|
|
54
|
+
* preview lists it without a restart. A template the build would refuse is
|
|
55
|
+
* reported, never thrown: an error in a watcher would stop the server.
|
|
56
|
+
*/
|
|
57
|
+
export declare function watchTemplates(emailsDir: string, regenerate: () => void): {
|
|
58
|
+
name: string;
|
|
59
|
+
configureServer(server: WatchedServer): void;
|
|
60
|
+
};
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=wrappers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrappers.d.ts","sourceRoot":"","sources":["../src/wrappers.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvC,kFAAkF;AAClF,MAAM,WAAW,KAAK;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAyBD,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACzB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,SAAS,MAAM,EAAE,GACxB,KAAK,GAAG,IAAI,CAWd;AAsBD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC5B;AAmDD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,EAAE,CAuBX;AAED,sDAAsD;AACtD,UAAU,aAAa;IACtB,QAAQ,CAAC,OAAO,EAAE;QACjB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;KACpE,CAAC;CACF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,IAAI;;4BAG9C,aAAa;EActC"}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @nxgt/mail-i18n — 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. The words they use (catalogue,
|
|
5
|
+
message, argument, placeholder, subject, manifest, wrapper) are defined once,
|
|
6
|
+
in the
|
|
7
|
+
[vocabulary](https://github.com/softistx/nxgt-mail/blob/develop/docs/vocabulary.md).
|
|
8
|
+
|
|
9
|
+
| Page | Read it when |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| [Catalogues](guide/catalogues.md) | You are writing `locales/<locale>.json`: nesting and `camelCase`, the kinds of argument, what each locale is checked for against the fallback locale, the subject key of each e-mail, catalogues a package ships (the `catalogues` option), and every build failure a catalogue causes |
|
|
12
|
+
| [Templates](guide/templates.md) | You are writing `emails/*.vue`: `t`, `locale` and `placeholder`, where a placeholder can go, templates a package ships (the `templates` option), what fails the build, the files the build writes, and `maizzle serve` and its watcher |
|
|
13
|
+
| [The manifest](guide/manifest.md) | You are reading `dist/mail-manifest.json`, the file the sending code uses: each field, how it is computed, and the flat layout; and `generated/mail.ts`, the `MailEmails` type that types `createMailRenderer`, with the `rendererTypes` option |
|
|
14
|
+
| [Editor and type checking](guide/editor.md) | You want the editor to complete `t('…')` and flag an unknown key or a wrong argument, and `vue-tsc` to check your templates in CI: the tsconfig, `maizzle prepare`, the generated `.maizzle/nxgt-mail-i18n.d.ts`, what each kind of argument accepts, and `TemplateMessages`, `TemplateKey`, `TemplateArgs` |
|
|
15
|
+
| [Translating outside templates](guide/translator.md) | You need a message in your application's code (a notification, a text message, a test) with `createTranslator`, and want to know how it differs from `@nxgt/i18n` |
|
|
16
|
+
| [Troubleshooting](troubleshooting.md) | You have an error message and want its cause and its fix |
|
|
17
|
+
| [Roadmap](roadmap.md) | You want to know what is coming, what shipped, and what is deliberately not planned |
|