Skip to content

Commit a66c5c6

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

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { run } = require('../index');
88

99
let rootDir = pkgDir.sync();
1010

11-
run(rootDir, { fix: process.argv.includes('--fix') })
11+
run(rootDir, { fix: process.argv.includes('--fix'), logDynamic: process.argv.includes('--log-dynamic') })
1212
.then(exitCode => {
1313
process.exitCode = exitCode;
1414
})

index.js

Lines changed: 93 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,22 @@ 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(` ⭐ Found ${chalk.bold.yellow(usedDynamicTranslations.size)} dynamic translations. This might cause this tool to report more unused translations than there actually are!`);
47+
log();
48+
for (let [key, files] of usedDynamicTranslations) {
49+
log(` - ${key} ${chalk.dim(`(used in ${generateFileList(files)})`)}`);
50+
}
51+
}
52+
}
3753

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

@@ -163,9 +179,10 @@ function joinPaths(inputPathOrPaths, outputPaths) {
163179

164180
async function analyzeFiles(cwd, files) {
165181
let allTranslationKeys = new Map();
182+
let allDynamicTranslations = new Map();
166183

167184
for (let file of files) {
168-
let translationKeys = await analyzeFile(cwd, file);
185+
let [translationKeys, dynamicTranslations] = await analyzeFile(cwd, file);
169186

170187
for (let key of translationKeys) {
171188
if (allTranslationKeys.has(key)) {
@@ -174,9 +191,17 @@ async function analyzeFiles(cwd, files) {
174191
allTranslationKeys.set(key, new Set([file]));
175192
}
176193
}
194+
195+
for (let dynamicTranslation of dynamicTranslations) {
196+
if (allDynamicTranslations.has(dynamicTranslation)) {
197+
allDynamicTranslations.get(dynamicTranslation).add(file);
198+
} else {
199+
allDynamicTranslations.set(dynamicTranslation, new Set([file]));
200+
}
201+
}
177202
}
178203

179-
return allTranslationKeys;
204+
return [allTranslationKeys, allDynamicTranslations];
180205
}
181206

182207
async function analyzeFile(cwd, file) {
@@ -229,24 +254,77 @@ async function analyzeJsFile(content) {
229254
},
230255
});
231256

232-
return translationKeys;
257+
return [translationKeys, new Set()];
233258
}
234259

235260
async function analyzeHbsFile(content) {
236261
let translationKeys = new Set();
262+
let dynamicTranslations = new Set();
237263

238264
// parse the HBS file
239265
let ast = Glimmer.preprocess(content);
240266

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

245323
return [...keysInFirstParam, ...keysInSecondParam];
246324
}
247325

248326
function findKeysInConcatExpression(node) {
249-
let potentialKeys = [''];
327+
let potentialKeys = [new StringKey('')];
250328

251329
for (let param of node.params) {
252330
let keysInParam = findKeysInNode(param);
@@ -255,7 +333,7 @@ async function analyzeHbsFile(content) {
255333

256334
potentialKeys = potentialKeys.reduce((newPotentialKeys, potentialKey) => {
257335
for (let key of keysInParam) {
258-
newPotentialKeys.push(potentialKey + key);
336+
newPotentialKeys.push(potentialKey.join(key));
259337
}
260338

261339
return newPotentialKeys;
@@ -269,14 +347,14 @@ async function analyzeHbsFile(content) {
269347
if (!node) return [];
270348

271349
if (node.type === 'StringLiteral') {
272-
return [node.value];
350+
return [new StringKey(node.value)];
273351
} else if (node.type === 'SubExpression' && node.path.original === 'if') {
274352
return findKeysInIfExpression(node);
275353
} else if (node.type === 'SubExpression' && node.path.original === 'concat') {
276354
return findKeysInConcatExpression(node);
277355
}
278356

279-
return [];
357+
return [new DynamicKey(node)];
280358
}
281359

282360
function processNode(node) {
@@ -285,7 +363,11 @@ async function analyzeHbsFile(content) {
285363
if (node.params.length === 0) return;
286364

287365
for (let key of findKeysInNode(node.params[0])) {
288-
translationKeys.add(key);
366+
if (key instanceof StringKey) {
367+
translationKeys.add(key.value);
368+
} else {
369+
dynamicTranslations.add(key.toString());
370+
}
289371
}
290372
}
291373

@@ -302,7 +384,7 @@ async function analyzeHbsFile(content) {
302384
},
303385
});
304386

305-
return translationKeys;
387+
return [translationKeys, dynamicTranslations];
306388
}
307389

308390
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)