@djangocfg/ext-newsletter 1.0.21 → 1.0.23

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/config.cjs CHANGED
@@ -27,7 +27,7 @@ var import_ext_base = require("@djangocfg/ext-base");
27
27
  // package.json
28
28
  var package_default = {
29
29
  name: "@djangocfg/ext-newsletter",
30
- version: "1.0.21",
30
+ version: "1.0.23",
31
31
  description: "Newsletter and subscription management extension for DjangoCFG",
32
32
  keywords: [
33
33
  "django",
@@ -73,6 +73,11 @@ var package_default = {
73
73
  types: "./dist/config.d.ts",
74
74
  import: "./dist/config.js",
75
75
  require: "./dist/config.cjs"
76
+ },
77
+ "./i18n": {
78
+ types: "./dist/i18n.d.ts",
79
+ import: "./dist/i18n.js",
80
+ require: "./dist/i18n.cjs"
76
81
  }
77
82
  },
78
83
  files: [
@@ -88,10 +93,12 @@ var package_default = {
88
93
  peerDependencies: {
89
94
  "@djangocfg/api": "workspace:*",
90
95
  "@djangocfg/ext-base": "workspace:*",
96
+ "@djangocfg/i18n": "workspace:*",
91
97
  "@djangocfg/ui-core": "workspace:*",
92
98
  consola: "^3.4.2",
93
99
  "lucide-react": "^0.545.0",
94
100
  next: "^16",
101
+ "next-intl": "^4",
95
102
  "p-retry": "^7.0.0",
96
103
  react: "^19",
97
104
  "react-dom": "^19",
@@ -101,10 +108,12 @@ var package_default = {
101
108
  devDependencies: {
102
109
  "@djangocfg/api": "workspace:*",
103
110
  "@djangocfg/ext-base": "workspace:*",
111
+ "@djangocfg/i18n": "workspace:*",
104
112
  "@djangocfg/typescript-config": "workspace:*",
105
113
  "@types/node": "^24.7.2",
106
114
  "@types/react": "^19.0.0",
107
115
  consola: "^3.4.2",
116
+ "next-intl": "^4.1.0",
108
117
  "p-retry": "^7.0.0",
109
118
  swr: "^2.3.7",
110
119
  tsup: "^8.5.0",
package/dist/config.js CHANGED
@@ -4,7 +4,7 @@ import { createExtensionConfig } from "@djangocfg/ext-base";
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "@djangocfg/ext-newsletter",
7
- version: "1.0.21",
7
+ version: "1.0.23",
8
8
  description: "Newsletter and subscription management extension for DjangoCFG",
9
9
  keywords: [
10
10
  "django",
@@ -50,6 +50,11 @@ var package_default = {
50
50
  types: "./dist/config.d.ts",
51
51
  import: "./dist/config.js",
52
52
  require: "./dist/config.cjs"
53
+ },
54
+ "./i18n": {
55
+ types: "./dist/i18n.d.ts",
56
+ import: "./dist/i18n.js",
57
+ require: "./dist/i18n.cjs"
53
58
  }
54
59
  },
55
60
  files: [
@@ -65,10 +70,12 @@ var package_default = {
65
70
  peerDependencies: {
66
71
  "@djangocfg/api": "workspace:*",
67
72
  "@djangocfg/ext-base": "workspace:*",
73
+ "@djangocfg/i18n": "workspace:*",
68
74
  "@djangocfg/ui-core": "workspace:*",
69
75
  consola: "^3.4.2",
70
76
  "lucide-react": "^0.545.0",
71
77
  next: "^16",
78
+ "next-intl": "^4",
72
79
  "p-retry": "^7.0.0",
73
80
  react: "^19",
74
81
  "react-dom": "^19",
@@ -78,10 +85,12 @@ var package_default = {
78
85
  devDependencies: {
79
86
  "@djangocfg/api": "workspace:*",
80
87
  "@djangocfg/ext-base": "workspace:*",
88
+ "@djangocfg/i18n": "workspace:*",
81
89
  "@djangocfg/typescript-config": "workspace:*",
82
90
  "@types/node": "^24.7.2",
83
91
  "@types/react": "^19.0.0",
84
92
  consola: "^3.4.2",
93
+ "next-intl": "^4.1.0",
85
94
  "p-retry": "^7.0.0",
86
95
  swr: "^2.3.7",
87
96
  tsup: "^8.5.0",
package/dist/hooks.cjs CHANGED
@@ -10,6 +10,7 @@ var react = require('react');
10
10
  var hooks = require('@djangocfg/ext-base/hooks');
11
11
  var jsxRuntime = require('react/jsx-runtime');
12
12
  var lucideReact = require('lucide-react');
13
+ var nextIntl = require('next-intl');
13
14
  var uiCore = require('@djangocfg/ui-core');
14
15
 
15
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -2124,7 +2125,7 @@ var apiNewsletter = api.createExtensionAPI(API);
2124
2125
  // package.json
2125
2126
  var package_default = {
2126
2127
  name: "@djangocfg/ext-newsletter",
2127
- version: "1.0.21",
2128
+ version: "1.0.23",
2128
2129
  description: "Newsletter and subscription management extension for DjangoCFG",
2129
2130
  keywords: [
2130
2131
  "django",
@@ -2170,6 +2171,11 @@ var package_default = {
2170
2171
  types: "./dist/config.d.ts",
2171
2172
  import: "./dist/config.js",
2172
2173
  require: "./dist/config.cjs"
2174
+ },
2175
+ "./i18n": {
2176
+ types: "./dist/i18n.d.ts",
2177
+ import: "./dist/i18n.js",
2178
+ require: "./dist/i18n.cjs"
2173
2179
  }
2174
2180
  },
2175
2181
  files: [
@@ -2185,10 +2191,12 @@ var package_default = {
2185
2191
  peerDependencies: {
2186
2192
  "@djangocfg/api": "workspace:*",
2187
2193
  "@djangocfg/ext-base": "workspace:*",
2194
+ "@djangocfg/i18n": "workspace:*",
2188
2195
  "@djangocfg/ui-core": "workspace:*",
2189
2196
  consola: "^3.4.2",
2190
2197
  "lucide-react": "^0.545.0",
2191
2198
  next: "^16",
2199
+ "next-intl": "^4",
2192
2200
  "p-retry": "^7.0.0",
2193
2201
  react: "^19",
2194
2202
  "react-dom": "^19",
@@ -2198,10 +2206,12 @@ var package_default = {
2198
2206
  devDependencies: {
2199
2207
  "@djangocfg/api": "workspace:*",
2200
2208
  "@djangocfg/ext-base": "workspace:*",
2209
+ "@djangocfg/i18n": "workspace:*",
2201
2210
  "@djangocfg/typescript-config": "workspace:*",
2202
2211
  "@types/node": "^24.7.2",
2203
2212
  "@types/react": "^19.0.0",
2204
2213
  consola: "^3.4.2",
2214
+ "next-intl": "^4.1.0",
2205
2215
  "p-retry": "^7.0.0",
2206
2216
  swr: "^2.3.7",
2207
2217
  tsup: "^8.5.0",
@@ -2476,6 +2486,65 @@ function useNewsletterContext() {
2476
2486
  }
2477
2487
  return context;
2478
2488
  }
2489
+
2490
+ // src/i18n/locales/en.ts
2491
+ var en = {
2492
+ hero: {
2493
+ placeholder: "Enter your email",
2494
+ subscribe: "Subscribe",
2495
+ subscribing: "Subscribing...",
2496
+ successMessage: "Successfully subscribed!",
2497
+ errorMessage: "Subscription failed. Please try again.",
2498
+ privacyNotice: "By subscribing, you agree to our Privacy Policy and consent to receive updates."
2499
+ }
2500
+ };
2501
+
2502
+ // src/i18n/locales/ru.ts
2503
+ var ru = {
2504
+ hero: {
2505
+ placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 email",
2506
+ subscribe: "\u041F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F",
2507
+ subscribing: "\u041F\u043E\u0434\u043F\u0438\u0441\u043A\u0430...",
2508
+ successMessage: "\u0412\u044B \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u043B\u0438\u0441\u044C!",
2509
+ errorMessage: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.",
2510
+ privacyNotice: "\u041F\u043E\u0434\u043F\u0438\u0441\u044B\u0432\u0430\u044F\u0441\u044C, \u0432\u044B \u0441\u043E\u0433\u043B\u0430\u0448\u0430\u0435\u0442\u0435\u0441\u044C \u0441 \u041F\u043E\u043B\u0438\u0442\u0438\u043A\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0434\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u0438 \u0438 \u0434\u0430\u0451\u0442\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u043D\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0439."
2511
+ }
2512
+ };
2513
+
2514
+ // src/i18n/locales/ko.ts
2515
+ var ko = {
2516
+ hero: {
2517
+ placeholder: "\uC774\uBA54\uC77C\uC744 \uC785\uB825\uD558\uC138\uC694",
2518
+ subscribe: "\uAD6C\uB3C5\uD558\uAE30",
2519
+ subscribing: "\uAD6C\uB3C5 \uC911...",
2520
+ successMessage: "\uC131\uACF5\uC801\uC73C\uB85C \uAD6C\uB3C5\uB418\uC5C8\uC2B5\uB2C8\uB2E4!",
2521
+ errorMessage: "\uAD6C\uB3C5\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
2522
+ privacyNotice: "\uAD6C\uB3C5 \uC2DC \uAC1C\uC778\uC815\uBCF4 \uCC98\uB9AC\uBC29\uCE68\uC5D0 \uB3D9\uC758\uD558\uACE0 \uC5C5\uB370\uC774\uD2B8 \uC218\uC2E0\uC5D0 \uB3D9\uC758\uD558\uB294 \uAC83\uC73C\uB85C \uAC04\uC8FC\uB429\uB2C8\uB2E4."
2523
+ }
2524
+ };
2525
+
2526
+ // src/i18n/useNewsletterT.ts
2527
+ var translations = { en, ru, ko };
2528
+ function getNestedValue(obj, path) {
2529
+ const keys = path.split(".");
2530
+ let result = obj;
2531
+ for (const key of keys) {
2532
+ if (result && typeof result === "object" && key in result) {
2533
+ result = result[key];
2534
+ } else {
2535
+ return path;
2536
+ }
2537
+ }
2538
+ return typeof result === "string" ? result : path;
2539
+ }
2540
+ function useNewsletterT() {
2541
+ const locale = nextIntl.useLocale();
2542
+ const t = react.useMemo(() => translations[locale] || translations.en, [locale]);
2543
+ return react.useCallback(
2544
+ (key) => getNestedValue(t, key),
2545
+ [t]
2546
+ );
2547
+ }
2479
2548
  var isDevelopment = process.env.NODE_ENV === "development";
2480
2549
  var logger = consola.createConsola({
2481
2550
  level: isDevelopment ? 4 : 1
@@ -2487,15 +2556,24 @@ function Hero({
2487
2556
  primaryAction,
2488
2557
  secondaryAction,
2489
2558
  showNewsletter = true,
2490
- newsletterPlaceholder = "Enter your email",
2491
- newsletterButtonText = "Subscribe",
2559
+ newsletterPlaceholder,
2560
+ newsletterButtonText,
2492
2561
  onNewsletterSubmit,
2493
2562
  className = ""
2494
2563
  }) {
2564
+ const nt = useNewsletterT();
2495
2565
  const [email, setEmail] = react.useState("");
2496
2566
  const [isLoading, setIsLoading] = react.useState(false);
2497
2567
  const [status, setStatus] = react.useState("idle");
2498
2568
  const [message, setMessage] = react.useState("");
2569
+ const labels = react.useMemo(() => ({
2570
+ placeholder: newsletterPlaceholder ?? nt("hero.placeholder"),
2571
+ subscribe: newsletterButtonText ?? nt("hero.subscribe"),
2572
+ subscribing: nt("hero.subscribing"),
2573
+ successMessage: nt("hero.successMessage"),
2574
+ errorMessage: nt("hero.errorMessage"),
2575
+ privacyNotice: nt("hero.privacyNotice")
2576
+ }), [nt, newsletterPlaceholder, newsletterButtonText]);
2499
2577
  const handleSubmit = async (e) => {
2500
2578
  e.preventDefault();
2501
2579
  if (!email || !onNewsletterSubmit) return;
@@ -2505,12 +2583,12 @@ function Hero({
2505
2583
  try {
2506
2584
  const result = await onNewsletterSubmit(email);
2507
2585
  setStatus("success");
2508
- setMessage((result && "message" in result ? result.message : void 0) || "Successfully subscribed!");
2586
+ setMessage((result && "message" in result ? result.message : void 0) || labels.successMessage);
2509
2587
  setEmail("");
2510
2588
  newsletterLogger.success("Newsletter subscription successful:", email);
2511
2589
  } catch (error) {
2512
2590
  setStatus("error");
2513
- setMessage(error instanceof Error ? error.message : "Subscription failed. Please try again.");
2591
+ setMessage(error instanceof Error ? error.message : labels.errorMessage);
2514
2592
  newsletterLogger.error("Newsletter subscription failed:", error);
2515
2593
  } finally {
2516
2594
  setIsLoading(false);
@@ -2553,7 +2631,7 @@ function Hero({
2553
2631
  uiCore.Input,
2554
2632
  {
2555
2633
  type: "email",
2556
- placeholder: newsletterPlaceholder,
2634
+ placeholder: labels.placeholder,
2557
2635
  value: email,
2558
2636
  onChange: (e) => setEmail(e.target.value),
2559
2637
  disabled: isLoading,
@@ -2570,8 +2648,8 @@ function Hero({
2570
2648
  className: "w-full sm:w-auto",
2571
2649
  children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2572
2650
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
2573
- "Subscribing..."
2574
- ] }) : newsletterButtonText
2651
+ labels.subscribing
2652
+ ] }) : labels.subscribe
2575
2653
  }
2576
2654
  )
2577
2655
  ] }),
@@ -2584,7 +2662,7 @@ function Hero({
2584
2662
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: message })
2585
2663
  ] })
2586
2664
  ] }),
2587
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-3", children: "By subscribing, you agree to our Privacy Policy and consent to receive updates." })
2665
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-3", children: labels.privacyNotice })
2588
2666
  ] })
2589
2667
  ] }) }) });
2590
2668
  }
