@windward/integrations 0.21.0 → 0.23.0

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 (150) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components/Content/Blocks/ActionPanel/TransformActivity.vue +386 -0
  3. package/components/Content/Blocks/ActionPanel/TransformBlock.vue +402 -59
  4. package/components/Content/Blocks/ExternalIntegration/LtiConsumer.vue +22 -2
  5. package/components/ExternalIntegration/Driver/Lti1p1/ManageConsumer.vue +8 -2
  6. package/components/ExternalIntegration/Driver/Lti1p1/ManageProvider.vue +4 -0
  7. package/components/ExternalIntegration/Driver/Lti1p3/ManageConsumer.vue +2 -2
  8. package/components/Integration/JobTable.vue +35 -3
  9. package/components/Integration/TranslateCourse.vue +574 -0
  10. package/components/Settings/ExternalIntegration/LtiConsumerSettings.vue +27 -0
  11. package/i18n/ar-SA/components/ai_agent/chat.ts +20 -0
  12. package/i18n/ar-SA/components/ai_agent/index.ts +5 -0
  13. package/i18n/ar-SA/components/content/blocks/action_panel/index.ts +5 -0
  14. package/i18n/ar-SA/components/content/blocks/action_panel/transform_block.ts +9 -0
  15. package/i18n/ar-SA/components/content/blocks/external_integration/index.ts +5 -0
  16. package/i18n/ar-SA/components/content/blocks/external_integration/lti_consumer.ts +17 -0
  17. package/i18n/ar-SA/components/content/blocks/index.ts +7 -0
  18. package/i18n/ar-SA/components/content/index.ts +5 -0
  19. package/i18n/ar-SA/components/external_integration/driver/lti1p1.ts +17 -0
  20. package/i18n/ar-SA/components/external_integration/driver/lti1p3.ts +25 -0
  21. package/i18n/ar-SA/components/external_integration/driver/scorm.ts +14 -0
  22. package/i18n/ar-SA/components/external_integration/index.ts +36 -0
  23. package/i18n/ar-SA/components/external_integration/provider_target.ts +9 -0
  24. package/i18n/ar-SA/components/file_import/index.ts +5 -0
  25. package/i18n/ar-SA/components/file_import/resourcespace.ts +4 -0
  26. package/i18n/ar-SA/components/index.ts +19 -0
  27. package/i18n/ar-SA/components/integration/driver.ts +67 -0
  28. package/i18n/ar-SA/components/integration/index.ts +9 -0
  29. package/i18n/ar-SA/components/integration/job.ts +25 -0
  30. package/i18n/ar-SA/components/integration/job_log.ts +24 -0
  31. package/i18n/ar-SA/components/llm/blooms.ts +15 -0
  32. package/i18n/ar-SA/components/llm/content_selector.ts +3 -0
  33. package/i18n/ar-SA/components/llm/generate_content/fake_text_stream.ts +62 -0
  34. package/i18n/ar-SA/components/llm/generate_content/generate_questions.ts +92 -0
  35. package/i18n/ar-SA/components/llm/generate_content/index.ts +8 -0
  36. package/i18n/ar-SA/components/llm/index.ts +10 -0
  37. package/i18n/ar-SA/components/navigation/index.ts +5 -0
  38. package/i18n/ar-SA/components/navigation/integrations.ts +9 -0
  39. package/i18n/ar-SA/components/settings/external_integration/index.ts +5 -0
  40. package/i18n/ar-SA/components/settings/external_integration/lti_consumer.ts +10 -0
  41. package/i18n/ar-SA/components/settings/index.ts +5 -0
  42. package/i18n/ar-SA/index.ts +16 -0
  43. package/i18n/ar-SA/modules/index.ts +5 -0
  44. package/i18n/ar-SA/pages/admin/index.ts +5 -0
  45. package/i18n/ar-SA/pages/admin/translateCourse.ts +68 -0
  46. package/i18n/ar-SA/pages/course/external_integration/index.ts +6 -0
  47. package/i18n/ar-SA/pages/course/index.ts +5 -0
  48. package/i18n/ar-SA/pages/importContent.ts +3 -0
  49. package/i18n/ar-SA/pages/importCourse.ts +13 -0
  50. package/i18n/ar-SA/pages/index.ts +15 -0
  51. package/i18n/ar-SA/pages/login/index.ts +9 -0
  52. package/i18n/ar-SA/pages/login/lti.ts +23 -0
  53. package/i18n/ar-SA/pages/login/saml.ts +7 -0
  54. package/i18n/ar-SA/pages/login/scorm.ts +28 -0
  55. package/i18n/ar-SA/pages/vendor.ts +11 -0
  56. package/i18n/ar-SA/shared/content_blocks.ts +9 -0
  57. package/i18n/ar-SA/shared/error.ts +9 -0
  58. package/i18n/ar-SA/shared/file.ts +5 -0
  59. package/i18n/ar-SA/shared/index.ts +17 -0
  60. package/i18n/ar-SA/shared/menu.ts +3 -0
  61. package/i18n/ar-SA/shared/notification.ts +14 -0
  62. package/i18n/ar-SA/shared/permission.ts +52 -0
  63. package/i18n/ar-SA/shared/settings.ts +9 -0
  64. package/i18n/en-US/components/content/blocks/action_panel/index.ts +2 -0
  65. package/i18n/en-US/components/content/blocks/action_panel/transform_activity.ts +8 -0
  66. package/i18n/en-US/components/content/blocks/action_panel/transform_block.ts +1 -1
  67. package/i18n/en-US/components/content/blocks/external_integration/lti_consumer.ts +1 -0
  68. package/i18n/en-US/components/integration/job.ts +2 -0
  69. package/i18n/en-US/components/navigation/integrations.ts +1 -0
  70. package/i18n/en-US/components/settings/external_integration/lti_consumer.ts +2 -0
  71. package/i18n/en-US/pages/admin/index.ts +5 -0
  72. package/i18n/en-US/pages/admin/translateCourse.ts +68 -0
  73. package/i18n/en-US/pages/index.ts +2 -0
  74. package/i18n/es-ES/components/content/blocks/action_panel/index.ts +2 -0
  75. package/i18n/es-ES/components/content/blocks/action_panel/transform_activity.ts +8 -0
  76. package/i18n/es-ES/components/content/blocks/action_panel/transform_block.ts +1 -1
  77. package/i18n/es-ES/components/content/blocks/external_integration/lti_consumer.ts +8 -7
  78. package/i18n/es-ES/components/integration/job.ts +2 -0
  79. package/i18n/es-ES/components/navigation/integrations.ts +1 -0
  80. package/i18n/es-ES/components/settings/external_integration/lti_consumer.ts +3 -0
  81. package/i18n/es-ES/pages/admin/index.ts +5 -0
  82. package/i18n/es-ES/pages/admin/translateCourse.ts +68 -0
  83. package/i18n/es-ES/pages/index.ts +2 -0
  84. package/i18n/fr-FR/components/ai_agent/chat.ts +20 -0
  85. package/i18n/fr-FR/components/ai_agent/index.ts +5 -0
  86. package/i18n/fr-FR/components/content/blocks/action_panel/index.ts +5 -0
  87. package/i18n/fr-FR/components/content/blocks/action_panel/transform_block.ts +9 -0
  88. package/i18n/fr-FR/components/content/blocks/external_integration/index.ts +5 -0
  89. package/i18n/fr-FR/components/content/blocks/external_integration/lti_consumer.ts +17 -0
  90. package/i18n/fr-FR/components/content/blocks/index.ts +7 -0
  91. package/i18n/fr-FR/components/content/index.ts +5 -0
  92. package/i18n/fr-FR/components/external_integration/driver/lti1p1.ts +17 -0
  93. package/i18n/fr-FR/components/external_integration/driver/lti1p3.ts +25 -0
  94. package/i18n/fr-FR/components/external_integration/driver/scorm.ts +14 -0
  95. package/i18n/fr-FR/components/external_integration/index.ts +36 -0
  96. package/i18n/fr-FR/components/external_integration/provider_target.ts +9 -0
  97. package/i18n/fr-FR/components/file_import/index.ts +5 -0
  98. package/i18n/fr-FR/components/file_import/resourcespace.ts +4 -0
  99. package/i18n/fr-FR/components/index.ts +19 -0
  100. package/i18n/fr-FR/components/integration/driver.ts +67 -0
  101. package/i18n/fr-FR/components/integration/index.ts +9 -0
  102. package/i18n/fr-FR/components/integration/job.ts +25 -0
  103. package/i18n/fr-FR/components/integration/job_log.ts +24 -0
  104. package/i18n/fr-FR/components/llm/blooms.ts +15 -0
  105. package/i18n/fr-FR/components/llm/content_selector.ts +3 -0
  106. package/i18n/fr-FR/components/llm/generate_content/fake_text_stream.ts +62 -0
  107. package/i18n/fr-FR/components/llm/generate_content/generate_questions.ts +92 -0
  108. package/i18n/fr-FR/components/llm/generate_content/index.ts +8 -0
  109. package/i18n/fr-FR/components/llm/index.ts +10 -0
  110. package/i18n/fr-FR/components/navigation/index.ts +5 -0
  111. package/i18n/fr-FR/components/navigation/integrations.ts +9 -0
  112. package/i18n/fr-FR/components/settings/external_integration/index.ts +5 -0
  113. package/i18n/fr-FR/components/settings/external_integration/lti_consumer.ts +10 -0
  114. package/i18n/fr-FR/components/settings/index.ts +5 -0
  115. package/i18n/fr-FR/index.ts +16 -0
  116. package/i18n/fr-FR/modules/index.ts +5 -0
  117. package/i18n/fr-FR/pages/admin/index.ts +5 -0
  118. package/i18n/fr-FR/pages/admin/translateCourse.ts +68 -0
  119. package/i18n/fr-FR/pages/course/external_integration/index.ts +6 -0
  120. package/i18n/fr-FR/pages/course/index.ts +5 -0
  121. package/i18n/fr-FR/pages/importContent.ts +3 -0
  122. package/i18n/fr-FR/pages/importCourse.ts +13 -0
  123. package/i18n/fr-FR/pages/index.ts +15 -0
  124. package/i18n/fr-FR/pages/login/index.ts +9 -0
  125. package/i18n/fr-FR/pages/login/lti.ts +23 -0
  126. package/i18n/fr-FR/pages/login/saml.ts +7 -0
  127. package/i18n/fr-FR/pages/login/scorm.ts +28 -0
  128. package/i18n/fr-FR/pages/vendor.ts +11 -0
  129. package/i18n/fr-FR/shared/content_blocks.ts +9 -0
  130. package/i18n/fr-FR/shared/error.ts +9 -0
  131. package/i18n/fr-FR/shared/file.ts +5 -0
  132. package/i18n/fr-FR/shared/index.ts +17 -0
  133. package/i18n/fr-FR/shared/menu.ts +3 -0
  134. package/i18n/fr-FR/shared/notification.ts +14 -0
  135. package/i18n/fr-FR/shared/permission.ts +52 -0
  136. package/i18n/fr-FR/shared/settings.ts +9 -0
  137. package/i18n/index.ts +4 -0
  138. package/i18n/sv-SE/components/content/blocks/action_panel/index.ts +2 -0
  139. package/i18n/sv-SE/components/content/blocks/action_panel/transform_activity.ts +8 -0
  140. package/i18n/sv-SE/components/content/blocks/action_panel/transform_block.ts +1 -1
  141. package/i18n/sv-SE/components/content/blocks/external_integration/lti_consumer.ts +9 -8
  142. package/i18n/sv-SE/components/integration/job.ts +6 -4
  143. package/i18n/sv-SE/components/navigation/integrations.ts +1 -0
  144. package/i18n/sv-SE/components/settings/external_integration/lti_consumer.ts +3 -0
  145. package/i18n/sv-SE/pages/admin/index.ts +5 -0
  146. package/i18n/sv-SE/pages/admin/translateCourse.ts +68 -0
  147. package/i18n/sv-SE/pages/index.ts +2 -0
  148. package/package.json +1 -1
  149. package/pages/admin/translateCourse.vue +81 -0
  150. package/plugin.js +32 -0
