@shipeasy/sdk 2.1.7 → 2.1.8
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/client/index.d.mts +21 -27
- package/dist/client/index.d.ts +21 -27
- package/dist/client/index.js +130 -17
- package/dist/client/index.mjs +130 -17
- package/dist/server/index.js +50 -15
- package/dist/server/index.mjs +50 -15
- package/package.json +10 -17
package/dist/client/index.d.mts
CHANGED
|
@@ -192,37 +192,31 @@ interface LabelAttrs {
|
|
|
192
192
|
"data-label-desc"?: string;
|
|
193
193
|
}
|
|
194
194
|
declare function labelAttrs(key: string, variables?: Record<string, string | number>, desc?: string): LabelAttrs;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
*
|
|
212
|
-
* Falls back to a plain translated string if `createElement` was not
|
|
213
|
-
* configured (e.g. server-side or in non-JSX contexts).
|
|
214
|
-
*/
|
|
215
|
-
tEl(key: string, fallback: string, variables?: Record<string, string | number>, desc?: string): any;
|
|
216
|
-
/** Wire up the element creator once at app startup (call before any tEl use). */
|
|
195
|
+
type I18nVariables = Record<string, string | number | null | undefined>;
|
|
196
|
+
type I18nTagRenderer = (content: string) => unknown;
|
|
197
|
+
type I18nRichComponents = Record<string, I18nTagRenderer>;
|
|
198
|
+
declare const __i18nKeyBrand: unique symbol;
|
|
199
|
+
declare const __i18nStringBrand: unique symbol;
|
|
200
|
+
type I18nKey = string & {
|
|
201
|
+
readonly [__i18nKeyBrand]?: never;
|
|
202
|
+
};
|
|
203
|
+
type I18nString = string & {
|
|
204
|
+
readonly [__i18nStringBrand]?: never;
|
|
205
|
+
};
|
|
206
|
+
interface I18nFacade {
|
|
207
|
+
t<F extends string>(key: I18nKey, fallback: F, variables?: I18nVariables): F & I18nString;
|
|
208
|
+
t(key: I18nKey, variables?: I18nVariables): I18nString;
|
|
209
|
+
rich(key: I18nKey, fallback: string, components?: I18nRichComponents, variables?: I18nVariables): unknown;
|
|
210
|
+
tEl<F extends string>(key: I18nKey, fallback: F, variables?: I18nVariables, desc?: string): F & I18nString;
|
|
217
211
|
configure(opts: {
|
|
218
|
-
|
|
212
|
+
components?: I18nRichComponents;
|
|
213
|
+
createElement?: (tag: string, props: object, children: string) => unknown;
|
|
219
214
|
}): void;
|
|
220
215
|
readonly locale: string | null;
|
|
221
216
|
readonly ready: boolean;
|
|
222
|
-
/** Resolves when the loader has installed window.i18n and fetched a profile. */
|
|
223
217
|
whenReady(): Promise<void>;
|
|
224
|
-
/** Subscribe to locale/profile updates. Returns an unsubscribe fn. */
|
|
225
218
|
onUpdate(cb: () => void): () => void;
|
|
226
|
-
}
|
|
219
|
+
}
|
|
220
|
+
declare const i18n: I18nFacade;
|
|
227
221
|
|
|
228
|
-
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
|
222
|
+
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -192,37 +192,31 @@ interface LabelAttrs {
|
|
|
192
192
|
"data-label-desc"?: string;
|
|
193
193
|
}
|
|
194
194
|
declare function labelAttrs(key: string, variables?: Record<string, string | number>, desc?: string): LabelAttrs;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
*
|
|
212
|
-
* Falls back to a plain translated string if `createElement` was not
|
|
213
|
-
* configured (e.g. server-side or in non-JSX contexts).
|
|
214
|
-
*/
|
|
215
|
-
tEl(key: string, fallback: string, variables?: Record<string, string | number>, desc?: string): any;
|
|
216
|
-
/** Wire up the element creator once at app startup (call before any tEl use). */
|
|
195
|
+
type I18nVariables = Record<string, string | number | null | undefined>;
|
|
196
|
+
type I18nTagRenderer = (content: string) => unknown;
|
|
197
|
+
type I18nRichComponents = Record<string, I18nTagRenderer>;
|
|
198
|
+
declare const __i18nKeyBrand: unique symbol;
|
|
199
|
+
declare const __i18nStringBrand: unique symbol;
|
|
200
|
+
type I18nKey = string & {
|
|
201
|
+
readonly [__i18nKeyBrand]?: never;
|
|
202
|
+
};
|
|
203
|
+
type I18nString = string & {
|
|
204
|
+
readonly [__i18nStringBrand]?: never;
|
|
205
|
+
};
|
|
206
|
+
interface I18nFacade {
|
|
207
|
+
t<F extends string>(key: I18nKey, fallback: F, variables?: I18nVariables): F & I18nString;
|
|
208
|
+
t(key: I18nKey, variables?: I18nVariables): I18nString;
|
|
209
|
+
rich(key: I18nKey, fallback: string, components?: I18nRichComponents, variables?: I18nVariables): unknown;
|
|
210
|
+
tEl<F extends string>(key: I18nKey, fallback: F, variables?: I18nVariables, desc?: string): F & I18nString;
|
|
217
211
|
configure(opts: {
|
|
218
|
-
|
|
212
|
+
components?: I18nRichComponents;
|
|
213
|
+
createElement?: (tag: string, props: object, children: string) => unknown;
|
|
219
214
|
}): void;
|
|
220
215
|
readonly locale: string | null;
|
|
221
216
|
readonly ready: boolean;
|
|
222
|
-
/** Resolves when the loader has installed window.i18n and fetched a profile. */
|
|
223
217
|
whenReady(): Promise<void>;
|
|
224
|
-
/** Subscribe to locale/profile updates. Returns an unsubscribe fn. */
|
|
225
218
|
onUpdate(cb: () => void): () => void;
|
|
226
|
-
}
|
|
219
|
+
}
|
|
220
|
+
declare const i18n: I18nFacade;
|
|
227
221
|
|
|
228
|
-
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
|
222
|
+
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
package/dist/client/index.js
CHANGED
|
@@ -455,13 +455,15 @@ var FlagsClientBrowser = class {
|
|
|
455
455
|
this.evalResult = data;
|
|
456
456
|
}
|
|
457
457
|
getFlag(name) {
|
|
458
|
+
if (this.evalResult === null) return false;
|
|
458
459
|
const ov = readGateOverride(name);
|
|
459
460
|
if (ov !== null) return ov;
|
|
460
|
-
return this.evalResult
|
|
461
|
+
return this.evalResult.flags[name] ?? false;
|
|
461
462
|
}
|
|
462
463
|
getConfig(name, decode) {
|
|
464
|
+
if (this.evalResult === null) return void 0;
|
|
463
465
|
const ov = readConfigOverride(name);
|
|
464
|
-
const raw = ov !== void 0 ? ov : this.evalResult
|
|
466
|
+
const raw = ov !== void 0 ? ov : this.evalResult.configs?.[name];
|
|
465
467
|
if (raw === void 0) return void 0;
|
|
466
468
|
if (!decode) return raw;
|
|
467
469
|
try {
|
|
@@ -797,15 +799,114 @@ function isEditLabelsMode() {
|
|
|
797
799
|
}
|
|
798
800
|
function interpolate(raw, variables) {
|
|
799
801
|
if (!variables) return raw;
|
|
800
|
-
return raw.replace(/\{\{(\w+)\}\}/g, (
|
|
802
|
+
return raw.replace(/\{\{(\w+)\}\}/g, (placeholder, k) => {
|
|
803
|
+
const v = variables[k];
|
|
804
|
+
return v != null ? String(v) : placeholder;
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
var _IS_BROWSER = typeof document !== "undefined";
|
|
808
|
+
var _RICH_HTML_TAGS = [
|
|
809
|
+
"b",
|
|
810
|
+
"i",
|
|
811
|
+
"u",
|
|
812
|
+
"s",
|
|
813
|
+
"em",
|
|
814
|
+
"strong",
|
|
815
|
+
"del",
|
|
816
|
+
"ins",
|
|
817
|
+
"mark",
|
|
818
|
+
"small",
|
|
819
|
+
"code",
|
|
820
|
+
"pre",
|
|
821
|
+
"kbd",
|
|
822
|
+
"sub",
|
|
823
|
+
"sup",
|
|
824
|
+
"span",
|
|
825
|
+
"a",
|
|
826
|
+
"p",
|
|
827
|
+
"br",
|
|
828
|
+
"hr"
|
|
829
|
+
];
|
|
830
|
+
function _makeBuiltinTags() {
|
|
831
|
+
const tags = {};
|
|
832
|
+
for (const tag of _RICH_HTML_TAGS) {
|
|
833
|
+
tags[tag] = _IS_BROWSER ? (text) => {
|
|
834
|
+
const el = document.createElement(tag);
|
|
835
|
+
if (tag !== "br" && tag !== "hr") el.textContent = text;
|
|
836
|
+
return el;
|
|
837
|
+
} : (text) => tag === "br" || tag === "hr" ? `<${tag}>` : `<${tag}>${text}</${tag}>`;
|
|
838
|
+
}
|
|
839
|
+
return tags;
|
|
840
|
+
}
|
|
841
|
+
var _builtinTags = _makeBuiltinTags();
|
|
842
|
+
var _configuredComponents = {};
|
|
843
|
+
var _RICH_TAG_RE = /<(\w+)(?:\s*\/>|>([\s\S]*?)<\/\1>)/g;
|
|
844
|
+
function _parseRichText(text, components) {
|
|
845
|
+
const parts = [];
|
|
846
|
+
let lastIndex = 0;
|
|
847
|
+
let match;
|
|
848
|
+
let allStrings = true;
|
|
849
|
+
_RICH_TAG_RE.lastIndex = 0;
|
|
850
|
+
while ((match = _RICH_TAG_RE.exec(text)) !== null) {
|
|
851
|
+
if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
|
|
852
|
+
const tag = match[1];
|
|
853
|
+
const content = match[2] ?? "";
|
|
854
|
+
const renderer = components[tag] ?? _configuredComponents[tag] ?? _builtinTags[tag];
|
|
855
|
+
if (renderer) {
|
|
856
|
+
const rendered = renderer(content);
|
|
857
|
+
if (typeof rendered !== "string") allStrings = false;
|
|
858
|
+
parts.push(rendered);
|
|
859
|
+
} else {
|
|
860
|
+
parts.push(content);
|
|
861
|
+
}
|
|
862
|
+
lastIndex = _RICH_TAG_RE.lastIndex;
|
|
863
|
+
}
|
|
864
|
+
if (lastIndex < text.length) parts.push(text.slice(lastIndex));
|
|
865
|
+
if (allStrings) return parts.join("");
|
|
866
|
+
return parts;
|
|
867
|
+
}
|
|
868
|
+
function _resolveTranslation(key, variables) {
|
|
869
|
+
if (typeof window !== "undefined" && window.i18n) {
|
|
870
|
+
const v = window.i18n.t(key, variables);
|
|
871
|
+
return v === key ? void 0 : v;
|
|
872
|
+
}
|
|
873
|
+
const store = getSSRI18nStore();
|
|
874
|
+
if (store?.strings[key]) return interpolate(store.strings[key], variables);
|
|
875
|
+
return void 0;
|
|
801
876
|
}
|
|
802
877
|
var i18n = {
|
|
803
|
-
t(key,
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
if (
|
|
878
|
+
t(key, fallbackOrVars, maybeVars) {
|
|
879
|
+
let fallback;
|
|
880
|
+
let variables;
|
|
881
|
+
if (typeof fallbackOrVars === "string") {
|
|
882
|
+
fallback = fallbackOrVars;
|
|
883
|
+
variables = maybeVars;
|
|
884
|
+
} else {
|
|
885
|
+
variables = fallbackOrVars;
|
|
886
|
+
}
|
|
887
|
+
const resolved = _resolveTranslation(key, variables);
|
|
888
|
+
if (resolved !== void 0) return resolved;
|
|
889
|
+
if (fallback !== void 0) return interpolate(fallback, variables);
|
|
807
890
|
return key;
|
|
808
891
|
},
|
|
892
|
+
/**
|
|
893
|
+
* Translate a key whose value contains `<tag>content</tag>` segments and
|
|
894
|
+
* render the tagged segments via per-call `components`, `configure()`-supplied
|
|
895
|
+
* components, or the built-in HTML tag renderers.
|
|
896
|
+
*
|
|
897
|
+
* Return shape:
|
|
898
|
+
* - all renderers return strings → returns a concatenated `string`
|
|
899
|
+
* - any renderer returns a non-string (e.g. JSX, DOM node) → returns
|
|
900
|
+
* `Array<string | T>` and the caller is responsible for rendering
|
|
901
|
+
*
|
|
902
|
+
* Framework-agnostic: this method does pure string parsing + callback
|
|
903
|
+
* execution. No React / DOM dependency in the SDK itself.
|
|
904
|
+
*/
|
|
905
|
+
rich(key, fallback, components, variables) {
|
|
906
|
+
const resolved = _resolveTranslation(key, variables);
|
|
907
|
+
const raw = resolved ?? interpolate(fallback, variables);
|
|
908
|
+
return _parseRichText(raw, components ?? {});
|
|
909
|
+
},
|
|
809
910
|
/**
|
|
810
911
|
* Translate a key and return a framework element (e.g. React <span>)
|
|
811
912
|
* carrying `data-label` / `data-variables` attributes so the ShipEasy
|
|
@@ -818,18 +919,30 @@ var i18n = {
|
|
|
818
919
|
* Falls back to a plain translated string if `createElement` was not
|
|
819
920
|
* configured (e.g. server-side or in non-JSX contexts).
|
|
820
921
|
*/
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
922
|
+
/**
|
|
923
|
+
* @deprecated Use `t(key, fallback, variables)` instead. tEl() now delegates
|
|
924
|
+
* to t() and returns the translated string. Prior behaviour (createElement
|
|
925
|
+
* wrapping + edit-mode markers) was a devtools feature that conflicted with
|
|
926
|
+
* type-safe usage and has been removed.
|
|
927
|
+
*/
|
|
928
|
+
tEl(key, fallback, variables, _desc) {
|
|
929
|
+
if (isEditLabelsMode()) {
|
|
930
|
+
const resolved = _resolveTranslation(key, variables);
|
|
931
|
+
const text = resolved ?? interpolate(fallback, variables);
|
|
932
|
+
return encodeLabelMarker(key, text);
|
|
933
|
+
}
|
|
934
|
+
return this.t(key, fallback, variables);
|
|
828
935
|
},
|
|
829
|
-
/**
|
|
830
|
-
|
|
936
|
+
/**
|
|
937
|
+
* Configure global rich-text component overrides and (legacy) the createElement
|
|
938
|
+
* factory. `components` registers default renderers used by `rich()` when no
|
|
939
|
+
* per-call override is supplied (e.g. swap `<a>` for a framework Link).
|
|
940
|
+
*/
|
|
831
941
|
configure(opts) {
|
|
832
|
-
|
|
942
|
+
if (opts.components) {
|
|
943
|
+
_configuredComponents = { ..._configuredComponents, ...opts.components };
|
|
944
|
+
}
|
|
945
|
+
if (opts.createElement) _createElement = opts.createElement;
|
|
833
946
|
},
|
|
834
947
|
get locale() {
|
|
835
948
|
if (typeof window !== "undefined" && window.i18n) return window.i18n.locale;
|
package/dist/client/index.mjs
CHANGED
|
@@ -412,13 +412,15 @@ var FlagsClientBrowser = class {
|
|
|
412
412
|
this.evalResult = data;
|
|
413
413
|
}
|
|
414
414
|
getFlag(name) {
|
|
415
|
+
if (this.evalResult === null) return false;
|
|
415
416
|
const ov = readGateOverride(name);
|
|
416
417
|
if (ov !== null) return ov;
|
|
417
|
-
return this.evalResult
|
|
418
|
+
return this.evalResult.flags[name] ?? false;
|
|
418
419
|
}
|
|
419
420
|
getConfig(name, decode) {
|
|
421
|
+
if (this.evalResult === null) return void 0;
|
|
420
422
|
const ov = readConfigOverride(name);
|
|
421
|
-
const raw = ov !== void 0 ? ov : this.evalResult
|
|
423
|
+
const raw = ov !== void 0 ? ov : this.evalResult.configs?.[name];
|
|
422
424
|
if (raw === void 0) return void 0;
|
|
423
425
|
if (!decode) return raw;
|
|
424
426
|
try {
|
|
@@ -754,15 +756,114 @@ function isEditLabelsMode() {
|
|
|
754
756
|
}
|
|
755
757
|
function interpolate(raw, variables) {
|
|
756
758
|
if (!variables) return raw;
|
|
757
|
-
return raw.replace(/\{\{(\w+)\}\}/g, (
|
|
759
|
+
return raw.replace(/\{\{(\w+)\}\}/g, (placeholder, k) => {
|
|
760
|
+
const v = variables[k];
|
|
761
|
+
return v != null ? String(v) : placeholder;
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
var _IS_BROWSER = typeof document !== "undefined";
|
|
765
|
+
var _RICH_HTML_TAGS = [
|
|
766
|
+
"b",
|
|
767
|
+
"i",
|
|
768
|
+
"u",
|
|
769
|
+
"s",
|
|
770
|
+
"em",
|
|
771
|
+
"strong",
|
|
772
|
+
"del",
|
|
773
|
+
"ins",
|
|
774
|
+
"mark",
|
|
775
|
+
"small",
|
|
776
|
+
"code",
|
|
777
|
+
"pre",
|
|
778
|
+
"kbd",
|
|
779
|
+
"sub",
|
|
780
|
+
"sup",
|
|
781
|
+
"span",
|
|
782
|
+
"a",
|
|
783
|
+
"p",
|
|
784
|
+
"br",
|
|
785
|
+
"hr"
|
|
786
|
+
];
|
|
787
|
+
function _makeBuiltinTags() {
|
|
788
|
+
const tags = {};
|
|
789
|
+
for (const tag of _RICH_HTML_TAGS) {
|
|
790
|
+
tags[tag] = _IS_BROWSER ? (text) => {
|
|
791
|
+
const el = document.createElement(tag);
|
|
792
|
+
if (tag !== "br" && tag !== "hr") el.textContent = text;
|
|
793
|
+
return el;
|
|
794
|
+
} : (text) => tag === "br" || tag === "hr" ? `<${tag}>` : `<${tag}>${text}</${tag}>`;
|
|
795
|
+
}
|
|
796
|
+
return tags;
|
|
797
|
+
}
|
|
798
|
+
var _builtinTags = _makeBuiltinTags();
|
|
799
|
+
var _configuredComponents = {};
|
|
800
|
+
var _RICH_TAG_RE = /<(\w+)(?:\s*\/>|>([\s\S]*?)<\/\1>)/g;
|
|
801
|
+
function _parseRichText(text, components) {
|
|
802
|
+
const parts = [];
|
|
803
|
+
let lastIndex = 0;
|
|
804
|
+
let match;
|
|
805
|
+
let allStrings = true;
|
|
806
|
+
_RICH_TAG_RE.lastIndex = 0;
|
|
807
|
+
while ((match = _RICH_TAG_RE.exec(text)) !== null) {
|
|
808
|
+
if (match.index > lastIndex) parts.push(text.slice(lastIndex, match.index));
|
|
809
|
+
const tag = match[1];
|
|
810
|
+
const content = match[2] ?? "";
|
|
811
|
+
const renderer = components[tag] ?? _configuredComponents[tag] ?? _builtinTags[tag];
|
|
812
|
+
if (renderer) {
|
|
813
|
+
const rendered = renderer(content);
|
|
814
|
+
if (typeof rendered !== "string") allStrings = false;
|
|
815
|
+
parts.push(rendered);
|
|
816
|
+
} else {
|
|
817
|
+
parts.push(content);
|
|
818
|
+
}
|
|
819
|
+
lastIndex = _RICH_TAG_RE.lastIndex;
|
|
820
|
+
}
|
|
821
|
+
if (lastIndex < text.length) parts.push(text.slice(lastIndex));
|
|
822
|
+
if (allStrings) return parts.join("");
|
|
823
|
+
return parts;
|
|
824
|
+
}
|
|
825
|
+
function _resolveTranslation(key, variables) {
|
|
826
|
+
if (typeof window !== "undefined" && window.i18n) {
|
|
827
|
+
const v = window.i18n.t(key, variables);
|
|
828
|
+
return v === key ? void 0 : v;
|
|
829
|
+
}
|
|
830
|
+
const store = getSSRI18nStore();
|
|
831
|
+
if (store?.strings[key]) return interpolate(store.strings[key], variables);
|
|
832
|
+
return void 0;
|
|
758
833
|
}
|
|
759
834
|
var i18n = {
|
|
760
|
-
t(key,
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
if (
|
|
835
|
+
t(key, fallbackOrVars, maybeVars) {
|
|
836
|
+
let fallback;
|
|
837
|
+
let variables;
|
|
838
|
+
if (typeof fallbackOrVars === "string") {
|
|
839
|
+
fallback = fallbackOrVars;
|
|
840
|
+
variables = maybeVars;
|
|
841
|
+
} else {
|
|
842
|
+
variables = fallbackOrVars;
|
|
843
|
+
}
|
|
844
|
+
const resolved = _resolveTranslation(key, variables);
|
|
845
|
+
if (resolved !== void 0) return resolved;
|
|
846
|
+
if (fallback !== void 0) return interpolate(fallback, variables);
|
|
764
847
|
return key;
|
|
765
848
|
},
|
|
849
|
+
/**
|
|
850
|
+
* Translate a key whose value contains `<tag>content</tag>` segments and
|
|
851
|
+
* render the tagged segments via per-call `components`, `configure()`-supplied
|
|
852
|
+
* components, or the built-in HTML tag renderers.
|
|
853
|
+
*
|
|
854
|
+
* Return shape:
|
|
855
|
+
* - all renderers return strings → returns a concatenated `string`
|
|
856
|
+
* - any renderer returns a non-string (e.g. JSX, DOM node) → returns
|
|
857
|
+
* `Array<string | T>` and the caller is responsible for rendering
|
|
858
|
+
*
|
|
859
|
+
* Framework-agnostic: this method does pure string parsing + callback
|
|
860
|
+
* execution. No React / DOM dependency in the SDK itself.
|
|
861
|
+
*/
|
|
862
|
+
rich(key, fallback, components, variables) {
|
|
863
|
+
const resolved = _resolveTranslation(key, variables);
|
|
864
|
+
const raw = resolved ?? interpolate(fallback, variables);
|
|
865
|
+
return _parseRichText(raw, components ?? {});
|
|
866
|
+
},
|
|
766
867
|
/**
|
|
767
868
|
* Translate a key and return a framework element (e.g. React <span>)
|
|
768
869
|
* carrying `data-label` / `data-variables` attributes so the ShipEasy
|
|
@@ -775,18 +876,30 @@ var i18n = {
|
|
|
775
876
|
* Falls back to a plain translated string if `createElement` was not
|
|
776
877
|
* configured (e.g. server-side or in non-JSX contexts).
|
|
777
878
|
*/
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
879
|
+
/**
|
|
880
|
+
* @deprecated Use `t(key, fallback, variables)` instead. tEl() now delegates
|
|
881
|
+
* to t() and returns the translated string. Prior behaviour (createElement
|
|
882
|
+
* wrapping + edit-mode markers) was a devtools feature that conflicted with
|
|
883
|
+
* type-safe usage and has been removed.
|
|
884
|
+
*/
|
|
885
|
+
tEl(key, fallback, variables, _desc) {
|
|
886
|
+
if (isEditLabelsMode()) {
|
|
887
|
+
const resolved = _resolveTranslation(key, variables);
|
|
888
|
+
const text = resolved ?? interpolate(fallback, variables);
|
|
889
|
+
return encodeLabelMarker(key, text);
|
|
890
|
+
}
|
|
891
|
+
return this.t(key, fallback, variables);
|
|
785
892
|
},
|
|
786
|
-
/**
|
|
787
|
-
|
|
893
|
+
/**
|
|
894
|
+
* Configure global rich-text component overrides and (legacy) the createElement
|
|
895
|
+
* factory. `components` registers default renderers used by `rich()` when no
|
|
896
|
+
* per-call override is supplied (e.g. swap `<a>` for a framework Link).
|
|
897
|
+
*/
|
|
788
898
|
configure(opts) {
|
|
789
|
-
|
|
899
|
+
if (opts.components) {
|
|
900
|
+
_configuredComponents = { ..._configuredComponents, ...opts.components };
|
|
901
|
+
}
|
|
902
|
+
if (opts.createElement) _createElement = opts.createElement;
|
|
790
903
|
},
|
|
791
904
|
get locale() {
|
|
792
905
|
if (typeof window !== "undefined" && window.i18n) return window.i18n.locale;
|
package/dist/server/index.js
CHANGED
|
@@ -366,10 +366,31 @@ var FlagsClient = class {
|
|
|
366
366
|
var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
|
|
367
367
|
var _EDIT_MODE_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode");
|
|
368
368
|
var _i18nALS = new import_node_async_hooks.AsyncLocalStorage();
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
369
|
+
var _I18N_CACHE_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n-cache");
|
|
370
|
+
var _i18nCache = globalThis[_I18N_CACHE_SYM] ?? (globalThis[_I18N_CACHE_SYM] = /* @__PURE__ */ new Map());
|
|
371
|
+
globalThis[_I18N_SSR_SYM] = () => {
|
|
372
|
+
const fromALS = _i18nALS.getStore();
|
|
373
|
+
if (fromALS && Object.keys(fromALS.strings).length > 0) return fromALS;
|
|
374
|
+
for (const v of _i18nCache.values()) {
|
|
375
|
+
if (Object.keys(v.strings).length > 0) return v;
|
|
376
|
+
}
|
|
377
|
+
return fromALS ?? null;
|
|
378
|
+
};
|
|
379
|
+
var _EDIT_MODE_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-als");
|
|
380
|
+
var _editModeALS = globalThis[_EDIT_MODE_ALS_SYM] ?? (globalThis[_EDIT_MODE_ALS_SYM] = new import_node_async_hooks.AsyncLocalStorage());
|
|
381
|
+
var _EDIT_MODE_FALLBACK_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-fallback");
|
|
382
|
+
if (globalThis[_EDIT_MODE_FALLBACK_SYM] === void 0) {
|
|
383
|
+
globalThis[_EDIT_MODE_FALLBACK_SYM] = false;
|
|
372
384
|
}
|
|
385
|
+
Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
|
|
386
|
+
get: () => _editModeALS.getStore() ?? globalThis[_EDIT_MODE_FALLBACK_SYM] ?? false,
|
|
387
|
+
set: (v) => {
|
|
388
|
+
const b = Boolean(v);
|
|
389
|
+
_editModeALS.enterWith(b);
|
|
390
|
+
globalThis[_EDIT_MODE_FALLBACK_SYM] = b;
|
|
391
|
+
},
|
|
392
|
+
configurable: true
|
|
393
|
+
});
|
|
373
394
|
var i18n = {
|
|
374
395
|
/**
|
|
375
396
|
* Fetch translation labels for the current request and store them in an
|
|
@@ -385,10 +406,18 @@ var i18n = {
|
|
|
385
406
|
* @param cdnBaseUrl Optional override for the i18n CDN (default: cdn.i18n.shipeasy.ai)
|
|
386
407
|
*/
|
|
387
408
|
async init(key, profile, cdnBaseUrl) {
|
|
388
|
-
|
|
409
|
+
const existingALS = _i18nALS.getStore();
|
|
410
|
+
if (existingALS && Object.keys(existingALS.strings).length > 0) return;
|
|
411
|
+
const cached = _i18nCache.get(profile);
|
|
412
|
+
if (cached && Object.keys(cached.strings).length > 0) {
|
|
413
|
+
_i18nALS.enterWith(cached);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
389
416
|
const labels = await fetchLabelsForSSR({ key, profile, cdnBaseUrl }).catch(() => null);
|
|
390
417
|
const locale = profile.split(":")[0] || "en";
|
|
391
|
-
|
|
418
|
+
const store = { strings: labels?.strings ?? {}, locale };
|
|
419
|
+
if (Object.keys(store.strings).length > 0) _i18nCache.set(profile, store);
|
|
420
|
+
_i18nALS.enterWith(store);
|
|
392
421
|
},
|
|
393
422
|
/**
|
|
394
423
|
* Return the translation strings loaded for the current request.
|
|
@@ -399,13 +428,14 @@ var i18n = {
|
|
|
399
428
|
return _i18nALS.getStore() ?? { strings: {}, locale: "en" };
|
|
400
429
|
}
|
|
401
430
|
};
|
|
402
|
-
var DEFAULT_I18N_CDN = "https://cdn.
|
|
403
|
-
async function fetchJson(url, timeoutMs = 2e3) {
|
|
431
|
+
var DEFAULT_I18N_CDN = "https://cdn.shipeasy.ai";
|
|
432
|
+
async function fetchJson(url, timeoutMs = 2e3, headers) {
|
|
404
433
|
const controller = new AbortController();
|
|
405
434
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
406
435
|
try {
|
|
407
436
|
const res = await fetch(url, {
|
|
408
437
|
signal: controller.signal,
|
|
438
|
+
headers,
|
|
409
439
|
next: { revalidate: 60 }
|
|
410
440
|
});
|
|
411
441
|
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
|
|
@@ -416,20 +446,24 @@ async function fetchJson(url, timeoutMs = 2e3) {
|
|
|
416
446
|
}
|
|
417
447
|
async function fetchLabelsForSSR(opts) {
|
|
418
448
|
const cdn = opts.cdnBaseUrl ?? DEFAULT_I18N_CDN;
|
|
419
|
-
const chunk = opts.chunk ?? "index";
|
|
420
449
|
try {
|
|
421
|
-
const
|
|
422
|
-
`${cdn}/
|
|
423
|
-
opts.timeoutMs
|
|
450
|
+
const body = await fetchJson(
|
|
451
|
+
`${cdn}/sdk/i18n/strings?profile=${encodeURIComponent(opts.profile)}`,
|
|
452
|
+
opts.timeoutMs,
|
|
453
|
+
{ "X-SDK-Key": opts.key }
|
|
424
454
|
);
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
455
|
+
return {
|
|
456
|
+
v: 1,
|
|
457
|
+
profile: opts.profile,
|
|
458
|
+
chunk: opts.chunk ?? "default",
|
|
459
|
+
strings: body.strings ?? {}
|
|
460
|
+
};
|
|
428
461
|
} catch {
|
|
429
462
|
return null;
|
|
430
463
|
}
|
|
431
464
|
}
|
|
432
465
|
var _server = null;
|
|
466
|
+
var _rememberedClientKey = null;
|
|
433
467
|
function configureShipeasyServer(opts) {
|
|
434
468
|
if (_server) return _server;
|
|
435
469
|
_server = new FlagsClient(opts);
|
|
@@ -449,7 +483,8 @@ async function shipeasy(opts) {
|
|
|
449
483
|
console.warn("[shipeasy] apiKey not set \u2014 falling back to clientKey for server requests.");
|
|
450
484
|
}
|
|
451
485
|
const apiKey = opts.apiKey ?? opts.clientKey ?? "";
|
|
452
|
-
const clientKey = opts.clientKey ?? opts.apiKey ?? "";
|
|
486
|
+
const clientKey = opts.clientKey ?? _rememberedClientKey ?? opts.apiKey ?? "";
|
|
487
|
+
if (opts.clientKey && !_rememberedClientKey) _rememberedClientKey = opts.clientKey;
|
|
453
488
|
const profile = opts.i18nDefaultProfile ?? "en:prod";
|
|
454
489
|
flags.configure({ apiKey });
|
|
455
490
|
let resolvedUrlOverrides = opts.urlOverrides;
|
package/dist/server/index.mjs
CHANGED
|
@@ -323,10 +323,31 @@ var FlagsClient = class {
|
|
|
323
323
|
var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
|
|
324
324
|
var _EDIT_MODE_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode");
|
|
325
325
|
var _i18nALS = new AsyncLocalStorage();
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
var _I18N_CACHE_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n-cache");
|
|
327
|
+
var _i18nCache = globalThis[_I18N_CACHE_SYM] ?? (globalThis[_I18N_CACHE_SYM] = /* @__PURE__ */ new Map());
|
|
328
|
+
globalThis[_I18N_SSR_SYM] = () => {
|
|
329
|
+
const fromALS = _i18nALS.getStore();
|
|
330
|
+
if (fromALS && Object.keys(fromALS.strings).length > 0) return fromALS;
|
|
331
|
+
for (const v of _i18nCache.values()) {
|
|
332
|
+
if (Object.keys(v.strings).length > 0) return v;
|
|
333
|
+
}
|
|
334
|
+
return fromALS ?? null;
|
|
335
|
+
};
|
|
336
|
+
var _EDIT_MODE_ALS_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-als");
|
|
337
|
+
var _editModeALS = globalThis[_EDIT_MODE_ALS_SYM] ?? (globalThis[_EDIT_MODE_ALS_SYM] = new AsyncLocalStorage());
|
|
338
|
+
var _EDIT_MODE_FALLBACK_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-edit-mode-fallback");
|
|
339
|
+
if (globalThis[_EDIT_MODE_FALLBACK_SYM] === void 0) {
|
|
340
|
+
globalThis[_EDIT_MODE_FALLBACK_SYM] = false;
|
|
329
341
|
}
|
|
342
|
+
Object.defineProperty(globalThis, _EDIT_MODE_SSR_SYM, {
|
|
343
|
+
get: () => _editModeALS.getStore() ?? globalThis[_EDIT_MODE_FALLBACK_SYM] ?? false,
|
|
344
|
+
set: (v) => {
|
|
345
|
+
const b = Boolean(v);
|
|
346
|
+
_editModeALS.enterWith(b);
|
|
347
|
+
globalThis[_EDIT_MODE_FALLBACK_SYM] = b;
|
|
348
|
+
},
|
|
349
|
+
configurable: true
|
|
350
|
+
});
|
|
330
351
|
var i18n = {
|
|
331
352
|
/**
|
|
332
353
|
* Fetch translation labels for the current request and store them in an
|
|
@@ -342,10 +363,18 @@ var i18n = {
|
|
|
342
363
|
* @param cdnBaseUrl Optional override for the i18n CDN (default: cdn.i18n.shipeasy.ai)
|
|
343
364
|
*/
|
|
344
365
|
async init(key, profile, cdnBaseUrl) {
|
|
345
|
-
|
|
366
|
+
const existingALS = _i18nALS.getStore();
|
|
367
|
+
if (existingALS && Object.keys(existingALS.strings).length > 0) return;
|
|
368
|
+
const cached = _i18nCache.get(profile);
|
|
369
|
+
if (cached && Object.keys(cached.strings).length > 0) {
|
|
370
|
+
_i18nALS.enterWith(cached);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
346
373
|
const labels = await fetchLabelsForSSR({ key, profile, cdnBaseUrl }).catch(() => null);
|
|
347
374
|
const locale = profile.split(":")[0] || "en";
|
|
348
|
-
|
|
375
|
+
const store = { strings: labels?.strings ?? {}, locale };
|
|
376
|
+
if (Object.keys(store.strings).length > 0) _i18nCache.set(profile, store);
|
|
377
|
+
_i18nALS.enterWith(store);
|
|
349
378
|
},
|
|
350
379
|
/**
|
|
351
380
|
* Return the translation strings loaded for the current request.
|
|
@@ -356,13 +385,14 @@ var i18n = {
|
|
|
356
385
|
return _i18nALS.getStore() ?? { strings: {}, locale: "en" };
|
|
357
386
|
}
|
|
358
387
|
};
|
|
359
|
-
var DEFAULT_I18N_CDN = "https://cdn.
|
|
360
|
-
async function fetchJson(url, timeoutMs = 2e3) {
|
|
388
|
+
var DEFAULT_I18N_CDN = "https://cdn.shipeasy.ai";
|
|
389
|
+
async function fetchJson(url, timeoutMs = 2e3, headers) {
|
|
361
390
|
const controller = new AbortController();
|
|
362
391
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
363
392
|
try {
|
|
364
393
|
const res = await fetch(url, {
|
|
365
394
|
signal: controller.signal,
|
|
395
|
+
headers,
|
|
366
396
|
next: { revalidate: 60 }
|
|
367
397
|
});
|
|
368
398
|
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
|
|
@@ -373,20 +403,24 @@ async function fetchJson(url, timeoutMs = 2e3) {
|
|
|
373
403
|
}
|
|
374
404
|
async function fetchLabelsForSSR(opts) {
|
|
375
405
|
const cdn = opts.cdnBaseUrl ?? DEFAULT_I18N_CDN;
|
|
376
|
-
const chunk = opts.chunk ?? "index";
|
|
377
406
|
try {
|
|
378
|
-
const
|
|
379
|
-
`${cdn}/
|
|
380
|
-
opts.timeoutMs
|
|
407
|
+
const body = await fetchJson(
|
|
408
|
+
`${cdn}/sdk/i18n/strings?profile=${encodeURIComponent(opts.profile)}`,
|
|
409
|
+
opts.timeoutMs,
|
|
410
|
+
{ "X-SDK-Key": opts.key }
|
|
381
411
|
);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
412
|
+
return {
|
|
413
|
+
v: 1,
|
|
414
|
+
profile: opts.profile,
|
|
415
|
+
chunk: opts.chunk ?? "default",
|
|
416
|
+
strings: body.strings ?? {}
|
|
417
|
+
};
|
|
385
418
|
} catch {
|
|
386
419
|
return null;
|
|
387
420
|
}
|
|
388
421
|
}
|
|
389
422
|
var _server = null;
|
|
423
|
+
var _rememberedClientKey = null;
|
|
390
424
|
function configureShipeasyServer(opts) {
|
|
391
425
|
if (_server) return _server;
|
|
392
426
|
_server = new FlagsClient(opts);
|
|
@@ -406,7 +440,8 @@ async function shipeasy(opts) {
|
|
|
406
440
|
console.warn("[shipeasy] apiKey not set \u2014 falling back to clientKey for server requests.");
|
|
407
441
|
}
|
|
408
442
|
const apiKey = opts.apiKey ?? opts.clientKey ?? "";
|
|
409
|
-
const clientKey = opts.clientKey ?? opts.apiKey ?? "";
|
|
443
|
+
const clientKey = opts.clientKey ?? _rememberedClientKey ?? opts.apiKey ?? "";
|
|
444
|
+
if (opts.clientKey && !_rememberedClientKey) _rememberedClientKey = opts.clientKey;
|
|
410
445
|
const profile = opts.i18nDefaultProfile ?? "en:prod";
|
|
411
446
|
flags.configure({ apiKey });
|
|
412
447
|
let resolvedUrlOverrides = opts.urlOverrides;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipeasy/sdk",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.8",
|
|
4
4
|
"description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://shipeasy.ai",
|
|
@@ -48,35 +48,28 @@
|
|
|
48
48
|
"LICENSE",
|
|
49
49
|
"README.md"
|
|
50
50
|
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"type-check": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"publish-loader": "wrangler r2 object put shipeasy-sdk/loader-v$npm_package_version.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=31536000, immutable' --remote && wrangler r2 object put shipeasy-sdk/loader.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=300' --remote"
|
|
57
|
+
},
|
|
51
58
|
"dependencies": {
|
|
52
59
|
"murmurhash-js": "^1.0.0"
|
|
53
60
|
},
|
|
54
61
|
"devDependencies": {
|
|
55
62
|
"@types/murmurhash-js": "^1.0.6",
|
|
63
|
+
"@types/node": "^20.0.0",
|
|
56
64
|
"tsup": "^8.3.0",
|
|
57
65
|
"typescript": "^5.7.4",
|
|
58
66
|
"vitest": "^2.1.0",
|
|
59
67
|
"wrangler": "^4.83.0"
|
|
60
68
|
},
|
|
61
|
-
"peerDependencies": {
|
|
62
|
-
"zod": "^4.0.0"
|
|
63
|
-
},
|
|
64
|
-
"peerDependenciesMeta": {
|
|
65
|
-
"zod": {
|
|
66
|
-
"optional": true
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
69
|
"publishConfig": {
|
|
70
70
|
"access": "public"
|
|
71
71
|
},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=20"
|
|
74
|
-
},
|
|
75
|
-
"scripts": {
|
|
76
|
-
"build": "tsup",
|
|
77
|
-
"type-check": "tsc --noEmit",
|
|
78
|
-
"test": "vitest run",
|
|
79
|
-
"test:watch": "vitest",
|
|
80
|
-
"publish-loader": "wrangler r2 object put shipeasy-sdk/loader-v$npm_package_version.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=31536000, immutable' --remote && wrangler r2 object put shipeasy-sdk/loader.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=300' --remote"
|
|
81
74
|
}
|
|
82
|
-
}
|
|
75
|
+
}
|