@primestyleai/tryon 3.9.2 → 3.11.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.
@@ -15,6 +15,8 @@ export type TranslateFn = (key: string) => string;
15
15
  export declare const SUPPORTED_LOCALES: string[];
16
16
  /** Master list of translatable keys (= the English dictionary). */
17
17
  export declare const TRANSLATION_KEYS: string[];
18
+ /** Human-readable labels for each bundled locale (in the locale's own language). */
19
+ export declare const LOCALE_LABELS: Record<string, string>;
18
20
  /**
19
21
  * Register an external dictionary at runtime.
20
22
  * Useful for custom / community translations not bundled in the SDK.
@@ -2917,6 +2917,28 @@ const DICTIONARIES = {
2917
2917
  };
2918
2918
  const SUPPORTED_LOCALES = Object.keys(DICTIONARIES);
2919
2919
  const TRANSLATION_KEYS = Object.keys(en);
2920
+ const LOCALE_LABELS = {
2921
+ en: "English",
2922
+ es: "Español",
2923
+ fr: "Français",
2924
+ de: "Deutsch",
2925
+ it: "Italiano",
2926
+ sv: "Svenska",
2927
+ ja: "日本語",
2928
+ cs: "Čeština",
2929
+ da: "Dansk",
2930
+ nl: "Nederlands",
2931
+ nb: "Norsk",
2932
+ pl: "Polski",
2933
+ "pt-br": "Português (BR)",
2934
+ "pt-pt": "Português (PT)",
2935
+ fi: "Suomi",
2936
+ tr: "Türkçe",
2937
+ th: "ไทย",
2938
+ "zh-cn": "中文 (简体)",
2939
+ "zh-tw": "中文 (繁體)",
2940
+ ko: "한국어"
2941
+ };
2920
2942
  function registerLocale(locale, dict) {
2921
2943
  DICTIONARIES[locale.toLowerCase()] = dict;
2922
2944
  }
@@ -2939,6 +2961,7 @@ function createT(locale) {
2939
2961
  }
2940
2962
  export {
2941
2963
  ApiClient as A,
2964
+ LOCALE_LABELS as L,
2942
2965
  PrimeStyleError as P,
2943
2966
  SseClient as S,
2944
2967
  TRANSLATION_KEYS as T,
@@ -1,5 +1,5 @@
1
- import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-B0KE3c8S.js";
2
- import { P, b, T, d, r } from "./index-B0KE3c8S.js";
1
+ import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage } from "./index-71-uiSXC.js";
2
+ import { P, b, T, d, r } from "./index-71-uiSXC.js";
3
3
  function detectProductImage() {
4
4
  const ogImage = document.querySelector(
5
5
  'meta[property="og:image"]'
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useMemo, useRef, useCallback } from "react";
4
- import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage, P as PrimeStyleError } from "../index-B0KE3c8S.js";
4
+ import { c as createT, A as ApiClient, S as SseClient, i as isValidImageFile, a as compressImage, P as PrimeStyleError, L as LOCALE_LABELS, b as SUPPORTED_LOCALES } from "../index-71-uiSXC.js";
5
5
  const HEADER_ALIASES = {
6
6
  // ── Size label columns (skipped during field derivation) ──
7
7
  size: "__size__",
@@ -515,6 +515,13 @@ function ChevronRightIcon() {
515
515
  function CheckIcon({ size = 14 }) {
516
516
  return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 3, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
517
517
  }
518
+ function GlobeIcon({ size = 16 }) {
519
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
520
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
521
+ /* @__PURE__ */ jsx("path", { d: "M2 12h20" }),
522
+ /* @__PURE__ */ jsx("path", { d: "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" })
523
+ ] });
524
+ }
518
525
  function getApiKey() {
519
526
  const key = process.env.NEXT_PUBLIC_PRIMESTYLE_API_KEY ?? "";
520
527
  if (!key) throw new PrimeStyleError("Missing NEXT_PUBLIC_PRIMESTYLE_API_KEY", "MISSING_API_KEY");
@@ -553,7 +560,11 @@ function PrimeStyleTryonInner({
553
560
  onError,
554
561
  sizeGuideData
555
562
  }) {
556
- const t = useMemo(() => createT(locale), [locale]);
563
+ const [activeLocale, setActiveLocale] = useState(() => locale || "");
564
+ useEffect(() => {
565
+ if (locale !== void 0) setActiveLocale(locale);
566
+ }, [locale]);
567
+ const t = useMemo(() => createT(activeLocale || void 0), [activeLocale]);
557
568
  const resolvedButtonText = buttonText ?? t("Virtual Try-On");
558
569
  const [view, setView] = useState("idle");
559
570
  const [selectedFile, setSelectedFile] = useState(null);
@@ -1696,6 +1707,42 @@ function PrimeStyleTryonInner({
1696
1707
  ] })
1697
1708
  ] }) });
