nuxt-i18n-micro 3.18.2 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/client/200.html +1 -1
  2. package/dist/client/404.html +1 -1
  3. package/dist/client/_nuxt/builds/latest.json +1 -1
  4. package/dist/client/_nuxt/builds/meta/bbe443af-4371-4ec3-b41c-7a5e9deb9b9b.json +1 -0
  5. package/dist/client/index.html +1 -1
  6. package/dist/module.d.mts +1 -0
  7. package/dist/module.json +1 -1
  8. package/dist/module.mjs +57 -149
  9. package/dist/runtime/composables/useI18nLocale.js +3 -3
  10. package/dist/runtime/composables/useLocaleHead.js +1 -1
  11. package/dist/runtime/plugins/01.plugin.js +6 -3
  12. package/dist/runtime/plugins/02.meta.js +2 -2
  13. package/dist/runtime/plugins/05.hooks.js +1 -1
  14. package/dist/runtime/plugins/06.redirect.js +14 -36
  15. package/dist/runtime/server/middleware/i18n.global.js +17 -35
  16. package/dist/runtime/server/plugins/watcher.dev.js +36 -66
  17. package/dist/runtime/server/routes/i18n.js +2 -2
  18. package/dist/runtime/server/utils/locale-detector.js +11 -1
  19. package/dist/runtime/server/utils/locale-server-middleware.js +2 -2
  20. package/dist/runtime/server/utils/server-loader.d.ts +3 -3
  21. package/dist/runtime/server/utils/server-loader.js +32 -8
  22. package/dist/runtime/server/utils/translation-server-middleware.js +2 -2
  23. package/dist/runtime/utils/storage.d.ts +4 -6
  24. package/dist/runtime/utils/storage.js +20 -11
  25. package/dist/types.d.mts +2 -0
  26. package/package.json +11 -8
  27. package/dist/client/_nuxt/builds/meta/a653a17d-86f7-440a-a09c-9871b01bc761.json +0 -1
  28. package/dist/runtime/utils/active-locales.d.ts +0 -4
  29. package/dist/runtime/utils/active-locales.js +0 -9
  30. package/dist/runtime/utils/cache-control.d.ts +0 -50
  31. package/dist/runtime/utils/cache-control.js +0 -88
  32. package/dist/runtime/utils/cookie.d.ts +0 -24
  33. package/dist/runtime/utils/cookie.js +0 -22
  34. package/dist/runtime/utils/deep-merge.d.ts +0 -15
  35. package/dist/runtime/utils/deep-merge.js +0 -14
  36. package/dist/runtime/utils/route-utils.d.ts +0 -33
  37. package/dist/runtime/utils/route-utils.js +0 -63
  38. package/dist/runtime/utils/runtime-i18n-config.d.ts +0 -8
  39. package/dist/runtime/utils/runtime-i18n-config.js +0 -90
@@ -1,11 +1,12 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
+ import { SERVER_CC_KEY, STORAGE_CC_KEY } from "@i18n-micro/hmr/cache-keys";
4
+ import { handleTranslationWatchChange, parseTranslationWatchRelativePath } from "@i18n-micro/hmr/watcher";
5
+ import { CacheControl } from "@i18n-micro/utils/cache-control";
3
6
  import { watch } from "chokidar";
4
7
  import { defineNitroPlugin } from "nitropack/runtime";
5
8
  import { getI18nPrivateConfig } from "#i18n-internal/config";
6
- import { CacheControl } from "../../utils/cache-control.js";
7
- const SERVER_CC_KEY = Symbol.for("__NUXT_I18N_SERVER_CACHE_CC__");
8
- const STORAGE_CC_KEY = Symbol.for("__NUXT_I18N_STORAGE_CC__");
9
+ import { getI18nConfig } from "#i18n-internal/strategy";
9
10
  function getCacheByKey(key) {
10
11
  const g = globalThis;
11
12
  const cc = g[key];
@@ -39,24 +40,6 @@ function readJsonSafe(filePath) {
39
40
  }
40
41
  return {};
41
42
  }
