@djangocfg/ext-newsletter 1.0.22 → 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.22",
30
+ version: "1.0.23",
31
31
  description: "Newsletter and subscription management extension for DjangoCFG",
32
32
  keywords: [
33
33
  "django",
@@ -98,6 +98,7 @@ var package_default = {
98
98
  consola: "^3.4.2",
99
99
  "lucide-react": "^0.545.0",
100
100
  next: "^16",
101
+ "next-intl": "^4",
101
102
  "p-retry": "^7.0.0",
102
103
  react: "^19",
103
104
  "react-dom": "^19",
@@ -112,6 +113,7 @@ var package_default = {
112
113
  "@types/node": "^24.7.2",
113
114
  "@types/react": "^19.0.0",
114
115
  consola: "^3.4.2",
116
+ "next-intl": "^4.1.0",
115
117
  "p-retry": "^7.0.0",
116
118
  swr: "^2.3.7",
117
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.22",
7
+ version: "1.0.23",
8
8
  description: "Newsletter and subscription management extension for DjangoCFG",
9
9
  keywords: [
10
10
  "django",
@@ -75,6 +75,7 @@ var package_default = {
75
75
  consola: "^3.4.2",
76
76
  "lucide-react": "^0.545.0",
77
77
  next: "^16",
78
+ "next-intl": "^4",
78
79
  "p-retry": "^7.0.0",
79
80
  react: "^19",
80
81
  "react-dom": "^19",
@@ -89,6 +90,7 @@ var package_default = {
89
90
  "@types/node": "^24.7.2",
90
91
  "@types/react": "^19.0.0",
91
92
  consola: "^3.4.2",
93
+ "next-intl": "^4.1.0",
92
94
  "p-retry": "^7.0.0",
93
95
  swr: "^2.3.7",
94
96
  tsup: "^8.5.0",
package/dist/hooks.cjs CHANGED
@@ -10,8 +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 i18n = require('@djangocfg/ext-base/i18n');
14
- var i18n$1 = require('@djangocfg/i18n');
13
+ var nextIntl = require('next-intl');
15
14
  var uiCore = require('@djangocfg/ui-core');
16
15
 
17
16
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -2126,7 +2125,7 @@ var apiNewsletter = api.createExtensionAPI(API);
2126
2125
  // package.json
2127
2126
  var package_default = {
2128
2127
  name: "@djangocfg/ext-newsletter",
2129
- version: "1.0.22",
2128
+ version: "1.0.23",
2130
2129
  description: "Newsletter and subscription management extension for DjangoCFG",
2131
2130
  keywords: [
2132
2131
  "django",
@@ -2197,6 +2196,7 @@ var package_default = {
2197
2196
  consola: "^3.4.2",
2198
2197
  "lucide-react": "^0.545.0",
2199
2198
  next: "^16",
2199
+ "next-intl": "^4",
2200
2200
  "p-retry": "^7.0.0",
2201
2201
  react: "^19",
2202
2202
  "react-dom": "^19",
@@ -2211,6 +2211,7 @@ var package_default = {
2211
2211
  "@types/node": "^24.7.2",
2212
2212
  "@types/react": "^19.0.0",
2213
2213
  consola: "^3.4.2",
2214
+ "next-intl": "^4.1.0",
2214
2215
  "p-retry": "^7.0.0",
2215
2216
  swr: "^2.3.7",
2216
2217
  tsup: "^8.5.0",
@@ -2522,14 +2523,28 @@ var ko = {
2522
2523
  }
2523
2524
  };
2524
2525
 
2525
- // src/i18n/index.ts
2526
- var NEWSLETTER_NAMESPACE = "newsletter";
2527
- var newsletterI18n = i18n.createExtensionI18n({
2528
- namespace: NEWSLETTER_NAMESPACE,
2529
- defaultLocale: "en",
2530
- locales: { en, ru, ko }
2531
- });
2532
- newsletterI18n.getAllTranslations();
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
+ }
2533
2548
  var isDevelopment = process.env.NODE_ENV === "development";
2534
2549
  var logger = consola.createConsola({
2535
2550
  level: isDevelopment ? 4 : 1
@@ -2546,12 +2561,11 @@ function Hero({
2546
2561
  onNewsletterSubmit,
2547
2562
  className = ""
2548
2563
  }) {
2549
- const baseT = i18n$1.useT();
2564
+ const nt = useNewsletterT();
2550
2565
  const [email, setEmail] = react.useState("");
2551
2566
  const [isLoading, setIsLoading] = react.useState(false);
2552
2567
  const [status, setStatus] = react.useState("idle");
2553
2568
  const [message, setMessage] = react.useState("");
2554
- const nt = i18n.createTypedExtensionT(baseT, NEWSLETTER_NAMESPACE);
2555
2569
  const labels = react.useMemo(() => ({
2556
2570
  placeholder: newsletterPlaceholder ?? nt("hero.placeholder"),
2557
2571
  subscribe: newsletterButtonText ?? nt("hero.subscribe"),
package/dist/hooks.js CHANGED
@@ -4,12 +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, useMemo } 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 { createExtensionI18n, createTypedExtensionT } from '@djangocfg/ext-base/i18n';
12
- import { useT } from '@djangocfg/i18n';
11
+ import { useLocale } from 'next-intl';
13
12
  import { Button, Input } from '@djangocfg/ui-core';
14
13
 
15
14
  var __defProp = Object.defineProperty;
@@ -2119,7 +2118,7 @@ var apiNewsletter = createExtensionAPI(API);
2119
2118
  // package.json
2120
2119
  var package_default = {
2121
2120
  name: "@djangocfg/ext-newsletter",
2122
- version: "1.0.22",
2121
+ version: "1.0.23",
2123
2122
  description: "Newsletter and subscription management extension for DjangoCFG",
2124
2123
  keywords: [
2125
2124
  "django",
@@ -2190,6 +2189,7 @@ var package_default = {
2190
2189
  consola: "^3.4.2",
2191
2190
  "lucide-react": "^0.545.0",
2192
2191
  next: "^16",
2192
+ "next-intl": "^4",
2193
2193
  "p-retry": "^7.0.0",
2194
2194
  react: "^19",
2195
2195
  "react-dom": "^19",
@@ -2204,6 +2204,7 @@ var package_default = {
2204
2204
  "@types/node": "^24.7.2",
2205
2205
  "@types/react": "^19.0.0",
2206
2206
  consola: "^3.4.2",
2207
+ "next-intl": "^4.1.0",
2207
2208
  "p-retry": "^7.0.0",
2208
2209
  swr: "^2.3.7",
2209
2210
  tsup: "^8.5.0",
@@ -2515,14 +2516,28 @@ var ko = {
2515
2516
  }
2516
2517
  };
2517
2518
 
2518
- // src/i18n/index.ts
2519
- var NEWSLETTER_NAMESPACE = "newsletter";
2520
- var newsletterI18n = createExtensionI18n({
2521
- namespace: NEWSLETTER_NAMESPACE,
2522
- defaultLocale: "en",
2523
- locales: { en, ru, ko }
2524
- });
2525
- newsletterI18n.getAllTranslations();
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
+ }
2526
2541
  var isDevelopment = process.env.NODE_ENV === "development";
2527
2542
  var logger = createConsola({
2528
2543
  level: isDevelopment ? 4 : 1
@@ -2539,12 +2554,11 @@ function Hero({
2539
2554
  onNewsletterSubmit,
2540
2555
  className = ""
2541
2556
  }) {
2542
- const baseT = useT();
2557
+ const nt = useNewsletterT();
2543
2558
  const [email, setEmail] = useState("");
2544
2559
  const [isLoading, setIsLoading] = useState(false);
2545
2560
  const [status, setStatus] = useState("idle");
2546
2561
  const [message, setMessage] = useState("");
2547
- const nt = createTypedExtensionT(baseT, NEWSLETTER_NAMESPACE);
2548
2562
  const labels = useMemo(() => ({
2549
2563
  placeholder: newsletterPlaceholder ?? nt("hero.placeholder"),
2550
2564
  subscribe: newsletterButtonText ?? nt("hero.subscribe"),
package/dist/i18n.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -19,12 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
20
  // src/i18n/index.ts
20
21
  var i18n_exports = {};
21
22
  __export(i18n_exports, {
22
- NEWSLETTER_NAMESPACE: () => NEWSLETTER_NAMESPACE,
23
- newsletterI18n: () => newsletterI18n,
24
- newsletterTranslations: () => newsletterTranslations
23
+ en: () => en,
24
+ ko: () => ko,
25
+ ru: () => ru,
26
+ useNewsletterT: () => useNewsletterT
25
27
  });
26
28
  module.exports = __toCommonJS(i18n_exports);
27
- var import_i18n = require("@djangocfg/ext-base/i18n");
29
+
30
+ // src/i18n/useNewsletterT.ts
31
+ var import_next_intl = require("next-intl");
32
+ var import_react = require("react");
28
33
 
29
34
  // src/i18n/locales/en.ts
30
35
  var en = {
@@ -62,17 +67,32 @@ var ko = {
62
67
  }
63
68
  };
64
69
 
65
- // src/i18n/index.ts
66
- var NEWSLETTER_NAMESPACE = "newsletter";
67
- var newsletterI18n = (0, import_i18n.createExtensionI18n)({
68
- namespace: NEWSLETTER_NAMESPACE,
69
- defaultLocale: "en",
70
- locales: { en, ru, ko }
71
- });
72
- var newsletterTranslations = newsletterI18n.getAllTranslations();
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
+ }
73
92
  // Annotate the CommonJS export names for ESM import in node:
74
93
  0 && (module.exports = {
75
- NEWSLETTER_NAMESPACE,
76
- newsletterI18n,
77
- newsletterTranslations
94
+ en,
95
+ ko,
96
+ ru,
97
+ useNewsletterT
78
98
  });
package/dist/i18n.d.cts CHANGED
@@ -1,16 +1,14 @@
1
- import * as _djangocfg_ext_base_i18n from '@djangocfg/ext-base/i18n';
2
- import { ExtensionKeys, PathKeys } from '@djangocfg/ext-base/i18n';
3
-
4
1
  /**
5
2
  * Newsletter Extension I18n Types
6
3
  */
7
-
8
4
  /**
9
- * All valid keys for newsletter translations (with namespace)
5
+ * Helper type to get dot-notation paths from nested object
10
6
  */
11
- type NewsletterKeys = ExtensionKeys<'newsletter', NewsletterTranslations>;
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;
12
10
  /**
13
- * Keys without namespace prefix (for createTypedExtensionT)
11
+ * Keys for newsletter translations
14
12
  */
15
13
  type NewsletterLocalKeys = PathKeys<NewsletterTranslations>;
16
14
  interface NewsletterTranslations {
@@ -25,11 +23,26 @@ interface NewsletterTranslations {
25
23
  };
26
24
  }
27
25
 
28
- /** Newsletter extension namespace */
29
- declare const NEWSLETTER_NAMESPACE: "newsletter";
30
- declare const newsletterI18n: _djangocfg_ext_base_i18n.ExtensionI18n<NewsletterTranslations>;
31
- declare const newsletterTranslations: Record<string, {
32
- [namespace: string]: NewsletterTranslations;
33
- }>;
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;
34
47
 
35
- export { NEWSLETTER_NAMESPACE, type NewsletterKeys, type NewsletterLocalKeys, type NewsletterTranslations, newsletterI18n, newsletterTranslations };
48
+ export { type NewsletterLocalKeys, type NewsletterTranslations, en, ko, ru, useNewsletterT };
package/dist/i18n.d.ts CHANGED
@@ -1,16 +1,14 @@
1
- import * as _djangocfg_ext_base_i18n from '@djangocfg/ext-base/i18n';
2
- import { ExtensionKeys, PathKeys } from '@djangocfg/ext-base/i18n';
3
-
4
1
  /**
5
2
  * Newsletter Extension I18n Types
6
3
  */
7
-
8
4
  /**
9
- * All valid keys for newsletter translations (with namespace)
5
+ * Helper type to get dot-notation paths from nested object
10
6
  */
11
- type NewsletterKeys = ExtensionKeys<'newsletter', NewsletterTranslations>;
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;
12
10
  /**
13
- * Keys without namespace prefix (for createTypedExtensionT)
11
+ * Keys for newsletter translations
14
12
  */
15
13
  type NewsletterLocalKeys = PathKeys<NewsletterTranslations>;
16
14
  interface NewsletterTranslations {
@@ -25,11 +23,26 @@ interface NewsletterTranslations {
25
23
  };
26
24
  }
27
25
 
28
- /** Newsletter extension namespace */
29
- declare const NEWSLETTER_NAMESPACE: "newsletter";
30
- declare const newsletterI18n: _djangocfg_ext_base_i18n.ExtensionI18n<NewsletterTranslations>;
31
- declare const newsletterTranslations: Record<string, {
32
- [namespace: string]: NewsletterTranslations;
33
- }>;
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;
34
47
 
35
- export { NEWSLETTER_NAMESPACE, type NewsletterKeys, type NewsletterLocalKeys, type NewsletterTranslations, newsletterI18n, newsletterTranslations };
48
+ export { type NewsletterLocalKeys, type NewsletterTranslations, en, ko, ru, useNewsletterT };
package/dist/i18n.js CHANGED
@@ -1,5 +1,8 @@
1
- // src/i18n/index.ts
2
- import { createExtensionI18n } from "@djangocfg/ext-base/i18n";
1
+ "use client";
2
+
3
+ // src/i18n/useNewsletterT.ts
4
+ import { useLocale } from "next-intl";
5
+ import { useMemo, useCallback } from "react";
3
6
 
4
7
  // src/i18n/locales/en.ts
5
8
  var en = {
@@ -37,16 +40,31 @@ var ko = {
37
40
  }
38
41
  };
39
42
 
40
- // src/i18n/index.ts
41
- var NEWSLETTER_NAMESPACE = "newsletter";
42
- var newsletterI18n = createExtensionI18n({
43
- namespace: NEWSLETTER_NAMESPACE,
44
- defaultLocale: "en",
45
- locales: { en, ru, ko }
46
- });
47
- var newsletterTranslations = newsletterI18n.getAllTranslations();
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
+ }
48
65
  export {
49
- NEWSLETTER_NAMESPACE,
50
- newsletterI18n,
51
- newsletterTranslations
66
+ en,
67
+ ko,
68
+ ru,
69
+ useNewsletterT
52
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.22",
2120
+ version: "1.0.23",
2121
2121
  description: "Newsletter and subscription management extension for DjangoCFG",
2122
2122
  keywords: [
2123
2123
  "django",
@@ -2188,6 +2188,7 @@ var package_default = {
2188
2188
  consola: "^3.4.2",
2189
2189
  "lucide-react": "^0.545.0",
2190
2190
  next: "^16",
2191
+ "next-intl": "^4",
2191
2192
  "p-retry": "^7.0.0",
2192
2193
  react: "^19",
2193
2194
  "react-dom": "^19",
@@ -2202,6 +2203,7 @@ var package_default = {
2202
2203
  "@types/node": "^24.7.2",
2203
2204
  "@types/react": "^19.0.0",
2204
2205
  consola: "^3.4.2",
2206
+ "next-intl": "^4.1.0",
2205
2207
  "p-retry": "^7.0.0",
2206
2208
  swr: "^2.3.7",
2207
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.22",
2114
+ version: "1.0.23",
2115
2115
  description: "Newsletter and subscription management extension for DjangoCFG",
2116
2116
  keywords: [
2117
2117
  "django",
@@ -2182,6 +2182,7 @@ var package_default = {
2182
2182
  consola: "^3.4.2",
2183
2183
  "lucide-react": "^0.545.0",
2184
2184
  next: "^16",
2185
+ "next-intl": "^4",
2185
2186
  "p-retry": "^7.0.0",
2186
2187
  react: "^19",
2187
2188
  "react-dom": "^19",
@@ -2196,6 +2197,7 @@ var package_default = {
2196
2197
  "@types/node": "^24.7.2",
2197
2198
  "@types/react": "^19.0.0",
2198
2199
  consola: "^3.4.2",
2200
+ "next-intl": "^4.1.0",
2199
2201
  "p-retry": "^7.0.0",
2200
2202
  swr: "^2.3.7",
2201
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.22",
3
+ "version": "1.0.23",
4
4
  "description": "Newsletter and subscription management extension for DjangoCFG",
5
5
  "keywords": [
6
6
  "django",
@@ -64,13 +64,14 @@
64
64
  "check": "tsc --noEmit"
65
65
  },
66
66
  "peerDependencies": {
67
- "@djangocfg/api": "^2.1.111",
68
- "@djangocfg/ext-base": "^1.0.17",
69
- "@djangocfg/i18n": "^2.1.111",
70
- "@djangocfg/ui-core": "^2.1.111",
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",
71
71
  "consola": "^3.4.2",
72
72
  "lucide-react": "^0.545.0",
73
73
  "next": "^16",
74
+ "next-intl": "^4",
74
75
  "p-retry": "^7.0.0",
75
76
  "react": "^19",
76
77
  "react-dom": "^19",
@@ -78,13 +79,14 @@
78
79
  "zod": "^4.3.4"
79
80
  },
80
81
  "devDependencies": {
81
- "@djangocfg/api": "^2.1.111",
82
- "@djangocfg/ext-base": "^1.0.17",
83
- "@djangocfg/i18n": "^2.1.111",
84
- "@djangocfg/typescript-config": "^2.1.111",
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",
85
86
  "@types/node": "^24.7.2",
86
87
  "@types/react": "^19.0.0",
87
88
  "consola": "^3.4.2",
89
+ "next-intl": "^4.1.0",
88
90
  "p-retry": "^7.0.0",
89
91
  "swr": "^2.3.7",
90
92
  "tsup": "^8.5.0",
@@ -8,9 +8,7 @@
8
8
  import { AlertCircle, CheckCircle2, Loader2, Mail } from 'lucide-react';
9
9
  import React, { useMemo, useState } from 'react';
10
10
 
11
- import { createTypedExtensionT } from '@djangocfg/ext-base/i18n';
12
- import { useT } from '@djangocfg/i18n';
13
- import { NEWSLETTER_NAMESPACE, type NewsletterTranslations } from '../../i18n';
11
+ import { useNewsletterT } from '../../i18n';
14
12
  import { Button, Input } from '@djangocfg/ui-core';
15
13
 
16
14
  import { newsletterLogger } from '../../utils/logger';
@@ -28,16 +26,12 @@ export function Hero({
28
26
  onNewsletterSubmit,
29
27
  className = '',
30
28
  }: HeroProps) {
31
- const baseT = useT();
29
+ const nt = useNewsletterT();
32
30
  const [email, setEmail] = useState('');
33
31
  const [isLoading, setIsLoading] = useState(false);
34
32
  const [status, setStatus] = useState<'idle' | 'success' | 'error'>('idle');
35
33
  const [message, setMessage] = useState('');
36
34
 
37
- // Type-safe translation function for newsletter extension
38
- // Keys are validated at compile time: nt('hero.placeholder') OK, nt('hero.typo') Error
39
- const nt = createTypedExtensionT<typeof NEWSLETTER_NAMESPACE, NewsletterTranslations>(baseT, NEWSLETTER_NAMESPACE);
40
-
41
35
  // Prepare labels before JSX render
42
36
  const labels = useMemo(() => ({
43
37
  placeholder: newsletterPlaceholder ?? nt('hero.placeholder'),
package/src/i18n/index.ts CHANGED
@@ -1,23 +1,26 @@
1
1
  /**
2
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
+ * ```
3
15
  */
4
16
 
5
- import { createExtensionI18n } from '@djangocfg/ext-base/i18n';
6
- import type { NewsletterTranslations } from './types';
7
- import { en } from './locales/en';
8
- import { ru } from './locales/ru';
9
- import { ko } from './locales/ko';
10
-
11
- /** Newsletter extension namespace */
12
- export const NEWSLETTER_NAMESPACE = 'newsletter' as const;
13
-
14
- export const newsletterI18n = createExtensionI18n<NewsletterTranslations>({
15
- namespace: NEWSLETTER_NAMESPACE,
16
- defaultLocale: 'en',
17
- locales: { en, ru, ko },
18
- });
19
-
20
- export const newsletterTranslations = newsletterI18n.getAllTranslations();
17
+ // Self-contained hook (recommended)
18
+ export { useNewsletterT } from './useNewsletterT';
21
19
 
22
20
  // Types
23
- export type { NewsletterTranslations, NewsletterKeys, NewsletterLocalKeys } from './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';
package/src/i18n/types.ts CHANGED
@@ -2,15 +2,21 @@
2
2
  * Newsletter Extension I18n Types
3
3
  */
4
4
 
5
- import type { ExtensionKeys, PathKeys } from '@djangocfg/ext-base/i18n';
6
-
7
5
  /**
8
- * All valid keys for newsletter translations (with namespace)
6
+ * Helper type to get dot-notation paths from nested object
9
7
  */
10
- export type NewsletterKeys = ExtensionKeys<'newsletter', NewsletterTranslations>;
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;
11
17
 
12
18
  /**
13
- * Keys without namespace prefix (for createTypedExtensionT)
19
+ * Keys for newsletter translations
14
20
  */
15
21
  export type NewsletterLocalKeys = PathKeys<NewsletterTranslations>;
16
22
 
@@ -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
+ }