@raketa-cloud/admin 0.0.69 → 0.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/handlers/articles/TranslationsHandler.js +129 -44
- package/src/handlers/events/TranslationsHandler.js +132 -49
- package/src/tests/handlers/articles/TranslationHandler.test/create.test.js +291 -0
- package/src/tests/handlers/articles/TranslationHandler.test/create_form.test.js +71 -0
- package/src/tests/handlers/articles/TranslationHandler.test/index.test.js +91 -0
- package/src/tests/handlers/events/TranslationHandler.test/create.test.js +297 -0
- package/src/tests/handlers/events/TranslationHandler.test/create_form.test.js +71 -0
- package/src/tests/handlers/events/TranslationHandler.test/index.test.js +91 -0
- package/src/tests/handlers/pages/TranslationHandler.test/create.test.js +1 -1
- package/src/views/articles/_shared/_history_toolbar.ejs +2 -2
- package/src/views/articles/show.ejs +15 -4
- package/src/views/articles/translations/_translation_toolbar.ejs +18 -0
- package/src/views/articles/translations/create_form.ejs +68 -4
- package/src/views/articles/translations/index.ejs +39 -0
- package/src/views/events/_shared/_history_toolbar.ejs +2 -2
- package/src/views/events/show.ejs +15 -4
- package/src/views/events/translations/_translation_toolbar.ejs +18 -0
- package/src/views/events/translations/create_form.ejs +68 -4
- package/src/views/events/translations/index.ejs +39 -0
- package/src/views/pages/_shared/_history_toolbar.ejs +2 -2
- package/src/views/pages/show.ejs +2 -4
- package/src/views/pages/translations/_translation_toolbar.ejs +18 -0
- package/src/views/pages/translations/index.ejs +2 -19
- package/src/views/articles/translations/_form.ejs +0 -31
- package/src/views/events/translations/_form.ejs +0 -33
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import EventRevisions from "../../../../cloud/EventRevisions";
|
|
2
|
+
import Events from "../../../../cloud/Events";
|
|
3
|
+
import TranslationsHandler from "../../../../handlers/events/TranslationsHandler";
|
|
4
|
+
import Factory from "../../../support/factory";
|
|
5
|
+
|
|
6
|
+
function buildCtx(params, event) {
|
|
7
|
+
return {
|
|
8
|
+
client: {
|
|
9
|
+
events: new Events(() => ({}), "token"),
|
|
10
|
+
eventRevisions: new EventRevisions(() => ({}), "token"),
|
|
11
|
+
},
|
|
12
|
+
params: { parent_id: event.id },
|
|
13
|
+
resourceParams: {
|
|
14
|
+
resource: params,
|
|
15
|
+
},
|
|
16
|
+
locale: "en",
|
|
17
|
+
resourceConfig: {
|
|
18
|
+
eventType: "event",
|
|
19
|
+
eventTypePlural: "events",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("Events Translation Handler", () => {
|
|
25
|
+
test("create fails with translation errors", async () => {
|
|
26
|
+
const event = Factory.build("event", {
|
|
27
|
+
translations: {},
|
|
28
|
+
categories: [{ id: "cat1" }],
|
|
29
|
+
});
|
|
30
|
+
const params = {
|
|
31
|
+
languages: ["fr"],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ctx = buildCtx(params, event);
|
|
35
|
+
|
|
36
|
+
const mockFind = jest
|
|
37
|
+
.spyOn(ctx.client.events, "find")
|
|
38
|
+
.mockImplementation(() => event);
|
|
39
|
+
|
|
40
|
+
const translationErrors = [{ key: "title", message: "cannot be blank" }];
|
|
41
|
+
const mockTranslate = jest
|
|
42
|
+
.spyOn(ctx.client.events, "translate")
|
|
43
|
+
.mockImplementation(() => ({
|
|
44
|
+
event: null,
|
|
45
|
+
errors: translationErrors,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
const mockFormatErrors = jest
|
|
49
|
+
.spyOn(ctx.client.events, "formatErrors")
|
|
50
|
+
.mockImplementation(() => ({ title: "cannot be blank" }));
|
|
51
|
+
|
|
52
|
+
const result = await TranslationsHandler.create(ctx);
|
|
53
|
+
|
|
54
|
+
expect(mockFind).toHaveBeenCalledWith(event.id, "en");
|
|
55
|
+
expect(mockTranslate).toHaveBeenCalled();
|
|
56
|
+
expect(mockFormatErrors).toHaveBeenCalledWith(translationErrors);
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual({
|
|
59
|
+
assigns: {
|
|
60
|
+
resource: {
|
|
61
|
+
title: event.title,
|
|
62
|
+
tags: [],
|
|
63
|
+
visibility: "private",
|
|
64
|
+
slug: event.slug,
|
|
65
|
+
settings: event.settings,
|
|
66
|
+
widgets: event.widgets,
|
|
67
|
+
categories: ["cat1"],
|
|
68
|
+
publishAt: event.publishAt,
|
|
69
|
+
startsAt: event.startsAt,
|
|
70
|
+
endsAt: event.endsAt,
|
|
71
|
+
translations: {},
|
|
72
|
+
languages: ["fr"],
|
|
73
|
+
},
|
|
74
|
+
blueprint: event,
|
|
75
|
+
targetLocale: "fr",
|
|
76
|
+
errors: { resource: { title: "cannot be blank" } },
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("create successfully multiple translations with already one is existing", async () => {
|
|
82
|
+
const event = Factory.build("event", {
|
|
83
|
+
translations: { de: "de-id" },
|
|
84
|
+
categories: [{ id: "cat1" }],
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const params = {
|
|
88
|
+
languages: ["fr", "es", "de"],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const ctx = buildCtx(params, event);
|
|
92
|
+
|
|
93
|
+
const mockFind = jest
|
|
94
|
+
.spyOn(ctx.client.events, "find")
|
|
95
|
+
.mockImplementation(() => event);
|
|
96
|
+
|
|
97
|
+
const mockTranslate = jest
|
|
98
|
+
.spyOn(ctx.client.events, "translate")
|
|
99
|
+
.mockImplementation((parentId, locale) => ({
|
|
100
|
+
event: { id: `translated-event-${locale}` },
|
|
101
|
+
errors: null,
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
const mockCreateRevision = jest
|
|
105
|
+
.spyOn(ctx.client.eventRevisions, "create")
|
|
106
|
+
.mockImplementation(() => ({ errors: null }));
|
|
107
|
+
|
|
108
|
+
const result = await TranslationsHandler.create(ctx);
|
|
109
|
+
|
|
110
|
+
expect(mockFind).toHaveBeenCalledWith(event.id, "en");
|
|
111
|
+
|
|
112
|
+
expect(mockTranslate).toHaveBeenCalledTimes(2);
|
|
113
|
+
expect(mockTranslate).toHaveBeenCalledWith(
|
|
114
|
+
event.id,
|
|
115
|
+
"fr",
|
|
116
|
+
expect.objectContaining({
|
|
117
|
+
title: event.title,
|
|
118
|
+
tags: [],
|
|
119
|
+
visibility: "private",
|
|
120
|
+
slug: event.slug,
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
expect(mockTranslate).toHaveBeenCalledWith(
|
|
124
|
+
event.id,
|
|
125
|
+
"es",
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
title: event.title,
|
|
128
|
+
tags: [],
|
|
129
|
+
visibility: "private",
|
|
130
|
+
slug: event.slug,
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(mockCreateRevision).toHaveBeenCalledTimes(2);
|
|
135
|
+
expect(mockCreateRevision).toHaveBeenCalledWith(
|
|
136
|
+
"translated-event-fr",
|
|
137
|
+
"fr"
|
|
138
|
+
);
|
|
139
|
+
expect(mockCreateRevision).toHaveBeenCalledWith(
|
|
140
|
+
"translated-event-es",
|
|
141
|
+
"es"
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(result).toEqual({
|
|
145
|
+
redirect: `/en/events/${event.id}/translations?translations=fr:translated-event-fr;es:translated-event-es`,
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("create translations fails if any translation fails", async () => {
|
|
150
|
+
const event = Factory.build("event", {
|
|
151
|
+
translations: {},
|
|
152
|
+
categories: [{ id: "some-category-uno" }],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const params = {
|
|
156
|
+
languages: ["fr", "es"],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const ctx = buildCtx(params, event);
|
|
160
|
+
|
|
161
|
+
const mockFind = jest
|
|
162
|
+
.spyOn(ctx.client.events, "find")
|
|
163
|
+
.mockImplementation(() => event);
|
|
164
|
+
|
|
165
|
+
const translationResponses = {
|
|
166
|
+
fr: { event: { id: "translated-event-fr" }, errors: null },
|
|
167
|
+
es: {
|
|
168
|
+
event: null,
|
|
169
|
+
errors: [{ key: "title", message: "invalid for spanish" }],
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const mockTranslate = jest
|
|
174
|
+
.spyOn(ctx.client.events, "translate")
|
|
175
|
+
.mockImplementation((parentId, locale) => translationResponses[locale]);
|
|
176
|
+
|
|
177
|
+
const mockFormatErrors = jest
|
|
178
|
+
.spyOn(ctx.client.events, "formatErrors")
|
|
179
|
+
.mockImplementation(() => ({
|
|
180
|
+
title: "invalid for spanish",
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
const mockCreateRevision = jest
|
|
184
|
+
.spyOn(ctx.client.eventRevisions, "create")
|
|
185
|
+
.mockImplementation(() => ({ errors: null }));
|
|
186
|
+
|
|
187
|
+
const result = await TranslationsHandler.create(ctx);
|
|
188
|
+
|
|
189
|
+
expect(mockFind).toHaveBeenCalledWith(event.id, "en");
|
|
190
|
+
|
|
191
|
+
expect(mockTranslate).toHaveBeenCalledWith(
|
|
192
|
+
event.id,
|
|
193
|
+
"fr",
|
|
194
|
+
expect.objectContaining({
|
|
195
|
+
title: event.title,
|
|
196
|
+
tags: [],
|
|
197
|
+
visibility: "private",
|
|
198
|
+
slug: event.slug,
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
expect(mockTranslate).toHaveBeenCalledWith(
|
|
203
|
+
event.id,
|
|
204
|
+
"es",
|
|
205
|
+
expect.objectContaining({
|
|
206
|
+
title: event.title,
|
|
207
|
+
tags: [],
|
|
208
|
+
visibility: "private",
|
|
209
|
+
slug: event.slug,
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(result).toEqual({
|
|
214
|
+
assigns: {
|
|
215
|
+
resource: {
|
|
216
|
+
title: event.title,
|
|
217
|
+
tags: [],
|
|
218
|
+
visibility: "private",
|
|
219
|
+
slug: event.slug,
|
|
220
|
+
settings: event.settings,
|
|
221
|
+
widgets: event.widgets,
|
|
222
|
+
categories: ["some-category-uno"],
|
|
223
|
+
publishAt: event.publishAt,
|
|
224
|
+
startsAt: event.startsAt,
|
|
225
|
+
endsAt: event.endsAt,
|
|
226
|
+
translations: {},
|
|
227
|
+
languages: ["fr", "es"],
|
|
228
|
+
},
|
|
229
|
+
blueprint: event,
|
|
230
|
+
targetLocale: "es",
|
|
231
|
+
errors: { resource: { title: "invalid for spanish" } },
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("create translation fails with revision errors", async () => {
|
|
237
|
+
const event = Factory.build("event", {
|
|
238
|
+
translations: {},
|
|
239
|
+
categories: [{ id: "cat1" }],
|
|
240
|
+
});
|
|
241
|
+
const params = {
|
|
242
|
+
languages: ["fr"],
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const ctx = buildCtx(params, event);
|
|
246
|
+
|
|
247
|
+
const mockFind = jest
|
|
248
|
+
.spyOn(ctx.client.events, "find")
|
|
249
|
+
.mockImplementation(() => event);
|
|
250
|
+
|
|
251
|
+
const mockTranslate = jest
|
|
252
|
+
.spyOn(ctx.client.events, "translate")
|
|
253
|
+
.mockImplementation(() => ({
|
|
254
|
+
event: { id: "translated-event-id" },
|
|
255
|
+
errors: null,
|
|
256
|
+
}));
|
|
257
|
+
|
|
258
|
+
const revisionErrors = [{ key: "base", message: "something went wrong" }];
|
|
259
|
+
const mockCreateRevision = jest
|
|
260
|
+
.spyOn(ctx.client.eventRevisions, "create")
|
|
261
|
+
.mockImplementation(() => ({ errors: revisionErrors }));
|
|
262
|
+
|
|
263
|
+
const mockFormatErrors = jest
|
|
264
|
+
.spyOn(ctx.client.events, "formatErrors")
|
|
265
|
+
.mockImplementation(() => ({ base: "something went wrong" }));
|
|
266
|
+
|
|
267
|
+
const result = await TranslationsHandler.create(ctx);
|
|
268
|
+
|
|
269
|
+
expect(mockCreateRevision).toHaveBeenCalledWith(
|
|
270
|
+
"translated-event-id",
|
|
271
|
+
"fr"
|
|
272
|
+
);
|
|
273
|
+
expect(mockFormatErrors).toHaveBeenCalledWith(revisionErrors);
|
|
274
|
+
|
|
275
|
+
expect(result).toEqual({
|
|
276
|
+
assigns: {
|
|
277
|
+
resource: {
|
|
278
|
+
title: event.title,
|
|
279
|
+
tags: [],
|
|
280
|
+
visibility: "private",
|
|
281
|
+
slug: event.slug,
|
|
282
|
+
settings: event.settings,
|
|
283
|
+
widgets: event.widgets,
|
|
284
|
+
categories: ["cat1"],
|
|
285
|
+
publishAt: event.publishAt,
|
|
286
|
+
startsAt: event.startsAt,
|
|
287
|
+
endsAt: event.endsAt,
|
|
288
|
+
translations: {},
|
|
289
|
+
languages: ["fr"],
|
|
290
|
+
},
|
|
291
|
+
blueprint: event,
|
|
292
|
+
targetLocale: "fr",
|
|
293
|
+
errors: { resource: { base: "something went wrong" } },
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import Events from "../../../../cloud/Events";
|
|
2
|
+
import TranslationsHandler from "../../../../handlers/events/TranslationsHandler";
|
|
3
|
+
import Factory from "../../../support/factory";
|
|
4
|
+
|
|
5
|
+
function buildCtx(event) {
|
|
6
|
+
return {
|
|
7
|
+
client: {
|
|
8
|
+
events: new Events(() => ({}), "token"),
|
|
9
|
+
},
|
|
10
|
+
params: { parent_id: event.id },
|
|
11
|
+
query: { target_locale: "" },
|
|
12
|
+
locale: "en",
|
|
13
|
+
resourceConfig: {
|
|
14
|
+
eventType: "event",
|
|
15
|
+
eventTypePlural: "events",
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("Event translation form", () => {
|
|
21
|
+
test("create_form when a event exists with target locale", async () => {
|
|
22
|
+
const event = Factory.build("event");
|
|
23
|
+
const ctx = buildCtx(event);
|
|
24
|
+
ctx.query.target_locale = "fr";
|
|
25
|
+
|
|
26
|
+
const mockFindEvent = jest
|
|
27
|
+
.spyOn(ctx.client.events, "find")
|
|
28
|
+
.mockImplementation(() => event);
|
|
29
|
+
|
|
30
|
+
const result = await TranslationsHandler.create_form(ctx);
|
|
31
|
+
|
|
32
|
+
expect(mockFindEvent).toHaveBeenCalledWith(event.id, "en");
|
|
33
|
+
|
|
34
|
+
const expectedResource = structuredClone(event);
|
|
35
|
+
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
assigns: {
|
|
38
|
+
resource: expectedResource,
|
|
39
|
+
blueprint: event,
|
|
40
|
+
targetLocale: "fr",
|
|
41
|
+
errors: {},
|
|
42
|
+
_translations: [],
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("create_form when a event exists without target locale", async () => {
|
|
48
|
+
const event = Factory.build("event");
|
|
49
|
+
const ctx = buildCtx(event);
|
|
50
|
+
|
|
51
|
+
const mockFindEvent = jest
|
|
52
|
+
.spyOn(ctx.client.events, "find")
|
|
53
|
+
.mockImplementation(() => event);
|
|
54
|
+
|
|
55
|
+
const result = await TranslationsHandler.create_form(ctx);
|
|
56
|
+
|
|
57
|
+
expect(mockFindEvent).toHaveBeenCalledWith(event.id, "en");
|
|
58
|
+
|
|
59
|
+
const expectedResource = structuredClone(event);
|
|
60
|
+
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
assigns: {
|
|
63
|
+
resource: expectedResource,
|
|
64
|
+
blueprint: event,
|
|
65
|
+
targetLocale: "",
|
|
66
|
+
errors: {},
|
|
67
|
+
_translations: [],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import EventRevisions from "../../../../cloud/EventRevisions";
|
|
2
|
+
import Events from "../../../../cloud/Events";
|
|
3
|
+
import TranslationsHandler from "../../../../handlers/events/TranslationsHandler";
|
|
4
|
+
import Factory from "../../../support/factory";
|
|
5
|
+
|
|
6
|
+
function buildCtx() {
|
|
7
|
+
const originalEvent = Factory.build("event", {
|
|
8
|
+
id: "original-event-id",
|
|
9
|
+
title: "Test Event Title",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
client: {
|
|
14
|
+
events: new Events(() => ({}), "token"),
|
|
15
|
+
eventRevisions: new EventRevisions(() => ({}), "token"),
|
|
16
|
+
},
|
|
17
|
+
params: { parent_id: originalEvent.id },
|
|
18
|
+
query: { translations: "fr:fr-event-id;es:es-event-id" },
|
|
19
|
+
locale: "en",
|
|
20
|
+
resourceConfig: {
|
|
21
|
+
eventType: "event",
|
|
22
|
+
eventTypePlural: "events",
|
|
23
|
+
},
|
|
24
|
+
originalEvent,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("Translations Handler", () => {
|
|
29
|
+
test("index", async () => {
|
|
30
|
+
const frEvent = Factory.build("event", { id: "fr-event-id" });
|
|
31
|
+
const esEvent = Factory.build("event", { id: "es-event-id" });
|
|
32
|
+
const esRevision = Factory.build("event_revision", {
|
|
33
|
+
id: "es-revision-id",
|
|
34
|
+
});
|
|
35
|
+
const frRevision = Factory.build("event_revision", {
|
|
36
|
+
id: "fr-revision-id",
|
|
37
|
+
});
|
|
38
|
+
const ctx = buildCtx();
|
|
39
|
+
|
|
40
|
+
const mockedFind = jest
|
|
41
|
+
.spyOn(ctx.client.events, "find")
|
|
42
|
+
.mockImplementation((id, locale) => {
|
|
43
|
+
if (locale === "fr") return frEvent;
|
|
44
|
+
if (locale === "es") return esEvent;
|
|
45
|
+
if (id === "original-event-id") return ctx.originalEvent;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const mockedRevisions = jest
|
|
49
|
+
.spyOn(ctx.client.eventRevisions, "all")
|
|
50
|
+
.mockImplementation((id, locale) => ({
|
|
51
|
+
list: locale === "fr" ? [frRevision] : [esRevision],
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
const mockedList = jest
|
|
55
|
+
.spyOn(ctx.client.events, "list")
|
|
56
|
+
.mockImplementation(() => ({}));
|
|
57
|
+
|
|
58
|
+
const result = await TranslationsHandler.index(ctx);
|
|
59
|
+
|
|
60
|
+
expect(mockedFind).toHaveBeenCalledWith("fr-event-id", "fr");
|
|
61
|
+
expect(mockedFind).toHaveBeenCalledWith("es-event-id", "es");
|
|
62
|
+
expect(mockedRevisions).toHaveBeenCalledWith("fr-event-id", "fr");
|
|
63
|
+
expect(mockedRevisions).toHaveBeenCalledWith("es-event-id", "es");
|
|
64
|
+
expect(mockedList).toHaveBeenCalledWith(
|
|
65
|
+
{ translations: "fr:fr-event-id;es:es-event-id" },
|
|
66
|
+
"en",
|
|
67
|
+
"event"
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual({
|
|
71
|
+
assigns: {
|
|
72
|
+
items: {},
|
|
73
|
+
visibilityOptions: ctx.client.events.visibilityOptions(),
|
|
74
|
+
translations: [
|
|
75
|
+
{
|
|
76
|
+
...frEvent,
|
|
77
|
+
translationLocale: "fr",
|
|
78
|
+
revisionId: "fr-revision-id",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
...esEvent,
|
|
82
|
+
translationLocale: "es",
|
|
83
|
+
revisionId: "es-revision-id",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
parent_id: "original-event-id",
|
|
87
|
+
eventTitle: "Test Event Title",
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -21,7 +21,7 @@ function buildCtx(params, page) {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
describe("Translation Handler", () => {
|
|
24
|
+
describe("Pages Translation Handler", () => {
|
|
25
25
|
test("create fails with translation errors", async () => {
|
|
26
26
|
const page = Factory.build("page", {
|
|
27
27
|
translations: {},
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</form>
|
|
13
13
|
<% } %>
|
|
14
14
|
|
|
15
|
-
<a href="#" class="btn-text
|
|
15
|
+
<a href="#" class="btn-text hidden">
|
|
16
16
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
|
17
17
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
|
|
18
18
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
|
@@ -20,4 +20,4 @@
|
|
|
20
20
|
|
|
21
21
|
Preview
|
|
22
22
|
</a>
|
|
23
|
-
</div>
|
|
23
|
+
</div>
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
</div>
|
|
6
6
|
|
|
7
7
|
<div class="flex flex-wrap items-center gap-1 mb-6 -my-4">
|
|
8
|
+
<%
|
|
9
|
+
const availableLocales = Object.keys(_context.locales);
|
|
10
|
+
const totalPossibleTranslations = availableLocales.length - 1;
|
|
11
|
+
const existingTranslationsCount = Object.keys(resource.translations).length;
|
|
12
|
+
const canBeTranslated = existingTranslationsCount < totalPossibleTranslations;
|
|
13
|
+
%>
|
|
14
|
+
|
|
8
15
|
<% Object.keys(_context.locales).forEach(key => { %>
|
|
9
16
|
<% const { name, _code, flag} = _context.locales[key] %>
|
|
10
17
|
<% if (key === _context.locale) { %>
|
|
@@ -19,6 +26,12 @@
|
|
|
19
26
|
</a>
|
|
20
27
|
<% } %>
|
|
21
28
|
<% }); %>
|
|
29
|
+
|
|
30
|
+
<% if (canBeTranslated) { %>
|
|
31
|
+
<a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.id}/translations/new` %>" class="btn-text ml-2">
|
|
32
|
+
Translate
|
|
33
|
+
</a>
|
|
34
|
+
<% } %>
|
|
22
35
|
</div>
|
|
23
36
|
|
|
24
37
|
<% if (revisions.length) { %>
|
|
@@ -55,9 +68,7 @@
|
|
|
55
68
|
</div>
|
|
56
69
|
<% } %>
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
<%= revision.title %>
|
|
60
|
-
</a>
|
|
71
|
+
<%= revision.title %>
|
|
61
72
|
</div>
|
|
62
73
|
</td>
|
|
63
74
|
|
|
@@ -155,4 +166,4 @@
|
|
|
155
166
|
</tbody>
|
|
156
167
|
</table>
|
|
157
168
|
</div>
|
|
158
|
-
</div>
|
|
169
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<div class="actions-toolbar justify-end">
|
|
2
|
+
<a href="/remote-login?redirect_to=/<%= translation.translationLocale %>/admin/<%= _context.resourceConfig.articleType %>-revisions/<%= translation.revisionId %>/builder" target="_blank" class="btn-text has-icon">
|
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
|
4
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" />
|
|
5
|
+
</svg>
|
|
6
|
+
|
|
7
|
+
Update Content
|
|
8
|
+
</a>
|
|
9
|
+
|
|
10
|
+
<a href="<%= `/${translation.translationLocale}/article_revisions/${translation.revisionId}/general/edit` %>" class="btn-text has-icon">
|
|
11
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
|
12
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894Z" />
|
|
13
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
|
14
|
+
</svg>
|
|
15
|
+
|
|
16
|
+
Update Settings
|
|
17
|
+
</a>
|
|
18
|
+
</div>
|
|
@@ -1,9 +1,73 @@
|
|
|
1
1
|
<div class="page-wrapper">
|
|
2
|
-
<div class="page-title">
|
|
3
|
-
<h1 class="heading-1">
|
|
2
|
+
<div class="page-title flex flex-col items-start">
|
|
3
|
+
<h1 class="heading-1">
|
|
4
|
+
<span class="opacity-50">Translate from <%= _context.locales[_context.locale].name %> / </span>
|
|
5
|
+
<%= blueprint.title %>
|
|
6
|
+
</h1>
|
|
4
7
|
</div>
|
|
5
8
|
|
|
6
9
|
<div class="grid gap-4 md:grid-cols-3">
|
|
7
|
-
|
|
10
|
+
<% form_for({ resource }, errors, (f) => { %>
|
|
11
|
+
<form action="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${blueprint.id}/translations` %>" method="post" class="form">
|
|
12
|
+
<h6>Available languages:</h6>
|
|
13
|
+
<% if (targetLocale) { %>
|
|
14
|
+
<input type="hidden" name="resource[languages][]" value="<%= targetLocale %>">
|
|
15
|
+
<% } %>
|
|
16
|
+
<div class="checkbox-group !max-h-fit mb-3">
|
|
17
|
+
<% Object.entries(_context.locales).forEach(([key, locale]) => {
|
|
18
|
+
const isExistingTranslation = resource.translations[key];
|
|
19
|
+
const isCurrentLocale = key === _context.locale;
|
|
20
|
+
const wasSelected = Array.isArray(resource.languages) && resource.languages.includes(key);
|
|
21
|
+
const isChecked = isExistingTranslation || isCurrentLocale || key === targetLocale || wasSelected;
|
|
22
|
+
const isDisabled = isExistingTranslation || isCurrentLocale || key === targetLocale;
|
|
23
|
+
%>
|
|
24
|
+
<% if(!isCurrentLocale) { %>
|
|
25
|
+
<label class="<%= isDisabled && key !== targetLocale ? 'opacity-50' : '' %>">
|
|
26
|
+
<input type="<%= isCurrentLocale ? 'hidden' : 'checkbox' %>" name="resource[languages][]" value="<%= key %>" <%= isChecked ? 'checked' : '' %> <%= isDisabled ? 'disabled' : '' %>>
|
|
27
|
+
<%= locale.name %>
|
|
28
|
+
</label>
|
|
29
|
+
<% } %>
|
|
30
|
+
<% }) %>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<% if (errors?.resource && targetLocale) { %>
|
|
34
|
+
<div>
|
|
35
|
+
<p class="text-sm text-red-600 font-medium mb-1">
|
|
36
|
+
Failed to create <%= _context.locales[targetLocale].name %> translation.
|
|
37
|
+
</p>
|
|
38
|
+
<% Object.entries(errors.resource).forEach(([errorKey, error]) => { %>
|
|
39
|
+
<p class="text-sm text-red-600 font-medium mb-3"> Reason is <%= errorKey %>: <%= error %></p>
|
|
40
|
+
<% }) %>
|
|
41
|
+
</div>
|
|
42
|
+
<% } %>
|
|
43
|
+
|
|
44
|
+
<div class="form-actions">
|
|
45
|
+
<div class="btn-toolbar">
|
|
46
|
+
<%- f.submit('Initiate translations', { class: 'btn-primary' }) %>
|
|
47
|
+
<a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${blueprint.id}` %>" class="btn-secondary">Cancel</a>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</form>
|
|
51
|
+
<% }) %>
|
|
52
|
+
|
|
53
|
+
<div>
|
|
54
|
+
<div class="help">
|
|
55
|
+
<div class="flex items-center gap-1 mb-2">
|
|
56
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
|
57
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
|
58
|
+
</svg>
|
|
59
|
+
|
|
60
|
+
<h3 class="heading-3">Translating articles</h3>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="markdown">
|
|
64
|
+
<ol>
|
|
65
|
+
<li>Select multiple locales to initiate bulk translations.</li>
|
|
66
|
+
<li>A private copy of the article will be automatically created for each selected language, with a new revision opened to facilitate the translation process.</li>
|
|
67
|
+
<li>On the next screen you'll see a list with quick links to transalte settings and widgets</li>
|
|
68
|
+
</ol>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
8
72
|
</div>
|
|
9
|
-
</div>
|
|
73
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<div class="page-wrapper">
|
|
2
|
+
<div class="page-title">
|
|
3
|
+
<div>
|
|
4
|
+
<h1 class="heading-1">Translations initiated</h1>
|
|
5
|
+
<span class="body-2 opacity-50"><%= articleTitle %></span>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<% if (translations && translations.length > 0) { %>
|
|
10
|
+
<table class="data-grid alt">
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<th>Locale</th>
|
|
14
|
+
<th> </th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
<tbody>
|
|
18
|
+
<% translations.forEach(translation => { %>
|
|
19
|
+
<tr class="border-b border-mono-25">
|
|
20
|
+
<td class="py-4 pl-2 w-full">
|
|
21
|
+
<div class="flex gap-1 items-center">
|
|
22
|
+
<a href="<%= `/${translation.translationLocale}/${_context.resourceConfig.articleTypePlural}/${translation.id}` %>" class="btn-text">
|
|
23
|
+
<%= _context.locales[translation.translationLocale].name %>
|
|
24
|
+
</a>
|
|
25
|
+
</div>
|
|
26
|
+
</td>
|
|
27
|
+
|
|
28
|
+
<td class="whitespace-nowrap">
|
|
29
|
+
<%- render_partial('./_translation_toolbar.ejs', { translation }) %>
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
<% }) %>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
<% } %>
|
|
36
|
+
<a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${parent_id}` %>" class="btn-primary mt-8">
|
|
37
|
+
Go to original
|
|
38
|
+
</a>
|
|
39
|
+
</div>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
</form>
|
|
13
13
|
<% } %>
|
|
14
14
|
|
|
15
|
-
<a href="#" class="btn-text
|
|
15
|
+
<a href="#" class="btn-text hidden">
|
|
16
16
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
|
17
17
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
|
|
18
18
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
|
@@ -20,4 +20,4 @@
|
|
|
20
20
|
|
|
21
21
|
Preview
|
|
22
22
|
</a>
|
|
23
|
-
</div>
|
|
23
|
+
</div>
|