42
- function deepMerge(target, source) {
43
- const result = { ...target };
44
- for (const key in source) {
45
- if (key === "__proto__" || key === "constructor") continue;
46
- const src = source[key];
47
- const dst = result[key];
48
- if (src !== null && typeof src === "object" && !Array.isArray(src) && dst !== null && typeof dst === "object" && !Array.isArray(dst)) {
49
- result[key] = { ...dst, ...src };
50
- } else {
51
- result[key] = src;
52
- }
53
- }
54
- return result;
55
- }
56
- function buildCacheEntry(data) {
57
- const json = JSON.stringify(data).replace(/</g, "\\u003c");
58
- return { data, json };
59
- }
60
43
  let watcherInstance = null;
61
44
  export default defineNitroPlugin((nitroApp) => {
62
45
  if (watcherInstance) {
@@ -75,56 +58,43 @@ export default defineNitroPlugin((nitroApp) => {
75
58
  const routesLocaleLinks = i18nConfig.routesLocaleLinks || {};
76
59
  const translationsRoot = path.resolve(i18nConfig.rootDir, i18nConfig.translationDir);
77
60
  log(`Watching for translation changes in: ${translationsRoot}`);
78
- function mergeAndSet(serverCache, locale, pageName) {
79
- const rootData = readJsonSafe(path.join(translationsRoot, `${locale}.json`));
80
- const pageData = readJsonSafe(path.join(translationsRoot, "pages", pageName, `${locale}.json`));
81
- const merged = deepMerge(rootData, pageData);
82
- const cacheKey = `${locale}:${pageName}`;
83
- serverCache.set(cacheKey, buildCacheEntry(merged));
84
- const storageCache = getStorageCache();
85
- if (storageCache) {
86
- storageCache.delete(cacheKey);
87
- }
88
- log(`Re-merged and cached '${cacheKey}'`);
89
- const aliases = Object.keys(routesLocaleLinks).filter((alias) => routesLocaleLinks[alias] === pageName);
90
- for (const alias of aliases) {
91
- const aliasKey = `${locale}:${alias}`;
92
- serverCache.set(aliasKey, buildCacheEntry(merged));
93
- if (storageCache) {
94
- storageCache.delete(aliasKey);
95
- }
96
- log(`Re-merged and cached alias '${aliasKey}'`);
97
- }
98
- }
99
61
  const invalidateAndRefresh = async (filePath, event) => {
100
- if (!filePath.endsWith(".json")) return;
101
62
  const relativePath = path.relative(translationsRoot, filePath).replace(/\\/g, "/");
102
- const isPageTranslation = relativePath.startsWith("pages/");
103
- const serverCache = getOrCreateServerCache();
63
+ const runtimeConfig = getI18nConfig();
104
64
  try {
105
- if (isPageTranslation) {
106
- const match = relativePath.match(/^pages\/(.+)\/([^/]+)\.json$/);
107
- if (!match || !match[1] || !match[2]) return;
108
- const pageName = match[1];
109
- const locale = match[2];
110
- mergeAndSet(serverCache, locale, pageName);
111
- } else {
112
- const match = relativePath.match(/^([^/]+)\.json$/);
113
- if (!match || !match[1]) return;
114
- const locale = match[1];
115
- if (!configuredLocales.has(locale)) {
116
- warn(`Detected ${event} for '${relativePath}', but locale '${locale}' is not in i18n.locales. Update config and restart dev server.`);
117
- return;
118
- }
119
- const pagesDir = path.join(translationsRoot, "pages");
120
- if (existsSync(pagesDir)) {
121
- const pageDirs = readdirSync(pagesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
122
- for (const pageName of pageDirs) {
123
- mergeAndSet(serverCache, locale, pageName);
65
+ const result = await handleTranslationWatchChange({
66
+ relativePath,
67
+ configuredLocales,
68
+ listPageNames: () => {
69
+ const pagesDir = path.join(translationsRoot, "pages");
70
+ if (!existsSync(pagesDir)) {
71
+ return [];
124
72
  }
73
+ return readdirSync(pagesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
74
+ },
75
+ serverCache: getOrCreateServerCache(),
76
+ storageCache: getStorageCache(),
77
+ routesLocaleLinks,
78
+ mergeInput: {
79
+ translationPayloadMode: runtimeConfig.translationPayloadMode,
80
+ locales: runtimeConfig.locales,
81
+ fallbackLocale: runtimeConfig.fallbackLocale,
82
+ disablePageLocales: runtimeConfig.disablePageLocales,
83
+ readLocaleFile: (relativeFilePath) => readJsonSafe(path.join(translationsRoot, relativeFilePath))
84
+ }
85
+ });
86
+ if (result === "root") {
87
+ const parsed = parseTranslationWatchRelativePath(relativePath);
88
+ if (parsed.type === "root") {
89
+ log(`Re-merged ALL pages for locale '${parsed.locale}'`);
90
+ }
91
+ } else if (result === "page") {
92
+ log(`Re-merged page cache for '${relativePath}'`);
93
+ } else if (result === "ignored") {
94
+ const parsed = parseTranslationWatchRelativePath(relativePath);
95
+ if (parsed.type === "root" && !configuredLocales.has(parsed.locale)) {
96
+ warn(`Detected ${event} for '${relativePath}', but locale '${parsed.locale}' is not in i18n.locales. Update config and restart dev server.`);
125
97
  }
126
- mergeAndSet(serverCache, locale, "index");
127
- log(`Re-merged ALL pages for locale '${locale}'`);
128
98
  }
129
99
  } catch (e) {
130
100
  warn("Failed to refresh server cache for", filePath, e);
@@ -1,8 +1,8 @@
1
+ import { isEnabledLocale } from "@i18n-micro/utils/active-locales";
2
+ import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
1
3
  import { createError, defineEventHandler, getRouterParam, send, setResponseHeader } from "h3";
2
4
  import { getI18nConfig } from "#i18n-internal/strategy";
3
5
  import { useRuntimeConfig } from "#imports";
4
- import { isEnabledLocale } from "../../utils/active-locales.js";
5
- import { resolveI18nConfigWithRuntimeOverrides } from "../../utils/runtime-i18n-config.js";
6
6
  import { loadTranslationsFromServer } from "../utils/server-loader.js";
7
7
  export default defineEventHandler(async (event) => {
8
8
  const page = getRouterParam(event, "page");
@@ -1,3 +1,4 @@
1
+ import { detectLocaleFromAcceptLanguage } from "@i18n-micro/utils/accept-language";
1
2
  import { getCookie, getQuery, getRequestURL } from "h3";
2
3
  export const detectCurrentLocale = (event, config, defaultLocale) => {
3
4
  const { fallbackLocale, defaultLocale: configDefaultLocale, locales } = config;
@@ -20,5 +21,14 @@ export const detectCurrentLocale = (event, config, defaultLocale) => {
20
21
  }
21
22
  }
22
23
  }
23
- return (getCookie(event, "user-locale") || event.headers.get("accept-language")?.split(",")[0] || fallbackLocale || defaultLocale || configDefaultLocale || "en").toString();
24
+ const cookieLocale = getCookie(event, "user-locale");
25
+ if (cookieLocale) {
26
+ return cookieLocale.toString();
27
+ }
28
+ const localeCodes = locales?.map((locale) => locale.code) ?? [];
29
+ const detectedFromHeader = detectLocaleFromAcceptLanguage(event.headers.get("accept-language") ?? void 0, localeCodes);
30
+ if (detectedFromHeader) {
31
+ return detectedFromHeader;
32
+ }
33
+ return (fallbackLocale || defaultLocale || configDefaultLocale || "en").toString();
24
34
  };
@@ -1,7 +1,7 @@
1
+ import { getEnabledLocaleCodes, getEnabledLocales } from "@i18n-micro/utils/active-locales";
2
+ import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
1
3
  import { getI18nConfig } from "#i18n-internal/strategy";
2
4
  import { useRuntimeConfig } from "#imports";
3
- import { getEnabledLocaleCodes, getEnabledLocales } from "../../utils/active-locales.js";
4
- import { resolveI18nConfigWithRuntimeOverrides } from "../../utils/runtime-i18n-config.js";
5
5
  import { detectCurrentLocale } from "./locale-detector.js";
6
6
  export const useLocaleServerMiddleware = (event, defaultLocale, currentLocale) => {
7
7
  const {
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Server-side translation loader.
3
- * All merging (layers, fallback locales, global + page) is done at build time.
4
- * This loader simply reads a single pre-built file from Nitro storage and returns it.
3
+ * In `premerged` mode, layers/fallback/root+page are merged at build time and this loader
4
+ * reads a single Nitro asset. In `source` mode, compact source files are merged at runtime.
5
5
  */
6
6
  import type { Translations } from '@i18n-micro/types';
7
7
  /**
8
8
  * Load translations for a given locale and page.
9
- * Returns a single pre-built file (global + page + fallback already baked in at build time).
9
+ * Returns merged translations and a pre-serialized JSON string for the API route.
10
10
  */
11
11
  export declare function loadTranslationsFromServer(locale: string, routeName: string): Promise<{
12
12
  data: Translations;
@@ -1,16 +1,20 @@
1
+ import { SERVER_CC_KEY } from "@i18n-micro/hmr/cache-keys";
2
+ import { isEnabledLocale } from "@i18n-micro/utils/active-locales";
3
+ import { CacheControl } from "@i18n-micro/utils/cache-control";
4
+ import { normalizeConfiguredLocales } from "@i18n-micro/utils/merge-source";
5
+ import { fetchTranslationPayloadFromHost } from "@i18n-micro/utils/payload-fetch";
6
+ import { resolveTranslationPayloadPage } from "@i18n-micro/utils/payload-url";
7
+ import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
8
+ import { loadSourceTranslationsFromStorage } from "@i18n-micro/utils/source-loader";
1
9
  import { useStorage } from "nitropack/runtime";
2
10
  import { getI18nConfig } from "#i18n-internal/strategy";
3
- import { isEnabledLocale } from "../../utils/active-locales.js";
4
- import { CacheControl } from "../../utils/cache-control.js";
5
- import { resolveI18nConfigWithRuntimeOverrides } from "../../utils/runtime-i18n-config.js";
6
- const CC_KEY = Symbol.for("__NUXT_I18N_SERVER_CACHE_CC__");
7
11
  function getServerCacheControl() {
8
12
  const g = globalThis;
9
- if (!g[CC_KEY]) {
13
+ if (!g[SERVER_CC_KEY]) {
10
14
  const cfg = resolveI18nConfigWithRuntimeOverrides(getI18nConfig());
11
- g[CC_KEY] = new CacheControl({ maxSize: cfg.cacheMaxSize ?? 0, ttl: cfg.cacheTtl ?? 0 });
15
+ g[SERVER_CC_KEY] = new CacheControl({ maxSize: cfg.cacheMaxSize ?? 0, ttl: cfg.cacheTtl ?? 0 });
12
16
  }
13
- return g[CC_KEY];
17
+ return g[SERVER_CC_KEY];
14
18
  }
15
19
  const ASSETS_PREFIX = "assets:i18n";
16
20
  function toTranslations(data) {
@@ -35,8 +39,28 @@ export async function loadTranslationsFromServer(locale, routeName) {
35
39
  }
36
40
  const storage = useStorage();
37
41
  const routesLocaleLinks = config.routesLocaleLinks || {};
38
- const resolvedPage = routesLocaleLinks[routeName] || routeName;
42
+ const resolvedPage = resolveTranslationPayloadPage(routeName, routesLocaleLinks);
39
43
  const normalizedPage = resolvedPage.replace(/\//g, ":");
44
+ if (config.apiBaseServerHost) {
45
+ const data2 = await fetchTranslationPayloadFromHost(config, locale, resolvedPage, $fetch);
46
+ const json2 = JSON.stringify(data2).replace(/</g, "\\u003c");
47
+ const entry2 = { data: data2, json: json2 };
48
+ cc.set(cacheKey, entry2);
49
+ return entry2;
50
+ }
51
+ if (config.translationPayloadMode === "source") {
52
+ const data2 = await loadSourceTranslationsFromStorage(storage, {
53
+ locale,
54
+ pageName: resolvedPage,
55
+ locales: normalizeConfiguredLocales(config.locales),
56
+ globalFallbackLocale: config.fallbackLocale,
57
+ disablePageLocales: config.disablePageLocales
58
+ });
59
+ const json2 = JSON.stringify(data2).replace(/</g, "\\u003c");
60
+ const entry2 = { data: data2, json: json2 };
61
+ cc.set(cacheKey, entry2);
62
+ return entry2;
63
+ }
40
64
  const key = `${ASSETS_PREFIX}:pages:${normalizedPage}:${locale}.json`;
41
65
  const loaded = await storage.getItem(key);
42
66
  const data = toTranslations(loaded);
@@ -1,8 +1,8 @@
1
1
  import { interpolate } from "@i18n-micro/core";
2
+ import { getEnabledLocales } from "@i18n-micro/utils/active-locales";
3
+ import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
2
4
  import { getI18nConfig } from "#i18n-internal/strategy";
3
5
  import { useRuntimeConfig } from "#imports";
4
- import { getEnabledLocales } from "../../utils/active-locales.js";
5
- import { resolveI18nConfigWithRuntimeOverrides } from "../../utils/runtime-i18n-config.js";
6
6
  import { detectCurrentLocale } from "./locale-detector.js";
7
7
  import { loadTranslationsFromServer } from "./server-loader.js";
8
8
  const I18N_CONTEXT_KEY = "__i18n_translations__";
@@ -1,9 +1,4 @@
1
- /**
2
- * Translation Storage
3
- * Unified translation storage for client and server.
4
- * Cache control (TTL, maxSize) delegated to CacheControl.
5
- */
6
- import { type CacheControlOptions } from './cache-control.js';
1
+ import { type CacheControlOptions } from '@i18n-micro/utils/cache-control';
7
2
  declare global {
8
3
  interface Window {
9
4
  __I18N__?: Record<string, unknown>;
@@ -12,7 +7,10 @@ declare global {
12
7
  export interface LoadOptions {
13
8
  apiBaseUrl: string;
14
9
  baseURL: string;
10
+ apiBaseClientHost?: string;
11
+ apiBaseServerHost?: string;
15
12
  dateBuild?: string | number;
13
+ routesLocaleLinks?: Record<string, string>;
16
14
  }
17
15
  export interface LoadResult {
18
16
  data: Record<string, unknown>;
@@ -1,11 +1,12 @@
1
- import { CacheControl } from "./cache-control.js";
2
- const CC_KEY = Symbol.for("__NUXT_I18N_STORAGE_CC__");
1
+ import { STORAGE_CC_KEY } from "@i18n-micro/hmr/cache-keys";
2
+ import { CacheControl } from "@i18n-micro/utils/cache-control";
3
+ import { buildTranslationPayloadFetchRequest } from "@i18n-micro/utils/payload-url";
3
4
  function getStorageCacheControl() {
4
5
  const g = globalThis;
5
- if (!g[CC_KEY]) {
6
- g[CC_KEY] = new CacheControl();
6
+ if (!g[STORAGE_CC_KEY]) {
7
+ g[STORAGE_CC_KEY] = new CacheControl();
7
8
  }
8
- return g[CC_KEY];
9
+ return g[STORAGE_CC_KEY];
9
10
  }
10
11
  class TranslationStorage {
11
12
  cc;
@@ -28,12 +29,20 @@ class TranslationStorage {
28
29
  // FETCH LOADER
29
30
  // ==========================================================================
30
31
  async fetchTranslations(locale, routeName, options) {
31
- const { apiBaseUrl, baseURL, dateBuild } = options;
32
- const page = routeName || "index";
33
- const path = `/${apiBaseUrl}/${page}/${locale}/data.json`;
34
- return await $fetch(path.replace(/\/{2,}/g, "/"), {
35
- baseURL,
36
- params: dateBuild ? { v: dateBuild } : void 0
32
+ const request = buildTranslationPayloadFetchRequest({
33
+ apiBaseUrl: options.apiBaseUrl,
34
+ routeName,
35
+ locale,
36
+ isServer: import.meta.server,
37
+ baseURL: options.baseURL,
38
+ apiBaseClientHost: options.apiBaseClientHost,
39
+ apiBaseServerHost: options.apiBaseServerHost,
40
+ dateBuild: options.dateBuild,
41
+ routesLocaleLinks: options.routesLocaleLinks
42
+ });
43
+ return await $fetch(request.path, {
44
+ baseURL: request.baseURL,
45
+ params: request.params
37
46
  });
38
47
  }
39
48
  // ==========================================================================
package/dist/types.d.mts CHANGED
@@ -8,6 +8,8 @@ export { type Getter, type GlobalLocaleRoutes, type Locale, type LocaleCode, typ
8
8
 
9
9
  export { type PluginsInjections } from '../dist/runtime/plugins/01.plugin.js'
10
10
 
11
+ export { type TranslationPayloadMode, type resolveTranslationPayloadMode, type resolveTranslationPayloadOptions, type resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config'
12
+
11
13
  export { default } from './module.mjs'
12
14
 
13
15
  export { type ModuleHooks } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
- "version": "3.18.2",
3
+ "version": "3.19.0",
4
4
  "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.",
5
5
  "repository": "s00d/nuxt-i18n-micro",
6
6
  "license": "MIT",
@@ -75,10 +75,12 @@
75
75
  "globby": "^14.1.0",
76
76
  "ufo": "^1.5.4",
77
77
  "@i18n-micro/core": "1.3.1",
78
+ "@i18n-micro/path-strategy": "1.3.2",
79
+ "@i18n-micro/hmr": "1.0.0",
78
80
  "@i18n-micro/route-strategy": "1.1.7",
79
- "@i18n-micro/test-utils": "1.2.1",
80
- "@i18n-micro/types": "1.2.2",
81
- "@i18n-micro/path-strategy": "1.3.2"
81
+ "@i18n-micro/types": "1.2.3",
82
+ "@i18n-micro/utils": "1.0.0",
83
+ "@i18n-micro/test-utils": "1.2.1"
82
84
  },
83
85
  "devDependencies": {
84
86
  "@biomejs/biome": "^2.3.14",
@@ -122,9 +124,10 @@
122
124
  "dev:generate": "nuxi generate playground",
123
125
  "release": "pnpm run release:check && pnpm run release:patch",
124
126
  "release:check": "pnpm run format && pnpm run lint && pnpm run typecheck && pnpm run test:types && pnpm run test && pnpm run test:vitest && pnpm run test:workspaces",
125
- "release:patch": "pnpm run prepack && node scripts/run-changelogen-release.mjs patch && pnpm publish -r && git push --follow-tags",
126
- "release:minor": "pnpm run prepack && node scripts/run-changelogen-release.mjs minor && pnpm publish -r && git push --follow-tags",
127
- "release:major": "pnpm run prepack && node scripts/run-changelogen-release.mjs major && pnpm publish -r && git push --follow-tags",
127
+ "release:auth": "node scripts/ensure-npm-auth.mjs",
128
+ "release:patch": "pnpm run release:auth && pnpm run prepack && node scripts/run-changelogen-release.mjs patch && pnpm publish -r && git push --follow-tags",
129
+ "release:minor": "pnpm run release:auth && pnpm run prepack && node scripts/run-changelogen-release.mjs minor && pnpm publish -r && git push --follow-tags",
130
+ "release:major": "pnpm run release:auth && pnpm run prepack && node scripts/run-changelogen-release.mjs major && pnpm publish -r && git push --follow-tags",
128
131
  "lint": "biome check .",
129
132
  "lint:fix": "biome check --write .",
130
133
  "format": "biome format --write .",
@@ -144,7 +147,7 @@
144
147
  "verify:packages": "node scripts/verify-packages.mjs",
145
148
  "verify:packages:publint": "node scripts/verify-packages.mjs --publint",
146
149
  "compare:published": "node scripts/compare-published-dist.mjs",
147
- "test:dist:packages": "pnpm --filter \"@i18n-micro/core\" --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",
150
+ "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",
148
151
  "typecheck": "tsc --noEmit",
149
152
  "typecheck:nuxt": "nuxt typecheck --no-emit",
150
153
  "docs:dev": "vitepress dev docs",
@@ -1 +0,0 @@
1
- {"id":"a653a17d-86f7-440a-a09c-9871b01bc761","timestamp":1779781934227,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
@@ -1,4 +0,0 @@
1
- import type { Locale } from '@i18n-micro/types';
2
- export declare function getEnabledLocales(locales?: Locale[] | null): Locale[];
3
- export declare function getEnabledLocaleCodes(locales?: Locale[] | null): string[];
4
- export declare function isEnabledLocale(locales: Locale[] | null | undefined, code: string): boolean;
@@ -1,9 +0,0 @@
1
- export function getEnabledLocales(locales) {
2
- return (locales ?? []).filter((locale) => !locale.disabled);
3
- }
4
- export function getEnabledLocaleCodes(locales) {
5
- return getEnabledLocales(locales).map((locale) => locale.code);
6
- }
7
- export function isEnabledLocale(locales, code) {
8
- return getEnabledLocales(locales).some((locale) => locale.code === code);
9
- }
@@ -1,50 +0,0 @@
1
- /**
2
- * CacheControl — O(1) LRU cache with optional sliding TTL.
3
- *
4
- * Map is created internally — no external references, no desync risk.
5
- *
6
- * Performance:
7
- * get — O(1) (lazy TTL check + LRU reorder via Map delete/set)
8
- * set — O(1) (LRU eviction via Map.keys().next())
9
- * has — O(1) (lazy TTL eviction)
10
- *
11
- * Eviction: LRU (Least Recently Used) based on native Map insertion order.
12
- */
13
- export interface CacheControlOptions {
14
- maxSize?: number;
15
- /** TTL in seconds (0 = no expiration) */
16
- ttl?: number;
17
- }
18
- export declare class CacheControl<T> {
19
- private readonly cache;
20
- private readonly expiry;
21
- private maxSize;
22
- private ttlMs;
23
- constructor(options?: CacheControlOptions);
24
- /** Update limits at runtime. Last call wins. */
25
- configure(options: CacheControlOptions): void;
26
- /**
27
- * Get entry. Returns undefined if missing or expired.
28
- * Refreshes TTL on hit (sliding expiration).
29
- * Promotes key to most-recently-used position.
30
- */
31
- get(key: string): T | undefined;
32
- /**
33
- * Check existence. Lazily evicts expired entries (side-effect by design).
34
- */
35
- has(key: string): boolean;
36
- /**
37
- * Store entry with O(1) LRU eviction.
38
- * Existing key — refreshes LRU position.
39
- * New key at capacity — evicts least-recently-used (first Map key).
40
- */
41
- set(key: string, value: T): void;
42
- /** Delete entry and its expiry metadata. */
43
- delete(key: string): boolean;
44
- /** Clear all entries and metadata. */
45
- clear(): void;
46
- /** Iterate over all cache keys. */
47
- keys(): IterableIterator<string>;
48
- /** Current number of entries. */
49
- get size(): number;
50
- }
@@ -1,88 +0,0 @@
1
- export class CacheControl {
2
- cache = /* @__PURE__ */ new Map();
3
- expiry = /* @__PURE__ */ new Map();
4
- maxSize = 0;
5
- ttlMs = 0;
6
- constructor(options) {
7
- if (options) this.configure(options);
8
- }
9
- /** Update limits at runtime. Last call wins. */
10
- configure(options) {
11
- this.maxSize = options.maxSize ?? 0;
12
- this.ttlMs = (options.ttl ?? 0) * 1e3;
13
- if (this.ttlMs === 0) {
14
- this.expiry.clear();
15
- }
16
- }
17
- /**
18
- * Get entry. Returns undefined if missing or expired.
19
- * Refreshes TTL on hit (sliding expiration).
20
- * Promotes key to most-recently-used position.
21
- */
22
- get(key) {
23
- const entry = this.cache.get(key);
24
- if (entry === void 0) return void 0;
25
- if (this.ttlMs > 0) {
26
- const exp = this.expiry.get(key);
27
- if (exp && Date.now() > exp) {
28
- this.delete(key);
29
- return void 0;
30
- }
31
- this.expiry.set(key, Date.now() + this.ttlMs);
32
- }
33
- this.cache.delete(key);
34
- this.cache.set(key, entry);
35
- return entry;
36
- }
37
- /**
38
- * Check existence. Lazily evicts expired entries (side-effect by design).
39
- */
40
- has(key) {
41
- if (!this.cache.has(key)) return false;
42
- if (this.ttlMs > 0) {
43
- const exp = this.expiry.get(key);
44
- if (exp && Date.now() > exp) {
45
- this.delete(key);
46
- return false;
47
- }
48
- }
49
- return true;
50
- }
51
- /**
52
- * Store entry with O(1) LRU eviction.
53
- * Existing key — refreshes LRU position.
54
- * New key at capacity — evicts least-recently-used (first Map key).
55
- */
56
- set(key, value) {
57
- if (this.cache.has(key)) {
58
- this.cache.delete(key);
59
- } else if (this.maxSize > 0 && this.cache.size >= this.maxSize) {
60
- const oldestKey = this.cache.keys().next().value;
61
- if (oldestKey !== void 0) {
62
- this.delete(oldestKey);
63
- }
64
- }
65
- this.cache.set(key, value);
66
- if (this.ttlMs > 0) {
67
- this.expiry.set(key, Date.now() + this.ttlMs);
68
- }
69
- }
70
- /** Delete entry and its expiry metadata. */
71
- delete(key) {
72
- this.expiry.delete(key);
73
- return this.cache.delete(key);
74
- }
75
- /** Clear all entries and metadata. */
76
- clear() {
77
- this.cache.clear();
78
- this.expiry.clear();
79
- }
80
- /** Iterate over all cache keys. */
81
- keys() {
82
- return this.cache.keys();
83
- }
84
- /** Current number of entries. */
85
- get size() {
86
- return this.cache.size;
87
- }
88
- }
@@ -1,24 +0,0 @@
1
- import type { ModuleOptionsExtend } from '@i18n-micro/types';
2
- export declare const DEFAULT_COOKIE_NAME = "user-locale";
3
- export declare const DEFAULT_HASH_COOKIE_NAME = "hash-locale";
4
- export declare const DEFAULT_COOKIE_MAX_AGE: number;
5
- /**
6
- * Returns cookie name from config.
7
- * If localeCookie === null, returns null (cookies disabled).
8
- */
9
- export declare function getLocaleCookieName(config: ModuleOptionsExtend): string | null;
10
- /**
11
- * Returns hash cookie name when hashMode is enabled.
12
- * Returns null when hashMode is false.
13
- */
14
- export declare function getHashCookieName(config: ModuleOptionsExtend): string | null;
15
- /**
16
- * Returns standard options for locale/hash cookies.
17
- */
18
- export declare function getLocaleCookieOptions(): {
19
- expires: Date;
20
- maxAge: number;
21
- path: string;
22
- watch: boolean;
23
- sameSite: "lax";
24
- };
@@ -1,22 +0,0 @@
1
- export const DEFAULT_COOKIE_NAME = "user-locale";
2
- export const DEFAULT_HASH_COOKIE_NAME = "hash-locale";
3
- export const DEFAULT_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
4
- export function getLocaleCookieName(config) {
5
- if (config.localeCookie === null) return null;
6
- return config.localeCookie || DEFAULT_COOKIE_NAME;
7
- }
8
- export function getHashCookieName(config) {
9
- if (!config.hashMode) return null;
10
- return DEFAULT_HASH_COOKIE_NAME;
11
- }
12
- export function getLocaleCookieOptions() {
13
- const date = /* @__PURE__ */ new Date();
14
- date.setTime(date.getTime() + DEFAULT_COOKIE_MAX_AGE * 1e3);
15
- return {
16
- expires: date,
17
- maxAge: DEFAULT_COOKIE_MAX_AGE,
18
- path: "/",
19
- watch: true,
20
- sameSite: "lax"
21
- };
22
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Optimized deep merge for translation objects (2-level depth).
3
- *
4
- * Regular shallow spread `{ ...old, ...new }` overwrites nested objects entirely:
5
- * { common: { fromA: "A" } } + { common: { fromB: "B" } } => { common: { fromB: "B" } }
6
- *
7
- * This function preserves sibling keys inside nested objects:
8
- * { common: { fromA: "A" } } + { common: { fromB: "B" } } => { common: { fromA: "A", fromB: "B" } }
9
- *
10
- * Performance: only iterates top-level keys of `source`; inner merge is a single spread.
11
- *
12
- * Limitation: only merges 2 levels deep. At level 3+, source overwrites target.
13
- * For 99% of i18n files (Section → Key → Value), this is sufficient.
14
- */
15
- export declare function deepMergeTranslations(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
@@ -1,14 +0,0 @@
1
- export function deepMergeTranslations(target, source) {
2
- const result = { ...target };
3
- for (const key in source) {
4
- if (key === "__proto__" || key === "constructor") continue;
5
- const src = source[key];
6
- const dst = result[key];
7
- if (src !== null && typeof src === "object" && !Array.isArray(src) && dst !== null && typeof dst === "object" && !Array.isArray(dst)) {
8
- result[key] = { ...dst, ...src };
9
- } else {
10
- result[key] = src;
11
- }
12
- }
13
- return result;
14
- }