richie-education 3.3.1-dev12 → 3.3.1-dev15
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/js/pages/DashboardBatchOrders/index.spec.tsx +103 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/BatchOrderPaymentManager.tsx +15 -27
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusAsideList/index.tsx +8 -27
- package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRunCompacted/index.tsx +41 -17
- package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +37 -4
- package/js/widgets/cunningham-fr-FR-locale.json +80 -0
- package/js/widgets/index.tsx +6 -1
- package/package.json +1 -1
|
@@ -234,4 +234,107 @@ describe('<DashboardBatchOrders/>', () => {
|
|
|
234
234
|
|
|
235
235
|
await screen.findByText('Completed');
|
|
236
236
|
});
|
|
237
|
+
|
|
238
|
+
it('allows retrying payment after aborting the payment tunnel', async () => {
|
|
239
|
+
const batchOrder = BatchOrderReadFactory({
|
|
240
|
+
payment_method: PaymentMethod.CARD_PAYMENT,
|
|
241
|
+
state: BatchOrderState.PENDING,
|
|
242
|
+
total: 200,
|
|
243
|
+
currency: 'EUR',
|
|
244
|
+
}).one();
|
|
245
|
+
|
|
246
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/batch-orders/?page=1&page_size=${perPage}`, {
|
|
247
|
+
results: [batchOrder],
|
|
248
|
+
count: 1,
|
|
249
|
+
next: null,
|
|
250
|
+
previous: null,
|
|
251
|
+
});
|
|
252
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/batch-orders/`, [batchOrder]);
|
|
253
|
+
fetchMock.get(`https://joanie.endpoint/api/v1.0/batch-orders/${batchOrder.id}/`, batchOrder);
|
|
254
|
+
|
|
255
|
+
render(<DashboardTest initialRoute={LearnerDashboardPaths.BATCH_ORDERS} />, {
|
|
256
|
+
wrapper: BaseJoanieAppWrapper,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await expectNoSpinner('Loading batch orders...');
|
|
260
|
+
await screen.findByText('Payment required');
|
|
261
|
+
|
|
262
|
+
// Open modal and start payment
|
|
263
|
+
await userEvent.click(await screen.findByRole('button', { name: 'Pay €200.00' }));
|
|
264
|
+
|
|
265
|
+
fetchMock.post(
|
|
266
|
+
`https://joanie.endpoint/api/v1.0/batch-orders/${batchOrder.id}/submit-for-payment/`,
|
|
267
|
+
{ payment_id: 'payment_id', provider: 'payment_provider', url: 'payment_url' },
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const firstModal = await screen.findByRole('dialog');
|
|
271
|
+
await userEvent.click(within(firstModal).getByRole('button', { name: 'Pay €200.00' }));
|
|
272
|
+
|
|
273
|
+
// Close payment modal
|
|
274
|
+
await screen.findByTestId('payment-abort');
|
|
275
|
+
fetchMock.get(
|
|
276
|
+
`https://joanie.endpoint/api/v1.0/batch-orders/?page=1&page_size=${perPage}`,
|
|
277
|
+
{
|
|
278
|
+
results: [{ ...batchOrder, state: BatchOrderState.PROCESS_PAYMENT }],
|
|
279
|
+
count: 1,
|
|
280
|
+
next: null,
|
|
281
|
+
previous: null,
|
|
282
|
+
},
|
|
283
|
+
{ overwriteRoutes: true },
|
|
284
|
+
);
|
|
285
|
+
fetchMock.get(
|
|
286
|
+
`https://joanie.endpoint/api/v1.0/batch-orders/${batchOrder.id}/`,
|
|
287
|
+
{ ...batchOrder, state: BatchOrderState.PROCESS_PAYMENT },
|
|
288
|
+
{ overwriteRoutes: true },
|
|
289
|
+
);
|
|
290
|
+
await userEvent.click(screen.getByTestId('payment-abort'));
|
|
291
|
+
|
|
292
|
+
const modalAfterAbort = await screen.findByRole('dialog');
|
|
293
|
+
await userEvent.click(within(modalAfterAbort).getByRole('button', { name: 'close' }));
|
|
294
|
+
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
|
|
295
|
+
|
|
296
|
+
await screen.findByText('Payment required');
|
|
297
|
+
await screen.findByRole('button', { name: 'Pay €200.00' });
|
|
298
|
+
|
|
299
|
+
// Retry payment successfully
|
|
300
|
+
await userEvent.click(await screen.findByRole('button', { name: 'Pay €200.00' }));
|
|
301
|
+
|
|
302
|
+
fetchMock.post(
|
|
303
|
+
`https://joanie.endpoint/api/v1.0/batch-orders/${batchOrder.id}/submit-for-payment/`,
|
|
304
|
+
{ payment_id: 'payment_id', provider: 'payment_provider', url: 'payment_url' },
|
|
305
|
+
{ overwriteRoutes: true },
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const retryModal = await screen.findByRole('dialog');
|
|
309
|
+
await userEvent.click(within(retryModal).getByRole('button', { name: 'Pay €200.00' }));
|
|
310
|
+
|
|
311
|
+
await screen.findByTestId('payment-success');
|
|
312
|
+
|
|
313
|
+
fetchMock.get(
|
|
314
|
+
`https://joanie.endpoint/api/v1.0/batch-orders/?page=1&page_size=${perPage}`,
|
|
315
|
+
{
|
|
316
|
+
results: [{ ...batchOrder, state: BatchOrderState.COMPLETED }],
|
|
317
|
+
count: 1,
|
|
318
|
+
next: null,
|
|
319
|
+
previous: null,
|
|
320
|
+
},
|
|
321
|
+
{ overwriteRoutes: true },
|
|
322
|
+
);
|
|
323
|
+
fetchMock.get(
|
|
324
|
+
`https://joanie.endpoint/api/v1.0/batch-orders/${batchOrder.id}/`,
|
|
325
|
+
{ ...batchOrder, state: BatchOrderState.COMPLETED },
|
|
326
|
+
{ overwriteRoutes: true },
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
await userEvent.click(screen.getByTestId('payment-success'));
|
|
330
|
+
|
|
331
|
+
await waitFor(() => {
|
|
332
|
+
expect(mockMessageModal).toHaveBeenCalledWith(
|
|
333
|
+
expect.objectContaining({ title: 'Payment successful' }),
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
expect(screen.queryByRole('button', { name: /Pay/ })).not.toBeInTheDocument();
|
|
338
|
+
await screen.findByText('Completed');
|
|
339
|
+
});
|
|
237
340
|
});
|
|
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
|
|
|
2
2
|
import { Button, useModal } from '@openfun/cunningham-react';
|
|
3
3
|
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
|
4
4
|
import { useBatchOrder } from 'hooks/useBatchOrder';
|
|
5
|
-
import { BatchOrderRead
|
|
5
|
+
import { BatchOrderRead } from 'types/Joanie';
|
|
6
6
|
import { DashboardSubItem } from 'widgets/Dashboard/components/DashboardItem/DashboardSubItem';
|
|
7
7
|
import { DashboardSubItemsList } from '../../DashboardSubItemsList';
|
|
8
8
|
import { BatchOrderPaymentModal } from '.';
|
|
@@ -13,11 +13,6 @@ const messages = defineMessages({
|
|
|
13
13
|
description: 'Label before the payment button',
|
|
14
14
|
defaultMessage: 'We are waiting for your payment to finalize the batch order.',
|
|
15
15
|
},
|
|
16
|
-
paymentProcessing: {
|
|
17
|
-
id: 'components.ProductCertificateFooter.paymentProcessing',
|
|
18
|
-
description: 'Button label for the payment is processing message',
|
|
19
|
-
defaultMessage: 'Payment processing...',
|
|
20
|
-
},
|
|
21
16
|
paymentNeededButton: {
|
|
22
17
|
id: 'components.ProductCertificateFooter.paymentNeededButton',
|
|
23
18
|
description: 'Button label for the payment needed message',
|
|
@@ -38,7 +33,6 @@ export const BatchOrderPaymentManager = ({ batchOrder }: BatchPaymentManagerProp
|
|
|
38
33
|
const intl = useIntl();
|
|
39
34
|
const retryModal = useModal();
|
|
40
35
|
const batchOrderQuery = useBatchOrder(batchOrder.id);
|
|
41
|
-
const processingPayment = batchOrder.state === BatchOrderState.PROCESS_PAYMENT;
|
|
42
36
|
|
|
43
37
|
useEffect(() => {
|
|
44
38
|
if (batchOrderQuery.item) {
|
|
@@ -54,30 +48,24 @@ export const BatchOrderPaymentManager = ({ batchOrder }: BatchPaymentManagerProp
|
|
|
54
48
|
footer={
|
|
55
49
|
<div className="content">
|
|
56
50
|
<FormattedMessage {...messages.batchOrderPayment} />
|
|
57
|
-
<Button
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{...messages.paymentNeededButton}
|
|
68
|
-
values={{
|
|
69
|
-
amount: intl.formatNumber(batchOrder.total ?? 0, {
|
|
70
|
-
style: 'currency',
|
|
71
|
-
currency: batchOrder.currency ?? 'EUR',
|
|
72
|
-
}),
|
|
73
|
-
}}
|
|
74
|
-
/>
|
|
75
|
-
)}
|
|
51
|
+
<Button size="small" color="primary" onClick={retryModal.open}>
|
|
52
|
+
<FormattedMessage
|
|
53
|
+
{...messages.paymentNeededButton}
|
|
54
|
+
values={{
|
|
55
|
+
amount: intl.formatNumber(batchOrder.total ?? 0, {
|
|
56
|
+
style: 'currency',
|
|
57
|
+
currency: batchOrder.currency ?? 'EUR',
|
|
58
|
+
}),
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
76
61
|
</Button>
|
|
77
62
|
<BatchOrderPaymentModal
|
|
78
63
|
{...retryModal}
|
|
79
64
|
batchOrder={batchOrder}
|
|
80
|
-
onClose={
|
|
65
|
+
onClose={() => {
|
|
66
|
+
retryModal.onClose();
|
|
67
|
+
batchOrderQuery.methods.invalidate();
|
|
68
|
+
}}
|
|
81
69
|
/>
|
|
82
70
|
</div>
|
|
83
71
|
}
|
|
@@ -18,17 +18,6 @@ const messages = defineMessages({
|
|
|
18
18
|
'Message displayed on the top of course runs list on syllabus when there is 0 or multiple course runs opened',
|
|
19
19
|
defaultMessage: 'Course runs',
|
|
20
20
|
},
|
|
21
|
-
noCourseRuns: {
|
|
22
|
-
id: 'components.SyllabusAsideList.noCourseRuns',
|
|
23
|
-
description: 'Message displayed on syllabus when there are no course runs to show',
|
|
24
|
-
defaultMessage: 'No course runs',
|
|
25
|
-
},
|
|
26
|
-
noOtherCourseRuns: {
|
|
27
|
-
id: 'components.SyllabusAsideList.noOtherCourseRuns',
|
|
28
|
-
description:
|
|
29
|
-
'Message displayed on syllabus when there are no other course runs to show than the only one opened',
|
|
30
|
-
defaultMessage: 'No other course runs',
|
|
31
|
-
},
|
|
32
21
|
toBeScheduled: {
|
|
33
22
|
id: 'components.SyllabusAsideList.toBeScheduled',
|
|
34
23
|
description: 'Message displayed on syllabus when there are course runs to be scheduled',
|
|
@@ -108,28 +97,20 @@ export const SyllabusAsideList = ({
|
|
|
108
97
|
|
|
109
98
|
const showLanguages = CourseRunHelper.IsAllCourseRunsWithSameLanguages(courseRuns);
|
|
110
99
|
|
|
100
|
+
// If there are no runs to display at all, don't render anything
|
|
101
|
+
if (openedRuns.length <= 1 && otherRuns.length === 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
111
105
|
return (
|
|
112
106
|
<>
|
|
113
107
|
<h2 className="course-detail__title">
|
|
114
|
-
{openedRuns.length
|
|
115
|
-
<FormattedMessage {...messages.otherCourseRuns} />
|
|
116
|
-
) : (
|
|
108
|
+
{openedRuns.length > 1 ? (
|
|
117
109
|
<FormattedMessage {...messages.courseRunsTitle} />
|
|
110
|
+
) : (
|
|
111
|
+
<FormattedMessage {...messages.otherCourseRuns} />
|
|
118
112
|
)}
|
|
119
113
|
</h2>
|
|
120
|
-
{openedRuns.length <= 1 && otherRuns.length === 0 && (
|
|
121
|
-
<div className="course-detail__row course-detail__no-runs">
|
|
122
|
-
{openedRuns.length === 0 ? (
|
|
123
|
-
<p>
|
|
124
|
-
<FormattedMessage {...messages.noCourseRuns} />
|
|
125
|
-
</p>
|
|
126
|
-
) : (
|
|
127
|
-
<p>
|
|
128
|
-
<FormattedMessage {...messages.noOtherCourseRuns} />
|
|
129
|
-
</p>
|
|
130
|
-
)}
|
|
131
|
-
</div>
|
|
132
|
-
)}
|
|
133
114
|
{openedRuns.length > 1 && (
|
|
134
115
|
<div
|
|
135
116
|
id="courseDetailsRunsOpen"
|
|
@@ -34,13 +34,18 @@ const messages = defineMessages({
|
|
|
34
34
|
selfPaceRunPeriod: {
|
|
35
35
|
id: 'components.SyllabusCourseRunCompacted.selfPaceCoursePeriod',
|
|
36
36
|
description: 'Course date of an opened and self paced course run block',
|
|
37
|
-
defaultMessage: '
|
|
37
|
+
defaultMessage: 'Until {endDate, select, undefined {} other {{endDate}}}',
|
|
38
38
|
},
|
|
39
39
|
selfPaceNoEndDate: {
|
|
40
40
|
id: 'components.SyllabusCourseRunCompacted.selfPaceNoEndDate',
|
|
41
41
|
description: 'Self paced course run block with no end date',
|
|
42
42
|
defaultMessage: 'Available',
|
|
43
43
|
},
|
|
44
|
+
enrollment: {
|
|
45
|
+
id: 'components.SyllabusCourseRun.enrollment',
|
|
46
|
+
description: 'Title of the enrollment dates section of an opened course run block',
|
|
47
|
+
defaultMessage: 'Enrollment',
|
|
48
|
+
},
|
|
44
49
|
coursePrice: {
|
|
45
50
|
id: 'components.SyllabusCourseRunCompacted.coursePrice',
|
|
46
51
|
description: 'Title of the course enrollment price section of an opened course run block',
|
|
@@ -105,6 +110,7 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
105
110
|
let enrollmentDiscountedPrice = '';
|
|
106
111
|
let certificatePrice = '';
|
|
107
112
|
let certificateDiscountedPrice = '';
|
|
113
|
+
const enrollmentEnd = courseRun.enrollment_end ? formatDate(courseRun.enrollment_end) : '...';
|
|
108
114
|
|
|
109
115
|
if (courseRun.offer) {
|
|
110
116
|
const offer = courseRun.offer.toUpperCase().replaceAll(' ', '_');
|
|
@@ -157,23 +163,41 @@ const OpenedSelfPacedCourseRun = ({
|
|
|
157
163
|
<>
|
|
158
164
|
{courseRun.title && <h3>{StringHelper.capitalizeFirst(courseRun.title)}</h3>}
|
|
159
165
|
<dl>
|
|
160
|
-
{
|
|
161
|
-
|
|
162
|
-
<
|
|
163
|
-
|
|
166
|
+
{hasEndDate ? (
|
|
167
|
+
<>
|
|
168
|
+
<dt>
|
|
169
|
+
<FormattedMessage {...messages.enrollment} />
|
|
170
|
+
</dt>
|
|
171
|
+
<dd>
|
|
172
|
+
<FormattedMessage
|
|
173
|
+
{...messages.selfPaceRunPeriod}
|
|
174
|
+
values={{
|
|
175
|
+
endDate: enrollmentEnd,
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
</dd>
|
|
179
|
+
<dt>
|
|
180
|
+
<FormattedMessage {...messages.course} />
|
|
181
|
+
</dt>
|
|
182
|
+
<dd>
|
|
183
|
+
<FormattedMessage
|
|
184
|
+
{...messages.selfPaceRunPeriod}
|
|
185
|
+
values={{
|
|
186
|
+
endDate: end,
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
</dd>
|
|
190
|
+
</>
|
|
191
|
+
) : (
|
|
192
|
+
<>
|
|
193
|
+
<dt>
|
|
194
|
+
<FormattedMessage {...messages.enrollment} />
|
|
195
|
+
</dt>
|
|
196
|
+
<dd>
|
|
197
|
+
<FormattedMessage {...messages.selfPaceNoEndDate} />
|
|
198
|
+
</dd>
|
|
199
|
+
</>
|
|
164
200
|
)}
|
|
165
|
-
<dd>
|
|
166
|
-
{hasEndDate ? (
|
|
167
|
-
<FormattedMessage
|
|
168
|
-
{...messages.selfPaceRunPeriod}
|
|
169
|
-
values={{
|
|
170
|
-
endDate: end,
|
|
171
|
-
}}
|
|
172
|
-
/>
|
|
173
|
-
) : (
|
|
174
|
-
<FormattedMessage {...messages.selfPaceNoEndDate} />
|
|
175
|
-
)}
|
|
176
|
-
</dd>
|
|
177
201
|
{!showLanguages && (
|
|
178
202
|
<>
|
|
179
203
|
<dt>
|
|
@@ -172,7 +172,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
172
172
|
});
|
|
173
173
|
const runContainer = heading.parentNode! as HTMLElement;
|
|
174
174
|
const courseDatesText = courseRun.end
|
|
175
|
-
? `
|
|
175
|
+
? `Until ${intl.formatDate(new Date(courseRun.end), DEFAULT_DATE_FORMAT)}`
|
|
176
176
|
: `Available`;
|
|
177
177
|
|
|
178
178
|
const courseDatesContainer = getByText(runContainer, courseDatesText);
|
|
@@ -221,7 +221,7 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
221
221
|
const expectEmptyPortalContainer = () => {
|
|
222
222
|
// This way of testing is a bit hard but we are SURE that there is no other content. This way
|
|
223
223
|
// we are also testing the absence of other elements.
|
|
224
|
-
expect(getPortalContainer().textContent).
|
|
224
|
+
expect(getPortalContainer().textContent).toContain('Other course runs');
|
|
225
225
|
};
|
|
226
226
|
|
|
227
227
|
it('has no opened course run', async () => {
|
|
@@ -390,10 +390,13 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
390
390
|
const courseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
391
391
|
resource_link: resourceLink,
|
|
392
392
|
}).one();
|
|
393
|
+
const archivedCourseRun = CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)({
|
|
394
|
+
resource_link: resourceLink,
|
|
395
|
+
}).one();
|
|
393
396
|
|
|
394
397
|
render(
|
|
395
398
|
<SyllabusCourseRunsList
|
|
396
|
-
courseRuns={[courseRun]}
|
|
399
|
+
courseRuns={[courseRun, archivedCourseRun]}
|
|
397
400
|
course={course}
|
|
398
401
|
maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
|
|
399
402
|
/>,
|
|
@@ -412,12 +415,42 @@ describe('<SyllabusCourseRunsList/>', () => {
|
|
|
412
415
|
expectEmptyPortalContainer();
|
|
413
416
|
});
|
|
414
417
|
|
|
418
|
+
it('has only one opened product', async () => {
|
|
419
|
+
const course = PacedCourseFactory().one();
|
|
420
|
+
const offering = OfferingFactory().one();
|
|
421
|
+
const resourceLink = `https://joanie.endpoint/api/v1.0/courses/${course.code}/products/${offering.product.id}/`;
|
|
422
|
+
fetchMock.get(resourceLink, offering);
|
|
423
|
+
|
|
424
|
+
const courseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)({
|
|
425
|
+
resource_link: resourceLink,
|
|
426
|
+
}).one();
|
|
427
|
+
|
|
428
|
+
render(
|
|
429
|
+
<SyllabusCourseRunsList
|
|
430
|
+
courseRuns={[courseRun]}
|
|
431
|
+
course={course}
|
|
432
|
+
maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
|
|
433
|
+
/>,
|
|
434
|
+
{
|
|
435
|
+
queryOptions: { client: createTestQueryClient({ user: null }) },
|
|
436
|
+
},
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
// Header.
|
|
440
|
+
expect(getHeaderContainer().querySelectorAll('.course-detail__run-descriptions').length).toBe(
|
|
441
|
+
1,
|
|
442
|
+
);
|
|
443
|
+
await expectCourseProduct(getHeaderContainer(), offering);
|
|
444
|
+
});
|
|
445
|
+
|
|
415
446
|
it('renders a specific title in portal when there is one opened course run', () => {
|
|
416
447
|
const course = PacedCourseFactory().one();
|
|
448
|
+
const ongoingOpenCourseRun = CourseRunFactoryFromPriority(Priority.ONGOING_OPEN)().one();
|
|
449
|
+
const archivedCourseRun = CourseRunFactoryFromPriority(Priority.ARCHIVED_CLOSED)().one();
|
|
417
450
|
|
|
418
451
|
render(
|
|
419
452
|
<SyllabusCourseRunsList
|
|
420
|
-
courseRuns={[
|
|
453
|
+
courseRuns={[ongoingOpenCourseRun, archivedCourseRun]}
|
|
421
454
|
course={course}
|
|
422
455
|
maxArchivedCourseRuns={MAX_ARCHIVED_COURSE_RUNS}
|
|
423
456
|
/>,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": {
|
|
3
|
+
"alert": {
|
|
4
|
+
"close_aria_label": "Supprimer l'alerte",
|
|
5
|
+
"expand_aria_label": "Ouvrir l'alerte",
|
|
6
|
+
"shrink_aria_label": "Fermer l'alerte"
|
|
7
|
+
},
|
|
8
|
+
"pagination": {
|
|
9
|
+
"goto_label": "Aller à",
|
|
10
|
+
"goto_label_aria": "Aller à la page",
|
|
11
|
+
"next_aria": "Aller à la page suivante",
|
|
12
|
+
"previous_aria": "Aller à la page précédente",
|
|
13
|
+
"goto_page_aria": "Aller à la page {page}",
|
|
14
|
+
"current_page_aria": "Vous êtes actuellement sur la page {page}"
|
|
15
|
+
},
|
|
16
|
+
"datagrid": {
|
|
17
|
+
"empty": "Ce tableau est vide",
|
|
18
|
+
"empty_alt": "Illustration d'un tableau vide",
|
|
19
|
+
"loader_aria": "Chargement des données",
|
|
20
|
+
"rows_selection_aria": "Toutes les lignes sélectionnées",
|
|
21
|
+
"row_selection_aria": "Ligne sélectionnée"
|
|
22
|
+
},
|
|
23
|
+
"provider": {
|
|
24
|
+
"test": "Ceci est un test : {name}"
|
|
25
|
+
},
|
|
26
|
+
"forms": {
|
|
27
|
+
"input": {
|
|
28
|
+
"password": {
|
|
29
|
+
"show_password": "Afficher le mot de passe",
|
|
30
|
+
"hide_password": "Masquer le mot de passe"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"select": {
|
|
34
|
+
"toggle_button_aria_label": "Ouvrir le menu",
|
|
35
|
+
"clear_button_aria_label": "Effacer la sélection",
|
|
36
|
+
"clear_all_button_aria_label": "Effacer toutes les sélections",
|
|
37
|
+
"menu_empty_placeholder": "Aucun choix disponible"
|
|
38
|
+
},
|
|
39
|
+
"file_uploader": {
|
|
40
|
+
"delete_file_name": "Supprimer le fichier {name}",
|
|
41
|
+
"delete_file": "Supprimer le fichier",
|
|
42
|
+
"uploading": "Upload en cours ...",
|
|
43
|
+
"caption": "Glisser-déposer ou ",
|
|
44
|
+
"browse_files": "Parcourir"
|
|
45
|
+
},
|
|
46
|
+
"date_picker": {
|
|
47
|
+
"toggle_button_aria_label_open": "Ouvrir le calendrier",
|
|
48
|
+
"toggle_button_aria_label_close": "Fermer le calendrier",
|
|
49
|
+
"clear_button_aria_label": "Effacer la date",
|
|
50
|
+
"next_month_button_aria_label": "Mois suivant",
|
|
51
|
+
"next_year_button_aria_label": "Année suivante",
|
|
52
|
+
"previous_month_button_aria_label": "Mois précédent",
|
|
53
|
+
"previous_year_button_aria_label": "Année précédente",
|
|
54
|
+
"year_select_button_aria_label": "Sélectionner une année",
|
|
55
|
+
"month_select_button_aria_label": "Sélectionner un mois"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"modals": {
|
|
59
|
+
"helpers": {
|
|
60
|
+
"delete_confirmation": {
|
|
61
|
+
"title": "Êtes-vous sûr ?",
|
|
62
|
+
"children": "Êtes-vous sûr de vouloir supprimer cet élément ?",
|
|
63
|
+
"cancel": "Annuler",
|
|
64
|
+
"delete": "Supprimer"
|
|
65
|
+
},
|
|
66
|
+
"confirmation": {
|
|
67
|
+
"title": "Êtes-vous sûr ?",
|
|
68
|
+
"children": "Êtes-vous sûr de vouloir faire cela ?",
|
|
69
|
+
"cancel": "Annuler",
|
|
70
|
+
"yes": "Oui"
|
|
71
|
+
},
|
|
72
|
+
"disclaimer": {
|
|
73
|
+
"title": "Avertissement",
|
|
74
|
+
"children": "Ceci est un avertissement",
|
|
75
|
+
"ok": "Ok"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
package/js/widgets/index.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import startCase from 'lodash-es/startCase';
|
|
|
4
4
|
import { lazy, Suspense } from 'react';
|
|
5
5
|
import ReactDOM from 'react-dom';
|
|
6
6
|
import { CunninghamProvider } from '@openfun/cunningham-react';
|
|
7
|
+
import cunninghamFrFRLocale from 'widgets/cunningham-fr-FR-locale.json';
|
|
7
8
|
import { HistoryProvider } from 'hooks/useHistory';
|
|
8
9
|
import { SessionProvider } from 'contexts/SessionContext';
|
|
9
10
|
import { Spinner } from 'components/Spinner';
|
|
@@ -105,7 +106,11 @@ export const Root = ({ richieReactSpots, locale = 'en-US' }: RootProps) => {
|
|
|
105
106
|
});
|
|
106
107
|
|
|
107
108
|
return (
|
|
108
|
-
<CunninghamProvider
|
|
109
|
+
<CunninghamProvider
|
|
110
|
+
currentLocale={locale}
|
|
111
|
+
// TODO: remove customLocales and the cunningham-fr-FR-locale.json file once Cunningham is upgraded to v4+
|
|
112
|
+
customLocales={{ 'fr-FR': cunninghamFrFRLocale }}
|
|
113
|
+
>
|
|
109
114
|
<SessionProvider>
|
|
110
115
|
<HistoryProvider>
|
|
111
116
|
<Suspense fallback={<Spinner />}>{portals}</Suspense>
|