@voyantjs/cruises-ui 0.16.0 → 0.17.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/README.md CHANGED
@@ -11,3 +11,17 @@ pnpm add @voyantjs/cruises-ui @voyantjs/cruises-react @voyantjs/ui @tanstack/rea
11
11
  `@voyantjs/ui` provides the design-system primitives. `@voyantjs/cruises-react` provides the data-layer hooks. Both are required peers.
12
12
 
13
13
  All components accept a `className` prop and merge it with `cn()`. Wrap or compose to extend; use the registry copy-paste path (`npx shadcn add @voyant/...`) for components you want to fork outright.
14
+
15
+ ## I18n
16
+
17
+ Components render English by default. To localize them, wrap your UI in
18
+ `CruisesUiMessagesProvider` and import only the locales your app supports.
19
+
20
+ ```tsx
21
+ import { CruisesUiMessagesProvider } from "@voyantjs/cruises-ui"
22
+ import { cruisesUiEn } from "@voyantjs/cruises-ui/i18n/en"
23
+ import { cruisesUiRo } from "@voyantjs/cruises-ui/i18n/ro"
24
+ ```
25
+
26
+ English-only apps should import only `./i18n/en`. Bilingual apps can import
27
+ `./i18n/en` and `./i18n/ro`.
@@ -1 +1 @@
1
- {"version":3,"file":"enrichment-program-list.d.ts","sourceRoot":"","sources":["../../src/components/enrichment-program-list.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAuBnC,MAAM,WAAW,0BAA2B,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACtF,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,UAAU,EACV,SAAS,EACT,GAAG,KAAK,EACT,EAAE,0BAA0B,2CAiE5B"}
1
+ {"version":3,"file":"enrichment-program-list.d.ts","sourceRoot":"","sources":["../../src/components/enrichment-program-list.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAgBnC,MAAM,WAAW,0BAA2B,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACtF,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,UAAU,EACV,SAAS,EACT,GAAG,KAAK,EACT,EAAE,0BAA0B,2CAkE5B"}
@@ -6,14 +6,7 @@ import { Badge } from "@voyantjs/ui/components/badge";
6
6
  import { Card, CardContent } from "@voyantjs/ui/components/card";
7
7
  import { cn } from "@voyantjs/ui/lib/utils";
8
8
  import { Camera, Compass, GraduationCap, Mic, ScrollText, Sparkles } from "lucide-react";
9
- const KIND_LABEL = {
10
- naturalist: "Naturalist",
11
- historian: "Historian",
12
- photographer: "Photographer",
13
- lecturer: "Lecturer",
14
- expert: "Expert",
15
- other: "Specialist",
16
- };
9
+ import { useCruisesUiI18nOrDefault } from "../i18n";
17
10
  const KIND_ICON = {
18
11
  naturalist: Compass,
19
12
  historian: ScrollText,
@@ -29,12 +22,14 @@ const KIND_ICON = {
29
22
  * driven by `useEnrichmentMutation`.
30
23
  */
31
24
  export function EnrichmentProgramList({ cruiseKey, emptyState, className, ...props }) {
25
+ const { messages } = useCruisesUiI18nOrDefault();
26
+ const m = messages.enrichmentProgramList;
32
27
  const { data, isLoading } = useEnrichmentPrograms(cruiseKey);
33
28
  if (isLoading) {
34
- return (_jsx("div", { "data-slot": "enrichment-loading", className: cn("py-8 text-center", className), ...props, children: _jsx("p", { className: "text-muted-foreground", children: "Loading enrichment programs\u2026" }) }));
29
+ return (_jsx("div", { "data-slot": "enrichment-loading", className: cn("py-8 text-center", className), ...props, children: _jsx("p", { className: "text-muted-foreground", children: m.loading }) }));
35
30
  }
36
31
  if (!data || data.length === 0) {
37
- return (_jsx("div", { "data-slot": "enrichment-empty", className: cn("py-8 text-center", className), ...props, children: emptyState ?? (_jsx("p", { className: "text-muted-foreground", children: "No enrichment programs published for this cruise." })) }));
32
+ return (_jsx("div", { "data-slot": "enrichment-empty", className: cn("py-8 text-center", className), ...props, children: emptyState ?? _jsx("p", { className: "text-muted-foreground", children: m.empty }) }));
38
33
  }
39
34
  return (_jsx("div", { "data-slot": "enrichment-list", className: cn("grid gap-4 sm:grid-cols-2", className), ...props, children: data.map((program) => {
40
35
  const Icon = KIND_ICON[program.kind];
@@ -45,6 +40,6 @@ export function EnrichmentProgramList({ cruiseKey, emptyState, className, ...pro
45
40
  .slice(0, 2)
46
41
  .join("")
47
42
  .toUpperCase();
48
- return (_jsx(Card, { "data-slot": "enrichment-card", className: "overflow-hidden", children: _jsxs(CardContent, { className: "flex gap-4 p-4", children: [_jsxs(Avatar, { className: "size-14 shrink-0", children: [program.bioImageUrl ? (_jsx(AvatarImage, { src: program.bioImageUrl, alt: program.name })) : null, _jsx(AvatarFallback, { children: initials || "?" })] }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs(Badge, { variant: "secondary", className: "font-normal", children: [_jsx(Icon, { "aria-hidden": true, className: "mr-1 size-3" }), KIND_LABEL[program.kind]] }) }), _jsx("div", { className: "font-semibold truncate", children: program.name }), program.title ? (_jsx("div", { className: "text-sm text-muted-foreground truncate", children: program.title })) : null, program.description ? (_jsx("p", { className: "text-sm text-muted-foreground line-clamp-3", children: program.description })) : null] })] }) }, program.id));
43
+ return (_jsx(Card, { "data-slot": "enrichment-card", className: "overflow-hidden", children: _jsxs(CardContent, { className: "flex gap-4 p-4", children: [_jsxs(Avatar, { className: "size-14 shrink-0", children: [program.bioImageUrl ? (_jsx(AvatarImage, { src: program.bioImageUrl, alt: program.name })) : null, _jsx(AvatarFallback, { children: initials || m.avatarFallback })] }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs(Badge, { variant: "secondary", className: "font-normal", children: [_jsx(Icon, { "aria-hidden": true, className: "mr-1 size-3" }), m.kindLabels[program.kind]] }) }), _jsx("div", { className: "font-semibold truncate", children: program.name }), program.title ? (_jsx("div", { className: "text-sm text-muted-foreground truncate", children: program.title })) : null, program.description ? (_jsx("p", { className: "text-sm text-muted-foreground line-clamp-3", children: program.description })) : null] })] }) }, program.id));
49
44
  }) }));
