Repository: vendure-ecommerce/ngx-translate-extract (ts)

Issue:
Translations are not extracted from routes declaration
Hello,

When declaring routes with a title resolver ([angular documentation](https://angular.dev/guide/routing/common-router-tasks#setting-the-page-title)), it seems translations are not extracted from the resolver function.
```typescript
export const ROUTES: Route[] = [
    {
        path: '',
        loadComponent: () => import('...').then(m => m.Component),
        title: (): string => {
            const translateService = inject(TranslateService);
            return translateService.instant('page.title');  // <-- not extracted
        }
    }
];
```

It looks like prior works has been done in https://github.com/vendure-ecommerce/ngx-translate-extract/issues/44 but it seems to be limited to direct function declarations, such as
```typescript
export const errorInterceptor: HttpInterceptorFn = (req, next): any => {
    const translateService = inject(TranslateService);

    translateService.instant('translation.key').join(', '); // <-- extracted since 44 has been fixed
};
```

Do you think it is feasible to support the former case?

As a workaround, I'm using the marker `_` newly added in `ngx-translate@v16` (thank you for the recent work on its support)
```typescript
import { _, TranslateService } from '@ngx-translate/core';

export const ROUTES: Route[] = [
    {
        path: '',
        loadComponent: () => import('...').then(m => m.Component),
        title: (): string => {
            const translateService = inject(TranslateService);
            return translateService.instant(_('page.title'));
        }
    }
];
```

Thank you in advance!

Pull request:
fix: Fix extraction of translation keys from nested function expressions
Fixes #59

Patch:
diff --git a/package-lock.json b/package-lock.json
index 06fb569..1637b3b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -36,6 +36,7 @@
         "prettier": "^2.7.1",
         "pretty-quick": "^3.1.3",
         "rimraf": "^5.0.5",
+        "tsx": "^4.19.2",
         "typescript": "~5.2.2",
         "vitest": "^1.3.1"
       },
@@ -560,6 +561,23 @@
         "node": ">=12"
       }
     },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+      "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/@esbuild/openbsd-x64": {
       "version": "0.19.12",
       "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
@@ -2747,6 +2765,19 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/get-tsconfig": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
+      "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "resolve-pkg-maps": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+      }
+    },
     "node_modules/gettext-parser": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-4.2.0.tgz",
@@ -4198,6 +4229,16 @@
         "node": ">=4"
       }
     },
+    "node_modules/resolve-pkg-maps": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+      "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+      }
+    },
     "node_modules/reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -4997,6 +5038,457 @@
       "dev": true,
       "peer": true
     },
+    "node_modules/tsx": {
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
+      "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "~0.23.0",
+        "get-tsconfig": "^4.7.5"
+      },
+      "bin": {
+        "tsx": "dist/cli.mjs"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+      "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/android-arm": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+      "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/android-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+      "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/android-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+      "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+      "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+      "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+      "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+      "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-arm": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+      "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+      "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+      "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+      "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+      "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+      "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+      "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+      "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+      "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+      "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+      "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+      "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+      "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+      "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/win32-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+      "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/esbuild": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
+      "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.23.1",
+        "@esbuild/android-arm": "0.23.1",
+        "@esbuild/android-arm64": "0.23.1",
+        "@esbuild/android-x64": "0.23.1",
+        "@esbuild/darwin-arm64": "0.23.1",
+        "@esbuild/darwin-x64": "0.23.1",
+        "@esbuild/freebsd-arm64": "0.23.1",
+        "@esbuild/freebsd-x64": "0.23.1",
+        "@esbuild/linux-arm": "0.23.1",
+        "@esbuild/linux-arm64": "0.23.1",
+        "@esbuild/linux-ia32": "0.23.1",
+        "@esbuild/linux-loong64": "0.23.1",
+        "@esbuild/linux-mips64el": "0.23.1",
+        "@esbuild/linux-ppc64": "0.23.1",
+        "@esbuild/linux-riscv64": "0.23.1",
+        "@esbuild/linux-s390x": "0.23.1",
+        "@esbuild/linux-x64": "0.23.1",
+        "@esbuild/netbsd-x64": "0.23.1",
+        "@esbuild/openbsd-arm64": "0.23.1",
+        "@esbuild/openbsd-x64": "0.23.1",
+        "@esbuild/sunos-x64": "0.23.1",
+        "@esbuild/win32-arm64": "0.23.1",
+        "@esbuild/win32-ia32": "0.23.1",
+        "@esbuild/win32-x64": "0.23.1"
+      }
+    },
     "node_modules/type-check": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index 115f299..6f2832b 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
     "prettier": "^2.7.1",
     "pretty-quick": "^3.1.3",
     "rimraf": "^5.0.5",
+    "tsx": "^4.19.2",
     "typescript": "~5.2.2",
     "vitest": "^1.3.1"
   },
