@raketa-cloud/admin 1.8.15 → 1.8.17
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/HonoAdapter.js +2 -0
- package/src/handlers/content_tasks/ContentTasksHandler.js +0 -17
- package/src/handlers/resources/TranslationsHandler.js +1 -5
- package/src/helpers/content_tasks_helper.js +25 -0
- package/src/tests/handlers/content_tasks/ContentTasksHandler.test/index.test.js +1 -82
- package/src/tests/helpers/content_tasks_helper.test.js +68 -0
- package/src/views/_shared/_content_tasks.ejs +110 -0
- package/src/views/content_tasks/index.ejs +1 -101
- package/src/views/resources/show.ejs +17 -0
- package/src/views/resources/translations/create_form.ejs +10 -5
- package/src/views/resources/translations/index.ejs +27 -5
package/package.json
CHANGED
package/src/HonoAdapter.js
CHANGED
|
@@ -13,6 +13,7 @@ import history from "./helpers/historyUtils";
|
|
|
13
13
|
import pagination from "./helpers/pagination_helper";
|
|
14
14
|
import sort_button from "./helpers/sort_button";
|
|
15
15
|
import utils from "./helpers/utils";
|
|
16
|
+
import * as content_tasks from "./helpers/content_tasks_helper";
|
|
16
17
|
|
|
17
18
|
function escapeHtml(string) {
|
|
18
19
|
const escape = {
|
|
@@ -168,6 +169,7 @@ export default class HonoAdapter {
|
|
|
168
169
|
sort_button,
|
|
169
170
|
pagination,
|
|
170
171
|
Inflector,
|
|
172
|
+
content_tasks,
|
|
171
173
|
};
|
|
172
174
|
}
|
|
173
175
|
|
|
@@ -1,28 +1,11 @@
|
|
|
1
|
-
function buildResourceUrl(resource, resourceRoutes) {
|
|
2
|
-
if (!resource) return "#";
|
|
3
|
-
|
|
4
|
-
const { locale, resourceType, id } = resource;
|
|
5
|
-
|
|
6
|
-
const route = resourceRoutes[resourceType];
|
|
7
|
-
|
|
8
|
-
return `/${locale}/${route}/${id}`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
1
|
async function index(ctx) {
|
|
12
2
|
const items = await ctx.client.contentTasks.list(ctx.query);
|
|
13
3
|
const editors = await ctx.client.contentTasks.editors();
|
|
14
|
-
const resourceRoutes = ctx.globalConfig.resourceRoutes;
|
|
15
|
-
|
|
16
|
-
if (!resourceRoutes) {
|
|
17
|
-
throw new Error("Missing resourceRoutes in the admin config!");
|
|
18
|
-
}
|
|
19
4
|
|
|
20
5
|
return {
|
|
21
6
|
assigns: {
|
|
22
7
|
items,
|
|
23
8
|
editors: editors.list || [],
|
|
24
|
-
buildResourceUrl: (resource) =>
|
|
25
|
-
buildResourceUrl(resource, resourceRoutes),
|
|
26
9
|
},
|
|
27
10
|
};
|
|
28
11
|
}
|
|
@@ -252,14 +252,10 @@ const create = async (ctx) => {
|
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
redirect: `/${originalLocale}/${resourceTypePlural}/${parent_id}/translations?translations=${translationIds.join(";")}`,
|
|
258
|
-
};
|
|
259
255
|
}
|
|
260
256
|
|
|
261
257
|
return {
|
|
262
|
-
redirect: `/${originalLocale}/${resourceTypePlural}/${parent_id}`,
|
|
258
|
+
redirect: `/${originalLocale}/${resourceTypePlural}/${parent_id}/translations?translations=${translationIds.join(";")}`,
|
|
263
259
|
};
|
|
264
260
|
};
|
|
265
261
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const WORKFLOWS_MAP = {
|
|
2
|
+
lokalise_to_ai_rt_hum: "Lokalise: AI translation & Human review",
|
|
3
|
+
lokalise_tt_hum: "Lokalise: Human translation",
|
|
4
|
+
lokalise_tt_ai: "Lokalise: AI translation",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const workflowsMap = (workflowType) => {
|
|
8
|
+
return WORKFLOWS_MAP[workflowType] || "Unknown workflow";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const buildResourceUrl = (resource, globalConfig) => {
|
|
12
|
+
if (!resource) return "#";
|
|
13
|
+
|
|
14
|
+
const resourceRoutes = globalConfig.resourceRoutes;
|
|
15
|
+
|
|
16
|
+
if (!resourceRoutes) {
|
|
17
|
+
throw new Error("Missing resourceRoutes in the admin config!");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { locale, resourceType, id } = resource;
|
|
21
|
+
|
|
22
|
+
const route = resourceRoutes[resourceType];
|
|
23
|
+
|
|
24
|
+
return `/${locale}/${route}/${id}`;
|
|
25
|
+
};
|
|
@@ -21,7 +21,7 @@ describe("ContentTasksHandler", () => {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
test("index - returns content tasks with editors
|
|
24
|
+
test("index - returns content tasks with editors", async () => {
|
|
25
25
|
const contentTasks = [
|
|
26
26
|
Factory.build("contentTask", {
|
|
27
27
|
id: "1",
|
|
@@ -76,86 +76,5 @@ describe("ContentTasksHandler", () => {
|
|
|
76
76
|
pagination: { currentPage: 1, pagesCount: 1 },
|
|
77
77
|
});
|
|
78
78
|
expect(result.assigns.editors).toEqual(editors);
|
|
79
|
-
expect(typeof result.assigns.buildResourceUrl).toBe("function");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("index - throws error when resourceRoutes is missing", async () => {
|
|
83
|
-
const ctx = buildCtx();
|
|
84
|
-
ctx.globalConfig = {}; // No resourceRoutes
|
|
85
|
-
|
|
86
|
-
const mockedList = jest
|
|
87
|
-
.spyOn(ctx.client.contentTasks, "list")
|
|
88
|
-
.mockImplementation(() => ({ list: [], pagination: {} }));
|
|
89
|
-
|
|
90
|
-
const mockedEditors = jest
|
|
91
|
-
.spyOn(ctx.client.contentTasks, "editors")
|
|
92
|
-
.mockImplementation(() => ({ list: [] }));
|
|
93
|
-
|
|
94
|
-
await expect(ContentTasksHandler.index(ctx)).rejects.toThrow(
|
|
95
|
-
"Missing resourceRoutes in the admin config!",
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("buildResourceUrl helper - builds correct URL for standard resource", async () => {
|
|
100
|
-
const ctx = buildCtx();
|
|
101
|
-
|
|
102
|
-
const mockedList = jest
|
|
103
|
-
.spyOn(ctx.client.contentTasks, "list")
|
|
104
|
-
.mockImplementation(() => ({ list: [], pagination: {} }));
|
|
105
|
-
|
|
106
|
-
const mockedEditors = jest
|
|
107
|
-
.spyOn(ctx.client.contentTasks, "editors")
|
|
108
|
-
.mockImplementation(() => ({ list: [] }));
|
|
109
|
-
|
|
110
|
-
const result = await ContentTasksHandler.index(ctx);
|
|
111
|
-
const { buildResourceUrl } = result.assigns;
|
|
112
|
-
|
|
113
|
-
const resource = {
|
|
114
|
-
id: "123",
|
|
115
|
-
locale: "en",
|
|
116
|
-
resourceType: "page",
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
expect(buildResourceUrl(resource)).toBe("/en/pages/123");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("buildResourceUrl helper - builds correct URL for custom route", async () => {
|
|
123
|
-
const ctx = buildCtx();
|
|
124
|
-
|
|
125
|
-
const mockedList = jest
|
|
126
|
-
.spyOn(ctx.client.contentTasks, "list")
|
|
127
|
-
.mockImplementation(() => ({ list: [], pagination: {} }));
|
|
128
|
-
|
|
129
|
-
const mockedEditors = jest
|
|
130
|
-
.spyOn(ctx.client.contentTasks, "editors")
|
|
131
|
-
.mockImplementation(() => ({ list: [] }));
|
|
132
|
-
|
|
133
|
-
const result = await ContentTasksHandler.index(ctx);
|
|
134
|
-
const { buildResourceUrl } = result.assigns;
|
|
135
|
-
|
|
136
|
-
const resource = {
|
|
137
|
-
id: "456",
|
|
138
|
-
locale: "de",
|
|
139
|
-
resourceType: "movie-type",
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
expect(buildResourceUrl(resource)).toBe("/de/movie-types/456");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("buildResourceUrl helper - returns # for null resource", async () => {
|
|
146
|
-
const ctx = buildCtx();
|
|
147
|
-
|
|
148
|
-
const mockedList = jest
|
|
149
|
-
.spyOn(ctx.client.contentTasks, "list")
|
|
150
|
-
.mockImplementation(() => ({ list: [], pagination: {} }));
|
|
151
|
-
|
|
152
|
-
const mockedEditors = jest
|
|
153
|
-
.spyOn(ctx.client.contentTasks, "editors")
|
|
154
|
-
.mockImplementation(() => ({ list: [] }));
|
|
155
|
-
|
|
156
|
-
const result = await ContentTasksHandler.index(ctx);
|
|
157
|
-
const { buildResourceUrl } = result.assigns;
|
|
158
|
-
|
|
159
|
-
expect(buildResourceUrl(null)).toBe("#");
|
|
160
79
|
});
|
|
161
80
|
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
workflowsMap,
|
|
3
|
+
buildResourceUrl,
|
|
4
|
+
} from "../../helpers/content_tasks_helper";
|
|
5
|
+
|
|
6
|
+
describe("content_tasks_helper", () => {
|
|
7
|
+
describe("workflowsMap", () => {
|
|
8
|
+
test("returns correct workflow name for known type", () => {
|
|
9
|
+
expect(workflowsMap("lokalise_to_ai_rt_hum")).toBe(
|
|
10
|
+
"Lokalise: AI translation & Human review",
|
|
11
|
+
);
|
|
12
|
+
expect(workflowsMap("lokalise_tt_hum")).toBe(
|
|
13
|
+
"Lokalise: Human translation",
|
|
14
|
+
);
|
|
15
|
+
expect(workflowsMap("lokalise_tt_ai")).toBe("Lokalise: AI translation");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns 'Unknown workflow' for unknown type", () => {
|
|
19
|
+
expect(workflowsMap("unknown")).toBe("Unknown workflow");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("buildResourceUrl", () => {
|
|
24
|
+
const globalConfig = {
|
|
25
|
+
resourceRoutes: {
|
|
26
|
+
page: "pages",
|
|
27
|
+
author: "authors",
|
|
28
|
+
article: "articles",
|
|
29
|
+
"movie-type": "movie-types",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
test("builds correct URL for standard resource", () => {
|
|
34
|
+
const resource = {
|
|
35
|
+
id: "123",
|
|
36
|
+
locale: "en",
|
|
37
|
+
resourceType: "page",
|
|
38
|
+
};
|
|
39
|
+
expect(buildResourceUrl(resource, globalConfig)).toBe("/en/pages/123");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("builds correct URL for custom route", () => {
|
|
43
|
+
const resource = {
|
|
44
|
+
id: "456",
|
|
45
|
+
locale: "de",
|
|
46
|
+
resourceType: "movie-type",
|
|
47
|
+
};
|
|
48
|
+
expect(buildResourceUrl(resource, globalConfig)).toBe(
|
|
49
|
+
"/de/movie-types/456",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("returns # for null resource", () => {
|
|
54
|
+
expect(buildResourceUrl(null, globalConfig)).toBe("#");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("throws error when resourceRoutes is missing", () => {
|
|
58
|
+
const resource = {
|
|
59
|
+
id: "123",
|
|
60
|
+
locale: "en",
|
|
61
|
+
resourceType: "page",
|
|
62
|
+
};
|
|
63
|
+
expect(() => buildResourceUrl(resource, {})).toThrow(
|
|
64
|
+
"Missing resourceRoutes in the admin config!",
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<% if (tasks.length > 0) { %>
|
|
2
|
+
<table class="data-grid alt">
|
|
3
|
+
<thead>
|
|
4
|
+
<tr>
|
|
5
|
+
<% if (showAllDetails) { %>
|
|
6
|
+
<th>Resource</th>
|
|
7
|
+
<th>Locale</th>
|
|
8
|
+
<% } %>
|
|
9
|
+
<th>Workflow</th>
|
|
10
|
+
<th>Started at</th>
|
|
11
|
+
<% if (showAllDetails) { %>
|
|
12
|
+
<th>Finished at</th>
|
|
13
|
+
<% } %>
|
|
14
|
+
<th>Status</th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
|
|
18
|
+
<tbody>
|
|
19
|
+
<% tasks.forEach(task => { %>
|
|
20
|
+
<tr>
|
|
21
|
+
<% if (showAllDetails) { %>
|
|
22
|
+
<td class="w-1/3">
|
|
23
|
+
<% if (task.resource) { %>
|
|
24
|
+
<a href="<%= content_tasks.buildResourceUrl(task.resource, _context.globalConfig) %>" target="_blank" class="btn-text">
|
|
25
|
+
<%= task.resource.title %>
|
|
26
|
+
</a>
|
|
27
|
+
|
|
28
|
+
<div class="body-3 text-raketa-foreground/50 mt-1">
|
|
29
|
+
<strong>Type:</strong> <%= Inflector.humanize(Inflector.capitalize(task.resource.resourceType)) %>
|
|
30
|
+
</div>
|
|
31
|
+
<% } else { %>
|
|
32
|
+
<span class="text-raketa-foreground/50">—</span>
|
|
33
|
+
<% } %>
|
|
34
|
+
</td>
|
|
35
|
+
|
|
36
|
+
<td>
|
|
37
|
+
<% if (task.resource && _context.locales[task.resource.locale]) { %>
|
|
38
|
+
<% const { name, flag } = _context.locales[task.resource.locale] %>
|
|
39
|
+
<img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="size-6 rounded-full border dark:border-neutral-700" />
|
|
40
|
+
<% } else { %>
|
|
41
|
+
<span class="text-raketa-foreground/50">—</span>
|
|
42
|
+
<% } %>
|
|
43
|
+
</td>
|
|
44
|
+
<% } %>
|
|
45
|
+
|
|
46
|
+
<td>
|
|
47
|
+
<span class="body-2">
|
|
48
|
+
<%= content_tasks.workflowsMap(task.workflow) %>
|
|
49
|
+
</span>
|
|
50
|
+
</td>
|
|
51
|
+
|
|
52
|
+
<td>
|
|
53
|
+
<div class="body-3 text-raketa-foreground/50">
|
|
54
|
+
<div class="flex items-center gap-1 mb-0.5">
|
|
55
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
|
|
56
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
57
|
+
</svg>
|
|
58
|
+
<time data-pretty-datetime datetime="<%= task.insertedAt %>"><%= utils.formattedDateTime(task.insertedAt) %></time>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="flex items-center gap-1" title="<%= task.operatorMeta?.name %>">
|
|
62
|
+
<% if (task.operatorMeta?.avatar) { %>
|
|
63
|
+
<img src="<%= task.operatorMeta?.avatar %>" class="size-4 rounded-full" alt="<%= task.operatorMeta?.name %>">
|
|
64
|
+
<% } else { %>
|
|
65
|
+
<div class="size-4 rounded-full"></div>
|
|
66
|
+
<% } %>
|
|
67
|
+
<%= task.operatorMeta?.name %>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</td>
|
|
71
|
+
|
|
72
|
+
<% if (showAllDetails) { %>
|
|
73
|
+
<td>
|
|
74
|
+
<% if (task.completedAt || task.failedAt) { %>
|
|
75
|
+
<div class="body-3 text-raketa-foreground/50">
|
|
76
|
+
<div class="flex items-center gap-1">
|
|
77
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
|
|
78
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
79
|
+
</svg>
|
|
80
|
+
<time data-pretty-datetime datetime="<%= task.completedAt || task.failedAt %>">
|
|
81
|
+
<%= utils.formattedDateTime(task.completedAt || task.failedAt) %>
|
|
82
|
+
</time>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
<% } else { %>
|
|
86
|
+
<span class="text-raketa-foreground/50">—</span>
|
|
87
|
+
<% } %>
|
|
88
|
+
</td>
|
|
89
|
+
<% } %>
|
|
90
|
+
|
|
91
|
+
<td>
|
|
92
|
+
<% if (task.completedAt) { %>
|
|
93
|
+
<span class="bg-emerald-100 chip text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300">
|
|
94
|
+
Completed
|
|
95
|
+
</span>
|
|
96
|
+
<% } else if (task.failedAt) { %>
|
|
97
|
+
<span class="bg-red-100 chip text-red-700 dark:bg-red-900/50 dark:text-red-300">
|
|
98
|
+
Failed
|
|
99
|
+
</span>
|
|
100
|
+
<% } else { %>
|
|
101
|
+
<span class="bg-blue-100 chip text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
|
102
|
+
In Progress
|
|
103
|
+
</span>
|
|
104
|
+
<% } %>
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
<% }) %>
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
<% } %>
|
|
@@ -11,107 +11,7 @@
|
|
|
11
11
|
</div>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
<thead>
|
|
16
|
-
<tr>
|
|
17
|
-
<th>Resource</th>
|
|
18
|
-
<th>Locale</th>
|
|
19
|
-
<th>Workflow</th>
|
|
20
|
-
<th>Started at</th>
|
|
21
|
-
<th>Finished at</th>
|
|
22
|
-
<th>Status</th>
|
|
23
|
-
</tr>
|
|
24
|
-
</thead>
|
|
25
|
-
|
|
26
|
-
<tbody>
|
|
27
|
-
<% if (items && items.list && items.list.length > 0) { %>
|
|
28
|
-
<% items.list.forEach(task => { %>
|
|
29
|
-
<tr>
|
|
30
|
-
<td class="w-1/3">
|
|
31
|
-
<% if (task.resource) { %>
|
|
32
|
-
<a href="<%= buildResourceUrl(task.resource) %>" target="_blank" class="btn-text">
|
|
33
|
-
<%= task.resource.title %>
|
|
34
|
-
</a>
|
|
35
|
-
<div class="body-3 text-raketa-foreground/50 mt-1">
|
|
36
|
-
<strong>Type:</strong> <%= Inflector.humanize(Inflector.capitalize(task.resource.resourceType)) %>
|
|
37
|
-
</div>
|
|
38
|
-
<% } else { %>
|
|
39
|
-
<span class="text-raketa-foreground/50">—</span>
|
|
40
|
-
<% } %>
|
|
41
|
-
</td>
|
|
42
|
-
|
|
43
|
-
<td>
|
|
44
|
-
<% if (task.resource && _context.locales[task.resource.locale]) { %>
|
|
45
|
-
<% const { name, flag } = _context.locales[task.resource.locale] %>
|
|
46
|
-
<img title="<%= name %>" src="<%= flag %>" alt="<%= name %>" class="size-6 rounded-full border dark:border-neutral-700" />
|
|
47
|
-
<% } else { %>
|
|
48
|
-
<span class="text-raketa-foreground/50">—</span>
|
|
49
|
-
<% } %>
|
|
50
|
-
</td>
|
|
51
|
-
|
|
52
|
-
<td>
|
|
53
|
-
<span class="body-2">
|
|
54
|
-
<%= Inflector.capitalize(task.workflow) %>
|
|
55
|
-
</span>
|
|
56
|
-
</td>
|
|
57
|
-
|
|
58
|
-
<td>
|
|
59
|
-
<div class="body-3 text-raketa-foreground/50">
|
|
60
|
-
<div class="flex items-center gap-1 mb-0.5">
|
|
61
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
|
|
62
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
63
|
-
</svg>
|
|
64
|
-
<time data-pretty-datetime datetime="<%= task.insertedAt %>"><%= utils.formattedDateTime(task.insertedAt) %></time>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div class="flex items-center gap-1" title="<%= task.operatorMeta?.name %>">
|
|
68
|
-
<% if (task.operatorMeta?.avatar) { %>
|
|
69
|
-
<img src="<%= task.operatorMeta?.avatar %>" class="size-4 rounded-full" alt="<%= task.operatorMeta?.name %>">
|
|
70
|
-
<% } else { %>
|
|
71
|
-
<div class="size-4 rounded-full"></div>
|
|
72
|
-
<% } %>
|
|
73
|
-
<%= task.operatorMeta?.name %>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
</td>
|
|
77
|
-
|
|
78
|
-
<td>
|
|
79
|
-
<% if (task.completedAt || task.failedAt) { %>
|
|
80
|
-
<div class="body-3 text-raketa-foreground/50">
|
|
81
|
-
<div class="flex items-center gap-1">
|
|
82
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4 text-raketa-foreground">
|
|
83
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
|
84
|
-
</svg>
|
|
85
|
-
<time data-pretty-datetime datetime="<%= task.completedAt || task.failedAt %>">
|
|
86
|
-
<%= utils.formattedDateTime(task.completedAt || task.failedAt) %>
|
|
87
|
-
</time>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
<% } else { %>
|
|
91
|
-
<span class="text-raketa-foreground/50">—</span>
|
|
92
|
-
<% } %>
|
|
93
|
-
</td>
|
|
94
|
-
|
|
95
|
-
<td>
|
|
96
|
-
<% if (task.completedAt) { %>
|
|
97
|
-
<span class="bg-emerald-100 chip text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300">
|
|
98
|
-
Completed
|
|
99
|
-
</span>
|
|
100
|
-
<% } else if (task.failedAt) { %>
|
|
101
|
-
<span class="bg-red-100 chip text-red-700 dark:bg-red-900/50 dark:text-red-300">
|
|
102
|
-
Failed
|
|
103
|
-
</span>
|
|
104
|
-
<% } else { %>
|
|
105
|
-
<span class="bg-blue-100 chip text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
|
106
|
-
In Progress
|
|
107
|
-
</span>
|
|
108
|
-
<% } %>
|
|
109
|
-
</td>
|
|
110
|
-
</tr>
|
|
111
|
-
<% }) %>
|
|
112
|
-
<% } %>
|
|
113
|
-
</tbody>
|
|
114
|
-
</table>
|
|
14
|
+
<%- render_partial('../_shared/_content_tasks.ejs', { tasks: items.list || [], showAllDetails: true }) %>
|
|
115
15
|
|
|
116
16
|
<% if (!items || !items.list || items.list.length === 0) { %>
|
|
117
17
|
<div class="empty-state">
|
|
@@ -166,6 +166,23 @@
|
|
|
166
166
|
</div>
|
|
167
167
|
<% } %>
|
|
168
168
|
|
|
169
|
+
<% if (utils.feature("contentTasks", _context) && resource.activeContentTask) { %>
|
|
170
|
+
<div class="mb-4">
|
|
171
|
+
<h4 class="flex items-center gap-2 pb-4 heading-3">
|
|
172
|
+
<svg class="size-4 text-raketa-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
173
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
174
|
+
</svg>
|
|
175
|
+
|
|
176
|
+
Active tasks
|
|
177
|
+
</h4>
|
|
178
|
+
|
|
179
|
+
<%- render_partial('../_shared/_content_tasks.ejs', {
|
|
180
|
+
tasks: [{...resource.activeContentTask, resource: resource}],
|
|
181
|
+
showAllDetails: false }
|
|
182
|
+
) %>
|
|
183
|
+
</div>
|
|
184
|
+
<% } %>
|
|
185
|
+
|
|
169
186
|
<%- render_partial('./_shared/_addons.ejs') %>
|
|
170
187
|
|
|
171
188
|
<div class="mb-4">
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
<div class="markdown">
|
|
83
83
|
Select one or multiple locales to initiate bulk translations. The process depends on your selected workflow:
|
|
84
84
|
<br />
|
|
85
|
-
<
|
|
85
|
+
<ul>
|
|
86
86
|
<li>
|
|
87
87
|
<strong>Default (Content Editors):</strong> A private copy and new revision are created immediately. The next screen provides quick links to translate settings and widgets so you can translate the content manually.
|
|
88
88
|
</li>
|
|
@@ -95,10 +95,15 @@
|
|
|
95
95
|
<li>
|
|
96
96
|
<strong>Human Translation (Lokalise):</strong> A task is sent to Lokalise where a human translator fully translates and reviews the content.
|
|
97
97
|
</li>
|
|
98
|
-
</
|
|
98
|
+
</ul>
|
|
99
99
|
|
|
100
100
|
<br />
|
|
101
|
-
<strong>
|
|
101
|
+
<strong>Notes:</strong> For all Lokalise workflows (<strong>AI, AI + Human</strong>, and <strong>Human</strong>):
|
|
102
|
+
<ul>
|
|
103
|
+
<li>Content is required to start a Lokalise task.</li>
|
|
104
|
+
<li>The private copy and new revision will be automatically created here in the CMS only after the external task is completed and returned.</li>
|
|
105
|
+
<li>You can track translation progress in the <a href="<%= `/${_context.locale}/content_tasks` %>" target="_blank">Content tasks</a> module.</li>
|
|
106
|
+
</ul>
|
|
102
107
|
</div>
|
|
103
108
|
</div>
|
|
104
109
|
|
|
@@ -114,11 +119,11 @@
|
|
|
114
119
|
</div>
|
|
115
120
|
|
|
116
121
|
<div class="markdown">
|
|
117
|
-
<
|
|
122
|
+
<ul>
|
|
118
123
|
<li>Select multiple locales to initiate bulk translations.</li>
|
|
119
124
|
<li>A private copy of the <%= Inflector.downcase(_context.resourceConfig.resourceType) %> will be automatically created for each selected language, with a new revision opened to facilitate the translation process.</li>
|
|
120
125
|
<li>On the next screen you'll see a list with quick links to transalte settings and widgets.</li>
|
|
121
|
-
</
|
|
126
|
+
</ul>
|
|
122
127
|
</div>
|
|
123
128
|
</div>
|
|
124
129
|
|
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
<%
|
|
9
|
+
<% const activeContentTasks = translations?.some(t => Boolean(t.activeContentTask)) %>
|
|
10
|
+
|
|
11
|
+
<% if (translations.length > 0) { %>
|
|
10
12
|
<table class="data-grid alt">
|
|
11
13
|
<thead>
|
|
12
14
|
<tr>
|
|
13
15
|
<th>Locale</th>
|
|
14
|
-
|
|
16
|
+
<% if (activeContentTasks) { %>
|
|
17
|
+
<th>Status</th>
|
|
18
|
+
<% } else { %>
|
|
19
|
+
<th> </th>
|
|
20
|
+
<% } %>
|
|
15
21
|
</tr>
|
|
16
22
|
</thead>
|
|
17
23
|
<tbody>
|
|
@@ -25,9 +31,25 @@
|
|
|
25
31
|
</div>
|
|
26
32
|
</td>
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
<% if (activeContentTasks) { %>
|
|
35
|
+
<td class="whitespace-nowrap">
|
|
36
|
+
<a href="<%= `/${_context.locale}/content_tasks`%>" target="blank">
|
|
37
|
+
<%= content_tasks.workflowsMap(translation.activeContentTask.workflow) %>
|
|
38
|
+
|
|
39
|
+
<% if (translation.activeContentTask.completedAt) { %>
|
|
40
|
+
completed
|
|
41
|
+
<% } else if (translation.activeContentTask.failedAt) { %>
|
|
42
|
+
failed
|
|
43
|
+
<% } else { %>
|
|
44
|
+
in Progress
|
|
45
|
+
<% } %>
|
|
46
|
+
</a>
|
|
47
|
+
</td>
|
|
48
|
+
<% } else { %>
|
|
49
|
+
<td class="whitespace-nowrap">
|
|
50
|
+
<%- render_partial('./_translation_toolbar.ejs', { translation }) %>
|
|
51
|
+
</td>
|
|
52
|
+
<% } %>
|
|
31
53
|
</tr>
|
|
32
54
|
<% }) %>
|
|
33
55
|
</tbody>
|