@djangocfg/centrifugo 2.1.109 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/centrifugo",
3
- "version": "2.1.109",
3
+ "version": "2.1.111",
4
4
  "description": "Production-ready Centrifugo WebSocket client for React with real-time subscriptions, RPC patterns, and connection state management",
5
5
  "keywords": [
6
6
  "centrifugo",
@@ -63,10 +63,11 @@
63
63
  "centrifuge": "^5.2.2"
64
64
  },
65
65
  "peerDependencies": {
66
- "@djangocfg/api": "^2.1.109",
67
- "@djangocfg/ui-core": "^2.1.109",
68
- "@djangocfg/ui-tools": "^2.1.109",
69
- "@djangocfg/layouts": "^2.1.109",
66
+ "@djangocfg/api": "^2.1.111",
67
+ "@djangocfg/i18n": "^2.1.111",
68
+ "@djangocfg/ui-core": "^2.1.111",
69
+ "@djangocfg/ui-tools": "^2.1.111",
70
+ "@djangocfg/layouts": "^2.1.111",
70
71
  "consola": "^3.4.2",
71
72
  "lucide-react": "^0.545.0",
72
73
  "moment": "^2.30.1",
@@ -74,11 +75,12 @@
74
75
  "react-dom": "^19.1.0"
75
76
  },
