@djangocfg/layouts 2.1.110 → 2.1.112

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.
@@ -0,0 +1,114 @@
1
+ /**
2
+ * LocaleSwitcher Component (Presentational)
3
+ *
4
+ * "Dumb" locale switcher that receives data via props.
5
+ * For the "smart" version with next-intl hooks, use @djangocfg/nextjs/i18n/components
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <LocaleSwitcher
10
+ * locale="en"
11
+ * locales={['en', 'ru', 'ko']}
12
+ * onChange={(locale) => router.push(`/${locale}`)}
13
+ * />
14
+ * ```
15
+ */
16
+
17
+ 'use client';
18
+
19
+ import { Globe } from 'lucide-react';
20
+
21
+ import {
22
+ Button,
23
+ DropdownMenu,
24
+ DropdownMenuContent,
25
+ DropdownMenuItem,
26
+ DropdownMenuTrigger,
27
+ } from '@djangocfg/ui-core/components';
28
+
29
+ // Default locale labels
30
+ const DEFAULT_LABELS: Record<string, string> = {
31
+ en: 'English',
32
+ ru: 'Русский',
33
+ ko: '한국어',
34
+ zh: '中文',
35
+ ja: '日本語',
36
+ es: 'Español',
37
+ fr: 'Français',
38
+ de: 'Deutsch',
39
+ pt: 'Português',
40
+ it: 'Italiano',
41
+ ar: 'العربية',
42
+ hi: 'हिन्दी',
43
+ tr: 'Türkçe',
44
+ pl: 'Polski',
45
+ nl: 'Nederlands',
46
+ uk: 'Українська',
47
+ };
48
+
49
+ export interface LocaleSwitcherProps {
50
+ /** Current locale */
51
+ locale: string;
52
+ /** Available locales */
53
+ locales: string[];
54
+ /** Callback when locale changes */
55
+ onChange: (locale: string) => void;
56
+ /** Custom labels for locales */
57
+ labels?: Record<string, string>;
58
+ /** Show locale code instead of/with label */
59
+ showCode?: boolean;
60
+ /** Button variant */
61
+ variant?: 'ghost' | 'outline' | 'default';
62
+ /** Button size */
63
+ size?: 'sm' | 'default' | 'lg' | 'icon';
64
+ /** Show icon */
65
+ showIcon?: boolean;
66
+ /** Custom className */
67
+ className?: string;
68
+ }
69
+
70
+ export function LocaleSwitcher({
71
+ locale,
72
+ locales,
73
+ onChange,
74
+ labels = {},
75
+ showCode = false,
76
+ variant = 'ghost',
77
+ size = 'sm',
78
+ showIcon = true,
79
+ className,
80
+ }: LocaleSwitcherProps) {
81
+ const allLabels = { ...DEFAULT_LABELS, ...labels };
82
+
83
+ const getLabel = (code: string) => {
84
+ const label = allLabels[code] || code.toUpperCase();
85
+ if (showCode) {
86
+ return `${code.toUpperCase()} - ${label}`;
87
+ }
88
+ return label;
89
+ };
90
+
91
+ const currentLabel = showCode ? locale.toUpperCase() : getLabel(locale);
92
+
93
+ return (
94
+ <DropdownMenu>
95
+ <DropdownMenuTrigger asChild>
96
+ <Button variant={variant} size={size} className={className}>
97
+ {showIcon && <Globe className="h-4 w-4 mr-1" />}
98
+ <span>{currentLabel}</span>
99
+ </Button>
100
+ </DropdownMenuTrigger>
101
+ <DropdownMenuContent align="end">
102
+ {locales.map((code) => (
103
+ <DropdownMenuItem
104
+ key={code}
105
+ onClick={() => onChange(code)}
106
+ className={code === locale ? 'bg-accent' : ''}
107
+ >
108
+ {getLabel(code)}
109
+ </DropdownMenuItem>
110
+ ))}
111
+ </DropdownMenuContent>
112
+ </DropdownMenu>
113
+ );
114
+ }
@@ -32,9 +32,10 @@
32
32
 
33
33
  import { LogOut } from 'lucide-react';
34
34
  import Link from 'next/link';
35
- import React from 'react';
35
+ import React, { useMemo } from 'react';
36
36
 
37
37
  import { useAuth } from '@djangocfg/api/auth';
