@verbumia/react-i18next 0.8.0 → 0.9.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 +64 -0
- package/dist/index.cjs +53 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -33
- package/dist/index.d.ts +23 -33
- package/dist/index.js +53 -195
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,10 @@ interface VerbumiaConfig {
|
|
|
66
66
|
defaultNS?: string; // alias: default namespace for single-ns apps
|
|
67
67
|
apiBase?: string; // default 'https://api.verbumia.dev'
|
|
68
68
|
cdnBase?: string; // default 'https://cdn.verbumia.ca'
|
|
69
|
+
version?: string; // version slug, default 'main' (in cache keys)
|
|
70
|
+
versionSlug?: string; // @deprecated alias of `version`
|
|
71
|
+
env?: 'prod' | 'dev'; // default 'prod' (drives fetch source)
|
|
72
|
+
plugins?: VerbumiaPlugin[]; // e.g. @verbumia/feedback, @verbumia/realtime
|
|
69
73
|
transport?: (batch: MissingKeyEvent[]) => void | Promise<void>;
|
|
70
74
|
missingHandler?: 'send' | 'log' | 'off'; // default 'send'
|
|
71
75
|
flushIntervalMs?: number; // default 5000
|
|
@@ -74,6 +78,19 @@ interface VerbumiaConfig {
|
|
|
74
78
|
}
|
|
75
79
|
```
|
|
76
80
|
|
|
81
|
+
`version` selects which published version's bundles to load
|
|
82
|
+
(`/p/<project>/<version>/latest/...`); it defaults to `'main'` and is part
|
|
83
|
+
of the SDK's bundle cache keys, so two providers with different `version`
|
|
84
|
+
values never share cached bundles. `versionSlug` is a **deprecated** alias
|
|
85
|
+
of `version` (if both are set, `version` wins).
|
|
86
|
+
|
|
87
|
+
> **Removed in 0.9.0:** the `liveUpdates` / `centrifugoWsUrl` /
|
|
88
|
+
> `centrifugoTokenEndpoint` config keys. Realtime updates now live in the
|
|
89
|
+
> separate [`@verbumia/realtime`](../realtime) plugin (see
|
|
90
|
+
> [Realtime updates](#realtime-updates)). Passing any of those keys throws
|
|
91
|
+
> a clear migration error.
|
|
92
|
+
```
|
|
93
|
+
|
|
77
94
|
### `useTranslation(defaultNamespace?)`
|
|
78
95
|
|
|
79
96
|
Returns `{ t, i18n }`.
|
|
@@ -95,9 +112,24 @@ interface I18nInstance {
|
|
|
95
112
|
t: TranslationFunction; // for out-of-React use via getI18n()
|
|
96
113
|
missingEvents: MissingKeyEvent[]; // newest first, capped buffer
|
|
97
114
|
flushMissing(): Promise<void>; // force-flush the pending batch
|
|
115
|
+
reload(opts?: { locale?: string; namespace?: string }): Promise<void>;
|
|
98
116
|
}
|
|
99
117
|
```
|
|
100
118
|
|
|
119
|
+
### `i18n.reload(opts?)`
|
|
120
|
+
|
|
121
|
+
Bust-refetches already-loaded bundles (bypassing the browser HTTP cache)
|
|
122
|
+
and re-renders. Without `opts` it refreshes every loaded `(locale, ns)`
|
|
123
|
+
bundle; pass `{ locale }` and/or `{ namespace }` to narrow. Returns once
|
|
124
|
+
all refetches settle. Useful for a manual "refresh translations" button,
|
|
125
|
+
and it's what [`@verbumia/realtime`](#realtime-updates) calls on a
|
|
126
|
+
`translations_published` push.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
await getI18n().reload(); // refresh all
|
|
130
|
+
await getI18n().reload({ locale: "fr", namespace: "common" });
|
|
131
|
+
```
|
|
132
|
+
|
|
101
133
|
### `<Trans>`
|
|
102
134
|
|
|
103
135
|
Inline translation with JSX slots:
|
|
@@ -212,6 +244,38 @@ import { defaultTransport, logTransport } from "@verbumia/react-i18next";
|
|
|
212
244
|
|
|
213
245
|
---
|
|
214
246
|
|
|
247
|
+
## Realtime updates
|
|
248
|
+
|
|
249
|
+
Zero-deploy translation updates (subscribe to the project's Centrifugo
|
|
250
|
+
`translations:` channel and bust-refetch on publish) live in the separate
|
|
251
|
+
[`@verbumia/realtime`](../realtime) package — added as a **plugin** of this
|
|
252
|
+
provider, not configured here:
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
import { VerbumiaProvider } from "@verbumia/react-i18next";
|
|
256
|
+
import { verbumiaRealtime } from "@verbumia/realtime/react";
|
|
257
|
+
|
|
258
|
+
<VerbumiaProvider
|
|
259
|
+
{...config}
|
|
260
|
+
env="dev"
|
|
261
|
+
plugins={[
|
|
262
|
+
verbumiaRealtime({ wsUrl: "wss://centrifugo.verbumia.ca/connection/websocket" }),
|
|
263
|
+
]}
|
|
264
|
+
>
|
|
265
|
+
<App />
|
|
266
|
+
</VerbumiaProvider>;
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Under the hood the plugin calls [`i18n.reload(...)`](#i18nreloadopts) on
|
|
270
|
+
each `translations_published` push. Realtime is a dev-version-only feature
|
|
271
|
+
(it only subscribes when `env: "dev"`).
|
|
272
|
+
|
|
273
|
+
> The `liveUpdates` / `centrifugoWsUrl` / `centrifugoTokenEndpoint` config
|
|
274
|
+
> keys were **removed in 0.9.0**. Install `@verbumia/realtime` and use the
|
|
275
|
+
> plugin instead.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
215
279
|
## Recipes
|
|
216
280
|
|
|
217
281
|
### Next.js (App Router)
|
package/dist/index.cjs
CHANGED
|
@@ -75,111 +75,6 @@ var logTransport = (batch) => {
|
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
// src/live.ts
|
|
79
|
-
var LiveClient = class {
|
|
80
|
-
constructor(cfg) {
|
|
81
|
-
this.cfg = cfg;
|
|
82
|
-
}
|
|
83
|
-
cfg;
|
|
84
|
-
_ws = null;
|
|
85
|
-
_id = 0;
|
|
86
|
-
_backoffMs = 1e3;
|
|
87
|
-
_disposed = false;
|
|
88
|
-
_connectAcked = false;
|
|
89
|
-
/** Open the socket and try to subscribe. Idempotent — calling twice is a no-op. */
|
|
90
|
-
async connect() {
|
|
91
|
-
if (this._ws) return;
|
|
92
|
-
if (this._disposed) return;
|
|
93
|
-
this.cfg.onStatus?.("connecting");
|
|
94
|
-
const token = this.cfg.refreshToken ? await this.cfg.refreshToken() : this.cfg.token;
|
|
95
|
-
let url = this.cfg.url;
|
|
96
|
-
if (!url.includes("/connection/websocket")) {
|
|
97
|
-
url = url.replace(/\/+$/, "") + "/connection/websocket";
|
|
98
|
-
}
|
|
99
|
-
const ws = new WebSocket(url);
|
|
100
|
-
this._ws = ws;
|
|
101
|
-
this._connectAcked = false;
|
|
102
|
-
ws.onopen = () => {
|
|
103
|
-
this._send({ id: ++this._id, connect: { token } });
|
|
104
|
-
};
|
|
105
|
-
ws.onmessage = (evt) => this._onFrame(evt.data);
|
|
106
|
-
ws.onclose = () => this._onClose();
|
|
107
|
-
ws.onerror = () => {
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
dispose() {
|
|
111
|
-
this._disposed = true;
|
|
112
|
-
if (this._ws) {
|
|
113
|
-
try {
|
|
114
|
-
this._ws.close();
|
|
115
|
-
} catch {
|
|
116
|
-
}
|
|
117
|
-
this._ws = null;
|
|
118
|
-
}
|
|
119
|
-
this.cfg.onStatus?.("disconnected");
|
|
120
|
-
}
|
|
121
|
-
// ---- internals ----
|
|
122
|
-
_send(msg) {
|
|
123
|
-
if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
|
|
124
|
-
try {
|
|
125
|
-
this._ws.send(JSON.stringify(msg));
|
|
126
|
-
} catch {
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
_onFrame(raw) {
|
|
130
|
-
let parsed;
|
|
131
|
-
try {
|
|
132
|
-
parsed = JSON.parse(raw);
|
|
133
|
-
} catch {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
if (!parsed || typeof parsed !== "object") return;
|
|
137
|
-
if (Object.keys(parsed).length === 0) {
|
|
138
|
-
this._send({});
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (parsed.connect && !this._connectAcked) {
|
|
142
|
-
this._connectAcked = true;
|
|
143
|
-
this.cfg.onStatus?.("connected");
|
|
144
|
-
this._backoffMs = 1e3;
|
|
145
|
-
this._send({
|
|
146
|
-
id: ++this._id,
|
|
147
|
-
subscribe: { channel: this.cfg.channel }
|
|
148
|
-
});
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const push = parsed.push;
|
|
152
|
-
if (push && push.channel === this.cfg.channel && push.pub) {
|
|
153
|
-
this.cfg.onMessage(push.pub.data);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
_onClose() {
|
|
157
|
-
this._ws = null;
|
|
158
|
-
this._connectAcked = false;
|
|
159
|
-
this.cfg.onStatus?.("disconnected");
|
|
160
|
-
if (this._disposed) return;
|
|
161
|
-
const delay = this._backoffMs;
|
|
162
|
-
this._backoffMs = Math.min(this._backoffMs * 2, 3e4);
|
|
163
|
-
setTimeout(() => {
|
|
164
|
-
if (!this._disposed) void this.connect();
|
|
165
|
-
}, delay);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
async function fetchCentrifugoToken(endpoint, projectUuid, authToken, fetchImpl = fetch) {
|
|
169
|
-
const r = await fetchImpl(endpoint, {
|
|
170
|
-
method: "POST",
|
|
171
|
-
headers: {
|
|
172
|
-
"Content-Type": "application/json",
|
|
173
|
-
Authorization: `ApiKey ${authToken}`
|
|
174
|
-
},
|
|
175
|
-
body: JSON.stringify({ project_uuid: projectUuid })
|
|
176
|
-
});
|
|
177
|
-
if (!r.ok) {
|
|
178
|
-
throw new Error(`centrifugo-token endpoint ${r.status}: ${await r.text()}`);
|
|
179
|
-
}
|
|
180
|
-
return await r.json();
|
|
181
|
-
}
|
|
182
|
-
|
|
183
78
|
// src/key-registry.ts
|
|
184
79
|
var GLOBAL = "__verbumia_key_registry__";
|
|
185
80
|
var SEP = "\0";
|
|
@@ -317,10 +212,10 @@ var VerbumiaI18n = class {
|
|
|
317
212
|
fallbackLng;
|
|
318
213
|
missingEvents = [];
|
|
319
214
|
_bundles = /* @__PURE__ */ new Map();
|
|
320
|
-
// `${locale}/${ns}` -> tree
|
|
215
|
+
// `${version}/${locale}/${ns}` -> tree
|
|
321
216
|
_attempted = /* @__PURE__ */ new Set();
|
|
322
|
-
// `${locale}/${ns}` keys we've fetched
|
|
323
|
-
// Tighter gate than `_attempted`: this set only contains (locale, ns)
|
|
217
|
+
// `${version}/${locale}/${ns}` keys we've fetched
|
|
218
|
+
// Tighter gate than `_attempted`: this set only contains (version, locale, ns)
|
|
324
219
|
// pairs whose CDN response was 200 with at least one top-level key. An
|
|
325
220
|
// empty bundle (404 → {} OR 200 → {}) is treated as "no data yet";
|
|
326
221
|
// calling t() against a key in such a bundle does NOT fire reportMissing.
|
|
@@ -335,13 +230,20 @@ var VerbumiaI18n = class {
|
|
|
335
230
|
// dedup `${locale}/${ns}/${key}` per-flush
|
|
336
231
|
_timer = null;
|
|
337
232
|
_listeners = /* @__PURE__ */ new Set();
|
|
338
|
-
_live = null;
|
|
339
233
|
// Stable snapshot reference for useSyncExternalStore. Returning a fresh
|
|
340
234
|
// object on each getSnapshot call would loop React forever — we rebuild
|
|
341
235
|
// it ONLY in _notify (when state actually changed) and return the cached
|
|
342
236
|
// reference between notifications.
|
|
343
237
|
_snapshot;
|
|
344
238
|
constructor(config) {
|
|
239
|
+
const removedRealtimeKeys = Object.keys(config).filter(
|
|
240
|
+
(k) => k === "liveUpdates" || k.startsWith("centrifugo")
|
|
241
|
+
);
|
|
242
|
+
if (removedRealtimeKeys.length > 0) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`@verbumia/react-i18next: ${removedRealtimeKeys.join(", ")} ${removedRealtimeKeys.length > 1 ? "were" : "was"} removed in 0.9.0 \u2014 realtime is now the @verbumia/realtime plugin. Remove them and pass \`plugins: [verbumiaRealtime({ wsUrl })]\` to <VerbumiaProvider> instead.`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
345
247
|
this.locale = config.defaultLocale;
|
|
346
248
|
this.fallbackLng = config.fallbackLng;
|
|
347
249
|
this._config = {
|
|
@@ -354,10 +256,7 @@ var VerbumiaI18n = class {
|
|
|
354
256
|
flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_MS,
|
|
355
257
|
flushBatchSize: config.flushBatchSize ?? DEFAULT_BATCH,
|
|
356
258
|
missingEventsBufferSize: config.missingEventsBufferSize ?? DEFAULT_BUFFER,
|
|
357
|
-
|
|
358
|
-
liveUpdates: !!config.liveUpdates,
|
|
359
|
-
centrifugoTokenEndpoint: config.centrifugoTokenEndpoint ?? `${(config.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, "")}/v1/auth/centrifugo-token`,
|
|
360
|
-
centrifugoWsUrl: config.centrifugoWsUrl ?? "",
|
|
259
|
+
version: config.version ?? config.versionSlug ?? DEFAULT_VERSION_SLUG,
|
|
361
260
|
env: config.env ?? "prod"
|
|
362
261
|
};
|
|
363
262
|
this._transport = config.transport ?? (this._config.missingHandler === "log" ? logTransport : defaultTransport({
|
|
@@ -384,9 +283,16 @@ var VerbumiaI18n = class {
|
|
|
384
283
|
changeLanguage: this.changeLanguage,
|
|
385
284
|
t: this.t,
|
|
386
285
|
missingEvents: this.missingEvents,
|
|
387
|
-
flushMissing: this.flushMissing
|
|
286
|
+
flushMissing: this.flushMissing,
|
|
287
|
+
reload: this.reload
|
|
388
288
|
};
|
|
389
289
|
}
|
|
290
|
+
/** Bundle cache-key builder. Includes `version` so providers with
|
|
291
|
+
* different `version` values never share cached bundles. The segments
|
|
292
|
+
* (version/locale/ns) are slugs and never contain '/'. */
|
|
293
|
+
_bundleKey(locale, ns) {
|
|
294
|
+
return `${this._config.version}/${locale}/${ns}`;
|
|
295
|
+
}
|
|
390
296
|
_notify() {
|
|
391
297
|
this._snapshot = this._buildSnapshot();
|
|
392
298
|
for (const l of this._listeners) l();
|
|
@@ -409,9 +315,6 @@ var VerbumiaI18n = class {
|
|
|
409
315
|
);
|
|
410
316
|
this.ready = true;
|
|
411
317
|
this._startTimer();
|
|
412
|
-
if (this._config.liveUpdates && this._config.env === "dev") {
|
|
413
|
-
this._startLive(fetchImpl);
|
|
414
|
-
}
|
|
415
318
|
this._notify();
|
|
416
319
|
}
|
|
417
320
|
setLocale = async (next) => {
|
|
@@ -437,97 +340,52 @@ var VerbumiaI18n = class {
|
|
|
437
340
|
clearInterval(this._timer);
|
|
438
341
|
this._timer = null;
|
|
439
342
|
}
|
|
440
|
-
if (this._live) {
|
|
441
|
-
this._live.dispose();
|
|
442
|
-
this._live = null;
|
|
443
|
-
}
|
|
444
343
|
}
|
|
445
344
|
/**
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
345
|
+
* Bust-refetch already-loaded bundles and re-render once. Generic
|
|
346
|
+
* replacement for the old realtime-only refetch: iterate the
|
|
347
|
+
* `_attempted` cache keys (`${version}/${locale}/${ns}`), optionally
|
|
348
|
+
* filtered by `opts.locale` / `opts.namespace`, and re-pull each one
|
|
349
|
+
* with `{ bust: true }` so the mutable CDN `latest/` alias bypasses the
|
|
350
|
+
* HTTP cache. After all settle, `_notify()` once so React re-renders.
|
|
351
|
+
*
|
|
352
|
+
* Used by `@verbumia/realtime` on a `translations_published` push and as
|
|
353
|
+
* a manual refresh hook. If nothing matches, returns without notifying.
|
|
450
354
|
*/
|
|
451
|
-
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
355
|
+
reload = async (opts = {}) => {
|
|
356
|
+
const targets = [];
|
|
357
|
+
for (const key of this._attempted) {
|
|
358
|
+
const parts = key.split("/");
|
|
359
|
+
const locale = parts[1];
|
|
360
|
+
const ns = parts[2];
|
|
361
|
+
if (!locale || !ns) continue;
|
|
362
|
+
if (opts.locale && opts.locale !== locale) continue;
|
|
363
|
+
if (opts.namespace && opts.namespace !== ns) continue;
|
|
364
|
+
targets.push({ locale, ns });
|
|
460
365
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
projectUuid,
|
|
468
|
-
apiToken,
|
|
469
|
-
fetchImpl
|
|
470
|
-
);
|
|
471
|
-
return token;
|
|
472
|
-
};
|
|
473
|
-
void (async () => {
|
|
474
|
-
let channel;
|
|
475
|
-
let token;
|
|
476
|
-
try {
|
|
477
|
-
const minted = await fetchCentrifugoToken(
|
|
478
|
-
tokenEndpoint,
|
|
479
|
-
projectUuid,
|
|
480
|
-
apiToken,
|
|
481
|
-
fetchImpl
|
|
482
|
-
);
|
|
483
|
-
channel = minted.channel;
|
|
484
|
-
token = minted.token;
|
|
485
|
-
} catch (err) {
|
|
486
|
-
if (typeof console !== "undefined") {
|
|
487
|
-
console.warn("@verbumia/react-i18next: live token mint failed", err);
|
|
488
|
-
}
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
this._live = new LiveClient({
|
|
492
|
-
url: wsUrl,
|
|
493
|
-
token,
|
|
494
|
-
channel,
|
|
495
|
-
refreshToken,
|
|
496
|
-
onMessage: (data) => this._onLiveMessage(data, fetchImpl)
|
|
497
|
-
});
|
|
498
|
-
void this._live.connect();
|
|
499
|
-
})();
|
|
500
|
-
}
|
|
501
|
-
_onLiveMessage(data, fetchImpl) {
|
|
502
|
-
if (!data || typeof data !== "object") return;
|
|
503
|
-
const d = data;
|
|
504
|
-
if (d.event !== "translations_published") return;
|
|
505
|
-
const lang = d.language_code;
|
|
506
|
-
const ns = d.namespace_slug;
|
|
507
|
-
if (!lang || !ns) return;
|
|
508
|
-
const cacheKey = `${lang}/${ns}`;
|
|
509
|
-
if (!this._attempted.has(cacheKey)) return;
|
|
510
|
-
void this._loadBundle(lang, ns, fetchImpl, { bust: true }).then(() => {
|
|
511
|
-
this._notify();
|
|
512
|
-
});
|
|
513
|
-
}
|
|
366
|
+
if (targets.length === 0) return;
|
|
367
|
+
await Promise.all(
|
|
368
|
+
targets.map((t) => this._loadBundle(t.locale, t.ns, fetch, { bust: true }))
|
|
369
|
+
);
|
|
370
|
+
this._notify();
|
|
371
|
+
};
|
|
514
372
|
// ---- Translation ----
|
|
515
373
|
t = (key, optionsOrDefault, maybeOptions) => {
|
|
516
374
|
const options = typeof optionsOrDefault === "string" ? { ...maybeOptions ?? {}, defaultValue: optionsOrDefault } : optionsOrDefault;
|
|
517
375
|
const namespace = this._splitNamespace(key);
|
|
518
376
|
const bareKey = namespace.bareKey;
|
|
519
377
|
const ns = namespace.ns;
|
|
520
|
-
const fromActive = resolve(this._bundles.get(
|
|
378
|
+
const fromActive = resolve(this._bundles.get(this._bundleKey(this.locale, ns)), bareKey);
|
|
521
379
|
if (fromActive != null) {
|
|
522
380
|
return this._render(fromActive, this.locale, options);
|
|
523
381
|
}
|
|
524
382
|
if (this.fallbackLng && this.fallbackLng !== this.locale) {
|
|
525
|
-
const fb = resolve(this._bundles.get(
|
|
383
|
+
const fb = resolve(this._bundles.get(this._bundleKey(this.fallbackLng, ns)), bareKey);
|
|
526
384
|
if (fb != null) {
|
|
527
385
|
return this._render(fb, this.fallbackLng, options);
|
|
528
386
|
}
|
|
529
387
|
}
|
|
530
|
-
if (this.ready && this._attempted.has(
|
|
388
|
+
if (this.ready && this._attempted.has(this._bundleKey(this.locale, ns)) && this._hasContent.has(this._bundleKey(this.locale, ns))) {
|
|
531
389
|
this._reportMissing({
|
|
532
390
|
key: bareKey,
|
|
533
391
|
namespace: ns,
|
|
@@ -574,13 +432,13 @@ var VerbumiaI18n = class {
|
|
|
574
432
|
return { ns: this._config.namespaces[0], bareKey: key };
|
|
575
433
|
}
|
|
576
434
|
async _loadBundle(locale, ns, fetchImpl = fetch, opts = {}) {
|
|
577
|
-
const cacheKey =
|
|
435
|
+
const cacheKey = this._bundleKey(locale, ns);
|
|
578
436
|
let url;
|
|
579
437
|
let init;
|
|
580
438
|
if (this._config.env === "dev") {
|
|
581
439
|
const params = new URLSearchParams({ language: locale, namespace: ns });
|
|
582
|
-
if (this._config.
|
|
583
|
-
params.set("version_slug", this._config.
|
|
440
|
+
if (this._config.version && this._config.version !== "main") {
|
|
441
|
+
params.set("version_slug", this._config.version);
|
|
584
442
|
}
|
|
585
443
|
url = `${this._config.apiBase.replace(/\/+$/, "")}/v1/projects/${this._config.projectUuid}/translations/runtime?${params.toString()}`;
|
|
586
444
|
init = {
|
|
@@ -589,7 +447,7 @@ var VerbumiaI18n = class {
|
|
|
589
447
|
credentials: "omit"
|
|
590
448
|
};
|
|
591
449
|
} else {
|
|
592
|
-
url = `${this._config.cdnBase.replace(/\/+$/, "")}/p/${this._config.projectUuid}/${this._config.
|
|
450
|
+
url = `${this._config.cdnBase.replace(/\/+$/, "")}/p/${this._config.projectUuid}/${this._config.version}/latest/${locale}/${ns}.json`;
|
|
593
451
|
init = { method: "GET", credentials: "omit" };
|
|
594
452
|
}
|
|
595
453
|
if (opts.bust) {
|
|
@@ -644,7 +502,7 @@ var VerbumiaI18n = class {
|
|
|
644
502
|
return options.defaultValue;
|
|
645
503
|
}
|
|
646
504
|
if (this.fallbackLng && this.fallbackLng !== this.locale) {
|
|
647
|
-
const fb = resolve(this._bundles.get(
|
|
505
|
+
const fb = resolve(this._bundles.get(this._bundleKey(this.fallbackLng, ns)), bareKey);
|
|
648
506
|
if (typeof fb === "string") {
|
|
649
507
|
return fb;
|
|
650
508
|
}
|