@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/dist/index.d.cts CHANGED
@@ -83,6 +83,20 @@ interface LanguageMeta {
83
83
  /** CLDR plural categories present for this language. */
84
84
  plural_categories?: string[];
85
85
  }
86
+ /**
87
+ * A language PUBLISHED for the active version (task: available-languages) — a
88
+ * {@link LanguageMeta} (enriched from the catalog) plus publish info from the
89
+ * per-version CDN manifest. Returned by `i18n.availableLanguages` /
90
+ * `useAvailableLanguages()`.
91
+ */
92
+ interface AvailableLanguage extends LanguageMeta {
93
+ /** ISO timestamp this language was published for the active version, when
94
+ * the manifest provides it (optional / forward-compat). Render a
95
+ * "recently added" hint if present; ignore if absent. */
96
+ published_at?: string;
97
+ /** True when this is the project's default locale for the active version. */
98
+ is_default?: boolean;
99
+ }
86
100
  interface MissingKeyEvent {
87
101
  key: string;
88
102
  namespace: Namespace;
@@ -162,6 +176,14 @@ interface SonentaConfig {
162
176
  * you supply {@link SonentaConfig.languageCatalog} yourself.
163
177
  */
164
178
  disableLanguageCatalog?: boolean;
179
+ /**
180
+ * Skip the best-effort fetch of the per-version published-languages manifest
181
+ * (`{cdnBase}/p/{project}/{version}/latest/languages.json`, public, no auth)
182
+ * that powers `i18n.availableLanguages` / `useAvailableLanguages()`. When
183
+ * skipped (or on a 404), `availableLanguages` falls back to the configured
184
+ * `defaultLocale` + `fallbackLng`.
185
+ */
186
+ disableLanguageManifest?: boolean;
165
187
  /**
166
188
  * Interpolation hooks forwarded to i18next. Currently exposes `format`, the
167
189
  * value formatter i18next invokes for `{{value, format}}` placeholders — wire
@@ -434,6 +456,15 @@ interface I18nInstance {
434
456
  * parent, …), defaulting to the active locale; `undefined` if unknown.
435
457
  */
436
458
  languageMeta: (lng?: Locale) => LanguageMeta | undefined;
459
+ /**
460
+ * The languages PUBLISHED for the active version (from the per-version CDN
461
+ * manifest, enriched with catalog metadata) — drives a runtime language
462
+ * switcher that updates when a new language ships WITHOUT recompiling the
463
+ * app. Falls back to `defaultLocale` + `fallbackLng` when no manifest is
464
+ * available. Refreshed on `reload()`. Prefer the `useAvailableLanguages()`
465
+ * hook in React (reactive).
466
+ */
467
+ availableLanguages: AvailableLanguage[];
437
468
  /**
438
469
  * The underlying real `i18next` instance powering this SDK (thin-wrapper,
439
470
  * #805). Exposed for react-i18next DROP-IN: pass it to react-i18next's own
@@ -490,6 +521,25 @@ interface UseTranslationResult {
490
521
  * contribution is keyed to THIS hook instance and dropped on unmount, so
491
522
  * navigating away removes its keys automatically. */
492
523
  declare function useTranslation(defaultNamespace?: string): UseTranslationResult;
524
+ /**
525
+ * React hook — the languages PUBLISHED for the active version, enriched with
526
+ * catalog metadata (native_name / rtl / …). Drives a runtime language switcher
527
+ * that picks up a newly-published language WITHOUT recompiling the app: the
528
+ * per-version manifest is CDN-served, so the new language appears on the next
529
+ * load (or after `i18n.reload()`). Falls back to `defaultLocale` +
530
+ * `fallbackLng` when no manifest is available.
531
+ *
532
+ * ```tsx
533
+ * const langs = useAvailableLanguages();
534
+ * const { i18n } = useTranslation();
535
+ * return langs.map((l) => (
536
+ * <button key={l.code} onClick={() => i18n.setLocale(l.code)}>
537
+ * {l.native_name ?? l.code}{l.is_default ? " ★" : ""}
538
+ * </button>
539
+ * ));
540
+ * ```
541
+ */
542
+ declare function useAvailableLanguages(): AvailableLanguage[];
493
543
 
494
544
  interface TransProps {
495
545
  /** The translation key (optionally `ns:key`). */
@@ -625,4 +675,4 @@ declare class KeyRegistry {
625
675
  /** Process-wide singleton — there is exactly one on-screen registry. */
626
676
  declare const keyRegistry: KeyRegistry;
627
677
 
628
- export { A11Y_SURFACES, type A11ySurface, type AssetRef, DEFAULT_SURFACE_BREAKPOINTS, type DeclaredKey, type I18nInstance, type LanguageMeta, type Locale, type MissingHandlerMode, type MissingKeyEvent, type Namespace, type SonentaConfig, type SonentaPlugin, type SonentaPluginContext, SonentaProvider, type Surface, type SurfaceBreakpoints, Trans, type TranslationFunction, type TranslationOptions, type Transport, type SonentaConfig as VerbumiaConfig, type SonentaPlugin as VerbumiaPlugin, type SonentaPluginContext as VerbumiaPluginContext, SonentaProvider as VerbumiaProvider, defaultTransport, getI18n, getI18nSafe, keyRegistry, logTransport, surfaceForWidth, t, useTranslation };
678
+ export { A11Y_SURFACES, type A11ySurface, type A11yTranslationFunction, type AssetRef, type AvailableLanguage, DEFAULT_SURFACE_BREAKPOINTS, type DeclaredKey, type I18nInstance, type LanguageMeta, type Locale, type MissingHandlerMode, type MissingKeyEvent, type Namespace, type SonentaConfig, type SonentaPlugin, type SonentaPluginContext, SonentaProvider, type Surface, type SurfaceBreakpoints, Trans, type TranslationFunction, type TranslationOptions, type Transport, type SonentaConfig as VerbumiaConfig, type SonentaPlugin as VerbumiaPlugin, type SonentaPluginContext as VerbumiaPluginContext, SonentaProvider as VerbumiaProvider, defaultTransport, getI18n, getI18nSafe, keyRegistry, logTransport, surfaceForWidth, t, useAvailableLanguages, useTranslation };
package/dist/index.d.ts CHANGED
@@ -83,6 +83,20 @@ interface LanguageMeta {
83
83
  /** CLDR plural categories present for this language. */
84
84
  plural_categories?: string[];
85
85
  }
86
+ /**
87
+ * A language PUBLISHED for the active version (task: available-languages) — a
88
+ * {@link LanguageMeta} (enriched from the catalog) plus publish info from the
89
+ * per-version CDN manifest. Returned by `i18n.availableLanguages` /
90
+ * `useAvailableLanguages()`.
91
+ */
92
+ interface AvailableLanguage extends LanguageMeta {
93
+ /** ISO timestamp this language was published for the active version, when
94
+ * the manifest provides it (optional / forward-compat). Render a
95
+ * "recently added" hint if present; ignore if absent. */
96
+ published_at?: string;
97
+ /** True when this is the project's default locale for the active version. */
98
+ is_default?: boolean;
99
+ }
86
100
  interface MissingKeyEvent {
87
101
  key: string;
88
102
  namespace: Namespace;
@@ -162,6 +176,14 @@ interface SonentaConfig {
162
176
  * you supply {@link SonentaConfig.languageCatalog} yourself.
163
177
  */
164
178
  disableLanguageCatalog?: boolean;
179
+ /**
180
+ * Skip the best-effort fetch of the per-version published-languages manifest
181
+ * (`{cdnBase}/p/{project}/{version}/latest/languages.json`, public, no auth)
182
+ * that powers `i18n.availableLanguages` / `useAvailableLanguages()`. When
183
+ * skipped (or on a 404), `availableLanguages` falls back to the configured
184
+ * `defaultLocale` + `fallbackLng`.
185
+ */
186
+ disableLanguageManifest?: boolean;
165
187
  /**
166
188
  * Interpolation hooks forwarded to i18next. Currently exposes `format`, the
167
189
  * value formatter i18next invokes for `{{value, format}}` placeholders — wire
@@ -434,6 +456,15 @@ interface I18nInstance {
434
456
  * parent, …), defaulting to the active locale; `undefined` if unknown.
435
457
  */
436
458
  languageMeta: (lng?: Locale) => LanguageMeta | undefined;
459
+ /**
460
+ * The languages PUBLISHED for the active version (from the per-version CDN
461
+ * manifest, enriched with catalog metadata) — drives a runtime language
462
+ * switcher that updates when a new language ships WITHOUT recompiling the
463
+ * app. Falls back to `defaultLocale` + `fallbackLng` when no manifest is
464
+ * available. Refreshed on `reload()`. Prefer the `useAvailableLanguages()`
465
+ * hook in React (reactive).
466
+ */
467
+ availableLanguages: AvailableLanguage[];
437
468
  /**
438
469
  * The underlying real `i18next` instance powering this SDK (thin-wrapper,
439
470
  * #805). Exposed for react-i18next DROP-IN: pass it to react-i18next's own
@@ -490,6 +521,25 @@ interface UseTranslationResult {
490
521
  * contribution is keyed to THIS hook instance and dropped on unmount, so
491
522
  * navigating away removes its keys automatically. */
492
523
  declare function useTranslation(defaultNamespace?: string): UseTranslationResult;
524
+ /**
525
+ * React hook — the languages PUBLISHED for the active version, enriched with
526
+ * catalog metadata (native_name / rtl / …). Drives a runtime language switcher
527
+ * that picks up a newly-published language WITHOUT recompiling the app: the
528
+ * per-version manifest is CDN-served, so the new language appears on the next
529
+ * load (or after `i18n.reload()`). Falls back to `defaultLocale` +
530
+ * `fallbackLng` when no manifest is available.
531
+ *
532
+ * ```tsx
533
+ * const langs = useAvailableLanguages();
534
+ * const { i18n } = useTranslation();
535
+ * return langs.map((l) => (
536
+ * <button key={l.code} onClick={() => i18n.setLocale(l.code)}>
537
+ * {l.native_name ?? l.code}{l.is_default ? " ★" : ""}
538
+ * </button>
539
+ * ));
540
+ * ```
541
+ */
542
+ declare function useAvailableLanguages(): AvailableLanguage[];
493
543
 
494
544
  interface TransProps {
495
545
  /** The translation key (optionally `ns:key`). */
@@ -625,4 +675,4 @@ declare class KeyRegistry {
625
675
  /** Process-wide singleton — there is exactly one on-screen registry. */
626
676
  declare const keyRegistry: KeyRegistry;
627
677
 
628
- export { A11Y_SURFACES, type A11ySurface, type AssetRef, DEFAULT_SURFACE_BREAKPOINTS, type DeclaredKey, type I18nInstance, type LanguageMeta, type Locale, type MissingHandlerMode, type MissingKeyEvent, type Namespace, type SonentaConfig, type SonentaPlugin, type SonentaPluginContext, SonentaProvider, type Surface, type SurfaceBreakpoints, Trans, type TranslationFunction, type TranslationOptions, type Transport, type SonentaConfig as VerbumiaConfig, type SonentaPlugin as VerbumiaPlugin, type SonentaPluginContext as VerbumiaPluginContext, SonentaProvider as VerbumiaProvider, defaultTransport, getI18n, getI18nSafe, keyRegistry, logTransport, surfaceForWidth, t, useTranslation };
678
+ export { A11Y_SURFACES, type A11ySurface, type A11yTranslationFunction, type AssetRef, type AvailableLanguage, DEFAULT_SURFACE_BREAKPOINTS, type DeclaredKey, type I18nInstance, type LanguageMeta, type Locale, type MissingHandlerMode, type MissingKeyEvent, type Namespace, type SonentaConfig, type SonentaPlugin, type SonentaPluginContext, SonentaProvider, type Surface, type SurfaceBreakpoints, Trans, type TranslationFunction, type TranslationOptions, type Transport, type SonentaConfig as VerbumiaConfig, type SonentaPlugin as VerbumiaPlugin, type SonentaPluginContext as VerbumiaPluginContext, SonentaProvider as VerbumiaProvider, defaultTransport, getI18n, getI18nSafe, keyRegistry, logTransport, surfaceForWidth, t, useAvailableLanguages, useTranslation };
package/dist/index.js CHANGED
@@ -210,7 +210,7 @@ function flattenPlurals(tree, locale) {
210
210
 
211
211
  // src/transport.ts
212
212
  var SDK_LIB = "@sonenta/react-i18next";
213
- var SDK_VER = true ? "2.2.0" : "0.0.0-dev";
213
+ var SDK_VER = true ? "2.3.0" : "0.0.0-dev";
214
214
  function defaultTransport(opts) {
215
215
  return async (batch) => {
216
216
  if (!batch.length) return;
@@ -402,6 +402,14 @@ var SonentaI18n = class {
402
402
  // config.languageCatalog and/or the public GET /v1/languages in start().
403
403
  _catalog = new LanguageCatalog();
404
404
  _catalogDisabled = false;
405
+ // Published-languages manifest (available-languages feature). Per-version,
406
+ // public CDN, no auth: the SET of locales published for the active version.
407
+ // Codes are enriched from `_catalog` (#803) for native_name/rtl/etc.
408
+ _manifestDisabled = false;
409
+ _manifest = null;
410
+ // The configured default locale (the initial `this.locale`, which is mutable
411
+ // via setLocale) — used as the available-languages fallback + default marker.
412
+ _defaultLocale;
405
413
  _config;
406
414
  // Surface variant (#911). When set, each loaded (locale,ns) is composed as
407
415
  // base ⊕ sparse `{ns}.{surface}.json` overlay (overlay wins per key). We
@@ -449,6 +457,7 @@ var SonentaI18n = class {
449
457
  );
450
458
  }
451
459
  this.locale = config.defaultLocale;
460
+ this._defaultLocale = config.defaultLocale;
452
461
  this.fallbackLng = config.fallbackLng;
453
462
  this._surface = config.surface;
454
463
  for (const s of config.a11ySurfaces ?? []) this._a11ySurfaces.add(s);
@@ -551,6 +560,7 @@ var SonentaI18n = class {
551
560
  this._rebindFormat();
552
561
  this._catalogDisabled = config.disableLanguageCatalog === true;
553
562
  this._catalog.merge(config.languageCatalog);
563
+ this._manifestDisabled = config.disableLanguageManifest === true;
554
564
  const active = config.initialBundles?.[this.locale];
555
565
  if (active && this._config.namespaces.every(
556
566
  (n) => active[n] && Object.keys(active[n]).length > 0
@@ -647,6 +657,7 @@ var SonentaI18n = class {
647
657
  dir: this.dir,
648
658
  nativeName: this.nativeName,
649
659
  languageMeta: this.languageMeta,
660
+ availableLanguages: this.availableLanguages,
650
661
  i18next: this._i18next
651
662
  };
652
663
  }
@@ -708,7 +719,9 @@ var SonentaI18n = class {
708
719
  // Best-effort: align the key separator with the version's key_style (#754).
709
720
  this._loadKeyStyle(fetchImpl),
710
721
  // Best-effort: load the public language catalog for dir()/nativeName().
711
- this._loadCatalog(fetchImpl)
722
+ this._loadCatalog(fetchImpl),
723
+ // Best-effort: load the per-version published-languages manifest.
724
+ this._loadManifest(fetchImpl)
712
725
  ]);
713
726
  await this._syncLanguage();
714
727
  this.ready = true;
@@ -732,6 +745,71 @@ var SonentaI18n = class {
732
745
  }
733
746
  /** Best-effort: fetch the PUBLIC language catalog and merge it in (#803).
734
747
  * Skipped when disabled; a failure keeps any embedded catalog. */
748
+ /**
749
+ * Best-effort: fetch the per-version published-languages manifest
750
+ * (`{cdnBase}/p/{project}/{version}/latest/languages.json`, public, no auth,
751
+ * CDN-cached) — the SET of locales published for the active version. Codes
752
+ * only; enriched from the catalog. A 404/offline keeps any prior manifest
753
+ * (`availableLanguages` then falls back to default + fallback). CDN-only:
754
+ * `env: "dev"` skips it (the dev runtime has no manifest yet).
755
+ */
756
+ async _loadManifest(fetchImpl, opts = {}) {
757
+ if (this._manifestDisabled) return;
758
+ if (this._config.env === "dev") return;
759
+ const url = `${this._config.cdnBase.replace(/\/+$/, "")}/p/${this._config.projectUuid}/${this._config.version}/latest/languages.json`;
760
+ const init = { method: "GET", credentials: "omit" };
761
+ if (opts.bust) init.cache = "reload";
762
+ try {
763
+ const r = await fetchImpl(url, init);
764
+ if (!r.ok) return;
765
+ const data = await r.json();
766
+ const raw = Array.isArray(data.languages) ? data.languages : [];
767
+ const languages = [];
768
+ for (const e of raw) {
769
+ if (typeof e === "string") {
770
+ languages.push({ code: e });
771
+ } else if (e && typeof e === "object" && typeof e.code === "string") {
772
+ const obj = e;
773
+ languages.push({
774
+ code: obj.code,
775
+ published_at: typeof obj.published_at === "string" ? obj.published_at : void 0
776
+ });
777
+ }
778
+ }
779
+ this._manifest = {
780
+ default_locale: typeof data.default_locale === "string" ? data.default_locale : void 0,
781
+ languages
782
+ };
783
+ } catch {
784
+ }
785
+ }
786
+ /** Languages PUBLISHED for the active version (available-languages feature):
787
+ * the manifest's codes enriched with catalog metadata, marked with the
788
+ * default locale + any `published_at`. Falls back to the configured
789
+ * `defaultLocale` + `fallbackLng` when no manifest is available. */
790
+ get availableLanguages() {
791
+ const def = this._manifest?.default_locale ?? this._defaultLocale;
792
+ let entries = this._manifest?.languages;
793
+ if (!entries || entries.length === 0) {
794
+ const seen = /* @__PURE__ */ new Set();
795
+ entries = [];
796
+ for (const c of [this._defaultLocale, ...asArray(this.fallbackLng)]) {
797
+ if (c && !seen.has(c)) {
798
+ seen.add(c);
799
+ entries.push({ code: c });
800
+ }
801
+ }
802
+ }
803
+ return entries.map(({ code, published_at }) => {
804
+ const meta = this.languageMeta(code);
805
+ return {
806
+ ...meta ?? {},
807
+ code,
808
+ ...published_at ? { published_at } : {},
809
+ is_default: code === def
810
+ };
811
+ });
812
+ }
735
813
  async _loadCatalog(fetchImpl) {
736
814
  if (this._catalogDisabled) return;
737
815
  this._catalog.merge(await loadCatalog(this._config.apiBase, fetchImpl));
@@ -822,12 +900,14 @@ var SonentaI18n = class {
822
900
  if (opts.namespace && opts.namespace !== ns) continue;
823
901
  targets.push({ locale, ns });
824
902
  }
825
- if (targets.length === 0) return;
826
- await Promise.all(
827
- targets.map(
903
+ if (targets.length === 0 && (opts.locale || opts.namespace)) return;
904
+ const manifest = this._loadManifest(fetch, { bust: true });
905
+ await Promise.all([
906
+ manifest,
907
+ ...targets.map(
828
908
  (t2) => this._loadBundle(t2.locale, t2.ns, fetch, { bust: true })
829
909
  )
830
- );
910
+ ]);
831
911
  this._notify();
832
912
  };
833
913
  /**
@@ -1418,6 +1498,9 @@ function useTranslation(defaultNamespace) {
1418
1498
  }, []);
1419
1499
  return { t: t2, i18n: snapshot };
1420
1500
  }
1501
+ function useAvailableLanguages() {
1502
+ return useI18nSnapshot().availableLanguages;
1503
+ }
1421
1504
 
1422
1505
  // src/trans.tsx
1423
1506
  import { Children, cloneElement, isValidElement } from "react";
@@ -1471,6 +1554,7 @@ export {
1471
1554
  logTransport,
1472
1555
  surfaceForWidth,
1473
1556
  t,
1557
+ useAvailableLanguages,
1474
1558
  useTranslation
1475
1559
  };
1476
1560
  //# sourceMappingURL=index.js.map