@lingual/i18n-check 0.8.2 → 0.8.3

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.
@@ -1,4 +1,4 @@
1
- import { CheckResult, InvalidTranslationsResult } from "./types";
1
+ import { CheckResult, InvalidTranslationsResult } from './types';
2
2
  export type StandardReporter = {
3
3
  file: string;
4
4
  key: string;
@@ -6,16 +6,16 @@ exports.formatTable = formatTable;
6
6
  exports.formatCheckResultTable = formatCheckResultTable;
7
7
  exports.formatInvalidTranslationsResultTable = formatInvalidTranslationsResultTable;
8
8
  exports.CheckOptions = [
9
- "invalidKeys",
10
- "missingKeys",
11
- "unused",
12
- "undefined",
9
+ 'invalidKeys',
10
+ 'missingKeys',
11
+ 'unused',
12
+ 'undefined',
13
13
  ];
14
14
  exports.contextMapping = {
15
- invalidKeys: "invalid",
16
- missingKeys: "missing",
17
- unused: "unused",
18
- undefined: "undefined",
15
+ invalidKeys: 'invalid',
16
+ missingKeys: 'missing',
17
+ unused: 'unused',
18
+ undefined: 'undefined',
19
19
  };
20
20
  function formatSummaryTable(result) {
21
21
  return formatTable(getSummaryRows(result));
@@ -25,9 +25,9 @@ const getSummaryRows = (checkResult) => {
25
25
  for (const [file, keys] of Object.entries(checkResult)) {
26
26
  rows.push([truncate(file), String(keys.length)]);
27
27
  }
28
- return [[["file", "total"]], rows];
28
+ return [[['file', 'total']], rows];
29
29
  };
30
- function formatTable(rowGroups, lineSep = "\n") {
30
+ function formatTable(rowGroups, lineSep = '\n') {
31
31
  // +2 for whitespace padding left and right
32
32
  const padding = 2;
33
33
  const colWidths = [];
@@ -39,40 +39,40 @@ function formatTable(rowGroups, lineSep = "\n") {
39
39
  }
40
40
  }
41
41
  const lines = [];
42
- lines.push(formatSeparatorRow(colWidths, "┌┬┐"));
42
+ lines.push(formatSeparatorRow(colWidths, '┌┬┐'));
43
43
  for (const rows of rowGroups) {
44
44
  for (const row of rows) {
45
45
  lines.push(formatRow(row, colWidths));
46
46
  }
47
- lines.push(formatSeparatorRow(colWidths, "├┼┤"));
47
+ lines.push(formatSeparatorRow(colWidths, '├┼┤'));
48
48
  }
49
- lines[lines.length - 1] = formatSeparatorRow(colWidths, "└┴┘");
49
+ lines[lines.length - 1] = formatSeparatorRow(colWidths, '└┴┘');
50
50
  return lines.join(lineSep);
51
51
  }
52
52
  function formatSeparatorRow(widths, [left, middle, right]) {
53
- return (left + widths.map((width) => "".padEnd(width, "")).join(middle) + right);
53
+ return (left + widths.map((width) => ''.padEnd(width, '')).join(middle) + right);
54
54
  }
55
55
  function formatRow(values, widths) {
56
56
  return (`│` +
57
57
  values
58
- .map((val, index) => ` ${val} `.padEnd(widths[index], " "))
59
- .join("") +
58
+ .map((val, index) => ` ${val} `.padEnd(widths[index], ' '))
59
+ .join('') +
60
60
  `│`);
61
61
  }
62
62
  const truncate = (chars, len = 80) => chars.length > 80 ? `${chars.substring(0, len)}...` : chars;
63
63
  function formatCheckResultTable(result) {
64
64
  return formatTable([
65
- [["file", "key"]],
65
+ [['file', 'key']],
66
66
  Object.entries(result).flatMap(([file, keys]) => keys.map((key) => [truncate(file), truncate(key)])),
67
67
  ]);
68
68
  }
69
69
  function formatInvalidTranslationsResultTable(result) {
70
70
  return formatTable([
71
- [["info", "result"]],
71
+ [['info', 'result']],
72
72
  ...Object.entries(result).flatMap(([file, errors]) => errors.map(({ key, msg }) => [
73
- ["file", truncate(file)],
74
- ["key", truncate(key)],
75
- ["msg", truncate(msg, 120)],
73
+ ['file', truncate(file)],
74
+ ['key', truncate(key)],
75
+ ['msg', truncate(msg, 120)],
76
76
  ])),
77
77
  ]);
78
78
  }
@@ -1,28 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const errorReporters_1 = require("./errorReporters");
4
- describe("formatTable", () => {
5
- test("single col and row", () => {
6
- expect((0, errorReporters_1.formatTable)([[["lorem ipsum"]]])).toEqual(`
4
+ describe('formatTable', () => {
5
+ test('single col and row', () => {
6
+ expect((0, errorReporters_1.formatTable)([[['lorem ipsum']]])).toEqual(`
7
7
  ┌─────────────┐
8
8
  │ lorem ipsum │
9
9
  └─────────────┘
10
10
  `.trim());
11
11
  });
12
- test("single col and two rows", () => {
13
- expect((0, errorReporters_1.formatTable)([[["lorem ipsum"], ["foo bar"]]])).toEqual(`
12
+ test('single col and two rows', () => {
13
+ expect((0, errorReporters_1.formatTable)([[['lorem ipsum'], ['foo bar']]])).toEqual(`
14
14
  ┌─────────────┐
15
15
  │ lorem ipsum │
16
16
  │ foo bar │
17
17
  └─────────────┘
18
18
  `.trim());
19
19
  });
20
- test("with two columns and two row groups", () => {
20
+ test('with two columns and two row groups', () => {
21
21
  expect((0, errorReporters_1.formatTable)([
22
- [["col1", "col2"]],
22
+ [['col1', 'col2']],
23
23
  [
24
- ["lorem ipsum dolor", "foobar"],
25
- ["baz", "more text"],
24
+ ['lorem ipsum dolor', 'foobar'],
25
+ ['baz', 'more text'],
26
26
  ],
27
27
  ])).toEqual(`
28
28
  ┌───────────────────┬───────────┐
@@ -33,14 +33,14 @@ describe("formatTable", () => {
33
33
  └───────────────────┴───────────┘
34
34
  `.trim());
35
35
  });
36
- test("with two columns and three row groups", () => {
36
+ test('with two columns and three row groups', () => {
37
37
  expect((0, errorReporters_1.formatTable)([
38
- [["one", "two"]],
38
+ [['one', 'two']],
39
39
  [
40
- ["lorem ipsum dolor", "foobar"],
41
- ["baz", "more text"],
40
+ ['lorem ipsum dolor', 'foobar'],
41
+ ['baz', 'more text'],
42
42
  ],
43
- [["hello world", "here is more text for testing"]],
43
+ [['hello world', 'here is more text for testing']],
44
44
  ])).toEqual(`
45
45
  ┌───────────────────┬───────────────────────────────┐
46
46
  │ one │ two │
@@ -53,10 +53,10 @@ describe("formatTable", () => {
53
53
  `.trim());
54
54
  });
55
55
  });
56
- describe("formatCheckResultTable", () => {
57
- test("with one file and two keys", () => {
56
+ describe('formatCheckResultTable', () => {
57
+ test('with one file and two keys', () => {
58
58
  expect((0, errorReporters_1.formatCheckResultTable)({
59
- "some/file.json": ["key.one", "key.two"],
59
+ 'some/file.json': ['key.one', 'key.two'],
60
60
  })).toEqual(`
61
61
  ┌────────────────┬─────────┐
62
62
  │ file │ key │
@@ -66,10 +66,10 @@ describe("formatCheckResultTable", () => {
66
66
  └────────────────┴─────────┘
67
67
  `.trim());
68
68
  });
69
- test("with two files and three keys", () => {
69
+ test('with two files and three keys', () => {
70
70
  expect((0, errorReporters_1.formatCheckResultTable)({
71
- "some/de.json": ["key.one", "key.two"],
72
- "some/en.json": ["key.three"],
71
+ 'some/de.json': ['key.one', 'key.two'],
72
+ 'some/en.json': ['key.three'],
73
73
  })).toEqual(`
74
74
  ┌──────────────┬───────────┐
75
75
  │ file │ key │
@@ -81,10 +81,10 @@ describe("formatCheckResultTable", () => {
81
81
  `.trim());
82
82
  });
83
83
  });
84
- describe("formatInvalidTranslationsResultTable", () => {
85
- test("with one file and one key", () => {
84
+ describe('formatInvalidTranslationsResultTable', () => {
85
+ test('with one file and one key', () => {
86
86
  expect((0, errorReporters_1.formatInvalidTranslationsResultTable)({
87
- "some/en.json": [{ key: "key.one", msg: "key one error msg" }],
87
+ 'some/en.json': [{ key: 'key.one', msg: 'key one error msg' }],
88
88
  })).toEqual(`
89
89
  ┌──────┬───────────────────┐
90
90
  │ info │ result │
@@ -95,13 +95,13 @@ describe("formatInvalidTranslationsResultTable", () => {
95
95
  └──────┴───────────────────┘
96
96
  `.trim());
97
97
  });
98
- test("with two files and three keys", () => {
98
+ test('with two files and three keys', () => {
99
99
  expect((0, errorReporters_1.formatInvalidTranslationsResultTable)({
100
- "some/en-US.json": [
101
- { key: "key.one", msg: "key one error msg" },
102
- { key: "key.two", msg: "another msg" },
100
+ 'some/en-US.json': [
101
+ { key: 'key.one', msg: 'key one error msg' },
102
+ { key: 'key.two', msg: 'another msg' },
103
103
  ],
104
- "some/de.json": [{ key: "key.three", msg: "key three msg" }],
104
+ 'some/de.json': [{ key: 'key.three', msg: 'key three msg' }],
105
105
  })).toEqual(`
106
106
  ┌──────┬───────────────────┐
107
107
  │ info │ result │
@@ -121,10 +121,10 @@ describe("formatInvalidTranslationsResultTable", () => {
121
121
  `.trim());
122
122
  });
123
123
  });
124
- describe("formatSummaryTable", () => {
125
- test("with CheckResult with single file and key", () => {
124
+ describe('formatSummaryTable', () => {
125
+ test('with CheckResult with single file and key', () => {
126
126
  expect((0, errorReporters_1.formatSummaryTable)({
127
- "some/file.json": ["key.one"],
127
+ 'some/file.json': ['key.one'],
128
128
  })).toEqual(`
129
129
  ┌────────────────┬───────┐
130
130
  │ file │ total │
@@ -133,10 +133,10 @@ describe("formatSummaryTable", () => {
133
133
  └────────────────┴───────┘
134
134
  `.trim());
135
135
  });
136
- test("with CheckResult with two files and three keys", () => {
136
+ test('with CheckResult with two files and three keys', () => {
137
137
  expect((0, errorReporters_1.formatSummaryTable)({
138
- "some/de.json": ["key.one", "key.two"],
139
- "some/en.json": ["key.three"],
138
+ 'some/de.json': ['key.one', 'key.two'],
139
+ 'some/en.json': ['key.three'],
140
140
  })).toEqual(`
141
141
  ┌──────────────┬───────┐
142
142
  │ file │ total │
@@ -146,13 +146,13 @@ describe("formatSummaryTable", () => {
146
146
  └──────────────┴───────┘
147
147
  `.trim());
148
148
  });
149
- test("with InvalidTranslationsResult with two files and three keys", () => {
149
+ test('with InvalidTranslationsResult with two files and three keys', () => {
150
150
  expect((0, errorReporters_1.formatSummaryTable)({
151
- "some/en-US.json": [
152
- { key: "key.one", msg: "key one error msg" },
153
- { key: "key.two", msg: "another msg" },
151
+ 'some/en-US.json': [
152
+ { key: 'key.one', msg: 'key one error msg' },
153
+ { key: 'key.two', msg: 'another msg' },
154
154
  ],
155
- "some/de.json": [{ key: "key.three", msg: "key three msg" }],
155
+ 'some/de.json': [{ key: 'key.three', msg: 'key three msg' }],
156
156
  })).toEqual(`
157
157
  ┌─────────────────┬───────┐
158
158
  │ file │ total │
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { CheckResult, InvalidTranslationsResult, Translation, TranslationFile } from "./types";
2
- import { Context } from "./errorReporters";
1
+ import { CheckResult, InvalidTranslationsResult, Translation, TranslationFile } from './types';
2
+ import { Context } from './errorReporters';
3
3
  export type Options = {
4
- format?: "icu" | "i18next" | "react-intl" | "next-intl";
4
+ format?: 'icu' | 'i18next' | 'react-intl' | 'next-intl';
5
5
  checks?: Context[];
6
6
  };
7
7
  export declare const checkInvalidTranslations: (source: Translation, targets: Record<string, Translation>, options?: Options) => InvalidTranslationsResult;
package/dist/index.js CHANGED
@@ -11,9 +11,9 @@ const cli_lib_1 = require("@formatjs/cli-lib");
11
11
  const nextIntlSrcParser_1 = require("./utils/nextIntlSrcParser");
12
12
  const fs_1 = __importDefault(require("fs"));
13
13
  const path_1 = __importDefault(require("path"));
14
- const ParseFormats = ["react-intl", "i18next", "next-intl"];
15
- const checkInvalidTranslations = (source, targets, options = { format: "icu" }) => {
16
- return options.format === "i18next"
14
+ const ParseFormats = ['react-intl', 'i18next', 'next-intl'];
15
+ const checkInvalidTranslations = (source, targets, options = { format: 'icu' }) => {
16
+ return options.format === 'i18next'
17
17
  ? (0, findInvalidi18nTranslations_1.findInvalid18nTranslations)(source, targets)
18
18
  : (0, findInvalidTranslations_1.findInvalidTranslations)(source, targets);
19
19
  };
@@ -22,12 +22,12 @@ const checkMissingTranslations = (source, targets) => {
22
22
  return (0, findMissingKeys_1.findMissingKeys)(source, targets);
23
23
  };
24
24
  exports.checkMissingTranslations = checkMissingTranslations;
25
- const checkTranslations = (source, targets, options = { format: "icu", checks: ["invalidKeys", "missingKeys"] }) => {
26
- const { checks = ["invalidKeys", "missingKeys"] } = options;
27
- let missingKeys = {};
28
- let invalidKeys = {};
29
- const hasMissingKeys = checks.includes("missingKeys");
30
- const hasInvalidKeys = checks.includes("invalidKeys");
25
+ const checkTranslations = (source, targets, options = { format: 'icu', checks: ['invalidKeys', 'missingKeys'] }) => {
26
+ const { checks = ['invalidKeys', 'missingKeys'] } = options;
27
+ const missingKeys = {};
28
+ const invalidKeys = {};
29
+ const hasMissingKeys = checks.includes('missingKeys');
30
+ const hasInvalidKeys = checks.includes('invalidKeys');
31
31
  source.forEach(({ name, content }) => {
32
32
  const files = Object.fromEntries(targets
33
33
  .filter(({ reference }) => reference === name)
@@ -46,27 +46,27 @@ const checkTranslations = (source, targets, options = { format: "icu", checks: [
46
46
  };
47
47
  exports.checkTranslations = checkTranslations;
48
48
  function merge(left, right) {
49
- for (let [k, v] of Object.entries(right)) {
49
+ for (const [k, v] of Object.entries(right)) {
50
50
  left[k] = (left?.[k] ?? []).concat(v);
51
51
  }
52
52
  }
53
53
  const checkUnusedKeys = async (translationFiles, filesToParse, options = {
54
- format: "react-intl",
54
+ format: 'react-intl',
55
55
  checks: [],
56
56
  }, componentFunctions = []) => {
57
57
  if (!options.format || !ParseFormats.includes(options.format)) {
58
58
  return undefined;
59
59
  }
60
- if (!options.checks || !options.checks.includes("unused")) {
60
+ if (!options.checks || !options.checks.includes('unused')) {
61
61
  return undefined;
62
62
  }
63
- if (options.format === "react-intl") {
63
+ if (options.format === 'react-intl') {
64
64
  return findUnusedReactIntlTranslations(translationFiles, filesToParse);
65
65
  }
66
- else if (options.format === "i18next") {
66
+ else if (options.format === 'i18next') {
67
67
  return findUnusedI18NextTranslations(translationFiles, filesToParse, componentFunctions);
68
68
  }
69
- else if (options.format === "next-intl") {
69
+ else if (options.format === 'next-intl') {
70
70
  return findUnusedNextIntlTranslations(translationFiles, filesToParse);
71
71
  }
72
72
  };
@@ -90,7 +90,7 @@ const findUnusedReactIntlTranslations = async (translationFiles, filesToParse) =
90
90
  const findUnusedI18NextTranslations = async (source, filesToParse, componentFunctions = []) => {
91
91
  const unusedKeys = {};
92
92
  const { extractedResult, skippableKeys } = await getI18NextKeysInCode(filesToParse, componentFunctions);
93
- const extractedResultSet = new Set(extractedResult.map(({ key }) => key));
93
+ const extractedResultSet = new Set(extractedResult.map(({ key, namespace }) => namespace ? `${namespace}.${key}` : key));
94
94
  source.forEach(({ name, content }) => {
95
95
  const keysInSource = Object.keys(content);
96
96
  const found = [];
@@ -101,7 +101,10 @@ const findUnusedI18NextTranslations = async (source, filesToParse, componentFunc
101
101
  if (isSkippable !== undefined) {
102
102
  continue;
103
103
  }
104
- if (!extractedResultSet.has(keyInSource)) {
104
+ // find the file name
105
+ const [fileName] = (name.split(path_1.default.sep).pop() ?? "").split(".");
106
+ if (!extractedResultSet.has(`${fileName}.${keyInSource}`) &&
107
+ !extractedResultSet.has(keyInSource)) {
105
108
  found.push(keyInSource);
106
109
  }
107
110
  }
@@ -131,8 +134,8 @@ const findUnusedNextIntlTranslations = async (translationFiles, filesToParse) =>
131
134
  // Check if key is part of a dynamic namespace
132
135
  // Skip the key if it is part of the dynamic namespace
133
136
  const isDynamicNamespace = dynamicNamespaces.find((dynamicNamespace) => {
134
- const keyInSourceNamespaces = keyInSource.split(".");
135
- return dynamicNamespace.split(".").every((namePart, index) => {
137
+ const keyInSourceNamespaces = keyInSource.split('.');
138
+ return dynamicNamespace.split('.').every((namePart, index) => {
136
139
  return namePart === keyInSourceNamespaces[index];
137
140
  });
138
141
  });
@@ -148,22 +151,22 @@ const findUnusedNextIntlTranslations = async (translationFiles, filesToParse) =>
148
151
  return unusedKeys;
149
152
  };
150
153
  const checkUndefinedKeys = async (source, filesToParse, options = {
151
- format: "react-intl",
154
+ format: 'react-intl',
152
155
  checks: [],
153
156
  }, componentFunctions = []) => {
154
157
  if (!options.format || !ParseFormats.includes(options.format)) {
155
158
  return undefined;
156
159
  }
157
- if (!options.checks || !options.checks.includes("undefined")) {
160
+ if (!options.checks || !options.checks.includes('undefined')) {
158
161
  return undefined;
159
162
  }
160
- if (options.format === "react-intl") {
163
+ if (options.format === 'react-intl') {
161
164
  return findUndefinedReactIntlKeys(source, filesToParse);
162
165
  }
163
- else if (options.format === "i18next") {
166
+ else if (options.format === 'i18next') {
164
167
  return findUndefinedI18NextKeys(source, filesToParse, componentFunctions);
165
168
  }
166
- else if (options.format === "next-intl") {
169
+ else if (options.format === 'next-intl') {
167
170
  return findUndefinedNextIntlKeys(source, filesToParse);
168
171
  }
169
172
  };
@@ -175,11 +178,11 @@ const findUndefinedReactIntlKeys = async (translationFiles, filesToParse) => {
175
178
  const extractedResult = await (0, cli_lib_1.extract)(filesToParse, {
176
179
  extractSourceLocation: true,
177
180
  });
178
- let undefinedKeys = {};
181
+ const undefinedKeys = {};
179
182
  Object.entries(JSON.parse(extractedResult)).forEach(([key, meta]) => {
180
183
  if (!sourceKeys.has(key)) {
181
184
  const data = meta;
182
- if (!("file" in data) || typeof data.file !== "string") {
185
+ if (!('file' in data) || typeof data.file !== 'string') {
183
186
  return;
184
187
  }
185
188
  const file = path_1.default.normalize(data.file);
@@ -196,7 +199,7 @@ const findUndefinedI18NextKeys = async (source, filesToParse, componentFunctions
196
199
  const sourceKeys = new Set(source.flatMap(({ content }) => {
197
200
  return Object.keys(content);
198
201
  }));
199
- let undefinedKeys = {};
202
+ const undefinedKeys = {};
200
203
  extractedResult.forEach(({ file, key }) => {
201
204
  const isSkippable = skippableKeys.find((skippableKey) => {
202
205
  return key.includes(skippableKey);
@@ -215,10 +218,9 @@ const findUndefinedNextIntlKeys = async (translationFiles, filesToParse) => {
215
218
  return Object.keys(content);
216
219
  }));
217
220
  const extractedResult = (0, nextIntlSrcParser_1.extract)(filesToParse);
218
- let undefinedKeys = {};
221
+ const undefinedKeys = {};
219
222
  extractedResult.forEach(({ key, meta }) => {
220
223
  if (!meta.dynamic && !sourceKeys.has(key)) {
221
- // @ts-ignore
222
224
  const file = meta.file;
223
225
  if (!undefinedKeys[file]) {
224
226
  undefinedKeys[file] = [];
@@ -229,26 +231,27 @@ const findUndefinedNextIntlKeys = async (translationFiles, filesToParse) => {
229
231
  return undefinedKeys;
230
232
  };
231
233
  const isRecord = (data) => {
232
- return (typeof data === "object" &&
234
+ return (typeof data === 'object' &&
233
235
  !Array.isArray(data) &&
234
236
  data !== null &&
235
237
  data !== undefined);
236
238
  };
237
239
  const getI18NextKeysInCode = async (filesToParse, componentFunctions = []) => {
240
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
238
241
  // @ts-ignore
239
- const { transform } = await import("i18next-parser");
242
+ const { transform } = await import('i18next-parser');
240
243
  const i18nextParser = new transform({
241
244
  lexers: {
242
245
  jsx: [
243
246
  {
244
- lexer: "JsxLexer",
245
- componentFunctions: componentFunctions.concat(["Trans"]),
247
+ lexer: 'JsxLexer',
248
+ componentFunctions: componentFunctions.concat(['Trans']),
246
249
  },
247
250
  ],
248
251
  tsx: [
249
252
  {
250
- lexer: "JsxLexer",
251
- componentFunctions: componentFunctions.concat(["Trans"]),
253
+ lexer: 'JsxLexer',
254
+ componentFunctions: componentFunctions.concat(['Trans']),
252
255
  },
253
256
  ],
254
257
  },
@@ -256,32 +259,46 @@ const getI18NextKeysInCode = async (filesToParse, componentFunctions = []) => {
256
259
  // Skip any parsed keys that have the `returnObjects` property set to true
257
260
  // As these are used dynamically, they will be skipped to prevent
258
261
  // these keys from being marked as unused.
259
- let extractedResult = [];
262
+ const extractedResult = [];
260
263
  const skippableKeys = [];
261
264
  filesToParse.forEach((file) => {
262
- const rawContent = fs_1.default.readFileSync(file, "utf-8");
265
+ const rawContent = fs_1.default.readFileSync(file, 'utf-8');
263
266
  const entries = i18nextParser.parser.parse(rawContent, file);
264
267
  // Intermediate solution to retrieve all keys from the parser.
265
268
  // This will be built out to also include the namespace and check
266
269
  // the key against the namespace corresponding file.
267
270
  // The current implementation considers the key as used no matter the namespace.
268
271
  for (const entry of entries) {
272
+ // check for namespace, i.e. `namespace:some.key`
273
+ const [namespace, ...keyParts] = entry.key.split(":");
274
+ // If there is a namespace make sure to assign the namespace
275
+ // and update the key name
276
+ // Ensure that the assumed key is not the default value
277
+ if (keyParts.length > 0 && entry.key !== entry.defaultValue) {
278
+ entry.namespace = namespace;
279
+ // rebuild the key without the namespace
280
+ entry.key = keyParts.join(":");
281
+ }
269
282
  if (entry.returnObjects) {
270
283
  skippableKeys.push(entry.key);
271
284
  }
272
285
  else {
273
- extractedResult.push({ file, key: entry.key });
286
+ extractedResult.push({
287
+ file,
288
+ key: entry.key,
289
+ namespace: entry.namespace,
290
+ });
274
291
  }
275
292
  }
276
293
  });
277
294
  return { extractedResult, skippableKeys };
278
295
  };
279
- function flatten(object, prefix = null, result = {}) {
280
- for (let key in object) {
281
- let propName = prefix ? `${prefix}.${key}` : key;
296
+ function _flatten(object, prefix = null, result = {}) {
297
+ for (const key in object) {
298
+ const propName = prefix ? `${prefix}.${key}` : key;
282
299
  const data = object[key];
283
300
  if (isRecord(data)) {
284
- flatten(data, propName, result);
301
+ _flatten(data, propName, result);
285
302
  }
286
303
  else {
287
304
  result[propName] = data;
@@ -1,5 +1,5 @@
1
- import { MessageFormatElement } from "@formatjs/icu-messageformat-parser";
2
- import { InvalidTranslationEntry, InvalidTranslationsResult, Translation } from "../types";
1
+ import { MessageFormatElement } from '@formatjs/icu-messageformat-parser';
2
+ import { InvalidTranslationEntry, InvalidTranslationsResult, Translation } from '../types';
3
3
  export declare const findInvalidTranslations: (source: Translation, files: Record<string, Translation>) => InvalidTranslationsResult;
4
4
  export declare const compareTranslationFiles: (a: Translation, b: Translation) => InvalidTranslationEntry[];
5
5
  export declare const hasDiff: (a: MessageFormatElement[], b: MessageFormatElement[]) => boolean;
@@ -62,8 +62,9 @@ const hasDiff = (a, b) => {
62
62
  ((0, icu_messageformat_parser_1.isPoundElement)(formatElementA) && (0, icu_messageformat_parser_1.isPoundElement)(formatElementB))) {
63
63
  return false;
64
64
  }
65
- // @ts-ignore
66
- if (formatElementA.value !== formatElementB.value) {
65
+ if (!(0, icu_messageformat_parser_1.isPoundElement)(formatElementA) &&
66
+ !(0, icu_messageformat_parser_1.isPoundElement)(formatElementB) &&
67
+ formatElementA.value !== formatElementB.value) {
67
68
  return true;
68
69
  }
69
70
  if ((0, icu_messageformat_parser_1.isTagElement)(formatElementA) && (0, icu_messageformat_parser_1.isTagElement)(formatElementB)) {
@@ -72,7 +73,7 @@ const hasDiff = (a, b) => {
72
73
  if ((0, icu_messageformat_parser_1.isSelectElement)(formatElementA) && (0, icu_messageformat_parser_1.isSelectElement)(formatElementB)) {
73
74
  const optionsA = Object.keys(formatElementA.options).sort();
74
75
  const optionsB = Object.keys(formatElementB.options).sort();
75
- if (optionsA.join("-") !== optionsB.join("-")) {
76
+ if (optionsA.join('-') !== optionsB.join('-')) {
76
77
  return true;
77
78
  }
78
79
  return optionsA.some((key) => {
@@ -133,7 +134,7 @@ const getErrorMessage = (a, b) => {
133
134
  }
134
135
  if ((0, icu_messageformat_parser_1.isSelectElement)(formatElementA) && (0, icu_messageformat_parser_1.isSelectElement)(formatElementB)) {
135
136
  const optionsA = Object.keys(formatElementA.options).sort();
136
- let elementErrors = [];
137
+ const elementErrors = [];
137
138
  optionsA.forEach((key) => {
138
139
  if (formatElementB.options[key]) {
139
140
  elementErrors.push(getErrorMessage(formatElementA.options[key].value, formatElementB.options[key].value));
@@ -141,12 +142,12 @@ const getErrorMessage = (a, b) => {
141
142
  });
142
143
  acc.push(`Error in select: ${elementErrors
143
144
  .flatMap((elementError) => elementError)
144
- .join(", ")}`);
145
+ .join(', ')}`);
145
146
  return acc;
146
147
  }
147
148
  if ((0, icu_messageformat_parser_1.isPluralElement)(formatElementA) && (0, icu_messageformat_parser_1.isPluralElement)(formatElementB)) {
148
149
  const optionsA = Object.keys(formatElementA.options).sort();
149
- let elementErrors = [];
150
+ const elementErrors = [];
150
151
  optionsA.forEach((key) => {
151
152
  if (formatElementB.options[key]) {
152
153
  elementErrors.push(getErrorMessage(formatElementA.options[key].value, formatElementB.options[key].value));
@@ -154,7 +155,7 @@ const getErrorMessage = (a, b) => {
154
155
  });
155
156
  acc.push(`Error in plural: ${elementErrors
156
157
  .flatMap((elementError) => elementError)
157
- .join(", ")}`);
158
+ .join(', ')}`);
158
159
  return acc;
159
160
  }
160
161
  return acc;
@@ -166,19 +167,19 @@ const getErrorMessage = (a, b) => {
166
167
  acc.push(`Unexpected ${typeLookup[formatElementB.type]} element`);
167
168
  return acc;
168
169
  }, [])
169
- .join(", ");
170
- return [...errors, unexpectedElements].join(", ");
170
+ .join(', ');
171
+ return [...errors, unexpectedElements].join(', ');
171
172
  }
172
- return errors.join(", ");
173
+ return errors.join(', ');
173
174
  };
174
175
  const typeLookup = {
175
- 0: "literal",
176
- 1: "argument",
177
- 2: "number",
178
- 3: "date",
179
- 4: "time",
180
- 5: "select",
181
- 6: "plural",
182
- 7: "pound",
183
- 8: "tag",
176
+ 0: 'literal',
177
+ 1: 'argument',
178
+ 2: 'number',
179
+ 3: 'date',
180
+ 4: 'time',
181
+ 5: 'select',
182
+ 6: 'plural',
183
+ 7: 'pound',
184
+ 8: 'tag',
184
185
  };