diff --git a/src/utils/ast-helpers.ts b/src/utils/ast-helpers.ts
index 0b859ec..e2de999 100644
--- a/src/utils/ast-helpers.ts
+++ b/src/utils/ast-helpers.ts
@@ -10,11 +10,21 @@ import pkg, {
 	StringLiteral,
 	SourceFile,
 	PropertyDeclaration,
-	PropertyAccessExpression,
-	isPropertyAccessExpression,
-	isCallExpression
+	PropertyAccessExpression
 } from 'typescript';
-const { SyntaxKind, isStringLiteralLike, isArrayLiteralExpression, isBinaryExpression, isConditionalExpression } = pkg;
+
+// Importing non-type members from 'typescript' this way to prevent runtime errors such as:
+// `SyntaxError: Named export 'isCallExpression' not found. The requested module 'typescript' is a CommonJS module,
+//  which may not support all module.exports as named exports.`
+const {
+	isArrayLiteralExpression,
+	isBinaryExpression,
+	isCallExpression,
+	isConditionalExpression,
+	isPropertyAccessExpression,
+	isStringLiteralLike,
+	SyntaxKind
+} = pkg;
 
 export function getAST(source: string, fileName = ''): SourceFile {
 	const supportedScriptTypes: Record<string, ScriptKind> = {
@@ -81,7 +91,7 @@ export function findClassDeclarations(node: Node, name: string = null): ClassDec
 }
 
 export function findFunctionExpressions(node: Node) {
-	return tsquery(node, 'VariableDeclaration > ArrowFunction, VariableDeclaration > FunctionExpression');
+	return tsquery(node, 'VariableDeclaration ArrowFunction, VariableDeclaration FunctionExpression');
 }
 
 export function getSuperClassName(node: Node): string | null {


Test patch:
diff --git a/tests/cli/cli.spec.ts b/tests/cli/cli.spec.ts
new file mode 100644
index 0000000..284db04
--- /dev/null
+++ b/tests/cli/cli.spec.ts
@@ -0,0 +1,42 @@
+import { exec } from 'node:child_process';
+import { resolve, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { readFileSync, rmSync } from 'node:fs';
+
+import { afterEach, describe, expect, test } from 'vitest';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+
+const REPOSITORY_ROOT = resolve(__dirname, '../..');
+const CLI_PATH = resolve(REPOSITORY_ROOT, 'src/cli/cli.ts');
+const FIXTURES_PATH = resolve(REPOSITORY_ROOT, 'tests/cli/fixtures/');
+const OUTPUT_PATH = resolve(REPOSITORY_ROOT, 'tests/cli/fixtures/tmp');
+const OUTPUT_FILE = resolve(OUTPUT_PATH, 'strings.json');
+
+describe('CLI Integration Tests', () => {
+	afterEach(() => {
+		rmSync(OUTPUT_PATH, { recursive: true, force: true });
+	});
+
+	test('extracts translation keys from a component', () =>
+		new Promise<void>((done) => {
+			exec(`npx tsx ${CLI_PATH} --input ${FIXTURES_PATH} --output ${OUTPUT_FILE}`, (error, stdout) => {
+				expect(error).toBeNull();
+				expect(stdout.trim()).toStrictEqual(expect.stringContaining('Extracting:'));
+				expect(stdout.trim()).toStrictEqual(expect.stringContaining('simple.component.fixture.ts'));
+				expect(stdout.trim()).toStrictEqual(expect.stringContaining('Found 2 strings.'));
+				expect(stdout.trim()).toStrictEqual(expect.stringContaining('Saving:'));
+				expect(stdout.trim()).toStrictEqual(expect.stringContaining('strings.json'));
+				expect(stdout.trim()).toStrictEqual(expect.stringContaining('Done.'));
+
+				const extracted = JSON.parse(readFileSync(OUTPUT_FILE, { encoding: 'utf8' }));
+				const extractedKeys = Object.keys(extracted);
+
+				expect(Object.keys(extracted).length).toBe(2);
+				expect(extractedKeys[0]).toBe('home.welcome');
+				expect(extractedKeys[1]).toBe('home.description');
+
+				done();
+			});
+		}));
+});
diff --git a/tests/cli/fixtures/simple.component.fixture.ts b/tests/cli/fixtures/simple.component.fixture.ts
new file mode 100644
index 0000000..86753b0
--- /dev/null
+++ b/tests/cli/fixtures/simple.component.fixture.ts
@@ -0,0 +1,19 @@
+import { Component } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+
+@Component({
+	selector: 'app-home',
+	standalone: true,
+	template: `
+		<div>
+			<h1>{{ welcomeMessage }}</h1>
+			<p>{{ descriptionMessage }}</p>
+		</div>
+	`
+})
+export class HomeComponent {
+	private readonly translate = inject(TranslateService);
+
+	readonly welcomeMessage = this.translate.instant('home.welcome');
+	readonly descriptionMessage = this.translate.instant('home.description');
+}
diff --git a/tests/parsers/service.parser.spec.ts b/tests/parsers/service.parser.spec.ts
index 5bf5fb1..8937eb4 100644
--- a/tests/parsers/service.parser.spec.ts
+++ b/tests/parsers/service.parser.spec.ts
@@ -349,7 +349,7 @@ describe('ServiceParser', () => {
 			@Component({})
 			export class MyComponent {
 			    readonly #translate = inject(TranslateService);
-				
+
 				public test() {
 					this.#translate.get('get.works');
 					this.#translate.stream('stream.works');
@@ -614,7 +614,7 @@ describe('ServiceParser', () => {
 			    set color(value: unknown) {
 					const newValue = <string>value;
 				    this._color = value;
-				    
+
 					this._translateService.instant('hello.from.input.setter');
 			    }
 				_color: unknown;
@@ -635,9 +635,9 @@ describe('ServiceParser', () => {
 			export const errorInterceptor: HttpInterceptorFn = (req, next) => {
 				const translateService = inject(TranslateService);
 				const router = inject(Router);
-	
+
 				translateService.instant('translation.key');
-	
+
 				const nestedFunction = () => {
 					translateService.instant('translation.key.from.nested.function');
 				}
@@ -652,9 +652,9 @@ describe('ServiceParser', () => {
 			export const errorInterceptor: HttpInterceptorFn = function(req, next) {
 				const translateService = inject(TranslateService);
 				const router = inject(Router);
-	
+
 				translateService.instant('translation.key')
-	
+
 				const nestedFunction = function() {
 					translateService.instant('translation.key.from.nested.function');
 				}
@@ -668,7 +668,7 @@ describe('ServiceParser', () => {
 			const contents = `
 			export const errorInterceptor: HttpInterceptorFn = (req, next) => {
 				const translate = inject(TranslateService);
-	
+
 				translate.get('get.translation.key')
 				translate.instant('instant.translation.key')
 				translate.stream('stream.translation.key')
@@ -682,7 +682,7 @@ describe('ServiceParser', () => {
 			const contents = `
 			export const errorInterceptor: HttpInterceptorFn = (req, next) => {
 				const translate = inject(TranslateService);
-	
+
 				const strings = ['a', 'b', 'c'];
 				strings.map(string => translate.instant(string)).join(', ');
 			};
@@ -690,5 +690,102 @@ describe('ServiceParser', () => {
 			const keys = parser.extract(contents, componentFilename)?.keys();
 			expect(keys).to.deep.equal([]);
 		});
+
+		it('should extract from a route title resolver', () => {
+			const contents = `
+				export const ROUTES: Route[] = [
+					{
+						path: 'component-a',
+						loadComponent: () => import('./path/to/a/component').then(m => m.Component),
+						title: (): string => {
+							const translateService = inject(TranslateService);
+							return translateService.instant('a.component.title');
+						}
+					},
+					{
+						path: 'component-b',
+						component: AComponent,
+						title: (): string => {
+							const translateService = inject(TranslateService);
+							return translateService.instant('b.component.title');
+						}
+					},
+					{
+						path: 'component-c',
+						component: CComponent,
+						title: function(): string {
+							const translateService = inject(TranslateService);
+							return translateService.instant('c.component.title');
+						}
+					}
+				];
+			`;
+			const keys = parser.extract(contents, componentFilename)?.keys();
+			expect(keys).to.deep.equal(['a.component.title', 'b.component.title', 'c.component.title']);
+		})
+
+		it('should extract keys from a `ResolveFn`', () => {
+			const contents = `
+				const routes: Routes = [
+					{
+						path: 'component',
+						title: 'Wrapper component',
+						component: WrapperComponent,
+						children: [
+							{
+								path: 'child-a',
+								title: resolvedChildATitle,
+								component: ChildAComponent,
+							},
+							{
+								path: 'child-b',
+								title: 'child b',
+								component: ChildBComponent,
+							},
+						],
+					},
+				];
+				const resolvedChildATitle: ResolveFn<string> = () => {
+					const translateService = inject(TranslateService);
+					return Promise.resolve(translateService.instant('child.a.title'));
+				}
+			`;
+			const keys = parser.extract(contents, componentFilename)?.keys();
+			expect(keys).to.deep.equal(['child.a.title']);
+		});
+
+		it('should extract keys from deeply nested routes', () => {
+			const contents = `
+				const routes: Routes = [
+					{
+						path: 'component',
+						title: 'Wrapper component',
+						component: WrapperComponent,
+						children: [
+							{
+								path: 'child-a',
+								title: (): string => {
+									const translateService = inject(TranslateService);
+									return translateService.instant('child.a.title');
+								},
+								children: [
+									{
+										path: 'grandchild-a1',
+										title: (): string => {
+											const translateService = inject(TranslateService);
+											return translateService.instant('grandchild.a1.title');
+										},
+										component: GrandchildA1Component
+									},
+								]
+							}
+						]
+					}
+				];
+  			`;
+
+			const keys = parser.extract(contents, componentFilename)?.keys();
+			expect(keys).to.deep.equal(['child.a.title', 'grandchild.a1.title']);
+		});
 	});
 });