50
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"external-badge.d.ts","sourceRoot":"","sources":["../../src/components/external-badge.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AAErD,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAEnC,MAAM,WAAW,wBAAyB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,KAAK,CAAC;IAC5F,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,cAAc,EACd,SAAS,EACT,GAAG,KAAK,EACT,EAAE,wBAAwB,2CAe1B"}
1
+ {"version":3,"file":"external-badge.d.ts","sourceRoot":"","sources":["../../src/components/external-badge.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AAErD,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAInC,MAAM,WAAW,wBAAyB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,KAAK,CAAC;IAC5F,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,cAAc,EACd,SAAS,EACT,GAAG,KAAK,EACT,EAAE,wBAAwB,2CAkB1B"}
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { formatMessage } from "@voyantjs/i18n";
3
4
  import { Badge } from "@voyantjs/ui/components/badge";
4
5
  import { cn } from "@voyantjs/ui/lib/utils";
6
+ import { useCruisesUiI18nOrDefault } from "../i18n";
5
7
  /**
6
8
  * Small inline badge that surfaces cruise provenance to admin/storefront UIs.
7
9
  * Renders as a subtle "External · <provider>" pill so operators (and shoppers,
@@ -9,5 +11,7 @@ import { cn } from "@voyantjs/ui/lib/utils";
9
11
  * upstream and which is the operator's own.
10
12
  */
11
13
  export function ExternalCruiseBadge({ sourceProvider, className, ...props }) {
12
- return (_jsxs(Badge, { "data-slot": "external-cruise-badge", variant: "outline", className: cn("font-normal", className), title: `Sourced via ${sourceProvider}`, ...props, children: [_jsx("span", { "aria-hidden": "true", className: "mr-1", children: "\u2197" }), "External \u00B7 ", sourceProvider] }));
14
+ const { messages } = useCruisesUiI18nOrDefault();
15
+ const m = messages.externalCruiseBadge;
16
+ return (_jsxs(Badge, { "data-slot": "external-cruise-badge", variant: "outline", className: cn("font-normal", className), title: formatMessage(m.title, { sourceProvider }), ...props, children: [_jsx("span", { "aria-hidden": "true", className: "mr-1", children: "\u2197" }), formatMessage(m.label, { sourceProvider })] }));
13
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pricing-grid.d.ts","sourceRoot":"","sources":["../../src/components/pricing-grid.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAW1D,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAEnC,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC5E,uGAAuG;IACvG,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAA;IAC9C,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1D,mGAAmG;IACnG,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;CAC5C;AA2BD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,aAAa,EACb,WAAgC,EAChC,YAAY,EACZ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,gBAAgB,2CA+FlB"}
1
+ {"version":3,"file":"pricing-grid.d.ts","sourceRoot":"","sources":["../../src/components/pricing-grid.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAY1D,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAInC,MAAM,WAAW,gBAAiB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC5E,uGAAuG;IACvG,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAA;IAC9C,+DAA+D;IAC/D,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1D,mGAAmG;IACnG,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAA;CAC5C;AAaD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,aAAa,EACb,WAAW,EACX,YAAY,EACZ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,gBAAgB,2CAsGlB"}
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { formatMessage } from "@voyantjs/i18n";
3
4
  import { Badge } from "@voyantjs/ui/components/badge";
4
5
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
5
6
  import { cn } from "@voyantjs/ui/lib/utils";
7
+ import { useCruisesUiI18nOrDefault } from "../i18n";
6
8
  const AVAILABILITY_VARIANT = {
7
9
  available: "default",
8
10
  limited: "secondary",
@@ -10,19 +12,6 @@ const AVAILABILITY_VARIANT = {
10
12
  wait_list: "outline",
11
13
  sold_out: "destructive",
12
14
  };
13
- const AVAILABILITY_LABEL = {
14
- available: "Available",
15
- limited: "Limited",
16
- on_request: "On request",
17
- wait_list: "Wait list",
18
- sold_out: "Sold out",
19
- };
20
- function defaultFormatPrice(amount, currency) {
21
- const n = Number(amount);
22
- if (!Number.isFinite(n))
23
- return `${currency} ${amount}`;
24
- return `${currency} ${n.toLocaleString(undefined, { maximumFractionDigits: 0 })}`;
25
- }
26
15
  /**
27
16
  * The cabin × occupancy pricing matrix that's the heart of any cruise booking
28
17
  * flow. Rows = cabin categories; columns = occupancy variants present in the
@@ -30,9 +19,16 @@ function defaultFormatPrice(amount, currency) {
30
19
  * an availability badge and the price; clicking surfaces the underlying row
31
20
  * for the booking flow to consume.
32
21
  */
33
- export function PricingGrid({ prices, categoryLabel, formatPrice = defaultFormatPrice, onCellSelect, className, ...props }) {
22
+ export function PricingGrid({ prices, categoryLabel, formatPrice, onCellSelect, className, ...props }) {
23
+ const i18n = useCruisesUiI18nOrDefault();
24
+ const m = i18n.messages.pricingGrid;
25
+ const formatResolvedPrice = formatPrice ??
26
+ ((amount, currency) => formatCruiseMoney(amount, currency, {
27
+ fallbackCurrencyAmount: i18n.messages.common.fallbackCurrencyAmount,
28
+ formatCurrency: i18n.formatCurrency,
29
+ }, { maximumFractionDigits: 0 }));
34
30
  if (prices.length === 0) {
35
- return (_jsx("div", { "data-slot": "pricing-grid-empty", className: cn("py-8 text-center", className), ...props, children: _jsx("p", { className: "text-muted-foreground", children: "No pricing published for this sailing." }) }));
31
+ return (_jsx("div", { "data-slot": "pricing-grid-empty", className: cn("py-8 text-center", className), ...props, children: _jsx("p", { className: "text-muted-foreground", children: m.empty }) }));
36
32
  }
37
33
  // Pivot the flat list into a (categoryId × occupancy) → cheapest price row.
38
34
  const occupancies = Array.from(new Set(prices.map((p) => p.occupancy))).sort((a, b) => a - b);
@@ -48,15 +44,7 @@ export function PricingGrid({ prices, categoryLabel, formatPrice = defaultFormat
48
44
  row.set(p.occupancy, p);
49
45
  }
50
46
  }
51
- return (_jsx("div", { "data-slot": "pricing-grid", className: cn("overflow-x-auto", className), ...props, children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[220px]", children: "Cabin category" }), occupancies.map((occ) => (_jsx(TableHead, { className: "text-center", children: occ === 1
52
- ? "Single"
53
- : occ === 2
54
- ? "Double"
55
- : occ === 3
56
- ? "Triple"
57
- : occ === 4
58
- ? "Quad"
59
- : `${occ}-occupancy` }, occ)))] }) }), _jsx(TableBody, { children: Array.from(grouped.entries()).map(([categoryId, row]) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: categoryLabel?.(categoryId) ?? categoryId }), occupancies.map((occ) => {
47
+ return (_jsx("div", { "data-slot": "pricing-grid", className: cn("overflow-x-auto", className), ...props, children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[220px]", children: m.cabinCategory }), occupancies.map((occ) => (_jsx(TableHead, { className: "text-center", children: occupancyLabel(occ, i18n.messages.common.occupancyTableLabels) }, occ)))] }) }), _jsx(TableBody, { children: Array.from(grouped.entries()).map(([categoryId, row]) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: categoryLabel?.(categoryId) ?? categoryId }), occupancies.map((occ) => {
60
48
  const price = row.get(occ);
61
49
  if (!price) {
62
50
  return (_jsx(TableCell, { className: "text-center text-muted-foreground", children: "\u2014" }, occ));
@@ -65,6 +53,29 @@ export function PricingGrid({ prices, categoryLabel, formatPrice = defaultFormat
65
53
  price.availability !== "sold_out" &&
66
54
  "cursor-pointer hover:bg-accent"), onClick: onCellSelect && price.availability !== "sold_out"
67
55
  ? () => onCellSelect(price)
68
- : undefined, "data-slot": "pricing-grid-cell", children: [_jsx("div", { className: "font-semibold", children: formatPrice(price.pricePerPerson, price.currency) }), _jsx("div", { className: "text-xs text-muted-foreground", children: "per person" }), _jsx(Badge, { variant: AVAILABILITY_VARIANT[price.availability], className: "mt-2 font-normal", children: AVAILABILITY_LABEL[price.availability] })] }, occ));
56
+ : undefined, "data-slot": "pricing-grid-cell", children: [_jsx("div", { className: "font-semibold", children: formatResolvedPrice(price.pricePerPerson, price.currency) }), _jsx("div", { className: "text-xs text-muted-foreground", children: m.perPerson }), _jsx(Badge, { variant: AVAILABILITY_VARIANT[price.availability], className: "mt-2 font-normal", children: m.availabilityLabels[price.availability] })] }, occ));
69
57
  })] }, categoryId))) })] }) }));
