@formatjs/cli-lib 8.0.8 → 8.1.1

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 (40) hide show
  1. package/index.d.ts +7 -7
  2. package/index.js +2 -2
  3. package/main.d.ts +0 -1
  4. package/main.js +1 -1
  5. package/package.json +5 -5
  6. package/src/cli.js +99 -153
  7. package/src/compile.d.ts +45 -45
  8. package/src/compile.js +79 -80
  9. package/src/compile_folder.d.ts +1 -1
  10. package/src/compile_folder.js +6 -6
  11. package/src/console_utils.d.ts +3 -1
  12. package/src/console_utils.js +44 -46
  13. package/src/extract.d.ts +68 -68
  14. package/src/extract.js +165 -182
  15. package/src/formatters/crowdin.d.ts +3 -3
  16. package/src/formatters/crowdin.js +19 -20
  17. package/src/formatters/default.d.ts +1 -1
  18. package/src/formatters/default.js +8 -7
  19. package/src/formatters/index.d.ts +6 -6
  20. package/src/formatters/index.js +29 -34
  21. package/src/formatters/lokalise.d.ts +5 -5
  22. package/src/formatters/lokalise.js +16 -17
  23. package/src/formatters/simple.d.ts +1 -1
  24. package/src/formatters/simple.js +7 -6
  25. package/src/formatters/smartling.d.ts +13 -15
  26. package/src/formatters/smartling.js +35 -40
  27. package/src/formatters/transifex.d.ts +5 -5
  28. package/src/formatters/transifex.js +16 -17
  29. package/src/gts_extractor.js +11 -11
  30. package/src/hbs_extractor.js +34 -41
  31. package/src/parse_script.d.ts +5 -5
  32. package/src/parse_script.js +40 -43
  33. package/src/pseudo_locale.d.ts +15 -15
  34. package/src/pseudo_locale.js +210 -94
  35. package/src/verify/checkExtraKeys.js +28 -32
  36. package/src/verify/checkMissingKeys.js +29 -33
  37. package/src/verify/checkStructuralEquality.js +67 -71
  38. package/src/verify/index.d.ts +5 -5
  39. package/src/verify/index.js +24 -27
  40. package/src/vue_extractor.js +52 -62
@@ -1,115 +1,231 @@
1
- import { parse, TYPE, isLiteralElement, isPluralElement, isSelectElement, isTagElement, } from '@formatjs/icu-messageformat-parser';
1
+ import { parse, TYPE, isLiteralElement, isPluralElement, isSelectElement, isTagElement } from "@formatjs/icu-messageformat-parser";
2
2
  function forEachLiteralElement(ast, fn) {
3
- ast.forEach(el => {
4
- if (isLiteralElement(el)) {
5
- fn(el);
6
- }
7
- else if (isPluralElement(el) || isSelectElement(el)) {
8
- for (const opt of Object.values(el.options)) {
9
- forEachLiteralElement(opt.value, fn);
10
- }
11
- }
12
- else if (isTagElement(el)) {
13
- forEachLiteralElement(el.children, fn);
14
- }
15
- });
3
+ ast.forEach((el) => {
4
+ if (isLiteralElement(el)) {
5
+ fn(el);
6
+ } else if (isPluralElement(el) || isSelectElement(el)) {
7
+ for (const opt of Object.values(el.options)) {
8
+ forEachLiteralElement(opt.value, fn);
9
+ }
10
+ } else if (isTagElement(el)) {
11
+ forEachLiteralElement(el.children, fn);
12
+ }
13
+ });
16
14
  }
