@vocab/phrase 1.0.0 → 1.1.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.
@@ -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
- console.log('\nLink:', response.headers.get('Link'), '\n'); // Print All Headers:
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
- console.log(`Internal Result (Length: ${result.length})\n`);
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('Cant parse next page URL');
50
+ throw new Error("Can't parse next page URL");
51
51
  }
52
52
 
53
- console.log('Results recieved with next page: ', nextPageUrl);
53
+ console.log('Results received with next page: ', nextPageUrl);
54
54
  const nextPageResult = await _callPhrase(nextPageUrl, options);
55
55
  return [...result, ...nextPageResult];
56
56
  }
@@ -71,7 +71,6 @@ async function callPhrase(relativePath, options = {}) {
71
71
  }
72
72
 
73
73
  return _callPhrase(`https://api.phrase.com/v2/projects/${projectId}/${relativePath}`, options).then(result => {
74
- // console.log('Result:', result);
75
74
  if (Array.isArray(result)) {
76
75
  console.log('Result length:', result.length);
77
76
  }
@@ -109,12 +108,35 @@ async function pushTranslationsByLocale(contents, locale, branch) {
109
108
  formData.append('locale_id', locale);
110
109
  formData.append('branch', branch);
111
110
  formData.append('update_translations', 'true');
112
- trace('Starting to upload:', locale);
113
- await callPhrase(`uploads`, {
111
+ log('Starting to upload:', locale, '\n');
112
+ const {
113
+ id
114
+ } = await callPhrase(`uploads`, {
114
115
  method: 'POST',
115
116
  body: formData
116
117
  });
118
+ log('Upload ID:', id, '\n');
117
119
  log('Successfully Uploaded:', locale, '\n');
120
+ return {
121
+ uploadId: id
122
+ };
123
+ }
124
+ async function deleteUnusedKeys(uploadId, locale, branch) {
125
+ const query = `unmentioned_in_upload:${uploadId}`;
126
+ const {
127
+ records_affected
128
+ } = await callPhrase('keys', {
129
+ method: 'DELETE',
130
+ headers: {
131
+ 'Content-Type': 'application/json'
132
+ },
133
+ body: JSON.stringify({
134
+ branch,
135
+ locale_id: locale,
136
+ q: query
137
+ })
138
+ });
139
+ log('Successfully deleted', records_affected, 'unused keys from branch', branch);
118
140
  }
119
141
  async function ensureBranch(branch) {
120
142
  await callPhrase(`branches`, {
@@ -126,7 +148,7 @@ async function ensureBranch(branch) {
126
148
  name: branch
127
149
  })
128
150
  });
129
- trace('Created branch:', branch);
151
+ log('Created branch:', branch);
130
152
  }
131
153
 
132
154
  async function pull({
@@ -137,6 +159,13 @@ async function pull({
137
159
  const alternativeLanguages = getAltLanguages(config);
138
160
  const allPhraseTranslations = await pullAllTranslations(branch);
139
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
+
140
169
  const allVocabTranslations = await loadAllTranslations({
141
170
  fallbacks: 'none',
142
171
  includeNodeModules: false
@@ -162,31 +191,33 @@ async function pull({
162
191
  await writeFile(loadedTranslation.filePath, `${JSON.stringify(defaultValues, null, 2)}\n`);
163
192
 
164
193
  for (const alternativeLanguage of alternativeLanguages) {
165
- const altTranslations = { ...loadedTranslation.languages[alternativeLanguage]
166
- };
167
- const phraseAltTranslations = allPhraseTranslations[alternativeLanguage];
194
+ if (alternativeLanguage in allPhraseTranslations) {
195
+ const altTranslations = { ...loadedTranslation.languages[alternativeLanguage]
196
+ };
197
+ const phraseAltTranslations = allPhraseTranslations[alternativeLanguage];
198
+
199
+ for (const key of localKeys) {
200
+ var _phraseAltTranslation;
168
201
 
169
- for (const key of localKeys) {
170
- var _phraseAltTranslation;
202
+ const phraseKey = getUniqueKey(key, loadedTranslation.namespace);
203
+ const phraseTranslationMessage = (_phraseAltTranslation = phraseAltTranslations[phraseKey]) === null || _phraseAltTranslation === void 0 ? void 0 : _phraseAltTranslation.message;
171
204
 
172
- const phraseKey = getUniqueKey(key, loadedTranslation.namespace);
173
- const phraseTranslationMessage = (_phraseAltTranslation = phraseAltTranslations[phraseKey]) === null || _phraseAltTranslation === void 0 ? void 0 : _phraseAltTranslation.message;
205
+ if (!phraseTranslationMessage) {
206
+ trace(`Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`);
207
+ continue;
208
+ }
174
209
 
175
- if (!phraseTranslationMessage) {
176
- trace(`Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`);
177
- continue;
210
+ altTranslations[key] = { ...altTranslations[key],
211
+ message: phraseTranslationMessage
212
+ };
178
213
  }
179
214
 
180
- altTranslations[key] = { ...altTranslations[key],
181
- message: phraseTranslationMessage
182
- };
215
+ const altTranslationFilePath = getAltLanguageFilePath(loadedTranslation.filePath, alternativeLanguage);
216
+ await mkdir(path.dirname(altTranslationFilePath), {
217
+ recursive: true
218
+ });
219
+ await writeFile(altTranslationFilePath, `${JSON.stringify(altTranslations, null, 2)}\n`);
183
220
  }
184
-
185
- const altTranslationFilePath = getAltLanguageFilePath(loadedTranslation.filePath, alternativeLanguage);
186
- await mkdir(path.dirname(altTranslationFilePath), {
187
- recursive: true
188
- });
189
- await writeFile(altTranslationFilePath, `${JSON.stringify(altTranslations, null, 2)}\n`);
190
221
  }
191
222
  }
192
223
  }
@@ -195,7 +226,8 @@ async function pull({
195
226
  * Uploading to the Phrase API for each language. Adding a unique namespace to each key using file path they key came from
196
227
  */
197
228
  async function push({
198
- branch
229
+ branch,
230
+ deleteUnusedKeys: deleteUnusedKeys$1
199
231
  }, config) {
200
232
  const allLanguageTranslations = await loadAllTranslations({
201
233
  fallbacks: 'none',
@@ -228,7 +260,13 @@ async function push({
228
260
 
229
261
  for (const language of allLanguages) {
230
262
  if (phraseTranslations[language]) {
231
- await pushTranslationsByLocale(phraseTranslations[language], language, branch);
263
+ const {
264
+ uploadId
265
+ } = await pushTranslationsByLocale(phraseTranslations[language], language, branch);
266
+
267
+ if (deleteUnusedKeys$1) {
268
+ await deleteUnusedKeys(uploadId, language, branch);
269
+ }
232
270
  }
233
271
  }
234
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vocab/phrase",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "main": "dist/vocab-phrase.cjs.js",
5
5
  "module": "dist/vocab-phrase.esm.js",
6
6
  "author": "SEEK",
package/src/phrase-api.ts CHANGED
@@ -31,14 +31,14 @@ function _callPhrase(path: string, options: Parameters<typeof fetch>[1] = {}) {
31
31
  'X-Rate-Limit-Reset',
32
32
  )} seconds remaining})`,
33
33
  );
34
- console.log('\nLink:', response.headers.get('Link'), '\n');
34
+ trace('\nLink:', response.headers.get('Link'), '\n');
35
35
  // Print All Headers:
36
36
  // console.log(Array.from(r.headers.entries()));
37
37
 
38
38
  try {
39
39
  const result = await response.json();
40
40
 
41
- console.log(`Internal Result (Length: ${result.length})\n`);
41
+ trace(`Internal Result (Length: ${result.length})\n`);
42
42
 
43
43
  if (
44
44
  (!options.method || options.method === 'GET') &&
@@ -48,10 +48,10 @@ function _callPhrase(path: string, options: Parameters<typeof fetch>[1] = {}) {
48
48
  response.headers.get('Link')?.match(/<([^>]*)>; rel=next/) ?? [];
49
49
 
50
50
  if (!nextPageUrl) {
51
- throw new Error('Cant parse next page URL');
51
+ throw new Error("Can't parse next page URL");
52
52
  }
53
53
 
54
- console.log('Results recieved with next page: ', nextPageUrl);
54
+ console.log('Results received with next page: ', nextPageUrl);
55
55
 
56
56
  const nextPageResult = (await _callPhrase(nextPageUrl, options)) as any;
57
57
 
@@ -66,10 +66,10 @@ function _callPhrase(path: string, options: Parameters<typeof fetch>[1] = {}) {
66
66
  });
67
67
  }
68
68
 
69
- export async function callPhrase(
69
+ export async function callPhrase<T = any>(
70
70
  relativePath: string,
71
71
  options: Parameters<typeof fetch>[1] = {},
72
- ) {
72
+ ): Promise<T> {
73
73
  const projectId = process.env.PHRASE_PROJECT_ID;
74
74
 
75
75
  if (!projectId) {
@@ -80,7 +80,6 @@ export async function callPhrase(
80
80
  options,
81
81
  )
82
82
  .then((result) => {
83
- // console.log('Result:', result);
84
83
  if (Array.isArray(result)) {
85
84
  console.log('Result length:', result.length);
86
85
  }
@@ -95,18 +94,23 @@ export async function callPhrase(
95
94
  export async function pullAllTranslations(
96
95
  branch: string,
97
96
  ): Promise<TranslationsByLanguage> {
98
- const phraseResult: Array<{
99
- key: { name: string };
100
- locale: { code: string };
101
- content: string;
102
- }> = await callPhrase(`translations?branch=${branch}&per_page=100`);
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
+
103
105
  const translations: TranslationsByLanguage = {};
106
+
104
107
  for (const r of phraseResult) {
105
108
  if (!translations[r.locale.code]) {
106
109
  translations[r.locale.code] = {};
107
110
  }
108
111
  translations[r.locale.code][r.key.name] = { message: r.content };
109
112
  }
113
+
110
114
  return translations;
111
115
  }
112
116
 
@@ -127,13 +131,41 @@ export async function pushTranslationsByLocale(
127
131
  formData.append('branch', branch);
128
132
  formData.append('update_translations', 'true');
129
133
 
130
- trace('Starting to upload:', locale);
134
+ log('Starting to upload:', locale, '\n');
131
135
 
132
- await callPhrase(`uploads`, {
136
+ const { id } = await callPhrase<{ id: string }>(`uploads`, {
133
137
  method: 'POST',
134
138
  body: formData,
135
139
  });
140
+ log('Upload ID:', id, '\n');
136
141
  log('Successfully Uploaded:', locale, '\n');
142
+
143
+ return { uploadId: id };
144
+ }
145
+
146
+ export async function deleteUnusedKeys(
147
+ uploadId: string,
148
+ locale: string,
149
+ branch: string,
150
+ ) {
151
+ const query = `unmentioned_in_upload:${uploadId}`;
152
+ const { records_affected } = await callPhrase<{ records_affected: number }>(
153
+ 'keys',
154
+ {
155
+ method: 'DELETE',
156
+ headers: {
157
+ 'Content-Type': 'application/json',
158
+ },
159
+ body: JSON.stringify({ branch, locale_id: locale, q: query }),
160
+ },
161
+ );
162
+
163
+ log(
164
+ 'Successfully deleted',
165
+ records_affected,
166
+ 'unused keys from branch',
167
+ branch,
168
+ );
137
169
  }
138
170
 
139
171
  export async function ensureBranch(branch: string) {
@@ -144,5 +176,6 @@ export async function ensureBranch(branch: string) {
144
176
  },
145
177
  body: JSON.stringify({ name: branch }),
146
178
  });
147
- trace('Created branch:', branch);
179
+
180
+ log('Created branch:', branch);
148
181
  }
@@ -2,6 +2,7 @@ import path from 'path';
2
2
  import { pull } from './pull-translations';
3
3
  import { pullAllTranslations } from './phrase-api';
4
4
  import { writeFile } from './file';
5
+ import { GeneratedLanguageTarget, LanguageTarget } from '@vocab/types';
5
6
 
6
7
  jest.mock('./file', () => ({
7
8
  writeFile: jest.fn(() => Promise.resolve),
@@ -13,68 +14,201 @@ jest.mock('./phrase-api', () => ({
13
14
  pullAllTranslations: jest.fn(() => Promise.resolve({ en: {}, fr: {} })),
14
15
  }));
15
16
 
16
- function runPhrase() {
17
+ const devLanguage = 'en';
18
+
19
+ function runPhrase(options: {
20
+ languages: LanguageTarget[];
21
+ generatedLanguages: GeneratedLanguageTarget[];
22
+ }) {
17
23
  return pull(
18
24
  { branch: 'tester' },
19
25
  {
20
- devLanguage: 'en',
21
- languages: [{ name: 'en' }, { name: 'fr' }],
26
+ ...options,
27
+ devLanguage,
22
28
  projectRoot: path.resolve(__dirname, '..', '..', '..', 'fixtures/phrase'),
23
29
  },
24
30
  );
25
31
  }
26
32
 
27
- describe('pull', () => {
28
- beforeEach(() => {
29
- (pullAllTranslations as jest.Mock).mockClear();
30
- (writeFile as jest.Mock).mockClear();
31
- });
32
- it('should resolve', async () => {
33
- await expect(runPhrase()).resolves.toBeUndefined();
33
+ describe('pull translations', () => {
34
+ describe('when pulling translations for languages that already have translations', () => {
35
+ beforeEach(() => {
36
+ jest.mocked(pullAllTranslations).mockClear();
37
+ jest.mocked(writeFile).mockClear();
38
+ jest.mocked(pullAllTranslations).mockImplementation(() =>
39
+ Promise.resolve({
40
+ en: {
41
+ 'hello.mytranslations': {
42
+ message: 'Hi there',
43
+ },
44
+ },
45
+ fr: {
46
+ 'hello.mytranslations': {
47
+ message: 'merci',
48
+ },
49
+ },
50
+ }),
51
+ );
52
+ });
34
53
 
35
- expect(writeFile as jest.Mock).toHaveBeenCalledTimes(2);
36
- });
37
- it('should update keys', async () => {
38
- (pullAllTranslations as jest.Mock).mockImplementation(() =>
39
- Promise.resolve({
40
- en: {
41
- 'hello.mytranslations': {
42
- message: 'Hi there',
54
+ const options = {
55
+ languages: [{ name: 'en' }, { name: 'fr' }],
56
+ generatedLanguages: [
57
+ {
58
+ name: 'generatedLanguage',
59
+ extends: 'en',
60
+ generator: {
61
+ transformMessage: (message: string) => `[${message}]`,
43
62
  },
44
63
  },
45
- fr: {
46
- 'hello.mytranslations': {
47
- message: 'merci',
64
+ ],
65
+ };
66
+
67
+ it('should resolve', async () => {
68
+ await expect(runPhrase(options)).resolves.toBeUndefined();
69
+
70
+ expect(jest.mocked(writeFile)).toHaveBeenCalledTimes(2);
71
+ });
72
+
73
+ it('should update keys', async () => {
74
+ await expect(runPhrase(options)).resolves.toBeUndefined();
75
+
76
+ expect(
77
+ jest
78
+ .mocked(writeFile)
79
+ .mock.calls.map(([_filePath, contents]) =>
80
+ JSON.parse(contents as string),
81
+ ),
82
+ ).toMatchInlineSnapshot(`
83
+ [
84
+ {
85
+ "hello": {
86
+ "message": "Hi there",
87
+ },
88
+ "world": {
89
+ "message": "world",
90
+ },
48
91
  },
49
- },
50
- }),
51
- );
52
-
53
- await expect(runPhrase()).resolves.toBeUndefined();
54
-
55
- expect(
56
- (writeFile as jest.Mock).mock.calls.map(
57
- ([_filePath, contents]: [string, string]) => JSON.parse(contents),
58
- ),
59
- ).toMatchInlineSnapshot(`
60
- Array [
61
- Object {
62
- "hello": Object {
63
- "message": "Hi there",
92
+ {
93
+ "hello": {
94
+ "message": "merci",
95
+ },
96
+ "world": {
97
+ "message": "monde",
98
+ },
99
+ },
100
+ ]
101
+ `);
102
+ });
103
+ });
104
+
105
+ describe('when pulling translations and some languages do not have any translations', () => {
106
+ beforeEach(() => {
107
+ jest.mocked(pullAllTranslations).mockClear();
108
+ jest.mocked(writeFile).mockClear();
109
+ jest.mocked(pullAllTranslations).mockImplementation(() =>
110
+ Promise.resolve({
111
+ en: {
112
+ 'hello.mytranslations': {
113
+ message: 'Hi there',
114
+ },
64
115
  },
65
- "world": Object {
66
- "message": "world",
116
+ fr: {
117
+ 'hello.mytranslations': {
118
+ message: 'merci',
119
+ },
120
+ },
121
+ }),
122
+ );
123
+ });
124
+
125
+ const options = {
126
+ languages: [{ name: 'en' }, { name: 'fr' }, { name: 'ja' }],
127
+ generatedLanguages: [
128
+ {
129
+ name: 'generatedLanguage',
130
+ extends: 'en',
131
+ generator: {
132
+ transformMessage: (message: string) => `[${message}]`,
67
133
  },
68
134
  },
69
- Object {
70
- "hello": Object {
71
- "message": "merci",
135
+ ],
136
+ };
137
+
138
+ it('should resolve', async () => {
139
+ await expect(runPhrase(options)).resolves.toBeUndefined();
140
+
141
+ expect(jest.mocked(writeFile)).toHaveBeenCalledTimes(2);
142
+ });
143
+
144
+ it('should update keys', async () => {
145
+ await expect(runPhrase(options)).resolves.toBeUndefined();
146
+
147
+ expect(
148
+ jest
149
+ .mocked(writeFile)
150
+ .mock.calls.map(([_filePath, contents]) =>
151
+ JSON.parse(contents as string),
152
+ ),
153
+ ).toMatchInlineSnapshot(`
154
+ [
155
+ {
156
+ "hello": {
157
+ "message": "Hi there",
158
+ },
159
+ "world": {
160
+ "message": "world",
161
+ },
162
+ },
163
+ {
164
+ "hello": {
165
+ "message": "merci",
166
+ },
167
+ "world": {
168
+ "message": "monde",
169
+ },
72
170
  },
73
- "world": Object {
74
- "message": "monde",
171
+ ]
172
+ `);
173
+ });
174
+ });
175
+
176
+ describe('when pulling translations and the project has not configured translations for the dev language', () => {
177
+ beforeEach(() => {
178
+ jest.mocked(pullAllTranslations).mockClear();
179
+ jest.mocked(writeFile).mockClear();
180
+ jest.mocked(pullAllTranslations).mockImplementation(() =>
181
+ Promise.resolve({
182
+ fr: {
183
+ 'hello.mytranslations': {
184
+ message: 'merci',
185
+ },
186
+ },
187
+ }),
188
+ );
189
+ });
190
+
191
+ const options = {
192
+ languages: [{ name: 'en' }, { name: 'fr' }],
193
+ generatedLanguages: [
194
+ {
195
+ name: 'generatedLanguage',
196
+ extends: 'en',
197
+ generator: {
198
+ transformMessage: (message: string) => `[${message}]`,
75
199
  },
76
200
  },
77
- ]
78
- `);
201
+ ],
202
+ };
203
+
204
+ it('should throw an error', async () => {
205
+ await expect(runPhrase(options)).rejects.toThrow(
206
+ new Error(
207
+ `Phrase did not return any translations for the configured development language "en".\nPlease ensure this language is present in your Phrase project's configuration.`,
208
+ ),
209
+ );
210
+
211
+ expect(jest.mocked(writeFile)).toHaveBeenCalledTimes(0);
212
+ });
79
213
  });
80
214
  });
@@ -14,6 +14,7 @@ import { trace } from './logger';
14
14
 
15
15
  interface PullOptions {
16
16
  branch?: string;
17
+ deleteUnusedKeys?: boolean;
17
18
  }
18
19
 
19
20
  export async function pull(
@@ -30,6 +31,17 @@ export async function pull(
30
31
  } and ${alternativeLanguages.join(', ')}`,
31
32
  );
32
33
 
34
+ const phraseLanguages = Object.keys(allPhraseTranslations);
35
+ trace(
36
+ `Found Phrase translations for languages ${phraseLanguages.join(', ')}`,
37
+ );
38
+
39
+ if (!phraseLanguages.includes(config.devLanguage)) {
40
+ throw new Error(
41
+ `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.`,
42
+ );
43
+ }
44
+
33
45
  const allVocabTranslations = await loadAllTranslations(
34
46
  { fallbacks: 'none', includeNodeModules: false },
35
47
  config,
@@ -59,41 +71,44 @@ export async function pull(
59
71
  );
60
72
 
61
73
  for (const alternativeLanguage of alternativeLanguages) {
62
- const altTranslations = {
63
- ...loadedTranslation.languages[alternativeLanguage],
64
- };
65
- const phraseAltTranslations = allPhraseTranslations[alternativeLanguage];
66
-
67
- for (const key of localKeys) {
68
- const phraseKey = getUniqueKey(key, loadedTranslation.namespace);
69
- const phraseTranslationMessage =
70
- phraseAltTranslations[phraseKey]?.message;
71
-
72
- if (!phraseTranslationMessage) {
73
- trace(
74
- `Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`,
75
- );
76
- continue;
74
+ if (alternativeLanguage in allPhraseTranslations) {
75
+ const altTranslations = {
76
+ ...loadedTranslation.languages[alternativeLanguage],
77
+ };
78
+ const phraseAltTranslations =
79
+ allPhraseTranslations[alternativeLanguage];
80
+
81
+ for (const key of localKeys) {
82
+ const phraseKey = getUniqueKey(key, loadedTranslation.namespace);
83
+ const phraseTranslationMessage =
84
+ phraseAltTranslations[phraseKey]?.message;
85
+
86
+ if (!phraseTranslationMessage) {
87
+ trace(
88
+ `Missing translation. No translation for key ${key} in phrase as ${phraseKey} in language ${alternativeLanguage}.`,
89
+ );
90
+ continue;
91
+ }
92
+
93
+ altTranslations[key] = {
94
+ ...altTranslations[key],
95
+ message: phraseTranslationMessage,
96
+ };
77
97
  }
78
98
 
79
- altTranslations[key] = {
80
- ...altTranslations[key],
81
- message: phraseTranslationMessage,
82
- };
83
- }
99
+ const altTranslationFilePath = getAltLanguageFilePath(
100
+ loadedTranslation.filePath,
101
+ alternativeLanguage,
102
+ );
84
103
 
85
- const altTranslationFilePath = getAltLanguageFilePath(
86
- loadedTranslation.filePath,
87
- alternativeLanguage,
88
- );
89
-
90
- await mkdir(path.dirname(altTranslationFilePath), {
91
- recursive: true,
92
- });
93
- await writeFile(
94
- altTranslationFilePath,
95
- `${JSON.stringify(altTranslations, null, 2)}\n`,
96
- );
104
+ await mkdir(path.dirname(altTranslationFilePath), {
105
+ recursive: true,
106
+ });
107
+ await writeFile(
108
+ altTranslationFilePath,
109
+ `${JSON.stringify(altTranslations, null, 2)}\n`,
110
+ );
111
+ }
97
112
  }
98
113
  }
99
114
  }