@localazy/strapi-plugin 0.1.12 → 0.1.13-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.eslintrc +6 -3
  2. package/.github/workflows/.build.yml +1 -1
  3. package/admin/src/index.js +26 -1
  4. package/admin/src/modules/@common/components/LanguagesSelector/index.js +69 -0
  5. package/admin/src/modules/@common/locale/en.js +1 -0
  6. package/admin/src/modules/localazy-download/locale/en.js +3 -0
  7. package/admin/src/modules/plugin-settings/components/ContentTransferSetup/index.js +1 -2
  8. package/admin/src/modules/plugin-settings/components/GlobalSettings/index.js +269 -0
  9. package/admin/src/modules/plugin-settings/locale/en.js +38 -3
  10. package/admin/src/modules/plugin-settings/services/strapi-users-service.js +26 -0
  11. package/admin/src/pages/Download/index.js +49 -2
  12. package/admin/src/pages/PluginSettings/index.js +27 -11
  13. package/admin/src/utils/createAxiosInstance.js +2 -0
  14. package/package.json +24 -25
  15. package/server/bootstrap.js +50 -9
  16. package/server/controllers/localazy-transfer-controller.js +42 -4
  17. package/server/controllers/strapi-controller.js +3 -0
  18. package/server/lifecycles/deep-populate-hook.js +11 -0
  19. package/server/lifecycles/deprecate-event-entry-in-localazy-hook.js +69 -0
  20. package/server/lifecycles/upload-event-entry-to-localazy-hook.js +31 -0
  21. package/server/middlewares/index.js +5 -1
  22. package/server/middlewares/verify-webhook.js +77 -0
  23. package/server/routes/index.js +2 -0
  24. package/server/routes/localazy-public-transfer.js +20 -0
  25. package/server/routes/strapi.js +8 -0
  26. package/server/services/helpers/plugin-settings-service-helper.js +52 -0
  27. package/server/services/localazy-pubapi-service.js +6 -0
  28. package/server/services/plugin-settings-service.js +7 -1
  29. package/server/services/strapi-i18n-service.js +1 -1
  30. package/server/utils/get-picked-flatten-keys-for-hook-entry.js +270 -0
  31. package/server/utils/request-initiator-helper.js +36 -0
  32. package/admin/src/.eslintrc +0 -3
package/.eslintrc CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
- // "extends": "@strapi-community",
2
+ "extends": "@strapi-community",
3
3
  "env": {
4
4
  "jest/globals": true,
5
- "es6": true
5
+ "es6": true,
6
+ "node": true
6
7
  },
7
- "plugins": ["jest"],
8
+ "plugins": [
9
+ "jest"
10
+ ],
8
11
  "parserOptions": {
9
12
  "ecmaVersion": "latest"
10
13
  }
@@ -12,7 +12,7 @@ jobs:
12
12
  - name: Setup Node.js environment
13
13
  uses: actions/setup-node@v3
14
14
  with:
15
- node-version: 14.x
15
+ node-version: 16.x
16
16
  - name: Install dependencies
17
17
  run: npm ci
18
18
  - name: Run unit tests
