@vocab/phrase 0.0.0-delete-unused-keys-20228144520 → 0.0.0-package-files-20231142931
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -2
- package/dist/vocab-phrase.cjs.dev.js +17 -8
- package/dist/vocab-phrase.cjs.prod.js +17 -8
- package/dist/vocab-phrase.esm.js +17 -8
- package/package.json +5 -2
- package/src/file.ts +0 -4
- package/src/index.ts +0 -2
- package/src/logger.ts +0 -9
- package/src/phrase-api.ts +0 -173
- package/src/pull-translations.test.ts +0 -169
- package/src/pull-translations.ts +0 -103
- package/src/push-translations.test.ts +0 -150
- package/src/push-translations.ts +0 -67
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,36 @@
|
|
|
1
1
|
# @vocab/phrase
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-package-files-20231142931
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`29c81d3`](https://github.com/seek-oss/vocab/commit/29c81d370799f631d97c45e727b8cf81453bd398) Thanks [@askoufis](https://github.com/askoufis)! - Exclude source files from package build
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`29c81d3`](https://github.com/seek-oss/vocab/commit/29c81d370799f631d97c45e727b8cf81453bd398)]:
|
|
10
|
+
- @vocab/types@0.0.0-package-files-20231142931
|
|
11
|
+
|
|
12
|
+
## 1.1.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
|
6
15
|
|
|
7
|
-
- [`
|
|
16
|
+
- [`66ed22c`](https://github.com/seek-oss/vocab/commit/66ed22cac6f89018d5fd69fd6f6408e090e1a382) [#93](https://github.com/seek-oss/vocab/pull/93) Thanks [@askoufis](https://github.com/askoufis)! - Add an optional `deleteUnusedKeys` flag to the `push` function. If set to `true`, unused keys will be deleted from Phrase after translations are pushed.
|
|
17
|
+
|
|
18
|
+
**EXAMPLE USAGE**:
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import { push } from '@vocab/phrase';
|
|
22
|
+
|
|
23
|
+
const vocabConfig = {
|
|
24
|
+
devLanguage: 'en',
|
|
25
|
+
language: ['en', 'fr'],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await push({ branch: 'myBranch', deleteUnusedKeys: true }, vocabConfig);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- [`159d559`](https://github.com/seek-oss/vocab/commit/159d559c87c66c3e91c707fb45a1f67ebec07b4d) [#91](https://github.com/seek-oss/vocab/pull/91) Thanks [@askoufis](https://github.com/askoufis)! - Improve error message when Phrase doesn't return any translations for the dev language
|
|
8
34
|
|
|
9
35
|
## 1.0.1
|
|
10
36
|
|
|
@@ -44,14 +44,14 @@ function _callPhrase(path, options = {}) {
|
|
|
44
44
|
}).then(async response => {
|
|
45
45
|
console.log(`${path}: ${response.status} - ${response.statusText}`);
|
|
46
46
|
console.log(`Rate Limit: ${response.headers.get('X-Rate-Limit-Remaining')} of ${response.headers.get('X-Rate-Limit-Limit')} remaining. (${response.headers.get('X-Rate-Limit-Reset')} seconds remaining})`);
|
|
47
|
-
|
|
47
|
+
trace('\nLink:', response.headers.get('Link'), '\n'); // Print All Headers:
|
|
48
48
|
// console.log(Array.from(r.headers.entries()));
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
51
|
var _response$headers$get;
|
|
52
52
|
|
|
53
53
|
const result = await response.json();
|
|
54
|
-
|
|
54
|
+
trace(`Internal Result (Length: ${result.length})\n`);
|
|
55
55
|
|
|
56
56
|
if ((!options.method || options.method === 'GET') && (_response$headers$get = response.headers.get('Link')) !== null && _response$headers$get !== void 0 && _response$headers$get.includes('rel=next')) {
|
|
57
57
|
var _response$headers$get2, _response$headers$get3;
|
|
@@ -59,10 +59,10 @@ function _callPhrase(path, options = {}) {
|
|
|
59
59
|
const [, nextPageUrl] = (_response$headers$get2 = (_response$headers$get3 = response.headers.get('Link')) === null || _response$headers$get3 === void 0 ? void 0 : _response$headers$get3.match(/<([^>]*)>; rel=next/)) !== null && _response$headers$get2 !== void 0 ? _response$headers$get2 : [];
|
|
60
60
|
|
|
61
61
|
if (!nextPageUrl) {
|
|
62
|
-
throw new Error('
|
|
62
|
+
throw new Error("Can't parse next page URL");
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
console.log('Results
|
|
65
|
+
console.log('Results received with next page: ', nextPageUrl);
|
|
66
66
|
const nextPageResult = await _callPhrase(nextPageUrl, options);
|
|
67
67
|
return [...result, ...nextPageResult];
|
|
68
68
|
}
|
|
@@ -120,7 +120,7 @@ async function pushTranslationsByLocale(contents, locale, branch) {
|
|
|
120
120
|
formData.append('locale_id', locale);
|
|
121
121
|
formData.append('branch', branch);
|
|
122
122
|
formData.append('update_translations', 'true');
|
|
123
|
-
|
|
123
|
+
log('Starting to upload:', locale, '\n');
|
|
124
124
|
const {
|
|
125
125
|
id
|
|
126
126
|
} = await callPhrase(`uploads`, {
|
|
@@ -135,7 +135,9 @@ async function pushTranslationsByLocale(contents, locale, branch) {
|
|
|
135
135
|
}
|
|
136
136
|
async function deleteUnusedKeys(uploadId, locale, branch) {
|
|
137
137
|
const query = `unmentioned_in_upload:${uploadId}`;
|
|
138
|
-
const
|
|
138
|
+
const {
|
|
139
|
+
records_affected
|
|
140
|
+
} = await callPhrase('keys', {
|
|
139
141
|
method: 'DELETE',
|
|
140
142
|
headers: {
|
|
141
143
|
'Content-Type': 'application/json'
|
|
@@ -146,7 +148,7 @@ async function deleteUnusedKeys(uploadId, locale, branch) {
|
|
|
146
148
|
q: query
|
|
147
149
|
})
|
|
148
150
|
});
|
|
149
|
-
log('Successfully deleted',
|
|
151
|
+
log('Successfully deleted', records_affected, 'unused keys from branch', branch);
|
|
150
152
|
}
|
|
151
153
|
async function ensureBranch(branch) {
|
|
152
154
|
await callPhrase(`branches`, {
|
|
@@ -158,7 +160,7 @@ async function ensureBranch(branch) {
|
|
|
158
160
|
name: branch
|
|
159
161
|
})
|
|
160
162
|
});
|
|
161
|
-
|
|
163
|
+
log('Created branch:', branch);
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
async function pull({
|
|
@@ -169,6 +171,13 @@ async function pull({
|
|
|
169
171
|
const alternativeLanguages = core.getAltLanguages(config);
|
|
170
172
|
const allPhraseTranslations = await pullAllTranslations(branch);
|
|
171
173
|
trace(`Pulling translations from Phrase for languages ${config.devLanguage} and ${alternativeLanguages.join(', ')}`);
|
|
174
|
+
const phraseLanguages = Object.keys(allPhraseTranslations);
|
|
175
|
+
trace(`Found Phrase translations for languages ${phraseLanguages.join(', ')}`);
|
|
176
|
+
|
|
177
|
+
if (!phraseLanguages.includes(config.devLanguage)) {
|
|
178
|
+
throw new Error(`Phrase did not return any translations for the configured development language "${config.devLanguage}".\nPlease ensure this language is present in your Phrase project's configuration.`);
|
|
179
|
+
}
|
|
180
|
+
|
|
172
181
|
const allVocabTranslations = await core.loadAllTranslations({
|
|
173
182
|
fallbacks: 'none',
|
|
174
183
|
includeNodeModules: false
|
|
@@ -44,14 +44,14 @@ function _callPhrase(path, options = {}) {
|
|
|
44
44
|
}).then(async response => {
|
|
45
45
|
console.log(`${path}: ${response.status} - ${response.statusText}`);
|
|
46
46
|
console.log(`Rate Limit: ${response.headers.get('X-Rate-Limit-Remaining')} of ${response.headers.get('X-Rate-Limit-Limit')} remaining. (${response.headers.get('X-Rate-Limit-Reset')} seconds remaining})`);
|
|
47
|
-
|
|
47
|
+
trace('\nLink:', response.headers.get('Link'), '\n'); // Print All Headers:
|
|
48
48
|
// console.log(Array.from(r.headers.entries()));
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
51
|
var _response$headers$get;
|
|
52
52
|
|
|
53
53
|
const result = await response.json();
|
|
54
|
-
|
|
54
|
+
trace(`Internal Result (Length: ${result.length})\n`);
|
|
55
55
|
|
|
56
56
|
if ((!options.method || options.method === 'GET') && (_response$headers$get = response.headers.get('Link')) !== null && _response$headers$get !== void 0 && _response$headers$get.includes('rel=next')) {
|
|
57
57
|
var _response$headers$get2, _response$headers$get3;
|
|
@@ -59,10 +59,10 @@ function _callPhrase(path, options = {}) {
|
|
|
59
59
|
const [, nextPageUrl] = (_response$headers$get2 = (_response$headers$get3 = response.headers.get('Link')) === null || _response$headers$get3 === void 0 ? void 0 : _response$headers$get3.match(/<([^>]*)>; rel=next/)) !== null && _response$headers$get2 !== void 0 ? _response$headers$get2 : [];
|
|
60
60
|
|
|
61
61
|
if (!nextPageUrl) {
|
|
62
|
-
throw new Error('
|
|
62
|
+
throw new Error("Can't parse next page URL");
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
console.log('Results
|
|
65
|
+
console.log('Results received with next page: ', nextPageUrl);
|
|
66
66
|
const nextPageResult = await _callPhrase(nextPageUrl, options);
|
|
67
67
|
return [...result, ...nextPageResult];
|
|
68
68
|
}
|
|
@@ -120,7 +120,7 @@ async function pushTranslationsByLocale(contents, locale, branch) {
|
|
|
120
120
|
formData.append('locale_id', locale);
|
|
121
121
|
formData.append('branch', branch);
|
|
122
122
|
formData.append('update_translations', 'true');
|
|
123
|
-
|
|
123
|
+
log('Starting to upload:', locale, '\n');
|
|
124
124
|
const {
|
|
125
125
|
id
|
|
126
126
|
} = await callPhrase(`uploads`, {
|
|
@@ -135,7 +135,9 @@ async function pushTranslationsByLocale(contents, locale, branch) {
|
|
|
135
135
|
}
|
|
136
136
|
async function deleteUnusedKeys(uploadId, locale, branch) {
|
|
137
137
|
const query = `unmentioned_in_upload:${uploadId}`;
|
|
138
|
-
const
|
|
138
|
+
const {
|
|
139
|
+
records_affected
|
|
140
|
+
} = await callPhrase('keys', {
|
|
139
141
|
method: 'DELETE',
|
|
140
142
|
headers: {
|
|
141
143
|
'Content-Type': 'application/json'
|
|
@@ -146,7 +148,7 @@ async function deleteUnusedKeys(uploadId, locale, branch) {
|
|
|
146
148
|
q: query
|
|
147
149
|
})
|
|
148
150
|
});
|
|
149
|
-
log('Successfully deleted',
|
|
151
|
+
log('Successfully deleted', records_affected, 'unused keys from branch', branch);
|
|
150
152
|
}
|
|
151
153
|
async function ensureBranch(branch) {
|
|
152
154
|
await callPhrase(`branches`, {
|
|
@@ -158,7 +160,7 @@ async function ensureBranch(branch) {
|
|
|
158
160
|
name: branch
|
|
159
161
|
})
|
|
160
162
|
});
|
|
161
|
-
|
|
163
|
+
log('Created branch:', branch);
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
async function pull({
|
|
@@ -169,6 +171,13 @@ async function pull({
|
|
|
169
171
|
const alternativeLanguages = core.getAltLanguages(config);
|
|
170
172
|
const allPhraseTranslations = await pullAllTranslations(branch);
|
|
171
173
|
trace(`Pulling translations from Phrase for languages ${config.devLanguage} and ${alternativeLanguages.join(', ')}`);
|
|
174
|
+
const phraseLanguages = Object.keys(allPhraseTranslations);
|
|
175
|
+
trace(`Found Phrase translations for languages ${phraseLanguages.join(', ')}`);
|
|
176
|
+
|
|
177
|
+
if (!phraseLanguages.includes(config.devLanguage)) {
|
|
178
|
+
throw new Error(`Phrase did not return any translations for the configured development language "${config.devLanguage}".\nPlease ensure this language is present in your Phrase project's configuration.`);
|
|
179
|
+
}
|
|
180
|
+
|
|
172
181
|
const allVocabTranslations = await core.loadAllTranslations({
|
|
173
182
|
fallbacks: 'none',
|
|
174
183
|
includeNodeModules: false
|
package/dist/vocab-phrase.esm.js
CHANGED
|
@@ -32,14 +32,14 @@ function _callPhrase(path, options = {}) {
|
|
|
32
32
|
}).then(async response => {
|
|
33
33
|
console.log(`${path}: ${response.status} - ${response.statusText}`);
|
|
34
34
|
console.log(`Rate Limit: ${response.headers.get('X-Rate-Limit-Remaining')} of ${response.headers.get('X-Rate-Limit-Limit')} remaining. (${response.headers.get('X-Rate-Limit-Reset')} seconds remaining})`);
|
|
35
|
-
|
|
35
|
+
trace('\nLink:', response.headers.get('Link'), '\n'); // Print All Headers:
|
|
36
36
|
// console.log(Array.from(r.headers.entries()));
|
|
37
37
|
|
|
38
38
|
try {
|
|
39
39
|
var _response$headers$get;
|
|
40
40
|
|
|
41
41
|
const result = await response.json();
|
|
42
|
-
|
|
42
|
+
trace(`Internal Result (Length: ${result.length})\n`);
|
|
43
43
|
|
|
44
44
|
if ((!options.method || options.method === 'GET') && (_response$headers$get = response.headers.get('Link')) !== null && _response$headers$get !== void 0 && _response$headers$get.includes('rel=next')) {
|
|
45
45
|
var _response$headers$get2, _response$headers$get3;
|
|
@@ -47,10 +47,10 @@ function _callPhrase(path, options = {}) {
|
|
|
47
47
|
const [, nextPageUrl] = (_response$headers$get2 = (_response$headers$get3 = response.headers.get('Link')) === null || _response$headers$get3 === void 0 ? void 0 : _response$headers$get3.match(/<([^>]*)>; rel=next/)) !== null && _response$headers$get2 !== void 0 ? _response$headers$get2 : [];
|
|
48
48
|
|
|
49
49
|
if (!nextPageUrl) {
|
|
50
|
-
throw new Error('
|
|
50
|
+
throw new Error("Can't parse next page URL");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
console.log('Results
|
|
53
|
+
console.log('Results received with next page: ', nextPageUrl);
|
|
54
54
|
const nextPageResult = await _callPhrase(nextPageUrl, options);
|
|
55
55
|
return [...result, ...nextPageResult];
|
|
56
56
|
}
|
|
@@ -108,7 +108,7 @@ async function pushTranslationsByLocale(contents, locale, branch) {
|
|
|
108
108
|
formData.append('locale_id', locale);
|
|
109
109
|
formData.append('branch', branch);
|
|
110
110
|
formData.append('update_translations', 'true');
|
|
111
|
-
|
|
111
|
+
log('Starting to upload:', locale, '\n');
|
|
112
112
|
const {
|
|
113
113
|
id
|
|
114
114
|
} = await callPhrase(`uploads`, {
|
|
@@ -123,7 +123,9 @@ async function pushTranslationsByLocale(contents, locale, branch) {
|
|
|
123
123
|
}
|
|
124
124
|
async function deleteUnusedKeys(uploadId, locale, branch) {
|
|
125
125
|
const query = `unmentioned_in_upload:${uploadId}`;
|
|
126
|
-
const
|
|
126
|
+
const {
|
|
127
|
+
records_affected
|
|
128
|
+
} = await callPhrase('keys', {
|
|
127
129
|
method: 'DELETE',
|
|
128
130
|
headers: {
|
|
129
131
|
'Content-Type': 'application/json'
|
|
@@ -134,7 +136,7 @@ async function deleteUnusedKeys(uploadId, locale, branch) {
|
|
|
134
136
|
q: query
|
|
135
137
|
})
|
|
136
138
|
});
|
|
137
|
-
log('Successfully deleted',
|
|
139
|
+
log('Successfully deleted', records_affected, 'unused keys from branch', branch);
|
|
138
140
|
}
|
|
139
141
|
async function ensureBranch(branch) {
|
|
140
142
|
await callPhrase(`branches`, {
|
|
@@ -146,7 +148,7 @@ async function ensureBranch(branch) {
|
|
|
146
148
|
name: branch
|
|
147
149
|
})
|
|
148
150
|
});
|
|
149
|
-
|
|
151
|
+
log('Created branch:', branch);
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
async function pull({
|
|
@@ -157,6 +159,13 @@ async function pull({
|
|
|
157
159
|
const alternativeLanguages = getAltLanguages(config);
|
|
158
160
|
const allPhraseTranslations = await pullAllTranslations(branch);
|
|
159
161
|
trace(`Pulling translations from Phrase for languages ${config.devLanguage} and ${alternativeLanguages.join(', ')}`);
|
|
162
|
+
const phraseLanguages = Object.keys(allPhraseTranslations);
|
|
163
|
+
trace(`Found Phrase translations for languages ${phraseLanguages.join(', ')}`);
|
|
164
|
+
|
|
165
|
+
if (!phraseLanguages.includes(config.devLanguage)) {
|
|
166
|
+
throw new Error(`Phrase did not return any translations for the configured development language "${config.devLanguage}".\nPlease ensure this language is present in your Phrase project's configuration.`);
|
|
167
|
+
}
|
|
168
|
+
|
|
160
169
|
const allVocabTranslations = await loadAllTranslations({
|
|
161
170
|
fallbacks: 'none',
|
|
162
171
|
includeNodeModules: false
|
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vocab/phrase",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-package-files-20231142931",
|
|
4
4
|
"main": "dist/vocab-phrase.cjs.js",
|
|
5
5
|
"module": "dist/vocab-phrase.esm.js",
|
|
6
6
|
"author": "SEEK",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@vocab/core": "^1.0.0",
|
|
10
|
-
"@vocab/types": "^
|
|
10
|
+
"@vocab/types": "^0.0.0-package-files-20231142931",
|
|
11
11
|
"chalk": "^4.1.0",
|
|
12
12
|
"debug": "^4.3.1",
|
|
13
13
|
"form-data": "^3.0.0",
|
|
14
14
|
"node-fetch": "^2.6.1"
|
|
15
15
|
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
16
19
|
"devDependencies": {
|
|
17
20
|
"@types/node-fetch": "^2.5.7"
|
|
18
21
|
}
|
package/src/file.ts
DELETED
package/src/index.ts
DELETED
package/src/logger.ts
DELETED
package/src/phrase-api.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import FormData from 'form-data';
|
|
2
|
-
import { TranslationsByKey } from './../../types/src/index';
|
|
3
|
-
/* eslint-disable no-console */
|
|
4
|
-
import type { TranslationsByLanguage } from '@vocab/types';
|
|
5
|
-
import fetch from 'node-fetch';
|
|
6
|
-
import { log, trace } from './logger';
|
|
7
|
-
|
|
8
|
-
function _callPhrase(path: string, options: Parameters<typeof fetch>[1] = {}) {
|
|
9
|
-
const phraseApiToken = process.env.PHRASE_API_TOKEN;
|
|
10
|
-
|
|
11
|
-
if (!phraseApiToken) {
|
|
12
|
-
throw new Error('Missing PHRASE_API_TOKEN');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return fetch(path, {
|
|
16
|
-
...options,
|
|
17
|
-
headers: {
|
|
18
|
-
Authorization: `token ${phraseApiToken}`,
|
|
19
|
-
// Provide identification via User Agent as requested in https://developers.phrase.com/api/#overview--identification-via-user-agent
|
|
20
|
-
'User-Agent': 'SEEK Demo Candidate App (jhope@seek.com.au)',
|
|
21
|
-
...options.headers,
|
|
22
|
-
},
|
|
23
|
-
}).then(async (response) => {
|
|
24
|
-
console.log(`${path}: ${response.status} - ${response.statusText}`);
|
|
25
|
-
console.log(
|
|
26
|
-
`Rate Limit: ${response.headers.get(
|
|
27
|
-
'X-Rate-Limit-Remaining',
|
|
28
|
-
)} of ${response.headers.get(
|
|
29
|
-
'X-Rate-Limit-Limit',
|
|
30
|
-
)} remaining. (${response.headers.get(
|
|
31
|
-
'X-Rate-Limit-Reset',
|
|
32
|
-
)} seconds remaining})`,
|
|
33
|
-
);
|
|
34
|
-
console.log('\nLink:', response.headers.get('Link'), '\n');
|
|
35
|
-
// Print All Headers:
|
|
36
|
-
// console.log(Array.from(r.headers.entries()));
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
const result = await response.json();
|
|
40
|
-
|
|
41
|
-
console.log(`Internal Result (Length: ${result.length})\n`);
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
(!options.method || options.method === 'GET') &&
|
|
45
|
-
response.headers.get('Link')?.includes('rel=next')
|
|
46
|
-
) {
|
|
47
|
-
const [, nextPageUrl] =
|
|
48
|
-
response.headers.get('Link')?.match(/<([^>]*)>; rel=next/) ?? [];
|
|
49
|
-
|
|
50
|
-
if (!nextPageUrl) {
|
|
51
|
-
throw new Error('Cant parse next page URL');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
console.log('Results recieved with next page: ', nextPageUrl);
|
|
55
|
-
|
|
56
|
-
const nextPageResult = (await _callPhrase(nextPageUrl, options)) as any;
|
|
57
|
-
|
|
58
|
-
return [...result, ...nextPageResult];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return result;
|
|
62
|
-
} catch (e) {
|
|
63
|
-
console.error('Unable to parse response as JSON', e);
|
|
64
|
-
return response.text();
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export async function callPhrase<T = any>(
|
|
70
|
-
relativePath: string,
|
|
71
|
-
options: Parameters<typeof fetch>[1] = {},
|
|
72
|
-
): Promise<T> {
|
|
73
|
-
const projectId = process.env.PHRASE_PROJECT_ID;
|
|
74
|
-
|
|
75
|
-
if (!projectId) {
|
|
76
|
-
throw new Error('Missing PHRASE_PROJECT_ID');
|
|
77
|
-
}
|
|
78
|
-
return _callPhrase(
|
|
79
|
-
`https://api.phrase.com/v2/projects/${projectId}/${relativePath}`,
|
|
80
|
-
options,
|
|
81
|
-
)
|
|
82
|
-
.then((result) => {
|
|
83
|
-
if (Array.isArray(result)) {
|
|
84
|
-
console.log('Result length:', result.length);
|
|
85
|
-
}
|
|
86
|
-
return result;
|
|
87
|
-
})
|
|
88
|
-
.catch((error) => {
|
|
89
|
-
console.error(`Error calling phrase for ${relativePath}:`, error);
|
|
90
|
-
throw Error;
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export async function pullAllTranslations(
|
|
95
|
-
branch: string,
|
|
96
|
-
): Promise<TranslationsByLanguage> {
|
|
97
|
-
const phraseResult = await callPhrase<
|
|
98
|
-
Array<{
|
|
99
|
-
key: { name: string };
|
|
100
|
-
locale: { code: string };
|
|
101
|
-
content: string;
|
|
102
|
-
}>
|
|
103
|
-
>(`translations?branch=${branch}&per_page=100`);
|
|
104
|
-
const translations: TranslationsByLanguage = {};
|
|
105
|
-
for (const r of phraseResult) {
|
|
106
|
-
if (!translations[r.locale.code]) {
|
|
107
|
-
translations[r.locale.code] = {};
|
|
108
|
-
}
|
|
109
|
-
translations[r.locale.code][r.key.name] = { message: r.content };
|
|
110
|
-
}
|
|
111
|
-
return translations;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export async function pushTranslationsByLocale(
|
|
115
|
-
contents: TranslationsByKey,
|
|
116
|
-
locale: string,
|
|
117
|
-
branch: string,
|
|
118
|
-
) {
|
|
119
|
-
const formData = new FormData();
|
|
120
|
-
const fileContents = Buffer.from(JSON.stringify(contents));
|
|
121
|
-
formData.append('file', fileContents, {
|
|
122
|
-
contentType: 'application/json',
|
|
123
|
-
filename: `${locale}.json`,
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
formData.append('file_format', 'json');
|
|
127
|
-
formData.append('locale_id', locale);
|
|
128
|
-
formData.append('branch', branch);
|
|
129
|
-
formData.append('update_translations', 'true');
|
|
130
|
-
|
|
131
|
-
trace('Starting to upload:', locale);
|
|
132
|
-
|
|
133
|
-
const { id } = await callPhrase<{ id: string }>(`uploads`, {
|
|
134
|
-
method: 'POST',
|
|
135
|
-
body: formData,
|
|
136
|
-
});
|
|
137
|
-
log('Upload ID:', id, '\n');
|
|
138
|
-
log('Successfully Uploaded:', locale, '\n');
|
|
139
|
-
|
|
140
|
-
return { uploadId: id };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export async function deleteUnusedKeys(
|
|
144
|
-
uploadId: string,
|
|
145
|
-
locale: string,
|
|
146
|
-
branch: string,
|
|
147
|
-
) {
|
|
148
|
-
const query = `unmentioned_in_upload:${uploadId}`;
|
|
149
|
-
const result = await callPhrase<{ records_affected: number }>('keys', {
|
|
150
|
-
method: 'DELETE',
|
|
151
|
-
headers: {
|
|
152
|
-
'Content-Type': 'application/json',
|
|
153
|
-
},
|
|
154
|
-
body: JSON.stringify({ branch, locale_id: locale, q: query }),
|
|
155
|
-
});
|
|
156
|
-
log(
|
|
157
|
-
'Successfully deleted',
|
|
158
|
-
result.records_affected,
|
|
159
|
-
'unused keys from branch',
|
|
160
|
-
branch,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export async function ensureBranch(branch: string) {
|
|
165
|
-
await callPhrase(`branches`, {
|
|
166
|
-
method: 'POST',
|
|
167
|
-
headers: {
|
|
168
|
-
'Content-Type': 'application/json',
|
|
169
|
-
},
|
|
170
|
-
body: JSON.stringify({ name: branch }),
|
|
171
|
-
});
|
|
172
|
-
trace('Created branch:', branch);
|
|
173
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { pull } from './pull-translations';
|
|
3
|
-
import { pullAllTranslations } from './phrase-api';
|
|
4
|
-
import { writeFile } from './file';
|
|
5
|
-
import { GeneratedLanguageTarget, LanguageTarget } from '@vocab/types';
|
|
6
|
-
|
|
7
|
-
jest.mock('./file', () => ({
|
|
8
|
-
writeFile: jest.fn(() => Promise.resolve),
|
|
9
|
-
mkdir: jest.fn(() => Promise.resolve),
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
jest.mock('./phrase-api', () => ({
|
|
13
|
-
ensureBranch: jest.fn(() => Promise.resolve()),
|
|
14
|
-
pullAllTranslations: jest.fn(() => Promise.resolve({ en: {}, fr: {} })),
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
function runPhrase(options: {
|
|
18
|
-
languages: LanguageTarget[];
|
|
19
|
-
generatedLanguages: GeneratedLanguageTarget[];
|
|
20
|
-
}) {
|
|
21
|
-
return pull(
|
|
22
|
-
{ branch: 'tester' },
|
|
23
|
-
{
|
|
24
|
-
...options,
|
|
25
|
-
devLanguage: 'en',
|
|
26
|
-
projectRoot: path.resolve(__dirname, '..', '..', '..', 'fixtures/phrase'),
|
|
27
|
-
},
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe('pull translations', () => {
|
|
32
|
-
describe('when pulling translations for languages that already have translations', () => {
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
(pullAllTranslations as jest.Mock).mockClear();
|
|
35
|
-
(writeFile as jest.Mock).mockClear();
|
|
36
|
-
});
|
|
37
|
-
(pullAllTranslations as jest.Mock).mockImplementation(() =>
|
|
38
|
-
Promise.resolve({
|
|
39
|
-
en: {
|
|
40
|
-
'hello.mytranslations': {
|
|
41
|
-
message: 'Hi there',
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
fr: {
|
|
45
|
-
'hello.mytranslations': {
|
|
46
|
-
message: 'merci',
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
}),
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
const options = {
|
|
53
|
-
languages: [{ name: 'en' }, { name: 'fr' }],
|
|
54
|
-
generatedLanguages: [
|
|
55
|
-
{
|
|
56
|
-
name: 'generatedLanguage',
|
|
57
|
-
extends: 'en',
|
|
58
|
-
generator: {
|
|
59
|
-
transformMessage: (message: string) => `[${message}]`,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
it('should resolve', async () => {
|
|
66
|
-
await expect(runPhrase(options)).resolves.toBeUndefined();
|
|
67
|
-
|
|
68
|
-
expect(writeFile as jest.Mock).toHaveBeenCalledTimes(2);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should update keys', async () => {
|
|
72
|
-
await expect(runPhrase(options)).resolves.toBeUndefined();
|
|
73
|
-
|
|
74
|
-
expect(
|
|
75
|
-
(writeFile as jest.Mock).mock.calls.map(
|
|
76
|
-
([_filePath, contents]: [string, string]) => JSON.parse(contents),
|
|
77
|
-
),
|
|
78
|
-
).toMatchInlineSnapshot(`
|
|
79
|
-
Array [
|
|
80
|
-
Object {
|
|
81
|
-
"hello": Object {
|
|
82
|
-
"message": "Hi there",
|
|
83
|
-
},
|
|
84
|
-
"world": Object {
|
|
85
|
-
"message": "world",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
Object {
|
|
89
|
-
"hello": Object {
|
|
90
|
-
"message": "merci",
|
|
91
|
-
},
|
|
92
|
-
"world": Object {
|
|
93
|
-
"message": "monde",
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
]
|
|
97
|
-
`);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('when pulling translations and some languages do not have any translations', () => {
|
|
102
|
-
beforeEach(() => {
|
|
103
|
-
(pullAllTranslations as jest.Mock).mockClear();
|
|
104
|
-
(writeFile as jest.Mock).mockClear();
|
|
105
|
-
});
|
|
106
|
-
(pullAllTranslations as jest.Mock).mockImplementation(() =>
|
|
107
|
-
Promise.resolve({
|
|
108
|
-
en: {
|
|
109
|
-
'hello.mytranslations': {
|
|
110
|
-
message: 'Hi there',
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
fr: {
|
|
114
|
-
'hello.mytranslations': {
|
|
115
|
-
message: 'merci',
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
}),
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
const options = {
|
|
122
|
-
languages: [{ name: 'en' }, { name: 'fr' }, { name: 'ja' }],
|
|
123
|
-
generatedLanguages: [
|
|
124
|
-
{
|
|
125
|
-
name: 'generatedLanguage',
|
|
126
|
-
extends: 'en',
|
|
127
|
-
generator: {
|
|
128
|
-
transformMessage: (message: string) => `[${message}]`,
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
it('should resolve', async () => {
|
|
135
|
-
await expect(runPhrase(options)).resolves.toBeUndefined();
|
|
136
|
-
|
|
137
|
-
expect(writeFile as jest.Mock).toHaveBeenCalledTimes(2);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should update keys', async () => {
|
|
141
|
-
await expect(runPhrase(options)).resolves.toBeUndefined();
|
|
142
|
-
|
|
143
|
-
expect(
|
|
144
|
-
(writeFile as jest.Mock).mock.calls.map(
|
|
145
|
-
([_filePath, contents]: [string, string]) => JSON.parse(contents),
|
|
146
|
-
),
|
|
147
|
-
).toMatchInlineSnapshot(`
|
|
148
|
-
Array [
|
|
149
|
-
Object {
|
|
150
|
-
"hello": Object {
|
|
151
|
-
"message": "Hi there",
|
|
152
|
-
},
|
|
153
|
-
"world": Object {
|
|
154
|
-
"message": "world",
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
Object {
|
|
158
|
-
"hello": Object {
|
|
159
|
-
"message": "merci",
|
|
160
|
-
},
|
|
161
|
-
"world": Object {
|
|
162
|
-
"message": "monde",
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
]
|
|
166
|
-
`);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
});
|
package/src/pull-translations.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { writeFile, mkdir } from './file';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
loadAllTranslations,
|
|
6
|
-
getAltLanguageFilePath,
|
|
7
|
-
getAltLanguages,
|
|
8
|
-
getUniqueKey,
|
|
9
|
-
} from '@vocab/core';
|
|
10
|
-
import type { UserConfig } from '@vocab/types';
|
|
11
|
-
|
|
12
|
-
import { pullAllTranslations, ensureBranch } from './phrase-api';
|
|
13
|
-
import { trace } from './logger';
|
|
14
|
-
|
|
15
|
-
interface PullOptions {
|
|
16
|
-
branch?: string;
|
|
17
|
-
deleteUnusedKeys?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function pull(
|
|
21
|
-
{ branch = 'local-development' }: PullOptions,
|
|
22
|
-
config: UserConfig,
|
|
23
|
-
) {
|
|
24
|
-
trace(`Pulling translations from branch ${branch}`);
|
|
25
|
-
await ensureBranch(branch);
|
|
26
|
-
const alternativeLanguages = getAltLanguages(config);
|
|
27
|
-
const allPhraseTranslations = await pullAllTranslations(branch);
|
|
28
|
-
trace(
|
|
29
|
-
`Pulling translations from Phrase for languages ${
|
|
30
|
-
config.devLanguage
|
|
31
|
-
} and ${alternativeLanguages.join(', ')}`,
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const allVocabTranslations = await loadAllTranslations(
|
|
35
|
-
{ fallbacks: 'none', includeNodeModules: false },
|
|
36
|
-
config,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
for (const loadedTranslation of allVocabTranslations) {
|
|
40
|
-
const devTranslations = loadedTranslation.languages[config.devLanguage];
|
|
41
|
-
|
|
42
|
-
if (!devTranslations) {
|
|
43
|
-
throw new Error('No dev language translations loaded');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const defaultValues = { ...devTranslations };
|
|
47
|
-
const localKeys = Object.keys(defaultValues);
|
|
48
|
-
|
|
49
|
-
for (const key of localKeys) {
|
|
50
|
-
defaultValues[key] = {
|
|
51
|
-
...defaultValues[key],
|
|
52
|
-
...allPhraseTranslations[config.devLanguage][
|
|
53
|
-
getUniqueKey(key, loadedTranslation.namespace)
|
|
54
|
-
],
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
await writeFile(
|
|
58
|
-
loadedTranslation.filePath,
|
|
59
|
-
`${JSON.stringify(defaultValues, null, 2)}\n`,
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
for (const alternativeLanguage of alternativeLanguages) {
|
|
63
|
-
if (alternativeLanguage in allPhraseTranslations) {
|
|
64
|
-
const altTranslations = {
|
|
65
|
-
...loadedTranslation.languages[alternativeLanguage],
|
|
66
|
-
};
|
|
67
|
-
const phraseAltTranslations =
|
|
68
|
-
allPhraseTranslations[alternativeLanguage];
|
|
69
|
-
|
|
70
|
-
for (const key of localKeys) {
|
|
71
|
-
const phraseKey = getUniqueKey(key, loadedTranslation.namespace);
|
|
72
|
-
const phraseTranslationMessage =
|
|
73
|
-
phraseAltTranslations[phraseKey]?.message;
|
|
74
|
-
|
|
75
|
-
if (!phraseTranslationMessage) {
|
|
76
|
-
trace(
|
|
77
|
-
`Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`,
|
|
78
|
-
);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
altTranslations[key] = {
|
|
83
|
-
...altTranslations[key],
|
|
84
|
-
message: phraseTranslationMessage,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const altTranslationFilePath = getAltLanguageFilePath(
|
|
89
|
-
loadedTranslation.filePath,
|
|
90
|
-
alternativeLanguage,
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
await mkdir(path.dirname(altTranslationFilePath), {
|
|
94
|
-
recursive: true,
|
|
95
|
-
});
|
|
96
|
-
await writeFile(
|
|
97
|
-
altTranslationFilePath,
|
|
98
|
-
`${JSON.stringify(altTranslations, null, 2)}\n`,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import { push } from './push-translations';
|
|
3
|
-
import { pushTranslationsByLocale, deleteUnusedKeys } from './phrase-api';
|
|
4
|
-
import { writeFile } from './file';
|
|
5
|
-
|
|
6
|
-
jest.mock('./file', () => ({
|
|
7
|
-
writeFile: jest.fn(() => Promise.resolve),
|
|
8
|
-
mkdir: jest.fn(() => Promise.resolve),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
jest.mock('./phrase-api', () => ({
|
|
12
|
-
ensureBranch: jest.fn(() => Promise.resolve()),
|
|
13
|
-
pushTranslationsByLocale: jest.fn(() => Promise.resolve({ en: {}, fr: {} })),
|
|
14
|
-
deleteUnusedKeys: jest.fn(() => Promise.resolve()),
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
const uploadId = '1234';
|
|
18
|
-
|
|
19
|
-
function runPhrase(config: { deleteUnusedKeys: boolean }) {
|
|
20
|
-
return push(
|
|
21
|
-
{ branch: 'tester', deleteUnusedKeys: config.deleteUnusedKeys },
|
|
22
|
-
{
|
|
23
|
-
devLanguage: 'en',
|
|
24
|
-
languages: [{ name: 'en' }, { name: 'fr' }],
|
|
25
|
-
generatedLanguages: [
|
|
26
|
-
{
|
|
27
|
-
name: 'generatedLanguage',
|
|
28
|
-
extends: 'en',
|
|
29
|
-
generator: {
|
|
30
|
-
transformMessage: (message: string) => `[${message}]`,
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
projectRoot: path.resolve(__dirname, '..', '..', '..', 'fixtures/phrase'),
|
|
35
|
-
},
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe('push', () => {
|
|
40
|
-
describe('when deleteUnusedKeys is false', () => {
|
|
41
|
-
const config = { deleteUnusedKeys: false };
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
(pushTranslationsByLocale as jest.Mock).mockClear();
|
|
45
|
-
(writeFile as jest.Mock).mockClear();
|
|
46
|
-
(deleteUnusedKeys as jest.Mock).mockClear();
|
|
47
|
-
|
|
48
|
-
(pushTranslationsByLocale as jest.Mock).mockImplementation(() =>
|
|
49
|
-
Promise.resolve({ uploadId }),
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should resolve', async () => {
|
|
54
|
-
await expect(runPhrase(config)).resolves.toBeUndefined();
|
|
55
|
-
|
|
56
|
-
expect(pushTranslationsByLocale as jest.Mock).toHaveBeenCalledTimes(2);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should update keys', async () => {
|
|
60
|
-
await expect(runPhrase(config)).resolves.toBeUndefined();
|
|
61
|
-
|
|
62
|
-
expect(pushTranslationsByLocale as jest.Mock).toHaveBeenCalledWith(
|
|
63
|
-
{
|
|
64
|
-
'hello.mytranslations': {
|
|
65
|
-
message: 'Hello',
|
|
66
|
-
},
|
|
67
|
-
'world.mytranslations': {
|
|
68
|
-
message: 'world',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
'en',
|
|
72
|
-
'tester',
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
expect(pushTranslationsByLocale as jest.Mock).toHaveBeenCalledWith(
|
|
76
|
-
{
|
|
77
|
-
'hello.mytranslations': {
|
|
78
|
-
message: 'Bonjour',
|
|
79
|
-
},
|
|
80
|
-
'world.mytranslations': {
|
|
81
|
-
message: 'monde',
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
'fr',
|
|
85
|
-
'tester',
|
|
86
|
-
);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should not delete unused keys', () => {
|
|
90
|
-
expect(deleteUnusedKeys).not.toHaveBeenCalled();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe('when deleteUnusedKeys is true', () => {
|
|
95
|
-
const config = { deleteUnusedKeys: true };
|
|
96
|
-
|
|
97
|
-
beforeEach(() => {
|
|
98
|
-
(pushTranslationsByLocale as jest.Mock).mockClear();
|
|
99
|
-
(writeFile as jest.Mock).mockClear();
|
|
100
|
-
(deleteUnusedKeys as jest.Mock).mockClear();
|
|
101
|
-
|
|
102
|
-
(pushTranslationsByLocale as jest.Mock).mockImplementation(() =>
|
|
103
|
-
Promise.resolve({ uploadId }),
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should resolve', async () => {
|
|
108
|
-
await expect(runPhrase(config)).resolves.toBeUndefined();
|
|
109
|
-
|
|
110
|
-
expect(pushTranslationsByLocale as jest.Mock).toHaveBeenCalledTimes(2);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should update keys', async () => {
|
|
114
|
-
await expect(runPhrase(config)).resolves.toBeUndefined();
|
|
115
|
-
|
|
116
|
-
expect(pushTranslationsByLocale as jest.Mock).toHaveBeenCalledWith(
|
|
117
|
-
{
|
|
118
|
-
'hello.mytranslations': {
|
|
119
|
-
message: 'Hello',
|
|
120
|
-
},
|
|
121
|
-
'world.mytranslations': {
|
|
122
|
-
message: 'world',
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
'en',
|
|
126
|
-
'tester',
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
expect(pushTranslationsByLocale as jest.Mock).toHaveBeenCalledWith(
|
|
130
|
-
{
|
|
131
|
-
'hello.mytranslations': {
|
|
132
|
-
message: 'Bonjour',
|
|
133
|
-
},
|
|
134
|
-
'world.mytranslations': {
|
|
135
|
-
message: 'monde',
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
'fr',
|
|
139
|
-
'tester',
|
|
140
|
-
);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should delete unused keys', async () => {
|
|
144
|
-
await expect(runPhrase(config)).resolves.toBeUndefined();
|
|
145
|
-
|
|
146
|
-
expect(deleteUnusedKeys).toHaveBeenCalledWith(uploadId, 'en', 'tester');
|
|
147
|
-
expect(deleteUnusedKeys).toHaveBeenCalledWith(uploadId, 'fr', 'tester');
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
});
|
package/src/push-translations.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { TranslationsByLanguage } from './../../types/src/index';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
ensureBranch,
|
|
5
|
-
pushTranslationsByLocale,
|
|
6
|
-
deleteUnusedKeys as phraseDeleteUnusedKeys,
|
|
7
|
-
} from './phrase-api';
|
|
8
|
-
import { trace } from './logger';
|
|
9
|
-
import { loadAllTranslations, getUniqueKey } from '@vocab/core';
|
|
10
|
-
import { UserConfig } from '@vocab/types';
|
|
11
|
-
|
|
12
|
-
interface PushOptions {
|
|
13
|
-
branch: string;
|
|
14
|
-
deleteUnusedKeys?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Uploading to the Phrase API for each language. Adding a unique namespace to each key using file path they key came from
|
|
19
|
-
*/
|
|
20
|
-
export async function push(
|
|
21
|
-
{ branch, deleteUnusedKeys }: PushOptions,
|
|
22
|
-
config: UserConfig,
|
|
23
|
-
) {
|
|
24
|
-
const allLanguageTranslations = await loadAllTranslations(
|
|
25
|
-
{ fallbacks: 'none', includeNodeModules: false },
|
|
26
|
-
config,
|
|
27
|
-
);
|
|
28
|
-
trace(`Pushing translations to branch ${branch}`);
|
|
29
|
-
const allLanguages = config.languages.map((v) => v.name);
|
|
30
|
-
await ensureBranch(branch);
|
|
31
|
-
|
|
32
|
-
trace(
|
|
33
|
-
`Pushing translations to phrase for languages ${allLanguages.join(', ')}`,
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const phraseTranslations: TranslationsByLanguage = {};
|
|
37
|
-
|
|
38
|
-
for (const loadedTranslation of allLanguageTranslations) {
|
|
39
|
-
for (const language of allLanguages) {
|
|
40
|
-
const localTranslations = loadedTranslation.languages[language];
|
|
41
|
-
if (!localTranslations) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
if (!phraseTranslations[language]) {
|
|
45
|
-
phraseTranslations[language] = {};
|
|
46
|
-
}
|
|
47
|
-
for (const localKey of Object.keys(localTranslations)) {
|
|
48
|
-
const phraseKey = getUniqueKey(localKey, loadedTranslation.namespace);
|
|
49
|
-
phraseTranslations[language][phraseKey] = localTranslations[localKey];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
for (const language of allLanguages) {
|
|
55
|
-
if (phraseTranslations[language]) {
|
|
56
|
-
const { uploadId } = await pushTranslationsByLocale(
|
|
57
|
-
phraseTranslations[language],
|
|
58
|
-
language,
|
|
59
|
-
branch,
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
if (deleteUnusedKeys) {
|
|
63
|
-
await phraseDeleteUnusedKeys(uploadId, language, branch);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|