@hed-hog/lms 0.0.329 → 0.0.331

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 (60) hide show
  1. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +18 -8
  2. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -8
  3. package/hedhog/frontend/app/_components/create-lms-person-sheet.tsx.ejs +5 -9
  4. package/hedhog/frontend/app/_components/create-lms-student-person-sheet.tsx.ejs +5 -9
  5. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +15 -14
  6. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +66 -29
  7. package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +4 -2
  8. package/hedhog/frontend/app/certificates/models/TopBar.tsx.ejs +44 -34
  9. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +1 -1
  10. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +27 -27
  11. package/hedhog/frontend/app/classes/page.tsx.ejs +23 -15
  12. package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +2 -2
  13. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +8 -6
  14. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +5 -3
  15. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +1 -1
  16. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +9 -7
  17. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +3 -1
  18. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +4 -2
  19. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +24 -23
  20. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +228 -152
  21. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +21 -19
  22. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +78 -36
  23. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +18 -16
  24. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +13 -11
  25. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +5 -3
  26. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +14 -9
  27. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +42 -25
  28. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +37 -41
  29. package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +3 -1
  30. package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +10 -8
  31. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +22 -20
  32. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +3 -3
  33. package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +21 -19
  34. package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +34 -36
  35. package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +3 -1
  36. package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +7 -5
  37. package/hedhog/frontend/app/enterprise/page.tsx.ejs +106 -54
  38. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +1 -1
  39. package/hedhog/frontend/app/exams/page.tsx.ejs +6 -2
  40. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +79 -59
  41. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +145 -119
  42. package/hedhog/frontend/app/instructors/page.tsx.ejs +75 -54
  43. package/hedhog/frontend/app/paths/page.tsx.ejs +11 -7
  44. package/hedhog/frontend/app/reports/courses/page.tsx.ejs +5 -5
  45. package/hedhog/frontend/app/reports/dashboard/page.tsx.ejs +8 -8
  46. package/hedhog/frontend/app/reports/page.tsx.ejs +7 -7
  47. package/hedhog/frontend/app/reports/students/page.tsx.ejs +6 -6
  48. package/hedhog/frontend/app/training/page.tsx.ejs +5 -5
  49. package/hedhog/frontend/messages/en.json +899 -45
  50. package/hedhog/frontend/messages/pt.json +894 -38
  51. package/hedhog/frontend/widgets/active-classes-kpi.tsx.ejs +1 -1
  52. package/hedhog/frontend/widgets/active-courses-kpi.tsx.ejs +1 -1
  53. package/hedhog/frontend/widgets/approval-rate-kpi.tsx.ejs +1 -1
  54. package/hedhog/frontend/widgets/class-calendar.tsx.ejs +2 -2
  55. package/hedhog/frontend/widgets/completion-rate-kpi.tsx.ejs +1 -1
  56. package/hedhog/frontend/widgets/issued-certificates-kpi.tsx.ejs +1 -1
  57. package/hedhog/frontend/widgets/total-students-kpi.tsx.ejs +1 -1
  58. package/hedhog/table/instructor_qualification.yaml +1 -1
  59. package/hedhog/table/instructor_skill.yaml +1 -1
  60. package/package.json +7 -7
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
4
4
  import { Separator } from '@/components/ui/separator';
5
5
  import { cn } from '@/lib/utils';
6
6
  import { Copy, FolderOpen, Loader2, Trash2, X } from 'lucide-react';
7
+ import { useTranslations } from 'next-intl';
7
8
  import { toast } from 'sonner';
