@wervt/nuxt 0.3.10 → 0.3.12

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/dist/brand.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Locale } from "./runtime/shared/i18n.js";
1
2
  export interface BrandOptions {
2
3
  /**
3
4
  * The product name the login shows ("Sign in to …"). Defaults to the app's `app.head.title` up
@@ -30,6 +31,11 @@ export interface BrandOptions {
30
31
  * them by default. Providers the server has no keys for stay hidden either way.
31
32
  */
32
33
  signIn?: SignInMethod[];
34
+ /**
35
+ * The language of the login, the code mail and the consent page (`'de'`). Without it they
36
+ * follow the visitor's browser: German for a German preference, English otherwise.
37
+ */
38
+ lang?: Locale;
33
39
  }
34
40
  export declare const SIGN_IN_METHODS: readonly ['google', 'github', 'discord', 'twitch', 'spotify', 'passkey', 'email'];
35
41
  export type SignInMethod = (typeof SIGN_IN_METHODS)[number];
package/dist/brand.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { headIcon } from "./preview.js";
2
+ import { isLocale } from "./runtime/shared/i18n.js";
2
3
  // src/brand.ts
3
4
  const SIGN_IN_METHODS = [
4
5
  "google",
@@ -44,7 +45,8 @@ function brandManifest(option, head, app, favicon) {
44
45
  icon: pick(custom.icon, mailIcon(head) ?? headIcon(head) ?? favicon),
45
46
  color: color && HEX_COLOR.test(color) ? color : void 0,
46
47
  ...custom.securedByWervt === false && { securedByWervt: false },
47
- ...signIn && { signIn }
48
+ ...signIn && { signIn },
49
+ ...isLocale(custom.lang) && { lang: custom.lang }
48
50
  };
49
51
  }
50
52
  export { HEX_COLOR, MAIL_IMAGE, SIGN_IN_METHODS, brandManifest, signInMethods, titleName };
@@ -0,0 +1,34 @@
1
+ // src/content.ts
2
+ /**
3
+ * Nuxt Content on wervt: SQLite is a native module (or `node:sqlite`, newer than the isolate's
4
+ * Deno), so production builds keep content in the app's Postgres. `{{DATABASE_URL}}` is expanded
5
+ * at runtime (Nitro's envExpansion).
6
+ */
7
+ const CONTENT_DATABASE = {
8
+ type: "postgres",
9
+ url: "{{DATABASE_URL}}"
10
+ };
11
+ /**
12
+ * Our options for @nuxt/content, as a module dependency: applied before Content's setup, which
13
+ * picks its database adapter then, whatever the order of the modules. Setting them in our own
14
+ * setup came too late when Content came first, e.g. from a layer like Docus. An override, not a
15
+ * default: SQLite can't work in the isolate, whatever the app sets.
16
+ */
17
+ function contentDependency() {
18
+ return {
19
+ optional: true,
20
+ overrides: { database: CONTENT_DATABASE }
21
+ };
22
+ }
23
+ /**
24
+ * The bundle's manifest (wervt-bundle.json). An app with Nuxt Content needs a database even
25
+ * without Drizzle migrations, which are how the control plane otherwise knows it wants one.
26
+ */
27
+ function nuxtBundleManifest({ content }) {
28
+ return {
29
+ version: 2,
30
+ framework: "nuxt",
31
+ ...content ? { database: true } : {}
32
+ };
33
+ }
34
+ export { CONTENT_DATABASE, contentDependency, nuxtBundleManifest };
package/dist/module.js CHANGED
@@ -2,8 +2,10 @@ import { setupStudioPreview } from "./studio-preview.js";
2
2
  import { PROCESS_ENV_TYPES } from "./env-types.js";
3
3
  import { dependencyNames, ogImageDefaults, ogImageWarning } from "./og-image.js";
4
4
  import { iconDefaults, iconSources, iconWarning } from "./icons.js";
