diff --git a/branding/mintlify/favicon.svg b/branding/mintlify/favicon.svg new file mode 100644 index 000000000..4b2acad3c --- /dev/null +++ b/branding/mintlify/favicon.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/branding/mintlify/logo-dark.svg b/branding/mintlify/logo-dark.svg new file mode 100644 index 000000000..c6b2cda0e --- /dev/null +++ b/branding/mintlify/logo-dark.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + Nuxt + I18n Micro + diff --git a/branding/mintlify/logo-light.svg b/branding/mintlify/logo-light.svg new file mode 100644 index 000000000..b597cb7fc --- /dev/null +++ b/branding/mintlify/logo-light.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + Nuxt + I18n Micro + diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index c00acc99a..b00652140 100755 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -72,6 +72,61 @@ export default withFolderTree( ['meta', { name: 'application-name', content: SITE.name }], ['meta', { name: 'keywords', content: 'nuxt,i18n,internationalization,nuxt-module,localization,vue,ssr,seo' }], ['link', { rel: 'dns-prefetch', href: 'https://github.com' }], + ['link', { rel: 'dns-prefetch', href: 'https://widget.mintlify.com' }], + ['script', { type: 'module', src: 'https://widget.mintlify.com/v1/embed.js' }], + [ + 'script', + { type: 'module' }, + `const isDark = () => document.documentElement.classList.contains('dark') +const theme = () => (isDark() ? 'dark' : 'light') + +const trigger = document.createElement('button') +trigger.type = 'button' +trigger.className = 'ni18n-ask-ai' +trigger.setAttribute('aria-label', 'Ask AI') +trigger.innerHTML = 'Ask AI' +document.body.appendChild(trigger) + +await window.MintlifyAssistant.init({ + id: "mint_widget_fe4a84f3-0f6c-4dc0-94d6-3941d70125c2", + appearance: { + variant: "widget", + theme: theme(), + accent: "#32ba8c", + radius: "16px", + side: "bottom", + align: "end", + logo: "${SITE.url}/favicon.svg", + zIndex: 40, + }, + labels: { + title: "Ask AI", + trigger: "Ask AI", + placeholder: "Ask about Nuxt I18n Micro…", + suggestions: "Try asking", + }, + starterQuestions: [ + "How do I install nuxt-i18n-micro?", + "Which routing strategy should I use?", + "How do page-level translations work?", + ], +}) + +// Keep our trigger mounted over the closed Shadow DOM vendor button (z-index 50 > 40). +// Mintlify does not expose a supported way to hide the built-in trigger. +trigger.addEventListener('click', () => { + void window.MintlifyAssistant.open({ source: 'custom-trigger', focus: true }) +}) + +new MutationObserver(() => { + void window.MintlifyAssistant.update({ + appearance: { theme: theme() }, + }) +}).observe(document.documentElement, { + attributes: true, + attributeFilter: ["class"], +})`, + ], ], themeConfig: { diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css index 399e96470..a2a521c6b 100644 --- a/docs/.vitepress/theme/custom.css +++ b/docs/.vitepress/theme/custom.css @@ -11,3 +11,77 @@ border: none; box-shadow: none; } + +/* Custom Ask AI trigger — covers Mintlify built-in button (closed Shadow DOM, no hide API) */ +.ni18n-ask-ai { + position: fixed; + right: 24px; + bottom: 24px; + z-index: 50; + display: inline-flex; + align-items: center; + gap: 8px; + margin: 0; + padding: 12px 18px; + border: none; + border-radius: 999px; + background: #32ba8c; + color: #fff; + font: 600 14px/1 Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; + letter-spacing: -0.01em; + cursor: pointer; + box-shadow: + 0 10px 28px rgba(26, 80, 81, 0.28), + 0 2px 8px rgba(50, 186, 140, 0.35); + transition: + transform 0.15s ease, + background-color 0.15s ease, + box-shadow 0.15s ease; +} + +.ni18n-ask-ai:hover { + background: #2aa67c; + transform: translateY(-1px); + box-shadow: + 0 14px 32px rgba(26, 80, 81, 0.32), + 0 4px 12px rgba(50, 186, 140, 0.4); +} + +.ni18n-ask-ai:active { + transform: translateY(0); +} + +.ni18n-ask-ai:focus-visible { + outline: 2px solid #1a5051; + outline-offset: 3px; +} + +.dark .ni18n-ask-ai:focus-visible { + outline-color: #5fe0ae; +} + +.ni18n-ask-ai__icon { + font-size: 15px; + line-height: 1; +} + +.dark .ni18n-ask-ai { + background: #32ba8c; + color: #0b1f1c; + box-shadow: + 0 10px 28px rgba(0, 0, 0, 0.45), + 0 0 0 1px rgba(95, 224, 174, 0.25); +} + +.dark .ni18n-ask-ai:hover { + background: #5fe0ae; +} + +@media (max-width: 640px) { + .ni18n-ask-ai { + right: 16px; + bottom: 16px; + padding: 11px 16px; + } +} + diff --git a/docs/api/module-options.md b/docs/api/module-options.md index 30ce30234..3de8a7fcf 100644 --- a/docs/api/module-options.md +++ b/docs/api/module-options.md @@ -124,6 +124,7 @@ Parts of the module you can switch off. | Option | Type | Default | Description | | --- | --- | --- | --- | | `hreflangBaseLanguage` | `boolean` | `false` | Also emit a bare-language `hreflang` derived from each locale's `iso` (e.g. `es-ES` → also `es`). The first regional locale in `locales` claims the bare tag for that language. Routing `code` is never used — only `iso \|\| code`. | +| `vueDevtools` | `boolean` | `true` | Register Vue DevTools inspector and timeline in dev (browser extension). Separate from the Nuxt DevTools custom tab. | | `localizedRouteNamePrefix` | `string` | `'localized-'` | Prefix prepended to localized route names (e.g. `'localized-index'`). Used internally to distinguish original routes from generated locale variants. | | `routeDisableMeta` | `Record` | — | Per-route meta tag disabling, extracted from `defineI18nRoute()` calls. Maps a route path to `true` (disable all meta) or an array of locale codes for which meta should be disabled. | | `missingWarn` | `boolean` | `true` | Show console warnings when a translation key is missing. | diff --git a/docs/api/packages.md b/docs/api/packages.md index ec7eadb24..e464ca084 100644 --- a/docs/api/packages.md +++ b/docs/api/packages.md @@ -19,7 +19,7 @@ or CI failing. | Package | Entry points | Exports | | --- | --- | --- | | [`@i18n-micro/astro`](/api/packages/astro) | `@i18n-micro/astro`, `@i18n-micro/astro/client`, `@i18n-micro/astro/client/preact`, `@i18n-micro/astro/client/react`, `@i18n-micro/astro/client/svelte`, `@i18n-micro/astro/client/vue` | 56 | -| [`@i18n-micro/core`](/api/packages/core) | `@i18n-micro/core`, `@i18n-micro/core/helpers` | 42 | +| [`@i18n-micro/core`](/api/packages/core) | `@i18n-micro/core`, `@i18n-micro/core/devtools`, `@i18n-micro/core/helpers` | 50 | | [`@i18n-micro/devtools-ui`](/api/packages/devtools-ui) | `@i18n-micro/devtools-ui`, `@i18n-micro/devtools-ui/bridge`, `@i18n-micro/devtools-ui/bridge/create`, `@i18n-micro/devtools-ui/vite` | 25 | | [`@i18n-micro/hmr`](/api/packages/hmr) | `@i18n-micro/hmr/cache-keys`, `@i18n-micro/hmr/generate-plugin`, `@i18n-micro/hmr/watcher` | 8 | | [`@i18n-micro/node`](/api/packages/node) | `@i18n-micro/node` | 15 | diff --git a/docs/api/packages/astro.md b/docs/api/packages/astro.md index 7ed7aee11..b422afe52 100644 --- a/docs/api/packages/astro.md +++ b/docs/api/packages/astro.md @@ -36,7 +36,7 @@ import { /* … */ } from '@i18n-micro/astro' | `Getter` | type | `(key: TranslationKey, params?: Record, defaultValue?: string) => unknown` | | `I18nClientProps` | interface | 4 members | | `i18nIntegration` | function | `(options: I18nIntegrationOptions) => AstroIntegration` | -| `I18nIntegrationOptions` | interface | 52 members | +| `I18nIntegrationOptions` | interface | 53 members | | `I18nMiddlewareOptions` | interface | 7 members | | `I18nRoutingStrategy` | interface | 10 members | | `interpolate` | function | `(template: string, params: Params) => string` | @@ -165,7 +165,7 @@ import { /* … */ } from '@i18n-micro/astro'
-I18nIntegrationOptions — 52 members +I18nIntegrationOptions — 53 members | Member | Type | | --- | --- | @@ -221,6 +221,7 @@ import { /* … */ } from '@i18n-micro/astro' | `translationDir?` | `string \| undefined` | | `translationPayloads?` | `TranslationPayloadOptions \| undefined` | | `types?` | `boolean \| undefined` | +| `vueDevtools?` | `boolean \| undefined` |
diff --git a/docs/api/packages/core.md b/docs/api/packages/core.md index 05197e637..514ec7ba0 100644 --- a/docs/api/packages/core.md +++ b/docs/api/packages/core.md @@ -6,7 +6,7 @@ outline: 'deep' # `@i18n-micro/core` -42 exports across 2 entry points. +50 exports across 3 entry points. Generated from the API snapshot that [`pnpm run api:surface`](/guide/maintenance-commands#api-surface) checks against the TypeScript sources. @@ -166,6 +166,64 @@ import { /* … */ } from '@i18n-micro/core' | --- | --- | | `translations` | `Map` | +
+## `@i18n-micro/core/devtools` + +```ts +import { /* … */ } from '@i18n-micro/core/devtools' +``` + +| Export | Kind | Signature | +| --- | --- | --- | +| `buildInspectorState` | function | `(snapshot: I18nDevtoolsStateSnapshot) => Record>` | +| `buildInspectorTree` | function | `(options: BuildInspectorTreeOptions) => I18nDevtoolsInspectorNode[]` | +| `BuildInspectorTreeOptions` | interface | 6 members | +| `countTranslationKeys` | function | `(translations: Translations \| Record \| undefined) => number` | +| `decodeInspectorPath` | function | `(encoded: string) => string[]` | +| `encodeInspectorPath` | function | `(segments: string[]) => string` | +| `flattenTranslationNode` | function | `(obj: Record, parentSegments?: string[]) => I18nDevtoolsInspectorNode[]` | +| `getByInspectorPath` | function | `(obj: Record, segments: string[]) => unknown` | +| `I18nDevtoolsInspectorNode` | interface | 4 members | +| `I18nDevtoolsStateSnapshot` | interface | 7 members | +| `parseInspectorNodeId` | function | `(nodeId: string) => { kind: "root" \| "active" \| "locale" \| "chunk" \| "key"; locale?: string; routeName?: string; path?: string; segments?: string[]; }` | + +
+BuildInspectorTreeOptions — 6 members + +| Member | Type | +| --- | --- | +| `activeLocale` | `string` | +| `activeRouteName` | `string` | +| `activeTranslations?` | `Record \| undefined` | +| `configuredLocales` | `Locale[]` | +| `nodeId` | `string` | +| `storage` | `TranslationStorage` | + +
+
+I18nDevtoolsInspectorNode — 4 members + +| Member | Type | +| --- | --- | +| `children?` | `I18nDevtoolsInspectorNode[] \| undefined` | +| `id` | `string` | +| `label` | `string` | +| `tags?` | `{ label: string; textColor: number; backgroundColor: number; tooltip?: string; }[] \| undefined` | + +
+
+I18nDevtoolsStateSnapshot — 7 members + +| Member | Type | +| --- | --- | +| `cachedChunks` | `number` | +| `defaultLocale?` | `string \| undefined` | +| `fallbackLocale?` | `string \| undefined` | +| `locale` | `string` | +| `locales` | `{ code: string; displayName?: string; disabled?: boolean; }[]` | +| `routeName` | `string` | +| `strategy?` | `string \| undefined` | +
## `@i18n-micro/core/helpers` diff --git a/docs/api/packages/preact.md b/docs/api/packages/preact.md index 535e91f1d..27306499e 100644 --- a/docs/api/packages/preact.md +++ b/docs/api/packages/preact.md @@ -43,7 +43,7 @@ import { /* … */ } from '@i18n-micro/preact' | `interpolate` | function | `(template: string, params: Params) => string` | | `Locale` | interface | 11 members | | `LocaleCode` | type | `string` | -| `ModuleOptions` | interface | 46 members | +| `ModuleOptions` | interface | 47 members | | `Params` | type | `Record` | | `PluralFunc` | type | `(key: TranslationKey, count: number, params: Params, locale: string, getter: Getter) => string \| null` | | `PreactI18n` | class | 44 members | @@ -1370,7 +1370,7 @@ import { /* … */ } from '@i18n-micro/preact' | `seo?` | `boolean \| undefined` | -ModuleOptions — 46 members, identical to [`ModuleOptions`](/api/packages/types). +ModuleOptions — 47 members, identical to [`ModuleOptions`](/api/packages/types).
PreactI18n — 44 members diff --git a/docs/api/packages/solid.md b/docs/api/packages/solid.md index 92ed32ea2..4ae436df4 100644 --- a/docs/api/packages/solid.md +++ b/docs/api/packages/solid.md @@ -38,7 +38,7 @@ import { /* … */ } from '@i18n-micro/solid' | `interpolate` | function | `(template: string, params: Params) => string` | | `Locale` | interface | 11 members | | `LocaleCode` | type | `string` | -| `ModuleOptions` | interface | 46 members | +| `ModuleOptions` | interface | 47 members | | `Params` | type | `Record` | | `PluralFunc` | type | `(key: TranslationKey, count: number, params: Params, locale: string, getter: Getter) => string \| null` | | `SolidI18n` | class | 53 members | @@ -79,7 +79,7 @@ import { /* … */ } from '@i18n-micro/solid'
Locale — 11 members, identical to [`Locale`](/api/packages/types). -ModuleOptions — 46 members, identical to [`ModuleOptions`](/api/packages/types). +ModuleOptions — 47 members, identical to [`ModuleOptions`](/api/packages/types).
SolidI18n — 53 members diff --git a/docs/api/packages/types.md b/docs/api/packages/types.md index 37cc445dc..121088133 100644 --- a/docs/api/packages/types.md +++ b/docs/api/packages/types.md @@ -32,9 +32,9 @@ import { /* … */ } from '@i18n-micro/types' | `Locale` | interface | 11 members | | `LocaleCode` | type | `string` | | `MissingHandler` | type | `(locale: string, key: TranslationKey, routeName: string, instance?: unknown, type?: string) => void` | -| `ModuleOptions` | interface | 46 members | -| `ModuleOptionsExtend` | interface | 49 members | -| `ModulePrivateOptionsExtend` | interface | 49 members | +| `ModuleOptions` | interface | 47 members | +| `ModuleOptionsExtend` | interface | 50 members | +| `ModulePrivateOptionsExtend` | interface | 50 members | | `Params` | type | `Record` | | `PluralFunc` | type | `(key: TranslationKey, count: number, params: Params, locale: string, getter: Getter) => string \| null` | | `PluralTranslations` | interface | 2 members | @@ -113,7 +113,7 @@ import { /* … */ } from '@i18n-micro/types'
-ModuleOptions — 46 members +ModuleOptions — 47 members | Member | Type | | --- | --- | @@ -163,10 +163,11 @@ import { /* … */ } from '@i18n-micro/types' | `translationDir?` | `string \| undefined` | | `translationPayloads?` | `TranslationPayloadOptions \| undefined` | | `types?` | `boolean \| undefined` | +| `vueDevtools?` | `boolean \| undefined` |
-ModuleOptionsExtend — 49 members +ModuleOptionsExtend — 50 members | Member | Type | | --- | --- | @@ -219,10 +220,11 @@ import { /* … */ } from '@i18n-micro/types' | `translationPayloadMode?` | `"premerged" \| "source" \| undefined` | | `translationPayloads?` | `TranslationPayloadOptions \| undefined` | | `types?` | `boolean \| undefined` | +| `vueDevtools?` | `boolean \| undefined` |
-ModulePrivateOptionsExtend — 49 members +ModulePrivateOptionsExtend — 50 members | Member | Type | | --- | --- | @@ -275,6 +277,7 @@ import { /* … */ } from '@i18n-micro/types' | `translationDir` | `string` | | `translationPayloads?` | `TranslationPayloadOptions \| undefined` | | `types?` | `boolean \| undefined` | +| `vueDevtools?` | `boolean \| undefined` |
diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 046b6ef7d..1d91f9f28 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -55,6 +55,7 @@ The sections below explain how they work together; the | [`fallbackLocale`](/api/module-options) | `string` | `undefined (no fallback; returns the raw key)` | Global fallback locale code. | | [`localeCookie`](/api/module-options) | `string \| null` | `null` | Cookie name for persisting the user's locale preference across sessions. | | [`debug`](/api/module-options) | `boolean` | `false` | Enable verbose debug logging for locale detection, route generation, and translation loading. | +| [`vueDevtools`](/api/module-options) | `boolean` | `true` | Register Vue DevTools inspector and timeline in dev (browser extension). | | [`globalLocaleRoutes`](/api/module-options) | `GlobalLocaleRoutes` | `{}` | Global route-level locale configuration. | | [`customRegexMatcher`](/api/module-options) | `string \| RegExp` | `undefined (uses built-in pattern based on locale codes)` | Custom regular expression (or its string source) for matching locale codes in URL segments. | | [`noPrefixRedirect`](/api/module-options) | `boolean` | `false` | For `no_prefix` strategy: enable redirect from a locale-prefixed URL (e.g. `/en/about`) to the unprefixed version (`/about`). | diff --git a/docs/guide/devtools.md b/docs/guide/devtools.md index feadce7fe..a93c724c0 100644 --- a/docs/guide/devtools.md +++ b/docs/guide/devtools.md @@ -6,6 +6,31 @@ outline: 'deep' # 🛠️ DevTools in `nuxt-i18n-micro` +## Vue DevTools (browser extension) + +In development, the module can register a **Vue DevTools** custom inspector and timeline via `@vue/devtools-api`. This is separate from the Nuxt DevTools tab below. + +| Tool | Best for | +|------|----------| +| **Vue DevTools** | Live locale, in-memory translation cache, `$t` timeline (`debug: true`), missing-key events | +| **Nuxt DevTools tab** | Editing JSON files on disk, config overview, import/export | + +The vue-devtools plugin is registered only during `nuxt dev` (not in production builds). Dev-only bridge state in the main plugin is stripped by `import.meta.dev` dead-code elimination. + +Disable the browser integration while keeping the Nuxt tab: + +```ts +export default defineNuxtConfig({ + i18n: { + vueDevtools: false, + }, +}) +``` + +Verbose `$t` timeline events require `debug: true`. + +--- + ## 📖 What Is Nuxt DevTools? DevTools is a powerful development assistant seamlessly integrated into the `nuxt-i18n-micro` module. It provides an intuitive interface for managing localization and translation tasks, making your workflow faster and more efficient. Designed for developers who value convenience, Nuxt DevTools bridges the gap between coding and runtime management. diff --git a/docs/news/index.md b/docs/news/index.md index 57fc2fdd2..d3f0b9c74 100644 --- a/docs/news/index.md +++ b/docs/news/index.md @@ -6,6 +6,19 @@ outline: 'deep' # News +## Nuxt I18n Micro v3.26.1 — Vue DevTools live inspector + +**Date**: 2026-08-05 + +**Version**: `v3.26.1` + +Adds optional **Vue DevTools** integration for live runtime debugging: custom inspector (locale, route chunks, active dictionary), timeline events (locale switch, load, missing keys), and optional `$t` tracing when `debug: true`. The existing **Nuxt DevTools** custom tab (JSON editor on disk) is unchanged. + +- New option: `i18n.vueDevtools` (default `true` in dev) +- Optional peer: `@vue/devtools-api` ^8.1.5 +- `@i18n-micro/core` exports devtools helpers from `@i18n-micro/core/devtools` (not in main entry) +- Inspector key paths use encoded segments (`encodeInspectorPath`) so literal dotted keys stay distinct from nested paths + ## Nuxt I18n Micro v3.24.0 — Node SSR from `public/`, no Rollup `raw:` **Date**: 2026-07-30 diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 000000000..c9bc0bd9d --- /dev/null +++ b/docs/package.json @@ -0,0 +1,23 @@ +{ + "name": "@i18n-micro/docs", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "VitePress documentation site for nuxt-i18n-micro.", + "scripts": { + "generate": "pnpm -C ../scripts cli docs-generate", + "dev": "pnpm run generate && vitepress dev .", + "build": "pnpm run generate && vitepress build .", + "clean-dev": "pnpm run generate && vitepress dev . --clean-cache", + "preview": "vitepress preview ." + }, + "devDependencies": { + "chart.js": "catalog:", + "mermaid": "catalog:", + "vitepress": "catalog:", + "vitepress-plugin-chartjs": "catalog:", + "vitepress-plugin-folder-tree": "catalog:", + "vitepress-plugin-llms": "catalog:", + "vitepress-plugin-mermaid": "catalog:" + } +} diff --git a/package.json b/package.json index 1ac2011c1..711c4c99c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nuxt-i18n-micro", "version": "3.26.0", - "description": "Nuxt I18n Micro is a lightweight, high-performance internationalization module for Nuxt, designed to handle multi-language support with minimal overhead, fast build times, and efficient runtime performance.", + "description": "Fast, lightweight i18n for Nuxt with strategy-based routing and minimal overhead.", "keywords": [ "i18n", "internationalization", @@ -14,7 +14,7 @@ "seo", "translation" ], - "homepage": "https://github.com/s00d/nuxt-i18n-micro", + "homepage": "https://s00d.github.io/nuxt-i18n-micro/", "bugs": { "url": "https://github.com/s00d/nuxt-i18n-micro/issues" }, @@ -27,6 +27,7 @@ "repository": "s00d/nuxt-i18n-micro", "workspaces": [ "client", + "docs", "packages/**", "test/fixtures/**/*" ], @@ -109,11 +110,11 @@ "test:dist:packages": "pnpm --filter \"@i18n-micro/core\" --filter \"@i18n-micro/utils\" --filter \"@i18n-micro/hmr\" --filter \"@i18n-micro/vue\" --filter \"@i18n-micro/path-strategy\" --filter \"@i18n-micro/route-strategy\" --filter \"@i18n-micro/devtools-ui\" --filter \"@i18n-micro/astro\" run test:dist", "typecheck": "tsc --noEmit", "typecheck:nuxt": "nuxt typecheck --no-emit", - "docs:dev": "pnpm run docs:generate && vitepress dev docs", - "docs:build": "pnpm run docs:generate && vitepress build docs", - "docs:serve": "vitepress serve docs", - "docs:clean-dev": "pnpm run docs:generate && vitepress dev docs --clean-cache", - "docs:preview": "vitepress preview docs", + "docs:dev": "pnpm --filter @i18n-micro/docs dev", + "docs:build": "pnpm --filter @i18n-micro/docs build", + "docs:serve": "pnpm --filter @i18n-micro/docs preview", + "docs:clean-dev": "pnpm --filter @i18n-micro/docs clean-dev", + "docs:preview": "pnpm --filter @i18n-micro/docs preview", "smoke:pack": "pnpm -C scripts cli smoke-pack", "smoke:verify": "pnpm -C scripts cli smoke-verify", "smoke:browser": "pnpm -C scripts cli smoke-browser", @@ -149,27 +150,27 @@ "devDependencies": { "@nuxt/devtools": "catalog:", "@nuxt/devtools-ui-kit": "catalog:", + "@vue/devtools-api": "catalog:", "@nuxt/module-builder": "catalog:", "@nuxt/schema": "catalog:", "@nuxt/test-utils": "catalog:", "@playwright/test": "catalog:", "@types/node": "catalog:", "changelogen": "catalog:", - "chart.js": "catalog:", - "mermaid": "catalog:", "oxfmt": "catalog:", "oxlint": "catalog:", "typescript": "catalog:", - "vitepress": "catalog:", - "vitepress-plugin-chartjs": "catalog:", - "vitepress-plugin-folder-tree": "catalog:", - "vitepress-plugin-llms": "catalog:", - "vitepress-plugin-mermaid": "catalog:", "vitest": "catalog:", "vue-tsc": "catalog:" }, "peerDependencies": { - "vue-router": "catalog:" + "vue-router": "catalog:", + "@vue/devtools-api": "catalog:" + }, + "peerDependenciesMeta": { + "@vue/devtools-api": { + "optional": true + } }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "catalog:", diff --git a/packages/astro/package.json b/packages/astro/package.json index d7fa59edf..5d342b422 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/astro", - "version": "1.3.10", + "version": "1.3.11", "description": "Astro integration for i18n-micro — translations, routing, and islands.", "keywords": [ "astro", diff --git a/packages/core/package.json b/packages/core/package.json index 802087548..a27a3aee7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/core", - "version": "1.3.11", + "version": "1.3.13", "description": "Core utilities for translations, formatting, and locale routing in Nuxt I18n Micro.", "keywords": [ "formatting", @@ -64,6 +64,21 @@ }, "default": "./dist/helpers.mjs" }, + "./devtools": { + "production": { + "types": "./dist/devtools.d.ts", + "default": "./dist/devtools.mjs" + }, + "import": { + "types": "./dist/devtools.d.ts", + "default": "./dist/devtools.mjs" + }, + "require": { + "types": "./dist/devtools.d.cts", + "default": "./dist/devtools.cjs" + }, + "default": "./dist/devtools.mjs" + }, "./package.json": "./package.json" }, "publishConfig": { diff --git a/packages/core/src/devtools-entry.ts b/packages/core/src/devtools-entry.ts new file mode 100644 index 000000000..9a8cb6fdf --- /dev/null +++ b/packages/core/src/devtools-entry.ts @@ -0,0 +1,13 @@ +export { + buildInspectorState, + buildInspectorTree, + countTranslationKeys, + decodeInspectorPath, + encodeInspectorPath, + flattenTranslationNode, + getByInspectorPath, + parseInspectorNodeId, + type BuildInspectorTreeOptions, + type I18nDevtoolsInspectorNode, + type I18nDevtoolsStateSnapshot, +} from './devtools' diff --git a/packages/core/src/devtools.ts b/packages/core/src/devtools.ts new file mode 100644 index 000000000..fbb43a416 --- /dev/null +++ b/packages/core/src/devtools.ts @@ -0,0 +1,271 @@ +import type { Locale, Translations } from '@i18n-micro/types' +import type { TranslationStorage } from './translation' + +export interface I18nDevtoolsInspectorNode { + id: string + label: string + children?: I18nDevtoolsInspectorNode[] + tags?: Array<{ + label: string + textColor: number + backgroundColor: number + tooltip?: string + }> +} + +export interface I18nDevtoolsStateSnapshot { + locale: string + routeName: string + strategy?: string + defaultLocale?: string + fallbackLocale?: string + cachedChunks: number + locales: Array<{ code: string; displayName?: string; disabled?: boolean }> +} + +export interface BuildInspectorTreeOptions { + nodeId: string + storage: TranslationStorage + configuredLocales: Locale[] + activeLocale: string + activeRouteName: string + activeTranslations?: Record +} + +const ACTIVE_TAG = { + label: 'Active', + textColor: 0xffffff, + backgroundColor: 0x42b883, + tooltip: 'Current locale and route context', +} + +const NODE_ROOT = 'root' +const NODE_ACTIVE = 'active' +const PREFIX_LOCALE = 'locale|' +const PREFIX_CHUNK = 'chunk|' +const PREFIX_KEY = 'key|' + +const isPlainObject = (value: unknown): value is Record => value !== null && typeof value === 'object' && !Array.isArray(value) + +function formatInspectorValue(value: unknown): string { + if (typeof value === 'string') return JSON.stringify(value) + if (value === null) return 'null' + if (value === undefined) return 'undefined' + if (typeof value === 'object') return Array.isArray(value) ? `[Array(${value.length})]` : '{…}' + return String(value) +} + +function activeChunkTag(locale: string, routeName: string, activeLocale: string, activeRouteName: string) { + return locale === activeLocale && routeName === activeRouteName ? [ACTIVE_TAG] : undefined +} + +function collectRouteNamesForLocale(storage: TranslationStorage, locale: string): string[] { + const prefix = `${locale}:` + const routes = new Set() + + for (const key of storage.translations.keys()) { + if (!key.startsWith(prefix)) continue + routes.add(key.slice(prefix.length) || 'index') + } + + return [...routes].sort() +} + +function collectLocaleCodes(storage: TranslationStorage, configuredLocales: Locale[]): string[] { + const codes = new Set(configuredLocales.map((locale) => locale.code)) + + for (const key of storage.translations.keys()) { + const locale = key.split(':')[0] + if (locale) codes.add(locale) + } + + return [...codes].sort() +} + +/** + * Encode path segments so a literal key `"a.b"` and nested path `a → b` get distinct node IDs. + */ +export function encodeInspectorPath(segments: string[]): string { + return segments.map((segment) => encodeURIComponent(segment)).join('/') +} + +export function decodeInspectorPath(encoded: string): string[] { + if (!encoded) return [] + return encoded.split('/').map((segment) => decodeURIComponent(segment)) +} + +export function getByInspectorPath(obj: Record, segments: string[]): unknown { + let current: unknown = obj + for (const segment of segments) { + if (!isPlainObject(current) || !Object.prototype.hasOwnProperty.call(current, segment)) return undefined + current = current[segment] + } + return current +} + +export function parseInspectorNodeId(nodeId: string): { + kind: 'root' | 'active' | 'locale' | 'chunk' | 'key' + locale?: string + routeName?: string + path?: string + segments?: string[] +} { + if (!nodeId || nodeId === NODE_ROOT) return { kind: 'root' } + if (nodeId === NODE_ACTIVE) return { kind: 'active' } + + if (nodeId.startsWith(PREFIX_LOCALE)) { + return { kind: 'locale', locale: nodeId.slice(PREFIX_LOCALE.length) } + } + + if (nodeId.startsWith(PREFIX_CHUNK)) { + const [locale = '', routeName = 'index'] = nodeId.slice(PREFIX_CHUNK.length).split('|') + return { kind: 'chunk', locale, routeName } + } + + if (nodeId.startsWith(PREFIX_KEY)) { + const parts = nodeId.slice(PREFIX_KEY.length).split('|') + const locale = parts[0] ?? '' + const routeName = parts[1] ?? 'index' + const path = parts.slice(2).join('|') + return { kind: 'key', locale, routeName, path, segments: decodeInspectorPath(path) } + } + + return { kind: 'root' } +} + +function withKeyPrefix(locale: string, routeName: string, node: I18nDevtoolsInspectorNode): I18nDevtoolsInspectorNode { + return { + ...node, + id: `${PREFIX_KEY}${locale}|${routeName}|${node.id.slice(PREFIX_KEY.length)}`, + } +} + +export function flattenTranslationNode(obj: Record, parentSegments: string[] = []): I18nDevtoolsInspectorNode[] { + const target = parentSegments.length === 0 ? obj : getByInspectorPath(obj, parentSegments) + if (!isPlainObject(target)) return [] + + return Object.keys(target) + .filter((key) => key !== '__proto__') + .map((key) => { + const segments = [...parentSegments, key] + const value = target[key] + const nested = isPlainObject(value) + + return { + id: `${PREFIX_KEY}${encodeInspectorPath(segments)}`, + label: nested ? key : `${key}: ${formatInspectorValue(value)}`, + children: nested ? [] : undefined, + } + }) + .sort((a, b) => a.label.localeCompare(b.label)) +} + +export function buildInspectorTree(options: BuildInspectorTreeOptions): I18nDevtoolsInspectorNode[] { + const parsed = parseInspectorNodeId(options.nodeId) + const activeLocale = options.activeLocale + const activeRoute = options.activeRouteName || 'index' + + if (parsed.kind === 'root') { + const locales = collectLocaleCodes(options.storage, options.configuredLocales) + const nodes: I18nDevtoolsInspectorNode[] = [ + { + id: NODE_ACTIVE, + label: `Active (${activeLocale}:${activeRoute})`, + tags: [ACTIVE_TAG], + }, + ] + + for (const code of locales) { + const configured = options.configuredLocales.find((locale) => locale.code === code) + const routes = collectRouteNamesForLocale(options.storage, code) + + nodes.push({ + id: `${PREFIX_LOCALE}${code}`, + label: configured?.displayName ? `${code} (${configured.displayName})` : code, + tags: code === activeLocale ? [{ ...ACTIVE_TAG, tooltip: 'Active locale' }] : undefined, + }) + + // Flat chunk nodes (not nested): Vue DevTools selection is unreliable for nested + // custom-inspector children, so route chunks sit at the root next to their locale. + for (const routeName of routes) { + nodes.push({ + id: `${PREFIX_CHUNK}${code}|${routeName}`, + label: `${code}:${routeName}`, + tags: activeChunkTag(code, routeName, activeLocale, activeRoute), + }) + } + } + + return nodes + } + + if (parsed.kind === 'active') { + return flattenTranslationNode(options.activeTranslations ?? {}).map((node) => withKeyPrefix(activeLocale, activeRoute, node)) + } + + if (parsed.kind === 'locale' && parsed.locale) { + const locale = parsed.locale + return collectRouteNamesForLocale(options.storage, locale).map((routeName) => ({ + id: `${PREFIX_CHUNK}${locale}|${routeName}`, + label: routeName, + children: [], + tags: activeChunkTag(locale, routeName, activeLocale, activeRoute), + })) + } + + if (parsed.kind === 'chunk' && parsed.locale) { + const locale = parsed.locale + const routeName = parsed.routeName || 'index' + const chunk = options.storage.translations.get(`${locale}:${routeName}`) as Record | undefined + return flattenTranslationNode(chunk ?? {}).map((node) => withKeyPrefix(locale, routeName, node)) + } + + if (parsed.kind === 'key' && parsed.locale) { + const locale = parsed.locale + const routeName = parsed.routeName || 'index' + const chunk = options.storage.translations.get(`${locale}:${routeName}`) as Record | undefined + const segments = parsed.segments ?? [] + const isActiveContext = activeLocale === locale && activeRoute === routeName + const source = isActiveContext ? (options.activeTranslations ?? chunk ?? {}) : (chunk ?? {}) + + return flattenTranslationNode(source, segments).map((node) => withKeyPrefix(locale, routeName, node)) + } + + return [] +} + +export function buildInspectorState(snapshot: I18nDevtoolsStateSnapshot): Record> { + return { + Context: [ + { key: 'locale', value: snapshot.locale }, + { key: 'routeName', value: snapshot.routeName }, + { key: 'strategy', value: snapshot.strategy ?? '—' }, + { key: 'defaultLocale', value: snapshot.defaultLocale ?? '—' }, + { key: 'fallbackLocale', value: snapshot.fallbackLocale ?? '—' }, + { key: 'cachedChunks', value: snapshot.cachedChunks }, + ], + Locales: snapshot.locales.map((locale) => ({ + key: locale.code, + value: locale.displayName ?? locale.code, + editable: false, + })), + } +} + +export function countTranslationKeys(translations: Translations | Record | undefined): number { + if (!translations || typeof translations !== 'object') return 0 + + const paths = new Set() + const walk = (obj: Record, prefix = '') => { + for (const key of Object.keys(obj)) { + if (key === '__proto__') continue + const path = prefix ? `${prefix}.${key}` : key + paths.add(path) + const value = obj[key] + if (isPlainObject(value)) walk(value, path) + } + } + + walk(translations as Record) + return paths.size +} diff --git a/packages/core/tests/devtools.test.ts b/packages/core/tests/devtools.test.ts new file mode 100644 index 000000000..9a2c0168c --- /dev/null +++ b/packages/core/tests/devtools.test.ts @@ -0,0 +1,168 @@ +import { describe, expect, test } from 'vitest' +import { + buildInspectorState, + buildInspectorTree, + countTranslationKeys, + encodeInspectorPath, + flattenTranslationNode, + getByInspectorPath, + parseInspectorNodeId, +} from '../src/devtools' +import type { TranslationStorage } from '../src/translation' + +function createStorage(entries: Record>): TranslationStorage { + return { + translations: new Map(Object.entries(entries)), + } +} + +describe('devtools helpers', () => { + test('parseInspectorNodeId handles root, locale, chunk, and key nodes', () => { + expect(parseInspectorNodeId('root')).toEqual({ kind: 'root' }) + expect(parseInspectorNodeId('active')).toEqual({ kind: 'active' }) + expect(parseInspectorNodeId('locale|en')).toEqual({ kind: 'locale', locale: 'en' }) + expect(parseInspectorNodeId('chunk|en|about')).toEqual({ kind: 'chunk', locale: 'en', routeName: 'about' }) + expect(parseInspectorNodeId('key|en|index|nav%2Fhome')).toEqual({ + kind: 'key', + locale: 'en', + routeName: 'index', + path: 'nav%2Fhome', + segments: ['nav/home'], + }) + expect(parseInspectorNodeId(`key|en|index|${encodeInspectorPath(['nav', 'home'])}`)).toEqual({ + kind: 'key', + locale: 'en', + routeName: 'index', + path: 'nav/home', + segments: ['nav', 'home'], + }) + }) + + test('buildInspectorTree lists locale chunks as flat selectable nodes', () => { + const storage = createStorage({ + 'en:index': { hello: 'Hello', nav: { home: 'Home' } }, + 'en:about': { title: 'About' }, + 'de:index': { hello: 'Hallo' }, + }) + + const root = buildInspectorTree({ + nodeId: 'root', + storage, + configuredLocales: [{ code: 'en', displayName: 'English' }, { code: 'de' }], + activeLocale: 'en', + activeRouteName: 'index', + }) + + expect(root.map((node) => node.id)).toEqual(['active', 'locale|de', 'chunk|de|index', 'locale|en', 'chunk|en|about', 'chunk|en|index']) + expect(root.find((node) => node.id === 'active')?.children).toBeUndefined() + expect(root.find((node) => node.id === 'locale|en')?.label).toBe('en (English)') + expect(root.find((node) => node.id === 'locale|en')?.children).toBeUndefined() + expect(root.find((node) => node.id === 'chunk|en|about')?.label).toBe('en:about') + expect(root.find((node) => node.id === 'chunk|en|index')?.tags?.[0]?.label).toBe('Active') + + const routes = buildInspectorTree({ + nodeId: 'locale|en', + storage, + configuredLocales: [{ code: 'en' }], + activeLocale: 'en', + activeRouteName: 'index', + }) + + expect(routes.map((node) => node.label)).toEqual(['about', 'index']) + expect(routes.find((node) => node.label === 'index')?.tags?.[0]?.label).toBe('Active') + }) + + test('flattenTranslationNode lazily exposes nested keys', () => { + const nodes = flattenTranslationNode({ + nav: { home: 'Home', about: 'About' }, + title: 'Welcome', + }) + + expect(nodes.map((node) => node.label)).toEqual(['nav', 'title: "Welcome"']) + expect(nodes.find((node) => node.label === 'nav')?.children).toEqual([]) + expect(nodes.find((node) => node.label === 'nav')?.id).toBe(`key|${encodeInspectorPath(['nav'])}`) + }) + + test('flattenTranslationNode keeps literal dotted keys distinct from nested paths', () => { + const nodes = flattenTranslationNode({ + 'a.b': 'literal', + a: { b: 'nested' }, + }) + + const ids = nodes.map((node) => node.id) + expect(ids).toContain(`key|${encodeInspectorPath(['a.b'])}`) + expect(ids).toContain(`key|${encodeInspectorPath(['a'])}`) + expect(new Set(ids).size).toBe(ids.length) + + const nested = flattenTranslationNode( + { + 'a.b': 'literal', + a: { b: 'nested' }, + }, + ['a'], + ) + expect(nested.map((node) => node.id)).toEqual([`key|${encodeInspectorPath(['a', 'b'])}`]) + expect(nested[0]?.id).not.toBe(`key|${encodeInspectorPath(['a.b'])}`) + }) + + test('buildInspectorTree exposes active merged translations including fallback-only keys', () => { + const storage = createStorage({ + 'en:index': { hello: 'Hello' }, + }) + + const nodes = buildInspectorTree({ + nodeId: 'active', + storage, + configuredLocales: [{ code: 'en' }], + activeLocale: 'en', + activeRouteName: 'index', + activeTranslations: { hello: 'Hello', onlyFallback: 'From outgoing' }, + }) + + expect(nodes.map((node) => node.label)).toEqual(['hello: "Hello"', 'onlyFallback: "From outgoing"']) + + const selected = parseInspectorNodeId(nodes.find((node) => node.label.startsWith('onlyFallback'))!.id) + expect(getByInspectorPath({ hello: 'Hello', onlyFallback: 'From outgoing' }, selected.segments ?? [])).toBe('From outgoing') + }) + + test('buildInspectorState returns context and locales', () => { + const state = buildInspectorState({ + locale: 'en', + routeName: 'index', + strategy: 'prefix', + defaultLocale: 'en', + fallbackLocale: 'en', + cachedChunks: 2, + locales: [{ code: 'en', displayName: 'English' }, { code: 'de' }], + }) + + expect(state.Context?.map((entry) => entry.key)).toEqual(['locale', 'routeName', 'strategy', 'defaultLocale', 'fallbackLocale', 'cachedChunks']) + expect(state.Locales).toHaveLength(2) + }) + + test('countTranslationKeys counts nested paths', () => { + expect( + countTranslationKeys({ + hello: 'Hello', + nav: { home: 'Home', about: 'About' }, + }), + ).toBe(4) + }) + + test('buildInspectorTree tags index chunk when activeRouteName is empty', () => { + const storage = createStorage({ + 'en:index': { hello: 'Hello' }, + }) + + const root = buildInspectorTree({ + nodeId: 'root', + storage, + configuredLocales: [{ code: 'en' }], + activeLocale: 'en', + activeRouteName: '', + }) + + expect(root.find((node) => node.id === 'active')?.label).toBe('Active (en:index)') + expect(root.find((node) => node.id === 'chunk|en|index')?.tags?.[0]?.label).toBe('Active') + }) +}) diff --git a/packages/core/vite.config.mts b/packages/core/vite.config.mts index d74595e17..b61cb4835 100644 --- a/packages/core/vite.config.mts +++ b/packages/core/vite.config.mts @@ -9,7 +9,7 @@ function dualPackageBeforeWriteFile(filePath, content) { .split('/') .pop() ?.replace(/\.d\.ts$/, '') - if (base !== 'index' && base !== 'helpers') { + if (base !== 'index' && base !== 'helpers' && base !== 'devtools') { return { filePath, content } } const ctsPath = filePath.replace(/\.d\.ts$/, '.d.cts') @@ -24,6 +24,7 @@ export default defineConfig({ entry: { index: resolve(__dirname, 'src/index.ts'), helpers: resolve(__dirname, 'src/helpers-entry.ts'), + devtools: resolve(__dirname, 'src/devtools-entry.ts'), }, formats: ['cjs', 'es'], fileName: (format, entryName) => `${entryName}.${format === 'cjs' ? 'cjs' : 'mjs'}`, diff --git a/packages/devtools-ui/package.json b/packages/devtools-ui/package.json index 2478956a0..b58df38f5 100644 --- a/packages/devtools-ui/package.json +++ b/packages/devtools-ui/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/devtools-ui", - "version": "1.2.7", + "version": "1.2.8", "description": "DevTools UI and Vite plugin for Nuxt I18n Micro.", "homepage": "https://github.com/s00d/nuxt-i18n-micro/tree/main/packages/devtools-ui#readme", "bugs": { diff --git a/packages/hmr/package.json b/packages/hmr/package.json index 41915b6bb..1d3410cb2 100644 --- a/packages/hmr/package.json +++ b/packages/hmr/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/hmr", - "version": "1.0.6", + "version": "1.0.7", "description": "Translation HMR utilities for Nuxt I18n Micro (granular subpath imports).", "keywords": [ "hmr", diff --git a/packages/node/package.json b/packages/node/package.json index b36f44792..e147f5b35 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/node", - "version": "1.2.6", + "version": "1.2.7", "description": "Node.js utilities for loading translations and locale data in Nuxt I18n Micro.", "keywords": [ "i18n", diff --git a/packages/path-strategy/package.json b/packages/path-strategy/package.json index 58eb65024..b0c1fcfdd 100644 --- a/packages/path-strategy/package.json +++ b/packages/path-strategy/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/path-strategy", - "version": "1.3.9", + "version": "1.3.10", "description": "Shared strategy-based path and route name builder for Nuxt I18n Micro frontend routing.", "keywords": [ "i18n", diff --git a/packages/preact/package.json b/packages/preact/package.json index 20f215fbb..b573da09f 100644 --- a/packages/preact/package.json +++ b/packages/preact/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/preact", - "version": "1.2.5", + "version": "1.2.6", "description": "Preact bindings for i18n-micro — hooks, context, and UI components.", "keywords": [ "i18n", diff --git a/packages/react/package.json b/packages/react/package.json index 910c78538..8e9093b63 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/react", - "version": "1.3.5", + "version": "1.3.6", "description": "React bindings for i18n-micro — hooks, context, and UI components.", "keywords": [ "i18n", diff --git a/packages/route-strategy/package.json b/packages/route-strategy/package.json index 542910f00..89c272233 100644 --- a/packages/route-strategy/package.json +++ b/packages/route-strategy/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/route-strategy", - "version": "1.1.14", + "version": "1.1.15", "description": "Locale-aware route generator for Nuxt i18n micro", "keywords": [ "i18n", diff --git a/packages/solid/package.json b/packages/solid/package.json index ac635be54..845bbd5e9 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/solid", - "version": "1.2.6", + "version": "1.2.7", "description": "SolidJS bindings for i18n-micro — primitives, context, and UI components.", "keywords": [ "i18n", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 839588044..ce1bfa2f6 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/test-utils", - "version": "1.2.8", + "version": "1.2.9", "description": "Testing helpers for Nuxt I18n Micro (translations, locales, formatting mocks).", "keywords": [ "i18n", diff --git a/packages/types-generator/package.json b/packages/types-generator/package.json index b1c458c11..d2395aecb 100644 --- a/packages/types-generator/package.json +++ b/packages/types-generator/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/types-generator", - "version": "1.1.4", + "version": "1.1.5", "description": "Type generator for i18n-micro translation keys", "keywords": [ "i18n", diff --git a/packages/types/package.json b/packages/types/package.json index 135595263..ad2f808c3 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/types", - "version": "1.2.10", + "version": "1.2.11", "description": "TypeScript definitions for the Nuxt I18n Micro ecosystem.", "keywords": [ "i18n", diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 2921b0f55..26aa73940 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -384,6 +384,13 @@ export interface ModuleOptions { */ debug?: boolean + /** + * Register Vue DevTools inspector and timeline in dev (browser extension). + * Separate from the Nuxt DevTools custom tab. + * @default true + */ + vueDevtools?: boolean + /** * Global route-level locale configuration. * Allows restricting or customizing locale routes for specific pages without diff --git a/packages/utils/package.json b/packages/utils/package.json index 38752ef4d..3f41ef9ab 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/utils", - "version": "1.0.13", + "version": "1.0.14", "description": "Framework-agnostic utilities for Nuxt I18n Micro (granular subpath imports).", "keywords": [ "i18n", diff --git a/packages/vue/package.json b/packages/vue/package.json index 447523a4f..ae867985b 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@i18n-micro/vue", - "version": "1.3.9", + "version": "1.3.10", "description": "Vue 3 bindings for i18n-micro — composables, components, and routing helpers.", "keywords": [ "i18n", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11fbd9b69..5179d00f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,8 +100,8 @@ catalogs: specifier: ^3.5.25 version: 3.5.27 '@vue/devtools-api': - specifier: ^7.0.0 - version: 7.7.2 + specifier: ^8.1.5 + version: 8.2.1 '@vue/server-renderer': specifier: ^3.3.0 version: 3.5.25 @@ -345,22 +345,19 @@ importers: version: 4.5.0 '@nuxt/test-utils': specifier: 'catalog:' - version: 4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) + version: 4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) '@playwright/test': specifier: 'catalog:' version: 1.55.0 '@types/node': specifier: 'catalog:' version: 20.19.26 + '@vue/devtools-api': + specifier: 'catalog:' + version: 8.2.1 changelogen: specifier: 'catalog:' version: 0.5.7(magicast@0.3.5) - chart.js: - specifier: 'catalog:' - version: 4.5.1 - mermaid: - specifier: 'catalog:' - version: 11.12.2 oxfmt: specifier: 'catalog:' version: 0.43.0 @@ -370,24 +367,9 @@ importers: typescript: specifier: 'catalog:' version: 5.9.3 - vitepress: - specifier: 'catalog:' - version: 2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) - vitepress-plugin-chartjs: - specifier: 'catalog:' - version: 1.2.1(chart.js@4.5.1)(vitepress@2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) - vitepress-plugin-folder-tree: - specifier: 'catalog:' - version: 1.2.1(vitepress@2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) - vitepress-plugin-llms: - specifier: 'catalog:' - version: 1.13.3 - vitepress-plugin-mermaid: - specifier: 'catalog:' - version: 2.0.17(mermaid@11.12.2)(vitepress@2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vue-tsc: specifier: 'catalog:' version: 2.2.12(typescript@5.9.3) @@ -405,6 +387,30 @@ importers: specifier: 'catalog:' version: 0.18.1(vue@3.5.40(typescript@5.9.3)) + docs: + devDependencies: + chart.js: + specifier: 'catalog:' + version: 4.5.1 + mermaid: + specifier: 'catalog:' + version: 11.12.2 + vitepress: + specifier: 'catalog:' + version: 2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + vitepress-plugin-chartjs: + specifier: 'catalog:' + version: 1.2.1(chart.js@4.5.1)(vitepress@2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + vitepress-plugin-folder-tree: + specifier: 'catalog:' + version: 1.2.1(vitepress@2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + vitepress-plugin-llms: + specifier: 'catalog:' + version: 1.13.3 + vitepress-plugin-mermaid: + specifier: 'catalog:' + version: 2.0.17(mermaid@11.12.2)(vitepress@2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)) + packages/astro: dependencies: '@i18n-micro/core': @@ -443,7 +449,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vue: specifier: 'catalog:' version: 3.5.25(typescript@5.9.3) @@ -511,7 +517,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/devtools-ui: dependencies: @@ -548,7 +554,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vue: specifier: 'catalog:' version: 3.5.25(typescript@5.9.3) @@ -576,7 +582,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/node: dependencies: @@ -604,7 +610,7 @@ importers: version: 4.5.4(@types/node@20.19.26)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/node/playground: dependencies: @@ -648,7 +654,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/preact: dependencies: @@ -682,7 +688,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/preact/playground: dependencies: @@ -759,7 +765,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/react/playground: dependencies: @@ -827,7 +833,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/solid: dependencies: @@ -861,7 +867,7 @@ importers: version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/solid/playground: dependencies: @@ -911,7 +917,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/types: devDependencies: @@ -926,7 +932,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/types-generator: dependencies: @@ -960,7 +966,7 @@ importers: version: 4.5.4(@types/node@20.19.26)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/utils: dependencies: @@ -985,7 +991,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) packages/vue: dependencies: @@ -1016,7 +1022,7 @@ importers: version: 4.5.4(@types/node@26.1.2)(rollup@4.62.3)(typescript@5.9.3)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vue: specifier: 'catalog:' version: 3.5.25(typescript@5.9.3) @@ -1059,7 +1065,7 @@ importers: version: 6.0.3(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.25(typescript@5.9.3)) '@vue/devtools-api': specifier: 'catalog:' - version: 7.7.2 + version: 8.2.1 '@vue/server-renderer': specifier: 'catalog:' version: 3.5.25(vue@3.5.25(typescript@5.9.3)) @@ -1098,7 +1104,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) vue-docgen-api: specifier: 'catalog:' version: 4.79.2(vue@3.5.40(typescript@5.9.3)) @@ -1110,7 +1116,7 @@ importers: dependencies: '@nuxt/test-utils': specifier: 'catalog:' - version: 4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) + version: 4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) nuxt: specifier: 'catalog:' version: 4.5.1(@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.29.7))(@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@netlify/blobs@9.1.2)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.40)(better-sqlite3@12.2.0)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.2.0))(encoding@0.1.13)(esbuild@0.28.1)(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.3)(optionator@0.9.4)(oxc-parser@0.141.0)(oxlint@1.69.0(oxlint-tsgolint@0.24.0))(rollup-plugin-visualizer@7.0.1(rolldown@1.2.1)(rollup@4.62.3))(rollup@4.62.3)(sass@1.94.2)(srvx@0.12.5)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vue-tsc@3.0.6(typescript@5.9.3))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25))(yaml@2.9.0) @@ -3627,6 +3633,10 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} @@ -6154,9 +6164,6 @@ packages: '@vue/devtools-api@6.6.4': resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} - '@vue/devtools-api@7.7.2': - resolution: {integrity: sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==} - '@vue/devtools-api@8.2.1': resolution: {integrity: sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==} @@ -6165,15 +6172,9 @@ packages: peerDependencies: vue: ^3.0.0 - '@vue/devtools-kit@7.7.9': - resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} - '@vue/devtools-kit@8.2.1': resolution: {integrity: sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==} - '@vue/devtools-shared@7.7.9': - resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} - '@vue/devtools-shared@8.2.1': resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==} @@ -7129,10 +7130,6 @@ packages: resolution: {integrity: sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w==} engines: {node: '>=22'} - copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - core-js-pure@3.47.0: resolution: {integrity: sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==} @@ -11017,9 +11014,6 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -11361,10 +11355,6 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - speakingurl@14.0.1: - resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} - engines: {node: '>=0.10.0'} - splitpanes@4.1.2: resolution: {integrity: sha512-frNchoCv7w0nMQEuaDGgMGMU6jv3NhN6bBGQVfCNgwJdVcYaRmi1dwYDjp7SifAoUFdiQjBRB254LJNidzo15Q==} peerDependencies: @@ -11508,10 +11498,6 @@ packages: stylis@4.3.6: resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} - superjson@2.2.2: - resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} - engines: {node: '>=16'} - supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} @@ -17103,7 +17089,7 @@ snapshots: rc9: 3.0.1 std-env: 4.2.0 - '@nuxt/test-utils@4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25))': + '@nuxt/test-utils@4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25))': dependencies: '@clack/prompts': 1.2.0 '@nuxt/devtools-kit': 2.7.0(magicast@0.3.5)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) @@ -17132,14 +17118,14 @@ snapshots: tinyexec: 1.2.4 ufo: 1.6.4 unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) - vitest-environment-nuxt: 2.0.0(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) + vitest-environment-nuxt: 2.0.0(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) vue: 3.5.40(typescript@5.9.3) optionalDependencies: '@playwright/test': 1.55.0 '@vue/test-utils': 2.4.6 jsdom: 29.1.1 playwright-core: 1.55.0 - vitest: 4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -17154,7 +17140,7 @@ snapshots: - vite - webpack - '@nuxt/test-utils@4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25))': + '@nuxt/test-utils@4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25))': dependencies: '@clack/prompts': 1.2.0 '@nuxt/devtools-kit': 2.7.0(magicast@0.5.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) @@ -17183,14 +17169,14 @@ snapshots: tinyexec: 1.2.4 ufo: 1.6.4 unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) - vitest-environment-nuxt: 2.0.0(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) + vitest-environment-nuxt: 2.0.0(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) vue: 3.5.40(typescript@5.9.3) optionalDependencies: '@playwright/test': 1.55.0 '@vue/test-utils': 2.4.6 jsdom: 29.1.1 playwright-core: 1.55.0 - vitest: 4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) + vitest: 4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -18143,6 +18129,9 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@opentelemetry/api@1.9.0': + optional: true + '@oslojs/encoding@1.1.0': {} '@oxc-parser/binding-android-arm-eabi@0.131.0': @@ -20807,10 +20796,6 @@ snapshots: '@vue/devtools-api@6.6.4': {} - '@vue/devtools-api@7.7.2': - dependencies: - '@vue/devtools-kit': 7.7.9 - '@vue/devtools-api@8.2.1': dependencies: '@vue/devtools-kit': 8.2.1 @@ -20827,16 +20812,6 @@ snapshots: '@vue/devtools-shared': 8.2.1 vue: 3.5.40(typescript@5.9.3) - '@vue/devtools-kit@7.7.9': - dependencies: - '@vue/devtools-shared': 7.7.9 - birpc: 2.9.0 - hookable: 5.5.3 - mitt: 3.0.1 - perfect-debounce: 1.0.0 - speakingurl: 14.0.1 - superjson: 2.2.2 - '@vue/devtools-kit@8.2.1': dependencies: '@vue/devtools-shared': 8.2.1 @@ -20844,10 +20819,6 @@ snapshots: hookable: 5.5.3 perfect-debounce: 2.1.0 - '@vue/devtools-shared@7.7.9': - dependencies: - rfdc: 1.4.1 - '@vue/devtools-shared@8.2.1': {} '@vue/language-core@2.2.0(typescript@5.9.3)': @@ -22079,10 +22050,6 @@ snapshots: cookie@2.0.1: {} - copy-anything@3.0.5: - dependencies: - is-what: 4.1.16 - core-js-pure@3.47.0: {} core-util-is@1.0.3: {} @@ -28924,8 +28891,6 @@ snapshots: reusify@1.0.4: {} - rfdc@1.4.1: {} - robust-predicates@3.0.2: {} rolldown-string@0.3.1(rolldown@1.2.1): @@ -29507,8 +29472,6 @@ snapshots: space-separated-tokens@2.0.2: {} - speakingurl@14.0.1: {} - splitpanes@4.1.2(vue@3.5.40(typescript@5.9.3)): dependencies: vue: 3.5.40(typescript@5.9.3) @@ -29642,10 +29605,6 @@ snapshots: stylis@4.3.6: {} - superjson@2.2.2: - dependencies: - copy-anything: 3.0.5 - supports-color@10.2.2: {} supports-color@7.2.0: @@ -31275,16 +31234,16 @@ snapshots: optionalDependencies: vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0) - vitepress-plugin-chartjs@1.2.1(chart.js@4.5.1)(vitepress@2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + vitepress-plugin-chartjs@1.2.1(chart.js@4.5.1)(vitepress@2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): dependencies: chart.js: 4.5.1 - vitepress: 2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + vitepress: 2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) yaml: 2.9.0 zod: 4.4.3 - vitepress-plugin-folder-tree@1.2.1(vitepress@2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + vitepress-plugin-folder-tree@1.2.1(vitepress@2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): dependencies: - vitepress: 2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + vitepress: 2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) yaml: 2.9.0 zod: 4.4.3 @@ -31307,14 +31266,14 @@ snapshots: transitivePeerDependencies: - supports-color - vitepress-plugin-mermaid@2.0.17(mermaid@11.12.2)(vitepress@2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): + vitepress-plugin-mermaid@2.0.17(mermaid@11.12.2)(vitepress@2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0)): dependencies: mermaid: 11.12.2 - vitepress: 2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) + vitepress: 2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) optionalDependencies: '@mermaid-js/mermaid-mindmap': 9.3.0 - vitepress@2.0.0-alpha.18(@types/node@20.19.26)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): + vitepress@2.0.0-alpha.18(@types/node@26.1.2)(axios@1.13.2)(change-case@5.4.4)(esbuild@0.28.1)(fuse.js@7.5.0)(jiti@2.7.0)(jwt-decode@4.0.0)(postcss@8.5.25)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): dependencies: '@docsearch/css': 4.6.3 '@docsearch/js': 4.6.3 @@ -31324,7 +31283,7 @@ snapshots: '@shikijs/transformers': 4.3.1 '@shikijs/types': 4.3.1 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 6.0.8(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3)) + '@vitejs/plugin-vue': 6.0.8(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3)) '@vue/devtools-api': 8.2.1 '@vue/shared': 3.5.40 '@vueuse/core': 14.3.0(vue@3.5.40(typescript@5.9.3)) @@ -31333,7 +31292,7 @@ snapshots: mark.js: 8.11.1 minisearch: 7.2.0 shiki: 4.3.0 - vite: 8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0) + vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0) vue: 3.5.40(typescript@5.9.3) optionalDependencies: postcss: 8.5.25 @@ -31363,9 +31322,9 @@ snapshots: - universal-cookie - yaml - vitest-environment-nuxt@2.0.0(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)): + vitest-environment-nuxt@2.0.0(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)): dependencies: - '@nuxt/test-utils': 4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) + '@nuxt/test-utils': 4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.3.5)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) transitivePeerDependencies: - '@cucumber/cucumber' - '@farmfe/core' @@ -31390,9 +31349,9 @@ snapshots: - vitest - webpack - vitest-environment-nuxt@2.0.0(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)): + vitest-environment-nuxt@2.0.0(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)): dependencies: - '@nuxt/test-utils': 4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) + '@nuxt/test-utils': 4.0.3(@playwright/test@1.55.0)(@vue/test-utils@2.4.6)(crossws@0.4.10(srvx@0.12.5))(esbuild@0.28.1)(jsdom@29.1.1)(magicast@0.5.3)(playwright-core@1.55.0)(rolldown@1.2.1)(rollup@4.62.3)(typescript@5.9.3)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0))(vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)))(webpack@5.98.0(esbuild@0.28.1)(lightningcss@1.33.0)(postcss@8.5.25)) transitivePeerDependencies: - '@cucumber/cucumber' - '@farmfe/core' @@ -31417,7 +31376,7 @@ snapshots: - vitest - webpack - vitest@4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(vite@7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) @@ -31440,12 +31399,13 @@ snapshots: vite: 7.3.6(@types/node@20.19.26)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/node': 20.19.26 jsdom: 29.1.1 transitivePeerDependencies: - msw - vitest@4.1.10(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@20.19.26)(jsdom@29.1.1)(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) @@ -31468,12 +31428,13 @@ snapshots: vite: 8.2.0(@types/node@20.19.26)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/node': 20.19.26 jsdom: 29.1.1 transitivePeerDependencies: - msw - vitest@4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(vite@7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) @@ -31496,12 +31457,13 @@ snapshots: vite: 7.3.6(@types/node@26.1.2)(jiti@2.7.0)(lightningcss@1.33.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/node': 26.1.2 jsdom: 29.1.1 transitivePeerDependencies: - msw - vitest@4.1.10(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)): + vitest@4.1.10(@opentelemetry/api@1.9.0)(@types/node@26.1.2)(jsdom@29.1.1)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0)) @@ -31524,6 +31486,7 @@ snapshots: vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.94.2)(terser@5.49.0)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/node': 26.1.2 jsdom: 29.1.1 transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9750621ea..e65399fee 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,7 @@ packages: - 'packages/*/playground' - 'packages/*/playground-*' - 'client' + - 'docs' - 'scripts' - 'playground' - 'test/fixtures/**/*' @@ -41,7 +42,7 @@ catalog: vue-router: ^4.6.3 || ^5.0.0 vue-tsc: ^2.0.21 '@vue/test-utils': ^2.4.6 - '@vue/devtools-api': ^7.0.0 + '@vue/devtools-api': ^8.1.5 '@vue/server-renderer': ^3.3.0 # React (один диапазон для deps и peer) react: "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/scripts/api-surface/i18n-micro__astro.api.txt b/scripts/api-surface/i18n-micro__astro.api.txt index 7944a3887..42b40a78d 100644 --- a/scripts/api-surface/i18n-micro__astro.api.txt +++ b/scripts/api-surface/i18n-micro__astro.api.txt @@ -150,6 +150,7 @@ member I18nIntegrationOptions.strategy?: Strategies | undefined member I18nIntegrationOptions.translationDir?: string | undefined member I18nIntegrationOptions.translationPayloads?: TranslationPayloadOptions | undefined member I18nIntegrationOptions.types?: boolean | undefined +member I18nIntegrationOptions.vueDevtools?: boolean | undefined interface I18nMiddlewareOptions member I18nMiddlewareOptions.autoDetect?: boolean | undefined member I18nMiddlewareOptions.defaultLocale: string diff --git a/scripts/api-surface/i18n-micro__core.api.txt b/scripts/api-surface/i18n-micro__core.api.txt index 76cb64629..a80a48e66 100644 --- a/scripts/api-surface/i18n-micro__core.api.txt +++ b/scripts/api-surface/i18n-micro__core.api.txt @@ -99,6 +99,36 @@ member TranslationStorage.translations: Map function useTranslationHelper: (storage?: TranslationStorage) => { hasCache(locale: string, page: string): boolean; getCache(locale: string, routeName: string): Translations | undefined; setCache(_locale: string, _routeName: string, _cache: Map): void; hasTranslation(locale: string, key: string): boolean; hasPageTranslation(locale: string, routeName: string): boolean; getTranslation(locale: string, routeName: string, key: string): T | null; loadTranslations(locale: string, data: Translations, routeName?: string): void; setTranslations(locale: string, data: Translations, routeName?: string): void; loadPageTranslations(locale: string, routeName: string, data: Translations): void; mergeTranslation(locale: string, routeName: string, newTranslations: Translations, _force?: boolean): void; clearCache(): void; } function withPrefixStrategy: (strategy: Strategies) => strategy is "prefix" | "prefix_and_default" +# ./devtools (src/devtools.ts) +function buildInspectorState: (snapshot: I18nDevtoolsStateSnapshot) => Record> +function buildInspectorTree: (options: BuildInspectorTreeOptions) => I18nDevtoolsInspectorNode[] +interface BuildInspectorTreeOptions +member BuildInspectorTreeOptions.activeLocale: string +member BuildInspectorTreeOptions.activeRouteName: string +member BuildInspectorTreeOptions.activeTranslations?: Record | undefined +member BuildInspectorTreeOptions.configuredLocales: Locale[] +member BuildInspectorTreeOptions.nodeId: string +member BuildInspectorTreeOptions.storage: TranslationStorage +function countTranslationKeys: (translations: Translations | Record | undefined) => number +function decodeInspectorPath: (encoded: string) => string[] +function encodeInspectorPath: (segments: string[]) => string +function flattenTranslationNode: (obj: Record, parentSegments?: string[]) => I18nDevtoolsInspectorNode[] +function getByInspectorPath: (obj: Record, segments: string[]) => unknown +interface I18nDevtoolsInspectorNode +member I18nDevtoolsInspectorNode.children?: I18nDevtoolsInspectorNode[] | undefined +member I18nDevtoolsInspectorNode.id: string +member I18nDevtoolsInspectorNode.label: string +member I18nDevtoolsInspectorNode.tags?: { label: string; textColor: number; backgroundColor: number; tooltip?: string; }[] | undefined +interface I18nDevtoolsStateSnapshot +member I18nDevtoolsStateSnapshot.cachedChunks: number +member I18nDevtoolsStateSnapshot.defaultLocale?: string | undefined +member I18nDevtoolsStateSnapshot.fallbackLocale?: string | undefined +member I18nDevtoolsStateSnapshot.locale: string +member I18nDevtoolsStateSnapshot.locales: { code: string; displayName?: string; disabled?: boolean; }[] +member I18nDevtoolsStateSnapshot.routeName: string +member I18nDevtoolsStateSnapshot.strategy?: string | undefined +function parseInspectorNodeId: (nodeId: string) => { kind: "root" | "active" | "locale" | "chunk" | "key"; locale?: string; routeName?: string; path?: string; segments?: string[]; } + # ./helpers (src/helpers.ts) function collectTranslationPaths: (obj: Record, paths: Set, prefix?: string) => void const defaultPlural: PluralFunc diff --git a/scripts/api-surface/i18n-micro__preact.api.txt b/scripts/api-surface/i18n-micro__preact.api.txt index a7834d9d6..de2983cd5 100644 --- a/scripts/api-surface/i18n-micro__preact.api.txt +++ b/scripts/api-surface/i18n-micro__preact.api.txt @@ -1352,6 +1352,7 @@ member ModuleOptions.strategy?: Strategies | undefined member ModuleOptions.translationDir?: string | undefined member ModuleOptions.translationPayloads?: TranslationPayloadOptions | undefined member ModuleOptions.types?: boolean | undefined +member ModuleOptions.vueDevtools?: boolean | undefined type Params: Record type PluralFunc: (key: TranslationKey, count: number, params: Params, locale: string, getter: Getter) => string | null class PreactI18n diff --git a/scripts/api-surface/i18n-micro__solid.api.txt b/scripts/api-surface/i18n-micro__solid.api.txt index 99637725a..9b8dfb6e7 100644 --- a/scripts/api-surface/i18n-micro__solid.api.txt +++ b/scripts/api-surface/i18n-micro__solid.api.txt @@ -107,6 +107,7 @@ member ModuleOptions.strategy?: Strategies | undefined member ModuleOptions.translationDir?: string | undefined member ModuleOptions.translationPayloads?: TranslationPayloadOptions | undefined member ModuleOptions.types?: boolean | undefined +member ModuleOptions.vueDevtools?: boolean | undefined type Params: Record type PluralFunc: (key: TranslationKey, count: number, params: Params, locale: string, getter: Getter) => string | null class SolidI18n diff --git a/scripts/api-surface/i18n-micro__types.api.txt b/scripts/api-surface/i18n-micro__types.api.txt index 030795cb9..b5b317f62 100644 --- a/scripts/api-surface/i18n-micro__types.api.txt +++ b/scripts/api-surface/i18n-micro__types.api.txt @@ -85,6 +85,7 @@ member ModuleOptions.strategy?: Strategies | undefined member ModuleOptions.translationDir?: string | undefined member ModuleOptions.translationPayloads?: TranslationPayloadOptions | undefined member ModuleOptions.types?: boolean | undefined +member ModuleOptions.vueDevtools?: boolean | undefined interface ModuleOptionsExtend member ModuleOptionsExtend.apiBaseClientHost?: string | undefined member ModuleOptionsExtend.apiBaseServerHost?: string | undefined @@ -135,6 +136,7 @@ member ModuleOptionsExtend.translationDir?: string | undefined member ModuleOptionsExtend.translationPayloadMode?: "premerged" | "source" | undefined member ModuleOptionsExtend.translationPayloads?: TranslationPayloadOptions | undefined member ModuleOptionsExtend.types?: boolean | undefined +member ModuleOptionsExtend.vueDevtools?: boolean | undefined interface ModulePrivateOptionsExtend member ModulePrivateOptionsExtend.apiBaseClientHost?: string | undefined member ModulePrivateOptionsExtend.apiBaseServerHost?: string | undefined @@ -185,6 +187,7 @@ member ModulePrivateOptionsExtend.strategy?: Strategies | undefined member ModulePrivateOptionsExtend.translationDir: string member ModulePrivateOptionsExtend.translationPayloads?: TranslationPayloadOptions | undefined member ModulePrivateOptionsExtend.types?: boolean | undefined +member ModulePrivateOptionsExtend.vueDevtools?: boolean | undefined type Params: Record type PluralFunc: (key: TranslationKey, count: number, params: Params, locale: string, getter: Getter) => string | null interface PluralTranslations diff --git a/src/devtools.ts b/src/devtools.ts index e3361559f..dd4f5a52b 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -1,6 +1,6 @@ import * as fs from 'node:fs' import sirv from 'sirv' -import path, { resolve } from 'node:path' +import path, { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import type { ModuleOptions } from '@i18n-micro/types' import { addCustomTab, extendServerRpc, onDevToolsInitialized, startSubprocess } from '@nuxt/devtools-kit' @@ -13,9 +13,42 @@ export const DEVTOOLS_UI_PORT = 3030 export const DEVTOOLS_UI_ROUTE = '/__nuxt-i18n-micro' export const DEVTOOLS_RPC_NAMESPACE = 'nuxt-i18n-micro' -export const distDir = resolve(fileURLToPath(import.meta.url), '..') -/** Prebuilt client, shipped inside the package. Absent only when working on this repo. */ -export const clientDir = resolve(distDir, 'client') +const thisDir = dirname(fileURLToPath(import.meta.url)) +/** Repo / package root whether this file lives in `src/` or `dist/`. */ +const packageRoot = resolve(thisDir, '..') + +/** + * Directory that contains this file after resolve: + * - published / `nuxt-module-build`: `dist/` + * - playground (`modules: ['../src/module']`): `src/` + */ +export const distDir = thisDir + +/** + * Prebuilt DevTools UI (`index.html` + `_nuxt/`). + * + * Must work for both layouts above — `src/client` does not exist when the playground + * loads the module from source, so we also check `../dist/client`. + */ +export const clientDir = resolveClientDir() + +function resolveClientDir(): string { + const candidates = [ + resolve(thisDir, 'client'), // dist/client next to dist/module.mjs + resolve(thisDir, '../dist/client'), // src/devtools.ts → repo dist/client + resolve(thisDir, '../client/dist'), // client/dist symlink used in-repo + ] + return candidates.find((dir) => fs.existsSync(path.join(dir, 'index.html'))) ?? candidates[0]! +} + +function resolveClientPackageDir(): string { + return resolve(packageRoot, 'client') +} + +/** Force the client `nuxi dev` proxy even when a prebuilt UI exists (HMR for client work). */ +function useLiveClientDevServer(): boolean { + return process.env.I18N_DEVTOOLS_CLIENT_DEV === '1' || process.env.I18N_DEVTOOLS_CLIENT_DEV === 'true' +} export interface ServerFunctions { getConfigs: () => Promise @@ -33,10 +66,11 @@ export interface ClientFunctions { * Two situations, and they are distinguished by whether the prebuilt client shipped * with the package: * - * - **Installed as a dependency** — `dist/client` exists, so serve it as static files. - * This is the case for every user of the module. - * - **Developing this repo** — there is no prebuilt client, so run its dev server and - * forward to it, which keeps HMR while working on the UI. + * - **Installed / playground with prebuild** — `dist/client` exists, serve it as static + * files via sirv. Playground loads `src/module`, so resolution must find `../dist/client` + * (not the non-existent `src/client`). + * - **Live client UI work** — set `I18N_DEVTOOLS_CLIENT_DEV=1` to spawn `client/` `nuxi + * dev` and proxy to it for HMR. * * Only called when DevTools are enabled — nothing here should run for an app that * has them switched off, least of all the client's dev server. @@ -49,21 +83,21 @@ export interface ClientFunctions { */ export function setupDevToolsUI(options: ModuleOptions, rootDirs: string[]) { const nuxt = useNuxt() - const hasPrebuiltClient = fs.existsSync(clientDir) + const hasPrebuiltClient = fs.existsSync(path.join(clientDir, 'index.html')) + const liveClient = useLiveClientDevServer() - if (hasPrebuiltClient) { + if (hasPrebuiltClient && !liveClient) { addDevServerHandler({ route: DEVTOOLS_UI_ROUTE, handler: fromNodeMiddleware(sirv(clientDir, { dev: true, single: true })), }) } else { - // No prebuilt client: this is the repo itself, so run the client's dev server and - // forward to it, which keeps HMR while the UI is being worked on. + // Live client UI: run its dev server and forward, which keeps HMR while the UI is edited. startSubprocess( { command: 'npx', args: ['nuxi', 'dev', '--port', String(DEVTOOLS_UI_PORT)], - cwd: resolve(distDir, '../client'), + cwd: resolveClientPackageDir(), }, { id: 'nuxt-i18n-micro:client', name: 'i18n Micro DevTools UI' }, ) @@ -73,7 +107,9 @@ export function setupDevToolsUI(options: ModuleOptions, rootDirs: string[]) { // `addDevServerHandler` strips the mount prefix, so `event.path` arrives as `/` // or `/_nuxt/...` and the route has to be put back on for the client dev server, // which serves itself under that same base. - handler: defineEventHandler((event) => proxyRequest(event, `http://localhost:${DEVTOOLS_UI_PORT}${joinURL(DEVTOOLS_UI_ROUTE, event.path)}`)), + handler: defineEventHandler((event) => + proxyRequest(event, `http://localhost:${DEVTOOLS_UI_PORT}${joinURL(DEVTOOLS_UI_ROUTE, event.path || '/')}`), + ), }) } diff --git a/src/module.ts b/src/module.ts index 257c886ab..518523942 100644 --- a/src/module.ts +++ b/src/module.ts @@ -565,6 +565,15 @@ export default defineNuxtModule({ }) } + if (nuxt.options.dev && options.plugin !== false && options.vueDevtools !== false) { + addPlugin({ + src: resolver.resolve('./runtime/plugins/04.vue-devtools'), + name: 'i18n-vue-devtools', + mode: 'client', + order: 5, + }) + } + addPlugin({ src: resolver.resolve('./runtime/plugins/06.redirect'), mode: 'server', diff --git a/src/runtime/devtools/bridge.ts b/src/runtime/devtools/bridge.ts new file mode 100644 index 000000000..994fdb07e --- /dev/null +++ b/src/runtime/devtools/bridge.ts @@ -0,0 +1,22 @@ +import type { ModuleOptionsExtend } from '@i18n-micro/types' +import type { Ref } from 'vue' +import type { NuxtI18n } from '../utils/nuxt-i18n' + +export const I18N_DEVTOOLS_BRIDGE_KEY = 'i18n-micro-devtools-bridge' + +export interface I18nDevtoolsBridge { + i18n: NuxtI18n + i18nConfig: ModuleOptionsExtend + localeState: Ref +} + +/** Dev-only bridge — not serializable, so keep it off `useState`. */ +let bridge: I18nDevtoolsBridge | null = null + +export function setI18nDevtoolsBridge(value: I18nDevtoolsBridge | null): void { + bridge = value +} + +export function getI18nDevtoolsBridge(): I18nDevtoolsBridge | null { + return bridge +} diff --git a/src/runtime/devtools/vue-devtools.ts b/src/runtime/devtools/vue-devtools.ts new file mode 100644 index 000000000..97971efee --- /dev/null +++ b/src/runtime/devtools/vue-devtools.ts @@ -0,0 +1,329 @@ +import { + buildInspectorState, + buildInspectorTree, + countTranslationKeys, + getByInspectorPath, + parseInspectorNodeId, + type I18nDevtoolsStateSnapshot, +} from '@i18n-micro/core/devtools' +import type { ModuleOptionsExtend } from '@i18n-micro/types' +import type { App } from 'vue' +import type { I18nContextChangeReason, NuxtI18n } from '../utils/nuxt-i18n' + +const INSPECTOR_ID = 'i18n-micro-inspector' +const PLUGIN_ID = 'nuxt-i18n-micro' +const TIMELINE_LAYER_ID = 'i18n-micro' + +export interface VueI18nDevtoolsContext { + app: App + i18n: NuxtI18n + i18nConfig: ModuleOptionsExtend + onContextChange: (listener: (reason: I18nContextChangeReason) => void) => () => void + onMissingKey: (listener: (locale: string, key: string, routeName: string) => void) => () => void +} + +export interface VueI18nDevtoolsNotifier { + notifyLocaleSwitch: (from: string, to: string, routeName: string) => void + notifyLoad: (locale: string, routeName: string, keyCount: number) => void + notifyTranslate: (key: string, result: unknown) => void + refreshInspector: () => void +} + +const isPlainObject = (value: unknown): value is Record => value !== null && typeof value === 'object' && !Array.isArray(value) + +function toInspectorState(snapshot: I18nDevtoolsStateSnapshot) { + const groups = buildInspectorState(snapshot) + const state: Record> = {} + + for (const [group, entries] of Object.entries(groups)) { + state[group] = entries.map((entry) => ({ + key: entry.key, + value: entry.value, + })) + } + + return state +} + +function createSnapshot(i18n: NuxtI18n, i18nConfig: ModuleOptionsExtend): I18nDevtoolsStateSnapshot { + return { + locale: i18n.getCurrentLocale(), + routeName: i18n.getCurrentRouteName() || 'index', + strategy: i18nConfig.strategy, + defaultLocale: i18nConfig.defaultLocale, + fallbackLocale: i18nConfig.fallbackLocale, + cachedChunks: i18n.storage.translations.size, + locales: (i18nConfig.locales ?? []).map((locale) => ({ + code: locale.code, + displayName: locale.displayName, + disabled: locale.disabled, + })), + } +} + +function buildNodeState(i18n: NuxtI18n, i18nConfig: ModuleOptionsExtend, nodeId: string): Record> { + const parsed = parseInspectorNodeId(nodeId) + + if (parsed.kind === 'active') { + return { + Translations: objectToStateEntries(i18n.resolveTranslations() as Record), + } + } + + if (parsed.kind === 'locale' && parsed.locale) { + const locale = parsed.locale + const configured = (i18nConfig.locales ?? []).find((item) => item.code === locale) + const prefix = `${locale}:` + const chunks: Array<{ key: string; value: unknown }> = [] + + for (const [cacheKey, data] of i18n.storage.translations) { + if (!cacheKey.startsWith(prefix)) continue + const routeName = cacheKey.slice(prefix.length) || 'index' + chunks.push({ + key: routeName, + value: `${countTranslationKeys(data)} keys`, + }) + } + + chunks.sort((a, b) => a.key.localeCompare(b.key)) + + return { + Locale: [ + { key: 'code', value: locale }, + { key: 'displayName', value: configured?.displayName ?? locale }, + { key: 'disabled', value: Boolean(configured?.disabled) }, + { key: 'isActive', value: i18n.getCurrentLocale() === locale }, + { key: 'loadedChunks', value: chunks.length }, + ], + Chunks: chunks.length ? chunks : [{ key: '—', value: 'No translation chunks loaded for this locale yet' }], + } + } + + if (parsed.kind === 'chunk' && parsed.locale) { + const routeName = parsed.routeName || 'index' + const cacheKey = `${parsed.locale}:${routeName}` + const chunk = i18n.storage.translations.get(cacheKey) as Record | undefined + + if (!chunk) { + return { + Chunk: [ + { key: 'cacheKey', value: cacheKey }, + { key: 'status', value: 'not loaded' }, + ], + } + } + + const entries = objectToStateEntries(chunk) + return { + Chunk: [ + { key: 'cacheKey', value: cacheKey }, + { key: 'keys', value: countTranslationKeys(chunk) }, + { + key: 'isActive', + value: i18n.getCurrentLocale() === parsed.locale && (i18n.getCurrentRouteName() || 'index') === routeName, + }, + ], + Translations: entries.length ? entries : [{ key: '—', value: 'Chunk is loaded but empty' }], + } + } + + if (parsed.kind === 'key' && parsed.locale) { + const routeName = parsed.routeName || 'index' + const segments = parsed.segments ?? [] + const chunk = i18n.storage.translations.get(`${parsed.locale}:${routeName}`) as Record | undefined + const isActive = i18n.getCurrentLocale() === parsed.locale && (i18n.getCurrentRouteName() || 'index') === routeName + const source = isActive ? (i18n.resolveTranslations() as Record) : (chunk ?? {}) + const value = segments.length ? getByInspectorPath(source, segments) : source + + if (isPlainObject(value)) { + return { [(segments.join('.') || 'root')]: objectToStateEntries(value) } + } + + return { + Value: [{ key: segments.join('.') || 'value', value }], + } + } + + return toInspectorState(createSnapshot(i18n, i18nConfig)) +} + +function objectToStateEntries(obj: Record, limit = 500): Array<{ key: string; value: unknown }> { + const entries: Array<{ key: string; value: unknown }> = [] + let truncated = false + + // Breadth-first so shallow keys (navigation, test_key, …) aren't starved by deep + // playground nests like key0.key0.key0… before the leaf cap hits. + const queue: Array<{ node: Record; prefix: string }> = [{ node: obj, prefix: '' }] + + while (queue.length > 0) { + const { node, prefix } = queue.shift()! + const nested: Array<{ node: Record; prefix: string }> = [] + + for (const key of Object.keys(node)) { + if (key === '__proto__') continue + if (entries.length >= limit) { + truncated = true + break + } + + const path = prefix ? `${prefix}.${key}` : key + const value = node[key] + + if (isPlainObject(value)) { + nested.push({ node: value, prefix: path }) + continue + } + + entries.push({ key: path, value }) + } + + if (truncated) break + queue.push(...nested) + } + + entries.sort((a, b) => a.key.localeCompare(b.key)) + + if (truncated) { + entries.push({ + key: '…', + value: `Showing first ${limit} leaves (nested keys flattened)`, + }) + } + + return entries +} + +export async function setupVueI18nDevtools(ctx: VueI18nDevtoolsContext): Promise { + if (!import.meta.dev || import.meta.server) { + return null + } + + let notifier: VueI18nDevtoolsNotifier | null = null + + try { + const { setupDevtoolsPlugin } = await import('@vue/devtools-api') + + setupDevtoolsPlugin( + { + id: PLUGIN_ID, + label: 'i18n Micro', + packageName: 'nuxt-i18n-micro', + homepage: 'https://s00d.github.io/nuxt-i18n-micro/', + logo: 'https://s00d.github.io/nuxt-i18n-micro/favicon.svg', + app: ctx.app, + enableEarlyProxy: true, + }, + (api) => { + const refreshInspector = () => { + api.sendInspectorTree(INSPECTOR_ID) + api.sendInspectorState(INSPECTOR_ID) + } + + api.addInspector({ + id: INSPECTOR_ID, + label: 'i18n Micro', + icon: 'carbon:language', + actions: [ + { + icon: 'refresh', + tooltip: 'Refresh inspector', + action: refreshInspector, + }, + ], + }) + + api.addTimelineLayer({ + id: TIMELINE_LAYER_ID, + label: 'i18n Micro', + color: 0x42b883, + }) + + api.on.getInspectorTree((payload) => { + if (payload.inspectorId !== INSPECTOR_ID) return + + payload.rootNodes = buildInspectorTree({ + nodeId: 'root', + storage: ctx.i18n.storage, + configuredLocales: ctx.i18nConfig.locales ?? [], + activeLocale: ctx.i18n.getCurrentLocale(), + activeRouteName: ctx.i18n.getCurrentRouteName() || 'index', + activeTranslations: ctx.i18n.resolveTranslations() as Record, + }) as never + }) + + api.on.getInspectorState((payload) => { + if (payload.inspectorId !== INSPECTOR_ID) return + + payload.state = ( + payload.nodeId ? buildNodeState(ctx.i18n, ctx.i18nConfig, payload.nodeId) : toInspectorState(createSnapshot(ctx.i18n, ctx.i18nConfig)) + ) as never + }) + + ctx.onContextChange(() => { + refreshInspector() + }) + ctx.onMissingKey((locale, key, routeName) => { + api.addTimelineEvent({ + layerId: TIMELINE_LAYER_ID, + event: { + time: api.now(), + title: 'Missing translation', + subtitle: key, + data: { locale, key, routeName }, + }, + }) + }) + + notifier = { + notifyLocaleSwitch(from, to, routeName) { + api.addTimelineEvent({ + layerId: TIMELINE_LAYER_ID, + event: { + time: api.now(), + title: 'Locale switch', + subtitle: `${from || '—'} → ${to}`, + data: { from, to, routeName }, + }, + }) + refreshInspector() + }, + notifyLoad(locale, routeName, keyCount) { + api.addTimelineEvent({ + layerId: TIMELINE_LAYER_ID, + event: { + time: api.now(), + title: 'Translations loaded', + subtitle: `${locale}:${routeName}`, + data: { locale, routeName, keyCount }, + }, + }) + refreshInspector() + }, + notifyTranslate(key, result) { + api.addTimelineEvent({ + layerId: TIMELINE_LAYER_ID, + event: { + time: api.now(), + title: '$t', + subtitle: key, + data: { key, result }, + }, + }) + }, + refreshInspector, + } + + refreshInspector() + }, + ) + } catch { + return null + } + + return notifier +} + +export function countLoadedTranslationKeys(i18n: NuxtI18n, locale: string, routeName: string): number { + const chunk = i18n.storage.translations.get(i18n.getCacheKey(locale, routeName)) + return countTranslationKeys(chunk) +} diff --git a/src/runtime/plugins/01.plugin.ts b/src/runtime/plugins/01.plugin.ts index 1b514df29..c284de9a9 100644 --- a/src/runtime/plugins/01.plugin.ts +++ b/src/runtime/plugins/01.plugin.ts @@ -25,6 +25,7 @@ import { createError, defineNuxtPlugin, navigateTo, useRouter, useRuntimeConfig import { useI18nHead } from '../composables/useI18nHead' import { useI18nLocale } from '../composables/useI18nLocale' import { createNuxtI18nPluginApi, NuxtI18n, NuxtTranslationLoader } from '../utils/nuxt-i18n' +import { setI18nDevtoolsBridge } from '../devtools/bridge' import { translationStorage } from '../utils/storage' const isDev = process.env.NODE_ENV !== 'production' @@ -139,6 +140,14 @@ export default defineNuxtPlugin(async (nuxtApp) => { }, }) + if (import.meta.dev && import.meta.client) { + setI18nDevtoolsBridge({ + i18n, + i18nConfig, + localeState, + }) + } + router.beforeEach(async (to, from) => { if (to.name !== from.name) { i18nRouteParams.value = {} diff --git a/src/runtime/plugins/04.vue-devtools.ts b/src/runtime/plugins/04.vue-devtools.ts new file mode 100644 index 000000000..e0c70f2c1 --- /dev/null +++ b/src/runtime/plugins/04.vue-devtools.ts @@ -0,0 +1,77 @@ +import { defineNuxtPlugin, useNuxtApp } from '#imports' +import type { Params } from '@i18n-micro/types' +import { watch } from 'vue' +import { getI18nDevtoolsBridge } from '../devtools/bridge' +import { countLoadedTranslationKeys, setupVueI18nDevtools } from '../devtools/vue-devtools' + +export default defineNuxtPlugin({ + name: 'i18n-vue-devtools', + dependsOn: ['i18n-plugin-loader'], + async setup() { + if (!import.meta.dev || import.meta.server) { + return + } + + const nuxtApp = useNuxtApp() + const bridge = getI18nDevtoolsBridge() + if (!bridge) { + return + } + + const notifier = await setupVueI18nDevtools({ + app: nuxtApp.vueApp, + i18n: bridge.i18n, + i18nConfig: bridge.i18nConfig, + onContextChange: (listener) => bridge.i18n.onContextChange(listener), + onMissingKey: (listener) => bridge.i18n.onMissingKey(listener), + }) + + if (!notifier) { + return + } + + const notifyCurrentLoad = () => { + const locale = bridge.i18n.getCurrentLocale() + const routeName = bridge.i18n.getCurrentRouteName() || 'index' + notifier.notifyLoad(locale, routeName, countLoadedTranslationKeys(bridge.i18n, locale, routeName)) + } + + notifyCurrentLoad() + + watch( + () => bridge.localeState.value, + (to, from) => { + if (to && from && to !== from) { + notifier.notifyLocaleSwitch(from, to, bridge.i18n.getCurrentRouteName() || 'index') + } + }, + ) + + bridge.i18n.onContextChange((reason) => { + if (reason === 'load') { + notifyCurrentLoad() + return + } + notifier.refreshInspector() + }) + + if (bridge.i18nConfig.debug) { + const wrapTranslate = unknown>(translate: T): T => + ((key: string, ...args: never[]) => { + const result = translate(key, ...args) + notifier.notifyTranslate(key, result) + return result + }) as T + + const app = nuxtApp as typeof nuxtApp & { + $t: (key: string, params?: Params, defaultValue?: string | null) => unknown + $ts: (key: string, params?: Params, defaultValue?: string) => string + $tc: (key: string, params: number | Params, defaultValue?: string) => string + } + + app.$t = wrapTranslate(app.$t.bind(app)) + app.$ts = wrapTranslate(app.$ts.bind(app)) + app.$tc = wrapTranslate(app.$tc.bind(app)) + } + }, +}) diff --git a/src/runtime/utils/nuxt-i18n.ts b/src/runtime/utils/nuxt-i18n.ts index bde681dc0..49f653369 100644 --- a/src/runtime/utils/nuxt-i18n.ts +++ b/src/runtime/utils/nuxt-i18n.ts @@ -76,6 +76,9 @@ export type GetRouteTranslationContext = (route?: unknown) => RouteTranslationCo export type LoadTranslationsChunk = (locale: string, routeName?: string) => Promise> +/** Why the active translation context changed — used by Vue DevTools timeline. */ +export type I18nContextChangeReason = 'load' | 'refresh' + export interface NuxtI18nOptions extends BaseI18nOptions {} export class NuxtI18n extends BaseI18n { @@ -92,6 +95,8 @@ export class NuxtI18n extends BaseI18n { private currentLocale = '' private currentRouteName = '' private resolveRouteContext?: GetRouteTranslationContext + private readonly contextListeners = new Set<(reason: I18nContextChangeReason) => void>() + private missingKeyListener?: (locale: string, key: string, routeName: string) => void constructor(options: NuxtI18nOptions = {}) { const storage: TranslationStorage = { @@ -168,6 +173,28 @@ export class NuxtI18n extends BaseI18n { return this.currentRouteName } + onContextChange(listener: (reason: I18nContextChangeReason) => void): () => void { + this.contextListeners.add(listener) + return () => { + this.contextListeners.delete(listener) + } + } + + onMissingKey(listener: (locale: string, key: string, routeName: string) => void): () => void { + this.missingKeyListener = listener + return () => { + if (this.missingKeyListener === listener) { + this.missingKeyListener = undefined + } + } + } + + private notifyContextChange(reason: I18nContextChangeReason): void { + for (const listener of this.contextListeners) { + listener(reason) + } + } + /** * Hot-reload the active dictionary for the next route — replace, don't merge. * @@ -187,6 +214,7 @@ export class NuxtI18n extends BaseI18n { this.currentLocale = locale this.currentRouteName = routeName || '' triggerRef(this.contextSignal) + this.notifyContextChange('load') } /** The outgoing page has unmounted, so its keys must stop resolving. */ @@ -194,6 +222,7 @@ export class NuxtI18n extends BaseI18n { if (this.outgoingTranslations === null) return this.outgoingTranslations = null triggerRef(this.contextSignal) + this.notifyContextChange('refresh') } mergeTranslations(newTranslations: Translations): void { @@ -202,6 +231,7 @@ export class NuxtI18n extends BaseI18n { // walking the whole thing again costs its size for the sake of a few new keys. this.cachedTranslations = deepMergeTranslationsRecursive(this.cachedTranslations, newTranslations as Record) triggerRef(this.contextSignal) + this.notifyContextChange('load') } async loadPageTranslations(locale: string, routeName: string, translations: Translations): Promise { @@ -209,6 +239,7 @@ export class NuxtI18n extends BaseI18n { if (locale === this.currentLocale && routeName === this.currentRouteName) { this.cachedTranslations = deepMergeTranslationsRecursive(this.cachedTranslations, translations as Record) triggerRef(this.contextSignal) + this.notifyContextChange('load') } } @@ -222,6 +253,7 @@ export class NuxtI18n extends BaseI18n { if (locale === this.currentLocale && routeName === this.currentRouteName) { this.cachedTranslations = deepMergeTranslationsRecursive(this.cachedTranslations, newTranslations as Record) triggerRef(this.contextSignal) + this.notifyContextChange('load') } } @@ -286,20 +318,26 @@ export class NuxtI18n extends BaseI18n { setTranslationAtKey(this.getChunk(this.currentLocale, this.currentRouteName), String(key), value), ) triggerRef(this.contextSignal) + this.notifyContextChange('refresh') } - protected override getMissingContext(_routeContext?: unknown): { locale: string; routeName: string } { + protected override getMissingContext(routeContext?: unknown): { locale: string; routeName: string } { + if (routeContext && this.resolveRouteContext) { + return this.resolveRouteContext(routeContext) + } return { locale: this.currentLocale, routeName: this.currentRouteName } } - protected override warnMissing(key: TranslationKey): void { + protected override warnMissing(key: TranslationKey, routeContext?: unknown): void { + const { locale, routeName } = this.getMissingContext(routeContext) + this.missingKeyListener?.(locale, String(key), routeName) const customHandler = this.getCustomMissingHandler?.() if (customHandler) { - customHandler(this.currentLocale, String(key), this.currentRouteName) + customHandler(locale, String(key), routeName) return } if (this.missingWarn && process.env.NODE_ENV !== 'production' && import.meta.client) { - console.warn(`[i18n] Missing key '${key}' in '${this.currentLocale}' for route '${this.currentRouteName}'`) + console.warn(`[i18n] Missing key '${key}' in '${locale}' for route '${routeName}'`) } } @@ -308,6 +346,7 @@ export class NuxtI18n extends BaseI18n { this.cachedTranslations = {} this.outgoingTranslations = null triggerRef(this.contextSignal) + this.notifyContextChange('refresh') } } diff --git a/test/nuxt-i18n.test.ts b/test/nuxt-i18n.test.ts index 81996eb42..37266f334 100644 --- a/test/nuxt-i18n.test.ts +++ b/test/nuxt-i18n.test.ts @@ -146,6 +146,38 @@ describe('NuxtI18n', () => { expect(i18n.getChunk('en', 'index')).toEqual({}) }) + it('onContextChange reports load vs refresh reasons', () => { + const i18n = new NuxtI18n({ missingWarn: false }) + const reasons: string[] = [] + i18n.onContextChange((reason) => { + reasons.push(reason) + }) + + i18n.applySwitchContext('en', 'index', { title: 'Hello' }) + i18n.setTranslation('title', 'Updated') + i18n.applySwitchContext('en', 'about', { title: 'About' }) + i18n.finishTransition() + i18n.clearCache() + + expect(reasons).toEqual(['load', 'refresh', 'load', 'refresh', 'refresh']) + }) + + it('missing-key listener uses route context from tForRoute lookups', () => { + const i18n = new NuxtI18n({ missingWarn: false }) + i18n.setRouteContextResolver((route) => route as { locale: string; routeName: string }) + i18n.setChunk('en', 'page-a', { title: 'A' }) + i18n.applySwitchContext('en', 'page-b', { title: 'B' }) + + const missing: Array<{ locale: string; key: string; routeName: string }> = [] + i18n.onMissingKey((locale, key, routeName) => { + missing.push({ locale, key, routeName }) + }) + + const tForA = i18n.tForRoute({ locale: 'en', routeName: 'page-a' }) + expect(tForA('missing-on-a')).toBe('missing-on-a') + expect(missing).toEqual([{ locale: 'en', key: 'missing-on-a', routeName: 'page-a' }]) + }) + it('resolveTranslations exposes the hot-reload view during a transition', () => { const i18n = new NuxtI18n({ missingWarn: false }) i18n.applySwitchContext('en', 'page-a', { common: { fromA: 'From A' } })