@djangocfg/layouts 2.1.590 → 2.1.592

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.
Files changed (71) hide show
  1. package/package.json +12 -12
  2. package/src/layouts/PrivateLayout/PrivateLayout.tsx +37 -26
  3. package/src/layouts/PrivateLayout/README.md +20 -12
  4. package/src/layouts/PrivateLayout/components/PrivateContent.tsx +29 -42
  5. package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +23 -92
  6. package/src/layouts/PrivateLayout/components/PrivateSidebarAccount.tsx +32 -62
  7. package/src/layouts/PrivateLayout/components/SidebarBody.tsx +45 -0
  8. package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +64 -152
  9. package/src/layouts/PrivateLayout/components/SidebarBrandSwitcher.tsx +2 -30
  10. package/src/layouts/PrivateLayout/components/SidebarNavGroup.tsx +55 -115
  11. package/src/layouts/PrivateLayout/components/SidebarNavItem.tsx +37 -100
  12. package/src/layouts/PrivateLayout/components/SidebarPeek.tsx +34 -0
  13. package/src/layouts/PrivateLayout/components/SidebarResizeHandle.tsx +76 -0
  14. package/src/layouts/PrivateLayout/components/SidebarSlots.tsx +14 -53
  15. package/src/layouts/PrivateLayout/components/index.ts +0 -4
  16. package/src/layouts/PrivateLayout/context.tsx +10 -115
  17. package/src/layouts/PrivateLayout/geometry.ts +17 -0
  18. package/src/layouts/PrivateLayout/hooks/index.ts +2 -1
  19. package/src/layouts/PrivateLayout/hooks/useLayoutVisual.ts +4 -4
  20. package/src/layouts/PrivateLayout/hooks/useSidebarKeyboard.ts +33 -93
  21. package/src/layouts/PrivateLayout/hooks/useSidebarPeek.tsx +151 -0
  22. package/src/layouts/PrivateLayout/hooks/useSidebarWidth.ts +83 -0
  23. package/src/layouts/PrivateLayout/types.ts +13 -20
  24. package/src/layouts/SettingsLayout/README.md +23 -10
  25. package/src/layouts/SettingsLayout/SettingsDialog.tsx +14 -8
  26. package/src/layouts/SettingsLayout/SettingsForm.tsx +13 -9
  27. package/src/layouts/SettingsLayout/components/ApiKeySection/ApiKeySection.tsx +9 -7
  28. package/src/layouts/SettingsLayout/components/ApiKeySection/context.tsx +6 -41
  29. package/src/layouts/SettingsLayout/components/AvatarSection.tsx +11 -3
  30. package/src/layouts/SettingsLayout/components/SettingsNav.tsx +5 -9
  31. package/src/layouts/SettingsLayout/components/SettingsNavItem.tsx +5 -7
  32. package/src/layouts/SettingsLayout/components/SettingsPanel.tsx +5 -10
  33. package/src/layouts/SettingsLayout/components/SettingsSearch.tsx +5 -3
  34. package/src/layouts/SettingsLayout/components/SettingsShell.tsx +14 -21
  35. package/src/layouts/SettingsLayout/components/TwoFactorSection/TwoFactorSection.tsx +52 -105
  36. package/src/layouts/SettingsLayout/components/index.ts +0 -1
  37. package/src/layouts/SettingsLayout/context/SettingsContext.tsx +5 -2
  38. package/src/layouts/SettingsLayout/i18n/index.ts +39 -0
  39. package/src/layouts/SettingsLayout/i18n/locales/ar.ts +66 -0
  40. package/src/layouts/SettingsLayout/i18n/locales/da.ts +66 -0
  41. package/src/layouts/SettingsLayout/i18n/locales/de.ts +66 -0
  42. package/src/layouts/SettingsLayout/i18n/locales/en.ts +66 -0
  43. package/src/layouts/SettingsLayout/i18n/locales/es.ts +66 -0
  44. package/src/layouts/SettingsLayout/i18n/locales/fr.ts +66 -0
  45. package/src/layouts/SettingsLayout/i18n/locales/it.ts +66 -0
  46. package/src/layouts/SettingsLayout/i18n/locales/ja.ts +66 -0
  47. package/src/layouts/SettingsLayout/i18n/locales/ko.ts +66 -0
  48. package/src/layouts/SettingsLayout/i18n/locales/nl.ts +66 -0
  49. package/src/layouts/SettingsLayout/i18n/locales/no.ts +66 -0
  50. package/src/layouts/SettingsLayout/i18n/locales/pl.ts +66 -0
  51. package/src/layouts/SettingsLayout/i18n/locales/ptBR.ts +66 -0
  52. package/src/layouts/SettingsLayout/i18n/locales/ru.ts +66 -0
  53. package/src/layouts/SettingsLayout/i18n/locales/sv.ts +66 -0
  54. package/src/layouts/SettingsLayout/i18n/locales/tr.ts +66 -0
  55. package/src/layouts/SettingsLayout/i18n/locales/zh.ts +66 -0
  56. package/src/layouts/SettingsLayout/i18n/types.ts +65 -0
  57. package/src/layouts/SettingsLayout/index.ts +0 -1
  58. package/src/layouts/SettingsLayout/sections/AccountSection.tsx +9 -4
  59. package/src/layouts/SettingsLayout/sections/DeleteAccountRow.tsx +4 -1
  60. package/src/layouts/SettingsLayout/sections/GeneralSection.tsx +25 -0
  61. package/src/layouts/SettingsLayout/sections/PreferencesRows.tsx +12 -4
  62. package/src/layouts/SettingsLayout/sections/builtins.tsx +34 -29
  63. package/src/layouts/SettingsLayout/sections/index.ts +2 -2
  64. package/src/layouts/SettingsLayout/types.ts +12 -4
  65. package/src/layouts/types/layout.types.ts +5 -6
  66. package/src/styles/dashboard.css +10 -0
  67. package/src/layouts/PrivateLayout/density.ts +0 -48
  68. package/src/layouts/PrivateLayout/hooks/useHoverExpand.ts +0 -110
  69. package/src/layouts/PrivateLayout/hooks/useShellVisualState.ts +0 -130
  70. package/src/layouts/SettingsLayout/components/SettingsRailFooter.tsx +0 -74
  71. package/src/layouts/SettingsLayout/sections/SecuritySection.tsx +0 -15