@@ -12,6 +12,20 @@ const SETTINGS_SETUP = [
12
12
  intlId: `localazy_settings`,
13
13
  sectionDefaultLabel: "Localazy Settings",
14
14
  links: [
15
+ {
16
+ linkId: `${pluginId}-settings-global`,
17
+ intlId: `general`,
18
+ defaultLabel: "Global Settings",
19
+ to: `/settings/${pluginId}/global-settings`,
20
+ Component: async () => {
21
+ const component = await import("./pages/PluginSettings");
22
+
23
+ return component;
24
+ },
25
+ permissions: [
26
+ // TODO: only allow access to this page if the user has the right permissions
27
+ ],
28
+ },
15
29
  {
16
30
  linkId: `${pluginId}-settings-content-transfer-setup`,
17
31
  intlId: `content_transfer_setup`,
@@ -61,7 +75,7 @@ export default {
61
75
  },
62
76
  },
63
77
  [
64
- // links
78
+ // General
65
79
  {
66
80
  id: SETTINGS_SETUP[0].links[0].linkId,
67
81
  intlLabel: {
@@ -72,6 +86,17 @@ export default {
72
86
  Component: SETTINGS_SETUP[0].links[0].Component,
73
87
  permissions: SETTINGS_SETUP[0].links[0].permissions,
74
88
  },
89
+ // Content Transfer Setup
90
+ {
91
+ id: SETTINGS_SETUP[0].links[1].linkId,
92
+ intlLabel: {
93
+ id: SETTINGS_SETUP[0].links[1].intlId,
94
+ defaultMessage: SETTINGS_SETUP[0].links[1].defaultLabel,
95
+ },
96
+ to: SETTINGS_SETUP[0].links[1].to,
97
+ Component: SETTINGS_SETUP[0].links[1].Component,
98
+ permissions: SETTINGS_SETUP[0].links[1].permissions,
99
+ },
75
100
  ]
76
101
  );
77
102
  app.registerPlugin({
@@ -0,0 +1,69 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import React, { useEffect, useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useTranslation } from "react-i18next";
5
+ import cloneDeep from "lodash-es/cloneDeep";
6
+ import { Select, Option } from '@strapi/design-system/Select';
7
+
8
+ import "../../../../i18n";
9
+
10
+ function LanguagesSelector(props) {
11
+ /**
12
+ * Translation function
13
+ */
14
+ const { t } = useTranslation();
15
+
16
+ /**
17
+ * Selected languages
18
+ */
19
+ const [selectedLanguages, setSelectedLanguages] = useState([]);
20
+
21
+ const onChange = (values) => {
22
+ setSelectedLanguages(values);
23
+ props.onChange(cloneDeep(values));
24
+ }
25
+
26
+ useEffect(() => {
27
+ const existingPreselectedLanguages =
28
+ props.projectLanguages
29
+ .filter(lang => props.preselectedLanguages.includes(lang.code))
30
+ .map(lang => lang.code);
31
+
32
+ setSelectedLanguages(existingPreselectedLanguages);
33
+ }, [props.projectLanguages, props.preselectedLanguages]);
34
+
35
+ return (
36
+ <Select
37
+ label={props.label || t("common.select_languages")}
38
+ hint={props.hint || t("common.select_languages")}
39
+ clearLabel={t("plugin_settings.clear")}
40
+ placeholder={props.placeholder || t("common.select_languages")}
41
+ onClear={() => setSelectedLanguages([])}
42
+ value={selectedLanguages || []}
43
+ onChange={(values) => onChange(values)}
44
+ multi
45
+ withTags
46
+ >
47
+ {props.projectLanguages.map(lang => (
48
+ <Option
49
+ key={lang.id}
50
+ value={lang.code}
51
+ >
52
+ {`${lang.name} (${lang.code})`}
53
+ </Option>
54
+ ))}
55
+ </Select>
56
+ );
57
+ }
58
+
59
+ LanguagesSelector.propTypes = {
60
+ label: PropTypes.string,
61
+ hint: PropTypes.string,
62
+ placeholder: PropTypes.string,
63
+ projectLanguages: PropTypes.array.isRequired,
64
+ preselectedLanguages: PropTypes.array.isRequired,
65
+ onChange: PropTypes.func.isRequired,
66
+ };
67
+
68
+
69
+ export default LanguagesSelector;
@@ -10,4 +10,5 @@ export default {
10
10
  report_will_be_available_after_an_action:
11
11
  "Report will be available after performing an action...",
12
12
  report: "Report",
13
+ select_languages: "Select languages",
13
14
  };
@@ -17,4 +17,7 @@ export default {
17
17
  make_sure_note_a: "Make sure the translations are approved in Localazy, otherwise they ",
18
18
  make_sure_note_b: "will not ",
19
19
  make_sure_note_c: "be downloaded to Strapi.",
20
+ ui_languages: "Select languages to download",
21
+ ui_languages_info: "Select translations of which languages wil be downloaded from Localazy",
22
+ ui_languages_placeholder: "Download all languages",
20
23
  };
@@ -299,8 +299,7 @@ function ContentTransferSetup() {
299
299
  </Box>
300
300
  )}
301
301
  </Box>
302
- )
303
- }
302
+ )}
304
303
  </Box>
305
304
  </>
306
305
  );