5
+ import { CONTENT_DATABASE, contentDependency, nuxtBundleManifest } from "./content.js";
5
6
  import { findRouteTools } from "./route-tools.js";
6
7
  import { localFiles, previewManifest } from "./preview.js";
8
+ import { LOCALES } from "./runtime/shared/i18n.js";
7
9
  import { MAIL_IMAGE, SIGN_IN_METHODS, brandManifest } from "./brand.js";
8
10
  import { findShapeRoutes, shapeRoutesTypes, shapesManifest } from "./shapes.js";
9
11
  import { BUNDLE_MANIFEST } from "@wervt/core";
@@ -16,10 +18,6 @@ import { dirname, extname, join, relative, resolve, sep } from "node:path";
16
18
  import evlog from "evlog/nuxt";
17
19
  import { DENO_INJECT, denoPlugins } from "@wervt/nitro/build";
18
20
  // src/module.ts
19
- const CONTENT_DATABASE = {
20
- type: "postgres",
21
- url: "{{DATABASE_URL}}"
22
- };
23
21
  var module_default = defineNuxtModule({
24
22
  meta: {
25
23
  name: "@wervt/nuxt",
@@ -35,7 +33,8 @@ var module_default = defineNuxtModule({
35
33
  "nuxt-og-image": {
36
34
  optional: true,
37
35
  defaults: ogImageDefaults()
38
- }
36
+ },
37
+ "@nuxt/content": contentDependency()
39
38
  };
40
39
  },
41
40
  async setup(options, nuxt) {
@@ -71,16 +70,10 @@ var module_default = defineNuxtModule({
71
70
  }
72
71
  }
73
72
  addServerPlugin(resolver.resolve("./runtime/server/agent-token-plugin"));
74
- const useContentDatabase = !nuxt.options.dev && hasNuxtModule("@nuxt/content", nuxt);
75
- if (useContentDatabase) {
76
- const options = nuxt.options;
77
- options.content = {
78
- ...options.content,
79
- database: CONTENT_DATABASE
80
- };
81
- }
73
+ const usesContent = () => !nuxt.options.dev && hasNuxtModule("@nuxt/content", nuxt);
82
74
  nuxt.hook("nitro:config", (config) => {
83
75
  const options = config;
76
+ if (!nuxt.options.dev) options.serveStatic = false;
84
77
  options.externals = {
85
78
  ...options.externals,
86
79
  inline: [
@@ -97,7 +90,7 @@ var module_default = defineNuxtModule({
97
90
  exclude: [exclude].flat().map((pattern) => typeof pattern === "string" ? pattern : exceptRuntime(pattern))
98
91
  }
99
92
  };
100
- if (useContentDatabase) {
93
+ if (usesContent()) {
101
94
  options.experimental = {
102
95
  ...options.experimental,
103
96
  envExpansion: true
@@ -193,10 +186,8 @@ var module_default = defineNuxtModule({
193
186
  };
194
187
  await mkdir(nitroOptions.output.serverDir, { recursive: true });
195
188
  await writeFile(join(nitroOptions.output.serverDir, "wervt-tasks.json"), `${JSON.stringify(manifest, null, 2)}\n`);
196
- await writeFile(join(dirname(nitroOptions.output.serverDir), BUNDLE_MANIFEST), `${JSON.stringify({
197
- version: 2,
198
- framework: "nuxt"
199
- }, null, 2)}\n`);
189
+ const bundle = nuxtBundleManifest({ content: usesContent() });
190
+ await writeFile(join(dirname(nitroOptions.output.serverDir), BUNDLE_MANIFEST), `${JSON.stringify(bundle, null, 2)}\n`);
200
191
  });
201
192
  });
202
193
  }
@@ -402,6 +393,7 @@ var module_default = defineNuxtModule({
402
393
  } : false)) if (!existsSync(join(publicDir, file))) logger.warn(`The login branding names \`${file}\`, which isn't in public/.`);
403
394
  if (brand?.icon && !MAIL_IMAGE.test(brand.icon)) logger.warn(`The login branding's icon \`${brand.icon}\` won't show in the sign-in mail: Gmail and Outlook need PNG or JPEG. Set \`wervt.brand.icon\` to one.`);
404
395
  if (typeof options.brand === "object" && options.brand.color && !brand?.color) logger.warn("`wervt.brand.color` must be a hex color like `#ff5a1f`; it is ignored.");
396
+ if (typeof options.brand === "object" && options.brand.lang && !brand?.lang) logger.warn(`\`wervt.brand.lang\` must be one of ${LOCALES.join(", ")}; the login follows the browser instead.`);
405
397
  if (typeof options.brand === "object" && options.brand.signIn) {
406
398
  const unknown = options.brand.signIn.filter((m) => !SIGN_IN_METHODS.includes(m));
407
399
  if (unknown.length) logger.warn(`\`wervt.brand.signIn\` ignores ${unknown.map((m) => `\`${m}\``).join(", ")}; it knows ${SIGN_IN_METHODS.join(", ")}.`);
@@ -1,3 +1,4 @@
1
+ import { Locale } from "../shared/i18n.js";
1
2
  import { InjectionKey, PropType } from "vue";
2
3
  /** The template's subject, handed to renderEmail() while rendering. */
3
4
  export declare const SUBJECT_KEY: InjectionKey<(subject: string) => void>;
@@ -84,6 +85,14 @@ export declare const WervtEmail: import("vue").DefineComponent<import("vue").Ext
84
85
  type: PropType<EmailBrand | null>;
85
86
  default: null;
86
87
  };
88
+ /**
89
+ * The language of the frame's own texts ("Sent by …") and the document. English unless set;
90
+ * the template's own texts are up to the template.
91
+ */
92
+ lang: {
93
+ type: PropType<Locale>;
94
+ default: string;
95
+ };
87
96
  }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
88
97
  [key: string]: any;
89
98
  }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
@@ -117,12 +126,21 @@ export declare const WervtEmail: import("vue").DefineComponent<import("vue").Ext
117
126
  type: PropType<EmailBrand | null>;
118
127
  default: null;
119
128
  };
129
+ /**
130
+ * The language of the frame's own texts ("Sent by …") and the document. English unless set;
131
+ * the template's own texts are up to the template.
132
+ */
133
+ lang: {
134
+ type: PropType<Locale>;
135
+ default: string;
136
+ };
120
137
  }>> & Readonly<{}>, {
121
138
  app: string;
122
139
  preview: string;
123
140
  footer: string;
124
141
  reason: string;
125
142
  brand: EmailBrand | null;
143
+ lang: "de" | "en";
126
144
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
127
145
  export declare const EmailHeading: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
128
146
  /** A short label above the heading, in the landing page's mono eyebrow style. */
@@ -1,3 +1,5 @@
1
+ import { isLocale, translator } from "../shared/i18n.js";
2
+ import { mailMessages } from "../shared/messages.js";
1
3
  import { wervtInstance } from "../shared/instance.js";
2
4
  import { defineComponent, h, inject } from "vue";
3
5
  // src/runtime/email/components.ts
@@ -102,11 +104,19 @@ const WervtEmail = defineComponent({
102
104
  brand: {
103
105
  type: Object,
104
106
  default: null
107
+ },
108
+ /**
109
+ * The language of the frame's own texts ("Sent by …") and the document. English unless set;
110
+ * the template's own texts are up to the template.
111
+ */
112
+ lang: {
113
+ type: String,
114
+ default: "en"
105
115
  }
106
116
  },
107
117
  setup(props, { slots }) {
108
118
  inject(SUBJECT_KEY, void 0)?.(props.subject);
109
- return () => h("html", { lang: "en" }, [h("head", [
119
+ return () => h("html", { lang: isLocale(props.lang) ? props.lang : "en" }, [h("head", [
110
120
  h("meta", { charset: "utf-8" }),
111
121
  h("meta", {
112
122
  name: "viewport",
@@ -304,7 +314,8 @@ function accentLine(color) {
304
314
  }
305
315
  function footer(props) {
306
316
  const { brand } = props;
307
- const sentBy = props.footer || (brand ? `Sent by ${brand.name}` : props.app === "wervt" ? "Sent by wervt" : `Sent by ${props.app} via wervt`);
317
+ const say = translator(mailMessages, isLocale(props.lang) ? props.lang : "en");
318
+ const sentBy = props.footer || (brand ? say("sentBy", { app: brand.name }) : props.app === "wervt" ? say("sentBy", { app: "wervt" }) : say("sentByVia", { app: props.app }));
308
319
  const tile = brand ? mailIcon(brand) : `${t.assets}/mark-tile.png`;
309
320
  const small = {
310
321
  fontFamily: t.font,
@@ -367,7 +378,7 @@ function footer(props) {
367
378
  href: instance.site,
368
379
  class: "wv-muted",
369
380
  style: link
370
- }, "Secured by wervt")] : [h("a", {
381
+ }, say("securedBy"))] : [h("a", {
371
382
  href: instance.site,
372
383
  class: "wv-muted",
373
384
  style: link
@@ -375,7 +386,7 @@ function footer(props) {
375
386
  href: `${instance.docs}/getting-started/introduction`,
376
387
  class: "wv-muted",
377
388
  style: link
378
- }, "Docs")] : []])
389
+ }, say("docs"))] : []])
379
390
  ])])]);
380
391
  }
381
392
  function separator() {
@@ -1,3 +1,5 @@
1
+ import { Locale } from "../shared/i18n.js";
2
+ import { PropType } from "vue";
1
3
  /**
2
4
  * An invitation to an app: who invited, where to go, and that signing in with this address is
3
5
  * all it takes. Send it with `sendMail({ to, template: InviteEmail, props: { by, app, url } })`.
@@ -33,6 +35,11 @@ export declare const InviteEmail: import("vue").DefineComponent<import("vue").Ex
33
35
  type: StringConstructor;
34
36
  default: string;
35
37
  };
38
+ /** The language of the invitation's own texts; `message`, `action` and `note` stay as given. */
39
+ lang: {
40
+ type: PropType<Locale>;
41
+ default: string;
42
+ };
36
43
  }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
37
44
  [key: string]: any;
38
45
  }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
@@ -66,8 +73,14 @@ export declare const InviteEmail: import("vue").DefineComponent<import("vue").Ex
66
73
  type: StringConstructor;
67
74
  default: string;
68
75
  };
76
+ /** The language of the invitation's own texts; `message`, `action` and `note` stay as given. */
77
+ lang: {
78
+ type: PropType<Locale>;
79
+ default: string;
80
+ };
69
81
  }>> & Readonly<{}>, {
70
82
  message: string;
71
83
  action: string;
72
84
  note: string;
85
+ lang: "de" | "en";
73
86
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,3 +1,5 @@
1
+ import { isLocale, translator } from "../shared/i18n.js";
2
+ import { mailMessages } from "../shared/messages.js";
1
3
  import { EmailButton, EmailHeading, EmailMuted, EmailText, WervtEmail } from "./components.js";
2
4
  import { defineComponent, h } from "vue";
3
5
  // src/runtime/email/invite.ts
@@ -37,23 +39,37 @@ const InviteEmail = defineComponent({
37
39
  note: {
38
40
  type: String,
39
41
  default: ""
42
+ },
43
+ /** The language of the invitation's own texts; `message`, `action` and `note` stay as given. */
44
+ lang: {
45
+ type: String,
46
+ default: "en"
40
47
  }
41
48
  },
42
49
  setup(props) {
43
- return () => h(WervtEmail, {
44
- subject: `${props.by} invited you to ${props.app}`,
45
- app: props.app,
46
- preview: props.message || `Join ${props.app}`,
47
- reason: `You received this email because ${props.by} invited this address.`
48
- }, () => [
49
- h(EmailHeading, { eyebrow: "Invitation" }, () => `You're invited to ${props.app}`),
50
- h(EmailText, () => [`${props.by} invited you to ${props.app}.`, props.message].filter(Boolean).join(" ")),
51
- h(EmailButton, {
52
- href: props.url,
53
- color: "neutral"
54
- }, () => props.action || `Open ${props.app}`),
55
- h(EmailMuted, () => props.note || "Sign in with this email address to get in.")
56
- ]);
50
+ return () => {
51
+ const lang = isLocale(props.lang) ? props.lang : "en";
52
+ const t = translator(mailMessages, lang);
53
+ const who = {
54
+ by: props.by,
55
+ app: props.app
56
+ };
57
+ return h(WervtEmail, {
58
+ subject: t("inviteSubject", who),
59
+ app: props.app,
60
+ preview: props.message || t("invitePreview", who),
61
+ reason: t("inviteReason", who),
62
+ lang
63
+ }, () => [
64
+ h(EmailHeading, { eyebrow: t("inviteEyebrow") }, () => t("inviteHeading", who)),
65
+ h(EmailText, () => [t("inviteText", who), props.message].filter(Boolean).join(" ")),
66
+ h(EmailButton, {
67
+ href: props.url,
68
+ color: "neutral"
69
+ }, () => props.action || t("inviteAction", who)),
70
+ h(EmailMuted, () => props.note || t("inviteNote"))
71
+ ]);
72
+ };
57
73
  }
58
74
  });