76
77
  "devDependencies": {
77
- "@djangocfg/api": "^2.1.109",
78
- "@djangocfg/layouts": "^2.1.109",
79
- "@djangocfg/typescript-config": "^2.1.109",
80
- "@djangocfg/ui-core": "^2.1.109",
81
- "@djangocfg/ui-tools": "^2.1.109",
78
+ "@djangocfg/api": "^2.1.111",
79
+ "@djangocfg/i18n": "^2.1.111",
80
+ "@djangocfg/layouts": "^2.1.111",
81
+ "@djangocfg/typescript-config": "^2.1.111",
82
+ "@djangocfg/ui-core": "^2.1.111",
83
+ "@djangocfg/ui-tools": "^2.1.111",
82
84
  "@types/node": "^24.7.2",
83
85
  "@types/react": "^19.1.0",
84
86
  "@types/react-dom": "^19.1.0",
@@ -7,8 +7,9 @@
7
7
 
8
8
  'use client';
9
9
 
10
- import React from 'react';
10
+ import React, { useMemo } from 'react';
11
11
 
12
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
12
13
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@djangocfg/ui-core/components';
13
14
 
14
15
  import { ConnectionStatus } from '../ConnectionStatus';
@@ -54,6 +55,14 @@ export function CentrifugoMonitor({
54
55
  onSubscriptionClick,
55
56
  className = '',
56
57
  }: CentrifugoMonitorProps) {
58
+ const t = useTypedT<I18nTranslations>();
59
+
60
+ const labels = useMemo(() => ({
61
+ connection: t('centrifugo.tabs.connection'),
62
+ messages: t('centrifugo.tabs.messages'),
63
+ subscriptions: t('centrifugo.tabs.subscriptions'),
64
+ }), [t]);
65
+
57
66
  // Minimal variant - only connection status
58
67
  if (variant === 'minimal') {
59
68
  return (
@@ -86,9 +95,9 @@ export function CentrifugoMonitor({
86
95
 
87
96
  // Full variant - tabs with all features
88
97
  const tabsToShow = [
89
- showConnectionStatus && { value: 'connection', label: 'Connection' },
90
- showMessagesFeed && { value: 'messages', label: 'Messages' },
91
- showSubscriptions && { value: 'subscriptions', label: 'Subscriptions' },
98
+ showConnectionStatus && { value: 'connection', label: labels.connection },
99
+ showMessagesFeed && { value: 'messages', label: labels.messages },
100
+ showSubscriptions && { value: 'subscriptions', label: labels.subscriptions },
92
101
  ].filter(Boolean) as { value: string; label: string }[];
93
102
 
94
103
  if (tabsToShow.length === 0) {
@@ -8,8 +8,9 @@
8
8
  'use client';
9
9
 
10
10
  import { Activity } from 'lucide-react';
11
- import React, { useState } from 'react';
11
+ import React, { useMemo, useState } from 'react';
12
12
 
13
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
13
14
  import { useEventListener } from '@djangocfg/ui-core';
14
15
  import {
15
16
  Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle
@@ -23,9 +24,15 @@ import { CentrifugoMonitor } from './CentrifugoMonitor';
23
24
  // ─────────────────────────────────────────────────────────────────────────
24
25
 
25
26
  export function CentrifugoMonitorDialog() {
27
+ const t = useTypedT<I18nTranslations>();
26
28
  const [open, setOpen] = useState(false);
27
29
  const [variant, setVariant] = useState<'compact' | 'full' | 'minimal'>('full');
28
30
 
31
+ const labels = useMemo(() => ({
32
+ title: t('centrifugo.monitor.title'),
33
+ description: t('centrifugo.monitor.description'),
34
+ }), [t]);
35
+
29
36
  // Listen for dialog open event
30
37
  useEventListener<typeof CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG, OpenMonitorDialogPayload>(
31
38
  CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
@@ -58,10 +65,10 @@ export function CentrifugoMonitorDialog() {
58
65
  <SheetHeader>
59
66
  <SheetTitle className="flex items-center gap-2">
60
67
  <Activity className="h-5 w-5" />
61
- Centrifugo Monitor
68
+ {labels.title}
62
69
  </SheetTitle>
63
70
  <SheetDescription>
64
- Real-time WebSocket monitoring and debugging
71
+ {labels.description}
65
72
  </SheetDescription>
66
73
  </SheetHeader>
67
74
 
@@ -8,7 +8,9 @@
8
8
  'use client';
9
9
 
10
10
  import { Activity } from 'lucide-react';
11
- import React from 'react';
11
+ import React, { useMemo } from 'react';
12
+
13
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
12
14
 
13
15
  import { emitOpenMonitorDialog, OpenMonitorDialogPayload} from '../../events';
14
16
 
@@ -31,6 +33,11 @@ export function CentrifugoMonitorFAB({
31
33
  size = 'md',
32
34
  variant = 'full',
33
35
  }: CentrifugoMonitorFABProps) {
36
+ const t = useTypedT<I18nTranslations>();
37
+
38
+ const labels = useMemo(() => ({
39
+ openMonitor: t('centrifugo.monitor.openMonitor'),
40
+ }), [t]);
34
41
 
35
42
  // Position styles
36
43
  const positionStyles = {
@@ -67,7 +74,7 @@ export function CentrifugoMonitorFAB({
67
74
  ...sizeStyles[size],
68
75
  zIndex: 9999,
69
76
  }}
70
- aria-label="Open Centrifugo Monitor"
77
+ aria-label={labels.openMonitor}
71
78
  >
72
79
  <Activity className={iconSizes[size]} />
73
80
  </button>
@@ -8,8 +8,9 @@
8
8
  'use client';
9
9
 
10
10
  import { Activity, Maximize2 } 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 { Button, Card, CardContent, CardHeader, CardTitle } from '@djangocfg/ui-core/components';
14
15
 
15
16
  import { emitOpenMonitorDialog } from '../../events';
@@ -30,10 +31,18 @@ export interface CentrifugoMonitorWidgetProps {
30
31
  // ─────────────────────────────────────────────────────────────────────────
31
32
 
32
33
  export function CentrifugoMonitorWidget({
33
- title = 'WebSocket Monitor',
34
+ title,
34
35
  showExpandButton = true,
35
36
  className = '',
36
37
  }: CentrifugoMonitorWidgetProps) {
38
+ const t = useTypedT<I18nTranslations>();
39
+
40
+ const labels = useMemo(() => ({
41
+ widgetTitle: t('centrifugo.monitor.widgetTitle'),
42
+ }), [t]);
43
+
44
+ const displayTitle = title ?? labels.widgetTitle;
45
+
37
46
  const handleExpand = () => {
38
47
  emitOpenMonitorDialog({ variant: 'full' });
39
48
  };
@@ -43,7 +52,7 @@ export function CentrifugoMonitorWidget({
43
52
  <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
44
53
  <CardTitle className="text-sm font-medium flex items-center gap-2">
45
54
  <Activity className="h-4 w-4" />
46
- {title}
55
+ {displayTitle}
47
56
  </CardTitle>
48
57
  {showExpandButton && (
49
58
  <Button
@@ -9,8 +9,9 @@
9
9
 
10
10
  import { Clock, Radio, Wifi, WifiOff } from 'lucide-react';
11
11
  import moment from 'moment';
12
- import React, { useEffect, useState } from 'react';
12
+ import React, { useEffect, useMemo, useState } from 'react';
13
13
 
14
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
14
15
  import { Badge } from '@djangocfg/ui-core/components';
15
16
 
16
17
  import { getConsolaLogger } from '../../core/logger/consolaLogger';
@@ -39,11 +40,20 @@ export function ConnectionStatus({
39
40
  showSubscriptions = false,
40
41
  className = '',
41
42
  }: ConnectionStatusProps) {
43
+ const t = useTypedT<I18nTranslations>();
42
44
  const { isConnected, client } = useCentrifugo();
43
45
  const [connectionTime, setConnectionTime] = useState<moment.Moment | null>(null);
44
46
  const [uptime, setUptime] = useState<string>('');
45
47
  const [activeSubscriptions, setActiveSubscriptions] = useState<number>(0);
46
48
 
49
+ const labels = useMemo(() => ({
50
+ connected: t('centrifugo.status.connected'),
51
+ disconnected: t('centrifugo.status.disconnected'),
52
+ uptime: t('centrifugo.status.uptime'),
53
+ subscriptions: t('centrifugo.status.subscriptions'),
54
+ realtimeUnavailable: t('centrifugo.status.realtimeUnavailable'),
55
+ }), [t]);
56
+
47
57
  // Track connection time
48
58
  useEffect(() => {
49
59
  if (isConnected && !connectionTime) {
@@ -114,7 +124,7 @@ export function ConnectionStatus({
114
124
  className={`flex items-center gap-1 ${isConnected ? 'animate-pulse' : ''} ${className}`}
115
125
  >
116
126
  <span className={`h-2 w-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
117
- {isConnected ? 'Connected' : 'Disconnected'}
127
+ {isConnected ? labels.connected : labels.disconnected}
118
128
  </Badge>
119
129
  );
120
130
  }
@@ -129,7 +139,7 @@ export function ConnectionStatus({
129
139
  <WifiOff className="h-4 w-4 text-red-600" />
130
140
  )}
131
141
  <span className="text-sm font-medium">
132
- {isConnected ? 'Connected' : 'Disconnected'}
142
+ {isConnected ? labels.connected : labels.disconnected}
133
143
  </span>
134
144
  {showUptime && uptime && (
135
145
  <span className="text-xs text-muted-foreground">({uptime})</span>
@@ -154,7 +164,7 @@ export function ConnectionStatus({
154
164
  className={`flex items-center gap-1 ${isConnected ? 'animate-pulse' : ''}`}
155
165
  >
156
166
  <span className={`h-2 w-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
157
- {isConnected ? 'Connected' : 'Disconnected'}
167
+ {isConnected ? labels.connected : labels.disconnected}
158
168
  </Badge>
159
169
  </div>
160
170
 
@@ -166,7 +176,7 @@ export function ConnectionStatus({
166
176
  <div className="flex items-center justify-between text-xs">
167
177
  <span className="text-muted-foreground flex items-center gap-1">
168
178
  <Clock className="h-3 w-3" />
169
- Uptime:
179
+ {labels.uptime}
170
180
  </span>
171
181
  <span className="font-mono font-medium">{uptime}</span>
172
182
  </div>
@@ -177,7 +187,7 @@ export function ConnectionStatus({
177
187
  <div className="flex items-center justify-between text-xs">
178
188
  <span className="text-muted-foreground flex items-center gap-1">
179
189
  <Radio className="h-3 w-3" />
180
- Subscriptions:
190
+ {labels.subscriptions}
181
191
  </span>
182
192
  <span className="font-mono font-medium">{activeSubscriptions}</span>
183
193
  </div>
@@ -185,7 +195,7 @@ export function ConnectionStatus({
185
195
  </div>
186
196
  ) : (
187
197
  <div className="text-xs text-muted-foreground p-2 rounded bg-red-50 dark:bg-red-950/20">
188
- Real-time features unavailable
198
+ {labels.realtimeUnavailable}
189
199
  </div>
190
200
  )}
191
201
  </div>
@@ -7,8 +7,9 @@
7
7
  'use client';
8
8
 
9
9
  import { Filter, Search, X } 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 { Badge, Button, Input } from '@djangocfg/ui-core/components';
13
14
 
14
15
  import type { MessageFilters as MessageFiltersType } from './types';
@@ -34,6 +35,18 @@ export function MessageFilters({
34
35
  autoScroll,
35
36
  onAutoScrollChange,
36
37
  }: MessageFiltersProps) {
38
+ const t = useTypedT<I18nTranslations>();
39
+
40
+ const labels = useMemo(() => ({
41
+ title: t('centrifugo.filters.title'),
42
+ active: t('centrifugo.filters.active'),
43
+ clear: t('centrifugo.filters.clear'),
44
+ searchMessages: t('centrifugo.filters.searchMessages'),
45
+ level: t('centrifugo.filters.level'),
46
+ type: t('centrifugo.filters.type'),
47
+ autoScrollToLatest: t('centrifugo.filters.autoScrollToLatest'),
48
+ }), [t]);
49
+
37
50
  const hasActiveFilters =
38
51
  (filters.channels && filters.channels.length > 0) ||
39
52
  (filters.types && filters.types.length > 0) ||
@@ -78,17 +91,17 @@ export function MessageFilters({
78
91
  <div className="flex items-center justify-between">
79
92
  <div className="flex items-center gap-2">
80
93
  <Filter className="h-4 w-4 text-muted-foreground" />
81
- <span className="text-sm font-medium">Filters</span>
94
+ <span className="text-sm font-medium">{labels.title}</span>
82
95
  {hasActiveFilters && (
83
96
  <Badge variant="secondary" className="text-xs">
84
- Active
97
+ {labels.active}
85
98
  </Badge>
86
99
  )}
87
100
  </div>
88
101
  {hasActiveFilters && (
89
102
  <Button size="sm" variant="ghost" onClick={handleClearFilters}>
90
103
  <X className="h-3 w-3 mr-1" />
91
- Clear
104
+ {labels.clear}
92
105
  </Button>
93
106
  )}
94
107
  </div>
@@ -98,7 +111,7 @@ export function MessageFilters({
98
111
  <Search className="h-4 w-4 text-muted-foreground" />
99
112
  <Input
100
113
  type="text"
101
- placeholder="Search messages..."
114
+ placeholder={labels.searchMessages}
102
115
  value={filters.searchQuery || ''}
103
116
  onChange={handleSearchChange}
104
117
  className="flex-1"
@@ -107,7 +120,7 @@ export function MessageFilters({
107
120
 
108
121
  {/* Level Filters */}
109
122
  <div className="space-y-2">
110
- <span className="text-xs text-muted-foreground">Level:</span>
123
+ <span className="text-xs text-muted-foreground">{labels.level}</span>
111
124
  <div className="flex flex-wrap gap-2">
112
125
  {(['info', 'success', 'warning', 'error'] as const).map((level) => {
113
126
  const isActive = filters.levels?.includes(level);
@@ -127,7 +140,7 @@ export function MessageFilters({
127
140
 
128
141
  {/* Type Filters */}
129
142
  <div className="space-y-2">
130
- <span className="text-xs text-muted-foreground">Type:</span>
143
+ <span className="text-xs text-muted-foreground">{labels.type}</span>
131
144
  <div className="flex flex-wrap gap-2">
132
145
  {(['connection', 'subscription', 'publication', 'unsubscription', 'error', 'system'] as const).map((type) => {
133
146
  const isActive = filters.types?.includes(type);
@@ -155,7 +168,7 @@ export function MessageFilters({
155
168
  onChange={(e) => onAutoScrollChange(e.target.checked)}
156
169
  className="h-4 w-4 rounded border-gray-300"
157
170
  />
158
- <span>Auto-scroll to latest</span>
171
+ <span>{labels.autoScrollToLatest}</span>
159
172
  </label>
160
173
  </div>
161
174
  )}
@@ -13,6 +13,7 @@ import {
13
13
  import moment from 'moment';
14
14
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
15
 
16
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
16
17
  import {
17
18
  Badge, Button, Card, CardContent, CardHeader, CardTitle, ScrollArea
18
19
  } from '@djangocfg/ui-core/components';
@@ -35,7 +36,15 @@ export function MessagesFeed({
35
36
  onMessageClick,
36
37
  className = '',
37
38
  }: MessagesFeedProps) {
39
+ const t = useTypedT<I18nTranslations>();
38
40
  const { isConnected, client } = useCentrifugo();
41
+
42
+ const labels = useMemo(() => ({
43
+ title: t('centrifugo.feed.title'),
44
+ noMessages: t('centrifugo.feed.noMessages'),
45
+ pausedClickToResume: t('centrifugo.feed.pausedClickToResume'),
46
+ viewData: t('centrifugo.feed.viewData'),
47
+ }), [t]);
39
48
  const [messages, setMessages] = useState<CentrifugoMessage[]>([]);
40
49
  const [isPaused, setIsPaused] = useState(false);
41
50
  const [autoScroll, setAutoScroll] = useState(initialAutoScroll);
@@ -273,7 +282,7 @@ export function MessagesFeed({
273
282
  <div className="flex items-center justify-between">
274
283
  <CardTitle className="flex items-center gap-2">
275
284
  <Activity className="h-5 w-5" />
276
- Messages Feed
285
+ {labels.title}
277
286
  <Badge variant="outline">{filteredMessages.length}</Badge>
278
287
  </CardTitle>
279
288
 
@@ -326,7 +335,7 @@ export function MessagesFeed({
326
335
  <div className="flex flex-col items-center justify-center py-12 text-center">
327
336
  <Activity className="h-12 w-12 text-muted-foreground mb-4" />
328
337
  <p className="text-sm text-muted-foreground">
329
- {isPaused ? 'Paused - Click play to resume' : 'No messages yet'}
338
+ {isPaused ? labels.pausedClickToResume : labels.noMessages}
330
339
  </p>
331
340
  </div>
332
341
  ) : (
@@ -359,7 +368,7 @@ export function MessagesFeed({
359
368
  {msg.formattedData && (
360
369
  <details className="text-xs">
361
370
  <summary className="cursor-pointer text-muted-foreground hover:text-foreground">
362
- View data
371
+ {labels.viewData}
363
372
  </summary>
364
373
  <pre className="mt-2 p-2 bg-muted rounded overflow-x-auto">
365
374
  {msg.formattedData}
@@ -8,8 +8,9 @@
8
8
 
9
9
  import { Subscription, SubscriptionState } from 'centrifuge';
10
10
  import { Radio, RefreshCw, Trash2 } from 'lucide-react';
11
- import React, { useEffect, useState } from 'react';
11
+ import React, { useEffect, useMemo, useState } from 'react';
12
12
 
13
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
13
14
  import {
14
15
  Badge, Button, Card, CardContent, CardHeader, CardTitle, ScrollArea
15
16
  } from '@djangocfg/ui-core/components';
@@ -43,9 +44,16 @@ export function SubscriptionsList({
43
44
  onSubscriptionClick,
44
45
  className = '',
45
46
  }: SubscriptionsListProps) {
47
+ const t = useTypedT<I18nTranslations>();
46
48
  const { isConnected, client } = useCentrifugo();
47
49
  const [subscriptions, setSubscriptions] = useState<SubscriptionItem[]>([]);
48
50
 
51
+ const labels = useMemo(() => ({
52
+ title: t('centrifugo.subscriptionsList.title'),
53
+ notConnected: t('centrifugo.subscriptionsList.notConnected'),
54
+ noActiveSubscriptions: t('centrifugo.subscriptionsList.noActiveSubscriptions'),
55
+ }), [t]);
56
+
49
57
  // Update subscriptions list
50
58
  const updateSubscriptions = () => {
51
59
  if (!client || !isConnected) {
@@ -113,7 +121,7 @@ export function SubscriptionsList({
113
121
  <div className="flex items-center justify-between">
114
122
  <CardTitle className="flex items-center gap-2">
115
123
  <Radio className="h-5 w-5" />
116
- Active Subscriptions
124
+ {labels.title}
117
125
  <Badge variant="outline">{subscriptions.length}</Badge>
118
126
  </CardTitle>
119
127
 
@@ -128,11 +136,11 @@ export function SubscriptionsList({
128
136
  <CardContent>
129
137
  {!isConnected ? (
130
138
  <div className="text-center py-8 text-sm text-muted-foreground">
131
- Not connected to Centrifugo
139
+ {labels.notConnected}
132
140
  </div>
133
141
  ) : subscriptions.length === 0 ? (
134
142
  <div className="text-center py-8 text-sm text-muted-foreground">
135
- No active subscriptions
143
+ {labels.noActiveSubscriptions}
136
144
  </div>
137
145
  ) : (
138
146
  <ScrollArea className="h-[300px]">
@@ -8,8 +8,9 @@
8
8
  'use client';
9
9
 
10
10
  import { Bug } from 'lucide-react';
11
- import { useState } from 'react';
11
+ import { useMemo, useState } from 'react';
12
12
 
13
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
13
14
  import {
14
15
  Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, Tabs, TabsContent, TabsList,
15
16
  TabsTrigger
@@ -24,9 +25,19 @@ import { SubscriptionsTab } from '../SubscriptionsTab';
24
25
  // ─────────────────────────────────────────────────────────────────────────
25
26
 
26
27
  export function DebugPanel() {
28
+ const t = useTypedT<I18nTranslations>();
27
29
  const [isOpen, setIsOpen] = useState(false);
28
30
  const [activeTab, setActiveTab] = useState('connection');
29
31
 
32
+ const labels = useMemo(() => ({
33
+ title: t('centrifugo.debug.title'),
34
+ description: t('centrifugo.debug.description'),
35
+ openDebugPanel: t('centrifugo.debug.openDebugPanel'),
36
+ tabConnection: t('centrifugo.debug.tabConnection'),
37
+ tabLogs: t('centrifugo.debug.tabLogs'),
38
+ tabSubscriptions: t('centrifugo.debug.tabSubscriptions'),
39
+ }), [t]);
40
+
30
41
  return (
31
42
  <>
32
43
  {/* FAB Button (fixed bottom-left) */}
@@ -41,7 +52,7 @@ export function DebugPanel() {
41
52
  height: '56px',
42
53
  zIndex: 9999,
43
54
  }}
44
- aria-label="Open Centrifugo Debug Panel"
55
+ aria-label={labels.openDebugPanel}
45
56
  >
46
57
  <Bug className="h-6 w-6" />
47
58
  </button>
@@ -50,18 +61,18 @@ export function DebugPanel() {
50
61
  <Sheet open={isOpen} onOpenChange={setIsOpen}>
51
62
  <SheetContent side="right" className="w-full sm:max-w-2xl">
52
63
  <SheetHeader>
53
- <SheetTitle>Centrifugo Debug</SheetTitle>
64
+ <SheetTitle>{labels.title}</SheetTitle>
54
65
  <SheetDescription>
55
- WebSocket connection status, logs, and subscriptions
66
+ {labels.description}
56
67
  </SheetDescription>
57
68
  </SheetHeader>
58
69
 
59
70
  {/* Tabs */}
60
71
  <Tabs value={activeTab} onValueChange={setActiveTab} className="mt-6">
61
72
  <TabsList className="grid w-full grid-cols-3">
62
- <TabsTrigger value="connection">Connection</TabsTrigger>
63
- <TabsTrigger value="logs">Logs</TabsTrigger>
64
- <TabsTrigger value="subscriptions">Subscriptions</TabsTrigger>
73
+ <TabsTrigger value="connection">{labels.tabConnection}</TabsTrigger>
74
+ <TabsTrigger value="logs">{labels.tabLogs}</TabsTrigger>
75
+ <TabsTrigger value="subscriptions">{labels.tabSubscriptions}</TabsTrigger>
65
76
  </TabsList>
66
77
 
67
78
  <TabsContent value="connection" className="mt-4">