70
58
  }
59
+ function occupancyLabel(occupancy, labels) {
60
+ if (occupancy === 1)
61
+ return labels.single;
62
+ if (occupancy === 2)
63
+ return labels.double;
64
+ if (occupancy === 3)
65
+ return labels.triple;
66
+ if (occupancy === 4)
67
+ return labels.quad;
68
+ return formatMessage(labels.fallback, { count: occupancy });
69
+ }
70
+ function formatCruiseMoney(amount, currency, i18n, options) {
71
+ const n = Number(amount);
72
+ if (!Number.isFinite(n)) {
73
+ return formatMessage(i18n.fallbackCurrencyAmount, { currency, amount });
74
+ }
75
+ try {
76
+ return i18n.formatCurrency(n, currency, options);
77
+ }
78
+ catch {
79
+ return formatMessage(i18n.fallbackCurrencyAmount, { currency, amount });
80
+ }
81
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"quote-display.d.ts","sourceRoot":"","sources":["../../src/components/quote-display.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAE,IAAI,EAA2B,MAAM,8BAA8B,CAAA;AAE5E,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAInC,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,IAAI,CAAC;IACpF,KAAK,EAAE,KAAK,CAAA;IACZ,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;CAC3D;AAwBD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,WAAgC,EAChC,SAAS,EACT,GAAG,KAAK,EACT,EAAE,iBAAiB,2CAkFnB"}
1
+ {"version":3,"file":"quote-display.d.ts","sourceRoot":"","sources":["../../src/components/quote-display.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAEpD,OAAO,EAAE,IAAI,EAA2B,MAAM,8BAA8B,CAAA;AAE5E,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAMnC,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,IAAI,CAAC;IACpF,KAAK,EAAE,KAAK,CAAA;IACZ,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;CAC3D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,2CA4G1F"}
@@ -1,38 +1,45 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { formatMessage } from "@voyantjs/i18n";
3
4
  import { Card, CardContent, CardHeader } from "@voyantjs/ui/components/card";
4
5
  import { cn } from "@voyantjs/ui/lib/utils";
5
- function defaultFormatPrice(amount, currency) {
6
- const negative = amount.startsWith("-");
7
- const abs = negative ? amount.slice(1) : amount;
8
- const n = Number(abs);
9
- if (!Number.isFinite(n))
10
- return `${currency} ${amount}`;
11
- return `${negative ? "-" : ""}${currency} ${n.toLocaleString(undefined, {
12
- minimumFractionDigits: 2,
13
- maximumFractionDigits: 2,
14
- })}`;
15
- }
16
- const COMPONENT_KIND_LABEL = {
17
- gratuity: "Gratuity",
18
- onboard_credit: "Onboard credit",
19
- port_charge: "Port charges",
20
- tax: "Tax",
21
- ncf: "Non-comm fees",
22
- airfare: "Airfare",
23
- transfer: "Transfer",
24
- insurance: "Insurance",
25
- };
6
+ import { useCruisesUiI18nOrDefault } from "../i18n";
26
7
  /**
27
8
  * Renders an itemised cruise quote: base per person, components grouped by
28
9
  * direction (additions / inclusions / credits), and totals. Mirrors what the
29
10
  * server's composeQuote returns; pure presentational.
30
11
  */
31
- export function QuoteDisplay({ quote, formatPrice = defaultFormatPrice, className, ...props }) {
12
+ export function QuoteDisplay({ quote, formatPrice, className, ...props }) {
13
+ const i18n = useCruisesUiI18nOrDefault();
14
+ const m = i18n.messages.quoteDisplay;
15
+ const formatResolvedPrice = formatPrice ??
16
+ ((amount, currency) => formatCruiseMoney(amount, currency, {
17
+ fallbackCurrencyAmount: i18n.messages.common.fallbackCurrencyAmount,
18
+ formatCurrency: i18n.formatCurrency,
19
+ }, {
20
+ minimumFractionDigits: 2,
21
+ maximumFractionDigits: 2,
22
+ }));
32
23
  const additions = quote.components.filter((c) => c.direction === "addition");
33
24
  const inclusions = quote.components.filter((c) => c.direction === "inclusion");
34
25
  const credits = quote.components.filter((c) => c.direction === "credit");
35
- return (_jsxs(Card, { "data-slot": "quote-display", className: cn(className), ...props, children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-baseline justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold", children: "Your quote" }), quote.fareCodeName ? (_jsxs("p", { className: "text-sm text-muted-foreground", children: [quote.fareCodeName, quote.fareCode ? ` (${quote.fareCode})` : ""] })) : null] }), _jsxs("div", { className: "text-right", children: [_jsx("div", { className: "text-2xl font-bold", children: formatPrice(quote.totalForCabin, quote.currency) }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["for ", quote.guestCount, " guest", quote.guestCount === 1 ? "" : "s", " (", quote.occupancy, "-occupancy cabin)"] })] })] }) }), _jsxs(CardContent, { className: "space-y-4 text-sm", children: [_jsx(Row, { label: `Base · ${formatPrice(quote.basePerPerson, quote.currency)} pp × ${quote.guestCount}` }), additions.length > 0 ? (_jsx(Section, { title: "Additions", children: additions.map((c) => (_jsx(Row, { label: `${c.label ?? COMPONENT_KIND_LABEL[c.kind]}${c.perPerson ? " (per person)" : " (per cabin)"}`, amount: `+ ${formatPrice(c.amount, c.currency)}` }, `add-${c.kind}-${c.label ?? ""}-${c.amount}`))) })) : null, credits.length > 0 ? (_jsx(Section, { title: "Credits", children: credits.map((c) => (_jsx(Row, { label: `${c.label ?? COMPONENT_KIND_LABEL[c.kind]}${c.perPerson ? " (per person)" : " (per cabin)"}`, amount: `− ${formatPrice(c.amount, c.currency)}`, amountClassName: "text-emerald-600" }, `cred-${c.kind}-${c.label ?? ""}-${c.amount}`))) })) : null, inclusions.length > 0 ? (_jsx(Section, { title: "Included", children: inclusions.map((c) => (_jsx(Row, { label: c.label ?? COMPONENT_KIND_LABEL[c.kind], amount: "Included" }, `inc-${c.kind}-${c.label ?? ""}`))) })) : null, _jsxs("div", { className: "border-t pt-3", children: [_jsx(Row, { label: "Per person", amount: formatPrice(quote.totalPerPerson, quote.currency), amountClassName: "font-semibold" }), _jsx(Row, { label: "Total for cabin", amount: formatPrice(quote.totalForCabin, quote.currency), amountClassName: "font-bold text-base" })] })] })] }));
26
+ const guestLabel = quote.guestCount === 1 ? m.guestLabelSingular : m.guestLabelPlural;
27
+ const occupancyLabel = formatMessage(m.occupancyCabin, { count: quote.occupancy });
28
+ return (_jsxs(Card, { "data-slot": "quote-display", className: cn(className), ...props, children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-baseline justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold", children: m.heading }), quote.fareCodeName ? (_jsxs("p", { className: "text-sm text-muted-foreground", children: [quote.fareCodeName, quote.fareCode ? ` (${quote.fareCode})` : ""] })) : null] }), _jsxs("div", { className: "text-right", children: [_jsx("div", { className: "text-2xl font-bold", children: formatResolvedPrice(quote.totalForCabin, quote.currency) }), _jsx("div", { className: "text-xs text-muted-foreground", children: formatMessage(m.guestSummary, {
29
+ guestCount: quote.guestCount,
30
+ guestLabel,
31
+ occupancyLabel,
32
+ }) })] })] }) }), _jsxs(CardContent, { className: "space-y-4 text-sm", children: [_jsx(Row, { label: formatMessage(m.baseLine, {
33
+ price: formatResolvedPrice(quote.basePerPerson, quote.currency),
34
+ guestCount: quote.guestCount,
35
+ }) }), additions.length > 0 ? (_jsx(Section, { title: m.sections.additions, children: additions.map((c) => (_jsx(Row, { label: componentLabel(c, m), amount: `+ ${formatResolvedPrice(c.amount, c.currency)}` }, `add-${c.kind}-${c.label ?? ""}-${c.amount}`))) })) : null, credits.length > 0 ? (_jsx(Section, { title: m.sections.credits, children: credits.map((c) => (_jsx(Row, { label: componentLabel(c, m), amount: `− ${formatResolvedPrice(c.amount, c.currency)}`, amountClassName: "text-emerald-600" }, `cred-${c.kind}-${c.label ?? ""}-${c.amount}`))) })) : null, inclusions.length > 0 ? (_jsx(Section, { title: m.sections.included, children: inclusions.map((c) => (_jsx(Row, { label: c.label ?? m.componentKindLabels[c.kind], amount: m.includedAmount }, `inc-${c.kind}-${c.label ?? ""}`))) })) : null, _jsxs("div", { className: "border-t pt-3", children: [_jsx(Row, { label: m.totals.perPerson, amount: formatResolvedPrice(quote.totalPerPerson, quote.currency), amountClassName: "font-semibold" }), _jsx(Row, { label: m.totals.totalForCabin, amount: formatResolvedPrice(quote.totalForCabin, quote.currency), amountClassName: "font-bold text-base" })] })] })] }));
36
+ }
37
+ function componentLabel(component, messages) {
38
+ const baseLabel = component.label ?? messages.componentKindLabels[component.kind];
39
+ const scope = component.perPerson
40
+ ? messages.componentScope.perPerson
41
+ : messages.componentScope.perCabin;
42
+ return `${baseLabel} (${scope})`;
36
43
  }
