@vocab/phrase 0.0.0-delete-unused-keys-20228144520 → 0.0.0-global-key-support-20231025223328
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/LICENSE +21 -0
- package/dist/declarations/src/csv.d.ts +10 -0
- package/dist/declarations/src/file.d.ts +4 -4
- package/dist/declarations/src/index.d.ts +2 -2
- package/dist/declarations/src/logger.d.ts +3 -3
- package/dist/declarations/src/phrase-api.d.ts +12 -10
- package/dist/declarations/src/pull-translations.d.ts +8 -7
- package/dist/declarations/src/push-translations.d.ts +11 -10
- package/dist/vocab-phrase.cjs.dev.js +170 -87
- package/dist/vocab-phrase.cjs.prod.js +170 -87
- package/dist/vocab-phrase.esm.js +167 -84
- package/package.json +9 -5
- package/CHANGELOG.md +0 -128
- 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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
### MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 SEEK
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TranslationsByLanguage } from '@vocab/core';
|
|
2
|
+
export declare function translationsToCsv(translations: TranslationsByLanguage, devLanguage: string): {
|
|
3
|
+
csvFileStrings: {
|
|
4
|
+
[k: string]: string;
|
|
5
|
+
};
|
|
6
|
+
keyIndex: number;
|
|
7
|
+
messageIndex: number;
|
|
8
|
+
commentIndex: number;
|
|
9
|
+
tagColumn: number;
|
|
10
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { promises as fs } from 'fs';
|
|
3
|
-
export declare const mkdir: typeof fs.mkdir;
|
|
4
|
-
export declare const writeFile: typeof fs.writeFile;
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
export declare const mkdir: typeof fs.mkdir;
|
|
4
|
+
export declare const writeFile: typeof fs.writeFile;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { pull } from './pull-translations';
|
|
2
|
-
export { push } from './push-translations';
|
|
1
|
+
export { pull } from './pull-translations';
|
|
2
|
+
export { push } from './push-translations';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import debug from 'debug';
|
|
2
|
-
export declare const trace: debug.Debugger;
|
|
3
|
-
export declare const log: (...params: unknown[]) => void;
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
export declare const trace: debug.Debugger;
|
|
3
|
+
export declare const log: (...params: unknown[]) => void;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import type { TranslationsByLanguage } from '@vocab/core';
|
|
2
|
+
import fetch from 'node-fetch';
|
|
3
|
+
export declare function callPhrase<T = any>(relativePath: string, options?: Parameters<typeof fetch>[1]): Promise<T>;
|
|
4
|
+
export declare function pullAllTranslations(branch: string): Promise<TranslationsByLanguage>;
|
|
5
|
+
export declare function pushTranslations(translationsByLanguage: TranslationsByLanguage, { devLanguage, branch }: {
|
|
6
|
+
devLanguage: string;
|
|
7
|
+
branch: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
devLanguageUploadId: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function deleteUnusedKeys(uploadId: string, branch: string): Promise<void>;
|
|
12
|
+
export declare function ensureBranch(branch: string): Promise<void>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { UserConfig } from '@vocab/
|
|
2
|
-
interface PullOptions {
|
|
3
|
-
branch?: string;
|
|
4
|
-
deleteUnusedKeys?: boolean;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export {}
|
|
1
|
+
import type { UserConfig } from '@vocab/core';
|
|
2
|
+
interface PullOptions {
|
|
3
|
+
branch?: string;
|
|
4
|
+
deleteUnusedKeys?: boolean;
|
|
5
|
+
errorOnNoGlobalKeyTranslation?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function pull({ branch, errorOnNoGlobalKeyTranslation }: PullOptions, config: UserConfig): Promise<void>;
|
|
8
|
+
export {};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { UserConfig } from '@vocab/
|
|
2
|
-
interface PushOptions {
|
|
3
|
-
branch: string;
|
|
4
|
-
deleteUnusedKeys?: boolean;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export {}
|
|
1
|
+
import type { UserConfig } from '@vocab/core';
|
|
2
|
+
interface PushOptions {
|
|
3
|
+
branch: string;
|
|
4
|
+
deleteUnusedKeys?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Uploads translations to the Phrase API for each language.
|
|
8
|
+
* A unique namespace is appended to each key using the file path the key came from.
|
|
9
|
+
*/
|
|
10
|
+
export declare function push({ branch, deleteUnusedKeys }: PushOptions, config: UserConfig): Promise<void>;
|
|
11
|
+
export {};
|
|
@@ -9,6 +9,7 @@ var FormData = require('form-data');
|
|
|
9
9
|
var fetch = require('node-fetch');
|
|
10
10
|
var chalk = require('chalk');
|
|
11
11
|
var debug = require('debug');
|
|
12
|
+
var sync = require('csv-stringify/sync');
|
|
12
13
|
|
|
13
14
|
function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
|
|
14
15
|
|
|
@@ -21,20 +22,65 @@ var debug__default = /*#__PURE__*/_interopDefault(debug);
|
|
|
21
22
|
const mkdir = fs.promises.mkdir;
|
|
22
23
|
const writeFile = fs.promises.writeFile;
|
|
23
24
|
|
|
24
|
-
const trace = debug__default[
|
|
25
|
+
const trace = debug__default["default"](`vocab:phrase`);
|
|
25
26
|
const log = (...params) => {
|
|
26
27
|
// eslint-disable-next-line no-console
|
|
27
|
-
console.log(chalk__default[
|
|
28
|
+
console.log(chalk__default["default"].yellow('Vocab'), ...params);
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
function translationsToCsv(translations, devLanguage) {
|
|
32
|
+
const languages = Object.keys(translations);
|
|
33
|
+
const altLanguages = languages.filter(language => language !== devLanguage);
|
|
34
|
+
const devLanguageTranslations = translations[devLanguage];
|
|
35
|
+
const csvFilesByLanguage = Object.fromEntries(languages.map(language => [language, []]));
|
|
36
|
+
Object.entries(devLanguageTranslations).map(([key, {
|
|
37
|
+
message,
|
|
38
|
+
description,
|
|
39
|
+
tags
|
|
40
|
+
}]) => {
|
|
41
|
+
const sharedData = [key, description, tags === null || tags === void 0 ? void 0 : tags.join(',')];
|
|
42
|
+
const devLanguageRow = [...sharedData, message];
|
|
43
|
+
csvFilesByLanguage[devLanguage].push(devLanguageRow);
|
|
44
|
+
altLanguages.map(language => {
|
|
45
|
+
var _translations$languag, _translations$languag2;
|
|
46
|
+
const altTranslationMessage = (_translations$languag = translations[language]) === null || _translations$languag === void 0 ? void 0 : (_translations$languag2 = _translations$languag[key]) === null || _translations$languag2 === void 0 ? void 0 : _translations$languag2.message;
|
|
47
|
+
if (altTranslationMessage) {
|
|
48
|
+
csvFilesByLanguage[language].push([...sharedData, altTranslationMessage]);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
const csvFileStrings = Object.fromEntries(Object.entries(csvFilesByLanguage)
|
|
53
|
+
// Ensure CSV files are only created if the language has at least 1 translation
|
|
54
|
+
.filter(([_, csvFile]) => csvFile.length > 0).map(([language, csvFile]) => {
|
|
55
|
+
const csvFileString = sync.stringify(csvFile, {
|
|
56
|
+
delimiter: ',',
|
|
57
|
+
header: false
|
|
58
|
+
});
|
|
59
|
+
return [language, csvFileString];
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
// Column indices start at 1
|
|
63
|
+
const keyIndex = 1;
|
|
64
|
+
const commentIndex = keyIndex + 1;
|
|
65
|
+
const tagColumn = commentIndex + 1;
|
|
66
|
+
const messageIndex = tagColumn + 1;
|
|
67
|
+
return {
|
|
68
|
+
csvFileStrings,
|
|
69
|
+
keyIndex,
|
|
70
|
+
messageIndex,
|
|
71
|
+
commentIndex,
|
|
72
|
+
tagColumn
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* eslint-disable no-console */
|
|
30
77
|
function _callPhrase(path, options = {}) {
|
|
31
78
|
const phraseApiToken = process.env.PHRASE_API_TOKEN;
|
|
32
|
-
|
|
33
79
|
if (!phraseApiToken) {
|
|
34
80
|
throw new Error('Missing PHRASE_API_TOKEN');
|
|
35
81
|
}
|
|
36
|
-
|
|
37
|
-
|
|
82
|
+
return fetch__default["default"](path, {
|
|
83
|
+
...options,
|
|
38
84
|
headers: {
|
|
39
85
|
Authorization: `token ${phraseApiToken}`,
|
|
40
86
|
// Provide identification via User Agent as requested in https://developers.phrase.com/api/#overview--identification-via-user-agent
|
|
@@ -43,30 +89,26 @@ function _callPhrase(path, options = {}) {
|
|
|
43
89
|
}
|
|
44
90
|
}).then(async response => {
|
|
45
91
|
console.log(`${path}: ${response.status} - ${response.statusText}`);
|
|
46
|
-
|
|
47
|
-
console.log(
|
|
92
|
+
const secondsUntilLimitReset = Math.ceil(Number.parseFloat(response.headers.get('X-Rate-Limit-Reset') || '0') - Date.now() / 1000);
|
|
93
|
+
console.log(`Rate Limit: ${response.headers.get('X-Rate-Limit-Remaining')} of ${response.headers.get('X-Rate-Limit-Limit')} remaining. (${secondsUntilLimitReset} seconds remaining)`);
|
|
94
|
+
trace('\nLink:', response.headers.get('Link'), '\n');
|
|
95
|
+
// Print All Headers:
|
|
48
96
|
// console.log(Array.from(r.headers.entries()));
|
|
49
97
|
|
|
50
98
|
try {
|
|
51
99
|
var _response$headers$get;
|
|
52
|
-
|
|
53
100
|
const result = await response.json();
|
|
54
|
-
|
|
55
|
-
|
|
101
|
+
trace(`Internal Result (Length: ${result.length})\n`);
|
|
56
102
|
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
103
|
var _response$headers$get2, _response$headers$get3;
|
|
58
|
-
|
|
59
104
|
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
|
-
|
|
61
105
|
if (!nextPageUrl) {
|
|
62
|
-
throw new Error('
|
|
106
|
+
throw new Error("Can't parse next page URL");
|
|
63
107
|
}
|
|
64
|
-
|
|
65
|
-
console.log('Results recieved with next page: ', nextPageUrl);
|
|
108
|
+
console.log('Results received with next page: ', nextPageUrl);
|
|
66
109
|
const nextPageResult = await _callPhrase(nextPageUrl, options);
|
|
67
110
|
return [...result, ...nextPageResult];
|
|
68
111
|
}
|
|
69
|
-
|
|
70
112
|
return result;
|
|
71
113
|
} catch (e) {
|
|
72
114
|
console.error('Unable to parse response as JSON', e);
|
|
@@ -74,19 +116,15 @@ function _callPhrase(path, options = {}) {
|
|
|
74
116
|
}
|
|
75
117
|
});
|
|
76
118
|
}
|
|
77
|
-
|
|
78
119
|
async function callPhrase(relativePath, options = {}) {
|
|
79
120
|
const projectId = process.env.PHRASE_PROJECT_ID;
|
|
80
|
-
|
|
81
121
|
if (!projectId) {
|
|
82
122
|
throw new Error('Missing PHRASE_PROJECT_ID');
|
|
83
123
|
}
|
|
84
|
-
|
|
85
124
|
return _callPhrase(`https://api.phrase.com/v2/projects/${projectId}/${relativePath}`, options).then(result => {
|
|
86
125
|
if (Array.isArray(result)) {
|
|
87
126
|
console.log('Result length:', result.length);
|
|
88
127
|
}
|
|
89
|
-
|
|
90
128
|
return result;
|
|
91
129
|
}).catch(error => {
|
|
92
130
|
console.error(`Error calling phrase for ${relativePath}:`, error);
|
|
@@ -96,57 +134,81 @@ async function callPhrase(relativePath, options = {}) {
|
|
|
96
134
|
async function pullAllTranslations(branch) {
|
|
97
135
|
const phraseResult = await callPhrase(`translations?branch=${branch}&per_page=100`);
|
|
98
136
|
const translations = {};
|
|
99
|
-
|
|
100
137
|
for (const r of phraseResult) {
|
|
101
138
|
if (!translations[r.locale.code]) {
|
|
102
139
|
translations[r.locale.code] = {};
|
|
103
140
|
}
|
|
104
|
-
|
|
105
141
|
translations[r.locale.code][r.key.name] = {
|
|
106
142
|
message: r.content
|
|
107
143
|
};
|
|
108
144
|
}
|
|
109
|
-
|
|
110
145
|
return translations;
|
|
111
146
|
}
|
|
112
|
-
async function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
contentType: 'application/json',
|
|
117
|
-
filename: `${locale}.json`
|
|
118
|
-
});
|
|
119
|
-
formData.append('file_format', 'json');
|
|
120
|
-
formData.append('locale_id', locale);
|
|
121
|
-
formData.append('branch', branch);
|
|
122
|
-
formData.append('update_translations', 'true');
|
|
123
|
-
trace('Starting to upload:', locale);
|
|
147
|
+
async function pushTranslations(translationsByLanguage, {
|
|
148
|
+
devLanguage,
|
|
149
|
+
branch
|
|
150
|
+
}) {
|
|
124
151
|
const {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
152
|
+
csvFileStrings,
|
|
153
|
+
keyIndex,
|
|
154
|
+
commentIndex,
|
|
155
|
+
tagColumn,
|
|
156
|
+
messageIndex
|
|
157
|
+
} = translationsToCsv(translationsByLanguage, devLanguage);
|
|
158
|
+
let devLanguageUploadId = '';
|
|
159
|
+
for (const [language, csvFileString] of Object.entries(csvFileStrings)) {
|
|
160
|
+
const formData = new FormData__default["default"]();
|
|
161
|
+
const fileContents = Buffer.from(csvFileString);
|
|
162
|
+
formData.append('file', fileContents, {
|
|
163
|
+
contentType: 'text/csv',
|
|
164
|
+
filename: `${language}.translations.csv`
|
|
165
|
+
});
|
|
166
|
+
formData.append('file_format', 'csv');
|
|
167
|
+
formData.append('branch', branch);
|
|
168
|
+
formData.append('update_translations', 'true');
|
|
169
|
+
formData.append('update_descriptions', 'true');
|
|
170
|
+
formData.append(`locale_mapping[${language}]`, messageIndex);
|
|
171
|
+
formData.append('format_options[key_index]', keyIndex);
|
|
172
|
+
formData.append('format_options[comment_index]', commentIndex);
|
|
173
|
+
formData.append('format_options[tag_column]', tagColumn);
|
|
174
|
+
formData.append('format_options[enable_pluralization]', 'false');
|
|
175
|
+
log(`Uploading translations for language ${language}`);
|
|
176
|
+
const result = await callPhrase(`uploads`, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
body: formData
|
|
179
|
+
});
|
|
180
|
+
trace('Upload result:\n', result);
|
|
181
|
+
if (result && 'id' in result) {
|
|
182
|
+
log('Upload ID:', result.id, '\n');
|
|
183
|
+
log('Successfully Uploaded\n');
|
|
184
|
+
} else {
|
|
185
|
+
log(`Error uploading: ${result === null || result === void 0 ? void 0 : result.message}\n`);
|
|
186
|
+
log('Response:', result);
|
|
187
|
+
throw new Error('Error uploading');
|
|
188
|
+
}
|
|
189
|
+
if (language === devLanguage) {
|
|
190
|
+
devLanguageUploadId = result.id;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
132
193
|
return {
|
|
133
|
-
|
|
194
|
+
devLanguageUploadId
|
|
134
195
|
};
|
|
135
196
|
}
|
|
136
|
-
async function deleteUnusedKeys(uploadId,
|
|
197
|
+
async function deleteUnusedKeys(uploadId, branch) {
|
|
137
198
|
const query = `unmentioned_in_upload:${uploadId}`;
|
|
138
|
-
const
|
|
199
|
+
const {
|
|
200
|
+
records_affected
|
|
201
|
+
} = await callPhrase('keys', {
|
|
139
202
|
method: 'DELETE',
|
|
140
203
|
headers: {
|
|
141
204
|
'Content-Type': 'application/json'
|
|
142
205
|
},
|
|
143
206
|
body: JSON.stringify({
|
|
144
207
|
branch,
|
|
145
|
-
locale_id: locale,
|
|
146
208
|
q: query
|
|
147
209
|
})
|
|
148
210
|
});
|
|
149
|
-
log('Successfully deleted',
|
|
211
|
+
log('Successfully deleted', records_affected, 'unused keys from branch', branch);
|
|
150
212
|
}
|
|
151
213
|
async function ensureBranch(branch) {
|
|
152
214
|
await callPhrase(`branches`, {
|
|
@@ -158,65 +220,74 @@ async function ensureBranch(branch) {
|
|
|
158
220
|
name: branch
|
|
159
221
|
})
|
|
160
222
|
});
|
|
161
|
-
|
|
223
|
+
log('Created branch:', branch);
|
|
162
224
|
}
|
|
163
225
|
|
|
164
226
|
async function pull({
|
|
165
|
-
branch = 'local-development'
|
|
227
|
+
branch = 'local-development',
|
|
228
|
+
errorOnNoGlobalKeyTranslation
|
|
166
229
|
}, config) {
|
|
167
230
|
trace(`Pulling translations from branch ${branch}`);
|
|
168
231
|
await ensureBranch(branch);
|
|
169
232
|
const alternativeLanguages = core.getAltLanguages(config);
|
|
170
233
|
const allPhraseTranslations = await pullAllTranslations(branch);
|
|
171
234
|
trace(`Pulling translations from Phrase for languages ${config.devLanguage} and ${alternativeLanguages.join(', ')}`);
|
|
235
|
+
const phraseLanguages = Object.keys(allPhraseTranslations);
|
|
236
|
+
trace(`Found Phrase translations for languages ${phraseLanguages.join(', ')}`);
|
|
237
|
+
if (!phraseLanguages.includes(config.devLanguage)) {
|
|
238
|
+
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.`);
|
|
239
|
+
}
|
|
172
240
|
const allVocabTranslations = await core.loadAllTranslations({
|
|
173
241
|
fallbacks: 'none',
|
|
174
|
-
includeNodeModules: false
|
|
242
|
+
includeNodeModules: false,
|
|
243
|
+
withTags: true
|
|
175
244
|
}, config);
|
|
176
|
-
|
|
177
245
|
for (const loadedTranslation of allVocabTranslations) {
|
|
178
246
|
const devTranslations = loadedTranslation.languages[config.devLanguage];
|
|
179
|
-
|
|
180
247
|
if (!devTranslations) {
|
|
181
248
|
throw new Error('No dev language translations loaded');
|
|
182
249
|
}
|
|
183
|
-
|
|
184
|
-
|
|
250
|
+
const defaultValues = {
|
|
251
|
+
...devTranslations
|
|
185
252
|
};
|
|
186
253
|
const localKeys = Object.keys(defaultValues);
|
|
187
|
-
|
|
188
254
|
for (const key of localKeys) {
|
|
189
|
-
|
|
190
|
-
|
|
255
|
+
var _defaultValues$key$gl;
|
|
256
|
+
defaultValues[key] = {
|
|
257
|
+
...defaultValues[key],
|
|
258
|
+
...allPhraseTranslations[config.devLanguage][(_defaultValues$key$gl = defaultValues[key].globalKey) !== null && _defaultValues$key$gl !== void 0 ? _defaultValues$key$gl : core.getUniqueKey(key, loadedTranslation.namespace)]
|
|
191
259
|
};
|
|
192
260
|
}
|
|
193
261
|
|
|
262
|
+
// Only write a `_meta` field if necessary
|
|
263
|
+
if (Object.keys(loadedTranslation.metadata).length > 0) {
|
|
264
|
+
defaultValues._meta = loadedTranslation.metadata;
|
|
265
|
+
}
|
|
194
266
|
await writeFile(loadedTranslation.filePath, `${JSON.stringify(defaultValues, null, 2)}\n`);
|
|
195
|
-
|
|
196
267
|
for (const alternativeLanguage of alternativeLanguages) {
|
|
197
268
|
if (alternativeLanguage in allPhraseTranslations) {
|
|
198
|
-
const altTranslations = {
|
|
269
|
+
const altTranslations = {
|
|
270
|
+
...loadedTranslation.languages[alternativeLanguage]
|
|
199
271
|
};
|
|
200
272
|
const phraseAltTranslations = allPhraseTranslations[alternativeLanguage];
|
|
201
|
-
|
|
202
273
|
for (const key of localKeys) {
|
|
203
|
-
var _phraseAltTranslation;
|
|
204
|
-
|
|
205
|
-
const phraseKey = core.getUniqueKey(key, loadedTranslation.namespace);
|
|
274
|
+
var _defaultValues$key$gl2, _phraseAltTranslation;
|
|
275
|
+
const phraseKey = (_defaultValues$key$gl2 = defaultValues[key].globalKey) !== null && _defaultValues$key$gl2 !== void 0 ? _defaultValues$key$gl2 : core.getUniqueKey(key, loadedTranslation.namespace);
|
|
206
276
|
const phraseTranslationMessage = (_phraseAltTranslation = phraseAltTranslations[phraseKey]) === null || _phraseAltTranslation === void 0 ? void 0 : _phraseAltTranslation.message;
|
|
207
|
-
|
|
208
277
|
if (!phraseTranslationMessage) {
|
|
209
278
|
trace(`Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`);
|
|
279
|
+
if (errorOnNoGlobalKeyTranslation && defaultValues[key].globalKey) {
|
|
280
|
+
throw new Error(`Missing translation for global key ${key} in language ${alternativeLanguage}`);
|
|
281
|
+
}
|
|
210
282
|
continue;
|
|
211
283
|
}
|
|
212
|
-
|
|
213
|
-
|
|
284
|
+
altTranslations[key] = {
|
|
285
|
+
...altTranslations[key],
|
|
214
286
|
message: phraseTranslationMessage
|
|
215
287
|
};
|
|
216
288
|
}
|
|
217
|
-
|
|
218
289
|
const altTranslationFilePath = core.getAltLanguageFilePath(loadedTranslation.filePath, alternativeLanguage);
|
|
219
|
-
await mkdir(path__default[
|
|
290
|
+
await mkdir(path__default["default"].dirname(altTranslationFilePath), {
|
|
220
291
|
recursive: true
|
|
221
292
|
});
|
|
222
293
|
await writeFile(altTranslationFilePath, `${JSON.stringify(altTranslations, null, 2)}\n`);
|
|
@@ -226,7 +297,8 @@ async function pull({
|
|
|
226
297
|
}
|
|
227
298
|
|
|
228
299
|
/**
|
|
229
|
-
*
|
|
300
|
+
* Uploads translations to the Phrase API for each language.
|
|
301
|
+
* A unique namespace is appended to each key using the file path the key came from.
|
|
230
302
|
*/
|
|
231
303
|
async function push({
|
|
232
304
|
branch,
|
|
@@ -234,43 +306,54 @@ async function push({
|
|
|
234
306
|
}, config) {
|
|
235
307
|
const allLanguageTranslations = await core.loadAllTranslations({
|
|
236
308
|
fallbacks: 'none',
|
|
237
|
-
includeNodeModules: false
|
|
309
|
+
includeNodeModules: false,
|
|
310
|
+
withTags: true
|
|
238
311
|
}, config);
|
|
239
312
|
trace(`Pushing translations to branch ${branch}`);
|
|
240
313
|
const allLanguages = config.languages.map(v => v.name);
|
|
241
314
|
await ensureBranch(branch);
|
|
242
315
|
trace(`Pushing translations to phrase for languages ${allLanguages.join(', ')}`);
|
|
243
316
|
const phraseTranslations = {};
|
|
244
|
-
|
|
245
317
|
for (const loadedTranslation of allLanguageTranslations) {
|
|
246
318
|
for (const language of allLanguages) {
|
|
247
319
|
const localTranslations = loadedTranslation.languages[language];
|
|
248
|
-
|
|
249
320
|
if (!localTranslations) {
|
|
250
321
|
continue;
|
|
251
322
|
}
|
|
252
|
-
|
|
253
323
|
if (!phraseTranslations[language]) {
|
|
254
324
|
phraseTranslations[language] = {};
|
|
255
325
|
}
|
|
256
|
-
|
|
326
|
+
const {
|
|
327
|
+
metadata: {
|
|
328
|
+
tags: sharedTags = []
|
|
329
|
+
}
|
|
330
|
+
} = loadedTranslation;
|
|
257
331
|
for (const localKey of Object.keys(localTranslations)) {
|
|
258
|
-
|
|
259
|
-
|
|
332
|
+
var _globalKey;
|
|
333
|
+
const {
|
|
334
|
+
tags = [],
|
|
335
|
+
...localTranslation
|
|
336
|
+
} = localTranslations[localKey];
|
|
337
|
+
if (language === config.devLanguage) {
|
|
338
|
+
localTranslation.tags = [...tags, ...sharedTags];
|
|
339
|
+
}
|
|
340
|
+
let globalKey;
|
|
341
|
+
if (loadedTranslation.languages[config.devLanguage][localKey].globalKey) {
|
|
342
|
+
globalKey = loadedTranslation.languages[config.devLanguage][localKey].globalKey;
|
|
343
|
+
}
|
|
344
|
+
const phraseKey = (_globalKey = globalKey) !== null && _globalKey !== void 0 ? _globalKey : core.getUniqueKey(localKey, loadedTranslation.namespace);
|
|
345
|
+
phraseTranslations[language][phraseKey] = localTranslation;
|
|
260
346
|
}
|
|
261
347
|
}
|
|
262
348
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
await deleteUnusedKeys(uploadId, language, branch);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
349
|
+
const {
|
|
350
|
+
devLanguageUploadId
|
|
351
|
+
} = await pushTranslations(phraseTranslations, {
|
|
352
|
+
devLanguage: config.devLanguage,
|
|
353
|
+
branch
|
|
354
|
+
});
|
|
355
|
+
if (deleteUnusedKeys$1) {
|
|
356
|
+
await deleteUnusedKeys(devLanguageUploadId, branch);
|
|
274
357
|
}
|
|
275
358
|
}
|
|
276
359
|
|