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/CHANGELOG.md +19 -0
- package/README.md +10 -10
- package/dist/index.d.mts +36 -4
- package/dist/index.d.ts +36 -4
- package/dist/index.js +82 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +82 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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)
|
|
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-
|
|
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
|
|
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((
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ===
|
|
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
|
|
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
|
|
882
|
+
const supported = supportedLocales.map((l) => canonicalizeLocale(l));
|
|
839
883
|
for (const userLocale of userLocales) {
|
|
840
|
-
const
|
|
841
|
-
if (
|
|
884
|
+
const user = canonicalizeLocale(userLocale);
|
|
885
|
+
if (supported.includes(user)) return user;
|
|
842
886
|
}
|
|
843
887
|
for (const userLocale of userLocales) {
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
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
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
return
|
|
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
|