@@ -0,0 +1,574 @@
1
+ <template>
2
+ <v-sheet>
3
+ <v-row>
4
+ <v-col cols="12" md="8">
5
+ <h1 class="headline">
6
+ {{ $t('windward.integrations.pages.admin.translateCourse.title') }}
7
+ </h1>
8
+ <p class="subtitle-1 mb-4">
9
+ {{ $t('windward.integrations.pages.admin.translateCourse.subtitle') }}
10
+ </p>
11
+
12
+ <!-- Step 1: Source Language Selection -->
13
+ <v-autocomplete
14
+ v-model="sourceLocaleId"
15
+ :items="availableLocales"
16
+ item-text="displayName"
17
+ item-value="id"
18
+ :label="$t('windward.integrations.pages.admin.translateCourse.source_language')"
19
+ :loading="loadingLocales"
20
+ :disabled="loadingLocales"
21
+ clearable
22
+ @change="onSourceLocaleChange"
23
+ >
24
+ <template #item="{ item }">
25
+ <v-list-item-content>
26
+ <v-list-item-title>
27
+ {{ item.displayName }}
28
+ </v-list-item-title>
29
+ </v-list-item-content>
30
+ </template>
31
+ </v-autocomplete>
32
+
33
+ <!-- Step 2: Course Selection -->
34
+ <v-autocomplete
35
+ v-if="sourceLocaleId"
36
+ v-model="selectedCourseId"
37
+ :items="coursesInSourceLanguage"
38
+ item-text="name"
39
+ item-value="id"
40
+ :label="$t('windward.integrations.pages.admin.translateCourse.course')"
41
+ :loading="loadingCourses"
42
+ :disabled="loadingCourses"
43
+ clearable
44
+ @change="onCourseChange"
45
+ >
46
+ <template #item="{ item }">
47
+ <v-list-item-content>
48
+ <v-list-item-title>
49
+ {{ item.name }}
50
+ </v-list-item-title>
51
+ <v-list-item-subtitle>
52
+ {{ item.status }} • {{ item.ownerName || $t('windward.integrations.pages.admin.translateCourse.no_owner') }}
53
+ </v-list-item-subtitle>
54
+ </v-list-item-content>
55
+ </template>
56
+ <template #no-data>
57
+ <v-list-item>
58
+ <v-list-item-content>
59
+ <v-list-item-title>
60
+ {{ $t('windward.integrations.pages.admin.translateCourse.no_courses_found', [sourceLocaleName]) }}
61
+ </v-list-item-title>
62
+ </v-list-item-content>
63
+ </v-list-item>
64
+ </template>
65
+ </v-autocomplete>
66
+
67
+ <!-- Step 3: Target Language Selection -->
68
+ <v-autocomplete
69
+ v-if="selectedCourseId"
70
+ v-model="targetLocaleId"
71
+ :items="targetLocaleOptions"
72
+ item-text="displayName"
73
+ item-value="id"
74
+ :label="$t('windward.integrations.pages.admin.translateCourse.target_language')"
75
+ :loading="loadingLocales"
76
+ :disabled="loadingLocales"
77
+ clearable
78
+ @change="onTargetLocaleChange"
79
+ >
80
+ <template #item="{ item }">
81
+ <v-list-item-content>
82
+ <v-list-item-title>
83
+ {{ item.displayName }}
84
+ </v-list-item-title>
85
+ </v-list-item-content>
86
+ </template>
87
+ </v-autocomplete>
88
+
89
+ <v-switch
90
+ v-if="selectedCourseId"
91
+ v-model="sendEmail"
92
+ :label="$t('windward.integrations.pages.admin.translateCourse.send_email_notification')"
93
+ ></v-switch>
94
+
95
+ <!-- Translation Summary -->
96
+ <v-card v-if="selectedCourse && targetLocaleId" outlined class="mb-4">
97
+ <v-card-title class="text-subtitle-1">
98
+ {{ $t('windward.integrations.pages.admin.translateCourse.summary_title') }}
99
+ </v-card-title>
100
+ <v-card-text>
101
+ <v-simple-table>
102
+ <tbody>
103
+ <tr>
104
+ <td class="font-weight-bold" style="width: 150px;">{{ $t('windward.integrations.pages.admin.translateCourse.source_course') }}</td>
105
+ <td>{{ selectedCourse.name }}</td>
106
+ </tr>
107
+ <tr>
108
+ <td class="font-weight-bold">{{ $t('windward.integrations.pages.admin.translateCourse.source_language_label') }}</td>
109
+ <td>{{ sourceLocaleName }}</td>
110
+ </tr>
111
+ <tr>
112
+ <td class="font-weight-bold">{{ $t('windward.integrations.pages.admin.translateCourse.target_language_label') }}</td>
113
+ <td>{{ targetLocaleName }}</td>
114
+ </tr>
115
+ </tbody>
116
+ </v-simple-table>
117
+ </v-card-text>
118
+ </v-card>
119
+
120
+ <!-- Action Buttons -->
121
+ <v-card-actions>
122
+ <v-spacer></v-spacer>
123
+ <v-btn
124
+ text
125
+ @click="reset"
126
+ >
127
+ {{ $t('windward.integrations.pages.admin.translateCourse.reset_button') }}
128
+ </v-btn>
129
+ <v-btn
130
+ color="primary"
131
+ :disabled="!canTranslate"
132
+ :loading="translating"
133
+ @click="startTranslation"
134
+ >
135
+ {{ $t('windward.integrations.pages.admin.translateCourse.start_translation_button') }}
136
+ </v-btn>
137
+ </v-card-actions>
138
+ </v-col>
139
+ </v-row>
140
+
141
+ <!-- DIALOG 1: Retranslation Mode Selection -->
142
+ <v-dialog v-model="showRetranslationDialog" max-width="600" persistent>
143
+ <v-card>
144
+ <v-card-title>
145
+ <v-icon color="warning" class="mr-2">mdi-alert-circle</v-icon>
146
+ {{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.title') }}
147
+ </v-card-title>
148
+
149
+ <v-card-text>
150
+ <v-alert type="info" text class="mb-4">
151
+ <span v-html="$t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.existing_translation_info', [targetLocaleName])"></span>
152
+ <br><br>
153
+ <strong>{{ existingTranslation ? existingTranslation.name : '' }}</strong>
154
+ </v-alert>
155
+
156
+ <p class="mb-4">{{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.choose_how_to_proceed') }}</p>
157
+
158
+ <v-radio-group v-model="retranslationMode">
159
+ <v-radio value="incremental" class="mb-4">
160
+ <template #label>
161
+ <div>
162
+ <strong class="success--text">{{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.incremental_title') }}</strong> {{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.incremental_subtitle') }}
163
+ <div class="text-caption text--secondary">
164
+ {{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.incremental_description') }}
165
+ <br>
166
+ <strong>{{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.incremental_safe_note') }}</strong>
167
+ </div>
168
+ </div>
169
+ </template>
170
+ </v-radio>
171
+
172
+ <v-radio value="full">
173
+ <template #label>
174
+ <div>
175
+ <strong class="error--text">{{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.full_title') }}</strong>
176
+ <div class="text-caption text--secondary">
177
+ <strong class="error--text">{{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.full_warning') }}</strong> {{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.full_description') }}
178
+ </div>
179
+ </div>
180
+ </template>
181
+ </v-radio>
182
+ </v-radio-group>
183
+ </v-card-text>
184
+
185
+ <v-card-actions>
186
+ <v-spacer></v-spacer>
187
+ <v-btn text @click="cancelRetranslation">
188
+ {{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.cancel_button') }}
189
+ </v-btn>
190
+ <v-btn
191
+ color="primary"
192
+ @click="proceedToConfirmation"
193
+ >
194
+ {{ $t('windward.integrations.pages.admin.translateCourse.retranslation_dialog.continue_button') }}
195
+ </v-btn>
196
+ </v-card-actions>
197
+ </v-card>
198
+ </v-dialog>
199
+
200
+ <!-- DIALOG 2: Nuclear Option Confirmation (Double Check) -->
201
+ <v-dialog v-model="showDeleteConfirmDialog" max-width="500" persistent>
202
+ <v-card>
203
+ <v-card-title class="error--text">
204
+ <v-icon color="error" class="mr-2">mdi-alert</v-icon>
205
+ {{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.title') }}
206
+ </v-card-title>
207
+
208
+ <v-card-text>
209
+ <v-alert type="error" prominent class="mb-4">
210
+ <strong>{{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.cannot_undo') }}</strong>
211
+ </v-alert>
212
+
213
+ <p class="mb-3">
214
+ {{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.about_to_delete') }}
215
+ </p>
216
+
217
+ <v-card outlined class="mb-4">
218
+ <v-card-text>
219
+ <div class="text-subtitle-2">{{ existingTranslation ? existingTranslation.name : '' }}</div>
220
+ <div class="text-caption text--secondary">
221
+ {{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.course_id') }} {{ existingTranslation ? existingTranslation.id : '' }}
222
+ </div>
223
+ </v-card-text>
224
+ </v-card>
225
+
226
+ <p class="mb-3">
227
+ {{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.will_delete_title') }}
228
+ </p>
229
+ <ul class="mb-4">
230
+ <li>{{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.will_delete_content') }}</li>
231
+ <li>{{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.will_delete_assessments') }}</li>
232
+ <li>{{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.will_delete_files') }}</li>
233
+ <li>{{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.will_delete_progress') }}</li>
234
+ </ul>
235
+
236
+ <p class="mb-2">
237
+ <strong>{{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.are_you_sure') }}</strong>
238
+ </p>
239
+
240
+ <!-- Require typing confirmation -->
241
+ <v-text-field
242
+ v-model="deleteConfirmationText"
243
+ :label="$t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.type_delete_to_confirm')"
244
+ outlined
245
+ dense
246
+ class="mt-4"
247
+ :error="deleteConfirmationText && deleteConfirmationText !== 'DELETE'"
248
+ :hint="$t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.type_exactly_delete')"
249
+ persistent-hint
250
+ ></v-text-field>
251
+ </v-card-text>
252
+
253
+ <v-card-actions>
254
+ <v-spacer></v-spacer>
255
+ <v-btn
256
+ text
257
+ @click="cancelDeleteConfirmation"
258
+ >
259
+ {{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.cancel_button') }}
260
+ </v-btn>
261
+ <v-btn
262
+ color="error"
263
+ :disabled="deleteConfirmationText !== 'DELETE'"
264
+ :loading="translating"
265
+ @click="confirmRetranslation"
266
+ >
267
+ {{ $t('windward.integrations.pages.admin.translateCourse.delete_confirm_dialog.confirm_button') }}
268
+ </v-btn>
269
+ </v-card-actions>
270
+ </v-card>
271
+ </v-dialog>
272
+ </v-sheet>
273
+ </template>
274
+
275
+ <script>
276
+ import { mapGetters } from 'vuex'
277
+ import Locale from '~/models/Locale'
278
+ import Course from '~/models/Course'
279
+ import Organization from '~/models/Organization'
280
+
281
+ export default {
282
+ name: 'TranslateCourse',
283
+ data() {
284
+ return {
285
+ // Locale data
286
+ availableLocales: [],
287
+ loadingLocales: false,
288
+
289
+ // Source language selection
290
+ sourceLocaleId: null,
291
+
292
+ // Course selection
293
+ selectedCourseId: null,
294
+ selectedCourse: null,
295
+ coursesInSourceLanguage: [],
296
+ loadingCourses: false,
297
+
298
+ // Target language selection
299
+ targetLocaleId: null,
300
+
301
+ // Options
302
+ sendEmail: true,
303
+
304
+ // Translation state
305
+ translating: false,
306
+
307
+ // Retranslation dialog states
308
+ showRetranslationDialog: false,
309
+ showDeleteConfirmDialog: false,
310
+ retranslationMode: 'incremental',
311
+ existingTranslation: null,
312
+ deleteConfirmationText: '',
313
+ }
314
+ },
315
+ computed: {
316
+ ...mapGetters({
317
+ organization: 'organization/get',
318
+ }),
319
+ organizationModel() {
320
+ return new Organization({ id: this.organization.id })
321
+ },
322
+ canTranslate() {
323
+ return this.selectedCourse && this.targetLocaleId && !this.translating
324
+ },
325
+ sourceLocaleName() {
326
+ if (!this.sourceLocaleId) return ''
327
+ const locale = this.availableLocales.find(l => l.id === this.sourceLocaleId)
328
+ return locale ? locale.displayName : ''
329
+ },
330
+ targetLocaleName() {
331
+ if (!this.targetLocaleId) return ''
332
+ const locale = this.availableLocales.find(l => l.id === this.targetLocaleId)
333
+ return locale ? locale.displayName : ''
334
+ },
335
+ // Filter out source language from target options
336
+ targetLocaleOptions() {
337
+ return this.availableLocales.filter(l => l.id !== this.sourceLocaleId)
338
+ },
339
+ },
340
+ async mounted() {
341
+ await this.loadLocales()
342
+ },
343
+ methods: {
344
+ async loadLocales() {
345
+ this.loadingLocales = true
346
+ try {
347
+ const locales = await new Locale().get()
348
+ this.availableLocales = locales.map(locale => {
349
+ // Use i18n translation for full language name (e.g., 'en-US' → 'English US')
350
+ const i18nKey = `locales.${locale.name}`
351
+ const translatedName = this.$t(i18nKey)
352
+ // Get the native language name, fallback to raw name if not found
353
+ const nativeName = translatedName !== i18nKey ? translatedName : locale.name
354
+ // Add locale code next to the language name for clarity
355
+ const displayName = `${nativeName} (${locale.name})`
356
+ return {
357
+ id: locale.id,
358
+ name: locale.name,
359
+ displayName,
360
+ }
361
+ })
362
+ } catch (e) {
363
+ // eslint-disable-next-line no-console
364
+ console.error('Error loading locales:', e)
365
+ this.$dialog.error(this.$t('windward.integrations.pages.admin.translateCourse.locale_load_failed'))
366
+ } finally {
367
+ this.loadingLocales = false
368
+ }
369
+ },
370
+
371
+ async onSourceLocaleChange() {
372
+ // Clear dependent selections
373
+ this.selectedCourseId = null
374
+ this.selectedCourse = null
375
+ this.targetLocaleId = null
376
+
377
+ if (this.sourceLocaleId) {
378
+ await this.loadCoursesForLocale()
379
+ } else {
380
+ this.coursesInSourceLanguage = []
381
+ }
382
+ },
383
+
384
+ async loadCoursesForLocale() {
385
+ this.loadingCourses = true
386
+ this.coursesInSourceLanguage = []
387
+
388
+ try {
389
+ // Fetch courses filtered by locale_id
390
+ const response = await new Organization({ id: this.organization.id })
391
+ .courses()
392
+ .include('user', 'locale')
393
+ .where('locale_id', this.sourceLocaleId)
394
+ .limit(100)
395
+ .nowrap()
396
+ .get()
397
+
398
+ this.coursesInSourceLanguage = response.data.map(course => ({
399
+ id: course.id,
400
+ name: course.name,
401
+ status: course.status,
402
+ ownerName: course.user
403
+ ? `${course.user.first_name} ${course.user.last_name}`
404
+ : null,
405
+ ...course,
406
+ }))
407
+ } catch (e) {
408
+ // eslint-disable-next-line no-console
409
+ console.error('Error loading courses:', e)
410
+ this.$dialog.error(this.$t('windward.integrations.pages.admin.translateCourse.courses_load_failed'))
411
+ } finally {
412
+ this.loadingCourses = false
413
+ }
414
+ },
415
+
416
+ onCourseChange() {
417
+ // Clear target locale when course changes
418
+ this.targetLocaleId = null
419
+
420
+ // Find the selected course object
421
+ this.selectedCourse = this.coursesInSourceLanguage.find(
422
+ c => c.id === this.selectedCourseId
423
+ ) || null
424
+ },
425
+
426
+ onTargetLocaleChange() {
427
+ // Validate selection if needed
428
+ },
429
+
430
+ async startTranslation() {
431
+ this.translating = true
432
+ try {
433
+ const course = new Course({ id: this.selectedCourseId })
434
+
435
+ await course.request({
436
+ method: 'POST',
437
+ url: `${course.endpoint()}/translate`,
438
+ data: {
439
+ target_locale_id: this.targetLocaleId,
440
+ options: {
441
+ send_email: this.sendEmail,
442
+ },
443
+ },
444
+ })
445
+
446
+ // Success - show message and emit event
447
+ this.$dialog.success(this.$t('windward.integrations.pages.admin.translateCourse.translation_queued'))
448
+ this.$emit('translation-started')
449
+ this.reset()
450
+
451
+ } catch (e) {
452
+ // eslint-disable-next-line no-console
453
+ console.error('Translation API Error:', e.response?.status, e.response?.data)
454
+
455
+ // Check if translation exists and requires retranslation mode
456
+ if (e.response?.status === 422 && e.response?.data?.requires_retranslation_mode) {
457
+ // Translation exists - show mode selection dialog
458
+ this.existingTranslation = {
459
+ id: e.response.data.existing_course_id,
460
+ name: e.response.data.existing_course_name,
461
+ }
462
+ this.showRetranslationDialog = true
463
+ } else {
464
+ // eslint-disable-next-line no-console
465
+ console.error('Error starting translation:', e)
466
+ this.$dialog.error(
467
+ e.response?.data?.message || this.$t('windward.integrations.pages.admin.translateCourse.translation_failed')
468
+ )
469
+ }
470
+ } finally {
471
+ this.translating = false
472
+ }
473
+ },
474
+
475
+ /**
476
+ * Proceed from mode selection to confirmation
477
+ */
478
+ proceedToConfirmation() {
479
+ if (this.retranslationMode === 'full') {
480
+ // Show second confirmation dialog for nuclear option
481
+ this.showRetranslationDialog = false
482
+ this.showDeleteConfirmDialog = true
483
+ } else {
484
+ // Incremental mode - proceed directly
485
+ this.showRetranslationDialog = false
486
+ this.confirmRetranslation()
487
+ }
488
+ },
489
+
490
+ /**
491
+ * Cancel retranslation dialog
492
+ */
493
+ cancelRetranslation() {
494
+ this.showRetranslationDialog = false
495
+ this.retranslationMode = 'incremental' // Reset to default
496
+ this.existingTranslation = null
497
+ this.translating = false
498
+ },
499
+
500
+ /**
501
+ * Cancel delete confirmation dialog - go back to mode selection
502
+ */
503
+ cancelDeleteConfirmation() {
504
+ this.showDeleteConfirmDialog = false
505
+ this.deleteConfirmationText = '' // Reset
506
+ this.showRetranslationDialog = true // Go back to mode selection
507
+ this.translating = false
508
+ },
509
+
510
+ /**
511
+ * Confirm and execute retranslation
512
+ */
513
+ async confirmRetranslation() {
514
+ this.translating = true
515
+ try {
516
+ const course = new Course({ id: this.selectedCourseId })
517
+
518
+ await course.request({
519
+ method: 'POST',
520
+ url: `${course.endpoint()}/translate`,
521
+ data: {
522
+ target_locale_id: this.targetLocaleId,
523
+ retranslation_mode: this.retranslationMode,
524
+ options: {
525
+ send_email: this.sendEmail,
526
+ },
527
+ },
528
+ })
529
+
530
+ // Show appropriate success message
531
+ if (this.retranslationMode === 'full') {
532
+ this.$dialog.success(this.$t('windward.integrations.pages.admin.translateCourse.full_retranslation_queued'))
533
+ } else {
534
+ this.$dialog.success(this.$t('windward.integrations.pages.admin.translateCourse.incremental_retranslation_queued'))
535
+ }
536
+
537
+ // Emit event and reset form
538
+ this.$emit('translation-started')
539
+ this.reset()
540
+
541
+ } catch (e) {
542
+ // eslint-disable-next-line no-console
543
+ console.error('Error starting retranslation:', e)
544
+ this.$dialog.error(
545
+ e.response?.data?.message || this.$t('windward.integrations.pages.admin.translateCourse.retranslation_failed')
546
+ )
547
+ } finally {
548
+ this.translating = false
549
+ }
550
+ },
551
+
552
+ reset() {
553
+ this.sourceLocaleId = null
554
+ this.selectedCourseId = null
555
+ this.selectedCourse = null
556
+ this.coursesInSourceLanguage = []
557
+ this.targetLocaleId = null
558
+ this.translating = false
559
+ // Reset retranslation state
560
+ this.showRetranslationDialog = false
561
+ this.showDeleteConfirmDialog = false
562
+ this.retranslationMode = 'incremental'
563
+ this.existingTranslation = null
564
+ this.deleteConfirmationText = ''
565
+ },
566
+ },
567
+ }
568
+ </script>
569
+
570
+ <style scoped>
571
+ .v-card {
572
+ transition: all 0.3s ease;
573
+ }
574
+ </style>
@@ -12,6 +12,33 @@
12
12
  'windward.integrations.components.settings.external_integration.lti_consumer.link_select'
13
13
  )
14
14
  }}
15
+ <v-alert v-if="!consumers.length" type="warning">
16
+ <p>
17
+ {{
18
+ $t(
19
+ 'windward.integrations.components.settings.external_integration.lti_consumer.no_links'
20
+ )
21
+ }}
22
+ </p>
23
+ <p
24
+ v-if="
25
+ $PermissionService.userHasAccessTo(
26
+ 'plugin.windward.integrations.course.externalIntegration',
27
+ 'readable'
28
+ )
29
+ "
30
+ >
31
+ <NuxtLink
32
+ :to="'/course/' + course.id + '/integrations/external'"
33
+ >
34
+ {{
35
+ $t(
36
+ 'windward.integrations.components.settings.external_integration.lti_consumer.create_link'
37
+ )
38
+ }}
39
+ </NuxtLink>
40
+ </p>
41
+ </v-alert>
15
42
  <v-list>
16
43
  <v-list-item-group
17
44
  v-model="block.metadata.config.tool_id"
@@ -0,0 +1,20 @@
1
+ export default {
2
+ new_chat: 'New Chat',
3
+ recent_chats: 'Recent chats',
4
+ untitled_chat: 'Untitled chat',
5
+ no_history: 'No recent chats',
6
+ system_tip:
7
+ "Hi! I'm here to help with your course. Try asking about learning objectives, content structure, or best practices.",
8
+ placeholder: 'Ask about your course content...',
9
+ hint: 'Press Enter to send, Shift+Enter for new line',
10
+ error_generic: 'Something went wrong. Please try again.',
11
+ error_with_status:
12
+ 'Something went wrong. Please try again. (Error {status})',
13
+ action_try_again: 'Try again',
14
+ action_dismiss: 'Dismiss',
15
+ sender_you: 'You',
16
+ sender_agent: 'Assistant',
17
+ limit_reached:
18
+ 'You have reached the maximum number of messages allowed in this chat. Please start a new chat to continue.',
19
+ aria_open_history: 'Open chat history',
20
+ }
@@ -0,0 +1,5 @@
1
+ import chat from './chat'
2
+
3
+ export default {
4
+ chat,
5
+ }
@@ -0,0 +1,5 @@
1
+ import transformBlock from './transform_block'
2
+
3
+ export default {
4
+ transform_block: transformBlock,
5
+ }
@@ -0,0 +1,9 @@
1
+ export default {
2
+ change_block_type: 'Change block type',
3
+ change_block_type_insufficient_paragraphs:
4
+ 'Change block type is available when the text has at least two paragraphs.',
5
+ change_block_type_insufficient_items:
6
+ 'Change block type is available when the block has at least two items.',
7
+ change_block_type_error:
8
+ 'Unable to change block type. Try again or edit manually.',
9
+ }
@@ -0,0 +1,5 @@
1
+ import ltiConsumer from './lti_consumer'
2
+
3
+ export default {
4
+ lti_consumer: ltiConsumer,
5
+ }
@@ -0,0 +1,17 @@
1
+ export default {
2
+ title: 'LTI Link',
3
+ instructions:
4
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam fringilla ipsum eget ante tempus blandit. Maecenas vel massa\n' +
5
+ 'nec tellus vestibulum porttitor non a enim.',
6
+ launch: 'Launch',
7
+ goodbye: 'goodbye',
8
+ launch_in_new_window: 'launch {0} in new tab',
9
+ launch_in_modal: 'launch {0} in modal',
10
+ configure_warning:
11
+ 'Select an available LTI link to get started. Then, configure the desired launch type.',
12
+ no_access:
13
+ 'You do not have permissions to launch this link. Please contact your system administrator.',
14
+ missing_tool: 'The tool id {0} is missing',
15
+ unknown_error: 'Something went wrong! Could not launch this link!',
16
+ link_disabled: 'Link disabled',
17
+ }