langsys-js-typescript 0.2.2 → 0.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.mjs CHANGED
@@ -1,5 +1,30 @@
1
1
  import { IntlMessageFormat } from 'intl-messageformat';
2
2
 
3
+ // src/locale.ts
4
+ function canonicalizeLocale(locale) {
5
+ if (!locale || typeof locale !== "string") return locale;
6
+ const cleaned = locale.trim().replace(/_/g, "-");
7
+ try {
8
+ const [canonical] = Intl.getCanonicalLocales(cleaned);
9
+ return canonical ?? cleaned;
10
+ } catch {
11
+ return cleaned.split("-").map((part, i) => {
12
+ if (i === 0) return part.toLowerCase();
13
+ if (part.length === 4) return part[0].toUpperCase() + part.slice(1).toLowerCase();
14
+ if (part.length === 2 || /^\d{3}$/.test(part)) return part.toUpperCase();
15
+ return part.toLowerCase();
16
+ }).join("-");
17
+ }
18
+ }
19
+ function maximizedLangScript(locale) {
20
+ try {
21
+ const max = new Intl.Locale(canonicalizeLocale(locale)).maximize();
22
+ return `${max.language}-${max.script ?? ""}`;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
3
28
  // src/signal.ts
4
29
  function createSignal(initial) {
5
30
  let value = initial;
@@ -91,7 +116,7 @@ var LangsysAppAPIClass = class {
91
116
  return await this.get("authorize-project/[projectid]");
92
117
  }
93
118
  async getTranslations(locale) {
94
- return await this.get("translations", { project_id: this.config.projectid, locale });
119
+ return await this.get("translations", { project_id: this.config.projectid, locale: canonicalizeLocale(locale) });
95
120
  }
96
121
  /**
97
122
  * Register phrases and/or content blocks via the current unified endpoint.
@@ -237,24 +262,37 @@ var config = {
237
262
  function isICU(template) {
238
263
  return ICU_PATTERN.test(template);
239
264
  }
240
- var ICU_PATTERN = /\{[^{}]+,\s*(plural|select|selectordinal|number|date|time)\s*,/;
265
+ var ICU_PATTERN = /\{[^{}]+,\s*(plural|select|selectordinal|number|date|time)\s*[,}]/;
241
266
  function interpolate(template, params, locale) {
242
267
  if (isICU(template)) {
243
268
  try {
244
- return new IntlMessageFormat(template, locale ?? "en").format(params);
269
+ return new IntlMessageFormat(template, locale || "en").format(params);
245
270
  } catch {
246
- return simpleInterpolate(template, params);
271
+ return simpleInterpolate(template, params, locale);
247
272
  }
248
273
  }
249
- return simpleInterpolate(template, params);
274
+ return simpleInterpolate(template, params, locale);
250
275
  }
251
- function simpleInterpolate(template, params) {
276
+ function simpleInterpolate(template, params, locale) {
252
277
  return template.replace(/\{([^{},]+)\}/g, (match, rawKey) => {
253
278
  const key = rawKey.trim();
254
279
  if (!(key in params)) return match;
255
280
  const value = params[key];
256
281
  if (value === void 0 || value === null) return match;
257
- if (value instanceof Date) return value.toISOString();
282
+ if (value instanceof Date) {
283
+ try {
284
+ return new Intl.DateTimeFormat(locale || "en", { dateStyle: "medium" }).format(value);
285
+ } catch {
286
+ return value.toISOString();
287
+ }
288
+ }
289
+ if (typeof value === "number" || typeof value === "bigint") {
290
+ try {
291
+ return new Intl.NumberFormat(locale || "en").format(value);
292
+ } catch {
293
+ return String(value);
294
+ }
295
+ }
258
296
  return String(value);
259
297
  });
260
298
  }
@@ -280,7 +318,7 @@ var Translations = class {
280
318
  });
281
319
  this.config = config2;
282
320
  this.debug = new Logger(!!config2.debug);
283
- this.locale = config2.baseLocale || "";
321
+ this.locale = canonicalizeLocale(config2.baseLocale || "");
284
322
  this.tSignal = createSignal(this.buildTFn());
285
323
  sTranslations.subscribe(() => this.tSignal.set(this.buildTFn()));
286
324
  currentlyLoadedLocale.subscribe(() => this.tSignal.set(this.buildTFn()));
@@ -421,7 +459,7 @@ var Translations = class {
421
459
  this.config = config2;
422
460
  if (!this.config.projectid || !this.config.key) return;
423
461
  if (this.config.debug) this.debug.debugEnabled = this.config.debug;
424
- if (config2.baseLocale) this.locale = config2.baseLocale;
462
+ if (config2.baseLocale) this.locale = canonicalizeLocale(config2.baseLocale);
425
463
  this.debug.log("TRANSLATION SETUP INITIATED", this.config);
426
464
  if (typeof window !== "undefined") {
427
465
  if (this.isFirstClientRun && this.missingTokens.length > 0) {
@@ -449,8 +487,10 @@ var Translations = class {
449
487
  */
450
488
  markLoaded(locale) {
451
489
  if (!locale) return;
490
+ locale = canonicalizeLocale(locale);
452
491
  this.locale = locale;
453
492
  this.lastLoaded[locale] = (/* @__PURE__ */ new Date()).getTime() / 1e3;
493
+ this.readyResolve();
454
494
  }
455
495
  /**
456
496
  * React to a user-locale change. Fetches translations if we haven't
@@ -459,10 +499,12 @@ var Translations = class {
459
499
  async change(locale, force = false, skipFetch = false) {
460
500
  if (!locale) return false;
461
501
  if (!this.config.projectid || !this.config.key) return false;
502
+ locale = canonicalizeLocale(locale);
462
503
  if (skipFetch) {
463
504
  this.debug.log("Using pre-fetched translations for locale", locale);
464
505
  this.locale = locale;
465
506
  this.lastLoaded[locale] = (/* @__PURE__ */ new Date()).getTime() / 1e3;
507
+ this.readyResolve();
466
508
  return true;
467
509
  }
468
510
  const now = (/* @__PURE__ */ new Date()).getTime() / 1e3;
@@ -626,11 +668,11 @@ var LangsysAppClass = class {
626
668
  }
627
669
  if (!UserLocaleStore?.subscribe || typeof UserLocaleStore.get !== "function") {
628
670
  this.debug.error(
629
- "LangsysApp.init missing UserLocaleStore \u2014 pass any object satisfying LocaleSource (get + subscribe). createSignal('en-us') works if you have nothing of your own."
671
+ "LangsysApp.init missing UserLocaleStore \u2014 pass any object satisfying LocaleSource (get + subscribe). createSignal('en-US') works if you have nothing of your own."
630
672
  );
631
673
  return { status: false, errors: ["Missing UserLocaleStore"] };
632
674
  }
633
- baseLocale = baseLocale.toLowerCase();
675
+ baseLocale = canonicalizeLocale(baseLocale);
634
676
  if (!emulateFailureToLoad) {
635
677
  this.config = {
636
678
  projectid,
@@ -669,8 +711,9 @@ var LangsysAppClass = class {
669
711
  if (initialTranslations2 && initialTranslationsLocale) {
670
712
  this.Translations.markLoaded(initialTranslationsLocale);
671
713
  }
672
- this.config.sUserLocale.subscribe((locale) => {
714
+ this.config.sUserLocale.subscribe((rawLocale) => {
673
715
  if (!validateResponse.status) return;
716
+ const locale = canonicalizeLocale(rawLocale);
674
717
  this.getLocalesData(locale);
675
718
  this.debug.log("SUBSCRIBING TO USER LOCALE STORE");
676
719
  this.translationsLoadingPromise = this.Translations.change(locale, false);
@@ -678,7 +721,7 @@ var LangsysAppClass = class {
678
721
  return validateResponse;
679
722
  }
680
723
  async getDialCodes(inLocale) {
681
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
724
+ const locale = this.resolveLocale(inLocale);
682
725
  if (this.dialCodes.length && this.dialCodesLocale === locale) return this.dialCodes;
683
726
  const route = `countries/dial-codes/${locale}`;
684
727
  const response = await LangsysAppAPI.get(route);
@@ -691,7 +734,7 @@ var LangsysAppClass = class {
691
734
  return this.dialCodes;
692
735
  }
693
736
  async getCurrencies(inLocale) {
694
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
737
+ const locale = this.resolveLocale(inLocale);
695
738
  if (this.currencies.length && this.currenciesLocale === locale) return this.currencies;
696
739
  const route = `currencies/${locale}`;
697
740
  const response = await LangsysAppAPI.get(route);
@@ -704,7 +747,7 @@ var LangsysAppClass = class {
704
747
  return this.currencies;
705
748
  }
706
749
  async getCountries(inLocale) {
707
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
750
+ const locale = this.resolveLocale(inLocale);
708
751
  if (this.countries.length && this.countriesLocale === locale) return this.countries;
709
752
  const route = `countries/${locale}`;
710
753
  const response = await LangsysAppAPI.get(route);
@@ -718,7 +761,7 @@ var LangsysAppClass = class {
718
761
  }
719
762
  async getCountryName(forCountryCode, inLocale) {
720
763
  if (!forCountryCode) return "";
721
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
764
+ const locale = this.resolveLocale(inLocale);
722
765
  if (!this.countries.length || this.countriesLocale !== locale) await this.getCountries(inLocale);
723
766
  const country = this.countries.find((c) => c.code.toLowerCase() === forCountryCode.toLowerCase())?.label;
724
767
  if (!country) this.debug.warn("getCountryName failed to match", forCountryCode);
@@ -726,14 +769,14 @@ var LangsysAppClass = class {
726
769
  }
727
770
  async getCurrencyName(forCurrencyCode, inLocale) {
728
771
  if (!forCurrencyCode) return "";
729
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
772
+ const locale = this.resolveLocale(inLocale);
730
773
  if (!this.currencies.length || this.currenciesLocale !== locale) await this.getCurrencies(inLocale);
731
774
  const currency = this.currencies.find((c) => c.code?.toLowerCase() === forCurrencyCode.toLowerCase())?.name;
732
775
  if (!currency) this.debug.warn("getCurrencyName failed to match", forCurrencyCode);
733
776
  return currency || forCurrencyCode;
734
777
  }
735
778
  async getLocalesFormat(format = "", inLocale) {
736
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
779
+ const locale = this.resolveLocale(inLocale);
737
780
  const route = `locales/${locale}` + (format ? `/${format}` : "");
738
781
  const response = await LangsysAppAPI.get(route);
739
782
  if (response.errors || !response.status) {
@@ -754,7 +797,7 @@ var LangsysAppClass = class {
754
797
  return await this.getLocalesFormat("flat", inLocale);
755
798
  }
756
799
  async getLocalesData(inLocale, forceRefresh = false) {
757
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
800
+ const locale = this.resolveLocale(inLocale);
758
801
  if (this.locales?.[locale]?.length && !forceRefresh) return this.locales[locale];
759
802
  this.locales[locale] = await this.getLocalesFormat("data", inLocale);
760
803
  return this.locales[locale];
@@ -764,15 +807,16 @@ var LangsysAppClass = class {
764
807
  }
765
808
  async getLocaleNameWithLookup(forLocale, shortName = false, inLocale) {
766
809
  if (!forLocale) return "";
767
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
810
+ const locale = this.resolveLocale(inLocale);
768
811
  await this.getLocalesData(locale);
769
812
  return this.getLocaleName(forLocale, shortName, locale);
770
813
  }
771
814
  getLocaleName(forLocale, shortName = false, inLocale) {
772
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
815
+ const locale = this.resolveLocale(inLocale);
816
+ const target = canonicalizeLocale(forLocale).toLowerCase();
773
817
  let name = "";
774
818
  this.locales[locale]?.every((loc) => {
775
- if (loc.code === forLocale) {
819
+ if (loc.code.toLowerCase() === target) {
776
820
  name = shortName ? loc.lang_name : loc.locale_name;
777
821
  return false;
778
822
  }
@@ -798,7 +842,7 @@ var LangsysAppClass = class {
798
842
  const bestMatch = this.findBestLocaleMatch(userLocales, supportedLocales);
799
843
  if (bestMatch) return bestMatch;
800
844
  }
801
- return this.normalizeLocale(userLocales[0]);
845
+ return canonicalizeLocale(userLocales[0]);
802
846
  }
803
847
  getUserLanguagePreferences(acceptLanguageHeader) {
804
848
  if (acceptLanguageHeader) {
@@ -835,30 +879,26 @@ var LangsysAppClass = class {
835
879
  }
836
880
  findBestLocaleMatch(userLocales, supportedLocales) {
837
881
  if (!userLocales.length || !supportedLocales.length) return null;
838
- const normalizedSupported = supportedLocales.map((l) => this.normalizeLocale(l));
882
+ const supported = supportedLocales.map((l) => canonicalizeLocale(l));
839
883
  for (const userLocale of userLocales) {
840
- const normalizedUser = this.normalizeLocale(userLocale);
841
- if (normalizedSupported.includes(normalizedUser)) return normalizedUser;
884
+ const user = canonicalizeLocale(userLocale);
885
+ if (supported.includes(user)) return user;
842
886
  }
843
887
  for (const userLocale of userLocales) {
844
- const userLang = this.getLanguageCode(userLocale);
845
- for (const supported of normalizedSupported) {
846
- if (this.getLanguageCode(supported) === userLang) return supported;
847
- }
888
+ const userKey = maximizedLangScript(userLocale);
889
+ if (!userKey) continue;
890
+ const match = supported.find((s) => maximizedLangScript(s) === userKey);
891
+ if (match) return match;
848
892
  }
849
893
  return null;
850
894
  }
851
- normalizeLocale(locale) {
852
- if (!locale || typeof locale !== "string") return locale;
853
- const parts = locale.split(/[-_]/);
854
- if (parts.length === 1) return parts[0].toLowerCase();
855
- const language = parts[0].toLowerCase();
856
- const region = parts[1].toUpperCase();
857
- return `${language}-${region}`;
858
- }
859
- getLanguageCode(locale) {
860
- if (!locale || typeof locale !== "string") return "";
861
- return locale.split(/[-_]/)[0].toLowerCase();
895
+ /**
896
+ * Resolve the effective locale for a data-fetching helper: explicit
897
+ * argument, else the user's current locale, else the base locale —
898
+ * always in BCP 47 canonical form so cache keys and API routes agree.
899
+ */
900
+ resolveLocale(inLocale) {
901
+ return canonicalizeLocale(inLocale || this.config.sUserLocale.get() || this.config.baseLocale);
862
902
  }
863
903
  };
864
904
  var LangsysApp = new LangsysAppClass();
@@ -1484,6 +1524,6 @@ var t = ((phrase, ...rest) => {
1484
1524
  });
1485
1525
  var tSignal = LangsysApp.Translations.tSignal;
1486
1526
 
1487
- export { LangsysApp, LangsysAppAPI, Logger, PHRASE_MARKER_ATTR, Phrase, Translate, Translations, createSignal, currentlyLoadedLocale, encodeRichText, generateCustomId, getValue, interpolate, isContentBlockKnown, isEmpty, isICU, logger, markupTokenValues, md5, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
1527
+ export { LangsysApp, LangsysAppAPI, Logger, PHRASE_MARKER_ATTR, Phrase, Translate, Translations, canonicalizeLocale, createSignal, currentlyLoadedLocale, encodeRichText, generateCustomId, getValue, interpolate, isContentBlockKnown, isEmpty, isICU, logger, markupTokenValues, md5, persist, reconstitute, registerContentBlock, sTranslations, stripSentinels, t, tSignal, tokenizeElement };
1488
1528
  //# sourceMappingURL=index.mjs.map
1489
1529
  //# sourceMappingURL=index.mjs.map