@crowdin/app-project-module 0.11.0 → 0.12.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.
Files changed (37) hide show
  1. package/README.md +19 -0
  2. package/out/handlers/crowdin-file-progress.d.ts +2 -1
  3. package/out/handlers/crowdin-file-progress.js +2 -2
  4. package/out/handlers/crowdin-files.js +1 -1
  5. package/out/handlers/crowdin-project.d.ts +2 -1
  6. package/out/handlers/crowdin-project.js +2 -2
  7. package/out/handlers/crowdin-update.js +1 -1
  8. package/out/handlers/custom-file-format/download.d.ts +2 -1
  9. package/out/handlers/custom-file-format/download.js +2 -2
  10. package/out/handlers/custom-file-format/process.d.ts +2 -2
  11. package/out/handlers/custom-file-format/process.js +14 -4
  12. package/out/handlers/custom-mt/translate.d.ts +2 -2
  13. package/out/handlers/custom-mt/translate.js +2 -2
  14. package/out/handlers/install.js +1 -1
  15. package/out/handlers/integration-data.d.ts +2 -2
  16. package/out/handlers/integration-data.js +2 -2
  17. package/out/handlers/integration-login.js +1 -1
  18. package/out/handlers/integration-logout.d.ts +2 -1
  19. package/out/handlers/integration-logout.js +2 -2
  20. package/out/handlers/integration-update.js +1 -1
  21. package/out/handlers/main.js +1 -1
  22. package/out/handlers/oauth-login.js +1 -1
  23. package/out/handlers/settings-save.d.ts +2 -1
  24. package/out/handlers/settings-save.js +2 -2
  25. package/out/handlers/sync-settings-save.d.ts +2 -1
  26. package/out/handlers/sync-settings-save.js +3 -3
  27. package/out/handlers/sync-settings.d.ts +2 -1
  28. package/out/handlers/sync-settings.js +3 -3
  29. package/out/handlers/uninstall.js +1 -1
  30. package/out/index.js +12 -12
  31. package/out/middlewares/crowdin-client.js +1 -1
  32. package/out/middlewares/integration-credentials.js +1 -1
  33. package/out/models/index.d.ts +15 -2
  34. package/out/util/index.d.ts +1 -1
  35. package/out/util/index.js +10 -3
  36. package/out/views/login.handlebars +9 -1
  37. package/package.json +1 -1