17
15
  export function generateXXLS(msg) {
18
- const ast = typeof msg === 'string' ? parse(msg) : msg;
19
- const lastChunk = ast[ast.length - 1];
20
- if (lastChunk && isLiteralElement(lastChunk)) {
21
- lastChunk.value += 'SSSSSSSSSSSSSSSSSSSSSSSSS';
22
- return ast;
23
- }
24
- return [...ast, { type: TYPE.literal, value: 'SSSSSSSSSSSSSSSSSSSSSSSSS' }];
16
+ const ast = typeof msg === "string" ? parse(msg) : msg;
17
+ const lastChunk = ast[ast.length - 1];
18
+ if (lastChunk && isLiteralElement(lastChunk)) {
19
+ lastChunk.value += "SSSSSSSSSSSSSSSSSSSSSSSSS";
20
+ return ast;
21
+ }
22
+ return [...ast, {
23
+ type: TYPE.literal,
24
+ value: "SSSSSSSSSSSSSSSSSSSSSSSSS"
25
+ }];
25
26
  }
26
27
  export function generateXXAC(msg) {
27
- const ast = typeof msg === 'string' ? parse(msg) : msg;
28
- forEachLiteralElement(ast, el => {
29
- el.value = el.value.toUpperCase();
30
- });
31
- return ast;
28
+ const ast = typeof msg === "string" ? parse(msg) : msg;
29
+ forEachLiteralElement(ast, (el) => {
30
+ el.value = el.value.toUpperCase();
31
+ });
32
+ return ast;
32
33
  }
33
34
  export function generateXXHA(msg) {
34
- const ast = typeof msg === 'string' ? parse(msg) : msg;
35
- const [firstChunk, ...rest] = ast;
36
- if (firstChunk && isLiteralElement(firstChunk)) {
37
- firstChunk.value = '[javascript]' + firstChunk.value;
38
- return [firstChunk, ...rest];
39
- }
40
- return [{ type: TYPE.literal, value: '[javascript]' }, ...ast];
35
+ const ast = typeof msg === "string" ? parse(msg) : msg;
36
+ const [firstChunk, ...rest] = ast;
37
+ if (firstChunk && isLiteralElement(firstChunk)) {
38
+ firstChunk.value = "[javascript]" + firstChunk.value;
39
+ return [firstChunk, ...rest];
40
+ }
41
+ return [{
42
+ type: TYPE.literal,
43
+ value: "[javascript]"
44
+ }, ...ast];
41
45
  }
42
46
  const ACCENTED_MAP = {
43
- // ȦƁƇḒḖƑƓĦĪĴĶĿḾȠǾƤɊŘŞŦŬṼẆẊẎẐ
44
- // prettier-ignore
45
- "caps": [550, 385, 391, 7698, 7702, 401, 403, 294, 298, 308, 310, 319, 7742, 544, 510, 420, 586, 344, 350, 358, 364, 7804, 7814, 7818, 7822, 7824],
46
- // ȧƀƈḓḗƒɠħīĵķŀḿƞǿƥɋřşŧŭṽẇẋẏẑ
47
- // prettier-ignore
48
- "small": [551, 384, 392, 7699, 7703, 402, 608, 295, 299, 309, 311, 320, 7743, 414, 511, 421, 587, 345, 351, 359, 365, 7805, 7815, 7819, 7823, 7825],
47
+ "caps": [
48
+ 550,
49
+ 385,
50
+ 391,
51
+ 7698,
52
+ 7702,
53
+ 401,
54
+ 403,
55
+ 294,
56
+ 298,
57
+ 308,
58
+ 310,
59
+ 319,
60
+ 7742,
61
+ 544,
62
+ 510,
63
+ 420,
64
+ 586,
65
+ 344,
66
+ 350,
67
+ 358,
68
+ 364,
69
+ 7804,
70
+ 7814,
71
+ 7818,
72
+ 7822,
73
+ 7824
74
+ ],
75
+ "small": [
76
+ 551,
77
+ 384,
78
+ 392,
79
+ 7699,
80
+ 7703,
81
+ 402,
82
+ 608,
83
+ 295,
84
+ 299,
85
+ 309,
86
+ 311,
87
+ 320,
88
+ 7743,
89
+ 414,
90
+ 511,
91
+ 421,
92
+ 587,
93
+ 345,
94
+ 351,
95
+ 359,
96
+ 365,
97
+ 7805,
98
+ 7815,
99
+ 7819,
100
+ 7823,
101
+ 7825
102
+ ]
49
103
  };