38
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
38
39
  import {
39
40
  Avatar, AvatarFallback, AvatarImage, Button, DropdownMenu, DropdownMenuContent,
40
41
  DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator,
@@ -59,6 +60,13 @@ export function UserMenu({
59
60
  }: UserMenuProps) {
60
61
  const { user, isAuthenticated, logout } = useAuth();
61
62
  const [mounted, setMounted] = React.useState(false);
63
+ const t = useTypedT<I18nTranslations>();
64
+
65
+ const labels = useMemo(() => ({
66
+ signIn: t('layouts.profile.login'),
67
+ signOut: t('layouts.profile.signOut'),
68
+ userMenu: t('layouts.profile.userMenu'),
69
+ }), [t]);
62
70
 
63
71
  React.useEffect(() => {
64
72
  setMounted(true);
@@ -78,7 +86,7 @@ export function UserMenu({
78
86
  allGroups.push({
79
87
  items: [
80
88
  {
81
- label: 'Sign Out',
89
+ label: labels.signOut,
82
90
  onClick: () => logout(),
83
91
  icon: LogOut,
84
92
  variant: 'destructive',
@@ -87,7 +95,7 @@ export function UserMenu({
87
95
  });
88
96
 
89
97
  return allGroups;
90
- }, [groups, logout]);
98
+ }, [groups, logout, labels.signOut]);
91
99
 
92
100
  if (!mounted) {
93
101
  return null;
@@ -99,7 +107,7 @@ export function UserMenu({
99
107
  return (
100
108
  <Link href={authPath}>
101
109
  <Button variant="default" className="w-full">
102
- Sign In
110
+ {labels.signIn}
103
111
  </Button>
104
112
  </Link>
105
113
  );
@@ -107,7 +115,7 @@ export function UserMenu({
107
115
  return (
108
116
  <Link href={authPath}>
109
117
  <Button variant="default" size="sm">
110
- Sign In
118
+ {labels.signIn}
111
119
  </Button>
112
120
  </Link>
113
121
  );
@@ -194,7 +202,7 @@ export function UserMenu({
194
202
  <AvatarImage src={userAvatar} alt={displayName} />
195
203
  <AvatarFallback>{userInitial}</AvatarFallback>
196
204
  </Avatar>
197
- <span className="sr-only">User menu</span>
205
+ <span className="sr-only">{labels.userMenu}</span>
198
206
  </Button>
199
207
  </DropdownMenuTrigger>
200
208
  <DropdownMenuContent align="end" className="w-56">
@@ -2,6 +2,9 @@
2
2
  * Shared Layout Components
3
3
  */
4
4
 
5
+ export { LocaleSwitcher } from './LocaleSwitcher';
6
+ export type { LocaleSwitcherProps } from './LocaleSwitcher';
7
+
5
8
  export { UserMenu } from './UserMenu';
6
9
  export type { UserMenuProps } from './UserMenu';
7
10
 
@@ -11,8 +11,8 @@
11
11
  export * from './types';
12
12
 
13
13
  // Shared components
14
- export { UserMenu } from './_components';
15
- export type { UserMenuProps } from './_components';
14
+ export { LocaleSwitcher, UserMenu } from './_components';
15
+ export type { LocaleSwitcherProps, UserMenuProps } from './_components';
16
16
 
17
17
  // Smart layout router
18
18
  export * from './AppLayout';
@@ -1,12 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import { LogIn } from 'lucide-react';
4
- import React, { useState } from 'react';
4
+ import React, { useMemo, useState } from 'react';
5
5
 
6
+ import { useCfgRouter } from '@djangocfg/api/auth';
7
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
6
8
  import {
7
9
  Button, Dialog, DialogContent, DialogHeader, DialogTitle
8
10
  } from '@djangocfg/ui-core/components';
9
- import { useCfgRouter } from '@djangocfg/api/auth';
10
11
  import { useEventListener } from '@djangocfg/ui-core';
11
12
 
12
13
  // Re-export events for backwards compatibility
@@ -27,7 +28,15 @@ export const AuthDialog: React.FC<AuthDialogProps> = ({
27
28
  authPath = '/auth'
28
29
  }) => {
29
30
  const [open, setOpen] = useState(false);
30
- const [message, setMessage] = useState<string>('Please sign in to continue');
31
+ const t = useTypedT<I18nTranslations>();
32
+
33
+ const labels = useMemo(() => ({
34
+ authRequired: t('layouts.auth.authRequired'),
35
+ pleaseSignIn: t('layouts.auth.pleaseSignIn'),
36
+ goToSignIn: t('layouts.auth.goToSignIn'),
37
+ }), [t]);
38
+
39
+ const [message, setMessage] = useState<string>(labels.pleaseSignIn);
31
40
  const router = useCfgRouter();
32
41
 
33
42
  // Listen for open auth dialog event
@@ -44,7 +53,7 @@ export const AuthDialog: React.FC<AuthDialogProps> = ({
44
53
  });
45
54
 
46
55
  const handleClose = () => {
47
- setMessage('Please sign in to continue');
56
+ setMessage(labels.pleaseSignIn);
48
57
  setOpen(false);
49
58
  };
50
59
 
@@ -67,7 +76,7 @@ export const AuthDialog: React.FC<AuthDialogProps> = ({
67
76
  <Dialog open={open} onOpenChange={handleClose}>
68
77
  <DialogContent className="max-w-sm">
69
78
  <DialogHeader>
70
- <DialogTitle>Authentication Required</DialogTitle>
79
+ <DialogTitle>{labels.authRequired}</DialogTitle>
71
80
  </DialogHeader>
72
81
 
73
82
  <div className="space-y-4">
@@ -75,7 +84,7 @@ export const AuthDialog: React.FC<AuthDialogProps> = ({
75
84
 
76
85
  <Button onClick={handleGoToAuth} className="w-full">
77
86
  <LogIn className="h-4 w-4 mr-2" />
78
- Go to Sign In
87
+ {labels.goToSignIn}
79
88
  </Button>
80
89
  </div>
81
90
  </DialogContent>
@@ -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>