@crowdin/app-project-module 0.17.4 → 0.17.6

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 (79) hide show
  1. package/.github/workflows/basic.yml +39 -0
  2. package/.github/workflows/publish.yml +34 -0
  3. package/README.md +2 -1
  4. package/package.json +1 -1
  5. package/out/handlers/crowdin-file-progress.d.ts +0 -4
  6. package/out/handlers/crowdin-file-progress.js +0 -22
  7. package/out/handlers/crowdin-files.d.ts +0 -4
  8. package/out/handlers/crowdin-files.js +0 -31
  9. package/out/handlers/crowdin-project.d.ts +0 -4
  10. package/out/handlers/crowdin-project.js +0 -22
  11. package/out/handlers/crowdin-update.d.ts +0 -4
  12. package/out/handlers/crowdin-update.js +0 -26
  13. package/out/handlers/custom-file-format/download.d.ts +0 -4
  14. package/out/handlers/custom-file-format/download.js +0 -32
  15. package/out/handlers/custom-file-format/process.d.ts +0 -4
  16. package/out/handlers/custom-file-format/process.js +0 -134
  17. package/out/handlers/custom-mt/translate.d.ts +0 -4
  18. package/out/handlers/custom-mt/translate.js +0 -42
  19. package/out/handlers/install.d.ts +0 -4
  20. package/out/handlers/install.js +0 -45
  21. package/out/handlers/integration-data.d.ts +0 -4
  22. package/out/handlers/integration-data.js +0 -21
  23. package/out/handlers/integration-login.d.ts +0 -4
  24. package/out/handlers/integration-login.js +0 -30
  25. package/out/handlers/integration-logout.d.ts +0 -4
  26. package/out/handlers/integration-logout.js +0 -23
  27. package/out/handlers/integration-update.d.ts +0 -4
  28. package/out/handlers/integration-update.js +0 -25
  29. package/out/handlers/main.d.ts +0 -4
  30. package/out/handlers/main.js +0 -64
  31. package/out/handlers/manifest.d.ts +0 -3
  32. package/out/handlers/manifest.js +0 -126
  33. package/out/handlers/oauth-login.d.ts +0 -4
  34. package/out/handlers/oauth-login.js +0 -69
  35. package/out/handlers/settings-save.d.ts +0 -4
  36. package/out/handlers/settings-save.js +0 -21
  37. package/out/handlers/subscription-info.d.ts +0 -3
  38. package/out/handlers/subscription-info.js +0 -15
  39. package/out/handlers/subscription-paid.d.ts +0 -4
  40. package/out/handlers/subscription-paid.js +0 -22
  41. package/out/handlers/sync-settings-save.d.ts +0 -4
  42. package/out/handlers/sync-settings-save.js +0 -29
  43. package/out/handlers/sync-settings.d.ts +0 -4
  44. package/out/handlers/sync-settings.js +0 -27
  45. package/out/handlers/uninstall.d.ts +0 -4
  46. package/out/handlers/uninstall.js +0 -27
  47. package/out/index.d.ts +0 -5
  48. package/out/index.js +0 -191
  49. package/out/logo.png +0 -0
  50. package/out/middlewares/crowdin-client.d.ts +0 -10
  51. package/out/middlewares/crowdin-client.js +0 -88
  52. package/out/middlewares/integration-credentials.d.ts +0 -4
  53. package/out/middlewares/integration-credentials.js +0 -39
  54. package/out/middlewares/json-response.d.ts +0 -2
  55. package/out/middlewares/json-response.js +0 -7
  56. package/out/middlewares/ui-module.d.ts +0 -4
  57. package/out/middlewares/ui-module.js +0 -39
  58. package/out/models/index.d.ts +0 -538
  59. package/out/models/index.js +0 -41
  60. package/out/static/css/styles.css +0 -57
  61. package/out/static/js/main.js +0 -130
  62. package/out/static/js/polyfills/fetch.js +0 -494
  63. package/out/static/js/polyfills/promise.js +0 -375
  64. package/out/storage/index.d.ts +0 -22
  65. package/out/storage/index.js +0 -319
  66. package/out/util/connection.d.ts +0 -10
  67. package/out/util/connection.js +0 -213
  68. package/out/util/cron.d.ts +0 -3
  69. package/out/util/cron.js +0 -103
  70. package/out/util/defaults.d.ts +0 -5
  71. package/out/util/defaults.js +0 -153
  72. package/out/util/index.d.ts +0 -11
  73. package/out/util/index.js +0 -105
  74. package/out/views/install.handlebars +0 -16
  75. package/out/views/login.handlebars +0 -115
  76. package/out/views/main.handlebars +0 -471
  77. package/out/views/oauth.handlebars +0 -4
  78. package/out/views/partials/head.handlebars +0 -20
  79. package/out/views/subscription.handlebars +0 -26