50
104
  const FLIPPED_MAP = {
51
- // ∀ԐↃᗡƎℲ⅁HIſӼ⅂WNOԀÒᴚS⊥∩ɅMX⅄Z
52
- // prettier-ignore
53
- "caps": [8704, 1296, 8579, 5601, 398, 8498, 8513, 72, 73, 383, 1276, 8514, 87, 78, 79, 1280, 210, 7450, 83, 8869, 8745, 581, 77, 88, 8516, 90],
54
- // ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz
55
- // prettier-ignore
56
- "small": [592, 113, 596, 112, 477, 607, 387, 613, 305, 638, 670, 645, 623, 117, 111, 100, 98, 633, 115, 647, 110, 652, 653, 120, 654, 122],
105
+ "caps": [
106
+ 8704,
107
+ 1296,
108
+ 8579,
109
+ 5601,
110
+ 398,
111
+ 8498,
112
+ 8513,
113
+ 72,
114
+ 73,
115
+ 383,
116
+ 1276,
117
+ 8514,
118
+ 87,
119
+ 78,
120
+ 79,
121
+ 1280,
122
+ 210,
123
+ 7450,
124
+ 83,
125
+ 8869,
126
+ 8745,
127
+ 581,
128
+ 77,
129
+ 88,
130
+ 8516,
131
+ 90
132
+ ],
133
+ "small": [
134
+ 592,
135
+ 113,
136
+ 596,
137
+ 112,
138
+ 477,
139
+ 607,
140
+ 387,
141
+ 613,
142
+ 305,
143
+ 638,
144
+ 670,
145
+ 645,
146
+ 623,
147
+ 117,
148
+ 111,
149
+ 100,
150
+ 98,
151
+ 633,
152
+ 115,
153
+ 647,
154
+ 110,
155
+ 652,
156
+ 653,
157
+ 120,
158
+ 654,
159
+ 122
160
+ ]
57
161
  };
58
162
  /**
59
- * Based on: https://hg.mozilla.org/mozilla-central/file/a1f74e8c8fb72390d22054d6b00c28b1a32f6c43/intl/l10n/L10nRegistry.jsm#l425
60
- */
163
+ * Based on: https://hg.mozilla.org/mozilla-central/file/a1f74e8c8fb72390d22054d6b00c28b1a32f6c43/intl/l10n/L10nRegistry.jsm#l425
164
+ */
61
165
  function transformString(map, elongate = false, msg) {
62
- return msg.replace(/[a-z]/gi, ch => {
63
- const cc = ch.charCodeAt(0);
64
- if (cc >= 97 && cc <= 122) {
65
- const newChar = String.fromCodePoint(map.small[cc - 97]);
66
- // duplicate "a", "e", "o" and "u" to emulate ~30% longer text
67
- if (elongate && (cc === 97 || cc === 101 || cc === 111 || cc === 117)) {
68
- return newChar + newChar;
69
- }
70
- return newChar;
71
- }
72
- if (cc >= 65 && cc <= 90) {
73
- return String.fromCodePoint(map.caps[cc - 65]);
74
- }
75
- return ch;
76
- });
166
+ return msg.replace(/[a-z]/gi, (ch) => {
167
+ const cc = ch.charCodeAt(0);
168
+ if (cc >= 97 && cc <= 122) {
169
+ const newChar = String.fromCodePoint(map.small[cc - 97]);
170
+ // duplicate "a", "e", "o" and "u" to emulate ~30% longer text
171
+ if (elongate && (cc === 97 || cc === 101 || cc === 111 || cc === 117)) {
172
+ return newChar + newChar;
173
+ }
174
+ return newChar;
175
+ }
176
+ if (cc >= 65 && cc <= 90) {
177
+ return String.fromCodePoint(map.caps[cc - 65]);
178
+ }
179
+ return ch;
180
+ });
77
181
  }