package/dist/hooks.js CHANGED
@@ -4,10 +4,11 @@ import { z } from 'zod';
4
4
  import { initializeExtensionAPI, createExtensionAPI } from '@djangocfg/ext-base/api';
5
5
  import { createExtensionConfig } from '@djangocfg/ext-base';
6
6
  import useSWR, { useSWRConfig } from 'swr';
7
- import { createContext, useContext, useState } from 'react';
7
+ import { createContext, useContext, useState, useMemo, useCallback } from 'react';
8
8
  import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import { Mail, Loader2, CheckCircle2, AlertCircle } from 'lucide-react';
11
+ import { useLocale } from 'next-intl';
11
12
  import { Button, Input } from '@djangocfg/ui-core';
12
13
 
13
14
  var __defProp = Object.defineProperty;
@@ -2117,7 +2118,7 @@ var apiNewsletter = createExtensionAPI(API);
2117
2118
  // package.json
2118
2119
  var package_default = {
2119
2120
  name: "@djangocfg/ext-newsletter",
2120
- version: "1.0.21",
2121
+ version: "1.0.23",
2121
2122
  description: "Newsletter and subscription management extension for DjangoCFG",
2122
2123
  keywords: [
2123
2124
  "django",
@@ -2163,6 +2164,11 @@ var package_default = {
2163
2164
  types: "./dist/config.d.ts",
2164
2165
  import: "./dist/config.js",
2165
2166
  require: "./dist/config.cjs"
2167
+ },
2168
+ "./i18n": {
2169
+ types: "./dist/i18n.d.ts",
2170
+ import: "./dist/i18n.js",
2171
+ require: "./dist/i18n.cjs"
2166
2172
  }
2167
2173
  },
2168
2174
  files: [
@@ -2178,10 +2184,12 @@ var package_default = {
2178
2184
  peerDependencies: {
2179
2185
  "@djangocfg/api": "workspace:*",
2180
2186
  "@djangocfg/ext-base": "workspace:*",
2187
+ "@djangocfg/i18n": "workspace:*",
2181
2188
  "@djangocfg/ui-core": "workspace:*",
2182
2189
  consola: "^3.4.2",
2183
2190
  "lucide-react": "^0.545.0",
2184
2191
  next: "^16",
2192
+ "next-intl": "^4",
2185
2193
  "p-retry": "^7.0.0",
2186
2194
  react: "^19",
2187
2195
  "react-dom": "^19",
@@ -2191,10 +2199,12 @@ var package_default = {
2191
2199
  devDependencies: {
2192
2200
  "@djangocfg/api": "workspace:*",
2193
2201
  "@djangocfg/ext-base": "workspace:*",
2202
+ "@djangocfg/i18n": "workspace:*",
2194
2203
  "@djangocfg/typescript-config": "workspace:*",
2195
2204
  "@types/node": "^24.7.2",
2196
2205
  "@types/react": "^19.0.0",
2197
2206
  consola: "^3.4.2",
2207
+ "next-intl": "^4.1.0",
2198
2208
  "p-retry": "^7.0.0",
2199
2209
  swr: "^2.3.7",
2200
2210
  tsup: "^8.5.0",
@@ -2469,6 +2479,65 @@ function useNewsletterContext() {
2469
2479
  }
2470
2480
  return context;
2471
2481
  }
2482
+
2483
+ // src/i18n/locales/en.ts
2484
+ var en = {
2485
+ hero: {
2486
+ placeholder: "Enter your email",
2487
+ subscribe: "Subscribe",
2488
+ subscribing: "Subscribing...",
2489
+ successMessage: "Successfully subscribed!",
2490
+ errorMessage: "Subscription failed. Please try again.",
2491
+ privacyNotice: "By subscribing, you agree to our Privacy Policy and consent to receive updates."
2492
+ }
2493
+ };
2494
+
2495
+ // src/i18n/locales/ru.ts
2496
+ var ru = {
2497
+ hero: {
2498
+ placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 email",
2499
+ subscribe: "\u041F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F",
2500
+ subscribing: "\u041F\u043E\u0434\u043F\u0438\u0441\u043A\u0430...",
2501
+ successMessage: "\u0412\u044B \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u043B\u0438\u0441\u044C!",
2502
+ errorMessage: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.",
2503
+ privacyNotice: "\u041F\u043E\u0434\u043F\u0438\u0441\u044B\u0432\u0430\u044F\u0441\u044C, \u0432\u044B \u0441\u043E\u0433\u043B\u0430\u0448\u0430\u0435\u0442\u0435\u0441\u044C \u0441 \u041F\u043E\u043B\u0438\u0442\u0438\u043A\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0434\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u0438 \u0438 \u0434\u0430\u0451\u0442\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u043D\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0439."
2504
+ }
2505
+ };
2506
+
2507
+ // src/i18n/locales/ko.ts
2508
+ var ko = {
2509
+ hero: {
2510
+ placeholder: "\uC774\uBA54\uC77C\uC744 \uC785\uB825\uD558\uC138\uC694",
2511
+ subscribe: "\uAD6C\uB3C5\uD558\uAE30",
2512
+ subscribing: "\uAD6C\uB3C5 \uC911...",
2513
+ successMessage: "\uC131\uACF5\uC801\uC73C\uB85C \uAD6C\uB3C5\uB418\uC5C8\uC2B5\uB2C8\uB2E4!",
2514
+ errorMessage: "\uAD6C\uB3C5\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
2515
+ privacyNotice: "\uAD6C\uB3C5 \uC2DC \uAC1C\uC778\uC815\uBCF4 \uCC98\uB9AC\uBC29\uCE68\uC5D0 \uB3D9\uC758\uD558\uACE0 \uC5C5\uB370\uC774\uD2B8 \uC218\uC2E0\uC5D0 \uB3D9\uC758\uD558\uB294 \uAC83\uC73C\uB85C \uAC04\uC8FC\uB429\uB2C8\uB2E4."
2516
+ }
2517
+ };
2518
+
2519
+ // src/i18n/useNewsletterT.ts
2520
+ var translations = { en, ru, ko };
2521
+ function getNestedValue(obj, path) {
2522
+ const keys = path.split(".");
2523
+ let result = obj;
2524
+ for (const key of keys) {
2525
+ if (result && typeof result === "object" && key in result) {
2526
+ result = result[key];
2527
+ } else {
2528
+ return path;
2529
+ }
2530
+ }
2531
+ return typeof result === "string" ? result : path;
2532
+ }
2533
+ function useNewsletterT() {
2534
+ const locale = useLocale();
2535
+ const t = useMemo(() => translations[locale] || translations.en, [locale]);
2536
+ return useCallback(
2537
+ (key) => getNestedValue(t, key),
2538
+ [t]
2539
+ );
2540
+ }
2472
2541
  var isDevelopment = process.env.NODE_ENV === "development";
2473
2542
  var logger = createConsola({
2474
2543
  level: isDevelopment ? 4 : 1
@@ -2480,15 +2549,24 @@ function Hero({
2480
2549
  primaryAction,
2481
2550
  secondaryAction,
2482
2551
  showNewsletter = true,
2483
- newsletterPlaceholder = "Enter your email",
2484
- newsletterButtonText = "Subscribe",
2552
+ newsletterPlaceholder,
2553
+ newsletterButtonText,
2485
2554
  onNewsletterSubmit,
2486
2555
  className = ""
2487
2556
  }) {
2557
+ const nt = useNewsletterT();
2488
2558
  const [email, setEmail] = useState("");
2489
2559
  const [isLoading, setIsLoading] = useState(false);
2490
2560
  const [status, setStatus] = useState("idle");
2491
2561
  const [message, setMessage] = useState("");
2562
+ const labels = useMemo(() => ({
2563
+ placeholder: newsletterPlaceholder ?? nt("hero.placeholder"),
2564
+ subscribe: newsletterButtonText ?? nt("hero.subscribe"),
2565
+ subscribing: nt("hero.subscribing"),
2566
+ successMessage: nt("hero.successMessage"),
2567
+ errorMessage: nt("hero.errorMessage"),
2568
+ privacyNotice: nt("hero.privacyNotice")
2569
+ }), [nt, newsletterPlaceholder, newsletterButtonText]);
2492
2570
  const handleSubmit = async (e) => {
2493
2571
  e.preventDefault();
2494
2572
  if (!email || !onNewsletterSubmit) return;
@@ -2498,12 +2576,12 @@ function Hero({
2498
2576
  try {
2499
2577
  const result = await onNewsletterSubmit(email);
2500
2578
  setStatus("success");
2501
- setMessage((result && "message" in result ? result.message : void 0) || "Successfully subscribed!");
2579
+ setMessage((result && "message" in result ? result.message : void 0) || labels.successMessage);
2502
2580
  setEmail("");
2503
2581
  newsletterLogger.success("Newsletter subscription successful:", email);
2504
2582
  } catch (error) {
2505
2583
  setStatus("error");
2506
- setMessage(error instanceof Error ? error.message : "Subscription failed. Please try again.");
2584
+ setMessage(error instanceof Error ? error.message : labels.errorMessage);
2507
2585
  newsletterLogger.error("Newsletter subscription failed:", error);
2508
2586
  } finally {
2509
2587
  setIsLoading(false);
@@ -2546,7 +2624,7 @@ function Hero({
2546
2624
  Input,
2547
2625
  {
2548
2626
  type: "email",
2549
- placeholder: newsletterPlaceholder,
2627
+ placeholder: labels.placeholder,
2550
2628
  value: email,
2551
2629
  onChange: (e) => setEmail(e.target.value),
2552
2630
  disabled: isLoading,
@@ -2563,8 +2641,8 @@ function Hero({
2563
2641
  className: "w-full sm:w-auto",
2564
2642
  children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
2565
2643
  /* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
2566
- "Subscribing..."
2567
- ] }) : newsletterButtonText
2644
+ labels.subscribing
2645
+ ] }) : labels.subscribe
2568
2646
  }
2569
2647
  )
2570
2648
  ] }),
@@ -2577,7 +2655,7 @@ function Hero({
2577
2655
  /* @__PURE__ */ jsx("span", { children: message })
2578
2656
  ] })
2579
2657
  ] }),
2580
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-3", children: "By subscribing, you agree to our Privacy Policy and consent to receive updates." })
2658
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-3", children: labels.privacyNotice })
2581
2659
  ] })
2582
2660
  ] }) }) });
2583
2661
  }
