@raketa-cloud/admin 1.8.16 → 1.8.18

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raketa-cloud/admin",
3
- "version": "1.8.16",
3
+ "version": "1.8.18",
4
4
  "main": "src/index.js",
5
5
  "files": [
6
6
  "src/",
@@ -31,7 +31,7 @@
31
31
  "@hono/node-server": "2.0.12",
32
32
  "ejs": "^6.0.1",
33
33
  "graphql-request": "7.4.0",
34
- "hono": "4.12.32"
34
+ "hono": "4.12.34"
35
35
  },
36
36
  "overrides": {
37
37
  "error-ex": "1.3.4"
@@ -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,36 +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
- const WORKFLOWS_MAP = {
12
- lokalise_to_ai_rt_hum: "Lokalise: AI translation & Human review",
13
- lokalise_tt_hum: "Lokalise: Human translation",
14
- lokalise_tt_ai: "Lokalise: AI translation",
15
- };
16
-
17
1
  async function index(ctx) {
18
2
  const items = await ctx.client.contentTasks.list(ctx.query);
19
3
  const editors = await ctx.client.contentTasks.editors();
20
- const resourceRoutes = ctx.globalConfig.resourceRoutes;
21
-
22
- if (!resourceRoutes) {
23
- throw new Error("Missing resourceRoutes in the admin config!");
24
- }
25
4
 
26
5
  return {
27
6
  assigns: {
28
7
  items,
29
8
  editors: editors.list || [],
30
- buildResourceUrl: (resource) =>
31
- buildResourceUrl(resource, resourceRoutes),
32
- workflowsMap: (workflowType) =>
33
- WORKFLOWS_MAP[workflowType] || "Unknown workflow",
34
9
  },
35
10
  };
36
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 (DeepL) & 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 and buildResourceUrl helper", async () => {
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 (DeepL) & 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
+ <% } %>
@@ -1,6 +1,6 @@
1
1
  <div class="page-wrapper">
2
2
  <div class="page-title">
3
- <h1 class="heading-1">Content Tasks</h1>
3
+ <h1 class="heading-1">Translation Tasks</h1>
4
4
 
5
5
  <div class="actions-toolbar">
6
6
  <button type="button" data-toggle-sidebar="1" class="p-2.5 rounded hover:bg-neutral-200 transition-all duration-300 ease-in-out dark:hover:bg-neutral-700">
@@ -11,114 +11,14 @@
11
11
  </div>
12
12
  </div>
13
13
 
14
- <table class="data-grid">
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
- <%= workflowsMap(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">
118
18
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-12">
119
19
  <path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" />
120
20
  </svg>
121
- <h3 class="heading-3 mt-4">No content tasks</h3>
21
+ <h3 class="heading-3 mt-4">No translation tasks</h3>
122
22
  <p class="body-2 text-raketa-foreground/70 mt-2">
123
23
  <% if (Object.keys(_context.query).some(k => k.startsWith('f:'))) { %>
124
24
  No tasks match the selected filters. Try adjusting your filters.
@@ -70,6 +70,6 @@
70
70
  <% } %>
71
71
 
72
72
  <% if (hasActiveContentTask) { %>
73
- <div class="text-orange-700 mt-2 dark:text-orange-300">Actions are disabled while a content task is in progress.</div>
73
+ <div class="text-orange-700 mt-2 dark:text-orange-300">Actions are disabled while a translation task is in progress.</div>
74
74
  <% } %>
75
75
  </div>
@@ -42,7 +42,7 @@
42
42
  <% } %>
43
43
  <% if (utils.feature("contentTasks", _context) && item.activeContentTask && !item.activeContentTask.completedAt && !item.activeContentTask.failedAt) { %>
44
44
  <span class="bg-blue-100 chip text-blue-700 dark:bg-blue-900/50 dark:text-blue-300" title="<%= item.activeContentTask.workflow ? `${item.activeContentTask.workflow} workflow` : 'Translation in progress' %>">
45
- Content task in progress
45
+ Translation task in progress
46
46
  </span>
47
47
  <% } %>
48
48
 
@@ -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">
@@ -35,12 +35,21 @@
35
35
  <label for="workflow" class="font-semibold hover:cursor-pointer body-3">Translation workflow</label>
36
36
  <select name="resource[workflow]" id="workflow">
37
37
  <option value="">Default (no workflow)</option>
38
- <option value="LOKALISE_TO_AI_RT_HUM" <%= resource.workflow === 'LOKALISE_TO_AI_RT_HUM' ? 'selected' : '' %>>Lokalise: AI translation & Human review</option>
39
- <option value="LOKALISE_TT_HUM" <%= resource.workflow === 'LOKALISE_TT_HUM' ? 'selected' : '' %>>Lokalise: Human translation</option>
40
- <option value="LOKALISE_TT_AI" <%= resource.workflow === 'LOKALISE_TT_AI' ? 'selected' : '' %>>Lokalise: AI translation</option>
38
+
39
+ <option value="LOKALISE_TO_AI_RT_HUM" <%= resource.workflow === 'LOKALISE_TO_AI_RT_HUM' ? 'selected' : '' %>>
40
+ <%= content_tasks.workflowsMap("LOKALISE_TO_AI_RT_HUM".toLowerCase()) %>
41
+ </option>
42
+
43
+ <option value="LOKALISE_TT_HUM" <%= resource.workflow === 'LOKALISE_TT_HUM' ? 'selected' : '' %>>
44
+ <%= content_tasks.workflowsMap("LOKALISE_TT_HUM".toLowerCase()) %>
45
+ </option>
46
+
47
+ <option value="LOKALISE_TT_AI" <%= resource.workflow === 'LOKALISE_TT_AI' ? 'selected' : '' %>>
48
+ <%= content_tasks.workflowsMap("LOKALISE_TT_AI".toLowerCase()) %>
49
+ </option>
41
50
  </select>
42
51
  <p class="text-sm text-raketa-foreground/50 mt-1">
43
- Select a workflow for the translation process. Lokalise workflow initiates a content task for external translation.
52
+ Select a workflow for the translation process. Lokalise workflow initiates a translation task for external translation.
44
53
  </p>
45
54
  </div>
46
55
  <% } %>
@@ -102,6 +111,7 @@
102
111
  <ul>
103
112
  <li>Content is required to start a Lokalise task.</li>
104
113
  <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>
114
+ <li>You can track translation progress in the <a href="<%= `/${_context.locale}/content_tasks` %>" target="_blank">Translation tasks</a> module.</li>
105
115
  </ul>
106
116
  </div>
107
117
  </div>
@@ -6,12 +6,18 @@
6
6
  </div>
7
7
  </div>
8
8
 
9
- <% if (translations && translations.length > 0) { %>
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
- <th>&nbsp;</th>
16
+ <% if (activeContentTasks) { %>
17
+ <th>Status</th>
18
+ <% } else { %>
19
+ <th>&nbsp;</th>
20
+ <% } %>
15
21
  </tr>
16
22
  </thead>
17
23
  <tbody>
@@ -25,9 +31,25 @@
25
31
  </div>
26
32
  </td>
27
33
 
28
- <td class="whitespace-nowrap">
29
- <%- render_partial('./_translation_toolbar.ejs', { translation }) %>
30
- </td>
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>