@uipkge/nuxt 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -6,11 +6,6 @@ const module$1 = defineNuxtModule({
|
|
|
6
6
|
configKey: "i18now",
|
|
7
7
|
compatibility: { nuxt: ">=3.0.0" }
|
|
8
8
|
},
|
|
9
|
-
// Auto-install @nuxtjs/i18n when the user hasn't added it themselves.
|
|
10
|
-
// It ships as a dependency of @uipkge/nuxt so no separate install is needed.
|
|
11
|
-
moduleDependencies: {
|
|
12
|
-
"@nuxtjs/i18n": { version: "^9.0.0" }
|
|
13
|
-
},
|
|
14
9
|
defaults: {
|
|
15
10
|
host: "https://i18now.com",
|
|
16
11
|
cdnUrl: "https://cdn.i18now.com",
|
|
@@ -49,6 +44,7 @@ const module$1 = defineNuxtModule({
|
|
|
49
44
|
src: resolver.resolve("./runtime/plugin"),
|
|
50
45
|
mode: "server"
|
|
51
46
|
});
|
|
47
|
+
nuxt.options.build.transpile.push("@uipkge/core");
|
|
52
48
|
if (nuxt.options.dev) {
|
|
53
49
|
addServerHandler({
|
|
54
50
|
route: "/api/_i18now/sync",
|
|
@@ -1,83 +1,44 @@
|
|
|
1
1
|
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
|
|
2
|
-
|
|
3
|
-
const MAX_RETRIES = 3;
|
|
2
|
+
import { createI18nowSyncer } from "@uipkge/core";
|
|
4
3
|
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
5
4
|
const { projectId, cdnUrl, environment, syncIn, locale: configLocale } = useRuntimeConfig().public.i18now;
|
|
6
|
-
if (!syncIn.includes(process.env.NODE_ENV ?? "
|
|
5
|
+
if (!syncIn.includes(process.env.NODE_ENV ?? "production")) return;
|
|
7
6
|
if (!projectId) {
|
|
8
7
|
console.warn("[i18now] projectId is not set \u2014 key sync disabled.");
|
|
9
8
|
return;
|
|
10
9
|
}
|
|
11
10
|
const i18nGlobal = nuxtApp.$i18n ?? nuxtApp.vueApp.config.globalProperties.$i18n;
|
|
12
|
-
const
|
|
11
|
+
const syncer = createI18nowSyncer({
|
|
12
|
+
endpoint: "/api/_i18now/sync",
|
|
13
|
+
debug: import.meta.dev
|
|
14
|
+
});
|
|
13
15
|
const locale = i18nGlobal?.locale?.value ?? i18nGlobal?.locale ?? configLocale;
|
|
14
16
|
async function loadLocale(targetLocale) {
|
|
15
17
|
try {
|
|
16
|
-
const
|
|
18
|
+
const url = `${cdnUrl}/${projectId}/publish/${environment}/${targetLocale}.json`;
|
|
19
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(8e3) });
|
|
17
20
|
if (res.ok) {
|
|
18
21
|
const data = await res.json();
|
|
19
|
-
|
|
20
|
-
for (const key of Object.keys(data)) existingKeys.add(key);
|
|
22
|
+
syncer.setExistingKeys(Object.keys(data));
|
|
21
23
|
i18nGlobal?.setLocaleMessage(targetLocale, data);
|
|
22
|
-
} else if (
|
|
24
|
+
} else if (import.meta.dev) {
|
|
23
25
|
console.warn(
|
|
24
|
-
`[i18now] Could not load translations from CDN (${res.status}). All keys will be sent to sync \u2014 this is safe, the backend deduplicates them. URL: ${
|
|
26
|
+
`[i18now] Could not load translations from CDN (${res.status}). All keys will be sent to sync \u2014 this is safe, the backend deduplicates them. URL: ${url}`
|
|
25
27
|
);
|
|
26
28
|
}
|
|
27
29
|
} catch (err) {
|
|
28
|
-
if (
|
|
30
|
+
if (import.meta.dev) {
|
|
29
31
|
console.warn(`[i18now] CDN fetch failed for locale '${targetLocale}':`, err);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
await loadLocale(locale);
|
|
34
|
-
const synced = /* @__PURE__ */ new Set();
|
|
35
|
-
const retries = /* @__PURE__ */ new Map();
|
|
36
|
-
const pending = /* @__PURE__ */ new Map();
|
|
37
|
-
let flushTimer = null;
|
|
38
|
-
function flush() {
|
|
39
|
-
flushTimer = null;
|
|
40
|
-
if (pending.size === 0) return;
|
|
41
|
-
const keys = Array.from(pending.entries()).map(([key, value]) => ({ key, value }));
|
|
42
|
-
pending.clear();
|
|
43
|
-
fetch("/api/_i18now/sync", {
|
|
44
|
-
method: "POST",
|
|
45
|
-
headers: { "Content-Type": "application/json" },
|
|
46
|
-
body: JSON.stringify({ keys })
|
|
47
|
-
}).then((res) => {
|
|
48
|
-
if (!res.ok) {
|
|
49
|
-
if (process.env.NODE_ENV === "development") {
|
|
50
|
-
res.json().then((data) => console.warn(`[i18now] Sync failed (${res.status}):`, data?.statusMessage ?? data)).catch(() => console.warn(`[i18now] Sync failed with status ${res.status}`));
|
|
51
|
-
}
|
|
52
|
-
throw new Error(`${res.status}`);
|
|
53
|
-
}
|
|
54
|
-
}).catch(() => {
|
|
55
|
-
for (const { key } of keys) {
|
|
56
|
-
const attempts = (retries.get(key) ?? 0) + 1;
|
|
57
|
-
if (attempts < MAX_RETRIES) {
|
|
58
|
-
retries.set(key, attempts);
|
|
59
|
-
synced.delete(key);
|
|
60
|
-
} else {
|
|
61
|
-
retries.delete(key);
|
|
62
|
-
if (process.env.NODE_ENV === "development") {
|
|
63
|
-
console.warn(`[i18now] Sync failed after ${MAX_RETRIES} attempts for key "${key}". Giving up.`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
function syncKey(key, value) {
|
|
70
|
-
if (synced.has(key) || synced.size >= MAX_TRACKED) return;
|
|
71
|
-
synced.add(key);
|
|
72
|
-
pending.set(key, value);
|
|
73
|
-
if (!flushTimer) flushTimer = setTimeout(flush, 0);
|
|
74
|
-
}
|
|
75
36
|
const originalGlobalT = nuxtApp.vueApp.config.globalProperties.$t;
|
|
76
37
|
if (originalGlobalT) {
|
|
77
38
|
nuxtApp.vueApp.config.globalProperties.$t = function(key, defaultValue, ...rest) {
|
|
78
39
|
const result = originalGlobalT.call(this, key, defaultValue, ...rest);
|
|
79
|
-
if (!existingKeys.has(key) && typeof defaultValue === "string") {
|
|
80
|
-
syncKey(key, defaultValue);
|
|
40
|
+
if (!syncer.existingKeys.has(key) && typeof defaultValue === "string") {
|
|
41
|
+
syncer.syncKey(key, defaultValue);
|
|
81
42
|
}
|
|
82
43
|
return result;
|
|
83
44
|
};
|
|
@@ -87,7 +48,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
|
|
|
87
48
|
});
|
|
88
49
|
return {
|
|
89
50
|
provide: {
|
|
90
|
-
i18nowSync: { syncKey, existingKeys }
|
|
51
|
+
i18nowSync: { syncKey: syncer.syncKey, existingKeys: syncer.existingKeys }
|
|
91
52
|
}
|
|
92
53
|
};
|
|
93
54
|
});
|
package/dist/runtime/plugin.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
|
|
2
2
|
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
3
3
|
const { projectId, cdnUrl, environment, locale: configLocale } = useRuntimeConfig().public.i18now;
|
|
4
|
+
if (!projectId) return;
|
|
4
5
|
const i18n = nuxtApp.$i18n ?? nuxtApp.vueApp.config.globalProperties.$i18n;
|
|
5
6
|
async function loadLocale(locale) {
|
|
6
7
|
try {
|
|
7
8
|
const url = `${cdnUrl}/${projectId}/publish/${environment}/${locale}.json`;
|
|
8
|
-
const res = await fetch(url);
|
|
9
|
-
if (!res.ok)
|
|
9
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(8e3) });
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
if (import.meta.dev) {
|
|
12
|
+
console.warn(
|
|
13
|
+
`[i18now] Could not load locale '${locale}' from CDN (${res.status}). Default values will be used as fallback. URL: ${url}`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
10
18
|
const messages = await res.json();
|
|
11
19
|
if (i18n) i18n.setLocaleMessage(locale, messages);
|
|
12
20
|
} catch (err) {
|
|
13
|
-
if (
|
|
21
|
+
if (import.meta.dev) {
|
|
14
22
|
console.warn(`[i18now] Failed to load locale '${locale}' from CDN:`, err);
|
|
15
23
|
}
|
|
16
24
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { defineNuxtPlugin } from "#app";
|
|
2
2
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
const directGlobal = nuxtApp.$i18n?.global;
|
|
4
|
+
let symbolGlobal;
|
|
5
|
+
if (!directGlobal) {
|
|
6
|
+
const provides = nuxtApp.vueApp._context?.provides ?? {};
|
|
7
|
+
const i18nSym = Object.getOwnPropertySymbols(provides).find((s) => s.toString() === "Symbol(vue-i18n)");
|
|
8
|
+
const i18nInstance = i18nSym ? provides[i18nSym] : void 0;
|
|
9
|
+
symbolGlobal = i18nInstance?.global;
|
|
10
|
+
}
|
|
11
|
+
const globalComposer = directGlobal ?? symbolGlobal;
|
|
12
|
+
if (globalComposer) {
|
|
13
|
+
globalComposer.missingWarn = false;
|
|
14
|
+
globalComposer.fallbackWarn = false;
|
|
10
15
|
}
|
|
11
16
|
});
|
|
@@ -2,7 +2,7 @@ import { createError, defineEventHandler, readBody } from "h3";
|
|
|
2
2
|
export default defineEventHandler(async (event) => {
|
|
3
3
|
const config = useRuntimeConfig(event);
|
|
4
4
|
const publicConfig = config.public?.i18now;
|
|
5
|
-
const env = process.env.NODE_ENV ?? "
|
|
5
|
+
const env = process.env.NODE_ENV ?? "production";
|
|
6
6
|
if (!publicConfig?.syncIn?.includes(env)) {
|
|
7
7
|
throw createError({ statusCode: 403, statusMessage: "Key sync is disabled in this environment" });
|
|
8
8
|
}
|
|
@@ -23,7 +23,13 @@ export default defineEventHandler(async (event) => {
|
|
|
23
23
|
if (!Array.isArray(body?.keys)) {
|
|
24
24
|
throw createError({ statusCode: 400, statusMessage: 'Request body must contain a "keys" array' });
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
const validKeys = body.keys.filter(
|
|
27
|
+
(item) => item !== null && typeof item === "object" && typeof item.key === "string" && item.key.length > 0 && typeof item.value === "string" && item.value.length > 0
|
|
28
|
+
);
|
|
29
|
+
if (validKeys.length === 0) {
|
|
30
|
+
return { synced: 0 };
|
|
31
|
+
}
|
|
32
|
+
if (validKeys.length > 100) {
|
|
27
33
|
throw createError({ statusCode: 400, statusMessage: "Maximum 100 keys per request" });
|
|
28
34
|
}
|
|
29
35
|
const res = await fetch(
|
|
@@ -31,7 +37,7 @@ export default defineEventHandler(async (event) => {
|
|
|
31
37
|
{
|
|
32
38
|
method: "POST",
|
|
33
39
|
headers: { "Content-Type": "application/json" },
|
|
34
|
-
body: JSON.stringify({ apiKey, keys:
|
|
40
|
+
body: JSON.stringify({ apiKey, keys: validKeys })
|
|
35
41
|
}
|
|
36
42
|
);
|
|
37
43
|
const data = await res.json();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipkge/nuxt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"main": "./dist/module.mjs",
|
|
12
|
-
"files": [
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
13
15
|
"scripts": {
|
|
14
16
|
"build": "nuxt-module-build build",
|
|
15
17
|
"dev": "nuxt-module-build prepare",
|
|
@@ -17,21 +19,28 @@
|
|
|
17
19
|
"prepack": "nuxt-module-build build",
|
|
18
20
|
"test": "vitest run",
|
|
19
21
|
"test:watch": "vitest",
|
|
20
|
-
"test:coverage": "vitest run --coverage"
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"release": "vitest run && nuxt-module-build build && npm publish --access public"
|
|
21
24
|
},
|
|
22
25
|
"dependencies": {
|
|
23
26
|
"@nuxt/kit": "^3.0.0",
|
|
24
|
-
"@
|
|
27
|
+
"@uipkge/core": "workspace:*"
|
|
25
28
|
},
|
|
26
29
|
"peerDependencies": {
|
|
27
|
-
"vue-i18n": "^9.0.0 || ^10.0.0"
|
|
30
|
+
"vue-i18n": "^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
31
|
+
"@nuxtjs/i18n": "^9.0.0 || ^10.0.0"
|
|
28
32
|
},
|
|
29
33
|
"peerDependenciesMeta": {
|
|
30
|
-
"vue-i18n": {
|
|
34
|
+
"vue-i18n": {
|
|
35
|
+
"optional": false
|
|
36
|
+
},
|
|
37
|
+
"@nuxtjs/i18n": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
31
40
|
},
|
|
32
41
|
"devDependencies": {
|
|
33
42
|
"@nuxt/module-builder": "^1.0.0",
|
|
34
|
-
|
|
43
|
+
"happy-dom": "^17.0.0",
|
|
35
44
|
"nuxt": "^3.0.0",
|
|
36
45
|
"vitest": "^4.0.0"
|
|
37
46
|
}
|