8
9
  import {
9
10
  useBulkDeleteMutation,
@@ -18,6 +19,7 @@ import { useStructureStore } from './store';
18
19
  * Fixed below the search toolbar inside CourseTreePanel.
19
20
  */
20
21
  export function MultiSelectBar() {
22
+ const t = useTranslations('lms.CoursesPage.StructurePage.multiSelectBar');
21
23
  const selectedIds = useStructureStore((s) => s.selectedIds);
22
24
  const lessons = useStructureStore((s) => s.lessons);
23
25
  const sessions = useStructureStore((s) => s.sessions);
@@ -59,12 +61,12 @@ export function MultiSelectBar() {
59
61
  function handleCopy() {
60
62
  if (allAreLessons) {
61
63
  copyItems(selectedLessonIds, 'lesson');
62
- toast.success(`${count} aulas copiadas`);
64
+ toast.success(t('copyLessonsSuccess', { count }));
63
65
  } else if (allAreSessions) {
64
66
  copyItems(selectedSessionIds, 'session');
65
- toast.success(`${count} sessões copiadas`);
67
+ toast.success(t('copySessionsSuccess', { count }));
66
68
  } else {
67
- toast('Selecione apenas aulas ou apenas sessões para copiar.');
69
+ toast(t('copyMixedError'));
68
70
  }
69
71
  }
70
72
 
@@ -82,7 +84,7 @@ export function MultiSelectBar() {
82
84
  );
83
85
  };
84
86
  duplicateNext(0);
85
- toast.success(`${count} aulas sendo duplicadas...`);
87
+ toast.success(t('duplicatingLessons', { count }));
86
88
  } else if (allAreSessions) {
87
89
  const duplicateNext = (index: number) => {
88
90
  if (index >= selectedSessionIds.length) return;
@@ -92,7 +94,7 @@ export function MultiSelectBar() {
92
94
  );
93
95
  };
94
96
  duplicateNext(0);
95
- toast.success(`${count} sessões sendo duplicadas...`);
97
+ toast.success(t('duplicatingSessions', { count }));
96
98
  }
97
99
  }
98
100
 
