@verbaly/nuxt 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aron Soto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,88 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/AronSoto/verbaly/develop/assets/logo.png" alt="Verbaly" width="300" />
3
+ </p>
4
+
5
+ <p align="center"><em>Nuxt integration for Verbaly β€” zero-config module with per-request locale negotiation and flash-free hydration.</em></p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@verbaly/nuxt"><img src="https://img.shields.io/npm/v/@verbaly/nuxt?logo=npm&color=cb3837" alt="npm version" /></a>
9
+ <a href="https://github.com/AronSoto/verbaly/blob/develop/LICENSE"><img src="https://img.shields.io/npm/l/@verbaly/nuxt?color=blue" alt="MIT" /></a>
10
+ </p>
11
+
12
+ ---
13
+
14
+ Server-rendered pages arrive already translated in the visitor's language β€” cookie first, then `Accept-Language`, then your fallback β€” and the client hydrates with the **same locale and the same catalog**, so there's no flash of untranslated text and no hydration mismatch. Each request gets its **own instance**: no locale leaking between concurrent users.
15
+
16
+ One line in `nuxt.config` wires everything: the Vite plugin (live extraction + the `virtual:verbaly` module), the per-request negotiation and `<html lang>`.
17
+
18
+ ## πŸš€ Install
19
+
20
+ ```bash
21
+ pnpm add verbaly @verbaly/nuxt @verbaly/vue
22
+ ```
23
+
24
+ ## ⚑ Wire it up
25
+
26
+ **1. The module** β€” this is the whole setup:
27
+
28
+ ```ts
29
+ // nuxt.config.ts
30
+ export default defineNuxtConfig({
31
+ modules: ['@verbaly/nuxt'],
32
+ });
33
+ ```
34
+
35
+ Locales live in your `verbaly.config` (created by `npx verbaly init`), or inline:
36
+
37
+ ```ts
38
+ modules: [['@verbaly/nuxt', { locales: ['en', 'es', 'pt'] }]],
39
+ ```
40
+
41
+ **2. Write text** β€” components use the Vue bindings as usual:
42
+
43
+ ```vue
44
+ <script setup>
45
+ import { useT } from '@verbaly/vue';
46
+ const t = useT();
47
+ </script>
48
+
49
+ <template>
50
+ <h1>{{ t('x7Ka9q2f') }}</h1>
51
+ </template>
52
+ ```
53
+
54
+ **3. Switching languages** (client) β€” persists the cookie the server reads:
55
+
56
+ ```vue
57
+ <script setup>
58
+ import { switchLocale } from 'verbaly';
59
+ import { useVerbaly } from '@verbaly/vue';
60
+ const verbaly = useVerbaly();
61
+ </script>
62
+
63
+ <template>
64
+ <button @click="switchLocale(verbaly, 'es')">EspaΓ±ol</button>
65
+ </template>
66
+ ```
67
+
68
+ That's it. The module negotiates the locale per request (cookie β†’ `Accept-Language` β†’ fallback), awaits the catalog **before** render, installs the Vue plugin, and keeps `<html lang>` in sync.
69
+
70
+ ## πŸ“– Options
71
+
72
+ Via the `verbaly` key in `nuxt.config` or inline module options β€” every [`@verbaly/vite`](https://www.npmjs.com/package/@verbaly/vite) option passes through, plus:
73
+
74
+ | Option | What it does |
75
+ | --- | --- |
76
+ | `cookie` | Cookie read/written for the user's choice (default `verbaly-locale`); `false` = `Accept-Language` only. |
77
+ | `fallback` | Locale when nothing matches (defaults to the source locale). |
78
+
79
+ - No dependency on `nuxt` or `@nuxt/kit` β€” the module is typed structurally; the only moving parts are core primitives (`resolveRequestLocale`) and the generated `createRequestInstance`.
80
+ - For fully static sites (`nuxi generate`), consider [`verbaly render`](https://www.npmjs.com/package/@verbaly/compiler) for pre-translated output per locale.
81
+
82
+ ## πŸ“š Docs
83
+
84
+ Full guide: [verbaly-web.vercel.app/docs](https://verbaly-web.vercel.app/docs)
85
+
86
+ ## License
87
+
88
+ [MIT](https://github.com/AronSoto/verbaly/blob/develop/LICENSE) Β© Aron Soto
@@ -0,0 +1,35 @@
1
+ import { ViteVerbalyOptions, ViteVerbalyOptions as ViteVerbalyOptions$1 } from "@verbaly/vite";
2
+ //#region src/module.d.ts
3
+ interface VerbalyNuxtOptions extends ViteVerbalyOptions$1 {
4
+ /** cookie read/written for the user's choice; `false` = Accept-Language only */
5
+ cookie?: string | false;
6
+ /** default when nothing matches (defaults to the source locale) */
7
+ fallback?: string;
8
+ }
9
+ interface ViteConfigLike {
10
+ plugins?: unknown[];
11
+ }
12
+ interface NuxtLike {
13
+ options: {
14
+ rootDir: string;
15
+ plugins: unknown[];
16
+ build: {
17
+ transpile: unknown[];
18
+ };
19
+ runtimeConfig: {
20
+ public: Record<string, unknown>;
21
+ };
22
+ verbaly?: VerbalyNuxtOptions;
23
+ };
24
+ hook(name: 'vite:extendConfig', fn: (config: ViteConfigLike) => void): void;
25
+ }
26
+ declare function verbalyModule(inlineOptions: VerbalyNuxtOptions | undefined, nuxt: NuxtLike): void;
27
+ declare namespace verbalyModule {
28
+ var meta: {
29
+ name: string;
30
+ configKey: string;
31
+ };
32
+ }
33
+ //#endregion
34
+ export { NuxtLike, VerbalyNuxtOptions, type ViteVerbalyOptions, verbalyModule as default };
35
+ //# sourceMappingURL=module.d.ts.map
package/dist/module.js ADDED
@@ -0,0 +1,31 @@
1
+ import verbalyVite from "@verbaly/vite";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ //#region src/module.ts
5
+ const runtimeDir = join(dirname(fileURLToPath(import.meta.url)), "runtime");
6
+ function verbalyModule(inlineOptions, nuxt) {
7
+ const { cookie, fallback, ...vite } = {
8
+ ...nuxt.options.verbaly,
9
+ ...inlineOptions
10
+ };
11
+ nuxt.options.runtimeConfig.public.verbaly = {
12
+ ...cookie !== void 0 && { cookie },
13
+ ...fallback !== void 0 && { fallback }
14
+ };
15
+ nuxt.hook("vite:extendConfig", (config) => {
16
+ (config.plugins ??= []).push(verbalyVite({
17
+ root: nuxt.options.rootDir,
18
+ ...vite
19
+ }));
20
+ });
21
+ nuxt.options.build.transpile.push(runtimeDir);
22
+ nuxt.options.plugins.unshift(join(runtimeDir, "plugin.js"));
23
+ }
24
+ verbalyModule.meta = {
25
+ name: "@verbaly/nuxt",
26
+ configKey: "verbaly"
27
+ };
28
+ //#endregion
29
+ export { verbalyModule as default };
30
+
31
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","names":[],"sources":["../src/module.ts"],"sourcesContent":["import verbalyVite, { type ViteVerbalyOptions } from '@verbaly/vite';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type { ViteVerbalyOptions } from '@verbaly/vite';\n\nexport interface VerbalyNuxtOptions extends ViteVerbalyOptions {\n /** cookie read/written for the user's choice; `false` = Accept-Language only */\n cookie?: string | false;\n /** default when nothing matches (defaults to the source locale) */\n fallback?: string;\n}\n\n// structural subset of Nuxt β€” no runtime or type dependency on nuxt/@nuxt/kit\ninterface ViteConfigLike {\n plugins?: unknown[];\n}\nexport interface NuxtLike {\n options: {\n rootDir: string;\n plugins: unknown[];\n build: { transpile: unknown[] };\n runtimeConfig: { public: Record<string, unknown> };\n verbaly?: VerbalyNuxtOptions;\n };\n hook(name: 'vite:extendConfig', fn: (config: ViteConfigLike) => void): void;\n}\n\nconst runtimeDir = join(dirname(fileURLToPath(import.meta.url)), 'runtime');\n\n// plain-function Nuxt module β€” configKey `verbaly`, inline options win\nfunction verbalyModule(inlineOptions: VerbalyNuxtOptions | undefined, nuxt: NuxtLike): void {\n const { cookie, fallback, ...vite } = { ...nuxt.options.verbaly, ...inlineOptions };\n\n // negotiation options ride runtimeConfig to the runtime plugin\n nuxt.options.runtimeConfig.public.verbaly = {\n ...(cookie !== undefined && { cookie }),\n ...(fallback !== undefined && { fallback }),\n };\n\n // fresh plugin instance per Vite build β€” client and server builds never share state.\n // root pinned to the project dir: Nuxt's Vite root is srcDir (app/), where no verbaly.config lives\n nuxt.hook('vite:extendConfig', (config) => {\n (config.plugins ??= []).push(verbalyVite({ root: nuxt.options.rootDir, ...vite }));\n });\n\n nuxt.options.build.transpile.push(runtimeDir);\n // prepend β€” the instance must exist before app plugins and components run\n nuxt.options.plugins.unshift(join(runtimeDir, 'plugin.js'));\n}\n\nverbalyModule.meta = { name: '@verbaly/nuxt', configKey: 'verbaly' };\n\nexport default verbalyModule;\n"],"mappings":";;;;AA4BA,MAAM,aAAa,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC,GAAG,SAAS;AAG1E,SAAS,cAAc,eAA+C,MAAsB;CAC1F,MAAM,EAAE,QAAQ,UAAU,GAAG,SAAS;EAAE,GAAG,KAAK,QAAQ;EAAS,GAAG;CAAc;CAGlF,KAAK,QAAQ,cAAc,OAAO,UAAU;EAC1C,GAAI,WAAW,KAAA,KAAa,EAAE,OAAO;EACrC,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;CAC3C;CAIA,KAAK,KAAK,sBAAsB,WAAW;EACzC,CAAC,OAAO,YAAY,CAAC,EAAA,CAAG,KAAK,YAAY;GAAE,MAAM,KAAK,QAAQ;GAAS,GAAG;EAAK,CAAC,CAAC;CACnF,CAAC;CAED,KAAK,QAAQ,MAAM,UAAU,KAAK,UAAU;CAE5C,KAAK,QAAQ,QAAQ,QAAQ,KAAK,YAAY,WAAW,CAAC;AAC5D;AAEA,cAAc,OAAO;CAAE,MAAM;CAAiB,WAAW;AAAU"}
@@ -0,0 +1,51 @@
1
+ import { defineNuxtPlugin, useHead, useRuntimeConfig, useState } from "#imports";
2
+ import { verbalyPlugin } from "@verbaly/vue";
3
+ import { LOCALE_STORAGE_KEY, resolveRequestLocale } from "verbaly";
4
+ import { createRequestInstance, locales, sourceLocale } from "virtual:verbaly";
5
+ import { shallowRef } from "vue";
6
+ //#region src/runtime/plugin.ts
7
+ function readCookie(header, name) {
8
+ if (!header) return void 0;
9
+ for (const part of header.split(";")) {
10
+ const eq = part.indexOf("=");
11
+ if (eq === -1) continue;
12
+ if (part.slice(0, eq).trim() !== name) continue;
13
+ const value = part.slice(eq + 1).trim();
14
+ try {
15
+ return decodeURIComponent(value);
16
+ } catch {
17
+ return value;
18
+ }
19
+ }
20
+ }
21
+ var plugin_default = defineNuxtPlugin(async (nuxtApp) => {
22
+ if (!Array.isArray(locales) || locales.length === 0) throw new Error("[verbaly] no `locales` in 'virtual:verbaly' β€” declare them in verbaly.config or in the module options (`verbaly: { locales: [...] }` in nuxt.config)");
23
+ const config = useRuntimeConfig().public.verbaly ?? {};
24
+ const cookieName = config.cookie === false ? false : config.cookie || LOCALE_STORAGE_KEY;
25
+ const fallback = config.fallback ?? sourceLocale;
26
+ const instance = await createRequestInstance(useState("verbaly:locale", () => {
27
+ const headers = nuxtApp.ssrContext?.event?.headers;
28
+ if (headers) return resolveRequestLocale({
29
+ supported: locales,
30
+ cookie: cookieName ? readCookie(headers.get("cookie"), cookieName) : void 0,
31
+ header: headers.get("accept-language"),
32
+ fallback
33
+ });
34
+ return resolveRequestLocale({
35
+ supported: locales,
36
+ cookie: cookieName && typeof document !== "undefined" ? readCookie(document.cookie, cookieName) : void 0,
37
+ header: typeof navigator !== "undefined" ? navigator.languages?.join(",") ?? navigator.language : void 0,
38
+ fallback
39
+ });
40
+ }).value);
41
+ nuxtApp.vueApp.use(verbalyPlugin(instance));
42
+ const lang = shallowRef(instance.locale);
43
+ instance.subscribe(() => {
44
+ lang.value = instance.locale;
45
+ });
46
+ useHead({ htmlAttrs: { lang } });
47
+ });
48
+ //#endregion
49
+ export { plugin_default as default };
50
+
51
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../src/runtime/plugin.ts"],"sourcesContent":["import { defineNuxtPlugin, useHead, useRuntimeConfig, useState } from '#imports';\nimport { verbalyPlugin, type VerbalyPlugin } from '@verbaly/vue';\nimport { LOCALE_STORAGE_KEY, resolveRequestLocale } from 'verbaly';\nimport { createRequestInstance, locales, sourceLocale } from 'virtual:verbaly';\nimport { shallowRef } from 'vue';\n\ninterface VerbalyRuntimeConfig {\n cookie?: string | false;\n fallback?: string;\n}\n\n// structural subset of NuxtApp β€” the real one arrives at runtime\ninterface NuxtAppLike {\n vueApp: { use(plugin: VerbalyPlugin): unknown };\n ssrContext?: { event?: { headers?: { get(name: string): string | null } } };\n}\n\n// one cookie value out of a Cookie header / document.cookie\nfunction readCookie(header: string | null | undefined, name: string): string | undefined {\n if (!header) return undefined;\n for (const part of header.split(';')) {\n const eq = part.indexOf('=');\n if (eq === -1) continue;\n if (part.slice(0, eq).trim() !== name) continue;\n const value = part.slice(eq + 1).trim();\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n }\n return undefined;\n}\n\nexport default defineNuxtPlugin(async (nuxtApp: NuxtAppLike) => {\n if (!Array.isArray(locales) || locales.length === 0) {\n throw new Error(\n \"[verbaly] no `locales` in 'virtual:verbaly' β€” declare them in verbaly.config \" +\n 'or in the module options (`verbaly: { locales: [...] }` in nuxt.config)',\n );\n }\n const config = (useRuntimeConfig().public.verbaly ?? {}) as VerbalyRuntimeConfig;\n const cookieName = config.cookie === false ? false : config.cookie || LOCALE_STORAGE_KEY;\n const fallback = config.fallback ?? sourceLocale;\n\n // negotiated on the server, hydrated from the payload β€” the client renders the same locale\n const locale = useState<string>('verbaly:locale', () => {\n const headers = nuxtApp.ssrContext?.event?.headers;\n if (headers) {\n return resolveRequestLocale({\n supported: locales,\n cookie: cookieName ? readCookie(headers.get('cookie'), cookieName) : undefined,\n header: headers.get('accept-language'),\n fallback,\n });\n }\n // client-only app (ssr: false): cookie β†’ navigator.languages β†’ fallback\n return resolveRequestLocale({\n supported: locales,\n cookie:\n cookieName && typeof document !== 'undefined'\n ? readCookie(document.cookie, cookieName)\n : undefined,\n header:\n typeof navigator !== 'undefined'\n ? (navigator.languages?.join(',') ?? navigator.language)\n : undefined,\n fallback,\n });\n });\n\n // fresh instance per request, catalog awaited BEFORE render β€” the no-FOUC contract\n const instance = await createRequestInstance(locale.value);\n nuxtApp.vueApp.use(verbalyPlugin(instance));\n\n // <html lang> follows the live locale\n const lang = shallowRef(instance.locale);\n instance.subscribe(() => {\n lang.value = instance.locale;\n });\n useHead({ htmlAttrs: { lang } });\n});\n"],"mappings":";;;;;;AAkBA,SAAS,WAAW,QAAmC,MAAkC;CACvF,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,KAAK,MAAM,QAAQ,OAAO,MAAM,GAAG,GAAG;EACpC,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,OAAO,IAAI;EACf,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,MAAM,MAAM;EACvC,MAAM,QAAQ,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,KAAK;EACtC,IAAI;GACF,OAAO,mBAAmB,KAAK;EACjC,QAAQ;GACN,OAAO;EACT;CACF;AAEF;AAEA,IAAA,iBAAe,iBAAiB,OAAO,YAAyB;CAC9D,IAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,GAChD,MAAM,IAAI,MACR,sJAEF;CAEF,MAAM,SAAU,iBAAiB,CAAC,CAAC,OAAO,WAAW,CAAC;CACtD,MAAM,aAAa,OAAO,WAAW,QAAQ,QAAQ,OAAO,UAAU;CACtE,MAAM,WAAW,OAAO,YAAY;CA6BpC,MAAM,WAAW,MAAM,sBA1BR,SAAiB,wBAAwB;EACtD,MAAM,UAAU,QAAQ,YAAY,OAAO;EAC3C,IAAI,SACF,OAAO,qBAAqB;GAC1B,WAAW;GACX,QAAQ,aAAa,WAAW,QAAQ,IAAI,QAAQ,GAAG,UAAU,IAAI,KAAA;GACrE,QAAQ,QAAQ,IAAI,iBAAiB;GACrC;EACF,CAAC;EAGH,OAAO,qBAAqB;GAC1B,WAAW;GACX,QACE,cAAc,OAAO,aAAa,cAC9B,WAAW,SAAS,QAAQ,UAAU,IACtC,KAAA;GACN,QACE,OAAO,cAAc,cAChB,UAAU,WAAW,KAAK,GAAG,KAAK,UAAU,WAC7C,KAAA;GACN;EACF,CAAC;CACH,CAGkD,CAAC,CAAC,KAAK;CACzD,QAAQ,OAAO,IAAI,cAAc,QAAQ,CAAC;CAG1C,MAAM,OAAO,WAAW,SAAS,MAAM;CACvC,SAAS,gBAAgB;EACvB,KAAK,QAAQ,SAAS;CACxB,CAAC;CACD,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AACjC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@verbaly/nuxt",
3
+ "version": "0.18.0",
4
+ "description": "Nuxt integration for Verbaly β€” zero-config module with per-request locale negotiation and flash-free hydration.",
5
+ "keywords": [
6
+ "i18n",
7
+ "nuxt",
8
+ "nuxt-module",
9
+ "ssr",
10
+ "hydration",
11
+ "verbaly"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "Aron Soto",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/AronSoto/verbaly.git",
18
+ "directory": "packages/nuxt"
19
+ },
20
+ "homepage": "https://verbaly-web.vercel.app/docs/frameworks/vue",
21
+ "bugs": {
22
+ "url": "https://github.com/AronSoto/verbaly/issues"
23
+ },
24
+ "type": "module",
25
+ "types": "./dist/module.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/module.d.ts",
29
+ "import": "./dist/module.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "LICENSE"
35
+ ],
36
+ "sideEffects": false,
37
+ "engines": {
38
+ "node": ">=20"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "@verbaly/vite": "^0.18.0"
45
+ },
46
+ "peerDependencies": {
47
+ "vue": "^3.4.0",
48
+ "@verbaly/vue": "^0.18.0",
49
+ "verbaly": "^0.18.0"
50
+ },
51
+ "devDependencies": {
52
+ "@nuxt/schema": "^4.4.8",
53
+ "happy-dom": "^20.10.6",
54
+ "vue": "^3.5.39",
55
+ "verbaly": "0.18.0",
56
+ "@verbaly/vue": "0.18.0"
57
+ },
58
+ "scripts": {
59
+ "build": "tsdown",
60
+ "dev": "tsdown --watch",
61
+ "test": "vitest run --passWithNoTests",
62
+ "typecheck": "tsc --noEmit"
63
+ }
64
+ }