Skip to content

Commit ac0e717

Browse files
committed
Add option to log dynamic translation lookups
1 parent ec41a75 commit ac0e717

File tree

4 files changed

+109
-12
lines changed

4 files changed

+109
-12
lines changed

__snapshots__/test.js.snap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
exports[`Test Fixtures concat-expression 1`] = `
44
"[1/4] 🔍 Finding JS and HBS files...
55
[2/4] 🔍 Searching for translations keys in JS and HBS files...
6+
7+
⭐ Found 2 dynamic translations. This might cause this tool to report more unused translations than there actually are!
8+
9+
- prefix.{{this.dynamicKey}}.not-missing (used in app/templates/application.hbs)
10+
- prefix.{{this.dynamicKey}}.value (used in app/templates/application.hbs)
611
[3/4] ⚙️ Checking for unused translations...
712
[4/4] ⚙️ Checking for missing translations...
813

bin/cli.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ const { run } = require('../index');
88

99
let rootDir = pkgDir.sync();
1010

11-
run(rootDir, { fix: process.argv.includes('--fix') })
11+
run(rootDir, {
12+
fix: process.argv.includes('--fix'),
13+
logDynamic: process.argv.includes('--log-dynamic'),
14+
})
1215
.then(exitCode => {
1316
process.exitCode = exitCode;
1417
})

index.js

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ async function run(rootDir, options = {}) {
1515
let log = options.log || console.log;
1616
let writeToFile = options.writeToFile || fs.writeFileSync;
1717
let shouldFix = options.fix || false;
18+
let logDynamic = options.logDynamic || false;
1819

1920
let chalkOptions = {};
2021
if (options.color === false) {
@@ -33,7 +34,26 @@ async function run(rootDir, options = {}) {
3334
let files = [...appFiles, ...inRepoFiles];
3435

3536
log(`${step(2)} 🔍 Searching for translations keys in JS and HBS files...`);
36-
let usedTranslationKeys = await analyzeFiles(rootDir, files);
37+
let [usedTranslationKeys, usedDynamicTranslations] = await analyzeFiles(rootDir, files);
38+
39+
if (logDynamic) {
40+
if (usedDynamicTranslations.size === 0) {
41+
log();
42+
log(' ⭐ No dynamic translations were found.');
43+
log();
44+
} else {
45+
log();
46+
log(
47+
` ⭐ Found ${chalk.bold.yellow(
48+
usedDynamicTranslations.size
49+
)} dynamic translations. This might cause this tool to report more unused translations than there actually are!`
50+
);
51+
log();
52+
for (let [key, files] of usedDynamicTranslations) {
53+
log(` - ${key} ${chalk.dim(`(used in ${generateFileList(files)})`)}`);
54+
}
55+
}
56+
}
3757

3858
log(`${step(3)} ⚙️ Checking for unused translations...`);
3959

@@ -163,9 +183,10 @@ function joinPaths(inputPathOrPaths, outputPaths) {
163183

164184
async function analyzeFiles(cwd, files) {
165185
let allTranslationKeys = new Map();
186+
let allDynamicTranslations = new Map();
166187

167188
for (let file of files) {
168-
let translationKeys = await analyzeFile(cwd, file);
189+
let [translationKeys, dynamicTranslations] = await analyzeFile(cwd, file);
169190

170191
for (let key of translationKeys) {
171192
if (allTranslationKeys.has(key)) {
@@ -174,9 +195,17 @@ async function analyzeFiles(cwd, files) {
174195
allTranslationKeys.set(key, new Set([file]));
175196
}
176197
}
198+
199+
for (let dynamicTranslation of dynamicTranslations) {
200+
if (allDynamicTranslations.has(dynamicTranslation)) {
201+
allDynamicTranslations.get(dynamicTranslation).add(file);
202+
} else {
203+
allDynamicTranslations.set(dynamicTranslation, new Set([file]));
204+
}
205+
}
177206
}
178207

179-
return allTranslationKeys;
208+
return [allTranslationKeys, allDynamicTranslations];
180209
}
181210

182211
async function analyzeFile(cwd, file) {
@@ -229,24 +258,78 @@ async function analyzeJsFile(content) {
229258
},
230259
});
231260

232-
return translationKeys;
261+
return [translationKeys, new Set()];
233262
}
234263

235264
async function analyzeHbsFile(content) {
236265
let translationKeys = new Set();
266+
let dynamicTranslations = new Set();
237267

238268
// parse the HBS file
239269
let ast = Glimmer.preprocess(content);
240270

271+
class StringKey {
272+
constructor(value) {
273+
this.value = value;
274+
}
275+
276+
join(otherKey) {
277+
if (otherKey instanceof StringKey) {
278+
return new StringKey(this.value + otherKey.value);
279+
} else {
280+
return new CompositeKey(this, otherKey);
281+
}
282+
}
283+
284+
toString() {
285+
return this.value;
286+
}
287+
}
288+
289+
class DynamicKey {
290+
constructor(node) {
291+
this.node = node;
292+
}
293+
294+
join(otherKey) {
295+
return new CompositeKey(this, otherKey);
296+
}
297+
298+
toString() {
299+
if (this.node.type === 'PathExpression') {
300+
return `{{${this.node.original}}}`;
301+
} else if (this.node.type === 'SubExpression') {
302+
return `{{${this.node.path.original} helper}}`;
303+
}
304+
305+
return '{{dynamic key}}';
306+
}
307+
}
308+
309+
class CompositeKey {
310+
constructor(...values) {
311+
this.values = values;
312+
}
313+
314+
join(otherKey) {
315+
return new CompositeKey(...this.values, otherKey);
316+
}
317+
318+
toString() {
319+
return this.values.reduce((string, value) => string + value.toString(), '');
320+
}
321+
}
322+
241323
function findKeysInIfExpression(node) {
242324
let keysInFirstParam = findKeysInNode(node.params[1]);
243-
let keysInSecondParam = node.params.length > 2 ? findKeysInNode(node.params[2]) : [''];
325+
let keysInSecondParam =
326+
node.params.length > 2 ? findKeysInNode(node.params[2]) : [new StringKey('')];
244327

245328
return [...keysInFirstParam, ...keysInSecondParam];
246329
}
247330

248331
function findKeysInConcatExpression(node) {
249-
let potentialKeys = [''];
332+
let potentialKeys = [new StringKey('')];
250333

251334
for (let param of node.params) {
252335
let keysInParam = findKeysInNode(param);
@@ -255,7 +338,7 @@ async function analyzeHbsFile(content) {
255338

256339
potentialKeys = potentialKeys.reduce((newPotentialKeys, potentialKey) => {
257340
for (let key of keysInParam) {
258-
newPotentialKeys.push(potentialKey + key);
341+
newPotentialKeys.push(potentialKey.join(key));
259342
}
260343

261344
return newPotentialKeys;
@@ -269,14 +352,14 @@ async function analyzeHbsFile(content) {
269352
if (!node) return [];
270353

271354
if (node.type === 'StringLiteral') {
272-
return [node.value];
355+
return [new StringKey(node.value)];
273356
} else if (node.type === 'SubExpression' && node.path.original === 'if') {
274357
return findKeysInIfExpression(node);
275358
} else if (node.type === 'SubExpression' && node.path.original === 'concat') {
276359
return findKeysInConcatExpression(node);
277360
}
278361

279-
return [];
362+
return [new DynamicKey(node)];
280363
}
281364

282365
function processNode(node) {
@@ -285,7 +368,11 @@ async function analyzeHbsFile(content) {
285368
if (node.params.length === 0) return;
286369

287370
for (let key of findKeysInNode(node.params[0])) {
288-
translationKeys.add(key);
371+
if (key instanceof StringKey) {
372+
translationKeys.add(key.value);
373+
} else {
374+
dynamicTranslations.add(key.toString());
375+
}
289376
}
290377
}
291378

@@ -302,7 +389,7 @@ async function analyzeHbsFile(content) {
302389
},
303390
});
304391

305-
return translationKeys;
392+
return [translationKeys, dynamicTranslations];
306393
}
307394

308395
async function analyzeTranslationFiles(cwd, files) {

test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('Test Fixtures', () => {
1414
'external-addon-translations',
1515
];
1616
let fixturesWithFix = ['remove-unused-translations', 'remove-unused-translations-nested'];
17+
let fixturesWithDynamicKeys = ['concat-expression'];
1718
let fixturesWithConfig = {
1819
'external-addon-translations': {
1920
externalPaths: ['@*/*', 'external-addon'],
@@ -41,6 +42,7 @@ describe('Test Fixtures', () => {
4142
color: false,
4243
writeToFile,
4344
config: fixturesWithConfig[fixture],
45+
logDynamic: fixturesWithDynamicKeys.includes(fixture),
4446
});
4547

4648
let expectedReturnValue = fixturesWithErrors.includes(fixture) ? 1 : 0;

0 commit comments

Comments
 (0)