@djangocfg/layouts 2.1.110 → 2.1.111

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.
@@ -2,7 +2,9 @@
2
2
  import { ChevronRight, Home } from 'lucide-react';
3
3
  import Link from 'next/link';
4
4
  import { usePathname } from 'next/navigation';
5
- import React from 'react';
5
+ import React, { useMemo } from 'react';
6
+
7
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
6
8
 
7
9
  export interface BreadcrumbItem {
8
10
  path: string;
@@ -17,23 +19,32 @@ interface BreadcrumbsProps {
17
19
 
18
20
  const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ items, className = "" }) => {
19
21
  const pathname = usePathname();
20
-
22
+ const t = useTypedT<I18nTranslations>();
23
+
24
+ const labels = useMemo(() => ({
25
+ home: t('layouts.navigation.home'),
26
+ breadcrumb: t('layouts.navigation.breadcrumb'),
27
+ }), [t]);
28
+
21
29
  // Use provided items or generate from pathname
22
- const breadcrumbs = items || generateBreadcrumbsFromPath(pathname);
30
+ const breadcrumbs = useMemo(() => {
31
+ if (items) return items;
32
+ return generateBreadcrumbsFromPath(pathname, labels.home);
33
+ }, [items, pathname, labels.home]);
23
34
 
24
35
  if (breadcrumbs.length <= 1) {
25
36
  return null;
26
37
  }
27
38
 
28
39
  return (
29
- <nav className={`flex items-center space-x-2 text-sm ${className}`} aria-label="Breadcrumb">
40
+ <nav className={`flex items-center space-x-2 text-sm ${className}`} aria-label={labels.breadcrumb}>
30
41
  <ol className="flex items-center space-x-2">
31
42
  {breadcrumbs.map((item: BreadcrumbItem, index: number) => (
32
43
  <li key={item.path} className="flex items-center">
33
44
  {index > 0 && (
34
45
  <ChevronRight className="w-4 h-4 text-gray-400 mx-2" />
35
46
  )}
36
-
47
+
37
48
  {item.isActive ? (
38
49
  <span className="text-gray-900 dark:text-white font-medium flex items-center gap-1">
39
50
  {index === 0 && <Home className="w-4 h-4" />}
@@ -42,7 +53,7 @@ const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ items, className = "" }) => {
42
53
  ) : (
43
54
  <Link
44
55
  href={item.path}
45
- className="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200
56
+ className="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200
46
57
  transition-colors duration-200 flex items-center gap-1 hover:underline"
47
58
  >
48
59
  {index === 0 && <Home className="w-4 h-4" />}
@@ -57,10 +68,10 @@ const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ items, className = "" }) => {
57
68
  };
58
69
 
59
70
  // Helper function to generate breadcrumbs from pathname
60
- function generateBreadcrumbsFromPath(pathname: string): BreadcrumbItem[] {
71
+ function generateBreadcrumbsFromPath(pathname: string, homeLabel: string = 'Home'): BreadcrumbItem[] {
61
72
  const segments = pathname.split('/').filter(Boolean);
62
73
  const breadcrumbs: BreadcrumbItem[] = [
63
- { path: '/', label: 'Home', isActive: pathname === '/' }
74
+ { path: '/', label: homeLabel, isActive: pathname === '/' }
64
75
  ];
65
76
 
66
77
  let currentPath = '';
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { Bot, PanelRight, RotateCcw, X } from 'lucide-react';
4
- import React from 'react';
4
+ import React, { useMemo } from 'react';
5
5
 
6
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
6
7
  import { Button, Card, CardContent, CardFooter, CardHeader } from '@djangocfg/ui-core';
7
8
 
8
9
  import { useAIChatContext } from '../context/AIChatContext';
@@ -21,6 +22,15 @@ export const ChatPanel = React.memo(() => {
21
22
  stopStreaming,
22
23
  clearMessages,
23
24
  } = useAIChatContext();
25
+ const t = useTypedT<I18nTranslations>();
26
+
27
+ const labels = useMemo(() => ({
28
+ aiAssistant: t('layouts.chat.aiAssistant'),
29
+ defaultTitle: t('layouts.chat.defaultTitle'),
30
+ newChat: t('layouts.chat.newChat'),
31
+ closeChat: t('layouts.chat.closeChat'),
32
+ switchToSidebar: t('layouts.chat.switchToSidebar'),
33
+ }), [t]);
24
34
 
25
35
  // Mobile: fullscreen, Desktop: floating panel
26
36
  const panelStyles: React.CSSProperties = isMobile
@@ -60,8 +70,8 @@ export const ChatPanel = React.memo(() => {
60
70
  <Bot className="h-4 w-4 text-primary" />
61
71
  </div>
62
72
  <div>
63
- <h3 className="font-semibold text-sm">{config.title || 'DjangoCFG AI'}</h3>
64
- <p className="text-xs text-muted-foreground">AI Assistant</p>
73
+ <h3 className="font-semibold text-sm">{config.title || labels.defaultTitle}</h3>
74
+ <p className="text-xs text-muted-foreground">{labels.aiAssistant}</p>
65
75
  </div>
66
76
  </div>
67
77
  <div className="flex gap-1">
@@ -71,7 +81,7 @@ export const ChatPanel = React.memo(() => {
71
81
  size="icon"
72
82
  className="h-8 w-8"
73
83
  onClick={clearMessages}
74
- title="New chat"
84
+ title={labels.newChat}
75
85
  >
76
86
  <RotateCcw className="h-4 w-4" />
77
87
  </Button>
@@ -83,12 +93,12 @@ export const ChatPanel = React.memo(() => {
83
93
  size="icon"
84
94
  className="h-8 w-8"
85
95
  onClick={() => setDisplayMode('sidebar')}
86
- title="Switch to sidebar mode"
96
+ title={labels.switchToSidebar}
87
97
  >
88
98
  <PanelRight className="h-4 w-4" />
89
99
  </Button>
90
100
  )}
91
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={closeChat} title="Close">
101
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={closeChat} title={labels.closeChat}>
92
102
  <X className="h-4 w-4" />
93
103
  </Button>
94
104
  </div>
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { Bot, GripVertical, PanelRightClose, RotateCcw, X } from 'lucide-react';
4
- import React, { useEffect } from 'react';
4
+ import React, { useEffect, useMemo } from 'react';
5
5
 
6
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
6
7
  import { Button } from '@djangocfg/ui-core';
7
8
 
8
9
  import { useAIChatContext } from '../context/AIChatContext';
@@ -34,6 +35,17 @@ export const ChatSidebar = React.memo<ChatSidebarProps>(({
34
35
  stopStreaming,
35
36
  clearMessages,
36
37
  } = useAIChatContext();
38
+ const t = useTypedT<I18nTranslations>();
39
+
40
+ const labels = useMemo(() => ({
41
+ aiAssistant: t('layouts.chat.aiAssistant'),
42
+ defaultTitle: t('layouts.chat.defaultTitle'),
43
+ newChat: t('layouts.chat.newChat'),
44
+ closeChat: t('layouts.chat.closeChat'),
45
+ switchToFloating: t('layouts.chat.switchToFloating'),
46
+ dragToResize: t('layouts.chat.dragToResize'),
47
+ howCanIHelp: t('layouts.chat.howCanIHelp'),
48
+ }), [t]);
37
49
 
38
50
  // Use the layout hook for content pushing and resizing
39
51
  const { applyLayout, getSidebarStyles, startResize, isResizing } = useChatLayout();
@@ -65,7 +77,7 @@ export const ChatSidebar = React.memo<ChatSidebarProps>(({
65
77
  `}
66
78
  style={{ width: resizeHandleWidth }}
67
79
  onMouseDown={startResize}
68
- title="Drag to resize"
80
+ title={labels.dragToResize}
69
81
  >
70
82
  {showResizeIcon && (
71
83
  <GripVertical className={`h-4 w-4 ${isResizing ? 'text-primary' : 'text-muted-foreground/50'}`} />
@@ -87,8 +99,8 @@ export const ChatSidebar = React.memo<ChatSidebarProps>(({
87
99
  <Bot className="h-4 w-4 text-primary" />
88
100
  </div>
89
101
  <div>
90
- <h3 className="font-semibold text-sm">{config.title || 'DjangoCFG AI'}</h3>
91
- <p className="text-xs text-muted-foreground">AI Assistant</p>
102
+ <h3 className="font-semibold text-sm">{config.title || labels.defaultTitle}</h3>
103
+ <p className="text-xs text-muted-foreground">{labels.aiAssistant}</p>
92
104
  </div>
93
105
  </div>
94
106
  <div className="flex gap-1">
@@ -98,7 +110,7 @@ export const ChatSidebar = React.memo<ChatSidebarProps>(({
98
110
  size="icon"
99
111
  className="h-8 w-8"
100
112
  onClick={clearMessages}
101
- title="New chat"
113
+ title={labels.newChat}
102
114
  >
103
115
  <RotateCcw className="h-4 w-4" />
104
116
  </Button>
@@ -108,11 +120,11 @@ export const ChatSidebar = React.memo<ChatSidebarProps>(({
108
120
  size="icon"
109
121
  className="h-8 w-8"
110
122
  onClick={() => setDisplayMode('floating')}
111
- title="Switch to floating mode"
123
+ title={labels.switchToFloating}
112
124
  >
113
125
  <PanelRightClose className="h-4 w-4" />
114
126
  </Button>
115
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={closeChat} title="Close chat">
127
+ <Button variant="ghost" size="icon" className="h-8 w-8" onClick={closeChat} title={labels.closeChat}>
116
128
  <X className="h-4 w-4" />
117
129
  </Button>
118
130
  </div>
@@ -128,7 +140,7 @@ export const ChatSidebar = React.memo<ChatSidebarProps>(({
128
140
  isCompact={false}
129
141
  largeGreetingIcon
130
142
  greetingIcon="message"
131
- greetingTitle="How can I help?"
143
+ greetingTitle={labels.howCanIHelp}
132
144
  />
133
145
  </div>
134
146
 
@@ -12,8 +12,9 @@
12
12
  */
13
13
 
14
14
  import { ChevronRight, Download, Share, X } from 'lucide-react';
15
- import React, { useEffect, useState } from 'react';
15
+ import React, { useEffect, useMemo, useState } from 'react';
16
16
 
17
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
17
18
  import { cn } from '@djangocfg/ui-core/lib';
18
19
 
19
20
  import { useInstall } from '../context/InstallContext';
@@ -69,6 +70,18 @@ export function A2HSHint({
69
70
  const [show, setShow] = useState(false);
70
71
  const [showGuide, setShowGuide] = useState(false);
71
72
  const [installing, setInstalling] = useState(false);
73
+ const t = useTypedT<I18nTranslations>();
74
+
75
+ const labels = useMemo(() => ({
76
+ addToHomeScreen: t('layouts.pwa.addToHomeScreen'),
77
+ installApp: t('layouts.pwa.installApp'),
78
+ tapToLearnHow: t('layouts.pwa.tapToLearnHow'),
79
+ clickToSeeGuide: t('layouts.pwa.clickToSeeGuide'),
80
+ tapToInstall: t('layouts.pwa.tapToInstall'),
81
+ installing: t('layouts.pwa.installing'),
82
+ dismiss: t('layouts.pwa.dismiss'),
83
+ appLogo: t('layouts.pwa.appLogo'),
84
+ }), [t]);
72
85
 
73
86
  // Determine if should show hint
74
87
  // Production: iOS (all browsers support PWA via Share) & Android Chrome with native prompt
@@ -141,15 +154,15 @@ export function A2HSHint({
141
154
  let subtitle: React.ReactNode;
142
155
 
143
156
  if (isIOS) {
144
- title = 'Add to Home Screen';
145
- subtitle = <>Tap to learn how <ChevronRight className="w-3 h-3" /></>;
157
+ title = labels.addToHomeScreen;
158
+ subtitle = <>{labels.tapToLearnHow} <ChevronRight className="w-3 h-3" /></>;
146
159
  } else if (isDesktop) {
147
- title = 'Install App';
148
- subtitle = <>Click to see desktop guide <ChevronRight className="w-3 h-3" /></>;
160
+ title = labels.installApp;
161
+ subtitle = <>{labels.clickToSeeGuide} <ChevronRight className="w-3 h-3" /></>;
149
162
  } else {
150
163
  // Android or other mobile with native prompt
151
- title = 'Install App';
152
- subtitle = <>Tap to install <Download className="w-3 h-3" /></>;
164
+ title = labels.installApp;
165
+ subtitle = <>{labels.tapToInstall} <Download className="w-3 h-3" /></>;
153
166
  }
154
167
 
155
168
  return (
@@ -180,7 +193,7 @@ export function A2HSHint({
180
193
  {/* App logo or icon */}
181
194
  <div className="flex-shrink-0">
182
195
  {logo ? (
183
- <img src={logo} alt="App logo" className="w-10 h-10 rounded-lg" />
196
+ <img src={logo} alt={labels.appLogo} className="w-10 h-10 rounded-lg" />
184
197
  ) : (
185
198
  <Share className="w-5 h-5 text-blue-400" />
186
199
  )}
@@ -190,7 +203,7 @@ export function A2HSHint({
190
203
  <div className="flex-1 min-w-0">
191
204
  <p className="text-sm font-medium text-white mb-1">{title}</p>
192
205
  <p className="text-xs text-zinc-400 flex items-center gap-1">
193
- {installing ? 'Installing...' : subtitle}
206
+ {installing ? labels.installing : subtitle}
194
207
  </p>
195
208
  </div>
196
209
 
@@ -201,7 +214,7 @@ export function A2HSHint({
201
214
  handleDismiss();
202
215
  }}
203
216
  className="flex-shrink-0 p-1 hover:bg-zinc-700 rounded transition-colors"
204
- aria-label="Dismiss"
217
+ aria-label={labels.dismiss}
205
218
  >
206
219
  <X className="w-4 h-4 text-zinc-400" />
207
220
  </button>
@@ -11,6 +11,7 @@
11
11
  import { ArrowDownToLine, Check, Menu, Monitor, Plus, Search } from 'lucide-react';
12
12
  import React, { useMemo } from 'react';
13
13
 
14
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
14
15
  import {
15
16
  Button, Card, CardContent, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader,
16
17
  DialogTitle
@@ -32,28 +33,29 @@ function getBrowserCategory(browser: {
32
33
  return 'unknown';
33
34
  }
34
35
 
35
- function getBrowserSteps(category: BrowserCategory, browserName: string): InstallStep[] {
36
+ type TranslationFn = (key: string) => string;
37
+
38
+ function getBrowserSteps(category: BrowserCategory, t: TranslationFn): InstallStep[] {
36
39
  switch (category) {
37
40
  case 'chromium':
38
- // Chrome, Edge, Brave, Arc, Vivaldi, Opera, Yandex, etc.
39
41
  return [
40
42
  {
41
43
  number: 1,
42
- title: 'Find Install Icon',
44
+ title: t('layouts.pwa.chromiumStep1Title'),
43
45
  icon: ArrowDownToLine,
44
- description: 'Look for install icon in address bar (right side)',
46
+ description: t('layouts.pwa.chromiumStep1Desc'),
45
47
  },
46
48
  {
47
49
  number: 2,
48
- title: 'Click Install',
50
+ title: t('layouts.pwa.chromiumStep2Title'),
49
51
  icon: Plus,
50
- description: 'Click the icon and select "Install"',
52
+ description: t('layouts.pwa.chromiumStep2Desc'),
51
53
  },
52
54
  {
53
55
  number: 3,
54
- title: 'Confirm',
56
+ title: t('layouts.pwa.chromiumStep3Title'),
55
57
  icon: Check,
56
- description: 'Click "Install" in the popup dialog',
58
+ description: t('layouts.pwa.chromiumStep3Desc'),
57
59
  },
58
60
  ];
59
61
 
@@ -61,21 +63,21 @@ function getBrowserSteps(category: BrowserCategory, browserName: string): Instal
61
63
  return [
62
64
  {
63
65
  number: 1,
64
- title: 'Open Menu',
66
+ title: t('layouts.pwa.firefoxStep1Title'),
65
67
  icon: Menu,
66
- description: 'Click the menu button (three lines)',
68
+ description: t('layouts.pwa.firefoxStep1Desc'),
67
69
  },
68
70
  {
69
71
  number: 2,
70
- title: 'Find Install Option',
72
+ title: t('layouts.pwa.firefoxStep2Title'),
71
73
  icon: Search,
72
- description: 'Look for "Install" or "Add to Home Screen"',
74
+ description: t('layouts.pwa.firefoxStep2Desc'),
73
75
  },
74
76
  {
75
77
  number: 3,
76
- title: 'Confirm',
78
+ title: t('layouts.pwa.firefoxStep3Title'),
77
79
  icon: Check,
78
- description: 'Follow the installation prompts',
80
+ description: t('layouts.pwa.firefoxStep3Desc'),
79
81
  },
80
82
  ];
81
83
 
@@ -83,15 +85,15 @@ function getBrowserSteps(category: BrowserCategory, browserName: string): Instal
83
85
  return [
84
86
  {
85
87
  number: 1,
86
- title: 'Limited Support',
88
+ title: t('layouts.pwa.safariStep1Title'),
87
89
  icon: Monitor,
88
- description: 'Safari on macOS has limited PWA support',
90
+ description: t('layouts.pwa.safariStep1Desc'),
89
91
  },
90
92
  {
91
93
  number: 2,
92
- title: 'Use Chromium Browser',
94
+ title: t('layouts.pwa.safariStep2Title'),
93
95
  icon: ArrowDownToLine,
94
- description: 'Consider using Chrome, Edge, or Brave for full PWA experience',
96
+ description: t('layouts.pwa.safariStep2Desc'),
95
97
  },
96
98
  ];
97
99
 
@@ -99,21 +101,21 @@ function getBrowserSteps(category: BrowserCategory, browserName: string): Instal
99
101
  return [
100
102
  {
101
103
  number: 1,
102
- title: 'Check Address Bar',
104
+ title: t('layouts.pwa.unknownStep1Title'),
103
105
  icon: ArrowDownToLine,
104
- description: 'Look for an install or download icon',
106
+ description: t('layouts.pwa.unknownStep1Desc'),
105
107
  },
106
108
  {
107
109
  number: 2,
108
- title: 'Or Use Menu',
110
+ title: t('layouts.pwa.unknownStep2Title'),
109
111
  icon: Menu,
110
- description: 'Check browser menu for install option',
112
+ description: t('layouts.pwa.unknownStep2Desc'),
111
113
  },
112
114
  {
113
115
  number: 3,
114
- title: 'Confirm',
116
+ title: t('layouts.pwa.unknownStep3Title'),
115
117
  icon: Check,
116
- description: 'Follow the installation prompts',
118
+ description: t('layouts.pwa.unknownStep3Desc'),
117
119
  },
118
120
  ];
119
121
  }
@@ -157,15 +159,25 @@ export function DesktopGuide({ onDismiss, open = true }: IOSGuideModalProps) {
157
159
  isOpera,
158
160
  isYandex,
159
161
  } = useInstall();
162
+ const t = useTypedT<I18nTranslations>();
163
+
164
+ const labels = useMemo(() => ({
165
+ title: t('layouts.pwa.desktopTitle'),
166
+ description: t('layouts.pwa.desktopDescription'),
167
+ descSafari: t('layouts.pwa.desktopDescSafari'),
168
+ tip: t('layouts.pwa.desktopTip'),
169
+ firefoxNote: t('layouts.pwa.desktopFirefoxNote'),
170
+ gotIt: t('layouts.pwa.gotIt'),
171
+ }), [t]);
160
172
 
161
173
  const category = useMemo(
162
174
  () => getBrowserCategory({ isChromium, isFirefox, isSafari }),
163
175
  [isChromium, isFirefox, isSafari]
164
176
  );
165
177
 
166
- const steps = useMemo(() => getBrowserSteps(category, browserName), [category, browserName]);
178
+ const steps = useMemo(() => getBrowserSteps(category, t), [category, t]);
167
179
 
168
- // Get specific browser display name with emoji
180
+ // Get specific browser display name
169
181
  const displayName = useMemo(() => {
170
182
  if (isEdge) return 'Edge';
171
183
  if (isBrave) return 'Brave';
@@ -182,12 +194,12 @@ export function DesktopGuide({ onDismiss, open = true }: IOSGuideModalProps) {
182
194
  <DialogHeader className="text-left">
183
195
  <DialogTitle className="flex items-center gap-2">
184
196
  <Monitor className="w-5 h-5 text-primary" />
185
- Install App on Desktop
197
+ {labels.title}
186
198
  </DialogTitle>
187
199
  <DialogDescription className="text-left">
188
200
  {isSafari
189
- ? 'Safari on macOS has limited PWA support. For the best experience, use Chrome, Edge, or Brave.'
190
- : `Install this app on ${displayName} for quick access from your desktop`
201
+ ? labels.descSafari
202
+ : labels.description.replace('{browser}', displayName)
191
203
  }
192
204
  </DialogDescription>
193
205
  </DialogHeader>
@@ -200,20 +212,20 @@ export function DesktopGuide({ onDismiss, open = true }: IOSGuideModalProps) {
200
212
 
201
213
  {category === 'chromium' && (
202
214
  <div className="p-3 bg-muted/30 rounded-lg text-xs text-muted-foreground">
203
- 💡 Tip: You can also right-click the page and look for "Install" option in {displayName}
215
+ 💡 {labels.tip.replace('{browser}', displayName)}
204
216
  </div>
205
217
  )}
206
218
 
207
219
  {category === 'firefox' && (
208
220
  <div className="p-3 bg-amber-500/10 rounded-lg text-xs text-amber-700 dark:text-amber-400">
209
- ℹ️ Note: Firefox has limited PWA support. Some features may not work as expected.
221
+ ℹ️ {labels.firefoxNote}
210
222
  </div>
211
223
  )}
212
224
 
213
225
  <DialogFooter>
214
226
  <Button onClick={onDismiss} variant="default" className="w-full">
215
227
  <Check className="w-4 h-4 mr-2" />
216
- Got It
228
+ {labels.gotIt}
217
229
  </Button>
218
230
  </DialogFooter>
219
231
  </DialogContent>
@@ -8,35 +8,15 @@
8
8
  */
9
9
 
10
10
  import { ArrowDown, ArrowUpRight, Check, CheckCircle, Share } from 'lucide-react';
11
- import React from 'react';
11
+ import React, { useMemo } from 'react';
12
12
 
13
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
13
14
  import {
14
15
  Button, Card, CardContent, Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle
15
16
  } from '@djangocfg/ui-core/components';
16
17
 
17
18
  import type { IOSGuideModalProps, InstallStep } from '../types';
18
19
 
19
- const steps: InstallStep[] = [
20
- {
21
- number: 1,
22
- title: 'Tap Share',
23
- icon: ArrowUpRight,
24
- description: 'At the bottom of Safari',
25
- },
26
- {
27
- number: 2,
28
- title: 'Scroll & Tap',
29
- icon: ArrowDown,
30
- description: '"Add to Home Screen"',
31
- },
32
- {
33
- number: 3,
34
- title: 'Confirm',
35
- icon: CheckCircle,
36
- description: 'Tap "Add" in top-right',
37
- },
38
- ];
39
-
40
20
  function StepCard({ step }: { step: InstallStep }) {
41
21
  return (
42
22
  <Card className="border border-border">
@@ -63,16 +43,45 @@ function StepCard({ step }: { step: InstallStep }) {
63
43
  }
64
44
 
65
45
  export function IOSGuideDrawer({ onDismiss, open = true }: IOSGuideModalProps) {
46
+ const t = useTypedT<I18nTranslations>();
47
+
48
+ const labels = useMemo(() => ({
49
+ title: t('layouts.pwa.iosTitle'),
50
+ description: t('layouts.pwa.iosDescription'),
51
+ gotIt: t('layouts.pwa.gotIt'),
52
+ }), [t]);
53
+
54
+ const steps: InstallStep[] = useMemo(() => [
55
+ {
56
+ number: 1,
57
+ title: t('layouts.pwa.iosStep1Title'),
58
+ icon: ArrowUpRight,
59
+ description: t('layouts.pwa.iosStep1Desc'),
60
+ },
61
+ {
62
+ number: 2,
63
+ title: t('layouts.pwa.iosStep2Title'),
64
+ icon: ArrowDown,
65
+ description: t('layouts.pwa.iosStep2Desc'),
66
+ },
67
+ {
68
+ number: 3,
69
+ title: t('layouts.pwa.iosStep3Title'),
70
+ icon: CheckCircle,
71
+ description: t('layouts.pwa.iosStep3Desc'),
72
+ },
73
+ ], [t]);
74
+
66
75
  return (
67
76
  <Drawer open={open} onOpenChange={(isOpen) => !isOpen && onDismiss()}>
68
77
  <DrawerContent>
69
78
  <DrawerHeader className="text-left">
70
79
  <DrawerTitle className="flex items-center gap-2">
71
80
  <Share className="w-5 h-5 text-primary" />
72
- Add to Home Screen
81
+ {labels.title}
73
82
  </DrawerTitle>
74
83
  <DrawerDescription className="text-left">
75
- Install this app on your iPhone for quick access and a better experience
84
+ {labels.description}
76
85
  </DrawerDescription>
77
86
  </DrawerHeader>
78
87
 
@@ -85,7 +94,7 @@ export function IOSGuideDrawer({ onDismiss, open = true }: IOSGuideModalProps) {
85
94
  <div className="p-4 pt-0">
86
95
  <Button onClick={onDismiss} variant="default" className="w-full">
87
96
  <Check className="w-4 h-4 mr-2" />
88
- Got It
97
+ {labels.gotIt}
89
98
  </Button>
90
99
  </div>
91
100
  </DrawerContent>
@@ -7,8 +7,9 @@
7
7
  */
8
8
 
9
9
  import { ArrowDown, ArrowUpRight, Check, CheckCircle, Share } from 'lucide-react';
10
- import React from 'react';
10
+ import React, { useMemo } from 'react';
11
11
 
12
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
12
13
  import {
13
14
  Button, Card, CardContent, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader,
14
15
  DialogTitle
@@ -16,27 +17,6 @@ import {
16
17
 
17
18
  import type { IOSGuideModalProps, InstallStep } from '../types';
18
19
 
19
- const steps: InstallStep[] = [
20
- {
21
- number: 1,
22
- title: 'Tap Share',
23
- icon: ArrowUpRight,
24
- description: 'At the bottom of Safari',
25
- },
26
- {
27
- number: 2,
28
- title: 'Scroll & Tap',
29
- icon: ArrowDown,
30
- description: '"Add to Home Screen"',
31
- },
32
- {
33
- number: 3,
34
- title: 'Confirm',
35
- icon: CheckCircle,
36
- description: 'Tap "Add" in top-right',
37
- },
38
- ];
39
-
40
20
  function StepCard({ step }: { step: InstallStep }) {
41
21
  return (
42
22
  <Card className="border border-border">
@@ -63,16 +43,45 @@ function StepCard({ step }: { step: InstallStep }) {
63
43
  }
64
44
 
65
45
  export function IOSGuideModal({ onDismiss, open = true }: IOSGuideModalProps) {
46
+ const t = useTypedT<I18nTranslations>();
47
+
48
+ const labels = useMemo(() => ({
49
+ title: t('layouts.pwa.iosTitle'),
50
+ description: t('layouts.pwa.iosDescription'),
51
+ gotIt: t('layouts.pwa.gotIt'),
52
+ }), [t]);
53
+
54
+ const steps: InstallStep[] = useMemo(() => [
55
+ {
56
+ number: 1,
57
+ title: t('layouts.pwa.iosStep1Title'),
58
+ icon: ArrowUpRight,
59
+ description: t('layouts.pwa.iosStep1Desc'),
60
+ },
61
+ {
62
+ number: 2,
63
+ title: t('layouts.pwa.iosStep2Title'),
64
+ icon: ArrowDown,
65
+ description: t('layouts.pwa.iosStep2Desc'),
66
+ },
67
+ {
68
+ number: 3,
69
+ title: t('layouts.pwa.iosStep3Title'),
70
+ icon: CheckCircle,
71
+ description: t('layouts.pwa.iosStep3Desc'),
72
+ },
73
+ ], [t]);
74
+
66
75
  return (
67
76
  <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onDismiss()}>
68
77
  <DialogContent className="sm:max-w-md">
69
78
  <DialogHeader className="text-left">
70
79
  <DialogTitle className="flex items-center gap-2">
71
80
  <Share className="w-5 h-5 text-primary" />
72
- Add to Home Screen
81
+ {labels.title}
73
82
  </DialogTitle>
74
83
  <DialogDescription className="text-left">
75
- Install this app on your iPhone for quick access and a better experience
84
+ {labels.description}
76
85
  </DialogDescription>
77
86
  </DialogHeader>
78
87
 
@@ -85,7 +94,7 @@ export function IOSGuideModal({ onDismiss, open = true }: IOSGuideModalProps) {
85
94
  <DialogFooter>
86
95
  <Button onClick={onDismiss} variant="default" className="w-full">
87
96
  <Check className="w-4 h-4 mr-2" />
88
- Got It
97
+ {labels.gotIt}
89
98
  </Button>
90
99
  </DialogFooter>
91
100
  </DialogContent>