1698
1709
  }
1710
+ function LangSwitcher({ activeLocale: current, onSelect }) {
1711
+ const [open, setOpen] = useState(false);
1712
+ const ref = useRef(null);
1713
+ const currentLabel = LOCALE_LABELS[current] || "English";
1714
+ useEffect(() => {
1715
+ if (!open) return;
1716
+ const handler = (e) => {
1717
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
1718
+ };
1719
+ document.addEventListener("mousedown", handler);
1720
+ return () => document.removeEventListener("mousedown", handler);
1721
+ }, [open]);
1722
+ return /* @__PURE__ */ jsxs("div", { ref, className: "ps-tryon-lang-wrap", children: [
1723
+ /* @__PURE__ */ jsxs("button", { className: `ps-tryon-lang-trigger${open ? " ps-active" : ""}`, onClick: () => setOpen(!open), children: [
1724
+ /* @__PURE__ */ jsx(GlobeIcon, { size: 15 }),
1725
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-lang-current", children: currentLabel }),
1726
+ /* @__PURE__ */ jsx("span", { className: `ps-tryon-lang-arrow${open ? " ps-open" : ""}`, children: "▾" })
1727
+ ] }),
1728
+ open && /* @__PURE__ */ jsx("div", { className: "ps-tryon-lang-dropdown", children: /* @__PURE__ */ jsx("div", { className: "ps-tryon-lang-list", children: SUPPORTED_LOCALES.map((code) => /* @__PURE__ */ jsxs(
1729
+ "button",
1730
+ {
1731
+ className: `ps-tryon-lang-item${code === current ? " ps-selected" : ""}`,
1732
+ onClick: () => {
1733
+ onSelect(code);
1734
+ setOpen(false);
1735
+ },
1736
+ children: [
1737
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-lang-name", children: LOCALE_LABELS[code] || code }),
1738
+ /* @__PURE__ */ jsx("span", { className: "ps-tryon-lang-code", children: code.toUpperCase() }),
1739
+ code === current && /* @__PURE__ */ jsx("span", { className: "ps-tryon-lang-check", children: /* @__PURE__ */ jsx(CheckIcon, { size: 12 }) })
1740
+ ]
1741
+ },
1742
+ code
1743
+ )) }) })
1744
+ ] });
1745
+ }
1699
1746
  function renderBody() {
1700
1747
  switch (view) {
1701
1748
  case "welcome":
@@ -1729,6 +1776,7 @@ function PrimeStyleTryonInner({
1729
1776
  /* @__PURE__ */ jsxs("div", { className: cx("ps-tryon-header", cn.header), children: [
1730
1777
  /* @__PURE__ */ jsx("span", { className: cx("ps-tryon-title", cn.title), children: t("Virtual Try-On") }),
1731
1778
  /* @__PURE__ */ jsxs("div", { className: "ps-tryon-header-actions", children: [
1779
+ /* @__PURE__ */ jsx(LangSwitcher, { activeLocale, onSelect: setActiveLocale }),
1732
1780
  profiles.length > 0 && /* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("Profiles"), onClick: () => setDrawer(drawer === "profiles" ? null : "profiles"), children: /* @__PURE__ */ jsx(UserIcon, {}) }),
1733
1781
  history.length > 0 && /* @__PURE__ */ jsx("button", { className: "ps-tryon-header-icon", title: t("History"), onClick: () => setDrawer(drawer === "history" ? null : "history"), children: /* @__PURE__ */ jsx(ClockIcon, {}) }),
1734
1782
  /* @__PURE__ */ jsx("button", { onClick: handleClose, className: cx("ps-tryon-close", cn.closeButton), children: /* @__PURE__ */ jsx(XIcon, {}) })
@@ -1813,6 +1861,65 @@ const STYLES = `
1813
1861
  }
1814
1862
  .ps-tryon-close:hover { background: rgba(255,255,255,0.1); }
1815
1863
 
1864
+ /* Language switcher */
1865
+ .ps-tryon-lang-wrap { position: relative; z-index: 10; }
1866
+ .ps-tryon-lang-trigger {
1867
+ display: flex; align-items: center; gap: clamp(5px, 0.36vw, 7px);
1868
+ padding: clamp(5px, 0.36vw, 7px) clamp(10px, 0.73vw, 14px);
1869
+ border: 1.5px solid #333; border-radius: clamp(8px, 0.57vw, 11px);
1870
+ background: transparent; cursor: pointer; color: #999;
1871
+ transition: all 0.25s ease; font-family: inherit; white-space: nowrap;
1872
+ }
1873
+ .ps-tryon-lang-trigger:hover, .ps-tryon-lang-trigger.ps-active {
1874
+ border-color: #bb945c; color: #bb945c; background: rgba(187,148,92,0.06);
1875
+ }
1876
+ .ps-tryon-lang-trigger svg { stroke: currentColor; fill: none; flex-shrink: 0; }
1877
+ .ps-tryon-lang-current {
1878
+ font-size: clamp(11px, 0.68vw, 13px); font-weight: 500; letter-spacing: 0.01em;
1879
+ }
1880
+ .ps-tryon-lang-arrow {
1881
+ font-size: clamp(10px, 0.57vw, 12px); transition: transform 0.25s ease; display: inline-block;
1882
+ }
1883
+ .ps-tryon-lang-arrow.ps-open { transform: rotate(180deg); }
1884
+
1885
+ .ps-tryon-lang-dropdown {
1886
+ position: absolute; top: calc(100% + clamp(6px, 0.42vw, 8px)); right: 0;
1887
+ min-width: clamp(180px, 13vw, 240px);
1888
+ background: #1a1b1a; border: 1.5px solid #333;
1889
+ border-radius: clamp(10px, 0.73vw, 14px);
1890
+ box-shadow: 0 clamp(12px, 1vw, 20px) clamp(40px, 3vw, 60px) rgba(0,0,0,0.5),
1891
+ 0 0 0 1px rgba(255,255,255,0.03);
1892
+ overflow: hidden;
1893
+ animation: ps-lang-open 0.2s ease both;
1894
+ }
1895
+ @keyframes ps-lang-open {
1896
+ from { opacity: 0; transform: translateY(clamp(-6px, -0.42vw, -8px)) scale(0.96); }
1897
+ to { opacity: 1; transform: translateY(0) scale(1); }
1898
+ }
1899
+
1900
+ .ps-tryon-lang-list {
1901
+ max-height: clamp(240px, 18vw, 340px); overflow-y: auto; padding: clamp(4px, 0.31vw, 6px);
1902
+ scrollbar-width: thin; scrollbar-color: #333 transparent;
1903
+ }
1904
+ .ps-tryon-lang-item {
1905
+ display: flex; align-items: center; gap: clamp(8px, 0.57vw, 12px);
1906
+ width: 100%; padding: clamp(8px, 0.57vw, 11px) clamp(12px, 0.83vw, 16px);
1907
+ border: none; background: transparent; color: #ccc; cursor: pointer;
1908
+ border-radius: clamp(6px, 0.47vw, 9px); transition: all 0.15s ease;
1909
+ font-family: inherit; text-align: left;
1910
+ }
1911
+ .ps-tryon-lang-item:hover { background: rgba(255,255,255,0.06); color: #fff; }
1912
+ .ps-tryon-lang-item.ps-selected { background: rgba(187,148,92,0.1); color: #bb945c; }
1913
+ .ps-tryon-lang-name {
1914
+ flex: 1; font-size: clamp(12px, 0.73vw, 14px); font-weight: 500;
1915
+ }
1916
+ .ps-tryon-lang-code {
1917
+ font-size: clamp(9px, 0.52vw, 11px); color: #666; font-weight: 600;
1918
+ letter-spacing: 0.05em; font-family: ui-monospace, monospace;
1919
+ }
1920
+ .ps-tryon-lang-item.ps-selected .ps-tryon-lang-code { color: #bb945c; opacity: 0.7; }
1921
+ .ps-tryon-lang-check { color: #bb945c; display: flex; align-items: center; }
1922
+
1816
1923
  /* Stepper */
1817
1924
  .ps-tryon-stepper { padding: clamp(12px, 1.04vw, 20px) clamp(14px, 1.25vw, 24px) clamp(7px, 0.63vw, 12px); }
1818
1925
  .ps-tryon-stepper-track { display: flex; align-items: flex-start; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primestyleai/tryon",
3
- "version": "3.9.2",
3
+ "version": "3.11.0",
4
4
  "description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
5
5
  "type": "module",
6
6
  "main": "dist/primestyle-tryon.js",