78
182
  /**
79
- * accented - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
80
- * --------------------------------
81
- *
82
- * This locale replaces all Latin characters with their accented equivalents, and duplicates some
83
- * vowels to create roughly 30% longer strings. Strings are wrapped in markers (square brackets),
84
- * which help with detecting truncation.
85
- */
183
+ * accented - Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ
184
+ * --------------------------------
185
+ *
186
+ * This locale replaces all Latin characters with their accented equivalents, and duplicates some
187
+ * vowels to create roughly 30% longer strings. Strings are wrapped in markers (square brackets),
188
+ * which help with detecting truncation.
189
+ */
86
190
  export function generateENXA(msg) {
87
- const ast = typeof msg === 'string' ? parse(msg) : msg;
88
- forEachLiteralElement(ast, el => {
89
- el.value = transformString(ACCENTED_MAP, true, el.value);
90
- });
91
- return [
92
- { type: TYPE.literal, value: '[' },
93
- ...ast,
94
- { type: TYPE.literal, value: ']' },
95
- ];
191
+ const ast = typeof msg === "string" ? parse(msg) : msg;
192
+ forEachLiteralElement(ast, (el) => {
193
+ el.value = transformString(ACCENTED_MAP, true, el.value);
194
+ });
195
+ return [
196
+ {
197
+ type: TYPE.literal,
198
+ value: "["
199
+ },
200
+ ...ast,
201
+ {
202
+ type: TYPE.literal,
203
+ value: "]"
204
+ }
205
+ ];
96
206
  }
97
207
  /**
98
- * bidi - ɥsıʅƃuƎ ıpıԐ
99
- * -------------------
100
- *
101
- * This strategy replaces all Latin characters with their 180 degree rotated versions and enforces
102
- * right to left text flow using Unicode UAX#9 Explicit Directional Embeddings. In this mode, the UI
103
- * directionality will also be set to right-to-left.
104
- */
208
+ * bidi - ɥsıʅƃuƎ ıpıԐ
209
+ * -------------------
210
+ *
211
+ * This strategy replaces all Latin characters with their 180 degree rotated versions and enforces
212
+ * right to left text flow using Unicode UAX#9 Explicit Directional Embeddings. In this mode, the UI
213
+ * directionality will also be set to right-to-left.
214
+ */
105
215
  export function generateENXB(msg) {
106
- const ast = typeof msg === 'string' ? parse(msg) : msg;
107
- forEachLiteralElement(ast, el => {
108
- el.value = transformString(FLIPPED_MAP, false, el.value);
109
- });
110
- return [
111
- { type: TYPE.literal, value: '\u202e' },
112
- ...ast,
113
- { type: TYPE.literal, value: '\u202c' },
114
- ];
216
+ const ast = typeof msg === "string" ? parse(msg) : msg;
217
+ forEachLiteralElement(ast, (el) => {
218
+ el.value = transformString(FLIPPED_MAP, false, el.value);
219
+ });
220
+ return [
221
+ {
222
+ type: TYPE.literal,
223
+ value: "‮"
224
+ },
225
+ ...ast,
226
+ {
227
+ type: TYPE.literal,
228
+ value: "‬"
229
+ }
230
+ ];
115
231
  }