@@ -139,10 +141,10 @@ export function MultiSelectBar() {
139
141
  .map((l) => ({ lessonId: l.id, sessionId: l.sessionId }));
140
142
 
141
143
  showConfirm({
142
- title: `Excluir ${label}?`,
144
+ title: t('deleteTitle', { label }),
143
145
  description: allAreSessions
144
- ? 'As sessões e todas as suas aulas serão excluídas permanentemente.'
145
- : 'Esta ação não pode ser desfeita.',
146
+ ? t('deleteSessionsDescription')
147
+ : t('deleteDescription'),
146
148
  onConfirm: () =>
147
149
  bulkDelete.mutate({
148
150
  sessionIds: selectedSessionIds,
@@ -154,7 +156,7 @@ export function MultiSelectBar() {
154
156
  return (
155
157
  <div
156
158
  role="toolbar"
157
- aria-label={`${count} itens selecionados — ações`}
159
+ aria-label={t('toolbarAria', { count })}
158
160
  className={cn(
159
161
  'flex items-center gap-0.5 px-2 py-1 shrink-0 border-b',
160
162
  'bg-primary/5 dark:bg-primary/10'
@@ -177,8 +179,8 @@ export function MultiSelectBar() {
177
179
  variant="ghost"
178
180
  size="icon"
179
181
  className="size-6 text-muted-foreground hover:text-foreground"
180
- title="Copiar selecionados (Ctrl+C)"
181
- aria-label="Copiar selecionados"
182
+ title={t('copyTitle')}
183
+ aria-label={t('copyAria')}
182
184
  onClick={handleCopy}
183
185
  >
184
186
  <Copy className="size-3" />
@@ -189,8 +191,8 @@ export function MultiSelectBar() {
189
191
  variant="ghost"
190
192
  size="icon"
191
193
  className="size-6 text-muted-foreground hover:text-foreground"
192
- title="Duplicar selecionados (Ctrl+D)"
193
- aria-label="Duplicar selecionados"
194
+ title={t('duplicateTitle')}
195
+ aria-label={t('duplicateAria')}
194
196
  disabled={isDuplicating}
195
197
  onClick={handleDuplicate}
196
198
  >
@@ -216,8 +218,8 @@ export function MultiSelectBar() {
216
218
  variant="ghost"
217
219
  size="icon"
218
220
  className="size-6 text-muted-foreground hover:text-foreground"
219
- title="Mover para outra sessão"
220
- aria-label="Mover para outra sessão"
221
+ title={t('moveTitle')}
222
+ aria-label={t('moveAria')}
221
223
  disabled={isMoving}
222
224
  onClick={handleMove}
223
225
  >
@@ -234,8 +236,8 @@ export function MultiSelectBar() {
234
236
  variant="ghost"
235
237
  size="icon"
236
238
  className="size-6 text-destructive/60 hover:text-destructive"
237
- title="Excluir selecionados (Delete)"
238
- aria-label="Excluir selecionados"
239
+ title={t('deleteActionTitle')}
240
+ aria-label={t('deleteActionAria')}
239
241
  disabled={bulkDelete.isPending}
240
242
  onClick={handleDelete}
241
243
  >
@@ -253,8 +255,8 @@ export function MultiSelectBar() {
253
255
  variant="ghost"
254
256
  size="icon"
255
257
  className="size-6 text-muted-foreground hover:text-foreground"
256
- title="Limpar seleção (Escape)"
257
- aria-label="Limpar seleção"
258
+ title={t('clearTitle')}
259
+ aria-label={t('clearAria')}
258
260
  onClick={clearSelection}
259
261
  >
260
262
  <X className="size-3" />
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Keyboard } from 'lucide-react';
4
+ import { useTranslations } from 'next-intl';
4
5
 
5
6
  import { Badge } from '@/components/ui/badge';
6
7
  import { Button } from '@/components/ui/button';
@@ -17,36 +18,7 @@ import { Separator } from '@/components/ui/separator';
17
18
  const SHORTCUT_GROUPS: {
18
19
  heading: string;
19
20
  items: { keys: string[]; description: string }[];
20
- }[] = [
21
- {
22
- heading: 'Navegação',
23
- items: [
24
- { keys: ['↑', '↓'], description: 'Navegar entre itens' },
25
- { keys: ['→'], description: 'Expandir sessão selecionada' },
26
- { keys: ['←'], description: 'Recolher sessão / ir para pai' },
27
- { keys: ['Enter'], description: 'Focar primeiro campo do editor' },
28
- ],
29
- },
30
- {
31
- heading: 'Ações',
32
- items: [
33
- { keys: ['Ctrl', 'S'], description: 'Salvar formulário do painel' },
34
- { keys: ['Ctrl', 'N'], description: 'Criar nova sessão ou aula' },
35
- { keys: ['Ctrl', 'C'], description: 'Copiar item selecionado' },
36
- { keys: ['Ctrl', 'V'], description: 'Colar no contexto atual' },
37
- { keys: ['Ctrl', 'D'], description: 'Duplicar item' },
38
- { keys: ['Delete'], description: 'Excluir item(ns) selecionado(s)' },
39
- ],
40
- },
41
- {
42
- heading: 'Busca & Interface',
43
- items: [
44
- { keys: ['Ctrl', 'F'], description: 'Focar campo de busca' },
45
- { keys: ['Ctrl', '/'], description: 'Abrir ajuda de atalhos' },
46
- { keys: ['Esc'], description: 'Limpar busca / seleção / foco' },
47
- ],
48
- },
49
- ];
21
+ }[] = [];
50
22
 
51
23
  // ── Component ─────────────────────────────────────────────────────────────────
52
24
 
@@ -56,18 +28,88 @@ interface ShortcutsHelpProps {
56
28
  }
57
29
 
58
30
  export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
31
+ const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
32
+ const shortcutGroups = [
33
+ {
34
+ heading: t('groups.navigation.heading'),
35
+ items: [
36
+ {
37
+ keys: ['↑', '↓'],
38
+ description: t('groups.navigation.items.navigate'),
39
+ },
40
+ {
41
+ keys: ['→'],
42
+ description: t('groups.navigation.items.expand'),
43
+ },
44
+ {
45
+ keys: ['←'],
46
+ description: t('groups.navigation.items.collapse'),
47
+ },
48
+ {
49
+ keys: ['Enter'],
50
+ description: t('groups.navigation.items.focusEditor'),
51
+ },
52
+ ],
53
+ },
54
+ {
55
+ heading: t('groups.actions.heading'),
56
+ items: [
57
+ {
58
+ keys: ['Ctrl', 'S'],
59
+ description: t('groups.actions.items.savePanel'),
60
+ },
61
+ {
62
+ keys: ['Ctrl', 'N'],
63
+ description: t('groups.actions.items.createItem'),
64
+ },
65
+ {
66
+ keys: ['Ctrl', 'C'],
67
+ description: t('groups.actions.items.copyItem'),
68
+ },
69
+ {
70
+ keys: ['Ctrl', 'V'],
71
+ description: t('groups.actions.items.pasteItem'),
72
+ },
73
+ {
74
+ keys: ['Ctrl', 'D'],
75
+ description: t('groups.actions.items.duplicateItem'),
76
+ },
77
+ {
78
+ keys: ['Delete'],
79
+ description: t('groups.actions.items.deleteItems'),
80
+ },
81
+ ],
82
+ },
83
+ {
84
+ heading: t('groups.search.heading'),
85
+ items: [
86
+ {
87
+ keys: ['Ctrl', 'F'],
88
+ description: t('groups.search.items.focusSearch'),
89
+ },
90
+ {
91
+ keys: ['Ctrl', '/'],
92
+ description: t('groups.search.items.openHelp'),
93
+ },
94
+ {
95
+ keys: ['Esc'],
96
+ description: t('groups.search.items.clearState'),
97
+ },
98
+ ],
99
+ },
100
+ ];
59
101
  return (
60
102
  <Dialog open={open} onOpenChange={onOpenChange}>
61
103
  <DialogContent className="max-w-sm">
62
104
  <DialogHeader>
63
105
  <DialogTitle className="flex items-center gap-2">
64
106
  <Keyboard className="size-4" />
65
- Atalhos de teclado
107
+ {t('title')}
66
108
  </DialogTitle>
67
109
  </DialogHeader>
68
110
 
69
111
  <div className="flex flex-col gap-4 mt-1">
70
- {SHORTCUT_GROUPS.map((group, gi) => (
112
+ {shortcutGroups.map((group, gi) => (
71
113
  <div key={gi}>
72
114
  <p className="text-[0.65rem] font-semibold uppercase tracking-wider text-muted-foreground mb-2">
73
115
  {group.heading}
@@ -103,8 +145,7 @@ export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
103
145
  </div>
104
146
 
105
147
  <p className="text-[0.65rem] text-muted-foreground mt-1">
106
- Atalhos são desativados quando o foco está em inputs, exceto Ctrl+S,
107
- Ctrl+F e Ctrl+/ que funcionam em qualquer contexto.
148
+ {t('footer')}
108
149
  </p>
109
150
  </DialogContent>
110
151
  </Dialog>
@@ -115,16 +156,17 @@ export function ShortcutsHelp({ open, onOpenChange }: ShortcutsHelpProps) {
115
156
  * Convenience trigger button — renders standalone when no external state is needed.
116
157
  */
117
158
  export function ShortcutsHelpTrigger({ onOpen }: { onOpen: () => void }) {
159
+ const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
118
160
  return (
119
161
  <Button
120
162
  variant="ghost"
121
163
  size="sm"
122
164
  className="h-8 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
123
165
  onClick={onOpen}
124
- title="Atalhos de teclado (Ctrl+/)"
166
+ title={t('triggerTitle')}
125
167
  >
126
168
  <Keyboard className="size-3.5" />
127
- Atalhos
169
+ {t('triggerLabel')}
128
170
  <Badge
129
171
  variant="outline"
130
172
  className="h-4 px-1 text-[0.6rem] font-mono ml-0.5"
@@ -10,6 +10,7 @@ import {
10
10
  import { Separator } from '@/components/ui/separator';
11
11
  import { Switch } from '@/components/ui/switch';
12
12
  import { SlidersHorizontal } from 'lucide-react';
13
+ import { useTranslations } from 'next-intl';
13
14
 
14
15
  import { useTreeDisplaySettings } from './use-tree-display-settings';
15
16
 
@@ -18,6 +19,7 @@ import { useTreeDisplaySettings } from './use-tree-display-settings';
18
19
  // ─────────────────────────────────────────────────────────────────────────────
19
20
 
20
21
  export function TreeDisplaySettingsPopover() {
22
+ const t = useTranslations('lms.CoursesPage.StructurePage.displaySettings');
21
23
  const {
22
24
  showStatusDot,
23
25
  showVisibility,
@@ -35,26 +37,26 @@ export function TreeDisplaySettingsPopover() {
35
37
  variant="ghost"
36
38
  size="sm"
37
39
  className="h-8 gap-1.5 text-xs text-muted-foreground hover:text-foreground"
38
- title="Configurações de exibição da lista"
40
+ title={t('title')}
39
41
  >
40
42
  <SlidersHorizontal className="size-3.5" />
41
- <span className="sr-only sm:not-sr-only">Exibição</span>
43
+ <span className="sr-only sm:not-sr-only">{t('label')}</span>
42
44
  </Button>
43
45
  </PopoverTrigger>
44
46
 
45
47
  <PopoverContent align="end" className="w-56 p-0">
46
48
  <div className="px-3 py-2.5 border-b">
47
- <p className="text-xs font-semibold">Informações nas aulas</p>
49
+ <p className="text-xs font-semibold">{t('sectionTitle')}</p>
48
50
  <p className="text-[0.65rem] text-muted-foreground mt-0.5">
49
- Escolha o que exibir em cada linha
51
+ {t('sectionDescription')}
50
52
  </p>
51
53
  </div>
52
54
 
53
55
  <div className="flex flex-col gap-0">
54
56
  <SettingRow
55
57
  id="show-status-dot"
56
- label="Dot de status"
57
- description="Cor indicando etapa de produção"
58
+ label={t('showStatusDot.label')}
59
+ description={t('showStatusDot.description')}
58
60
  checked={showStatusDot}
59
61
  onCheckedChange={(v) => update({ showStatusDot: v })}
60
62
  />
@@ -63,8 +65,8 @@ export function TreeDisplaySettingsPopover() {
63
65
 
64
66
  <SettingRow
65
67
  id="show-visibility"
66
- label="Visibilidade"
67
- description="Ícone de público / privado / restrito"
68
+ label={t('showVisibility.label')}
69
+ description={t('showVisibility.description')}
68
70
  checked={showVisibility}
69
71
  onCheckedChange={(v) => update({ showVisibility: v })}
70
72
  />
@@ -73,8 +75,8 @@ export function TreeDisplaySettingsPopover() {
73
75
 
74
76
  <SettingRow
75
77
  id="show-code"
76
- label="Código"
77
- description="Exibe o código identificador"
78
+ label={t('showCode.label')}
79
+ description={t('showCode.description')}
78
80
  checked={showCode}
79
81
  onCheckedChange={(v) => update({ showCode: v })}
80
82
  />
@@ -83,8 +85,8 @@ export function TreeDisplaySettingsPopover() {
83
85
 
84
86
  <SettingRow
85
87
  id="show-video-indicator"
86
- label="Vídeo vinculado"
87
- description="Ícone quando aula de vídeo tem URL definida"
88
+ label={t('showVideoIndicator.label')}
89
+ description={t('showVideoIndicator.description')}
88
90
  checked={showVideoIndicator}
89
91
  onCheckedChange={(v) => update({ showVideoIndicator: v })}
90
92
  />
@@ -93,8 +95,8 @@ export function TreeDisplaySettingsPopover() {
93
95
 
94
96
  <SettingRow
95
97
  id="show-resources-indicator"
96
- label="Recursos para download"
97
- description="Ícone quando há arquivos anexados"
98
+ label={t('showResourcesIndicator.label')}
99
+ description={t('showResourcesIndicator.description')}
98
100
  checked={showResourcesIndicator}
99
101
  onCheckedChange={(v) => update({ showResourcesIndicator: v })}
100
102
  />
@@ -103,8 +105,8 @@ export function TreeDisplaySettingsPopover() {
103
105
 
104
106
  <SettingRow
105
107
  id="show-transcription-indicator"
106
- label="Transcrição"
107
- description="Ícone quando vídeo tem transcrição"
108
+ label={t('showTranscriptionIndicator.label')}
109
+ description={t('showTranscriptionIndicator.description')}
108
110
  checked={showTranscriptionIndicator}
109
111
  onCheckedChange={(v) => update({ showTranscriptionIndicator: v })}
110
112
  />
@@ -15,6 +15,7 @@ import {
15
15
  Video,
16
16
  type LucideIcon,
17
17
  } from 'lucide-react';
18
+ import { useTranslations } from 'next-intl';
18
19
  import { useEffect, useRef } from 'react';
19
20
  import { useUpdateLessonMutation } from '../_data/use-course-structure-mutations';
20
21
  import { HighlightedText } from './highlighted-text';
@@ -75,6 +76,7 @@ export function TreeRowLesson({
75
76
  query,
76
77
  onClick,
77
78
  }: TreeRowLessonProps) {
79
+ const t = useTranslations('lms.CoursesPage.StructurePage.lessonRow');
78
80
  const cfg = LESSON_TYPE_CONFIG[data.type];
79
81
  const Icon = cfg.icon;
80
82
 
@@ -181,7 +183,7 @@ export function TreeRowLesson({
181
183
  onClick={(e) => e.stopPropagation()}
182
184
  onBlur={(e) => commitAndPersist(e.target.value)}
183
185
  onKeyDown={handleKeyDown}
184
- aria-label="Renomear aula"
186
+ aria-label={t('rename')}
185
187
  />
186
188
  ) : (
187
189
  <span className="truncate flex-1 text-xs leading-tight">
@@ -209,8 +211,8 @@ export function TreeRowLesson({
209
211
  const VisIcon = visibilityCfg.icon;
210
212
  return (
211
213
  <span
212
- title={`Visibilidade: ${visibilityCfg.label}`}
213
- aria-label={`Visibilidade: ${visibilityCfg.label}`}
214
+ title={t('visibility', { value: visibilityCfg.label })}
215
+ aria-label={t('visibility', { value: visibilityCfg.label })}
214
216
  className="inline-flex items-center"
215
217
  >
216
218
  <VisIcon
@@ -223,15 +225,15 @@ export function TreeRowLesson({
223
225
  {showStatusDot && statusCfg && (
224
226
  <span
225
227
  className={cn('size-1.5 rounded-full shrink-0', statusCfg.dot)}
226
- title={`Status: ${statusCfg.label}`}
227
- aria-label={`Status: ${statusCfg.label}`}
228
+ title={t('status', { value: statusCfg.label })}
229
+ aria-label={t('status', { value: statusCfg.label })}
228
230
  />
229
231
  )}
230
232
  {/* Video linked indicator */}
231
233
  {showVideoIndicator && data.type === 'video' && data.videoUrl && (
232
234
  <span
233
- title="Vídeo vinculado"
234
- aria-label="Vídeo vinculado"
235
+ title={t('videoLinked')}
236
+ aria-label={t('videoLinked')}
235
237
  className="inline-flex items-center"
236
238
  >
237
239
  <Film className="size-3 shrink-0 text-violet-500" />
@@ -240,8 +242,8 @@ export function TreeRowLesson({
240
242
  {/* Resources indicator */}
241
243
  {showResourcesIndicator && data.resources.length > 0 && (
242
244
  <span
243
- title={`${data.resources.length} recurso${data.resources.length > 1 ? 's' : ''} para download`}
244
- aria-label={`${data.resources.length} recursos`}
245
+ title={t('resources', { count: data.resources.length })}
246
+ aria-label={t('resourcesAria', { count: data.resources.length })}
245
247
  className="inline-flex items-center gap-0.5"
246
248
  >
247
249
  <Paperclip className="size-3 shrink-0 text-sky-500" />
@@ -257,8 +259,8 @@ export function TreeRowLesson({
257
259
  data.type === 'video' &&
258
260
  data.transcription && (
259
261
  <span
260
- title="Possui transcrição"
261
- aria-label="Possui transcrição"
262
+ title={t('hasTranscription')}
263
+ aria-label={t('hasTranscription')}
262
264
  className="inline-flex items-center"
263
265
  >
264
266
  <ScrollText className="size-3 shrink-0 text-emerald-500" />
@@ -3,6 +3,7 @@
3
3
  import { Input } from '@/components/ui/input';
4
4
  import { cn } from '@/lib/utils';
5
5
  import { ChevronDown, ChevronRight, Layers } from 'lucide-react';
6
+ import { useTranslations } from 'next-intl';
6
7
  import { useEffect, useRef } from 'react';
7
8
  import { useUpdateSessionMutation } from '../_data/use-course-structure-mutations';
8
9
  import { HighlightedText } from './highlighted-text';
@@ -31,6 +32,7 @@ export function TreeRowSession({
31
32
  onClick,
32
33
  onToggleExpand,
33
34
  }: TreeRowSessionProps) {
35
+ const t = useTranslations('lms.CoursesPage.StructurePage.sessionRow');
34
36
  const inlineRenamingId = useStructureStore((s) => s.inlineRenamingId);
35
37
  const cancelRename = useStructureStore((s) => s.cancelRename);
36
38
  const startRename = useStructureStore((s) => s.startRename);
@@ -104,7 +106,7 @@ export function TreeRowSession({
104
106
  <button
105
107
  className="shrink-0 text-muted-foreground hover:text-foreground transition-colors p-0.5 rounded focus-visible:ring-1 focus-visible:ring-ring"
106
108
  onClick={onToggleExpand}
107
- aria-label={isExpanded ? 'Recolher sessão' : 'Expandir sessão'}
109
+ aria-label={isExpanded ? t('collapse') : t('expand')}
108
110
  tabIndex={-1}
109
111
  >
110
112
  {isExpanded ? (
@@ -129,7 +131,7 @@ export function TreeRowSession({
129
131
  onClick={(e) => e.stopPropagation()}
130
132
  onBlur={(e) => commitAndPersist(e.target.value)}
131
133
  onKeyDown={handleKeyDown}
132
- aria-label="Renomear sessão"
134
+ aria-label={t('rename')}
133
135
  />
134
136
  ) : (
135
137
  <span className="truncate flex-1 font-medium leading-tight">
@@ -158,7 +160,7 @@ export function TreeRowSession({
158
160
  ? 'bg-muted-foreground/30'
159
161
  : 'bg-emerald-500'
160
162
  )}
161
- title={data.published === false ? 'Rascunho' : 'Publicada'}
163
+ title={data.published === false ? t('draft') : t('published')}
162
164
  />
163
165
  </div>
164
166
  )}
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useCallback, useEffect, useRef } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { toast } from 'sonner';
5
6
 
6
7
  import type { SearchFilterHandle } from './search-filter';
@@ -44,6 +45,7 @@ export function useCourseStructureShortcuts({
44
45
  onDuplicate,
45
46
  onPaste,
46
47
  }: UseCourseStructureShortcutsOptions) {
48
+ const t = useTranslations('lms.CoursesPage.StructurePage.shortcuts');
47
49
  const course = useStructureStore((s) => s.course);
48
50
  const sessions = useStructureStore((s) => s.sessions);
49
51
  const lessons = useStructureStore((s) => s.lessons);
@@ -236,12 +238,15 @@ export function useCourseStructureShortcuts({
236
238
  if (!ids.length) return;
237
239
  const count = ids.length;
238
240
  s.showConfirm({
239
- title: `Excluir ${count > 1 ? count + ' itens' : 'item selecionado'}?`,
240
- description: 'Esta ação não pode ser desfeita.',
241
+ title: t('deleteTitle', {
242
+ label:
243
+ count > 1 ? `${count} ${t('items')}` : t('selectedItem'),
244
+ }),
245
+ description: t('deleteDescription'),
241
246
  onConfirm: () => {
242
247
  s.deleteSelected();
243
248
  toast.success(
244
- count > 1 ? `${count} itens excluídos` : 'Item excluído'
249
+ count > 1 ? t('itemsDeleted', { count }) : t('itemDeleted')
245
250
  );
246
251
  },
247
252
  });
@@ -260,7 +265,7 @@ export function useCourseStructureShortcuts({
260
265
  const ids = selectedKeys.map((key) => key.slice(key.indexOf(':') + 1));
261
266
  s.copyItems(ids, type);
262
267
  toast.success(
263
- `${ids.length > 1 ? ids.length + ' itens copiados' : 'Item copiado'}`
268
+ ids.length > 1 ? t('itemsCopied', { count: ids.length }) : t('itemCopied')
264
269
  );
265
270
  return;
266
271
  }
@@ -272,7 +277,7 @@ export function useCourseStructureShortcuts({
272
277
  onDuplicate();
273
278
  } else {
274
279
  s.duplicateSelected();
275
- toast.success('Item duplicado');
280
+ toast.success(t('itemDuplicated'));
276
281
  }
277
282
  return;
278
283
  }
@@ -283,7 +288,7 @@ export function useCourseStructureShortcuts({
283
288
  if (onPaste) {
284
289
  onPaste();
285
290
  } else {
286
- toast('Nada para colar');
291
+ toast(t('nothingToPaste'));
287
292
  }
288
293
  return;
289
294
  }
@@ -293,16 +298,16 @@ export function useCourseStructureShortcuts({
293
298
  e.preventDefault();
294
299
  if (s.activeItemType === 'course') {
295
300
  s.addSession();
296
- toast.success('Nova sessão criada');
301
+ toast.success(t('newSessionCreated'));
297
302
  } else if (s.activeItemType === 'session' && s.activeItemId) {
298
303
  s.addLesson(s.activeItemId);
299
- toast.success('Nova aula criada');
304
+ toast.success(t('newLessonCreated'));
300
305
  } else if (s.activeItemType === 'lesson') {
301
306
  // Find parent session and add there
302
307
  const lesson = s.lessons.find((l) => l.id === s.activeItemId);
303
308
  if (lesson) {
304
309
  s.addLesson(lesson.sessionId);
305
- toast.success('Nova aula criada na mesma sessão');
310
+ toast.success(t('newLessonSameSession'));
306
311
  }
307
312
  }
308
313
  return;