37
44
  function Section({ title, children }) {
38
45
  return (_jsxs("div", { className: "space-y-1", children: [_jsx("h4", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: title }), children] }));
@@ -40,3 +47,15 @@ function Section({ title, children }) {
40
47
  function Row({ label, amount, amountClassName, }) {
41
48
  return (_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsx("span", { className: "text-muted-foreground", children: label }), amount ? _jsx("span", { className: cn("tabular-nums", amountClassName), children: amount }) : null] }));
42
49
  }
50
+ function formatCruiseMoney(amount, currency, i18n, options) {
51
+ const n = Number(amount);
52
+ if (!Number.isFinite(n)) {
53
+ return formatMessage(i18n.fallbackCurrencyAmount, { currency, amount });
54
+ }
55
+ try {
56
+ return i18n.formatCurrency(n, currency, options);
57
+ }
58
+ catch {
59
+ return formatMessage(i18n.fallbackCurrencyAmount, { currency, amount });
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ import type { CruisesUiMessages } from "./messages";
2
+ export declare const cruisesUiEn: CruisesUiMessages;
3
+ //# sourceMappingURL=en.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/i18n/en.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,eAAO,MAAM,WAAW,EAAE,iBAwEzB,CAAA"}
@@ -0,0 +1,73 @@
1
+ export const cruisesUiEn = {
2
+ common: {
3
+ fallbackCurrencyAmount: "{currency} {amount}",
4
+ occupancyTableLabels: {
5
+ single: "Single",
6
+ double: "Double",
7
+ triple: "Triple",
8
+ quad: "Quad",
9
+ fallback: "{count}-occupancy",
10
+ },
11
+ },
12
+ enrichmentProgramList: {
13
+ loading: "Loading enrichment programs…",
14
+ empty: "No enrichment programs published for this cruise.",
15
+ avatarFallback: "?",
16
+ kindLabels: {
17
+ naturalist: "Naturalist",
18
+ historian: "Historian",
19
+ photographer: "Photographer",
20
+ lecturer: "Lecturer",
21
+ expert: "Expert",
22
+ other: "Specialist",
23
+ },
24
+ },
25
+ externalCruiseBadge: {
26
+ title: "Sourced via {sourceProvider}",
27
+ label: "External · {sourceProvider}",
28
+ },
29
+ pricingGrid: {
30
+ empty: "No pricing published for this sailing.",
31
+ cabinCategory: "Cabin category",
32
+ perPerson: "per person",
33
+ availabilityLabels: {
34
+ available: "Available",
35
+ limited: "Limited",
36
+ on_request: "On request",
37
+ wait_list: "Wait list",
38
+ sold_out: "Sold out",
39
+ },
40
+ },
41
+ quoteDisplay: {
42
+ heading: "Your quote",
43
+ guestLabelSingular: "guest",
44
+ guestLabelPlural: "guests",
45
+ occupancyCabin: "{count}-occupancy cabin",
46
+ guestSummary: "for {guestCount} {guestLabel} ({occupancyLabel})",
47
+ baseLine: "Base · {price} pp × {guestCount}",
48
+ sections: {
49
+ additions: "Additions",
50
+ credits: "Credits",
51
+ included: "Included",
52
+ },
53
+ componentKindLabels: {
54
+ gratuity: "Gratuity",
55
+ onboard_credit: "Onboard credit",
56
+ port_charge: "Port charges",
57
+ tax: "Tax",
58
+ ncf: "Non-comm fees",
59
+ airfare: "Airfare",
60
+ transfer: "Transfer",
61
+ insurance: "Insurance",
62
+ },
63
+ componentScope: {
64
+ perPerson: "per person",
65
+ perCabin: "per cabin",
66
+ },
67
+ includedAmount: "Included",
68
+ totals: {
69
+ perPerson: "Per person",
70
+ totalForCabin: "Total for cabin",
71
+ },
72
+ },
73
+ };
@@ -0,0 +1,5 @@
1
+ export { cruisesUiEn } from "./en";
2
+ export type { CruisePriceAvailability, CruiseQuoteComponentKind, CruisesUiMessages, EnrichmentProgramKind, } from "./messages";
3
+ export { type CruisesUiMessageOverrides, CruisesUiMessagesProvider, cruisesUiMessageDefinitions, getCruisesUiI18n, resolveCruisesUiMessages, useCruisesUiI18n, useCruisesUiI18nOrDefault, useCruisesUiMessages, useCruisesUiMessagesOrDefault, } from "./provider";
4
+ export { cruisesUiRo } from "./ro";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAA;AAClC,YAAY,EACV,uBAAuB,EACvB,wBAAwB,EACxB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,KAAK,yBAAyB,EAC9B,yBAAyB,EACzB,2BAA2B,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,6BAA6B,GAC9B,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,WAAW,EAAE,MAAM,MAAM,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { cruisesUiEn } from "./en";
2
+ export { CruisesUiMessagesProvider, cruisesUiMessageDefinitions, getCruisesUiI18n, resolveCruisesUiMessages, useCruisesUiI18n, useCruisesUiI18nOrDefault, useCruisesUiMessages, useCruisesUiMessagesOrDefault, } from "./provider";
3
+ export { cruisesUiRo } from "./ro";
@@ -0,0 +1,56 @@
1
+ import type { EnrichmentProgramRecord, PriceRecord, Quote } from "@voyantjs/cruises-react";
2
+ export type EnrichmentProgramKind = EnrichmentProgramRecord["kind"];
3
+ export type CruisePriceAvailability = PriceRecord["availability"];
4
+ export type CruiseQuoteComponentKind = Quote["components"][number]["kind"];
5
+ export type CruisesUiMessages = {
6
+ common: {
7
+ fallbackCurrencyAmount: string;
8
+ occupancyTableLabels: {
9
+ single: string;
10
+ double: string;
11
+ triple: string;
12
+ quad: string;
13
+ fallback: string;
14
+ };
15
+ };
16
+ enrichmentProgramList: {
17
+ loading: string;
18
+ empty: string;
19
+ avatarFallback: string;
20
+ kindLabels: Record<EnrichmentProgramKind, string>;
21
+ };
22
+ externalCruiseBadge: {
23
+ title: string;
24
+ label: string;
25
+ };
26
+ pricingGrid: {
27
+ empty: string;
28
+ cabinCategory: string;
29
+ perPerson: string;
30
+ availabilityLabels: Record<CruisePriceAvailability, string>;
31
+ };
32
+ quoteDisplay: {
33
+ heading: string;
34
+ guestLabelSingular: string;
35
+ guestLabelPlural: string;
36
+ occupancyCabin: string;
37
+ guestSummary: string;
38
+ baseLine: string;
39
+ sections: {
40
+ additions: string;
41
+ credits: string;
42
+ included: string;
43
+ };
44
+ componentKindLabels: Record<CruiseQuoteComponentKind, string>;
45
+ componentScope: {
46
+ perPerson: string;
47
+ perCabin: string;
48
+ };
49
+ includedAmount: string;
50
+ totals: {
51
+ perPerson: string;
52
+ totalForCabin: string;
53
+ };
54
+ };
55
+ };
56
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/i18n/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AAE1F,MAAM,MAAM,qBAAqB,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAA;AACnE,MAAM,MAAM,uBAAuB,GAAG,WAAW,CAAC,cAAc,CAAC,CAAA;AACjE,MAAM,MAAM,wBAAwB,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1E,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE;QACN,sBAAsB,EAAE,MAAM,CAAA;QAC9B,oBAAoB,EAAE;YACpB,MAAM,EAAE,MAAM,CAAA;YACd,MAAM,EAAE,MAAM,CAAA;YACd,MAAM,EAAE,MAAM,CAAA;YACd,IAAI,EAAE,MAAM,CAAA;YACZ,QAAQ,EAAE,MAAM,CAAA;SACjB,CAAA;KACF,CAAA;IACD,qBAAqB,EAAE;QACrB,OAAO,EAAE,MAAM,CAAA;QACf,KAAK,EAAE,MAAM,CAAA;QACb,cAAc,EAAE,MAAM,CAAA;QACtB,UAAU,EAAE,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;KAClD,CAAA;IACD,mBAAmB,EAAE;QACnB,KAAK,EAAE,MAAM,CAAA;QACb,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;IACD,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAA;QACb,aAAa,EAAE,MAAM,CAAA;QACrB,SAAS,EAAE,MAAM,CAAA;QACjB,kBAAkB,EAAE,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAA;KAC5D,CAAA;IACD,YAAY,EAAE;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,kBAAkB,EAAE,MAAM,CAAA;QAC1B,gBAAgB,EAAE,MAAM,CAAA;QACxB,cAAc,EAAE,MAAM,CAAA;QACtB,YAAY,EAAE,MAAM,CAAA;QACpB,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE;YACR,SAAS,EAAE,MAAM,CAAA;YACjB,OAAO,EAAE,MAAM,CAAA;YACf,QAAQ,EAAE,MAAM,CAAA;SACjB,CAAA;QACD,mBAAmB,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,CAAA;QAC7D,cAAc,EAAE;YACd,SAAS,EAAE,MAAM,CAAA;YACjB,QAAQ,EAAE,MAAM,CAAA;SACjB,CAAA;QACD,cAAc,EAAE,MAAM,CAAA;QACtB,MAAM,EAAE;YACN,SAAS,EAAE,MAAM,CAAA;YACjB,aAAa,EAAE,MAAM,CAAA;SACtB,CAAA;KACF,CAAA;CACF,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { type LocaleMessageOverrides, type PackageI18nValue } from "@voyantjs/i18n";
2
+ import type { ReactNode } from "react";
3
+ import type { CruisesUiMessages } from "./messages";
4
+ export declare const cruisesUiMessageDefinitions: {
5
+ en: CruisesUiMessages;
6
+ ro: CruisesUiMessages;
7
+ };
8
+ export type CruisesUiMessageOverrides = LocaleMessageOverrides<CruisesUiMessages>;
9
+ export declare function resolveCruisesUiMessages({ locale, overrides, }: {
10
+ locale: string | null | undefined;
11
+ overrides?: CruisesUiMessageOverrides | null;
12
+ }): CruisesUiMessages;
13
+ export declare function getCruisesUiI18n({ locale, overrides, }: {
14
+ locale?: string | null | undefined;
15
+ overrides?: CruisesUiMessageOverrides | null;
16
+ }): PackageI18nValue<CruisesUiMessages>;
17
+ export declare function CruisesUiMessagesProvider({ children, locale, overrides, }: {
18
+ children: ReactNode;
19
+ locale: string | null | undefined;
20
+ overrides?: CruisesUiMessageOverrides | null;
21
+ }): import("react/jsx-runtime").JSX.Element;
22
+ export declare const useCruisesUiI18n: () => PackageI18nValue<CruisesUiMessages>;
23
+ export declare const useCruisesUiMessages: () => CruisesUiMessages;
24
+ export declare function useCruisesUiI18nOrDefault(): PackageI18nValue<CruisesUiMessages>;
25
+ export declare function useCruisesUiMessagesOrDefault(): CruisesUiMessages;
26
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/i18n/provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EAEtB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAKnD,eAAO,MAAM,2BAA2B;;;CAGe,CAAA;AAEvD,MAAM,MAAM,yBAAyB,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,CAAA;AASjF,wBAAgB,wBAAwB,CAAC,EACvC,MAAM,EACN,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACjC,SAAS,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAA;CAC7C,qBAOA;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,SAAS,GACV,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAClC,SAAS,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAA;CAC7C,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,CAStC;AAED,wBAAgB,yBAAyB,CAAC,EACxC,QAAQ,EACR,MAAM,EACN,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAA;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IACjC,SAAS,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAA;CAC7C,2CAWA;AAED,eAAO,MAAM,gBAAgB,2CAA2B,CAAA;AACxD,eAAO,MAAM,oBAAoB,yBAA+B,CAAA;AAEhE,wBAAgB,yBAAyB,wCAExC;AAED,wBAAgB,6BAA6B,sBAE5C"}
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createLocaleFormatters, createPackageMessagesContext, resolvePackageMessages, } from "@voyantjs/i18n";
4
+ import { cruisesUiEn } from "./en";
5
+ import { cruisesUiRo } from "./ro";
6
+ const fallbackLocale = "en";
7
+ export const cruisesUiMessageDefinitions = {
8
+ en: cruisesUiEn,
9
+ ro: cruisesUiRo,
10
+ };
11
+ const cruisesUiContext = createPackageMessagesContext("CruisesUiMessages");
12
+ const defaultCruisesUiI18n = {
13
+ messages: cruisesUiEn,
14
+ ...createLocaleFormatters(fallbackLocale),
15
+ };
16
+ export function resolveCruisesUiMessages({ locale, overrides, }) {
17
+ return resolvePackageMessages({
18
+ definitions: cruisesUiMessageDefinitions,
19
+ fallbackLocale,
20
+ locale,
21
+ overrides,
22
+ });
23
+ }
24
+ export function getCruisesUiI18n({ locale, overrides, }) {
25
+ const resolvedLocale = locale ?? fallbackLocale;
26
+ return {
27
+ messages: resolveCruisesUiMessages({
28
+ locale: resolvedLocale,
29
+ overrides,
30
+ }),
31
+ ...createLocaleFormatters(resolvedLocale),
32
+ };
33
+ }
34
+ export function CruisesUiMessagesProvider({ children, locale, overrides, }) {
35
+ return (_jsx(cruisesUiContext.ResolvedMessagesProvider, { definitions: cruisesUiMessageDefinitions, fallbackLocale: fallbackLocale, locale: locale, overrides: overrides, children: children }));
36
+ }
37
+ export const useCruisesUiI18n = cruisesUiContext.useI18n;
38
+ export const useCruisesUiMessages = cruisesUiContext.useMessages;
39
+ export function useCruisesUiI18nOrDefault() {
40
+ return cruisesUiContext.useOptionalI18n() ?? defaultCruisesUiI18n;
41
+ }
42
+ export function useCruisesUiMessagesOrDefault() {
43
+ return useCruisesUiI18nOrDefault().messages;
44
+ }
@@ -0,0 +1,3 @@
1
+ import type { CruisesUiMessages } from "./messages";
2
+ export declare const cruisesUiRo: CruisesUiMessages;
3
+ //# sourceMappingURL=ro.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ro.d.ts","sourceRoot":"","sources":["../../src/i18n/ro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,eAAO,MAAM,WAAW,EAAE,iBAwEzB,CAAA"}
@@ -0,0 +1,73 @@
1
+ export const cruisesUiRo = {
2
+ common: {
3
+ fallbackCurrencyAmount: "{currency} {amount}",
4
+ occupancyTableLabels: {
5
+ single: "Single",
6
+ double: "Dubla",
7
+ triple: "Tripla",
8
+ quad: "Cvadrupla",
9
+ fallback: "{count} locuri",
10
+ },
11
+ },
12
+ enrichmentProgramList: {
13
+ loading: "Se incarca programele de imbogatire…",
14
+ empty: "Nu exista programe de imbogatire publicate pentru aceasta croaziera.",
15
+ avatarFallback: "?",
16
+ kindLabels: {
17
+ naturalist: "Naturalist",
18
+ historian: "Istoric",
19
+ photographer: "Fotograf",
20
+ lecturer: "Lector",
21
+ expert: "Expert",
22
+ other: "Specialist",
23
+ },
24
+ },
25
+ externalCruiseBadge: {
26
+ title: "Preluat prin {sourceProvider}",
27
+ label: "Extern · {sourceProvider}",
28
+ },
29
+ pricingGrid: {
30
+ empty: "Nu exista tarife publicate pentru aceasta plecare.",
31
+ cabinCategory: "Categoria cabinei",
32
+ perPerson: "de persoana",
33
+ availabilityLabels: {
34
+ available: "Disponibil",
35
+ limited: "Limitat",
36
+ on_request: "La cerere",
37
+ wait_list: "Lista de asteptare",
38
+ sold_out: "Epuizat",
39
+ },
40
+ },
41
+ quoteDisplay: {
42
+ heading: "Oferta ta",
43
+ guestLabelSingular: "pasager",
44
+ guestLabelPlural: "pasageri",
45
+ occupancyCabin: "cabina pentru {count} persoane",
46
+ guestSummary: "pentru {guestCount} {guestLabel} ({occupancyLabel})",
47
+ baseLine: "Baza · {price} pp × {guestCount}",
48
+ sections: {
49
+ additions: "Costuri suplimentare",
50
+ credits: "Credite",
51
+ included: "Inclus",
52
+ },
53
+ componentKindLabels: {
54
+ gratuity: "Bacsis",
55
+ onboard_credit: "Credit la bord",
56
+ port_charge: "Taxe portuare",
57
+ tax: "Taxa",
58
+ ncf: "Taxe necomisionabile",
59
+ airfare: "Bilet de avion",
60
+ transfer: "Transfer",
61
+ insurance: "Asigurare",
62
+ },
63
+ componentScope: {
64
+ perPerson: "de persoana",
65
+ perCabin: "pe cabina",
66
+ },
67
+ includedAmount: "Inclus",
68
+ totals: {
69
+ perPerson: "Per persoana",
70
+ totalForCabin: "Total pentru cabina",
71
+ },
72
+ },
73
+ };
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export { EnrichmentProgramList, type EnrichmentProgramListProps, } from "./compo
2
2
  export { ExternalCruiseBadge, type ExternalCruiseBadgeProps } from "./components/external-badge";
3
3
  export { PricingGrid, type PricingGridProps } from "./components/pricing-grid";
4
4
  export { QuoteDisplay, type QuoteDisplayProps } from "./components/quote-display";
5
+ export { type CruisePriceAvailability, type CruiseQuoteComponentKind, type CruisesUiMessageOverrides, type CruisesUiMessages, CruisesUiMessagesProvider, cruisesUiEn, cruisesUiMessageDefinitions, cruisesUiRo, type EnrichmentProgramKind, getCruisesUiI18n, resolveCruisesUiMessages, useCruisesUiI18n, useCruisesUiI18nOrDefault, useCruisesUiMessages, useCruisesUiMessagesOrDefault, } from "./i18n";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,6BAA6B,CAAA;AAChG,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,6BAA6B,CAAA;AAChG,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AACjF,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,yBAAyB,EACzB,WAAW,EACX,2BAA2B,EAC3B,WAAW,EACX,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,oBAAoB,EACpB,6BAA6B,GAC9B,MAAM,QAAQ,CAAA"}
package/dist/index.js CHANGED
@@ -2,3 +2,4 @@ export { EnrichmentProgramList, } from "./components/enrichment-program-list";
2
2
  export { ExternalCruiseBadge } from "./components/external-badge";
3
3
  export { PricingGrid } from "./components/pricing-grid";
4
4
  export { QuoteDisplay } from "./components/quote-display";
5
+ export { CruisesUiMessagesProvider, cruisesUiEn, cruisesUiMessageDefinitions, cruisesUiRo, getCruisesUiI18n, resolveCruisesUiMessages, useCruisesUiI18n, useCruisesUiI18nOrDefault, useCruisesUiMessages, useCruisesUiMessagesOrDefault, } from "./i18n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/cruises-ui",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "license": "FSL-1.1-Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,6 +14,18 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "import": "./dist/index.js"
16
16
  },