@@ -1,471 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- {{> head}}
4
-
5
- <body>
6
- <div class="i_w">
7
- <div class="top">
8
- {{#if checkSubscription}}
9
- <crowdin-alert id="subscription-info" no-icon="true" type="warning" style="display: none;">
10
- <div class="box-center">
11
- <p class="m-0"></p>
12
- <crowdin-button class="ml-1" primary onclick="window.open(subscriptionLink,'_blank')">Subscribe</crowdin-button>
13
- </div>
14
- </crowdin-alert>
15
- {{/if}}
16
- {{#if infoModal}}
17
- <crowdin-button icon-before="info" onclick="infoModal.open();">{{infoModal.title}}</crowdin-button>
18
- {{/if}}
19
- {{#if configurationFields}}
20
- <crowdin-button icon-before="settings" onclick="settingsModal.open();fillSettingsForm();">Settings</crowdin-button>
21
- {{/if}}
22
- <crowdin-button icon-before="account_circle" onclick="integrationLogout()">Log out</crowdin-button>
23
- </div>
24
- <crowdin-simple-integration
25
- {{#if withWebhookSync.crowdin}}
26
- crowdin-sync="true"
27
- {{/if}}
28
- {{#if withWebhookSync.integration}}
29
- integration-sync="true"
30
- {{/if}}
31
- {{#if withCronSync.crowdin}}
32
- crowdin-schedule="true"
33
- {{/if}}
34
- {{#if withCronSync.integration}}
35
- integration-schedule="true"
36
- {{/if}}
37
- integration-name="{{name}}"
38
- integration-logo="logo.png"
39
- >
40
- </crowdin-simple-integration>
41
- </div>
42
- <crowdin-toasts></crowdin-toasts>
43
- <crowdin-modal id="subscription-modal" modal-width="50" close-button="false">
44
- <div>
45
- <crowdin-alert type="warning">Subscribe to continue using the {{name}} app.</crowdin-alert>
46
- </div>
47
- <div slot="footer">
48
- <crowdin-button primary onclick="window.open(subscriptionLink,'_blank')">Subscribe</crowdin-button>
49
- <crowdin-button outlined onclick="window.open('https://crowdin.com/contacts','_blank')">Contact us</crowdin-button>
50
- <crowdin-button class="ml-10" secondary onclick="integrationLogout()">Log out</crowdin-button>
51
- </div>
52
- </crowdin-modal>
53
- {{#if infoModal}}
54
- <crowdin-modal id="info-modal" modal-width="50" modal-title="{{infoModal.title}}" close-button-title="Close">
55
- <div>
56
- {{{infoModal.content}}}
57
- </div>
58
- </crowdin-modal>
59
- {{/if}}
60
- {{#if configurationFields}}
61
- <crowdin-modal
62
- id="settings-modal"
63
- body-overflow-unset="{{#checkLength configurationFields 3}}false{{else}}true{{/checkLength}}"
64
- modal-width="50"
65
- modal-title="Settings"
66
- close-button-title="Close"
67
- >
68
- <div id="modal-content">
69
- {{#each configurationFields}}
70
- {{#if key}}
71
- {{#ifeq type "checkbox"}}
72
- <crowdin-checkbox
73
- use-switch
74
- label="{{label}}"
75
- value="false"
76
- id="{{key}}-settings"
77
- key="{{key}}"
78
- {{#if helpText}}
79
- help-text="{{helpText}}"
80
- {{/if}}
81
- >
82
- </crowdin-checkbox>
83
- {{/ifeq}}
84
- {{#ifeq type "select"}}
85
- <crowdin-select
86
- {{#if isMulti}}
87
- is-multi
88
- close-on-select="false"
89
- {{/if}}
90
- id="{{key}}-settings"
91
- key="{{key}}"
92
- label="{{label}}"
93
- {{#if helpText}}
94
- help-text="{{helpText}}"
95
- {{/if}}
96
- >
97
- {{#each options}}
98
- <option value="{{value}}">{{label}}</option>
99
- {{/each}}
100
- </crowdin-select>
101
- {{/ifeq}}
102
- {{#ifeq type "text"}}
103
- <crowdin-input
104
- with-fixed-height
105
- label="{{label}}"
106
- value=""
107
- id="{{key}}-settings"
108
- key="{{key}}"
109
- {{#if helpText}}
110
- help-text="{{helpText}}"
111
- {{/if}}
112
- >
113
- </crowdin-input>
114
- {{/ifeq}}
115
- {{else}}
116
- <crowdin-p>{{label}}</crowdin-p>
117
- {{/if}}
118
- <div style="padding: 8px"></div>
119
- {{/each}}
120
- </div>
121
- <div slot="footer">
122
- <crowdin-button id="settings-save-btn" outlined onclick="saveSettings()">Save</crowdin-button>
123
- </div>
124
- </crowdin-modal>
125
- {{/if}}
126
- </body>
127
- <script type="text/javascript">
128
- document.body.addEventListener('refreshFilesList', (e) => {
129
- if (e.detail.refreshIntegration) {
130
- getIntegrationData();
131
- } else if (e.detail.refreshCrowdin) {
132
- getCrowdinData();
133
- }
134
- });
135
- document.body.addEventListener('crowdinFilesFolderToggled', (event) => {
136
- if (event.detail.componentId === 'crowdin-files' && event.detail.isOpen && event.detail.type === '1') {
137
- getFileProgress(event.detail.id);
138
- }
139
- });
140
- document.body.addEventListener('uploadFilesToCrowdin', uploadFilesToCrowdin);
141
- document.body.addEventListener('uploadFilesToIntegration', uploadFilesToIntegration);
142
-
143
- const appComponent = document.querySelector('crowdin-simple-integration');
144
- const subscriptionModal = document.getElementById('subscription-modal');
145
-
146
- const folderType = '0';
147
- const fileType = '1';
148
-
149
- let project = {};
150
- let crowdinData = [];
151
-
152
- getCrowdinData();
153
- getIntegrationData();
154
-
155
- function integrationLogout() {
156
- checkOrigin()
157
- .then(queryParams => fetch(`api/logout${queryParams}`, { method: 'POST' }))
158
- .then(checkResponse)
159
- .then(reloadLocation)
160
- .catch(e => catchRejection(e, 'Looks like you are not logged in'));
161
- }
162
-
163
- function getCrowdinData() {
164
- appComponent.setAttribute('is-crowdin-loading', true);
165
- checkOrigin()
166
- .then(restParams => fetch('api/crowdin/files' + restParams))
167
- .then(checkResponse)
168
- .then((res) => {
169
- const tree = res.map(e => {
170
- const item = {
171
- parent_id: e.parentId ? e.parentId : '0',
172
- name: e.name,
173
- id: e.id,
174
- };
175
- if (e.type) {
176
- item.type = e.type;
177
- item.node_type = fileType;
178
- } else {
179
- item.node_type = folderType;
180
- }
181
- return item;
182
- });
183
- crowdinData = tree;
184
- appComponent.setCrowdinFilesData(tree);
185
- return checkOrigin();
186
- })
187
- .then(restParams => fetch('api/crowdin/project' + restParams))
188
- .then(checkResponse)
189
- .then((res) => {
190
- project = res;
191
- appComponent.setCrowdinLanguagesData(project.targetLanguages)
192
- })
193
- {{#if withCronSync}}
194
- .then(() => getSyncSettings('crowdin'))
195
- {{/if}}
196
- .catch(e => catchRejection(e, 'Can\'t fetch Crowdin data'))
197
- .finally(() => (appComponent.setAttribute('is-crowdin-loading', false)));
198
- }
199
-
200
- function getIntegrationData() {
201
- appComponent.setAttribute('is-integration-loading', true);
202
- checkOrigin()
203
- .then(restParams => fetch('api/integration/data' + restParams))
204
- .then(checkResponse)
205
- .then((res) => {
206
- const tree = res.map(e => {
207
- const item = {
208
- parent_id: e.parentId ? e.parentId : '0',
209
- name: e.name,
210
- id: e.id,
211
- };
212
- if (e.type) {
213
- item.type = e.type;
214
- item.node_type = fileType;
215
- } else {
216
- item.node_type = folderType;
217
- }
218
- return item;
219
- });
220
- appComponent.setIntegrationFilesData(tree);
221
- })
222
- {{#if withCronSync}}
223
- .then(() => getSyncSettings('integration'))
224
- {{/if}}
225
- .catch(e => catchRejection(e, 'Can\'t fetch {{name}} templates'))
226
- .finally(() => (appComponent.setAttribute('is-integration-loading', false)));
227
- }
228
-
229
- function getSyncSettings(provider) {
230
- checkOrigin()
231
- .then(restParams => fetch(`/api/sync-settings/${provider}` + restParams))
232
- .then(checkResponse)
233
- .then((res) => {
234
- if (provider === 'crowdin') {
235
- appComponent.setCrowdinScheduleSync(res);
236
- } else {
237
- appComponent.setIntegrationScheduleSync(res);
238
- }
239
- })
240
- .catch(e => catchRejection(e, 'Can\'t fetch file progress'));
241
- }
242
-
243
- function getFileProgress(fileId) {
244
- checkOrigin()
245
- .then(restParams => fetch(`api/crowdin/file-progress/${fileId}` + restParams))
246
- .then(checkResponse)
247
- .then((res) => (appComponent.addCrowdinFileProgress(res)))
248
- .catch(e => catchRejection(e, 'Can\'t fetch file progress'));
249
- }
250
-
251
- function uploadFilesToCrowdin(e) {
252
- if (e.detail.length === 0) {
253
- showToast('Select templates which will be pushed to Crowdin');
254
- return;
255
- }
256
- appComponent.setAttribute('is-to-crowdin-process', true);
257
- const req = e.detail.filter(f => f.node_type !== folderType).map(f => ({
258
- name: f.name,
259
- id: f.id,
260
- type: f.type,
261
- parentId: f.parent_id
262
- }));
263
- checkOrigin()
264
- .then(restParams => fetch('api/crowdin/update' + restParams, {
265
- method: 'POST',
266
- headers: { 'Content-Type': 'application/json' },
267
- body: JSON.stringify(req)
268
- }))
269
- .then(checkResponse)
270
- .then(() => showToast(`File${e.detail.length > 1 ? 's' : ''} imported successfully`))
271
- .then(getCrowdinData)
272
- .catch(e => catchRejection(e, 'Can\'t upload templates to Crowdin'))
273
- .finally(() => (appComponent.setAttribute('is-to-crowdin-process', false)));
274
- }
275
-
276
- function uploadFilesToIntegration(e) {
277
- if (Object.keys(e.detail).length === 0) {
278
- showToast('Select files which will be uploaded to {{name}}');
279
- return;
280
- }
281
- appComponent.setAttribute('is-to-integration-process', true);
282
- const req = {};
283
- Object.keys(e.detail)
284
- .filter(id => crowdinData.find(c => c.id === id).node_type === fileType)
285
- .forEach(id => (req[id] = e.detail[id]));
286
- checkOrigin()
287
- .then(restParams => fetch('api/integration/update' + restParams, {
288
- method: 'POST',
289
- headers: { 'Content-Type': 'application/json' },
290
- body: JSON.stringify(req)
291
- }))
292
- .then(checkResponse)
293
- .then(() => showToast(`File${Object.keys(e.detail).length > 1 ? 's' : ''} exported successfully`))
294
- .catch(e => catchRejection(e, 'Can\'t upload files to {{name}}'))
295
- .finally(() => (appComponent.setAttribute('is-to-integration-process', false)));
296
- }
297
-
298
- {{#if configurationFields}}
299
- const settingsModal = document.getElementById('settings-modal');
300
- const settingsSaveBtn = document.getElementById('settings-save-btn');
301
- let config = JSON.parse('{{{config}}}');
302
-
303
- function fillSettingsForm() {
304
- Object.entries(config).forEach(([key, value]) => {
305
- if (value) {
306
- const el = document.getElementById(`${key}-settings`);
307
- if (el.tagName.toLowerCase() === 'crowdin-select') {
308
- if (el.hasAttribute('is-multi')) {
309
- el.value = JSON.stringify(value);
310
- } else {
311
- el.value = JSON.stringify([value]);
312
- }
313
- } else if (el.tagName.toLowerCase() === 'crowdin-checkbox') {
314
- el.checked = !!value;
315
- } else {
316
- el.value = value;
317
- }
318
- }
319
- });
320
- {{#if withCronSync}}
321
- const sheduleSettings = document.querySelector('#schedule-settings');
322
- if (JSON.parse(sheduleSettings.value).length <= 0) {
323
- sheduleSettings.value = JSON.stringify(["0"]);
324
- }
325
- {{/if}}
326
- }
327
-
328
- function saveSettings() {
329
- const settingsElements = Array.from(document.getElementById('modal-content').children);
330
- const tags = ['crowdin-checkbox', 'crowdin-select', 'crowdin-input'];
331
- const configReq = {};
332
- settingsElements
333
- .filter(e => tags.includes(e.tagName.toLowerCase()))
334
- .forEach(e => {
335
- const key = e.getAttribute('key');
336
- let value;
337
- if (e.tagName.toLowerCase() === 'crowdin-select') {
338
- value = JSON.parse(e.value);
339
- if (!e.hasAttribute('is-multi')) {
340
- value = value.length > 0 ? value[0] : undefined;
341
- }
342
- } else if (e.tagName.toLowerCase() === 'crowdin-checkbox') {
343
- value = !!e.checked;
344
- } else {
345
- value = e.value;
346
- }
347
- configReq[key] = value;
348
- });
349
- settingsSaveBtn.setAttribute('disabled', true);
350
- checkOrigin()
351
- .then(restParams => fetch('api/settings' + restParams, {
352
- method: 'POST',
353
- headers: { 'Content-Type': 'application/json' },
354
- body: JSON.stringify({ config: configReq })
355
- }))
356
- .then(checkResponse)
357
- .then(() => {
358
- showToast('Settings successfully saved');
359
- config = configReq;
360
- })
361
- .catch(e => catchRejection(e, 'Can\'t save settings'))
362
- .finally(() => {
363
- settingsSaveBtn.removeAttribute('disabled');
364
- settingsModal.close();
365
- {{#if reloadOnConfigSave}}
366
- getIntegrationData();
367
- getCrowdinData();
368
- {{/if}}
369
- });
370
- }
371
- {{/if}}
372
-
373
- {{#if infoModal}}
374
- const infoModal = document.getElementById('info-modal');
375
- {{/if}}
376
-
377
- document.addEventListener('keydown', (event) => {
378
- if (event.keyCode == 27) {
379
- if (infoModal) {
380
- infoModal.close();
381
- }
382
- if (settingsModal) {
383
- settingsModal.close();
384
- }
385
- }
386
- });
387
-
388
- {{#if withCronSync}}
389
- document.body.addEventListener('integrationScheduleSync', setIntegrationScheduleSync);
390
- document.body.addEventListener('crowdinScheduleSync', setCrowdinScheduleSync);
391
- document.body.addEventListener('crowdinDisableSync', disableCrowdinSync);
392
- document.body.addEventListener('integrationDisableSync', disableIntegrationSync);
393
-
394
- async function setIntegrationScheduleSync(e) {
395
- if (e.detail.length === 0) {
396
- showToast('Select templates which will be pushed to Crowdin');
397
- return;
398
- }
399
- appComponent.setAttribute('is-integration-loading', true);
400
- const syncedFiles = await appComponent.getIntegrationScheduleSync();
401
-
402
- updateSyncSettings(syncedFiles, 'schedule', 'integration');
403
- }
404
-
405
- async function setCrowdinScheduleSync(e) {
406
- if (e.detail.length === 0) {
407
- showToast('Select templates which will be pushed to Crowdin');
408
- return;
409
- }
410
- appComponent.setAttribute('is-crowdin-loading', true);
411
- const syncedFiles = await appComponent.getCrowdinScheduleSync();
412
-
413
- updateSyncSettings(syncedFiles, 'schedule', 'crowdin');
414
- }
415
-
416
- async function disableIntegrationSync(e) {
417
- if (e.detail.length === 0) {
418
- showToast('Select templates which will be pushed to Crowdin');
419
- return;
420
- }
421
- appComponent.setAttribute('is-integration-loading', true);
422
- const syncedFiles = await appComponent.getIntegrationScheduleSync();
423
-
424
- updateSyncSettings(syncedFiles, 'schedule', 'integration');
425
- }
426
-
427
- async function disableCrowdinSync(e) {
428
- if (e.detail.length === 0) {
429
- showToast('Select templates which will be pushed to Crowdin');
430
- return;
431
- }
432
- appComponent.setAttribute('is-crowdin-loading', true);
433
- const syncedFiles = await appComponent.getCrowdinScheduleSync();
434
-
435
- updateSyncSettings(syncedFiles, 'schedule', 'crowdin');
436
- }
437
-
438
- function updateSyncSettings(files, type, provider) {
439
- checkOrigin()
440
- .then(restParams => fetch('api/sync-settings' + restParams, {
441
- method: 'POST',
442
- headers: { 'Content-Type': 'application/json' },
443
- body: JSON.stringify({ files, type, provider })
444
- }))
445
- .then(checkResponse)
446
- .catch(e => catchRejection(e, 'Can\'t save schedule sync settings'))
447
- .finally(() => (appComponent.setAttribute(`is-${provider}-loading`, false)));
448
- }
449
- {{/if}}
450
-
451
- {{#if checkSubscription}}
452
- const subscriptionInfo = document.getElementById('subscription-info');
453
- const subscriptionInfoText = subscriptionInfo.getElementsByTagName('p')[0];
454
- function getSubscriptionInfo() {
455
- checkOrigin()
456
- .then(restParams => fetch('api/subscription-info' + restParams))
457
- .then(checkResponse)
458
- .then((res) => {
459
- if (res.showInfo) {
460
- subscriptionLink = res.subscribeLink;
461
- subscriptionInfoText.textContent = `Your trial expires in ${res.daysLeft} days.`
462
- subscriptionInfo.style.display = 'block';
463
- }
464
- });
465
- }
466
-
467
- getSubscriptionInfo();
468
- {{/if}}
469
- </script>
470
-
471
- </html>
@@ -1,4 +0,0 @@
1
- <script type="text/javascript">
2
- window.opener.postMessage('{{{message}}}');
3
- window.close();
4
- </script>
@@ -1,20 +0,0 @@
1
- <head>
2
- <meta charset="UTF-8">
3
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
5
- <title></title>
6
- <link rel="stylesheet" href="assets/css/styles.css">
7
- <script type="module"
8
- src="https://crowdin-web-components.s3.amazonaws.com/crowdin-web-components/crowdin-web-components.esm.js"></script>
9
- <script nomodule=""
10
- src="https://crowdin-web-components.s3.amazonaws.com/crowdin-web-components/crowdin-web-components.js"></script>
11
- <script type="text/javascript" src="https://cdn.crowdin.com/apps/dist/iframe.js"></script>
12
- <script type="text/javascript" src="assets/js/polyfills/promise.js"></script>
13
- <script type="text/javascript" src="assets/js/polyfills/fetch.js"></script>
14
- <script type="text/javascript" src="assets/js/main.js"></script>
15
- <style>
16
- .ml-10 {
17
- margin-left: 10px;
18
- }
19
- </style>
20
- </head>
@@ -1,26 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <meta http-equiv="X-UA-Compatible" content="ie=edge">
8
- <title></title>
9
- <script type="module"
10
- src="https://crowdin-web-components.s3.amazonaws.com/crowdin-web-components/crowdin-web-components.esm.js"></script>
11
- <script nomodule=""
12
- src="https://crowdin-web-components.s3.amazonaws.com/crowdin-web-components/crowdin-web-components.js"></script>
13
- <script type="text/javascript" src="https://cdn.crowdin.com/apps/dist/iframe.js"></script>
14
- </head>
15
-
16
- <body>
17
- <div class="i_w center">
18
- <crowdin-alert type="warning">Subscribe to continue using the {{name}} app.</crowdin-alert>
19
- <br/>
20
- <crowdin-button primary onclick="window.open('{{subscribeLink}}','_blank')">Subscribe</crowdin-button>
21
- <crowdin-button outlined onclick="window.open('https://crowdin.com/contacts','_blank')">Contact us
22
- </crowdin-button>
23
- </div>
24
- </body>
25
-
26
- </html>