@@ -1,21 +1,19 @@
1
1
  'use client';
2
2
 
3
- import { Loader2, Shield, ShieldOff } from 'lucide-react';
3
+ import { Loader2, ShieldOff } from 'lucide-react';
4
4
  import React, { useEffect, useState } from 'react';
5
5
 
6
6
  import { useTwoFactorSetup, useTwoFactorStatus } from '@djangocfg/api/auth';
7
7
  import {
8
8
  Alert,
9
9
  AlertDescription,
10
- Badge,
11
10
  Button,
12
11
  OTPInput,
13
- Preloader,
14
12
  SettingRow,
15
13
  SettingsBlock,
16
14
  } from '@djangocfg/ui-core/components';
17
- import { cn } from '@djangocfg/ui-core/lib';
18
15
 
16
+ import { fill, useSettingsCopy, type SettingsCopy } from '../../i18n';
19
17
  import { TwoFactorSetup } from '../../../../snippets/TwoFactor';
20
18
 
21
19
  // ─────────────────────────────────────────────────────────────────────────────
@@ -24,24 +22,6 @@ import { TwoFactorSetup } from '../../../../snippets/TwoFactor';
24
22
 
25
23
  type View = 'status' | 'setup';
26
24
 
27
- // ─────────────────────────────────────────────────────────────────────────────
28
- // Status badge
29
- // ─────────────────────────────────────────────────────────────────────────────
30
-
31
- function StatusBadge({ enabled }: { enabled: boolean }) {
32
- return (
33
- <Badge
34
- variant={enabled ? 'default' : 'secondary'}
35
- className={cn(
36
- 'text-xs font-medium',
37
- enabled && 'bg-success-background text-success-foreground border-success-border',
38
- )}
39
- >
40
- {enabled ? 'Enabled' : 'Disabled'}
41
- </Badge>
42
- );
43
- }
44
-
45
25
  // ─────────────────────────────────────────────────────────────────────────────
46
26
  // Disable confirmation — inline, not a dialog. Settings already lives in a
47
27
  // dialog; stacking a second modal on top fights its focus trap and dims the