17
+ "./i18n": {
18
+ "types": "./dist/i18n/index.d.ts",
19
+ "import": "./dist/i18n/index.js"
20
+ },
21
+ "./i18n/en": {
22
+ "types": "./dist/i18n/en.d.ts",
23
+ "import": "./dist/i18n/en.js"
24
+ },
25
+ "./i18n/ro": {
26
+ "types": "./dist/i18n/ro.d.ts",
27
+ "import": "./dist/i18n/ro.js"
28
+ },
17
29
  "./components/*": {
18
30
  "types": "./dist/components/*.d.ts",
19
31
  "import": "./dist/components/*.js"
@@ -23,8 +35,11 @@
23
35
  "@tanstack/react-query": "^5.0.0",
24
36
  "react": "^19.0.0",
25
37
  "react-dom": "^19.0.0",
26
- "@voyantjs/cruises-react": "0.16.0",
27
- "@voyantjs/ui": "0.16.0"
38
+ "@voyantjs/cruises-react": "0.17.0",
39
+ "@voyantjs/ui": "0.17.0"
40
+ },
41
+ "dependencies": {
42
+ "@voyantjs/i18n": "0.17.0"
28
43
  },
29
44
  "devDependencies": {
30
45
  "@tanstack/react-query": "^5.96.2",
@@ -35,9 +50,10 @@
35
50
  "react-dom": "^19.2.4",
36
51
  "typescript": "^6.0.2",
37
52
  "vitest": "^4.1.2",
38
- "@voyantjs/cruises-react": "0.16.0",
53
+ "@voyantjs/cruises-react": "0.17.0",
54
+ "@voyantjs/i18n": "0.17.0",
39
55
  "@voyantjs/voyant-typescript-config": "0.1.0",
40
- "@voyantjs/ui": "0.16.0"
56
+ "@voyantjs/ui": "0.17.0"
41
57
  },
42
58
  "files": [
43
59
  "dist"