59
75
  export { InviteEmail };
@@ -0,0 +1,3 @@
1
+ import { Catalog, DEFAULT_LOCALE, LOCALES, Locale, Messages, Translate, catalogProblems, defineMessages, isLocale, negotiateLocale, translator } from "./shared/i18n.js";
2
+ import { mailMessages } from "./shared/messages.js";
3
+ export { Catalog, DEFAULT_LOCALE, LOCALES, Locale, Messages, Translate, catalogProblems, defineMessages, isLocale, mailMessages, negotiateLocale, translator };
@@ -0,0 +1,3 @@
1
+ import { DEFAULT_LOCALE, LOCALES, catalogProblems, defineMessages, isLocale, negotiateLocale, translator } from "./shared/i18n.js";
2
+ import { mailMessages } from "./shared/messages.js";
3
+ export { DEFAULT_LOCALE, LOCALES, catalogProblems, defineMessages, isLocale, mailMessages, negotiateLocale, translator };
@@ -0,0 +1,28 @@
1
+ export declare const LOCALES: readonly ['en', 'de'];
2
+ export type Locale = (typeof LOCALES)[number];
3
+ export declare const DEFAULT_LOCALE: Locale;
4
+ export declare const isLocale: (value: unknown) => value is Locale;
5
+ /**
6
+ * The best supported language for an `Accept-Language` header: the highest-ranked one there is a
7
+ * catalog for, English when there's none.
8
+ */
9
+ export declare function negotiateLocale(header: string | null | undefined): Locale;
10
+ /** One locale's texts, with the keys of the English catalog. */
11
+ export type Catalog<M> = { readonly [K in keyof M]: string; };
12
+ /** Every locale's catalog; English is the source of keys and placeholders. */
13
+ export type Messages<M> = {
14
+ en: M;
15
+ } & { [L in Exclude<Locale, 'en'>]: Catalog<M>; };
16
+ /** Checks the catalogs against English at compile time; returns them unchanged. */
17
+ export declare const defineMessages: <const M extends Record<string, string>>(messages: Messages<M>) => Messages<M>;
18
+ type Placeholders<S> = S extends `${string}{${infer K}}${infer Rest}` ? K | Placeholders<Rest> : never;
19
+ type Params<S> = [Placeholders<S>] extends [never] ? [] : [params: Record<Placeholders<S>, string | number>];
20
+ export type Translate<M> = <K extends keyof M & string>(key: K, ...params: Params<M[K]>) => string;
21
+ /** `t(key, params)` for one locale. */
22
+ export declare function translator<M extends Record<string, string>>(messages: Messages<M>, locale: Locale): Translate<M>;
23
+ /**
24
+ * What's wrong with the catalogs, for tests: keys English has and another locale lacks (or the
25
+ * other way round), empty texts and placeholders that differ from English.
26
+ */
27
+ export declare function catalogProblems(messages: Messages<Record<string, string>>): string[];
28
+ export {};
@@ -0,0 +1,54 @@
1
+ // src/runtime/shared/i18n.ts
2
+ const LOCALES = ["en", "de"];
3
+ const DEFAULT_LOCALE = "en";
4
+ const isLocale = (value) => typeof value === "string" && LOCALES.includes(value);
5
+ /**
6
+ * The best supported language for an `Accept-Language` header: the highest-ranked one there is a
7
+ * catalog for, English when there's none.
8
+ */
9
+ function negotiateLocale(header) {
10
+ if (!header) return "en";
11
+ return header.split(",").map((part, index) => {
12
+ const [tag = "", ...params] = part.trim().split(";");
13
+ const q = params.map((p) => p.trim()).find((p) => p.startsWith("q="));
14
+ return {
15
+ lang: tag.trim().toLowerCase().split("-")[0],
16
+ q: q ? Number(q.slice(2)) : 1,
17
+ index
18
+ };
19
+ }).filter((entry) => entry.q > 0).sort((a, b) => b.q - a.q || a.index - b.index).map((entry) => entry.lang).find(isLocale) ?? "en";
20
+ }
21
+ /** Checks the catalogs against English at compile time; returns them unchanged. */
22
+ const defineMessages = (messages) => messages;
23
+ const fill = (text, params) => params ? text.replace(/\{(\w+)\}/g, (all, key) => String(params[key] ?? all)) : text;
24
+ /** `t(key, params)` for one locale. */
25
+ function translator(messages, locale) {
26
+ const catalog = messages[locale];
27
+ return (key, ...params) => fill(catalog[key] ?? messages.en[key], params[0]);
28
+ }
29
+ const placeholders = (text) => [...text.matchAll(/\{(\w+)\}/g)].map((m) => m[1]).sort();
30
+ /**
31
+ * What's wrong with the catalogs, for tests: keys English has and another locale lacks (or the
32
+ * other way round), empty texts and placeholders that differ from English.
33
+ */
34
+ function catalogProblems(messages) {
35
+ const problems = [];
36
+ const source = messages.en;
37
+ for (const locale of LOCALES) {
38
+ const catalog = messages[locale];
39
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(catalog)])) {
40
+ const text = catalog[key];
41
+ if (!(key in source)) problems.push(`${locale}.${key}: not in en`);
42
+ else if (text === void 0) problems.push(`${locale}.${key}: missing`);
43
+ else if (!text.trim()) problems.push(`${locale}.${key}: empty`);
44
+ else {
45
+ const own = placeholders(text).join(",");
46
+ const expected = placeholders(source[key]).join(",");
47
+ if (own !== expected) problems.push(`${locale}.${key}: placeholders ${fmt(own)} differ from en ${fmt(expected)}`);
48
+ }
49
+ }
50
+ }
51
+ return problems;
52
+ }
53
+ const fmt = (list) => list.split(",").filter(Boolean).map((p) => `{${p}}`).join(" ");
54
+ export { DEFAULT_LOCALE, LOCALES, catalogProblems, defineMessages, isLocale, negotiateLocale, translator };
@@ -0,0 +1,15 @@
1
+ import { Messages } from "./i18n.js";
2
+ export declare const mailMessages: Messages<{
3
+ readonly sentBy: "Sent by {app}";
4
+ readonly sentByVia: "Sent by {app} via wervt";
5
+ readonly securedBy: "Secured by wervt";
6
+ readonly docs: "Docs";
7
+ readonly inviteSubject: "{by} invited you to {app}";
8
+ readonly invitePreview: "Join {app}";
9
+ readonly inviteReason: "You received this email because {by} invited this address.";
10
+ readonly inviteEyebrow: "Invitation";
11
+ readonly inviteHeading: "You're invited to {app}";
12
+ readonly inviteText: "{by} invited you to {app}.";
13
+ readonly inviteAction: "Open {app}";
14
+ readonly inviteNote: "Sign in with this email address to get in.";
15
+ }>;
@@ -0,0 +1,33 @@
1
+ import { defineMessages } from "./i18n.js";
2
+ // src/runtime/shared/messages.ts
3
+ const mailMessages = defineMessages({
4
+ en: {
5
+ sentBy: "Sent by {app}",
6
+ sentByVia: "Sent by {app} via wervt",
7
+ securedBy: "Secured by wervt",
8
+ docs: "Docs",
9
+ inviteSubject: "{by} invited you to {app}",
10
+ invitePreview: "Join {app}",
11
+ inviteReason: "You received this email because {by} invited this address.",
12
+ inviteEyebrow: "Invitation",
13
+ inviteHeading: "You're invited to {app}",
14
+ inviteText: "{by} invited you to {app}.",
15
+ inviteAction: "Open {app}",
16
+ inviteNote: "Sign in with this email address to get in."
17
+ },
18
+ de: {
19
+ sentBy: "Gesendet von {app}",
20
+ sentByVia: "Gesendet von {app} über wervt",
21
+ securedBy: "Gesichert durch wervt",
22
+ docs: "Doku",
23
+ inviteSubject: "{by} hat dich zu {app} eingeladen",
24
+ invitePreview: "Mach mit bei {app}",
25
+ inviteReason: "Du bekommst diese E-Mail, weil {by} diese Adresse eingeladen hat.",
26
+ inviteEyebrow: "Einladung",
27
+ inviteHeading: "Du bist zu {app} eingeladen",
28
+ inviteText: "{by} hat dich zu {app} eingeladen.",
29
+ inviteAction: "{app} öffnen",
30
+ inviteNote: "Melde dich mit dieser E-Mail-Adresse an, um reinzukommen."
31
+ }
32
+ });
33
+ export { mailMessages };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wervt/nuxt",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "description": "Nuxt module for apps hosted on wervt: database, live data, MCP and runtime setup.",
5
5
  "keywords": [
6
6
  "drizzle",
@@ -37,6 +37,10 @@
37
37
  "types": "./dist/runtime/email/index.d.ts",
38
38
  "default": "./dist/runtime/email/index.js"
39
39
  },
40
+ "./i18n": {
41
+ "types": "./dist/runtime/i18n.d.ts",
42
+ "default": "./dist/runtime/i18n.js"
43
+ },
40
44
  "./anthropic": {
41
45
  "types": "./dist/runtime/server/anthropic.d.ts",
42
46
  "default": "./dist/runtime/server/anthropic.js"
@@ -51,7 +55,7 @@
51
55
  "@valibot/to-json-schema": "^1.8.0",
52
56
  "@wervt/client": "^0.0.2",
53
57
  "@wervt/core": "^0.0.2",
54
- "@wervt/nitro": "^0.0.2",
58
+ "@wervt/nitro": "^0.0.3",
55
59
  "@wervt/platform": "^0.0.2",
56
60
  "aws4fetch": "^1.0.20",
57
61
  "evlog": "^2.34.0",