@@ -32,6 +32,7 @@ const SRC_DIRS = [
3232 join ( REPO_ROOT , "apps" , "web" , "src" ) ,
3333 join ( REPO_ROOT , "apps" , "mobile" , "app" ) ,
3434 join ( REPO_ROOT , "apps" , "mobile" , "src" ) ,
35+ join ( REPO_ROOT , "apps" , "api" , "src" ) ,
3536] ;
3637const FIX_MISSING = process . argv . includes ( "--fix-missing" ) ;
3738
@@ -172,6 +173,8 @@ const sourceFiles = SRC_DIRS.filter((d) => {
172173
173174const usedKeys = new Set < string > ( ) ;
174175const namespacesWithDynamicCalls = new Set < string > ( ) ;
176+ const sliceNamespaces = new Set < string > ( ) ;
177+ const dollarPrefixedNames = new Set < string > ( ) ;
175178
176179for ( const file of sourceFiles ) {
177180 const content = readFileSync ( file , "utf-8" ) ;
@@ -195,15 +198,49 @@ for (const file of sourceFiles) {
195198 if ( dynamicPattern . test ( content ) ) {
196199 namespacesWithDynamicCalls . add ( ns ) ;
197200 }
201+
202+ // Detect $-prefix convention: varName(expr.slice(1)) or varName(expr.substring(1))
203+ const slicePattern = new RegExp ( `\\b${ varName } \\([^)]*\\.(?:slice|substring)\\(1\\)` , "g" ) ;
204+ if ( slicePattern . test ( content ) ) {
205+ sliceNamespaces . add ( ns ) ;
206+ }
198207 }
199208
200209 // react-i18next: const { t } = useTranslation() — uses flat dotted keys like t("ns.key")
201210 const i18nextPattern = / \{ \s * t \s * \} \s * = \s * u s e T r a n s l a t i o n \( \) / ;
202211 if ( i18nextPattern . test ( content ) ) {
212+ // String literal calls: t("ns.key")
203213 const flatPattern = / \b t \( \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / g;
204214 for ( const match of content . matchAll ( flatPattern ) ) {
205215 usedKeys . add ( match [ 1 ] ) ;
206216 }
217+
218+ // Template literals without expressions: t(`ns.key`)
219+ for ( const match of content . matchAll ( / \b t \( ` ( [ ^ ` $ ] + ) ` \) / g) ) {
220+ usedKeys . add ( match [ 1 ] ) ;
221+ }
222+
223+ // Template literals with expressions: t(`prefix.${expr}`)
224+ for ( const match of content . matchAll ( / \b t \( ` ( \w + ) \. \$ \{ / g) ) {
225+ namespacesWithDynamicCalls . add ( match [ 1 ] ) ;
226+ }
227+
228+ // $-prefix convention in template literals: t(`ns.${expr.slice(1)}`)
229+ for ( const match of content . matchAll ( / \b t \( ` ( \w + ) \. \$ \{ [ ^ } ] * \. (?: s l i c e | s u b s t r i n g ) \( 1 \) / g) ) {
230+ sliceNamespaces . add ( match [ 1 ] ) ;
231+ }
232+ }
233+
234+ // Collect $-prefixed entity names (convention for translatable names)
235+ for ( const match of content . matchAll ( / (?: n a m e | l a b e l ) [ " ' ] ? \s * [: = ] \s * [ " ' ] \$ ( [ a - z A - Z ] \w * ) [ " ' ] / g) ) {
236+ dollarPrefixedNames . add ( match [ 1 ] ) ;
237+ }
238+ }
239+
240+ // Derive dynamic keys: $-prefixed entity names + namespaces using slice(1) convention
241+ for ( const word of dollarPrefixedNames ) {
242+ for ( const ns of sliceNamespaces ) {
243+ usedKeys . add ( `${ ns } .${ word } ` ) ;
207244 }
208245}
209246
0 commit comments