@crowdin/app-project-module 0.77.0 → 0.78.0

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.
@@ -21,7 +21,7 @@ const job_1 = require("../util/job");
21
21
  const files_1 = require("../util/files");
22
22
  function handle(config, integration) {
23
23
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
24
- var _a, _b;
24
+ var _a, _b, _c;
25
25
  const projectId = req.crowdinContext.jwtPayload.context.project_id || (req === null || req === void 0 ? void 0 : req.body.projectId);
26
26
  const uploadTranslations = req.query.uploadTranslations === 'true' || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.uploadTranslations);
27
27
  req.logInfo(`Updating crowdin project ${projectId}`);
@@ -33,6 +33,11 @@ function handle(config, integration) {
33
33
  if (((_b = config.api) === null || _b === void 0 ? void 0 : _b.default) && req.body.files) {
34
34
  req.body = req.body.files;
35
35
  }
36
+ const excludedTargetLanguages = yield (0, files_1.getExcludedTargetLanguages)({
37
+ client: req.crowdinApiClient,
38
+ projectId,
39
+ languages: ((_c = req.query.languages) === null || _c === void 0 ? void 0 : _c.split(',')) || [],
40
+ });
36
41
  yield (0, job_1.runAsJob)({
37
42
  integrationId: req.crowdinContext.clientId,
38
43
  crowdinId: req.crowdinContext.crowdinId,
@@ -40,13 +45,13 @@ function handle(config, integration) {
40
45
  title: 'Sync files to Crowdin',
41
46
  payload: req.body,
42
47
  res,
43
- projectId: projectId,
48
+ projectId,
44
49
  client: req.crowdinApiClient,
45
50
  jobType: types_1.JobClientType.MANUAL,
46
51
  jobStoreType: integration.jobStoreType,
47
52
  jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
48
- var _c;
49
- if (req.body && ((_c = req.body) === null || _c === void 0 ? void 0 : _c.length)) {
53
+ var _d;
54
+ if (req.body && ((_d = req.body) === null || _d === void 0 ? void 0 : _d.length)) {
50
55
  req.body = yield (0, files_1.expandFilesTree)(req.body, req, integration, job);
51
56
  req.body = (0, lodash_uniqby_1.default)(req.body, 'id');
52
57
  }
@@ -59,6 +64,7 @@ function handle(config, integration) {
59
64
  appSettings: req.integrationSettings,
60
65
  uploadTranslations,
61
66
  job,
67
+ excludedTargetLanguages: req.query.languages ? excludedTargetLanguages : undefined,
62
68
  });
63
69
  let message;
64
70
  if ((0, files_1.isExtendedResultType)(result)) {
@@ -19,7 +19,9 @@ function handle(config, integration) {
19
19
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
20
20
  var _a;
21
21
  req.logInfo('Updating integration data');
22
- const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, req.crowdinApiClient, req.crowdinContext.jwtPayload.context.project_id);
22
+ const client = req.crowdinApiClient;
23
+ const projectId = req.crowdinContext.jwtPayload.context.project_id;
24
+ const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, req.crowdinApiClient, projectId);
23
25
  if (rootFolder) {
24
26
  req.logInfo(`Updating integration data for crowding root folder ${rootFolder.id}`);
25
27
  }
@@ -27,23 +29,35 @@ function handle(config, integration) {
27
29
  if (((_a = config.api) === null || _a === void 0 ? void 0 : _a.default) && req.body.files) {
28
30
  req.body = req.body.files;
29
31
  }
32
+ let payload = req.body;
33
+ if (integration.excludedTargetLanguages) {
34
+ let options = {};
35
+ if (rootFolder) {
36
+ options = {
37
+ directoryId: rootFolder.id,
38
+ recursion: 'true',
39
+ };
40
+ }
41
+ const files = (yield client.sourceFilesApi.withFetchAll().listProjectFiles(projectId, options)).data.map((d) => d.data);
42
+ payload = (0, files_1.filterLanguages)(payload, files);
43
+ }
30
44
  yield (0, job_1.runAsJob)({
31
45
  integrationId: req.crowdinContext.clientId,
32
46
  crowdinId: req.crowdinContext.crowdinId,
33
47
  type: types_1.JobType.UPDATE_TO_INTEGRATION,
34
48
  title: 'Sync files to ' + config.name,
35
- payload: req.body,
49
+ payload,
36
50
  res,
37
- projectId: req.crowdinContext.jwtPayload.context.project_id,
38
- client: req.crowdinApiClient,
51
+ projectId,
52
+ client,
39
53
  jobType: types_1.JobClientType.MANUAL,
40
54
  jobStoreType: integration.jobStoreType,
41
55
  jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
42
56
  const result = yield integration.updateIntegration({
43
- projectId: req.crowdinContext.jwtPayload.context.project_id,
44
- client: req.crowdinApiClient,
57
+ projectId,
58
+ client,
45
59
  credentials: req.integrationCredentials,
46
- request: req.body,
60
+ request: payload,
47
61
  rootFolder,
48
62
  appSettings: req.integrationSettings,
49
63
  job,
@@ -94,6 +94,7 @@ function handle(config, integration) {
94
94
  options.integrationSearchListener = integration.integrationSearchListener;
95
95
  options.checkSubscription = !(0, subscription_1.isAppFree)(config);
96
96
  options.uploadTranslations = integration.uploadTranslations;
97
+ options.excludedTargetLanguages = integration.excludedTargetLanguages;
97
98
  options.sentryData = process.env.SENTRY_DSN
98
99
  ? {
99
100
  dsn: process.env.SENTRY_DSN,
@@ -34,7 +34,7 @@ export interface IntegrationLogic extends ModuleKey {
34
34
  /**
35
35
  * function to update crowdin files (e.g. pull integration data to crowdin source files)
36
36
  */
37
- updateCrowdin: ({ projectId, client, credentials, request, rootFolder, appSettings, uploadTranslations, job, }: {
37
+ updateCrowdin: ({ projectId, client, credentials, request, rootFolder, appSettings, uploadTranslations, job, excludedTargetLanguages, }: {
38
38
  projectId: number;
39
39
  client: Crowdin;
40
40
  credentials: any;
@@ -43,6 +43,7 @@ export interface IntegrationLogic extends ModuleKey {
43
43
  appSettings?: any;
44
44
  uploadTranslations?: boolean;
45
45
  job: JobClient;
46
+ excludedTargetLanguages?: string[];
46
47
  }) => Promise<void | ExtendedResult<void>>;
47
48
  /**
48
49
  * function to update integration content (e.g. load crowdin translations and push them to integration service)
@@ -125,6 +126,10 @@ export interface IntegrationLogic extends ModuleKey {
125
126
  * Enable the option to upload translations to crowdin that are already present in the integration.
126
127
  */
127
128
  uploadTranslations?: boolean;
129
+ /**
130
+ * Enable the option to upload file for translation into selected languages.
131
+ */
132
+ excludedTargetLanguages?: boolean;
128
133
  /**
129
134
  * function to get crowdin file translation progress
130
135
  */
@@ -299,6 +304,7 @@ export interface File {
299
304
  customContent?: string;
300
305
  labels?: LabelTreeElement[];
301
306
  failed?: boolean;
307
+ excludedTargetLanguages?: string[];
302
308
  }
303
309
  export interface Folder {
304
310
  id: string;
@@ -116,6 +116,7 @@ function applyIntegrationModuleDefaults(config, integration) {
116
116
  parentId: parentId ? parentId.toString() : undefined,
117
117
  name: e.title || e.name,
118
118
  type: e.type,
119
+ excludedTargetLanguages: e.excludedTargetLanguages,
119
120
  });
120
121
  });
121
122
  return res;
@@ -1,6 +1,7 @@
1
- import { ExtendedResult, IntegrationFile, IntegrationLogic, IntegrationRequest, SkipIntegrationNodes, TreeItem } from '../types';
1
+ import { ExtendedResult, IntegrationFile, IntegrationLogic, IntegrationRequest, SkipIntegrationNodes, TreeItem, UpdateIntegrationRequest } from '../types';
2
2
  import { JobClient } from './types';
3
3
  import Crowdin from '@crowdin/crowdin-api-client';
4
+ import { SourceFilesModel } from '@crowdin/crowdin-api-client';
4
5
  export declare function skipFilesByRegex(files: TreeItem[] | undefined, skipIntegrationNodes?: SkipIntegrationNodes): TreeItem[];
5
6
  export declare function expandFilesTree(nodes: IntegrationFile[], req: IntegrationRequest, integration: IntegrationLogic, job?: JobClient): Promise<IntegrationFile[]>;
6
7
  export declare function isExtendedResultType<T>(data?: T | ExtendedResult<T>): data is ExtendedResult<T>;
@@ -10,3 +11,9 @@ export declare function markUnsyncedFiles({ integrationId, crowdinId, client, fi
10
11
  client: Crowdin;
11
12
  files?: TreeItem[];
12
13
  }): Promise<TreeItem[]>;
14
+ export declare function getExcludedTargetLanguages({ client, projectId, languages, }: {
15
+ client: Crowdin;
16
+ projectId: number;
17
+ languages: string[];
18
+ }): Promise<string[]>;
19
+ export declare function filterLanguages(request: UpdateIntegrationRequest, files: SourceFilesModel.File[]): UpdateIntegrationRequest;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.markUnsyncedFiles = exports.isExtendedResultType = exports.expandFilesTree = exports.skipFilesByRegex = void 0;
12
+ exports.filterLanguages = exports.getExcludedTargetLanguages = exports.markUnsyncedFiles = exports.isExtendedResultType = exports.expandFilesTree = exports.skipFilesByRegex = void 0;
13
13
  const types_1 = require("./types");
14
14
  const storage_1 = require("../../../storage");
15
15
  const util_1 = require("../../../util");
@@ -98,3 +98,26 @@ function markUnsyncedFiles({ integrationId, crowdinId, client, files, }) {
98
98
  });
99
99
  }
100
100
  exports.markUnsyncedFiles = markUnsyncedFiles;
101
+ function getExcludedTargetLanguages({ client, projectId, languages, }) {
102
+ return __awaiter(this, void 0, void 0, function* () {
103
+ const projectData = yield client.projectsGroupsApi.getProject(projectId);
104
+ const targetLanguages = projectData.data.targetLanguageIds;
105
+ return targetLanguages.filter((language) => !languages.includes(language));
106
+ });
107
+ }
108
+ exports.getExcludedTargetLanguages = getExcludedTargetLanguages;
109
+ function filterLanguages(request, files) {
110
+ const result = {};
111
+ for (const fileId in request) {
112
+ const file = files.find((f) => f.id === parseInt(fileId, 10));
113
+ if (file) {
114
+ const excludedLanguages = new Set((file === null || file === void 0 ? void 0 : file.excludedTargetLanguages) || []);
115
+ result[fileId] = request[fileId].filter((lang) => !excludedLanguages.has(lang));
116
+ }
117
+ else {
118
+ result[fileId] = request[fileId];
119
+ }
120
+ }
121
+ return result;
122
+ }
123
+ exports.filterLanguages = filterLanguages;
@@ -79,7 +79,15 @@
79
79
  integration-name="{{name}}"
80
80
  integration-logo="logo.png"
81
81
  {{#if uploadTranslations}}
82
- integration-button-menu-items='[{"label":"Sync translations", "title":"Sync translations to Crowdin", "action":"uploadTranslations"}]'
82
+ {{#if excludedTargetLanguages}}
83
+ integration-button-menu-items='[{"label":"Sync translations", "title":"Sync translations to Crowdin", "action":"uploadTranslations"}, {"label":"Select target languages", "title":"Upload file for translation into selected languages", "action":"excludedTargetLanguages"}]'
84
+ {{else}}
85
+ integration-button-menu-items='[{"label":"Sync translations", "title":"Sync translations to Crowdin", "action":"uploadTranslations"}]'
86
+ {{/if}}
87
+ {{else}}
88
+ {{#if excludedTargetLanguages}}
89
+ integration-button-menu-items='[{"label":"Select target languages", "title":"Upload file for translation into selected languages", "action":"excludedTargetLanguages"}]'
90
+ {{/if}}
83
91
  {{/if}}
84
92
  {{#if filtering.crowdinLanguages}}
85
93
  crowdin-filter
@@ -209,6 +217,15 @@
209
217
  </div>
210
218
  </crowdin-modal>
211
219
 
220
+ {{#if excludedTargetLanguages}}
221
+ <crowdin-modal
222
+ modal-width="50"
223
+ modal-title="Select Target Languages for Translation"
224
+ id="excluded-languages"
225
+ >
226
+ </crowdin-modal>
227
+ {{/if}}
228
+
212
229
  {{#if infoModal}}
213
230
  <crowdin-modal
214
231
  style="display: none;"
@@ -479,6 +496,7 @@
479
496
 
480
497
  let project = {};
481
498
  let crowdinData = [];
499
+ let fileToSync = [];
482
500
 
483
501
  getCrowdinData();
484
502
  getIntegrationData();
@@ -511,6 +529,7 @@
511
529
  item.type = e.type;
512
530
  item.node_type = fileType;
513
531
  item.failed = e.failed;
532
+ item.excludedTargetLanguages = e.excludedTargetLanguages;
514
533
  } else {
515
534
  item.node_type = e.nodeType || folderType;
516
535
  }
@@ -534,6 +553,8 @@
534
553
  languagesSorted.push({...project.inContextPseudoLanguage, inContext: true});
535
554
  }
536
555
 
556
+ setLanguagesForTranslation(languagesSorted);
557
+
537
558
  appComponent.setCrowdinLanguagesData(languagesSorted)
538
559
  })
539
560
  {{#or withCronSync webhooks}}
@@ -636,17 +657,21 @@
636
657
  .catch(e => catchRejection(e, 'Can\'t fetch file progress'));
637
658
  }
638
659
 
639
- function uploadFilesToCrowdin(event) {
660
+ function uploadFilesToCrowdin(event, languages = []) {
640
661
  let files = [];
641
662
  let uploadTranslations = false;
642
- if (event.detail.action === 'uploadTranslations') {
663
+ if (event.detail?.action === 'uploadTranslations') {
643
664
  files = event.detail.files;
644
665
  uploadTranslations = true;
666
+ } else if (event.detail?.action === 'excludedTargetLanguages') {
667
+ fileToSync = event.detail.files;
668
+ openLanguageModal();
669
+ return;
645
670
  } else {
646
671
  files = event.detail;
647
672
  }
648
673
 
649
- if (event.detail.length === 0) {
674
+ if (event.detail?.length === 0 || !event.detail) {
650
675
  showToast('Select templates which will be pushed to Crowdin');
651
676
  return;
652
677
  }
@@ -669,7 +694,7 @@
669
694
  }));
670
695
  {{/if}}
671
696
  checkOrigin()
672
- .then(restParams => fetch(`api/crowdin/update${restParams}&uploadTranslations=${uploadTranslations}`, {
697
+ .then(restParams => fetch(`api/crowdin/update${restParams}&uploadTranslations=${uploadTranslations}&languages=${languages}`, {
673
698
  method: 'POST',
674
699
  headers: { 'Content-Type': 'application/json' },
675
700
  body: JSON.stringify(req)
@@ -844,6 +869,56 @@
844
869
  .catch(e => catchRejection(e, 'Can\'t upload files to {{name}}'))
845
870
  }
846
871
 
872
+ function openLanguageModal() {
873
+ openModal(languageModal);
874
+ }
875
+
876
+ async function uploadFilesToCrowdinWithExcludedLanguages() {
877
+ const languageSelect = document.querySelector('#language-select');
878
+ const selectedLanguages = await languageSelect.getValue();
879
+ closeModal(languageModal);
880
+ uploadFilesToCrowdin({ detail: fileToSync }, selectedLanguages);
881
+ }
882
+
883
+ async function setLanguagesForTranslation(languages) {
884
+ const languageIds = languages.map(language => language.id);
885
+ if (languageModal) {
886
+ let modalContent = `
887
+ <div>
888
+ <crowdin-select
889
+ is-multi
890
+ is-searchable
891
+ is-position-fixed
892
+ close-on-select="false"
893
+ name="languages"
894
+ id="language-select"
895
+ label="Languages"
896
+ >`;
897
+
898
+ for (const language of languages) {
899
+ modalContent += `<option value="${language.id}">${language.name}</option>`;
900
+ }
901
+
902
+ modalContent += `
903
+ </crowdin-select>
904
+ </div>
905
+ <div slot="footer">
906
+ <crowdin-button id="upload-exclude-languages" outlined onclick="uploadFilesToCrowdinWithExcludedLanguages()">
907
+ <div class="integration-icon-button">
908
+ <span>Sync to</span>
909
+ <crowdin-logo is-icon/>
910
+ </div>
911
+ </crowdin-button>
912
+ </div>
913
+ `;
914
+
915
+ languageModal.innerHTML = modalContent;
916
+
917
+ const select = document.querySelector('#language-select')
918
+ await select.setValue(languageIds);
919
+ }
920
+ }
921
+
847
922
  {{#if configurationFields}}
848
923
  const settingsModal = document.getElementById('settings-modal');
849
924
  const settingsSaveBtn = document.getElementById('settings-save-btn');
@@ -1109,6 +1184,12 @@
1109
1184
 
1110
1185
  const permissions = document.getElementById('permissions-modal');
1111
1186
 
1187
+ {{#if excludedTargetLanguages}}
1188
+ const languageModal = document.getElementById('excluded-languages');
1189
+ {{else}}
1190
+ const languageModal = undefined;
1191
+ {{/if}}
1192
+
1112
1193
  {{#if infoModal}}
1113
1194
  const infoModal = document.getElementById('info-modal');
1114
1195
  {{else}}
@@ -1126,6 +1207,9 @@
1126
1207
  if (settingsModal) {
1127
1208
  closeModal(settingsModal);
1128
1209
  }
1210
+ if (languageModal) {
1211
+ closeModal(languageModal);
1212
+ }
1129
1213
  }
1130
1214
  });
1131
1215
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.77.0",
3
+ "version": "0.78.0",
4
4
  "description": "Module that generates for you all common endpoints for serving standalone Crowdin App",
5
5
  "main": "out/index.js",
6
6
  "types": "out/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@aws-sdk/client-s3": "^3.744.0",
23
23
  "@aws-sdk/s3-request-presigner": "^3.744.0",
24
- "@crowdin/crowdin-apps-functions": "^0.10.0",
24
+ "@crowdin/crowdin-apps-functions": "^0.11.0",
25
25
  "@crowdin/logs-formatter": "^2.1.7",
26
26
  "@godaddy/terminus": "^4.12.1",
27
27
  "@monaco-editor/react": "^4.6.0",