@@ -1,36 +1,32 @@
1
- import { debug, writeStderr } from '../console_utils.js';
1
+ import { debug, writeStderr } from "../console_utils.js";
2
2
  /**
3
- * Flatten nested obj into list of keys, delimited by `.`
4
- * @param obj
5
- * @param parentKey
6
- * @returns
7
- */
8
- function extractKeys(obj, parentKey = '') {
9
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
10
- return [];
11
- }
12
- return Object.keys(obj)
13
- .map(k => [parentKey ? `${parentKey}.${k}` : k, ...extractKeys(obj[k], k)])
14
- .flat();
3
+ * Flatten nested obj into list of keys, delimited by `.`
4
+ * @param obj
5
+ * @param parentKey
6
+ * @returns
7
+ */
8
+ function extractKeys(obj, parentKey = "") {
9
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
10
+ return [];
11
+ }
12
+ return Object.keys(obj).map((k) => [parentKey ? `${parentKey}.${k}` : k, ...extractKeys(obj[k], k)]).flat();
15
13
  }
16
14
  export async function checkExtraKeys(translationFilesContents, sourceLocale) {
17
- debug('Checking translation files:');
18
- const sourceContent = translationFilesContents[sourceLocale];
19
- if (!sourceContent) {
20
- throw new Error(`Missing source ${sourceLocale}.json file`);
21
- }
22
- const sourceKeys = extractKeys(sourceContent);
23
- return Object.entries(translationFilesContents)
24
- .filter(([locale]) => locale !== sourceLocale)
25
- .reduce((result, [locale, content]) => {
26
- const localeKeys = new Set(extractKeys(content));
27
- const extraKeys = new Set(Array.from(localeKeys).filter(k => !sourceKeys.includes(k)));
28
- if (!extraKeys.size) {
29
- return result;
30
- }
31
- writeStderr('---------------------------------\n');
32
- writeStderr(`Extra translation keys for locale ${locale}:\n`);
33
- extraKeys.forEach(r => writeStderr(`${r}\n`));
34
- return false;
35
- }, true);
15
+ debug("Checking translation files:");
16
+ const sourceContent = translationFilesContents[sourceLocale];
17
+ if (!sourceContent) {
18
+ throw new Error(`Missing source ${sourceLocale}.json file`);
19
+ }
20
+ const sourceKeys = extractKeys(sourceContent);
21
+ return Object.entries(translationFilesContents).filter(([locale]) => locale !== sourceLocale).reduce((result, [locale, content]) => {
22
+ const localeKeys = new Set(extractKeys(content));
23
+ const extraKeys = new Set(Array.from(localeKeys).filter((k) => !sourceKeys.includes(k)));
24
+ if (!extraKeys.size) {
25
+ return result;
26
+ }
27
+ writeStderr("---------------------------------\n");
28
+ writeStderr(`Extra translation keys for locale ${locale}:\n`);
29
+ extraKeys.forEach((r) => writeStderr(`${r}\n`));
30
+ return false;
31
+ }, true);
36
32
  }
@@ -1,37 +1,33 @@
1
- import { debug, writeStderr } from '../console_utils.js';
1
+ import { debug, writeStderr } from "../console_utils.js";
2
2
  /**
3
- * Flatten nested obj into list of keys, delimited by `.`
4
- * @param obj
5
- * @param parentKey
6
- * @returns
7
- */
8
- function extractKeys(obj, parentKey = '') {
9
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
10
- return [];
11
- }
12
- return Object.keys(obj)
13
- .map(k => [parentKey ? `${parentKey}.${k}` : k, ...extractKeys(obj[k], k)])
14
- .flat();
3
+ * Flatten nested obj into list of keys, delimited by `.`
4
+ * @param obj
5
+ * @param parentKey
6
+ * @returns
7
+ */
8
+ function extractKeys(obj, parentKey = "") {
9
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
10
+ return [];
11
+ }
12
+ return Object.keys(obj).map((k) => [parentKey ? `${parentKey}.${k}` : k, ...extractKeys(obj[k], k)]).flat();
15
13
  }