package/dist/i18n.cjs ADDED
@@ -0,0 +1,98 @@
1
+ "use client";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/i18n/index.ts
21
+ var i18n_exports = {};
22
+ __export(i18n_exports, {
23
+ en: () => en,
24
+ ko: () => ko,
25
+ ru: () => ru,
26
+ useNewsletterT: () => useNewsletterT
27
+ });
28
+ module.exports = __toCommonJS(i18n_exports);
29
+
30
+ // src/i18n/useNewsletterT.ts
31
+ var import_next_intl = require("next-intl");
32
+ var import_react = require("react");
33
+
34
+ // src/i18n/locales/en.ts
35
+ var en = {
36
+ hero: {
37
+ placeholder: "Enter your email",
38
+ subscribe: "Subscribe",
39
+ subscribing: "Subscribing...",
40
+ successMessage: "Successfully subscribed!",
41
+ errorMessage: "Subscription failed. Please try again.",
42
+ privacyNotice: "By subscribing, you agree to our Privacy Policy and consent to receive updates."
43
+ }
44
+ };
45
+
46
+ // src/i18n/locales/ru.ts
47
+ var ru = {
48
+ hero: {
49
+ placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 email",
50
+ subscribe: "\u041F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F",
51
+ subscribing: "\u041F\u043E\u0434\u043F\u0438\u0441\u043A\u0430...",
52
+ successMessage: "\u0412\u044B \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u043B\u0438\u0441\u044C!",
53
+ errorMessage: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.",
54
+ privacyNotice: "\u041F\u043E\u0434\u043F\u0438\u0441\u044B\u0432\u0430\u044F\u0441\u044C, \u0432\u044B \u0441\u043E\u0433\u043B\u0430\u0448\u0430\u0435\u0442\u0435\u0441\u044C \u0441 \u041F\u043E\u043B\u0438\u0442\u0438\u043A\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0434\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u0438 \u0438 \u0434\u0430\u0451\u0442\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u043D\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0439."
55
+ }
56
+ };
57
+
58
+ // src/i18n/locales/ko.ts
59
+ var ko = {
60
+ hero: {
61
+ placeholder: "\uC774\uBA54\uC77C\uC744 \uC785\uB825\uD558\uC138\uC694",
62
+ subscribe: "\uAD6C\uB3C5\uD558\uAE30",
63
+ subscribing: "\uAD6C\uB3C5 \uC911...",
64
+ successMessage: "\uC131\uACF5\uC801\uC73C\uB85C \uAD6C\uB3C5\uB418\uC5C8\uC2B5\uB2C8\uB2E4!",
65
+ errorMessage: "\uAD6C\uB3C5\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
66
+ privacyNotice: "\uAD6C\uB3C5 \uC2DC \uAC1C\uC778\uC815\uBCF4 \uCC98\uB9AC\uBC29\uCE68\uC5D0 \uB3D9\uC758\uD558\uACE0 \uC5C5\uB370\uC774\uD2B8 \uC218\uC2E0\uC5D0 \uB3D9\uC758\uD558\uB294 \uAC83\uC73C\uB85C \uAC04\uC8FC\uB429\uB2C8\uB2E4."
67
+ }
68
+ };
69
+
70
+ // src/i18n/useNewsletterT.ts
71
+ var translations = { en, ru, ko };
72
+ function getNestedValue(obj, path) {
73
+ const keys = path.split(".");
74
+ let result = obj;
75
+ for (const key of keys) {
76
+ if (result && typeof result === "object" && key in result) {
77
+ result = result[key];
78
+ } else {
79
+ return path;
80
+ }
81
+ }
82
+ return typeof result === "string" ? result : path;
83
+ }
84
+ function useNewsletterT() {
85
+ const locale = (0, import_next_intl.useLocale)();
86
+ const t = (0, import_react.useMemo)(() => translations[locale] || translations.en, [locale]);
87
+ return (0, import_react.useCallback)(
88
+ (key) => getNestedValue(t, key),
89
+ [t]
90
+ );
91
+ }
92
+ // Annotate the CommonJS export names for ESM import in node:
93
+ 0 && (module.exports = {
94
+ en,
95
+ ko,
96
+ ru,
97
+ useNewsletterT
98
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Newsletter Extension I18n Types
3
+ */
4
+ /**
5
+ * Helper type to get dot-notation paths from nested object
6
+ */
7
+ type PathKeys<T, Prefix extends string = ''> = T extends object ? {
8
+ [K in keyof T]: K extends string ? T[K] extends object ? PathKeys<T[K], `${Prefix}${K}.`> : `${Prefix}${K}` : never;
9
+ }[keyof T] : never;
10
+ /**
11
+ * Keys for newsletter translations
12
+ */
13
+ type NewsletterLocalKeys = PathKeys<NewsletterTranslations>;
14
+ interface NewsletterTranslations {
15
+ /** Hero */
16
+ hero: {
17
+ placeholder: string;
18
+ subscribe: string;
19
+ subscribing: string;
20
+ successMessage: string;
21
+ errorMessage: string;
22
+ privacyNotice: string;
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Self-contained translation hook for newsletter extension
28
+ *
29
+ * Uses built-in translations based on current locale from next-intl.
30
+ * No need to add translations to app's i18n config.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * function SubscribeForm() {
35
+ * const t = useNewsletterT();
36
+ * return <button>{t('hero.subscribe')}</button>;
37
+ * }
38
+ * ```
39
+ */
40
+ declare function useNewsletterT(): (key: NewsletterLocalKeys) => string;
41
+
42
+ declare const en: NewsletterTranslations;
43
+
44
+ declare const ru: NewsletterTranslations;
45
+
46
+ declare const ko: NewsletterTranslations;
47
+
48
+ export { type NewsletterLocalKeys, type NewsletterTranslations, en, ko, ru, useNewsletterT };
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Newsletter Extension I18n Types
3
+ */
4
+ /**
5
+ * Helper type to get dot-notation paths from nested object
6
+ */
7
+ type PathKeys<T, Prefix extends string = ''> = T extends object ? {
8
+ [K in keyof T]: K extends string ? T[K] extends object ? PathKeys<T[K], `${Prefix}${K}.`> : `${Prefix}${K}` : never;
9
+ }[keyof T] : never;
10
+ /**
11
+ * Keys for newsletter translations
12
+ */
13
+ type NewsletterLocalKeys = PathKeys<NewsletterTranslations>;
14
+ interface NewsletterTranslations {
15
+ /** Hero */
16
+ hero: {
17
+ placeholder: string;
18
+ subscribe: string;
19
+ subscribing: string;
20
+ successMessage: string;
21
+ errorMessage: string;
22
+ privacyNotice: string;
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Self-contained translation hook for newsletter extension
28
+ *
29
+ * Uses built-in translations based on current locale from next-intl.
30
+ * No need to add translations to app's i18n config.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * function SubscribeForm() {
35
+ * const t = useNewsletterT();
36
+ * return <button>{t('hero.subscribe')}</button>;
37
+ * }
38
+ * ```
39
+ */
40
+ declare function useNewsletterT(): (key: NewsletterLocalKeys) => string;
41
+
42
+ declare const en: NewsletterTranslations;
43
+
44
+ declare const ru: NewsletterTranslations;
45
+
46
+ declare const ko: NewsletterTranslations;
47
+
48
+ export { type NewsletterLocalKeys, type NewsletterTranslations, en, ko, ru, useNewsletterT };
package/dist/i18n.js ADDED
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ // src/i18n/useNewsletterT.ts
4
+ import { useLocale } from "next-intl";
5
+ import { useMemo, useCallback } from "react";
6
+
7
+ // src/i18n/locales/en.ts
8
+ var en = {
9
+ hero: {
10
+ placeholder: "Enter your email",
11
+ subscribe: "Subscribe",
12
+ subscribing: "Subscribing...",
13
+ successMessage: "Successfully subscribed!",
14
+ errorMessage: "Subscription failed. Please try again.",
15
+ privacyNotice: "By subscribing, you agree to our Privacy Policy and consent to receive updates."
16
+ }
17
+ };
18
+
19
+ // src/i18n/locales/ru.ts
20
+ var ru = {
21
+ hero: {
22
+ placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 email",
23
+ subscribe: "\u041F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F",
24
+ subscribing: "\u041F\u043E\u0434\u043F\u0438\u0441\u043A\u0430...",
25
+ successMessage: "\u0412\u044B \u0443\u0441\u043F\u0435\u0448\u043D\u043E \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u043B\u0438\u0441\u044C!",
26
+ errorMessage: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.",
27
+ privacyNotice: "\u041F\u043E\u0434\u043F\u0438\u0441\u044B\u0432\u0430\u044F\u0441\u044C, \u0432\u044B \u0441\u043E\u0433\u043B\u0430\u0448\u0430\u0435\u0442\u0435\u0441\u044C \u0441 \u041F\u043E\u043B\u0438\u0442\u0438\u043A\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0434\u0435\u043D\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0441\u0442\u0438 \u0438 \u0434\u0430\u0451\u0442\u0435 \u0441\u043E\u0433\u043B\u0430\u0441\u0438\u0435 \u043D\u0430 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u0438\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0439."
28
+ }
29
+ };
30
+
31
+ // src/i18n/locales/ko.ts
32
+ var ko = {
33
+ hero: {
34
+ placeholder: "\uC774\uBA54\uC77C\uC744 \uC785\uB825\uD558\uC138\uC694",
35
+ subscribe: "\uAD6C\uB3C5\uD558\uAE30",
36
+ subscribing: "\uAD6C\uB3C5 \uC911...",
37
+ successMessage: "\uC131\uACF5\uC801\uC73C\uB85C \uAD6C\uB3C5\uB418\uC5C8\uC2B5\uB2C8\uB2E4!",
38
+ errorMessage: "\uAD6C\uB3C5\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574 \uC8FC\uC138\uC694.",
39
+ privacyNotice: "\uAD6C\uB3C5 \uC2DC \uAC1C\uC778\uC815\uBCF4 \uCC98\uB9AC\uBC29\uCE68\uC5D0 \uB3D9\uC758\uD558\uACE0 \uC5C5\uB370\uC774\uD2B8 \uC218\uC2E0\uC5D0 \uB3D9\uC758\uD558\uB294 \uAC83\uC73C\uB85C \uAC04\uC8FC\uB429\uB2C8\uB2E4."
40
+ }
41
+ };
42
+
43
+ // src/i18n/useNewsletterT.ts
44
+ var translations = { en, ru, ko };
45
+ function getNestedValue(obj, path) {
46
+ const keys = path.split(".");
47
+ let result = obj;
48
+ for (const key of keys) {
49
+ if (result && typeof result === "object" && key in result) {
50
+ result = result[key];
51
+ } else {
52
+ return path;
53
+ }
54
+ }
55
+ return typeof result === "string" ? result : path;
56
+ }
57
+ function useNewsletterT() {
58
+ const locale = useLocale();
59
+ const t = useMemo(() => translations[locale] || translations.en, [locale]);
60
+ return useCallback(
61
+ (key) => getNestedValue(t, key),
62
+ [t]
63
+ );
64
+ }
65
+ export {
66
+ en,
67
+ ko,
68
+ ru,
69
+ useNewsletterT
70
+ };
package/dist/index.cjs CHANGED
@@ -2117,7 +2117,7 @@ var apiNewsletter = api.createExtensionAPI(API);
2117
2117
  // package.json
2118
2118
  var package_default = {
2119
2119
  name: "@djangocfg/ext-newsletter",
2120
- version: "1.0.21",
2120
+ version: "1.0.23",
2121
2121
  description: "Newsletter and subscription management extension for DjangoCFG",
2122
2122
  keywords: [
2123
2123
  "django",
@@ -2163,6 +2163,11 @@ var package_default = {
2163
2163
  types: "./dist/config.d.ts",
2164
2164
  import: "./dist/config.js",
2165
2165
  require: "./dist/config.cjs"
2166
+ },
2167
+ "./i18n": {
2168
+ types: "./dist/i18n.d.ts",
2169
+ import: "./dist/i18n.js",
2170
+ require: "./dist/i18n.cjs"
2166
2171
  }
2167
2172
  },
2168
2173
  files: [
@@ -2178,10 +2183,12 @@ var package_default = {
2178
2183
  peerDependencies: {
2179
2184
  "@djangocfg/api": "workspace:*",
2180
2185
  "@djangocfg/ext-base": "workspace:*",
2186
+ "@djangocfg/i18n": "workspace:*",
2181
2187
  "@djangocfg/ui-core": "workspace:*",
2182
2188
  consola: "^3.4.2",
2183
2189
  "lucide-react": "^0.545.0",
2184
2190
  next: "^16",
2191
+ "next-intl": "^4",
2185
2192
  "p-retry": "^7.0.0",
2186
2193
  react: "^19",
2187
2194
  "react-dom": "^19",
@@ -2191,10 +2198,12 @@ var package_default = {
2191
2198
  devDependencies: {
2192
2199
  "@djangocfg/api": "workspace:*",
2193
2200
  "@djangocfg/ext-base": "workspace:*",
2201
+ "@djangocfg/i18n": "workspace:*",
2194
2202
  "@djangocfg/typescript-config": "workspace:*",
2195
2203
  "@types/node": "^24.7.2",
2196
2204
  "@types/react": "^19.0.0",
2197
2205
  consola: "^3.4.2",
2206
+ "next-intl": "^4.1.0",
2198
2207
  "p-retry": "^7.0.0",
2199
2208
  swr: "^2.3.7",
2200
2209
  tsup: "^8.5.0",
package/dist/index.js CHANGED
@@ -2111,7 +2111,7 @@ var apiNewsletter = createExtensionAPI(API);
2111
2111
  // package.json
2112
2112
  var package_default = {
2113
2113
  name: "@djangocfg/ext-newsletter",
2114
- version: "1.0.21",
2114
+ version: "1.0.23",
2115
2115
  description: "Newsletter and subscription management extension for DjangoCFG",
2116
2116
  keywords: [
2117
2117
  "django",
@@ -2157,6 +2157,11 @@ var package_default = {
2157
2157
  types: "./dist/config.d.ts",
2158
2158
  import: "./dist/config.js",
2159
2159
  require: "./dist/config.cjs"
2160
+ },
2161
+ "./i18n": {
2162
+ types: "./dist/i18n.d.ts",
2163
+ import: "./dist/i18n.js",
2164
+ require: "./dist/i18n.cjs"
2160
2165
  }
2161
2166
  },
2162
2167
  files: [
@@ -2172,10 +2177,12 @@ var package_default = {
2172
2177
  peerDependencies: {
2173
2178
  "@djangocfg/api": "workspace:*",
2174
2179
  "@djangocfg/ext-base": "workspace:*",
2180
+ "@djangocfg/i18n": "workspace:*",
2175
2181
  "@djangocfg/ui-core": "workspace:*",
2176
2182
  consola: "^3.4.2",
2177
2183
  "lucide-react": "^0.545.0",
2178
2184
  next: "^16",
2185
+ "next-intl": "^4",
2179
2186
  "p-retry": "^7.0.0",
2180
2187
  react: "^19",
2181
2188
  "react-dom": "^19",
@@ -2185,10 +2192,12 @@ var package_default = {
2185
2192
  devDependencies: {
2186
2193
  "@djangocfg/api": "workspace:*",
2187
2194
  "@djangocfg/ext-base": "workspace:*",
2195
+ "@djangocfg/i18n": "workspace:*",
2188
2196
  "@djangocfg/typescript-config": "workspace:*",
2189
2197
  "@types/node": "^24.7.2",
2190
2198
  "@types/react": "^19.0.0",
2191
2199
  consola: "^3.4.2",
2200
+ "next-intl": "^4.1.0",
2192
2201
  "p-retry": "^7.0.0",
2193
2202
  swr: "^2.3.7",
2194
2203
  tsup: "^8.5.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ext-newsletter",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "Newsletter and subscription management extension for DjangoCFG",
5
5
  "keywords": [
6
6
  "django",
@@ -46,6 +46,11 @@
46
46
  "types": "./dist/config.d.ts",
47
47
  "import": "./dist/config.js",
48
48
  "require": "./dist/config.cjs"
49
+ },
50
+ "./i18n": {
51
+ "types": "./dist/i18n.d.ts",
52
+ "import": "./dist/i18n.js",
53
+ "require": "./dist/i18n.cjs"
49
54
  }
50
55
  },
51
56
  "files": [
@@ -59,12 +64,14 @@
59
64
  "check": "tsc --noEmit"
60
65
  },
61
66
  "peerDependencies": {
62
- "@djangocfg/api": "^2.1.109",
63
- "@djangocfg/ext-base": "^1.0.16",
64
- "@djangocfg/ui-core": "^2.1.109",
67
+ "@djangocfg/api": "^2.1.124",
68
+ "@djangocfg/ext-base": "^1.0.18",
69
+ "@djangocfg/i18n": "^2.1.124",
70
+ "@djangocfg/ui-core": "^2.1.124",
65
71
  "consola": "^3.4.2",
66
72
  "lucide-react": "^0.545.0",
67
73
  "next": "^16",
74
+ "next-intl": "^4",
68
75
  "p-retry": "^7.0.0",
69
76
  "react": "^19",
70
77
  "react-dom": "^19",
@@ -72,12 +79,14 @@
72
79
  "zod": "^4.3.4"
73
80
  },
74
81
  "devDependencies": {
75
- "@djangocfg/api": "^2.1.109",
76
- "@djangocfg/ext-base": "^1.0.16",
77
- "@djangocfg/typescript-config": "^2.1.109",
82
+ "@djangocfg/api": "^2.1.124",
83
+ "@djangocfg/ext-base": "^1.0.18",
84
+ "@djangocfg/i18n": "^2.1.124",
85
+ "@djangocfg/typescript-config": "^2.1.124",
78
86
  "@types/node": "^24.7.2",
79
87
  "@types/react": "^19.0.0",
80
88
  "consola": "^3.4.2",
89
+ "next-intl": "^4.1.0",
81
90
  "p-retry": "^7.0.0",
82
91
  "swr": "^2.3.7",
83
92
  "tsup": "^8.5.0",
@@ -6,8 +6,9 @@
6
6
  'use client';
7
7
 
8
8
  import { AlertCircle, CheckCircle2, Loader2, Mail } from 'lucide-react';
9
- import React, { useState } from 'react';
9
+ import React, { useMemo, useState } from 'react';
10
10
 
11
+ import { useNewsletterT } from '../../i18n';
11
12
  import { Button, Input } from '@djangocfg/ui-core';
12
13
 
13
14
  import { newsletterLogger } from '../../utils/logger';
@@ -20,16 +21,27 @@ export function Hero({
20
21
  primaryAction,
21
22
  secondaryAction,
22
23
  showNewsletter = true,
23
- newsletterPlaceholder = 'Enter your email',
24
- newsletterButtonText = 'Subscribe',
24
+ newsletterPlaceholder,
25
+ newsletterButtonText,
25
26
  onNewsletterSubmit,
26
27
  className = '',
27
28
  }: HeroProps) {
29
+ const nt = useNewsletterT();
28
30
  const [email, setEmail] = useState('');
29
31
  const [isLoading, setIsLoading] = useState(false);
30
32
  const [status, setStatus] = useState<'idle' | 'success' | 'error'>('idle');
31
33
  const [message, setMessage] = useState('');
32
34
 
35
+ // Prepare labels before JSX render
36
+ const labels = useMemo(() => ({
37
+ placeholder: newsletterPlaceholder ?? nt('hero.placeholder'),
38
+ subscribe: newsletterButtonText ?? nt('hero.subscribe'),
39
+ subscribing: nt('hero.subscribing'),
40
+ successMessage: nt('hero.successMessage'),
41
+ errorMessage: nt('hero.errorMessage'),
42
+ privacyNotice: nt('hero.privacyNotice'),
43
+ }), [nt, newsletterPlaceholder, newsletterButtonText]);
44
+
33
45
  const handleSubmit = async (e: React.FormEvent) => {
34
46
  e.preventDefault();
35
47
 
@@ -42,12 +54,12 @@ export function Hero({
42
54
  try {
43
55
  const result = await onNewsletterSubmit(email);
44
56
  setStatus('success');
45
- setMessage((result && 'message' in result ? result.message : undefined) || 'Successfully subscribed!');
57
+ setMessage((result && 'message' in result ? result.message : undefined) || labels.successMessage);
46
58
  setEmail('');
47
59
  newsletterLogger.success('Newsletter subscription successful:', email);
48
60
  } catch (error) {
49
61
  setStatus('error');
50
- setMessage(error instanceof Error ? error.message : 'Subscription failed. Please try again.');
62
+ setMessage(error instanceof Error ? error.message : labels.errorMessage);
51
63
  newsletterLogger.error('Newsletter subscription failed:', error);
52
64
  } finally {
53
65
  setIsLoading(false);
@@ -109,7 +121,7 @@ export function Hero({
109
121
  <Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
110
122
  <Input
111
123
  type="email"
112
- placeholder={newsletterPlaceholder}
124
+ placeholder={labels.placeholder}
113
125
  value={email}
114
126
  onChange={(e) => setEmail(e.target.value)}
115
127
  disabled={isLoading}
@@ -125,10 +137,10 @@ export function Hero({
125
137
  {isLoading ? (
126
138
  <>
127
139
  <Loader2 className="mr-2 h-4 w-4 animate-spin" />
128
- Subscribing...
140
+ {labels.subscribing}
129
141
  </>
130
142
  ) : (
131
- newsletterButtonText
143
+ labels.subscribe
132
144
  )}
133
145
  </Button>
134
146
  </div>
@@ -150,7 +162,7 @@ export function Hero({
150
162
  </form>
151
163
 
152
164
  <p className="text-xs text-muted-foreground mt-3">
153
- By subscribing, you agree to our Privacy Policy and consent to receive updates.
165
+ {labels.privacyNotice}
154
166
  </p>
155
167
  </div>
156
168
  )}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Newsletter Extension I18n
3
+ *
4
+ * Self-contained translations - no app configuration needed.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import { useNewsletterT } from '@djangocfg/ext-newsletter/i18n';
9
+ *
10
+ * function MyComponent() {
11
+ * const t = useNewsletterT();
12
+ * return <button>{t('hero.subscribe')}</button>;
13
+ * }
14
+ * ```
15
+ */
16
+
17
+ // Self-contained hook (recommended)
18
+ export { useNewsletterT } from './useNewsletterT';
19
+
20
+ // Types
21
+ export type { NewsletterTranslations, NewsletterLocalKeys } from './types';
22
+
23
+ // Locales (for direct access if needed)
24
+ export { en } from './locales/en';
25
+ export { ru } from './locales/ru';
26
+ export { ko } from './locales/ko';
@@ -0,0 +1,12 @@
1
+ import type { NewsletterTranslations } from '../types';
2
+
3
+ export const en: NewsletterTranslations = {
4
+ hero: {
5
+ placeholder: 'Enter your email',
6
+ subscribe: 'Subscribe',
7
+ subscribing: 'Subscribing...',
8
+ successMessage: 'Successfully subscribed!',
9
+ errorMessage: 'Subscription failed. Please try again.',
10
+ privacyNotice: 'By subscribing, you agree to our Privacy Policy and consent to receive updates.',
11
+ },
12
+ };
@@ -0,0 +1,12 @@
1
+ import type { NewsletterTranslations } from '../types';
2
+
3
+ export const ko: NewsletterTranslations = {
4
+ hero: {
5
+ placeholder: '이메일을 입력하세요',
6
+ subscribe: '구독하기',
7
+ subscribing: '구독 중...',
8
+ successMessage: '성공적으로 구독되었습니다!',
9
+ errorMessage: '구독에 실패했습니다. 다시 시도해 주세요.',
10
+ privacyNotice: '구독 시 개인정보 처리방침에 동의하고 업데이트 수신에 동의하는 것으로 간주됩니다.',
11
+ },
12
+ };
@@ -0,0 +1,12 @@
1
+ import type { NewsletterTranslations } from '../types';
2
+
3
+ export const ru: NewsletterTranslations = {
4
+ hero: {
5
+ placeholder: 'Введите email',
6
+ subscribe: 'Подписаться',
7
+ subscribing: 'Подписка...',
8
+ successMessage: 'Вы успешно подписались!',
9
+ errorMessage: 'Не удалось подписаться. Попробуйте ещё раз.',
10
+ privacyNotice: 'Подписываясь, вы соглашаетесь с Политикой конфиденциальности и даёте согласие на получение обновлений.',
11
+ },
12
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Newsletter Extension I18n Types
3
+ */
4
+
5
+ /**
6
+ * Helper type to get dot-notation paths from nested object
7
+ */
8
+ type PathKeys<T, Prefix extends string = ''> = T extends object
9
+ ? {
10
+ [K in keyof T]: K extends string
11
+ ? T[K] extends object
12
+ ? PathKeys<T[K], `${Prefix}${K}.`>
13
+ : `${Prefix}${K}`
14
+ : never;
15
+ }[keyof T]
16
+ : never;
17
+
18
+ /**
19
+ * Keys for newsletter translations
20
+ */
21
+ export type NewsletterLocalKeys = PathKeys<NewsletterTranslations>;
22
+
23
+ export interface NewsletterTranslations {
24
+ /** Hero */
25
+ hero: {
26
+ placeholder: string;
27
+ subscribe: string;
28
+ subscribing: string;
29
+ successMessage: string;
30
+ errorMessage: string;
31
+ privacyNotice: string;
32
+ };
33
+ }
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Self-contained translation hook for ext-newsletter
5
+ *
6
+ * Uses built-in translations, no app configuration needed.
7
+ */
8
+
9
+ import { useLocale } from 'next-intl';
10
+ import { useMemo, useCallback } from 'react';
11
+
12
+ import type { NewsletterTranslations, NewsletterLocalKeys } from './types';
13
+ import { en } from './locales/en';
14
+ import { ru } from './locales/ru';
15
+ import { ko } from './locales/ko';
16
+
17
+ const translations: Record<string, NewsletterTranslations> = { en, ru, ko };
18
+
19
+ /**
20
+ * Get nested value from object by dot-notation path
21
+ */
22
+ function getNestedValue(obj: Record<string, unknown>, path: string): string {
23
+ const keys = path.split('.');
24
+ let result: unknown = obj;
25
+
26
+ for (const key of keys) {
27
+ if (result && typeof result === 'object' && key in result) {
28
+ result = (result as Record<string, unknown>)[key];
29
+ } else {
30
+ return path; // Return key if not found
31
+ }
32
+ }
33
+
34
+ return typeof result === 'string' ? result : path;
35
+ }
36
+
37
+ /**
38
+ * Self-contained translation hook for newsletter extension
39
+ *
40
+ * Uses built-in translations based on current locale from next-intl.
41
+ * No need to add translations to app's i18n config.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * function SubscribeForm() {
46
+ * const t = useNewsletterT();
47
+ * return <button>{t('hero.subscribe')}</button>;
48
+ * }
49
+ * ```
50
+ */
51
+ export function useNewsletterT(): (key: NewsletterLocalKeys) => string {
52
+ const locale = useLocale();
53
+
54
+ const t = useMemo(() => translations[locale] || translations.en, [locale]);
55
+
56
+ return useCallback(
57
+ (key: NewsletterLocalKeys): string => getNestedValue(t as unknown as Record<string, unknown>, key),
58
+ [t]
59
+ );
60
+ }