@@ -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
164184async 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
182211async 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
235264async 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
308395async function analyzeTranslationFiles ( cwd , files ) {
0 commit comments