16
14
  export async function checkMissingKeys(translationFilesContents, sourceLocale) {
17
- debug('Checking translation files:');
18
- const sourceContent = translationFilesContents[sourceLocale];
19
- if (!sourceContent) {
20
- throw new Error(`Missing source ${sourceLocale}.json file`);
21
- }
22
- const sourceKeys = extractKeys(sourceContent);
23
- return Object.entries(translationFilesContents)
24
- .filter(([locale]) => locale !== sourceLocale)
25
- .reduce((result, [locale, content]) => {
26
- const localeKeys = new Set(extractKeys(content));
27
- const missingKeys = new Set(sourceKeys.filter(r => !localeKeys.has(r)));
28
- // We're being lenient here since only missing keys are currently considered breaking
29
- if (!missingKeys.size) {
30
- return result;
31
- }
32
- writeStderr('---------------------------------\n');
33
- writeStderr(`Missing translation keys for locale ${locale}:\n`);
34
- missingKeys.forEach(r => writeStderr(`${r}\n`));
35
- return false;
36
- }, true);
15
+ debug("Checking translation files:");
16
+ const sourceContent = translationFilesContents[sourceLocale];
17
+ if (!sourceContent) {
18
+ throw new Error(`Missing source ${sourceLocale}.json file`);
19
+ }
20
+ const sourceKeys = extractKeys(sourceContent);
21
+ return Object.entries(translationFilesContents).filter(([locale]) => locale !== sourceLocale).reduce((result, [locale, content]) => {
22
+ const localeKeys = new Set(extractKeys(content));
23
+ const missingKeys = new Set(sourceKeys.filter((r) => !localeKeys.has(r)));
24
+ // We're being lenient here since only missing keys are currently considered breaking
25
+ if (!missingKeys.size) {
26
+ return result;
27
+ }
28
+ writeStderr("---------------------------------\n");
29
+ writeStderr(`Missing translation keys for locale ${locale}:\n`);
30
+ missingKeys.forEach((r) => writeStderr(`${r}\n`));
31
+ return false;
32
+ }, true);
37
33
  }
@@ -1,75 +1,71 @@
1
- import { isStructurallySame, parse, } from '@formatjs/icu-messageformat-parser';
2
- import { debug, writeStderr } from '../console_utils.js';
3
- import { error } from 'console';
1
+ import { isStructurallySame, parse } from "@formatjs/icu-messageformat-parser";
2
+ import { debug, writeStderr } from "../console_utils.js";
3
+ import { error } from "console";
4
4
  /**
5
- * Flatten nested obj into list of keys, delimited by `.`
6
- * @param obj
7
- * @param parentKey
8
- * @returns
9
- */
10
- function flatten(obj, parentKey = '') {
11
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
12
- return {};
13
- }
14
- return Object.keys(obj).reduce((all, k) => {
15
- const value = obj[k];
16
- const key = parentKey ? `${parentKey}.${k}` : k;
17
- if (typeof value === 'object') {
18
- Object.assign(all, flatten(value, key));
19
- }
20
- else {
21
- all[key] = value;
22
- }
23
- return all;
24
- }, {});
5
+ * Flatten nested obj into list of keys, delimited by `.`
6
+ * @param obj
7
+ * @param parentKey
8
+ * @returns
9
+ */
10
+ function flatten(obj, parentKey = "") {
11
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
12
+ return {};
13
+ }
14
+ return Object.keys(obj).reduce((all, k) => {
15
+ const value = obj[k];
16
+ const key = parentKey ? `${parentKey}.${k}` : k;
17
+ if (typeof value === "object") {
18
+ Object.assign(all, flatten(value, key));
19
+ } else {
20
+ all[key] = value;
21
+ }
22
+ return all;
23
+ }, {});
25
24
  }
