@@ -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
164180async 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
182207async 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
235260async 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
308390async function analyzeTranslationFiles ( cwd , files ) {
0 commit comments