@@ -0,0 +1,269 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import cloneDeep from "lodash-es/cloneDeep";
4
+ import { HeaderLayout } from "@strapi/design-system/Layout";
5
+ import Check from "@strapi/icons/Check";
6
+ import { Box } from "@strapi/design-system/Box";
7
+ import { Button } from "@strapi/design-system/Button";
8
+ import { ToggleInput } from '@strapi/design-system/ToggleInput';
9
+ import { Select, Option } from '@strapi/design-system/Select';
10
+ import { Alert } from "@strapi/design-system/Alert";
11
+ import { Redirect } from "react-router-dom";
12
+ import { Flex } from "@strapi/design-system/Flex";
13
+ import { Divider } from "@strapi/design-system/Divider";
14
+ import { Typography } from "@strapi/design-system/Typography";
15
+ import isEqual from "lodash-es/isEqual";
16
+ import set from "lodash-es/set";
17
+ import LanguagesSelector from "../../../@common/components/LanguagesSelector";
18
+ import PluginSettingsService from "../../services/plugin-settings-service";
19
+ import StrapiUsersService from "../../services/strapi-users-service";
20
+ import { getLocalazyIdentity } from "../../../../state/localazy-identity";
21
+ import ProjectService from "../../../@common/services/project-service";
22
+ import pluginId from "../../../../pluginId";
23
+
24
+ import "../../../../i18n";
25
+
26
+ function GlobalSettings() {
27
+ /**
28
+ * Translation function
29
+ */
30
+ const { t } = useTranslation();
31
+
32
+ /**
33
+ * Is user logged in
34
+ */
35
+ const [localazyIdentity] = getLocalazyIdentity();
36
+ const hasLocalazyIdentity = () => !!localazyIdentity.accessToken;
37
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
38
+
39
+ /**
40
+ * Project Languages without default language
41
+ */
42
+ const [projectLanguages, setProjectLanguages] = useState([]);
43
+
44
+ /**
45
+ * Component state
46
+ */
47
+ const [isLoading, setIsLoading] = useState(true);
48
+ const [showSavedAlert, setShowSavedAlert] = useState(false);
49
+
50
+ /**
51
+ * Admin Panel Users
52
+ */
53
+ const [users, setUsers] = useState([]);
54
+
55
+ /**
56
+ * Global Settings form model
57
+ */
58
+ const [originalFormModel, setOriginalFormModel] = useState([]);
59
+ const [formModel, setFormModel] = useState([]);
60
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
61
+
62
+ const onCancelClick = () => {
63
+ setFormModel(cloneDeep(originalFormModel));
64
+ setHasUnsavedChanges(false);
65
+ }
66
+
67
+ /**
68
+ * Save the Global Settings
69
+ */
70
+ const saveGlobalSettings = async (data) => {
71
+ try {
72
+ await PluginSettingsService.updatePluginSettings(data);
73
+ setOriginalFormModel(cloneDeep(data));
74
+ setHasUnsavedChanges(false);
75
+ setShowSavedAlert(true);
76
+ } catch (e) {
77
+ throw e.data;
78
+ }
79
+ };
80
+
81
+ /**
82
+ * Patch Form Model
83
+ */
84
+ const patchFormModel = (key, value) => {
85
+ const newFormModel = cloneDeep(formModel);
86
+ set(newFormModel, key, value);
87
+ setFormModel(newFormModel);
88
+ setHasUnsavedChanges(!isEqual(originalFormModel, newFormModel));
89
+ };
90
+
91
+ useEffect(() => {
92
+ async function fetchData() {
93
+ setIsLoading(true);
94
+ setIsLoggedIn(hasLocalazyIdentity());
95
+
96
+ if (!hasLocalazyIdentity()) {
97
+ setIsLoading(false);
98
+
99
+ return;
100
+ }
101
+
102
+ const project = await ProjectService.getConnectedProject();
103
+ const projectLanguagesWithoutDefaultLanguage =
104
+ project?.languages?.filter(language => language.id !== project.sourceLanguage) || [];
105
+ setProjectLanguages(projectLanguagesWithoutDefaultLanguage);
106
+
107
+ const globalSettings = await PluginSettingsService.getPluginSettings();
108
+
109
+ setFormModel(globalSettings);
110
+ setOriginalFormModel(cloneDeep(globalSettings));
111
+
112
+ const users = await StrapiUsersService.getAdminPanelUsers();
113
+ setUsers(users);
114
+
115
+ setIsLoading(false);
116
+ }
117
+ fetchData();
118
+ // eslint-disable-next-line react-hooks/exhaustive-deps
119
+ }, []);
120
+
121
+ return (
122
+ <>
123
+ {!isLoading && !isLoggedIn && <Redirect to={`${process.env.ADMIN_PATH}plugins/${pluginId}/login`} />}
124
+
125
+ <HeaderLayout
126
+ title={t("plugin_settings.global_settings")}
127
+ subtitle={t("plugin_settings.global_settings_description")}
128
+ primaryAction={
129
+ <Flex gap={2}>
130
+ <Button
131
+ variant="secondary"
132
+ disabled={!hasUnsavedChanges}
133
+ onClick={onCancelClick}
134
+ >
135
+ {t("plugin_settings.cancel")}
136
+ </Button>
137
+ <Button
138
+ startIcon={<Check />}
139
+ disabled={!hasUnsavedChanges}
140
+ onClick={() => { saveGlobalSettings(formModel) }}
141
+ >
142
+ {t("plugin_settings.save")}
143
+ </Button>
144
+ </Flex>
145
+ }
146
+ as="h2"
147
+ />
148
+ <Box marginRight={10} marginLeft={10}>
149
+ {!isLoading && showSavedAlert && (
150
+ <Box marginBottom={8}>
151
+ <Alert
152
+ onClose={() => showSavedAlert(false)}
153
+ closeLabel={t("plugin_settings.close")}
154
+ title={t("plugin_settings.content_transfer_setup_saved")}
155
+ variant="success"
156
+ >
157
+ {t("plugin_settings.global_settings_saved_successfully")}
158
+ </Alert>
159
+ </Box>
160
+ )}
161
+ {!isLoading && (
162
+ <Box
163
+ background="neutral0"
164
+ padding={7}
165
+ paddingTop={6}
166
+ shadow="tableShadow"
167
+ hasRadius
168
+ >
169
+ {/* Upload Settings */}
170
+ <Typography
171
+ variant="delta"
172
+ textColor="neutral800"
173
+ >
174
+ {t("plugin_settings.upload_settings")}
175
+ </Typography>
176
+ {/* Allow automated upload to Localazy */}
177
+ <br /><br />
178
+ <ToggleInput
179
+ label={t("plugin_settings.allow_automated_upload_to_localazy")}
180
+ hint={t("plugin_settings.allow_automated_upload_to_localazy_info")}
181
+ offLabel={t("plugin_settings.off")}
182
+ onLabel={t("plugin_settings.on")}
183
+ checked={typeof formModel?.upload?.allowAutomated === "boolean" ? formModel.upload.allowAutomated : false}
184
+ onChange={e => patchFormModel("upload.allowAutomated", e.target.checked)} />
185
+ {/* Automated upload triggers */}
186
+ <br /><br />
187
+ <Select
188
+ label={t("plugin_settings.automated_upload_triggers")}
189
+ hint={t("plugin_settings.automated_upload_triggers_info")}
190
+ clearLabel={t("plugin_settings.clear")}
191
+ placeholder={t("plugin_settings.automated_upload_triggers_placeholder")}
192
+ onClear={() => patchFormModel("upload.automatedTriggers", [])}
193
+ value={formModel?.upload?.automatedTriggers || []}
194
+ onChange={(values) => patchFormModel("upload.automatedTriggers", values)}
195
+ disabled={typeof formModel?.upload?.allowAutomated === "boolean" ? !formModel.upload.allowAutomated : true}
196
+ multi
197
+ withTags
198
+ >
199
+ <Option value="created">{t("plugin_settings.creating_new_data_entry")}</Option>
200
+ <Option value="updated">{t("plugin_settings.editing_data_entry")}</Option>
201
+ </Select>
202
+ {/* Deprecate source keys on deletion */}
203
+ <br /><br />
204
+ <ToggleInput
205
+ label={t("plugin_settings.deprecate_source_keys_on_delete")}
206
+ hint={t("plugin_settings.deprecate_source_keys_on_delete_info")}
207
+ offLabel={t("plugin_settings.off")}
208
+ onLabel={t("plugin_settings.on")}
209
+ checked={typeof formModel?.upload?.allowDeprecate === "boolean" ? formModel.upload.allowDeprecate : false}
210
+ onChange={e => patchFormModel("upload.allowDeprecate", e.target.checked)} />
211
+ <br /><br />
212
+ <Divider />
213
+ {/* Download Settings */}
214
+ <br /><br />
215
+ <Typography
216
+ variant="delta"
217
+ textColor="neutral800"
218
+ >
219
+ {t("plugin_settings.download_settings")}
220
+ </Typography>
221
+ {/* Processing of download webhook */}
222
+ <br /><br />
223
+ <ToggleInput
224
+ label={t("plugin_settings.processing_of_download_webhook")}
225
+ hint={t("plugin_settings.processing_of_download_webhook_info")}
226
+ offLabel={t("plugin_settings.off")}
227
+ onLabel={t("plugin_settings.on")}
228
+ // eslint-disable-next-line max-len
229
+ checked={typeof formModel?.download?.processDownloadWebhook === "boolean" ? formModel.download.processDownloadWebhook : true}
230
+ onChange={e => patchFormModel("download.processDownloadWebhook", e.target.checked)} />
231
+ {/* Webhook actions author */}
232
+ <br /><br />
233
+ <Select
234
+ label={t("plugin_settings.webhook_author")}
235
+ hint={t("plugin_settings.webhook_author_info")}
236
+ clearLabel={t("plugin_settings.clear")}
237
+ placeholder={t("plugin_settings.webhook_author_placeholder")}
238
+ onClear={() => patchFormModel("download.webhookAuthorId", null)}
239
+ value={formModel?.download?.webhookAuthorId || null}
240
+ onChange={(value) => patchFormModel("download.webhookAuthorId", value)}
241
+ >
242
+ {users.map(user => (
243
+ <Option
244
+ key={user.id}
245
+ value={user.id}
246
+ >
247
+ {`${user.firstname} ${user.lastname} (${user.email})`}
248
+ </Option>
249
+ ))}
250
+ </Select>
251
+ {/* Webhook languages selector */}
252
+ <br /><br />
253
+ <LanguagesSelector
254
+ preselectedLanguages={formModel?.download?.webhookLanguages || []}
255
+ projectLanguages={projectLanguages}
256
+ onChange={(languages) => patchFormModel("download.webhookLanguages", languages)}
257
+ label={t("plugin_settings.webhook_languages")}
258
+ hint={t("plugin_settings.webhook_languages_info")}
259
+ placeholder={t("plugin_settings.webhook_languages_placeholder")}
260
+ />
261
+
262
+ </Box>
263
+ )}
264
+ </Box>
265
+ </>
266
+ );
267
+ }
268
+
269
+ export default GlobalSettings;
@@ -1,10 +1,43 @@
1
1
  export default {
2
- content_transfer_setup: "Content Transfer Setup",
3
- content_transfer_setup_description:
4
- "Select which content type fields will be uploaded to Localazy",
5
2
  save: "Save",
6
3
  cancel: "Cancel",
7
4
  close: "Close",
5
+ off: "Off",
6
+ on: "On",
7
+ clear: "Clear",
8
+
9
+ /* Global Settings */
10
+ global_settings: "Global Settings",
11
+ global_settings_description: "Setup the Localazy plugin upload/download options.",
12
+
13
+ upload_settings: "Upload settings",
14
+ allow_automated_upload_to_localazy: "Allow automated upload to Localazy",
15
+ allow_automated_upload_to_localazy_info: "Content will be automatically uploaded to Localazy after its creation or update.",
16
+ automated_upload_triggers: "Automated upload triggers",
17
+ automated_upload_triggers_placeholder: "Select triggers",
18
+ creating_new_data_entry: "Creating new data entry",
19
+ editing_data_entry: "Editing data entry",
20
+ automated_upload_triggers_info: "Select actions that will trigger an upload of Strapi data to Localazy.",
21
+
22
+ deprecate_source_keys_on_delete: "Deprecate source keys on deletion",
23
+ deprecate_source_keys_on_delete_info: "Source keys will be automatically set as deprecated if an entry in source language is deleted in Strapi",
24
+
25
+ download_settings: "Download settings",
26
+ processing_of_download_webhook: "Processing of download webhook",
27
+ processing_of_download_webhook_info: "You can temporarily disable the webhook processing and the information will be scrapped.",
28
+ webhook_author: "Webhook actions author",
29
+ webhook_author_info: "Select the author signed under the webhook actions (required by Strapi for certain actions, e.g. creating a locale).",
30
+ webhook_author_placeholder: "Select webhook actions author",
31
+ webhook_languages: "Allowed webhook languages",
32
+ webhook_languages_info: "Select the languages from Localazy. Translations of selected languages would be downloaded. If no language is selected, all translations will be downloaded.",
33
+ webhook_languages_placeholder: "Download all languages",
34
+
35
+ global_settings_saved_successfully: "Global Settings were saved",
36
+
37
+ /* Content Transfer Setup */
38
+ content_transfer_setup: "Content Transfer Setup",
39
+ content_transfer_setup_description:
40
+ "Select which content type fields will be uploaded to Localazy",
8
41
 
9
42
  content_transfer_setup_saved: "Content Transfer Setup saved",
10
43
  content_transfer_setup_saved_successfully:
@@ -23,4 +56,6 @@ export default {
23
56
  step_3: "Check <1>Enable localization for this Content-Type</1>",
24
57
  step_4: "Repeat for all Content-Types you want to translate",
25
58
  step_5: "Return to this screen and finish the transfer setup",
59
+
60
+ general: "General",
26
61
  };
@@ -0,0 +1,26 @@
1
+ /* eslint-disable no-useless-catch */
2
+ import createAxiosInstance from "../../../utils/createAxiosInstance";
3
+
4
+ const axiosInstance = createAxiosInstance("/admin/users");
5
+
6
+ export default class PluginSettingsService {
7
+ static async getAdminPanelUsers() {
8
+ try {
9
+ // TODO: update to return all users; not just the first 100
10
+ const result = await axiosInstance.get(
11
+ "",
12
+ {
13
+ params: {
14
+ "pageSize": 100,
15
+ "page": 1,
16
+ "sort": "firstname"
17
+ }
18
+ }
19
+ );
20
+
21
+ return result.data.data.results;
22
+ } catch (e) {
23
+ throw e;
24
+ }
25
+ }
26
+ }
@@ -1,5 +1,7 @@
1
1
  import React, { useState, useEffect } from "react";
2
2
  import PropTypes from "prop-types";
3
+ import cloneDeep from "lodash-es/cloneDeep";
4
+ import set from "lodash-es/set";
3
5
  import { HeaderLayout } from "@strapi/design-system/Layout";
4
6
  import { Button } from "@strapi/design-system/Button";
5
7
  import DownloadIcon from "@strapi/icons/Download";
@@ -20,6 +22,8 @@ import { getLocalazyIdentity } from "../../state/localazy-identity";
20
22
  import ProductAnalyticsService from "../../modules/@common/services/product-analytics-service";
21
23
  import PluginSettingsService from "../../modules/plugin-settings/services/plugin-settings-service";
22
24
  import { PLUGIN_ROUTES } from "../../modules/@common/utils/redirect-to-plugin-route";
25
+ import LanguagesSelector from "../../modules/@common/components/LanguagesSelector";
26
+ import ProjectService from "../../modules/@common/services/project-service";
23
27
 
24
28
  function Download(props) {
25
29
  const { t } = useTranslation();
@@ -36,13 +40,36 @@ function Download(props) {
36
40
  const [isDownloading, setIsDownloading] = useState(false);
37
41
 
38
42
  /**
39
- * localazy identity / access token
40
- */
43
+ * Localazy identity / access token
44
+ */
41
45
  const [localazyIdentity] = getLocalazyIdentity();
42
46
  const onTranslateInLocalazyClick = () => {
43
47
  window.open(localazyIdentity.project.url, "_blank");
44
48
  }
45
49
 
50
+ /**
51
+ * Project Languages without default language
52
+ */
53
+ const [projectLanguages, setProjectLanguages] = useState([]);
54
+
55
+ /**
56
+ * Global Settings form model
57
+ */
58
+ const [formModel, setFormModel] = useState([]);
59
+
60
+ const onDownloadLanguagesChange = (languages) => {
61
+ const newFormModel = cloneDeep(formModel);
62
+ set(newFormModel, 'download.uiLanguages', languages);
63
+ setFormModel(newFormModel);
64
+
65
+ try {
66
+ PluginSettingsService.updatePluginSettings(newFormModel);
67
+ } catch (e) {
68
+ console.error(e.message);
69
+ }
70
+ };
71
+
72
+
46
73
  const onDownloadClick = async () => {
47
74
  setIsDownloading(true);
48
75
  const result = await LocalazyDownloadService.download();
@@ -71,6 +98,14 @@ function Download(props) {
71
98
  setModelChanged(await hasModelChanged());
72
99
  setLocalesIncompatible(!(await areLocalesCompatible()));
73
100
 
101
+ const project = await ProjectService.getConnectedProject();
102
+ const projectLanguagesWithoutDefaultLanguage =
103
+ project?.languages?.filter(language => language.id !== project.sourceLanguage) || [];
104
+ setProjectLanguages(projectLanguagesWithoutDefaultLanguage);
105
+
106
+ const globalSettings = await PluginSettingsService.getPluginSettings();
107
+ setFormModel(globalSettings);
108
+
74
109
  PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.DOWNLOAD });
75
110
 
76
111
  setIsLoading(false);
@@ -124,6 +159,18 @@ function Download(props) {
124
159
  <Typography variant="omega">
125
160
  {t("download.make_sure_note_c")}
126
161
  </Typography>
162
+ <Box
163
+ paddingTop={6}
164
+ >
165
+ <LanguagesSelector
166
+ preselectedLanguages={formModel?.download?.uiLanguages || []}
167
+ projectLanguages={projectLanguages}
168
+ onChange={(languages) => onDownloadLanguagesChange(languages)}
169
+ label={t("download.ui_languages")}
170
+ hint={t("download.ui_languages_info")}
171
+ placeholder={t("download.ui_languages_placeholder")}
172
+ />
173
+ </Box>
127
174
  <Box
128
175
  paddingTop={4}
129
176
  paddingBottom={4}
@@ -3,12 +3,22 @@ import { Box } from "@strapi/design-system/Box";
3
3
  import { Switch, Route } from "react-router-dom";
4
4
  import { ThemeProvider } from "@strapi/design-system/ThemeProvider";
5
5
  import { lightTheme } from "@strapi/design-system/themes";
6
+ import { useTranslation } from "react-i18next";
6
7
  import pluginId from "../../pluginId";
7
8
  import ContentTransferSetup from "../../modules/plugin-settings/components/ContentTransferSetup";
9
+ import GlobalSettings from "../../modules/plugin-settings/components/GlobalSettings";
8
10
  import LocalazyUserService from "../../modules/user/services/localazy-user-service";
9
11
  import { setLocalazyIdentity } from "../../state/localazy-identity";
12
+ import Loader from "../../modules/@common/components/PluginPageLoader";
13
+
14
+ import "../../i18n";
10
15
 
11
16
  function PluginSettings() {
17
+ /**
18
+ * Translation function
19
+ */
20
+ const { t } = useTranslation();
21
+
12
22
  const [isLoading, setIsLoading] = useState(true);
13
23
 
14
24
  useEffect(() => {
@@ -25,17 +35,23 @@ function PluginSettings() {
25
35
  }, []);
26
36
 
27
37
  return (
28
- !isLoading && (
29
- <ThemeProvider theme={lightTheme}>
30
- <Box padding={8}>
31
- <Switch>
32
- <Route path={`/settings/${pluginId}/content-transfer-setup`} exact>
33
- <ContentTransferSetup />
34
- </Route>
35
- </Switch>
36
- </Box>
37
- </ThemeProvider>
38
- )
38
+ <>
39
+ {isLoading && <Loader>{t("common.loading_content")}</Loader>}
40
+ {!isLoading && (
41
+ <ThemeProvider theme={lightTheme}>
42
+ <Box padding={8}>
43
+ <Switch>
44
+ <Route path={`/settings/${pluginId}/content-transfer-setup`} exact>
45
+ <ContentTransferSetup />
46
+ </Route>
47
+ <Route path={`/settings/${pluginId}/global-settings`} exact>
48
+ <GlobalSettings />
49
+ </Route>
50
+ </Switch>
51
+ </Box>
52
+ </ThemeProvider>
53
+ )}
54
+ </>
39
55
  );
40
56
  }
41
57
 
@@ -23,6 +23,8 @@ const createAxiosInstance = (baseUrl = null) => {
23
23
  Authorization: `Bearer ${auth.getToken()}`,
24
24
  Accept: "application/json",
25
25
  "Content-Type": "application/json",
26
+ // Add this header to identify the request as initiated by the plugin
27
+ "X-Localazy-Initiated-By": "strapi-plugin-localazy",
26
28
  };
27
29
 
28
30
  return config;