26
25
  export async function checkStructuralEquality(translationFilesContents, sourceLocale) {
27
- debug('Checking translation files:');
28
- const sourceContent = translationFilesContents[sourceLocale];
29
- if (!sourceContent) {
30
- throw new Error(`Missing source ${sourceLocale}.json file`);
31
- }
32
- const sourceMessages = Object.entries(flatten(sourceContent)).reduce((all, [key, value]) => {
33
- try {
34
- all[key] = parse(value);
35
- }
36
- catch (e) {
37
- error('Error parsing message', key, value, e);
38
- }
39
- return all;
40
- }, {});
41
- return Object.entries(translationFilesContents)
42
- .filter(([locale]) => locale !== sourceLocale)
43
- .reduce((result, [locale, content]) => {
44
- const localeMessages = flatten(content);
45
- const problematicKeys = Object.keys(sourceMessages)
46
- .map(k => {
47
- if (!localeMessages[k]) {
48
- return { key: k, success: true };
49
- }
50
- const sourceMessage = sourceMessages[k];
51
- try {
52
- const localeMessage = parse(localeMessages[k]);
53
- return {
54
- key: k,
55
- ...isStructurallySame(sourceMessage, localeMessage),
56
- };
57
- }
58
- catch (e) {
59
- return {
60
- key: k,
61
- success: false,
62
- error: e instanceof Error ? e : new Error(String(e)),
63
- };
64
- }
65
- })
66
- .filter(s => !s.success);
67
- if (!problematicKeys.length) {
68
- return result;
69
- }
70
- writeStderr('---------------------------------\n');
71
- writeStderr(`These translation keys for locale ${locale} are structurally different from ${sourceLocale}:\n`);
72
- problematicKeys.forEach(({ key, error }) => writeStderr(`${key}: ${error?.message}\n`));
73
- return false;
74
- }, true);
26
+ debug("Checking translation files:");
27
+ const sourceContent = translationFilesContents[sourceLocale];
28
+ if (!sourceContent) {
29
+ throw new Error(`Missing source ${sourceLocale}.json file`);
30
+ }
31
+ const sourceMessages = Object.entries(flatten(sourceContent)).reduce((all, [key, value]) => {
32
+ try {
33
+ all[key] = parse(value);
34
+ } catch (e) {
35
+ error("Error parsing message", key, value, e);
36
+ }
37
+ return all;
38
+ }, {});
39
+ return Object.entries(translationFilesContents).filter(([locale]) => locale !== sourceLocale).reduce((result, [locale, content]) => {
40
+ const localeMessages = flatten(content);
41
+ const problematicKeys = Object.keys(sourceMessages).map((k) => {
42
+ if (!localeMessages[k]) {
43
+ return {
44
+ key: k,
45
+ success: true
46
+ };
47
+ }
48
+ const sourceMessage = sourceMessages[k];
49
+ try {
50
+ const localeMessage = parse(localeMessages[k]);
51
+ return {
52
+ key: k,
53
+ ...isStructurallySame(sourceMessage, localeMessage)
54
+ };
55
+ } catch (e) {
56
+ return {
57
+ key: k,
58
+ success: false,
59
+ error: e instanceof Error ? e : new Error(String(e))
60
+ };
61
+ }
62
+ }).filter((s) => !s.success);
63
+ if (!problematicKeys.length) {
64
+ return result;
65
+ }
66
+ writeStderr("---------------------------------\n");
67
+ writeStderr(`These translation keys for locale ${locale} are structurally different from ${sourceLocale}:\n`);
68
+ problematicKeys.forEach(({ key, error }) => writeStderr(`${key}: ${error?.message}\n`));
69
+ return false;
70
+ }, true);
75
71
  }
@@ -1,8 +1,8 @@
1
1
  export interface VerifyOpts {
2
- sourceLocale: string;
3
- missingKeys: boolean;
4
- extraKeys: boolean;
5
- structuralEquality: boolean;
6
- ignore?: string[];
2
+ sourceLocale: string;
3
+ missingKeys: boolean;
4
+ extraKeys: boolean;
5
+ structuralEquality: boolean;
6
+ ignore?: string[];
7
7
  }
8
8
  export declare function verify(files: string[], { sourceLocale, missingKeys, extraKeys, structuralEquality }: VerifyOpts): Promise<void>;