@verbaly/nuxt 0.23.0 → 0.25.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/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
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
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>`.
16
+ One line in `nuxt.config` wires everything: the Vite plugin (live extraction + the `virtual:verbaly` module), the per-request negotiation and `<html lang>`/`<html dir>`.
17
17
 
18
18
  ## 🚀 Install
19
19
 
@@ -65,16 +65,16 @@ const verbaly = useVerbaly();
65
65
  </template>
66
66
  ```
67
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.
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>` and `<html dir>` in sync.
69
69
 
70
70
  ## 📖 Options
71
71
 
72
72
  Via the `verbaly` key in `nuxt.config` or inline module options (fully typed in `nuxt.config.ts`: autocomplete and typo checking, since 0.21.0). Every [`@verbaly/vite`](https://www.npmjs.com/package/@verbaly/vite) option passes through, plus:
73
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). |
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
78
 
79
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
80
  - For fully static sites (`nuxi generate`), consider [`verbaly render`](https://www.npmjs.com/package/@verbaly/compiler) for pre-translated output per locale.
@@ -1,6 +1,6 @@
1
1
  import { defineNuxtPlugin, useHead, useRuntimeConfig, useState } from "#imports";
2
2
  import { verbalyPlugin } from "@verbaly/vue";
3
- import { LOCALE_STORAGE_KEY, resolveRequestLocale } from "verbaly";
3
+ import { LOCALE_STORAGE_KEY, localeDirection, resolveRequestLocale } from "verbaly";
4
4
  import { createRequestInstance, locales, sourceLocale } from "virtual:verbaly";
5
5
  import { shallowRef } from "vue";
6
6
  //#region src/runtime/plugin.ts
@@ -40,10 +40,15 @@ var plugin_default = defineNuxtPlugin(async (nuxtApp) => {
40
40
  }).value);
41
41
  nuxtApp.vueApp.use(verbalyPlugin(instance));
42
42
  const lang = shallowRef(instance.locale);
43
+ const dir = shallowRef(localeDirection(instance.locale));
43
44
  instance.subscribe(() => {
44
45
  lang.value = instance.locale;
46
+ dir.value = localeDirection(instance.locale);
45
47
  });
46
- useHead({ htmlAttrs: { lang } });
48
+ useHead({ htmlAttrs: {
49
+ lang,
50
+ dir
51
+ } });
47
52
  });
48
53
  //#endregion
49
54
  export { plugin_default as default };
@@ -1 +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,qJAEF;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"}
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, localeDirection, 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> and <html dir> follow the live locale\n const lang = shallowRef(instance.locale);\n const dir = shallowRef(localeDirection(instance.locale));\n instance.subscribe(() => {\n lang.value = instance.locale;\n dir.value = localeDirection(instance.locale);\n });\n useHead({ htmlAttrs: { lang, dir } });\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,qJAEF;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,MAAM,MAAM,WAAW,gBAAgB,SAAS,MAAM,CAAC;CACvD,SAAS,gBAAgB;EACvB,KAAK,QAAQ,SAAS;EACtB,IAAI,QAAQ,gBAAgB,SAAS,MAAM;CAC7C,CAAC;CACD,QAAQ,EAAE,WAAW;EAAE;EAAM;CAAI,EAAE,CAAC;AACtC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verbaly/nuxt",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Nuxt integration for Verbaly: zero-config module with per-request locale negotiation and flash-free hydration.",
5
5
  "keywords": [
6
6
  "i18n",
@@ -41,19 +41,19 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@verbaly/vite": "^0.23.0"
44
+ "@verbaly/vite": "^0.25.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "vue": "^3.4.0",
48
- "verbaly": "^0.23.0",
49
- "@verbaly/vue": "^0.23.0"
48
+ "@verbaly/vue": "^0.25.0",
49
+ "verbaly": "^0.25.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@nuxt/schema": "^4.4.8",
53
53
  "happy-dom": "^20.10.6",
54
- "vue": "^3.5.39",
55
- "verbaly": "0.23.0",
56
- "@verbaly/vue": "0.23.0"
54
+ "vue": "^3.5.40",
55
+ "@verbaly/vue": "0.25.0",
56
+ "verbaly": "0.25.0"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsdown",