@sonenta/react-i18next 2.2.0 → 2.3.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 +35 -0
- package/dist/index.cjs +91 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +90 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -540,6 +540,41 @@ Resolution is locale-outer / surface-inner (same chain as `t()`): `(fr-CA,
|
|
|
540
540
|
aria_label) > (fr-CA, base) > (fr, aria_label) > (fr, base)`. Overlays are
|
|
541
541
|
CDN-only (`env: "dev"` is base-only).
|
|
542
542
|
|
|
543
|
+
## Available languages (runtime switcher)
|
|
544
|
+
|
|
545
|
+
List the languages **published for the active version** at runtime — so adding
|
|
546
|
+
a language in Sonenta makes it appear in your switcher **without recompiling
|
|
547
|
+
the app**. The set comes from a per-version CDN manifest (public, cacheable,
|
|
548
|
+
no auth); each code is enriched from the language catalog (`native_name`,
|
|
549
|
+
`rtl`, …).
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
import { useTranslation, useAvailableLanguages } from "@sonenta/react-i18next";
|
|
553
|
+
|
|
554
|
+
function LanguageSwitcher() {
|
|
555
|
+
const { i18n } = useTranslation();
|
|
556
|
+
const languages = useAvailableLanguages(); // [{ code, native_name, rtl, is_default, published_at? }]
|
|
557
|
+
return (
|
|
558
|
+
<select value={i18n.language} onChange={(e) => i18n.setLocale(e.target.value)}>
|
|
559
|
+
{languages.map((l) => (
|
|
560
|
+
<option key={l.code} value={l.code}>
|
|
561
|
+
{l.native_name ?? l.code}
|
|
562
|
+
</option>
|
|
563
|
+
))}
|
|
564
|
+
</select>
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
- A newly-published language appears on the **next load** (the manifest is
|
|
570
|
+
CDN-served), or immediately after `i18n.reload()` — no rebuild, no redeploy
|
|
571
|
+
of your app.
|
|
572
|
+
- `is_default` marks the project's default locale; `published_at` (when the
|
|
573
|
+
manifest provides it) lets you flag recently-added languages.
|
|
574
|
+
- Falls back to `defaultLocale` + `fallbackLng` when no manifest is available
|
|
575
|
+
(or `env: "dev"`, which is CDN-only). Opt out with `disableLanguageManifest`.
|
|
576
|
+
- Also imperative: `i18n.availableLanguages`.
|
|
577
|
+
|
|
543
578
|
## Recipes
|
|
544
579
|
|
|
545
580
|
### Next.js (App Router)
|
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ __export(index_exports, {
|
|
|
32
32
|
logTransport: () => logTransport,
|
|
33
33
|
surfaceForWidth: () => surfaceForWidth,
|
|
34
34
|
t: () => t,
|
|
35
|
+
useAvailableLanguages: () => useAvailableLanguages,
|
|
35
36
|
useTranslation: () => useTranslation
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -237,7 +238,7 @@ function flattenPlurals(tree, locale) {
|
|
|
237
238
|
|
|
238
239
|
// src/transport.ts
|
|
239
240
|
var SDK_LIB = "@sonenta/react-i18next";
|
|
240
|
-
var SDK_VER = true ? "2.
|
|
241
|
+
var SDK_VER = true ? "2.3.0" : "0.0.0-dev";
|
|
241
242
|
function defaultTransport(opts) {
|
|
242
243
|
return async (batch) => {
|
|
243
244
|
if (!batch.length) return;
|
|
@@ -429,6 +430,14 @@ var SonentaI18n = class {
|
|
|
429
430
|
// config.languageCatalog and/or the public GET /v1/languages in start().
|
|
430
431
|
_catalog = new LanguageCatalog();
|
|
431
432
|
_catalogDisabled = false;
|
|
433
|
+
// Published-languages manifest (available-languages feature). Per-version,
|
|
434
|
+
// public CDN, no auth: the SET of locales published for the active version.
|
|
435
|
+
// Codes are enriched from `_catalog` (#803) for native_name/rtl/etc.
|
|
436
|
+
_manifestDisabled = false;
|
|
437
|
+
_manifest = null;
|
|
438
|
+
// The configured default locale (the initial `this.locale`, which is mutable
|
|
439
|
+
// via setLocale) — used as the available-languages fallback + default marker.
|
|
440
|
+
_defaultLocale;
|
|
432
441
|
_config;
|
|
433
442
|
// Surface variant (#911). When set, each loaded (locale,ns) is composed as
|
|
434
443
|
// base ⊕ sparse `{ns}.{surface}.json` overlay (overlay wins per key). We
|
|
@@ -476,6 +485,7 @@ var SonentaI18n = class {
|
|
|
476
485
|
);
|
|
477
486
|
}
|
|
478
487
|
this.locale = config.defaultLocale;
|
|
488
|
+
this._defaultLocale = config.defaultLocale;
|
|
479
489
|
this.fallbackLng = config.fallbackLng;
|
|
480
490
|
this._surface = config.surface;
|
|
481
491
|
for (const s of config.a11ySurfaces ?? []) this._a11ySurfaces.add(s);
|
|
@@ -578,6 +588,7 @@ var SonentaI18n = class {
|
|
|
578
588
|
this._rebindFormat();
|
|
579
589
|
this._catalogDisabled = config.disableLanguageCatalog === true;
|
|
580
590
|
this._catalog.merge(config.languageCatalog);
|
|
591
|
+
this._manifestDisabled = config.disableLanguageManifest === true;
|
|
581
592
|
const active = config.initialBundles?.[this.locale];
|
|
582
593
|
if (active && this._config.namespaces.every(
|
|
583
594
|
(n) => active[n] && Object.keys(active[n]).length > 0
|
|
@@ -674,6 +685,7 @@ var SonentaI18n = class {
|
|
|
674
685
|
dir: this.dir,
|
|
675
686
|
nativeName: this.nativeName,
|
|
676
687
|
languageMeta: this.languageMeta,
|
|
688
|
+
availableLanguages: this.availableLanguages,
|
|
677
689
|
i18next: this._i18next
|
|
678
690
|
};
|
|
679
691
|
}
|
|
@@ -735,7 +747,9 @@ var SonentaI18n = class {
|
|
|
735
747
|
// Best-effort: align the key separator with the version's key_style (#754).
|
|
736
748
|
this._loadKeyStyle(fetchImpl),
|
|
737
749
|
// Best-effort: load the public language catalog for dir()/nativeName().
|
|
738
|
-
this._loadCatalog(fetchImpl)
|
|
750
|
+
this._loadCatalog(fetchImpl),
|
|
751
|
+
// Best-effort: load the per-version published-languages manifest.
|
|
752
|
+
this._loadManifest(fetchImpl)
|
|
739
753
|
]);
|
|
740
754
|
await this._syncLanguage();
|
|
741
755
|
this.ready = true;
|
|
@@ -759,6 +773,71 @@ var SonentaI18n = class {
|
|
|
759
773
|
}
|
|
760
774
|
/** Best-effort: fetch the PUBLIC language catalog and merge it in (#803).
|
|
761
775
|
* Skipped when disabled; a failure keeps any embedded catalog. */
|
|
776
|
+
/**
|
|
777
|
+
* Best-effort: fetch the per-version published-languages manifest
|
|
778
|
+
* (`{cdnBase}/p/{project}/{version}/latest/languages.json`, public, no auth,
|
|
779
|
+
* CDN-cached) — the SET of locales published for the active version. Codes
|
|
780
|
+
* only; enriched from the catalog. A 404/offline keeps any prior manifest
|
|
781
|
+
* (`availableLanguages` then falls back to default + fallback). CDN-only:
|
|
782
|
+
* `env: "dev"` skips it (the dev runtime has no manifest yet).
|
|
783
|
+
*/
|
|
784
|
+
async _loadManifest(fetchImpl, opts = {}) {
|
|
785
|
+
if (this._manifestDisabled) return;
|
|
786
|
+
if (this._config.env === "dev") return;
|
|
787
|
+
const url = `${this._config.cdnBase.replace(/\/+$/, "")}/p/${this._config.projectUuid}/${this._config.version}/latest/languages.json`;
|
|
788
|
+
const init = { method: "GET", credentials: "omit" };
|
|
789
|
+
if (opts.bust) init.cache = "reload";
|
|
790
|
+
try {
|
|
791
|
+
const r = await fetchImpl(url, init);
|
|
792
|
+
if (!r.ok) return;
|
|
793
|
+
const data = await r.json();
|
|
794
|
+
const raw = Array.isArray(data.languages) ? data.languages : [];
|
|
795
|
+
const languages = [];
|
|
796
|
+
for (const e of raw) {
|
|
797
|
+
if (typeof e === "string") {
|
|
798
|
+
languages.push({ code: e });
|
|
799
|
+
} else if (e && typeof e === "object" && typeof e.code === "string") {
|
|
800
|
+
const obj = e;
|
|
801
|
+
languages.push({
|
|
802
|
+
code: obj.code,
|
|
803
|
+
published_at: typeof obj.published_at === "string" ? obj.published_at : void 0
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
this._manifest = {
|
|
808
|
+
default_locale: typeof data.default_locale === "string" ? data.default_locale : void 0,
|
|
809
|
+
languages
|
|
810
|
+
};
|
|
811
|
+
} catch {
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
/** Languages PUBLISHED for the active version (available-languages feature):
|
|
815
|
+
* the manifest's codes enriched with catalog metadata, marked with the
|
|
816
|
+
* default locale + any `published_at`. Falls back to the configured
|
|
817
|
+
* `defaultLocale` + `fallbackLng` when no manifest is available. */
|
|
818
|
+
get availableLanguages() {
|
|
819
|
+
const def = this._manifest?.default_locale ?? this._defaultLocale;
|
|
820
|
+
let entries = this._manifest?.languages;
|
|
821
|
+
if (!entries || entries.length === 0) {
|
|
822
|
+
const seen = /* @__PURE__ */ new Set();
|
|
823
|
+
entries = [];
|
|
824
|
+
for (const c of [this._defaultLocale, ...asArray(this.fallbackLng)]) {
|
|
825
|
+
if (c && !seen.has(c)) {
|
|
826
|
+
seen.add(c);
|
|
827
|
+
entries.push({ code: c });
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return entries.map(({ code, published_at }) => {
|
|
832
|
+
const meta = this.languageMeta(code);
|
|
833
|
+
return {
|
|
834
|
+
...meta ?? {},
|
|
835
|
+
code,
|
|
836
|
+
...published_at ? { published_at } : {},
|
|
837
|
+
is_default: code === def
|
|
838
|
+
};
|
|
839
|
+
});
|
|
840
|
+
}
|
|
762
841
|
async _loadCatalog(fetchImpl) {
|
|
763
842
|
if (this._catalogDisabled) return;
|
|
764
843
|
this._catalog.merge(await loadCatalog(this._config.apiBase, fetchImpl));
|
|
@@ -849,12 +928,14 @@ var SonentaI18n = class {
|
|
|
849
928
|
if (opts.namespace && opts.namespace !== ns) continue;
|
|
850
929
|
targets.push({ locale, ns });
|
|
851
930
|
}
|
|
852
|
-
if (targets.length === 0) return;
|
|
853
|
-
|
|
854
|
-
|
|
931
|
+
if (targets.length === 0 && (opts.locale || opts.namespace)) return;
|
|
932
|
+
const manifest = this._loadManifest(fetch, { bust: true });
|
|
933
|
+
await Promise.all([
|
|
934
|
+
manifest,
|
|
935
|
+
...targets.map(
|
|
855
936
|
(t2) => this._loadBundle(t2.locale, t2.ns, fetch, { bust: true })
|
|
856
937
|
)
|
|
857
|
-
);
|
|
938
|
+
]);
|
|
858
939
|
this._notify();
|
|
859
940
|
};
|
|
860
941
|
/**
|
|
@@ -1445,6 +1526,9 @@ function useTranslation(defaultNamespace) {
|
|
|
1445
1526
|
}, []);
|
|
1446
1527
|
return { t: t2, i18n: snapshot };
|
|
1447
1528
|
}
|
|
1529
|
+
function useAvailableLanguages() {
|
|
1530
|
+
return useI18nSnapshot().availableLanguages;
|
|
1531
|
+
}
|
|
1448
1532
|
|
|
1449
1533
|
// src/trans.tsx
|
|
1450
1534
|
var import_react3 = require("react");
|
|
@@ -1499,6 +1583,7 @@ function splitOnComponents(text, components) {
|
|
|
1499
1583
|
logTransport,
|
|
1500
1584
|
surfaceForWidth,
|
|
1501
1585
|
t,
|
|
1586
|
+
useAvailableLanguages,
|
|
1502
1587
|
useTranslation
|
|
1503
1588
|
});
|
|
1504
1589
|
//# sourceMappingURL=index.cjs.map
|