langsys-js-typescript 0.2.3 → 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,6 +487,7 @@ 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;
454
493
  this.readyResolve();
@@ -460,6 +499,7 @@ var Translations = class {
460
499
  async change(locale, force = false, skipFetch = false) {
461
500
  if (!locale) return false;
462
501
  if (!this.config.projectid || !this.config.key) return false;
502
+ locale = canonicalizeLocale(locale);
463
503
  if (skipFetch) {
464
504
  this.debug.log("Using pre-fetched translations for locale", locale);
465
505
  this.locale = locale;
@@ -628,11 +668,11 @@ var LangsysAppClass = class {
628
668
  }
629
669
  if (!UserLocaleStore?.subscribe || typeof UserLocaleStore.get !== "function") {
630
670
  this.debug.error(
631
- "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."
632
672
  );
633
673
  return { status: false, errors: ["Missing UserLocaleStore"] };
634
674
  }
635
- baseLocale = baseLocale.toLowerCase();
675
+ baseLocale = canonicalizeLocale(baseLocale);
636
676
  if (!emulateFailureToLoad) {
637
677
  this.config = {
638
678
  projectid,
@@ -671,8 +711,9 @@ var LangsysAppClass = class {
671
711
  if (initialTranslations2 && initialTranslationsLocale) {
672
712
  this.Translations.markLoaded(initialTranslationsLocale);
673
713
  }
674
- this.config.sUserLocale.subscribe((locale) => {
714
+ this.config.sUserLocale.subscribe((rawLocale) => {
675
715
  if (!validateResponse.status) return;
716
+ const locale = canonicalizeLocale(rawLocale);
676
717
  this.getLocalesData(locale);
677
718
  this.debug.log("SUBSCRIBING TO USER LOCALE STORE");
678
719
  this.translationsLoadingPromise = this.Translations.change(locale, false);
@@ -680,7 +721,7 @@ var LangsysAppClass = class {
680
721
  return validateResponse;
681
722
  }
682
723
  async getDialCodes(inLocale) {
683
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
724
+ const locale = this.resolveLocale(inLocale);
684
725
  if (this.dialCodes.length && this.dialCodesLocale === locale) return this.dialCodes;
685
726
  const route = `countries/dial-codes/${locale}`;
686
727
  const response = await LangsysAppAPI.get(route);
@@ -693,7 +734,7 @@ var LangsysAppClass = class {
693
734
  return this.dialCodes;
694
735
  }
695
736
  async getCurrencies(inLocale) {
696
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
737
+ const locale = this.resolveLocale(inLocale);
697
738
  if (this.currencies.length && this.currenciesLocale === locale) return this.currencies;
698
739
  const route = `currencies/${locale}`;
699
740
  const response = await LangsysAppAPI.get(route);
@@ -706,7 +747,7 @@ var LangsysAppClass = class {
706
747
  return this.currencies;
707
748
  }
708
749
  async getCountries(inLocale) {
709
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
750
+ const locale = this.resolveLocale(inLocale);
710
751
  if (this.countries.length && this.countriesLocale === locale) return this.countries;
711
752
  const route = `countries/${locale}`;
712
753
  const response = await LangsysAppAPI.get(route);
@@ -720,7 +761,7 @@ var LangsysAppClass = class {
720
761
  }
721
762
  async getCountryName(forCountryCode, inLocale) {
722
763
  if (!forCountryCode) return "";
723
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
764
+ const locale = this.resolveLocale(inLocale);
724
765
  if (!this.countries.length || this.countriesLocale !== locale) await this.getCountries(inLocale);
725
766
  const country = this.countries.find((c) => c.code.toLowerCase() === forCountryCode.toLowerCase())?.label;
726
767
  if (!country) this.debug.warn("getCountryName failed to match", forCountryCode);
@@ -728,14 +769,14 @@ var LangsysAppClass = class {
728
769
  }
729
770
  async getCurrencyName(forCurrencyCode, inLocale) {
730
771
  if (!forCurrencyCode) return "";
731
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
772
+ const locale = this.resolveLocale(inLocale);
732
773
  if (!this.currencies.length || this.currenciesLocale !== locale) await this.getCurrencies(inLocale);
733
774
  const currency = this.currencies.find((c) => c.code?.toLowerCase() === forCurrencyCode.toLowerCase())?.name;
734
775
  if (!currency) this.debug.warn("getCurrencyName failed to match", forCurrencyCode);
735
776
  return currency || forCurrencyCode;
736
777
  }
737
778
  async getLocalesFormat(format = "", inLocale) {
738
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
779
+ const locale = this.resolveLocale(inLocale);
739
780
  const route = `locales/${locale}` + (format ? `/${format}` : "");
740
781
  const response = await LangsysAppAPI.get(route);
741
782
  if (response.errors || !response.status) {
@@ -756,7 +797,7 @@ var LangsysAppClass = class {
756
797
  return await this.getLocalesFormat("flat", inLocale);
757
798
  }
758
799
  async getLocalesData(inLocale, forceRefresh = false) {
759
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
800
+ const locale = this.resolveLocale(inLocale);
760
801
  if (this.locales?.[locale]?.length && !forceRefresh) return this.locales[locale];
761
802
  this.locales[locale] = await this.getLocalesFormat("data", inLocale);
762
803
  return this.locales[locale];
@@ -766,15 +807,16 @@ var LangsysAppClass = class {
766
807
  }
767
808
  async getLocaleNameWithLookup(forLocale, shortName = false, inLocale) {
768
809
  if (!forLocale) return "";
769
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
810
+ const locale = this.resolveLocale(inLocale);
770
811
  await this.getLocalesData(locale);
771
812
  return this.getLocaleName(forLocale, shortName, locale);
772
813
  }
773
814
  getLocaleName(forLocale, shortName = false, inLocale) {
774
- const locale = inLocale || this.config.sUserLocale.get() || this.config.baseLocale;
815
+ const locale = this.resolveLocale(inLocale);
816
+ const target = canonicalizeLocale(forLocale).toLowerCase();
775
817
  let name = "";
776
818
  this.locales[locale]?.every((loc) => {
777
- if (loc.code === forLocale) {
819
+ if (loc.code.toLowerCase() === target) {
778
820
  name = shortName ? loc.lang_name : loc.locale_name;
779
821
  return false;
780
822
  }
@@ -800,7 +842,7 @@ var LangsysAppClass = class {
800
842
  const bestMatch = this.findBestLocaleMatch(userLocales, supportedLocales);
801
843
  if (bestMatch) return bestMatch;
802
844
  }
803
- return this.normalizeLocale(userLocales[0]);
845
+ return canonicalizeLocale(userLocales[0]);
804
846
  }
805
847
  getUserLanguagePreferences(acceptLanguageHeader) {
806
848
  if (acceptLanguageHeader) {
@@ -837,30 +879,26 @@ var LangsysAppClass = class {
837
879
  }
838
880
  findBestLocaleMatch(userLocales, supportedLocales) {
839
881
  if (!userLocales.length || !supportedLocales.length) return null;
840
- const normalizedSupported = supportedLocales.map((l) => this.normalizeLocale(l));
882
+ const supported = supportedLocales.map((l) => canonicalizeLocale(l));
841
883
  for (const userLocale of userLocales) {
842
- const normalizedUser = this.normalizeLocale(userLocale);
843
- if (normalizedSupported.includes(normalizedUser)) return normalizedUser;
884
+ const user = canonicalizeLocale(userLocale);
885
+ if (supported.includes(user)) return user;
844
886
  }
845
887
  for (const userLocale of userLocales) {
846
- const userLang = this.getLanguageCode(userLocale);
847
- for (const supported of normalizedSupported) {
848
- if (this.getLanguageCode(supported) === userLang) return supported;
849
- }
888
+ const userKey = maximizedLangScript(userLocale);
889
+ if (!userKey) continue;
890
+ const match = supported.find((s) => maximizedLangScript(s) === userKey);
891
+ if (match) return match;
850
892
  }
851
893
  return null;
852
894
  }
853
- normalizeLocale(locale) {
854
- if (!locale || typeof locale !== "string") return locale;
855
- const parts = locale.split(/[-_]/);
856
- if (parts.length === 1) return parts[0].toLowerCase();
857
- const language = parts[0].toLowerCase();
858
- const region = parts[1].toUpperCase();
859
- return `${language}-${region}`;
860
- }
861
- getLanguageCode(locale) {
862
- if (!locale || typeof locale !== "string") return "";
863
- 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);
864
902
  }
865
903
  };
866
904
  var LangsysApp = new LangsysAppClass();
@@ -1486,6 +1524,6 @@ var t = ((phrase, ...rest) => {
1486
1524
  });
1487
1525
  var tSignal = LangsysApp.Translations.tSignal;
1488
1526
 
1489
- 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 };
1490
1528
  //# sourceMappingURL=index.mjs.map
1491
1529
  //# sourceMappingURL=index.mjs.map