@uipkge/nuxt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/dist/module.d.mts +22 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +75 -0
- package/dist/runtime/composables/useI18n.d.ts +11 -0
- package/dist/runtime/composables/useI18n.js +24 -0
- package/dist/runtime/plugin.client.d.ts +9 -0
- package/dist/runtime/plugin.client.js +93 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.js +23 -0
- package/dist/runtime/plugin.suppress-warnings.d.ts +12 -0
- package/dist/runtime/plugin.suppress-warnings.js +11 -0
- package/dist/runtime/server/sync.d.ts +2 -0
- package/dist/runtime/server/sync.js +42 -0
- package/dist/types.d.mts +3 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @uipkge/nuxt
|
|
2
|
+
|
|
3
|
+
Nuxt module for [i18now](https://i18now.com) — loads translations from the i18now CDN and automatically syncs new keys to your project during development.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Loads published translations from the i18now CDN at SSR time
|
|
8
|
+
- Reloads translations on locale switch (client-side)
|
|
9
|
+
- In development: detects missing translation keys and syncs them to i18now automatically
|
|
10
|
+
- Suppresses vue-i18n missing-key warnings in dev — missing keys are expected until published
|
|
11
|
+
- API key is never exposed to the browser (proxied through a local Nuxt server route)
|
|
12
|
+
- Zero bundle impact in production — dev-only code is tree-shaken at build time
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @uipkge/nuxt
|
|
18
|
+
# or
|
|
19
|
+
pnpm add @uipkge/nuxt
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`@nuxtjs/i18n` is a dependency and will be installed automatically.
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
Add the module to `nuxt.config.ts`:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
export default defineNuxtConfig({
|
|
30
|
+
modules: ['@uipkge/nuxt'],
|
|
31
|
+
|
|
32
|
+
i18now: {
|
|
33
|
+
projectId: process.env.I18NOW_PROJECT_ID ?? '',
|
|
34
|
+
apiKey: process.env.I18NOW_API_KEY ?? '',
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
i18n: {
|
|
38
|
+
defaultLocale: 'en',
|
|
39
|
+
locales: ['en', 'es', 'fr'],
|
|
40
|
+
bundle: {
|
|
41
|
+
optimizeTranslationDirective: false, // buggy, deprecated in v10
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
| Option | Type | Default | Description |
|
|
50
|
+
|---------------|------------|----------------------------|-----------------------------------------------------------------------------|
|
|
51
|
+
| `projectId` | `string` | — | **Required.** Your i18now project ID. |
|
|
52
|
+
| `apiKey` | `string` | — | **Required.** Your i18now API key. Never sent to the browser. |
|
|
53
|
+
| `host` | `string` | `https://i18now.com` | i18now server URL. Set to `http://localhost:3220` for local development. |
|
|
54
|
+
| `cdnUrl` | `string` | `https://cdn.i18now.com` | CDN base URL where published translations are served. |
|
|
55
|
+
| `environment` | `string` | `'dev'` | Which published environment to load translations from (`dev`, `stage`, `prod`). |
|
|
56
|
+
| `syncIn` | `string[]` | `['development']` | Node environments where missing-key sync is active. Keep as `['development']`. |
|
|
57
|
+
| `locale` | `string` | `'en'` | Fallback locale used before `@nuxtjs/i18n` initialises. |
|
|
58
|
+
|
|
59
|
+
### Environment variables
|
|
60
|
+
|
|
61
|
+
Store secrets in `.env`:
|
|
62
|
+
|
|
63
|
+
```env
|
|
64
|
+
I18NOW_PROJECT_ID=your-project-id
|
|
65
|
+
I18NOW_API_KEY=your-api-key
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
### Production
|
|
71
|
+
|
|
72
|
+
The server plugin fetches the published translation JSON from the CDN (`{cdnUrl}/{projectId}/publish/{environment}/{locale}.json`) during SSR and injects it into vue-i18n. Translations are refreshed on locale switch.
|
|
73
|
+
|
|
74
|
+
### Development
|
|
75
|
+
|
|
76
|
+
The client plugin intercepts every `$t()` and `useI18n().t()` call. When a key is missing from the CDN snapshot it batches that key (with its default value) and POSTs it to a local Nuxt server route (`/api/_i18now/sync`). The server route adds the API key and forwards the request to i18now — so your API key never appears in browser network requests.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Browser $t('key', 'Default text')
|
|
80
|
+
→ client plugin detects missing key
|
|
81
|
+
→ POST /api/_i18now/sync { keys: [{ key, value }] }
|
|
82
|
+
→ Nuxt server adds apiKey
|
|
83
|
+
→ POST i18now /api/v1/projects/:id/sync
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The `useI18n` composable is auto-imported by the module and silently overrides the one from `@nuxtjs/i18n` — no changes to existing component code are required.
|
|
87
|
+
|
|
88
|
+
Vue-i18n's missing-key and fallback warnings are suppressed automatically in dev. Keys are expected to be absent until they are published from the i18now dashboard, at which point they load from CDN and no longer trigger warnings.
|
|
89
|
+
|
|
90
|
+
## Security
|
|
91
|
+
|
|
92
|
+
- `apiKey` is stored in Nuxt's private (server-only) `runtimeConfig` — it is never serialised into the client bundle or sent in any browser request.
|
|
93
|
+
- The `syncIn` option defaults to `['development']`. **Never add `'production'` or `'staging'`** — doing so would expose your API key to end users and flood your project with traffic.
|
|
94
|
+
|
|
95
|
+
## Local development against a local i18now server
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// nuxt.config.ts
|
|
99
|
+
i18now: {
|
|
100
|
+
projectId: 'your-project-id',
|
|
101
|
+
apiKey: 'your-api-key',
|
|
102
|
+
host: 'http://localhost:3220',
|
|
103
|
+
cdnUrl: 'http://localhost:3220', // if serving snapshots locally
|
|
104
|
+
environment: 'dev',
|
|
105
|
+
},
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Playground
|
|
109
|
+
|
|
110
|
+
A minimal playground is included in `playground/`. To run it:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pnpm dev:playground
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Building locally
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pnpm build
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
To use the built package in another local project, add a `file:` reference:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"dependencies": {
|
|
127
|
+
"@uipkge/nuxt": "file:../path/to/i18now/packages/nuxt"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as nuxt_schema from 'nuxt/schema';
|
|
2
|
+
|
|
3
|
+
interface ModuleOptions {
|
|
4
|
+
projectId: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
host?: string;
|
|
7
|
+
/** CDN base URL where published translations are served. Default: https://cdn.i18now.com */
|
|
8
|
+
cdnUrl?: string;
|
|
9
|
+
/** Which published environment to load translations from. Default: 'dev' */
|
|
10
|
+
environment?: string;
|
|
11
|
+
/** Environments where key sync is active. Default: ['development'] */
|
|
12
|
+
syncIn?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Fallback locale to load from CDN when @nuxtjs/i18n has not yet initialised.
|
|
15
|
+
* Default: 'en'
|
|
16
|
+
*/
|
|
17
|
+
locale?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const _default: nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
20
|
+
|
|
21
|
+
export { _default as default };
|
|
22
|
+
export type { ModuleOptions };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addPlugin, addServerHandler, addImports } from '@nuxt/kit';
|
|
2
|
+
|
|
3
|
+
const module$1 = defineNuxtModule({
|
|
4
|
+
meta: {
|
|
5
|
+
name: "@uipkge/nuxt",
|
|
6
|
+
configKey: "i18now",
|
|
7
|
+
compatibility: { nuxt: ">=3.0.0" }
|
|
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
|
+
defaults: {
|
|
15
|
+
host: "https://i18now.com",
|
|
16
|
+
cdnUrl: "https://cdn.i18now.com",
|
|
17
|
+
environment: "dev",
|
|
18
|
+
syncIn: ["development"],
|
|
19
|
+
locale: "en"
|
|
20
|
+
},
|
|
21
|
+
async setup(options, nuxt) {
|
|
22
|
+
const resolver = createResolver(import.meta.url);
|
|
23
|
+
if (!options.projectId) {
|
|
24
|
+
console.warn("[i18now] projectId is required. The module will not work until it is set.");
|
|
25
|
+
}
|
|
26
|
+
if (!options.apiKey) {
|
|
27
|
+
console.warn("[i18now] apiKey is required. The module will not work until it is set.");
|
|
28
|
+
}
|
|
29
|
+
const dangerousEnvs = (options.syncIn ?? []).filter(
|
|
30
|
+
(e) => ["production", "staging", "prod", "stage"].includes(e)
|
|
31
|
+
);
|
|
32
|
+
if (dangerousEnvs.length > 0) {
|
|
33
|
+
console.error(
|
|
34
|
+
`[i18now] DANGER: syncIn includes "${dangerousEnvs.join('", "')}" \u2014 key sync is enabled in a production-like environment. This will expose your API key to users and flood i18now with production traffic. syncIn should only contain "development".`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
nuxt.options.runtimeConfig.i18now = {
|
|
38
|
+
apiKey: options.apiKey
|
|
39
|
+
};
|
|
40
|
+
nuxt.options.runtimeConfig.public.i18now = {
|
|
41
|
+
projectId: options.projectId,
|
|
42
|
+
host: options.host,
|
|
43
|
+
cdnUrl: options.cdnUrl,
|
|
44
|
+
environment: options.environment,
|
|
45
|
+
syncIn: options.syncIn,
|
|
46
|
+
locale: options.locale
|
|
47
|
+
};
|
|
48
|
+
addPlugin({
|
|
49
|
+
src: resolver.resolve("./runtime/plugin"),
|
|
50
|
+
mode: "server"
|
|
51
|
+
});
|
|
52
|
+
if (nuxt.options.dev) {
|
|
53
|
+
addServerHandler({
|
|
54
|
+
route: "/api/_i18now/sync",
|
|
55
|
+
handler: resolver.resolve("./runtime/server/sync"),
|
|
56
|
+
method: "post"
|
|
57
|
+
});
|
|
58
|
+
addPlugin({
|
|
59
|
+
src: resolver.resolve("./runtime/plugin.client"),
|
|
60
|
+
mode: "client"
|
|
61
|
+
});
|
|
62
|
+
addPlugin({
|
|
63
|
+
src: resolver.resolve("./runtime/plugin.suppress-warnings")
|
|
64
|
+
});
|
|
65
|
+
addImports({
|
|
66
|
+
name: "useI18n",
|
|
67
|
+
as: "useI18n",
|
|
68
|
+
from: resolver.resolve("./runtime/composables/useI18n"),
|
|
69
|
+
priority: 2
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useI18n as _useI18n } from 'vue-i18n';
|
|
2
|
+
type UseI18nOptions = Parameters<typeof _useI18n>[0];
|
|
3
|
+
type UseI18nReturn = ReturnType<typeof _useI18n>;
|
|
4
|
+
/**
|
|
5
|
+
* Drop-in replacement for useI18n() that wraps t() to sync missing keys to i18now.
|
|
6
|
+
* Auto-imported by the @uipkge/nuxt module — no code changes required.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useI18n(options?: UseI18nOptions): UseI18nReturn & {
|
|
9
|
+
te: (key: string, locale?: string) => boolean;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useNuxtApp } from "#app";
|
|
2
|
+
import { useI18n as _useI18n } from "vue-i18n";
|
|
3
|
+
export function useI18n(options) {
|
|
4
|
+
const i18n = _useI18n({ useScope: "global", ...options });
|
|
5
|
+
const nuxtApp = useNuxtApp();
|
|
6
|
+
const originalT = i18n.t.bind(i18n);
|
|
7
|
+
const patchedT = (key, defaultValue, ...rest) => {
|
|
8
|
+
const result = originalT(key, defaultValue, ...rest);
|
|
9
|
+
const sync = nuxtApp.$i18nowSync;
|
|
10
|
+
if (sync && !sync.existingKeys.has(key) && typeof defaultValue === "string") {
|
|
11
|
+
sync.syncKey(key, defaultValue);
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
};
|
|
15
|
+
const te = (key, _locale) => {
|
|
16
|
+
const result = originalT(key);
|
|
17
|
+
return typeof result === "string" && result !== key;
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
...i18n,
|
|
21
|
+
te,
|
|
22
|
+
t: patchedT
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
|
|
2
|
+
const MAX_TRACKED = 2e3;
|
|
3
|
+
const MAX_RETRIES = 3;
|
|
4
|
+
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
5
|
+
const { projectId, cdnUrl, environment, syncIn, locale: configLocale } = useRuntimeConfig().public.i18now;
|
|
6
|
+
if (!syncIn.includes(process.env.NODE_ENV ?? "development")) return;
|
|
7
|
+
if (!projectId) {
|
|
8
|
+
console.warn("[i18now] projectId is not set \u2014 key sync disabled.");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const i18nGlobal = nuxtApp.$i18n ?? nuxtApp.vueApp.config.globalProperties.$i18n;
|
|
12
|
+
const existingKeys = /* @__PURE__ */ new Set();
|
|
13
|
+
const locale = i18nGlobal?.locale?.value ?? i18nGlobal?.locale ?? configLocale;
|
|
14
|
+
async function loadLocale(targetLocale) {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(`${cdnUrl}/${projectId}/publish/${environment}/${targetLocale}.json`);
|
|
17
|
+
if (res.ok) {
|
|
18
|
+
const data = await res.json();
|
|
19
|
+
existingKeys.clear();
|
|
20
|
+
for (const key of Object.keys(data)) existingKeys.add(key);
|
|
21
|
+
i18nGlobal?.setLocaleMessage(targetLocale, data);
|
|
22
|
+
} else if (process.env.NODE_ENV === "development") {
|
|
23
|
+
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: ${cdnUrl}/${projectId}/publish/${environment}/${targetLocale}.json`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (process.env.NODE_ENV === "development") {
|
|
29
|
+
console.warn(`[i18now] CDN fetch failed for locale '${targetLocale}':`, err);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
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
|
+
const originalGlobalT = nuxtApp.vueApp.config.globalProperties.$t;
|
|
76
|
+
if (originalGlobalT) {
|
|
77
|
+
nuxtApp.vueApp.config.globalProperties.$t = function(key, defaultValue, ...rest) {
|
|
78
|
+
const result = originalGlobalT.call(this, key, defaultValue, ...rest);
|
|
79
|
+
if (!existingKeys.has(key) && typeof defaultValue === "string") {
|
|
80
|
+
syncKey(key, defaultValue);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
nuxtApp.hook("i18n:beforeLocaleSwitch", async ({ newLocale }) => {
|
|
86
|
+
await loadLocale(newLocale);
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
provide: {
|
|
90
|
+
i18nowSync: { syncKey, existingKeys }
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
|
|
2
|
+
export default defineNuxtPlugin(async (nuxtApp) => {
|
|
3
|
+
const { projectId, cdnUrl, environment, locale: configLocale } = useRuntimeConfig().public.i18now;
|
|
4
|
+
const i18n = nuxtApp.$i18n ?? nuxtApp.vueApp.config.globalProperties.$i18n;
|
|
5
|
+
async function loadLocale(locale) {
|
|
6
|
+
try {
|
|
7
|
+
const url = `${cdnUrl}/${projectId}/publish/${environment}/${locale}.json`;
|
|
8
|
+
const res = await fetch(url);
|
|
9
|
+
if (!res.ok) return;
|
|
10
|
+
const messages = await res.json();
|
|
11
|
+
if (i18n) i18n.setLocaleMessage(locale, messages);
|
|
12
|
+
} catch (err) {
|
|
13
|
+
if (process.env.NODE_ENV === "development") {
|
|
14
|
+
console.warn(`[i18now] Failed to load locale '${locale}' from CDN:`, err);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const currentLocale = i18n?.locale?.value ?? i18n?.locale ?? configLocale;
|
|
19
|
+
await loadLocale(currentLocale);
|
|
20
|
+
nuxtApp.hook("i18n:beforeLocaleSwitch", async ({ newLocale }) => {
|
|
21
|
+
await loadLocale(newLocale);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-only: suppress vue-i18n "Not found key" and fallback warnings.
|
|
3
|
+
* In i18now's workflow, missing keys are expected — they haven't been published
|
|
4
|
+
* to the CDN yet. The sync plugin handles them automatically, so the warnings
|
|
5
|
+
* are noise rather than actionable signal.
|
|
6
|
+
*
|
|
7
|
+
* Note: In @nuxtjs/i18n v9, nuxtApp.$i18n is the NuxtI18n object (with setLocale,
|
|
8
|
+
* locales, etc.) — it has no .global property. The real vue-i18n I18n instance
|
|
9
|
+
* lives under Symbol('vue-i18n') in the Vue app's provides.
|
|
10
|
+
*/
|
|
11
|
+
declare const _default: (nuxtApp: unknown) => void;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from "#app";
|
|
2
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
3
|
+
const provides = nuxtApp.vueApp._context?.provides ?? {};
|
|
4
|
+
const i18nSym = Object.getOwnPropertySymbols(provides).find((s) => s.toString() === "Symbol(vue-i18n)");
|
|
5
|
+
const i18nInstance = i18nSym ? provides[i18nSym] : void 0;
|
|
6
|
+
const global = i18nInstance?.global;
|
|
7
|
+
if (global) {
|
|
8
|
+
global.missingWarn = false;
|
|
9
|
+
global.fallbackWarn = false;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createError, defineEventHandler, readBody } from "h3";
|
|
2
|
+
export default defineEventHandler(async (event) => {
|
|
3
|
+
const config = useRuntimeConfig(event);
|
|
4
|
+
const publicConfig = config.public?.i18now;
|
|
5
|
+
const env = process.env.NODE_ENV ?? "development";
|
|
6
|
+
if (!publicConfig?.syncIn?.includes(env)) {
|
|
7
|
+
throw createError({ statusCode: 403, statusMessage: "Key sync is disabled in this environment" });
|
|
8
|
+
}
|
|
9
|
+
if (!publicConfig.projectId || !publicConfig.host) {
|
|
10
|
+
throw createError({ statusCode: 500, statusMessage: "i18now module is not configured" });
|
|
11
|
+
}
|
|
12
|
+
const privateConfig = config.i18now;
|
|
13
|
+
const apiKey = privateConfig?.apiKey;
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
throw createError({ statusCode: 500, statusMessage: "i18now apiKey is not configured" });
|
|
16
|
+
}
|
|
17
|
+
let body;
|
|
18
|
+
try {
|
|
19
|
+
body = await readBody(event);
|
|
20
|
+
} catch {
|
|
21
|
+
throw createError({ statusCode: 400, statusMessage: "Invalid request body" });
|
|
22
|
+
}
|
|
23
|
+
if (!Array.isArray(body?.keys)) {
|
|
24
|
+
throw createError({ statusCode: 400, statusMessage: 'Request body must contain a "keys" array' });
|
|
25
|
+
}
|
|
26
|
+
if (body.keys.length > 100) {
|
|
27
|
+
throw createError({ statusCode: 400, statusMessage: "Maximum 100 keys per request" });
|
|
28
|
+
}
|
|
29
|
+
const res = await fetch(
|
|
30
|
+
`${publicConfig.host}/api/v1/projects/${publicConfig.projectId}/sync`,
|
|
31
|
+
{
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "Content-Type": "application/json" },
|
|
34
|
+
body: JSON.stringify({ apiKey, keys: body.keys })
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
throw createError({ statusCode: res.status, data });
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
});
|
package/dist/types.d.mts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uipkge/nuxt",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/module.mjs"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/module.mjs",
|
|
12
|
+
"files": ["dist"],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "nuxt-module-build build",
|
|
15
|
+
"dev": "nuxt-module-build prepare",
|
|
16
|
+
"dev:playground": "nuxi dev playground",
|
|
17
|
+
"prepack": "nuxt-module-build build",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"test:coverage": "vitest run --coverage"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@nuxt/kit": "^3.0.0",
|
|
24
|
+
"@nuxtjs/i18n": "^9.0.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"vue-i18n": "^9.0.0 || ^10.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"vue-i18n": { "optional": true }
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@nuxt/module-builder": "^1.0.0",
|
|
34
|
+
"happy-dom": "^17.0.0",
|
|
35
|
+
"nuxt": "^3.0.0",
|
|
36
|
+
"vitest": "^4.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|