@@ -61,6 +41,7 @@ function DisableConfirm({
61
41
  onCancel: () => void;
62
42
  }) {
63
43
  // Mounted fresh each time the confirmation opens — state starts clean.
44
+ const copy = useSettingsCopy().security;
64
45
  const [code, setCode] = useState('');
65
46
 
66
47
  return (
@@ -68,10 +49,9 @@ function DisableConfirm({
68
49
  <div className="flex gap-2.5">
69
50
  <ShieldOff className="mt-0.5 h-4 w-4 flex-shrink-0 text-destructive" />
70
51
  <div className="space-y-1">
71
- <p className="text-sm font-medium">Disable two-factor authentication?</p>
52
+ <p className="text-sm font-medium">{copy.disableTitle}</p>
72
53
  <p className="text-[13px] leading-relaxed text-muted-foreground">
73
- Enter the 6-digit code from your authenticator app to confirm.
74
- This will make your account less secure.
54
+ {copy.disableHint}
75
55
  </p>
76
56
  </div>
77
57
  </div>
@@ -94,7 +74,7 @@ function DisableConfirm({
94
74
  />
95
75
  <div className="flex gap-2">
96
76
  <Button variant="outline" size="sm" onClick={onCancel} disabled={isLoading}>
97
- Cancel
77
+ {copy.cancel}
98
78
  </Button>
99
79
  <Button
100
80
  variant="destructive"
@@ -103,9 +83,9 @@ function DisableConfirm({
103
83
  disabled={isLoading || code.length !== 6}
104
84
  >
105
85
  {isLoading ? (
106
- <><Loader2 className="mr-2 h-4 w-4 animate-spin" />Disabling…</>
86
+ <><Loader2 className="mr-2 h-4 w-4 animate-spin" />{copy.disabling}</>
107
87
  ) : (
108
- 'Disable 2FA'
88
+ copy.disable
109
89
  )}
110
90
  </Button>
111
91
  </div>
@@ -118,9 +98,9 @@ function DisableConfirm({
118
98
  // Helpers
119
99
  // ─────────────────────────────────────────────────────────────────────────────
120
100
 
121
- function deviceDescription(createdAt: string, isPrimary: boolean): string {
122
- const added = `Added ${new Date(createdAt).toLocaleDateString()}`;
123
- return isPrimary ? `${added} · Primary` : added;
101
+ function deviceDescription(copy: SettingsCopy['security'], createdAt: string, isPrimary: boolean): string {
102
+ const added = fill(copy.deviceAdded, { date: new Date(createdAt).toLocaleDateString() });
103
+ return isPrimary ? `${added} · ${copy.devicePrimary}` : added;
124
104
  }
125
105
 
126
106
  // ─────────────────────────────────────────────────────────────────────────────
@@ -128,6 +108,7 @@ function deviceDescription(createdAt: string, isPrimary: boolean): string {
128
108
  // ─────────────────────────────────────────────────────────────────────────────
129
109
 
130
110
  export const TwoFactorSection: React.FC = () => {
111
+ const copy = useSettingsCopy().security;
131
112
  const [view, setView] = useState<View>('status');
132
113
  const [showDisable, setShowDisable] = useState(false);
133
114
 
@@ -170,13 +151,13 @@ export const TwoFactorSection: React.FC = () => {
170
151
  // visible (Preloader-based) loading state.
171
152
  if (view === 'setup') {
172
153
  return (
173
- <SettingsBlock title="Set up Two-Factor Authentication">
154
+ <SettingsBlock title={copy.setupTitle}>
174
155
  <div className="flex items-center justify-between pb-2">
175
156
  <p className="text-[13px] leading-relaxed text-muted-foreground">
176
- Scan the QR code with your authenticator app (Google Authenticator, Authy, etc.)
157
+ {copy.setupHint}
177
158
  </p>
178
159
  <Button variant="ghost" size="sm" onClick={() => setView('status')}>
179
- Cancel
160
+ {copy.cancel}
180
161
  </Button>
181
162
  </div>
182
163
  <div className="flex justify-center py-2">
@@ -189,22 +170,15 @@ export const TwoFactorSection: React.FC = () => {
189
170
  );
190
171
  }
191
172
 
192
- // ── Loading skeleton ────────────────────────────────────────────────────────
193
- if (isLoading && has2FAEnabled === null) {
194
- return <Preloader variant="inline" className="py-10" />;
195
- }
196
-
197
173
  // ── Status view ─────────────────────────────────────────────────────────────
198
- const statusDescription = (
199
- <span className="inline-flex items-center gap-2">
200
- <StatusBadge enabled={!!has2FAEnabled} />
201
- <span>
202
- {has2FAEnabled
203
- ? `${devices.length} authenticator device${devices.length !== 1 ? 's' : ''} connected`
204
- : 'Add an extra layer of security to your account'}
205
- </span>
206
- </span>
207
- );
174
+ // One compact block: a single status row (state + action), the inline confirm
175
+ // when turning off, then the connected devices as plain rows.
176
+ const unknown = has2FAEnabled === null;
177
+ const statusDescription = unknown
178
+ ? isLoading ? copy.checking : copy.unavailable
179
+ : has2FAEnabled
180
+ ? copy.on
181
+ : copy.off;
208
182
 
209
183
  const statusAction = has2FAEnabled ? (
210
184
  <Button
@@ -212,70 +186,43 @@ export const TwoFactorSection: React.FC = () => {
212
186
  size="sm"
213
187
  onClick={() => { clearError(); setShowDisable(true); }}
214
188
  disabled={isLoading || showDisable}
215
- className="text-destructive hover:text-destructive hover:bg-destructive/10 border-destructive/30"
216
189
  >
217
- Disable
190
+ {copy.turnOff}
218
191
  </Button>
219
192
  ) : (
220
- <Button size="sm" onClick={handleEnableClick} disabled={isLoading}>
221
- Enable 2FA
193
+ <Button size="sm" onClick={handleEnableClick} disabled={isLoading || unknown}>
194
+ {copy.turnOn}
222
195
  </Button>
223
196
  );
224
197
 
225
198
  return (
226
- <>
227
- <div className="space-y-6">
228
- <SettingsBlock>
229
- <SettingRow
230
- label="Two-Factor Authentication"
231
- description={statusDescription}
232
- action={statusAction}
233
- />
234
-
235
- {/* Inline destructive confirmation (no nested dialog) */}
236
- {showDisable && (
237
- <DisableConfirm
238
- isLoading={isLoading}
239
- error={error}
240
- onConfirm={handleDisableConfirm}
241
- onCancel={handleDisableCancel}
242
- />
243
- )}
244
-
245
- {/* Fetch error */}
246
- {error && !showDisable && (
247
- <div className="py-3">
248
- <Alert variant="destructive">
249
- <AlertDescription>{error}</AlertDescription>
250
- </Alert>
251
- </div>
252
- )}
199
+ <SettingsBlock title={copy.title}>
200
+ <SettingRow
201
+ label={copy.twoFactor}
202
+ description={statusDescription}
203
+ action={statusAction}
204
+ />
205
+
206
+ {showDisable && (
207
+ <DisableConfirm
208
+ isLoading={isLoading}
209
+ error={error}
210
+ onConfirm={handleDisableConfirm}
211
+ onCancel={handleDisableCancel}
212
+ />
213
+ )}
253
214
 
254
- {/* Not enabled — subtle muted note */}
255
- {!has2FAEnabled && (
256
- <p className="flex gap-2 pt-3 text-[13px] leading-relaxed text-muted-foreground">
257
- <Shield className="mt-0.5 h-4 w-4 flex-shrink-0" />
258
- <span>
259
- Two-factor authentication adds a second verification step when signing in,
260
- protecting your account even if your password is compromised.
261
- </span>
262
- </p>
263
- )}
264
- </SettingsBlock>
215
+ {error && !showDisable && (
216
+ <p className="py-2 text-[13px] text-destructive">{error}</p>
217
+ )}
265
218
 
266
- {/* Device list */}
267
- {has2FAEnabled && devices.length > 0 && (
268
- <SettingsBlock title="Connected devices">
269
- {devices.map((device) => (
270
- <SettingRow
271
- key={device.id}
272
- label={device.name}
273
- description={deviceDescription(device.createdAt, device.isPrimary)}
274
- />
275
- ))}
276
- </SettingsBlock>
277
- )}
278
- </div>
279
- </>
219
+ {has2FAEnabled && devices.map((device) => (
220
+ <SettingRow
221
+ key={device.id}
222
+ label={device.name}
223
+ description={deviceDescription(copy, device.createdAt, device.isPrimary)}
224
+ />
225
+ ))}
226
+ </SettingsBlock>
280
227
  );
281
228
  };
@@ -4,4 +4,3 @@ export { SettingsTabs } from './SettingsTabs';
4
4
  export { SettingsNavItem } from './SettingsNavItem';
5
5
  export { SettingsPanel } from './SettingsPanel';
6
6
  export { SettingsSearch } from './SettingsSearch';
7
- export { SettingsRailFooter } from './SettingsRailFooter';
@@ -40,6 +40,9 @@ interface SettingsContextValue {
40
40
  searchable: boolean;
41
41
  }
42
42
 
43
+ /** `searchable: 'auto'` shows the search box only when the rail is long enough to need it. */
44
+ export const SEARCH_MIN_SECTIONS = 10;
45
+
43
46
  const SettingsContext = createContext<SettingsContextValue | null>(null);
44
47
 
45
48
  export const useSettingsContext = (): SettingsContextValue => {
@@ -54,7 +57,7 @@ export interface SettingsProviderProps {
54
57
  /** Fully-merged sections (built-ins + app), pre-concatenated by the caller. */
55
58
  sections: SettingsSection[];
56
59
  groups?: SettingsGroup[];
57
- searchable: boolean;
60
+ searchable: boolean | 'auto';
58
61
  /** Controlled active id (dialog mode passes the store value). */
59
62
  activeId: string | null;
60
63
  /** Called when active section changes (dialog mode writes the store). */
@@ -113,7 +116,7 @@ export const SettingsProvider: React.FC<SettingsProviderProps> = ({
113
116
  setActive,
114
117
  query,
115
118
  setQuery,
116
- searchable,
119
+ searchable: searchable === 'auto' ? visible.length >= SEARCH_MIN_SECTIONS : searchable,
117
120
  }),
118
121
  [title, resolvedGroups, visible, active, resolvedActiveId, setActive, query, searchable],
119
122
  );
@@ -0,0 +1,39 @@
1
+ 'use client';
2
+
3
+ import { createCopy, type Locale } from '@djangocfg/i18n/copy';
4
+
5
+ import { ar } from './locales/ar';
6
+ import { da } from './locales/da';
7
+ import { de } from './locales/de';
8
+ import { en } from './locales/en';
9
+ import { es } from './locales/es';
10
+ import { fr } from './locales/fr';
11
+ import { it } from './locales/it';
12
+ import { ja } from './locales/ja';
13
+ import { ko } from './locales/ko';
14
+ import { nl } from './locales/nl';
15
+ import { no } from './locales/no';
16
+ import { pl } from './locales/pl';
17
+ import { ptBR } from './locales/ptBR';
18
+ import { ru } from './locales/ru';
19
+ import { sv } from './locales/sv';
20
+ import { tr } from './locales/tr';
21
+ import { zh } from './locales/zh';
22
+
23
+ import type { SettingsCopy } from './types';
24
+
25
+ export type { SettingsCopy } from './types';
26
+ export { fill } from '@djangocfg/i18n/copy';
27
+
28
+ /** Every bundled dictionary, typed complete over the shared `Locale` union. */
29
+ export const SETTINGS_DICTIONARIES: Readonly<Record<Locale, SettingsCopy>> = {
30
+ en, ru, ko, ja, de, fr, zh, it, es, nl, ar, tr, pl, sv, no, da,
31
+ 'pt-BR': ptBR,
32
+ };
33
+
34
+ /** Settings copy for the active language: the `@djangocfg/i18n` provider, else English. */
35
+ const kit = createCopy(SETTINGS_DICTIONARIES);
36
+ export const useSettingsCopy = kit.useCopy;
37
+
38
+ /** The bundled locale `useSettingsCopy` resolved to, for date formatting. */
39
+ export const useSettingsLocale = kit.useLocale;
@@ -0,0 +1,66 @@
1
+ import type { SettingsCopy } from '../types';
2
+
3
+ export const ar: SettingsCopy = {
4
+ nav: {
5
+ title: "الإعدادات",
6
+ platform: "المنصة",
7
+ account: "الحساب",
8
+ preferences: "التفضيلات",
9
+ apiKeys: "مفاتيح API",
10
+ apiKeysDescription: "أنشئ المفاتيح وأدرها للوصول البرمجي.",
11
+ },
12
+ prefs: {
13
+ general: "عام",
14
+ other: "أخرى",
15
+ language: "اللغة",
16
+ timeZone: "المنطقة الزمنية",
17
+ theme: "المظهر",
18
+ },
19
+ account: {
20
+ dangerZone: "منطقة الخطر",
21
+ deleteAccountDescription: "احذف حسابك وجميع البيانات المرتبطة به نهائيًا.",
22
+ changeAvatar: "تغيير الصورة الرمزية",
23
+ },
24
+ search: {
25
+ clear: "مسح البحث",
26
+ },
27
+ security: {
28
+ title: "الأمان",
29
+ twoFactor: "المصادقة الثنائية",
30
+ on: "مفعّلة",
31
+ off: "متوقفة · تضيف خطوة ثانية عند تسجيل الدخول",
32
+ checking: "جارٍ التحقق…",
33
+ unavailable: "غير متاحة",
34
+ turnOn: "تفعيل",
35
+ turnOff: "إيقاف",
36
+ setupTitle: "إعداد المصادقة الثنائية",
37
+ setupHint: "امسح رمز QR بتطبيق المصادقة (Google Authenticator أو Authy وغيرهما)",
38
+ cancel: "إلغاء",
39
+ deviceAdded: "أُضيف في {{date}}",
40
+ devicePrimary: "أساسي",
41
+ disableTitle: "إيقاف المصادقة الثنائية؟",
42
+ disableHint: "أدخل الرمز المكوّن من 6 أرقام من تطبيق المصادقة للتأكيد. سيجعل ذلك حسابك أقل أمانًا.",
43
+ disabling: "جارٍ الإيقاف…",
44
+ disable: "إيقاف المصادقة الثنائية",
45
+ },
46
+ apiKey: {
47
+ title: "مفتاح API",
48
+ description: "مفتاح API الشخصي للوصول البرمجي",
49
+ created: "تاريخ الإنشاء",
50
+ reissued: "آخر إعادة إصدار",
51
+ neverReissued: "لم يُعاد إصداره قط",
52
+ regenerate: "إعادة إنشاء مفتاح API",
53
+ confirmRegenerate: "انقر للتأكيد",
54
+ regenerating: "جارٍ إعادة الإنشاء…",
55
+ regenerated: "تمت إعادة إنشاء مفتاح API",
56
+ failedToRegenerate: "تعذّرت إعادة إنشاء مفتاح API",
57
+ done: "تم",
58
+ test: "اختبار مفتاح API",
59
+ testing: "جارٍ الاختبار…",
60
+ testSuccess: "مفتاح API صالح",
61
+ testFailed: "فشل اختبار مفتاح API",
62
+ keyRow: "مفتاح API",
63
+ active: "نشط",
64
+ freshKey: "هذا هو مفتاح API الجديد، انسخه الآن. سيُخفى عند مغادرة هذه الصفحة.",
65
+ },
66
+ };
@@ -0,0 +1,66 @@
1
+ import type { SettingsCopy } from '../types';
2
+
3
+ export const da: SettingsCopy = {
4
+ nav: {
5
+ title: "Indstillinger",
6
+ platform: "Platform",
7
+ account: "Konto",
8
+ preferences: "Indstillinger",
9
+ apiKeys: "API-nøgler",
10
+ apiKeysDescription: "Opret og administrer nøgler til programmatisk adgang.",
11
+ },
12
+ prefs: {
13
+ general: "Generelt",
14
+ other: "Andet",
15
+ language: "Sprog",
16
+ timeZone: "Tidszone",
17
+ theme: "Tema",
18
+ },
19
+ account: {
20
+ dangerZone: "Farezone",
21
+ deleteAccountDescription: "Slet din konto og alle tilknyttede data permanent.",
22
+ changeAvatar: "Skift avatar",
23
+ },
24
+ search: {
25
+ clear: "Ryd søgning",
26
+ },
27
+ security: {
28
+ title: "Sikkerhed",
29
+ twoFactor: "Tofaktorgodkendelse",
30
+ on: "Til",
31
+ off: "Fra · tilføjer et ekstra trin ved login",
32
+ checking: "Kontrollerer…",
33
+ unavailable: "Ikke tilgængelig",
34
+ turnOn: "Slå til",
35
+ turnOff: "Slå fra",
36
+ setupTitle: "Opsæt tofaktorgodkendelse",
37
+ setupHint: "Scan QR-koden med din godkendelsesapp (Google Authenticator, Authy osv.)",
38
+ cancel: "Annuller",
39
+ deviceAdded: "Tilføjet {{date}}",
40
+ devicePrimary: "Primær",
41
+ disableTitle: "Slå tofaktorgodkendelse fra?",
42
+ disableHint: "Indtast den 6-cifrede kode fra din godkendelsesapp for at bekræfte. Din konto bliver mindre sikker.",
43
+ disabling: "Slår fra…",
44
+ disable: "Slå 2FA fra",
45
+ },
46
+ apiKey: {
47
+ title: "API-nøgle",
48
+ description: "Din personlige API-nøgle til programmatisk adgang",
49
+ created: "Oprettet",
50
+ reissued: "Sidst udstedt igen",
51
+ neverReissued: "Aldrig udstedt igen",
52
+ regenerate: "Generer API-nøgle igen",
53
+ confirmRegenerate: "Klik for at bekræfte",
54
+ regenerating: "Genererer igen…",
55
+ regenerated: "API-nøglen blev genereret igen",
56
+ failedToRegenerate: "API-nøglen kunne ikke genereres igen",
57
+ done: "Færdig",
58
+ test: "Test API-nøgle",
59
+ testing: "Tester…",
60
+ testSuccess: "API-nøglen er gyldig",
61
+ testFailed: "API-nøgletesten mislykkedes",
62
+ keyRow: "API-nøgle",
63
+ active: "Aktiv",
64
+ freshKey: "Det er din nye API-nøgle – kopier den nu. Den skjules, når du forlader siden.",
65
+ },
66
+ };
@@ -0,0 +1,66 @@
1
+ import type { SettingsCopy } from '../types';
2
+
3
+ export const de: SettingsCopy = {
4
+ nav: {
5
+ title: "Einstellungen",
6
+ platform: "Plattform",
7
+ account: "Konto",
8
+ preferences: "Einstellungen",
9
+ apiKeys: "API-Schlüssel",
10
+ apiKeysDescription: "Schlüssel für den programmatischen Zugriff erstellen und verwalten.",
11
+ },
12
+ prefs: {
13
+ general: "Allgemein",
14
+ other: "Sonstiges",
15
+ language: "Sprache",
16
+ timeZone: "Zeitzone",
17
+ theme: "Design",
18
+ },
19
+ account: {
20
+ dangerZone: "Gefahrenbereich",
21
+ deleteAccountDescription: "Konto und alle zugehörigen Daten dauerhaft löschen.",
22
+ changeAvatar: "Avatar ändern",
23
+ },
24
+ search: {
25
+ clear: "Suche löschen",
26
+ },
27
+ security: {
28
+ title: "Sicherheit",
29
+ twoFactor: "Zwei-Faktor-Authentifizierung",
30
+ on: "An",
31
+ off: "Aus · fügt beim Anmelden einen zweiten Schritt hinzu",
32
+ checking: "Wird geprüft…",
33
+ unavailable: "Nicht verfügbar",
34
+ turnOn: "Einschalten",
35
+ turnOff: "Ausschalten",
36
+ setupTitle: "Zwei-Faktor-Authentifizierung einrichten",
37
+ setupHint: "Scannen Sie den QR-Code mit Ihrer Authenticator-App (Google Authenticator, Authy usw.)",
38
+ cancel: "Abbrechen",
39
+ deviceAdded: "Hinzugefügt am {{date}}",
40
+ devicePrimary: "Primär",
41
+ disableTitle: "Zwei-Faktor-Authentifizierung deaktivieren?",
42
+ disableHint: "Geben Sie zur Bestätigung den 6-stelligen Code Ihrer Authenticator-App ein. Ihr Konto ist dann weniger sicher.",
43
+ disabling: "Wird deaktiviert…",
44
+ disable: "2FA deaktivieren",
45
+ },
46
+ apiKey: {
47
+ title: "API-Schlüssel",
48
+ description: "Ihr persönlicher API-Schlüssel für den programmatischen Zugriff",
49
+ created: "Erstellt",
50
+ reissued: "Zuletzt neu ausgestellt",
51
+ neverReissued: "Nie neu ausgestellt",
52
+ regenerate: "API-Schlüssel neu erzeugen",
53
+ confirmRegenerate: "Zum Bestätigen klicken",
54
+ regenerating: "Wird neu erzeugt…",
55
+ regenerated: "API-Schlüssel neu erzeugt",
56
+ failedToRegenerate: "API-Schlüssel konnte nicht neu erzeugt werden",
57
+ done: "Fertig",
58
+ test: "API-Schlüssel testen",
59
+ testing: "Wird getestet…",
60
+ testSuccess: "API-Schlüssel ist gültig",
61
+ testFailed: "API-Schlüssel-Test fehlgeschlagen",
62
+ keyRow: "API-Schlüssel",
63
+ active: "Aktiv",
64
+ freshKey: "Das ist Ihr neuer API-Schlüssel – kopieren Sie ihn jetzt. Beim Verlassen der Seite wird er ausgeblendet.",
65
+ },
66
+ };
@@ -0,0 +1,66 @@
1
+ import type { SettingsCopy } from '../types';
2
+
3
+ export const en: SettingsCopy = {
4
+ nav: {
5
+ title: "Settings",
6
+ platform: "Platform",
7
+ account: "Account",
8
+ preferences: "Preferences",
9
+ apiKeys: "API keys",
10
+ apiKeysDescription: "Create and manage keys for programmatic access.",
11
+ },
12
+ prefs: {
13
+ general: "General",
14
+ other: "Other",
15
+ language: "Language",
16
+ timeZone: "Time zone",
17
+ theme: "Theme",
18
+ },
19
+ account: {
20
+ dangerZone: "Danger zone",
21
+ deleteAccountDescription: "Permanently remove your account and all associated data.",
22
+ changeAvatar: "Change avatar",
23
+ },
24
+ search: {
25
+ clear: "Clear search",
26
+ },
27
+ security: {
28
+ title: "Security",
29
+ twoFactor: "Two-factor authentication",
30
+ on: "On",
31
+ off: "Off · adds a second step when you sign in",
32
+ checking: "Checking…",
33
+ unavailable: "Unavailable",
34
+ turnOn: "Turn on",
35
+ turnOff: "Turn off",
36
+ setupTitle: "Set up two-factor authentication",
37
+ setupHint: "Scan the QR code with your authenticator app (Google Authenticator, Authy, etc.)",
38
+ cancel: "Cancel",
39
+ deviceAdded: "Added {{date}}",
40
+ devicePrimary: "Primary",
41
+ disableTitle: "Disable two-factor authentication?",
42
+ disableHint: "Enter the 6-digit code from your authenticator app to confirm. This will make your account less secure.",
43
+ disabling: "Disabling…",
44
+ disable: "Disable 2FA",
45
+ },
46
+ apiKey: {
47
+ title: "API key",
48
+ description: "Your personal API key for programmatic access",
49
+ created: "Created",
50
+ reissued: "Last reissued",
51
+ neverReissued: "Never reissued",
52
+ regenerate: "Regenerate API key",
53
+ confirmRegenerate: "Click to confirm",
54
+ regenerating: "Regenerating…",
55
+ regenerated: "API key regenerated",
56
+ failedToRegenerate: "Failed to regenerate API key",
57
+ done: "Done",
58
+ test: "Test API key",
59
+ testing: "Testing…",
60
+ testSuccess: "API key is valid",
61
+ testFailed: "API key test failed",
62
+ keyRow: "API key",
63
+ active: "Active",
64
+ freshKey: "This is your new API key — copy it now. It will be masked when you leave this page.",
65
+ },
66
+ };
@@ -0,0 +1,66 @@
1
+ import type { SettingsCopy } from '../types';
2
+
3
+ export const es: SettingsCopy = {
4
+ nav: {
5
+ title: "Ajustes",
6
+ platform: "Plataforma",
7
+ account: "Cuenta",
8
+ preferences: "Preferencias",
9
+ apiKeys: "Claves API",
10
+ apiKeysDescription: "Crea y gestiona claves para el acceso programático.",
11
+ },
12
+ prefs: {
13
+ general: "General",
14
+ other: "Otros",
15
+ language: "Idioma",
16
+ timeZone: "Zona horaria",
17
+ theme: "Tema",
18
+ },
19
+ account: {
20
+ dangerZone: "Zona de peligro",
21
+ deleteAccountDescription: "Elimina de forma permanente tu cuenta y todos los datos asociados.",
22
+ changeAvatar: "Cambiar avatar",
23
+ },
24
+ search: {
25
+ clear: "Borrar búsqueda",
26
+ },
27
+ security: {
28
+ title: "Seguridad",
29
+ twoFactor: "Autenticación en dos pasos",
30
+ on: "Activada",
31
+ off: "Desactivada · añade un segundo paso al iniciar sesión",
32
+ checking: "Comprobando…",
33
+ unavailable: "No disponible",
34
+ turnOn: "Activar",
35
+ turnOff: "Desactivar",
36
+ setupTitle: "Configurar la autenticación en dos pasos",
37
+ setupHint: "Escanea el código QR con tu aplicación de autenticación (Google Authenticator, Authy, etc.)",
38
+ cancel: "Cancelar",
39
+ deviceAdded: "Añadido el {{date}}",
40
+ devicePrimary: "Principal",
41
+ disableTitle: "¿Desactivar la autenticación en dos pasos?",
42
+ disableHint: "Introduce el código de 6 dígitos de tu aplicación de autenticación para confirmar. Tu cuenta será menos segura.",
43
+ disabling: "Desactivando…",
44
+ disable: "Desactivar 2FA",
45
+ },
46
+ apiKey: {
47
+ title: "Clave API",
48
+ description: "Tu clave API personal para el acceso programático",
49
+ created: "Creada",
50
+ reissued: "Última regeneración",
51
+ neverReissued: "Nunca regenerada",
52
+ regenerate: "Regenerar clave API",
53
+ confirmRegenerate: "Haz clic para confirmar",
54
+ regenerating: "Regenerando…",
55
+ regenerated: "Clave API regenerada",
56
+ failedToRegenerate: "No se pudo regenerar la clave API",
57
+ done: "Hecho",
58
+ test: "Probar clave API",
59
+ testing: "Probando…",
60
+ testSuccess: "La clave API es válida",
61
+ testFailed: "Falló la prueba de la clave API",
62
+ keyRow: "Clave API",
63
+ active: "Activa",
64
+ freshKey: "Esta es tu nueva clave API: cópiala ahora. Se ocultará al salir de esta página.",
65
+ },
66
+ };