package/README.md CHANGED
@@ -36,6 +36,7 @@ In both options you will need to provide Crowdin App configuration file. Please
36
36
  - [Info window](#info-window)
37
37
  - [Background tasks](#background-tasks)
38
38
  - [Error propagation](#error-propagation)
39
+ - [Error interceptor](#error-interceptor)
39
40
  - [Custom File Format](#custom-file-format)
40
41
  - [Custom MT](#custom-mt)
41
42
  - [Resources](#resources)
@@ -385,6 +386,23 @@ configuration.integrartion.getIntegrationFiles = async (credentials, appSettings
385
386
  }
386
387
  ```
387
388
 
389
+ ## Error interceptor
390
+
391
+ You can also provide interceptor to catch errors and process them (e.g. to log them in the centralized place).
392
+
393
+ ```javascript
394
+ const Sentry = require('@sentry/node');
395
+
396
+ Sentry.init({
397
+ dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
398
+ tracesSampleRate: 1.0,
399
+ });
400
+
401
+ configuration.onError = (error) => {
402
+ Sentry.captureException(e);
403
+ };
404
+ ```
405
+
388
406
  ## Custom File Format
389
407
 
390
408
  Example of [custom file format module](https://support.crowdin.com/crowdin-apps-modules/#custom-file-format-module).
@@ -405,6 +423,7 @@ const configuration = {
405
423
  customFileFormat: {
406
424
  type: 'type-xyz',
407
425
  multilingual: false,
426
+ autoUploadTranslations: true, //useful when single language format
408
427
  signaturePatterns: {
409
428
  fileName: '^.+\.xml$'
410
429
  },
@@ -1,3 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- export default function handle(): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config } from '../models';
4
+ export default function handle(config: Config): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -10,11 +10,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const util_1 = require("../util");
13
- function handle() {
13
+ function handle(config) {
14
14
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
15
15
  const fileId = Number(req.params.fileId);
16
16
  const progress = yield req.crowdinApiClient.translationStatusApi.getFileProgress(req.crowdinContext.jwtPayload.context.project_id, fileId);
17
17
  res.send({ [fileId]: progress.data.map(e => e.data) });
18
- }));
18
+ }), config.onError);
19
19
  }
20
20
  exports.default = handle;
@@ -22,6 +22,6 @@ function handle(config, integration) {
22
22
  else {
23
23
  res.send([]);
24
24
  }
25
- }));
25
+ }), config.onError);
26
26
  }
27
27
  exports.default = handle;
@@ -1,3 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- export default function handle(): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config } from '../models';
4
+ export default function handle(config: Config): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -10,10 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const util_1 = require("../util");
13
- function handle() {
13
+ function handle(config) {
14
14
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
15
15
  const project = yield req.crowdinApiClient.projectsGroupsApi.getProject(req.crowdinContext.jwtPayload.context.project_id);
16
16
  res.send(project.data);
17
- }));
17
+ }), config.onError);
18
18
  }
19
19
  exports.default = handle;
@@ -15,6 +15,6 @@ function handle(config, integration) {
15
15
  const rootFolder = yield (0, util_1.getRootFolder)(config, integration, req.crowdinApiClient, req.crowdinContext.jwtPayload.context.project_id);
16
16
  yield integration.updateCrowdin(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, req.integrationCredentials, req.body, rootFolder, req.integrationSettings);
17
17
  res.status(204).end();
18
- }));
18
+ }), config.onError);
19
19
  }
20
20
  exports.default = handle;
@@ -1,3 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Request, Response } from 'express';
3
- export default function handle(folder: string): (req: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config } from '../../models';
4
+ export default function handle(config: Config, folder: string): (req: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -15,7 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const fs_1 = __importDefault(require("fs"));
16
16
  const path_1 = __importDefault(require("path"));
17
17
  const util_1 = require("../../util");
18
- function handle(folder) {
18
+ function handle(config, folder) {
19
19
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
20
20
  const filePath = path_1.default.join(folder, 'custom-file-format', req.query.file);
21
21
  res.download(filePath, function (err) {
@@ -26,6 +26,6 @@ function handle(folder) {
26
26
  fs_1.default.unlinkSync(filePath);
27
27
  }
28
28
  });
29
- }));
29
+ }), config.onError);
30
30
  }
31
31
  exports.default = handle;
@@ -1,4 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Request, Response } from 'express';
3
- import { CustomFileFormatLogic } from '../../models';
4
- export default function handle(baseUrl: string, folder: string, config: CustomFileFormatLogic): (req: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config, CustomFileFormatLogic } from '../../models';
4
+ export default function handle(baseConfig: Config, baseUrl: string, folder: string, config: CustomFileFormatLogic): (req: Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -69,9 +69,19 @@ function handleParseFile(baseUrl, dataFolder, config, file, req) {
69
69
  }
70
70
  }
71
71
  if (res.strings) {
72
- const stringsJson = JSON.stringify(res.strings);
72
+ let strings = res.strings;
73
+ if (config.autoUploadTranslations) {
74
+ strings = strings.map(string => {
75
+ const translations = {};
76
+ req.targetLanguages
77
+ .map(targetLanguage => targetLanguage.id)
78
+ .forEach(targetLanguage => (translations[targetLanguage] = { text: string.text }));
79
+ return Object.assign(Object.assign({}, string), { translations });
80
+ });
81
+ }
82
+ const stringsJson = JSON.stringify(strings);
73
83
  if (Buffer.byteLength(stringsJson, 'utf8') < MAX_BODY_SIZE) {
74
- response.strings = res.strings;
84
+ response.strings = strings;
75
85
  }
76
86
  else {
77
87
  const storedFile = yield storeFile(stringsJson, dataFolder);
@@ -81,7 +91,7 @@ function handleParseFile(baseUrl, dataFolder, config, file, req) {
81
91
  return response;
82
92
  });
83
93
  }
84
- function handle(baseUrl, folder, config) {
94
+ function handle(baseConfig, baseUrl, folder, config) {
85
95
  if (!fs_1.default.existsSync(path_1.default.join(folder, 'custom-file-format'))) {
86
96
  fs_1.default.mkdirSync(path_1.default.join(folder, 'custom-file-format'), { recursive: true });
87
97
  }
@@ -105,6 +115,6 @@ function handle(baseUrl, folder, config) {
105
115
  break;
106
116
  }
107
117
  res.send({ data: response });
108
- }));
118
+ }), baseConfig.onError);
109
119
  }
110
120
  exports.default = handle;
@@ -1,4 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- import { CustomMTLogic } from '../../models';
4
- export default function handle(config: CustomMTLogic): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config, CustomMTLogic } from '../../models';
4
+ export default function handle(baseConfig: Config, config: CustomMTLogic): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const util_1 = require("../../util");
13
- function handle(config) {
13
+ function handle(baseConfig, config) {
14
14
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
15
15
  const source = req.query.source;
16
16
  const target = req.query.target;
@@ -26,6 +26,6 @@ function handle(config) {
26
26
  const translations = yield config.translate(req.crowdinApiClient, req.crowdinContext, projectId, source, target, body.strings);
27
27
  res.send({ data: { translations } });
28
28
  }
29
- }));
29
+ }), baseConfig.onError);
30
30
  }
31
31
  exports.default = handle;
@@ -32,6 +32,6 @@ function handle(config) {
32
32
  yield (0, storage_1.saveCrowdinCredentials)(credentials);
33
33
  }
34
34
  res.status(204).end();
35
- }));
35
+ }), config.onError);
36
36
  }
37
37
  exports.default = handle;
@@ -1,4 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- import { IntegrationLogic } from '../models';
4
- export default function handle(integration: IntegrationLogic): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config, IntegrationLogic } from '../models';
4
+ export default function handle(config: Config, integration: IntegrationLogic): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -10,10 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const util_1 = require("../util");
13
- function handle(integration) {
13
+ function handle(config, integration) {
14
14
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
15
15
  const files = yield integration.getIntegrationFiles(req.integrationCredentials, req.integrationSettings);
16
16
  res.send(files);
17
- }));
17
+ }), config.onError);
18
18
  }
19
19
  exports.default = handle;
@@ -22,6 +22,6 @@ function handle(config, integration) {
22
22
  }
23
23
  yield (0, storage_1.saveIntegrationCredentials)(req.crowdinContext.clientId, (0, util_1.encryptData)(config.clientSecret, JSON.stringify(req.body.credentials)), req.crowdinContext.crowdinId);
24
24
  res.status(204).end();
25
- }));
25
+ }), config.onError);
26
26
  }
27
27
  exports.default = handle;
@@ -1,3 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- export default function handle(): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config } from '../models';
4
+ export default function handle(config: Config): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -11,10 +11,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const storage_1 = require("../storage");
13
13
  const util_1 = require("../util");
14
- function handle() {
14
+ function handle(config) {
15
15
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
16
  yield (0, storage_1.deleteIntegrationCredentials)(req.crowdinContext.clientId);
17
17
  res.status(204).end();
18
- }));
18
+ }), config.onError);
19
19
  }
20
20
  exports.default = handle;
@@ -15,6 +15,6 @@ function handle(config, integration) {
15
15
  const rootFolder = yield (0, util_1.getRootFolder)(config, integration, req.crowdinApiClient, req.crowdinContext.jwtPayload.context.project_id);
16
16
  yield integration.updateIntegration(req.crowdinContext.jwtPayload.context.project_id, req.crowdinApiClient, req.integrationCredentials, req.body, rootFolder, req.integrationSettings);
17
17
  res.status(204).end();
18
- }));
18
+ }), config.onError);
19
19
  }
20
20
  exports.default = handle;
@@ -52,6 +52,6 @@ function handle(config, integration) {
52
52
  options.withCronSync = integration.withCronSync;
53
53
  options.withWebhookSync = integration.withWebhookSync;
54
54
  return res.render(view, options);
55
- }));
55
+ }), config.onError);
56
56
  }
57
57
  exports.default = handle;
@@ -49,6 +49,6 @@ function handle(config, integration) {
49
49
  }
50
50
  message.data = oauthCredentials;
51
51
  return res.render('oauth', { message: JSON.stringify(message) });
52
- }));
52
+ }), config.onError);
53
53
  }
54
54
  exports.default = handle;
@@ -1,3 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- export default function handle(): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config } from '../models';
4
+ export default function handle(config: Config): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -11,10 +11,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const storage_1 = require("../storage");
13
13
  const util_1 = require("../util");
14
- function handle() {
14
+ function handle(config) {
15
15
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
16
  yield (0, storage_1.updateIntegrationConfig)(req.crowdinContext.clientId, JSON.stringify(req.body.config));
17
17
  res.status(204).end();
18
- }));
18
+ }), config.onError);
19
19
  }
20
20
  exports.default = handle;
@@ -1,3 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- export default function handle(): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config } from '../models';
4
+ export default function handle(config: Config): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -9,9 +9,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const util_1 = require("../util");
13
12
  const storage_1 = require("../storage");
14
- function handle() {
13
+ const util_1 = require("../util");
14
+ function handle(config) {
15
15
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
16
  const { files, type, provider } = req.body;
17
17
  const existingSettings = yield (0, storage_1.getSyncSettings)(req.crowdinContext.clientId, req.crowdinContext.crowdinId, type, provider);
@@ -22,6 +22,6 @@ function handle() {
22
22
  yield (0, storage_1.saveSyncSettings)(JSON.stringify(files), req.crowdinContext.clientId, req.crowdinContext.crowdinId, type, provider);
23
23
  }
24
24
  res.status(204).end();
25
- }));
25
+ }), config.onError);
26
26
  }
27
27
  exports.default = handle;
@@ -1,3 +1,4 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- export default function handle(): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
3
+ import { Config } from '../models';
4
+ export default function handle(config: Config): (req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
@@ -9,9 +9,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const util_1 = require("../util");
13
12
  const storage_1 = require("../storage");
14
- function handle() {
13
+ const util_1 = require("../util");
14
+ function handle(config) {
15
15
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
16
  let files = {};
17
17
  const provider = req.params.provider;
@@ -20,6 +20,6 @@ function handle() {
20
20
  files = JSON.parse(syncSettings.files) || [];
21
21
  }
22
22
  res.send(files);
23
- }));
23
+ }), config.onError);
24
24
  }
25
25
  exports.default = handle;
@@ -20,6 +20,6 @@ function handle(config) {
20
20
  yield config.onUninstall(organization);
21
21
  }
22
22
  res.status(204).end();
23
- }));
23
+ }), config.onError);
24
24
  }
25
25
  exports.default = handle;
package/out/index.js CHANGED
@@ -48,12 +48,12 @@ const integration_data_1 = __importDefault(require("./handlers/integration-data"
48
48
  const integration_login_1 = __importDefault(require("./handlers/integration-login"));
49
49
  const integration_logout_1 = __importDefault(require("./handlers/integration-logout"));
50
50
  const integration_update_1 = __importDefault(require("./handlers/integration-update"));
51
- const sync_settings_save_1 = __importDefault(require("./handlers/sync-settings-save"));
52
- const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
53
51
  const main_1 = __importDefault(require("./handlers/main"));
54
52
  const manifest_1 = __importDefault(require("./handlers/manifest"));
55
53
  const oauth_login_1 = __importDefault(require("./handlers/oauth-login"));
56
54
  const settings_save_1 = __importDefault(require("./handlers/settings-save"));
55
+ const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
56
+ const sync_settings_save_1 = __importDefault(require("./handlers/sync-settings-save"));
57
57
  const uninstall_1 = __importDefault(require("./handlers/uninstall"));
58
58
  const crowdin_client_1 = __importStar(require("./middlewares/crowdin-client"));
59
59
  const integration_credentials_1 = __importDefault(require("./middlewares/integration-credentials"));
@@ -86,17 +86,17 @@ function addCrowdinEndpoints(app, config) {
86
86
  if (integrationLogic) {
87
87
  (0, util_1.applyDefaults)(config, integrationLogic);
88
88
  app.get('/', (0, crowdin_client_1.default)(config, true), (0, integration_credentials_1.default)(config, integrationLogic, true), (0, main_1.default)(config, integrationLogic));
89
- app.post('/api/settings', (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, settings_save_1.default)());
89
+ app.post('/api/settings', (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, settings_save_1.default)(config));
90
90
  app.post('/api/login', (0, crowdin_client_1.default)(config), (0, integration_login_1.default)(config, integrationLogic));
91
- app.post('/api/logout', (0, crowdin_client_1.default)(config), (0, integration_logout_1.default)());
91
+ app.post('/api/logout', (0, crowdin_client_1.default)(config), (0, integration_logout_1.default)(config));
92
92
  app.get('/api/crowdin/files', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, crowdin_files_1.default)(config, integrationLogic));
93
- app.get('/api/crowdin/project', json_response_1.default, (0, crowdin_client_1.default)(config), (0, crowdin_project_1.default)());
94
- app.get('/api/crowdin/file-progress/:fileId', (0, crowdin_client_1.default)(config), (0, crowdin_file_progress_1.default)());
95
- app.get('/api/integration/data', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, integration_data_1.default)(integrationLogic));
93
+ app.get('/api/crowdin/project', json_response_1.default, (0, crowdin_client_1.default)(config), (0, crowdin_project_1.default)(config));
94
+ app.get('/api/crowdin/file-progress/:fileId', (0, crowdin_client_1.default)(config), (0, crowdin_file_progress_1.default)(config));
95
+ app.get('/api/integration/data', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, integration_data_1.default)(config, integrationLogic));
96
96
  app.post('/api/crowdin/update', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, crowdin_update_1.default)(config, integrationLogic));
97
97
  app.post('/api/integration/update', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, integration_update_1.default)(config, integrationLogic));
98
- app.get('/api/sync-settings/:provider', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, sync_settings_1.default)());
99
- app.post('/api/sync-settings', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, sync_settings_save_1.default)());
98
+ app.get('/api/sync-settings/:provider', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, sync_settings_1.default)(config));
99
+ app.post('/api/sync-settings', json_response_1.default, (0, crowdin_client_1.default)(config), (0, integration_credentials_1.default)(config, integrationLogic), (0, sync_settings_save_1.default)(config));
100
100
  if (integrationLogic.oauthLogin) {
101
101
  app.get((0, util_1.getOauthRoute)(integrationLogic), (0, oauth_login_1.default)(config, integrationLogic));
102
102
  }
@@ -114,11 +114,11 @@ function addCrowdinEndpoints(app, config) {
114
114
  }
115
115
  }
116
116
  if (config.customFileFormat) {
117
- app.post('/process', (0, process_1.default)(config.baseUrl, config.customFileFormat.filesFolder || config.dbFolder, config.customFileFormat));
118
- app.get('/file/download', (0, download_1.default)(config.customFileFormat.filesFolder || config.dbFolder));
117
+ app.post('/process', (0, process_1.default)(config, config.baseUrl, config.customFileFormat.filesFolder || config.dbFolder, config.customFileFormat));
118
+ app.get('/file/download', (0, download_1.default)(config, config.customFileFormat.filesFolder || config.dbFolder));
119
119
  }
120
120
  if (config.customMT) {
121
- app.post('/translate', (0, crowdin_client_1.default)(config), (0, translate_1.default)(config.customMT));
121
+ app.post('/translate', (0, crowdin_client_1.default)(config), (0, translate_1.default)(config, config.customMT));
122
122
  }
123
123
  if (config.resources) {
124
124
  app.use('/resources', express_1.default.static(config.resources.uiPath));
@@ -63,6 +63,6 @@ function handle(config, optional = false) {
63
63
  const message = typeof e === 'string' ? e : e.message;
64
64
  return res.status(403).send({ error: message || 'Error' });
65
65
  }
66
- }));
66
+ }), config.onError);
67
67
  }
68
68
  exports.default = handle;
@@ -32,6 +32,6 @@ function handle(config, integration, optional = false) {
32
32
  throw new util_1.CodeError('Credentials to integration either exprired or invalid', 401);
33
33
  }
34
34
  next();
35
- }));
35
+ }), config.onError);
36
36
  }
37
37
  exports.default = handle;
@@ -74,6 +74,10 @@ export interface Config {
74
74
  * Uninstall hook for cleanup logic
75
75
  */
76
76
  onUninstall?: (organization: string) => Promise<void>;
77
+ /**
78
+ * Error interceptor (can be used to log error in centralized place)
79
+ */
80
+ onError?: (error: any) => void;
77
81
  }
78
82
  export interface IntegrationLogic {
79
83
  /**
@@ -329,9 +333,13 @@ export interface CustomFileFormatLogic {
329
333
  */
330
334
  multilingual?: boolean;
331
335
  /**
332
- * Contains fileName and/or fileContent regular expressions used to detect file type when uploading a new source file via UI (or via API without specified type parameter). If the file matches regular expressions, it's labeled as a custom format file.
336
+ * Contains fileName and/or fileContent regular expressions used to detect file type when uploading a new source file via UI (or via API without specified type parameter). If the file matches regular expressions, it's labeled as a custom format file.
333
337
  */
334
338
  signaturePatterns?: SignaturePatterns;
339
+ /**
340
+ * Flag to automatically upload translations
341
+ */
342
+ autoUploadTranslations?: boolean;
335
343
  /**
336
344
  * Used for initial source file upload, source file update, and translation upload
337
345
  */
@@ -379,7 +387,12 @@ export interface ProcessFileString {
379
387
  hasPlurals?: boolean;
380
388
  labels?: string[];
381
389
  text: string | SourceStringsModel.PluralText;
382
- translations?: any;
390
+ translations?: StringTranslations;
391
+ }
392
+ export interface StringTranslations {
393
+ [language: string]: {
394
+ text: string | SourceStringsModel.PluralText;
395
+ };
383
396
  }
384
397
  export interface CustomMTLogic {
385
398
  translate: (client: Crowdin, context: CrowdinContextInfo, projectId: number, source: string, target: string, strings: string[]) => Promise<string[]>;
@@ -5,7 +5,7 @@ export declare class CodeError extends Error {
5
5
  code: number | undefined;
6
6
  constructor(message: string, code?: number);
7
7
  }
8
- export declare function runAsyncWrapper(callback: Function): (req: Request, res: Response, next: Function) => void;
8
+ export declare function runAsyncWrapper(callback: Function, onError?: (e: any) => void): (req: Request, res: Response, next: Function) => void;
9
9
  export declare function encryptData(secret: string, data: string): string;
10
10
  export declare function decryptData(secret: string, data: string): string;
11
11
  export declare function getOauthRoute(integration: IntegrationLogic): string;
package/out/util/index.js CHANGED
@@ -50,7 +50,6 @@ function isCrowdinClientRequest(req) {
50
50
  }
51
51
  function handleError(err, req, res) {
52
52
  return __awaiter(this, void 0, void 0, function* () {
53
- console.error(err);
54
53
  const code = err.code ? err.code : 500;
55
54
  if (code === 401 && isCrowdinClientRequest(req)) {
56
55
  yield (0, storage_1.deleteIntegrationCredentials)(req.crowdinContext.clientId);
@@ -72,9 +71,17 @@ function handleError(err, req, res) {
72
71
  res.status(code).send({ message, code });
73
72
  });
74
73
  }
75
- function runAsyncWrapper(callback) {
74
+ function runAsyncWrapper(callback, onError) {
76
75
  return (req, res, next) => {
77
- callback(req, res, next).catch((e) => handleError(e, req, res));
76
+ callback(req, res, next).catch((e) => {
77
+ if (onError) {
78
+ onError(e);
79
+ }
80
+ else {
81
+ console.error(e);
82
+ }
83
+ handleError(e, req, res);
84
+ });
78
85
  };
79
86
  }
80
87
  exports.runAsyncWrapper = runAsyncWrapper;
@@ -61,6 +61,8 @@
61
61
  <crowdin-toasts></crowdin-toasts>
62
62
  </body>
63
63
  <script type="text/javascript">
64
+ const loginButton = document.querySelector('crowdin-button');
65
+
64
66
  function oauthLogin() {
65
67
  const url = '{{{ oauthUrl }}}';
66
68
  const oauthWindow = window.open(url, '{{ name }}', 'location=0,status=0,width=800,height=400');
@@ -86,6 +88,8 @@
86
88
  {{/ifeq}}
87
89
  {{/each}}
88
90
  };
91
+ loginButton.setAttribute('disabled', true);
92
+ loginButton.setAttribute('is-loading', true);
89
93
  checkOrigin()
90
94
  .then(queryParams =>
91
95
  fetch(`api/login${queryParams}`, {
@@ -96,7 +100,11 @@
96
100
  )
97
101
  .then(checkResponse)
98
102
  .then(reloadLocation)
99
- .catch(e => catchRejection(e, 'Credentials are not stored'));
103
+ .catch(e => catchRejection(e, 'Credentials are not stored'))
104
+ .finally(() => {
105
+ loginButton.setAttribute('disabled', false);
106
+ loginButton.setAttribute('is-loading', false);
107
+ });
100
108
  }
101
109
  </script>
102
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.11.0",
3
+ "version": "0.12.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",