You’re building a NestJS application, perhaps deploying it or just running a production build locally, and you hit a roadblock during startup. Your console logs an error message like this:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
Error: ENOENT: no such file or directory, stat '/Users/jay/Projects/test/server/dist/i18n/'
at async stat (node:internal/fs/promises:1032:18)
at async exists (/Users/jay/Projects/test/server/node_modules/nestjs-i18n/src/utils/file.ts:6:13)
... (Rest of the stack trace)
at async I18nModule.onModuleInit (...)
at async NestApplication.init (...)
at async NestApplication.listen (...)
at async bootstrap (/Users/jay/Projects/test/server/src/main.ts:48:3) // <- Your application entry point
The core of the error is clear: ENOENT: no such file or directory, stat ‘…/dist/i18n/’. The stat system call failed because the operating system couldn’t find the specified path. The stack trace points deep into the nestjs-i18n module during its initialization phase (onModuleInit, load, parseTranslations), specifically when it’s trying to check for the existence of (exists, stat) files within what it believes should be the directory /dist/i18n/.
You look at your project structure, and yes, you do have an i18n folder, but it’s probably located in your src directory:
your-project/
├── src/
│ ├── i18n/ <-- Your translation files live here
│ │ ├── en/
│ │ │ └── common.json
│ │ └── ko/
│ │ └── common.json
│ └── main.ts
│ └── app.module.ts
└── dist/ <-- Your compiled code goes here
So, why is nestjs-i18n looking for /dist/i18n/ and not finding it?
🔍 The Root Cause: Paths and the Build Process
The error stems from a common challenge when building Node.js/TypeScript applications: how file paths are handled after compilation.
- __dirname Behavior: The __dirname variable in Node.js always points to the directory containing the currently executing file.
- NestJS Compilation: When you build your NestJS application (e.g., using nest build), the TypeScript code from your src directory is compiled into JavaScript and placed in the dist directory. The original directory structure within src might be partially replicated in dist.
- Your I18nModule Configuration: You configure nestjs-i18n with path: join(__dirname, ‘../i18n’).
- In your source code (e.g., src/app.module.ts or wherever I18nModule is configured), __dirname is typically /Users/jay/Projects/test/server/src. The relative path ../i18n correctly resolves to /Users/jay/Projects/test/server/i18n (or potentially /Users/jay/Projects/test/server/src/i18n depending on where the config file is relative to src). If you were running your code directly with ts-node or similar in development, this might work.
- However, when the compiled code runs from the dist folder, __dirname points to the directory of the compiled .js file (e.g., /Users/jay/Projects/test/server/dist/src/ or just /Users/jay/Projects/test/server/dist/ depending on build output structure). The relative path ../i18n relative to this new __dirname now points to a directory that doesn’t exist or doesn’t contain your translation files because your build process hasn’t copied them there.
Specifically, the traceback shows the module is looking in /Users/jay/Projects/test/server/dist/i18n/. This indicates the compiled file where the I18nModule path is configured is likely in a subdirectory like dist/src/, and ../i18n relative to that reaches dist/i18n. But your source files were in src/i18n, and they weren’t automatically moved or copied to dist/i18n.
The core problem is: Your build output (dist folder) does not contain the i18n directory with your translation files in the location where nestjs-i18n expects to find them based on the compiled __dirname and your configured relative path.
✅ How to Fix It: Ensuring Translation Files are Built
The solution is to ensure your translation files (src/i18n/**/*) are included in the final build output (dist) and placed in the correct location relative to your compiled JavaScript files.
Here are a few ways to achieve this:
Option 1: Configure NestJS Assets (Recommended)
The standard NestJS way to include static assets like i18n files, templates, etc., in your build is by configuring the assets array in your nest-cli.json. This tells the build process to copy these files from your source directory (src) to your output directory (dist).
- Ensure your i18n folder is inside src (as is standard with NestJS source structure). For example, src/i18n/en/common.json.
- Update nest-cli.json: Add i18n/**/* to the assets array within the compilerOptions. The **/* glob pattern means “include all files and directories recursively within the i18n folder”.
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true, // Optional: Clean dist before build
"assets": [
"i18n/**/*", // <--- ADD THIS LINE
{ "include": "../**/*.graphql", "watchAssets": true, "outDir": "./dist/graphql" } // Example for graphql, adjust as needed
// ... other assets you might have
],
"watchAssets": true // Optional: Helps in development builds
}
}
3. Your I18nModule path configuration: With this setup, the i18n folder from src will be copied to the root of your dist folder (e.g., dist/i18n/). Your original path: join(__dirname, ‘../i18n’) might now correctly resolve to dist/i18n/ when run from dist/src/ (as the compiled file would be in dist/src, and ../i18n would go up one level to dist/i18n). However, to be safer and less dependent on the exact compiled output structure, you can adjust the path in I18nModule to point directly to the dist/i18n folder relative to where the app is executed from. The process.cwd() method is useful here.
Option 2: Use an Absolute Path Based on Project Root (Alternative)
Instead of relying on the potentially tricky relative path based on __dirname in the compiled output, you can use process.cwd() which returns the current working directory where the Node.js process was launched (usually the project root).
Modify your I18nModule configuration:
import { join } from 'path';
import { Module } from '@nestjs/common';
// ... other imports
import { I18nModule, AcceptLanguageResolver } from 'nestjs-i18n';
@Module({
imports: [
I18nModule.forRoot({
fallbackLanguage: 'en',
loaderOptions: {
// Use process.cwd() to get the project root
// Then join it with the source i18n path
path: join(process.cwd(), 'src/i18n'), // <-- Use this path
watch: true, // Keep watch for development, remove in production if needed
},
resolvers: [AcceptLanguageResolver],
// ... other I18nModule options
}),
// ... other modules
],
// ... controllers, providers
})
export class AppModule {}
This approach works regardless of whether you are running the code from src (e.g., using ts-node for dev) or dist (for production). The path always points to your source i18n directory relative to the project root. However, you still need to ensure these files are copied to the dist folder using the nest-cli.json assets configuration (Option 1) so they exist when the compiled code runs! Using process.cwd() changes where the loader looks, but the files still need to be present in the build output.
Therefore, combining Option 1 (copying assets) and Option 2 (using process.cwd()) is the most robust method.
// In app.module.ts or your I18n config file
import { join } from 'path';
// ...
I18nModule.forRoot({
fallbackLanguage: 'en',
loaderOptions: {
// This path should now correctly point to where the assets are copied
path: join(process.cwd(), 'dist/i18n'), // <-- Adjust path to point inside dist
watch: true, // Use `false` in production builds
},
resolvers: [AcceptLanguageResolver],
// ...
}),
Correction: Upon re-reading the nestjs-i18n docs, the path for the loader usually refers to the path where the loader should look, which is typically inside the dist folder after compilation if assets are copied there. So, if nest-cli.json copies src/i18n to dist/i18n, your I18nModule configuration should look inside dist/i18n.
Let’s refine the options based on common NestJS setups:
Revised Recommended Solution: Configure Assets and Point Loader to Build Output
- Keep your source files in src/i18n/.
- Configure nest-cli.json to copy src/i18n/**/* to dist/i18n/ (or just dist/). The standard NestJS asset copying will put them at the root of dist.
"assets": [
"i18n/**/*"
// ... other assets
],
3. Configure I18nModule to look for translation files in the dist/i18n directory relative to the project root using process.cwd().
import { join } from 'path';
// ...
I18nModule.forRoot({
fallbackLanguage: 'en',
loaderOptions: {
// Path to the i18n folder *in the build output*
path: join(process.cwd(), 'dist/i18n'), // <-- Assuming nest-cli.json copies to dist/i18n
watch: true,
},
resolvers: [AcceptLanguageResolver],
// ...
}),
(Note: Some NestJS asset configurations copy assets directly to dist root, others might respect source subdirectories. If nest build copies src/i18n contents directly into dist, you might just need path: join(process.cwd(), ‘dist’) and your filenames would be en/common.json etc. Check your dist folder after a build to confirm the asset destination path).
This approach is reliable because process.cwd() consistently finds your project root, and you’re explicitly telling the loader to look in the directory within your build output where you’ve configured assets to be copied.
Option 3: Conditional Path (Less Ideal)
As originally suggested, you could use a conditional path, but this requires careful management of environment variables and build processes to ensure the condition is correctly evaluated in production vs. development.
I18nModule.forRoot({
fallbackLanguage: 'en',
loaderOptions: {
// This is trickier due to where __dirname resolves in dist vs src
// Needs careful testing in both environments
path: join(__dirname, process.env.NODE_ENV === 'production' ? '../i18n' : '../../src/i18n'), // Adjust relative paths based on actual compiled output structure
watch: true,
},
resolvers: [AcceptLanguageResolver],
// ...
}),
This requires inspecting where the compiled app.module.js actually ends up in dist relative to dist/i18n. The process.cwd() approach combined with asset copying is generally simpler to reason about.
Option 4: Disable I18n Module Temporarily
If you’re just trying to get the application to launch and don’t need internationalization immediately, comment out the I18nModule import and configuration in your AppModule. This will remove the source of the error and allow you to proceed with other development tasks, although it’s not a long-term solution if you intend to use i18n.
@Module({
imports: [
// Comment this out to disable I18nModule startup logic
// I18nModule.forRoot({
// fallbackLanguage: 'en',
// loaderOptions: {
// path: join(process.cwd(), 'dist/i18n'),
// watch: true,
// },
// resolvers: [AcceptLanguageResolver],
// }),
// ... rest of your imports
],
// ... controllers, providers
})
export class AppModule {}
Bringing It Together: The Solution in Action
If you choose the recommended approach (Option 1 assets + Option 2 process.cwd() in config):
- Create your translation file structure: src/i18n/en/common.json, src/i18n/ko/common.json, etc.
- Example src/i18n/en/common.json:
{
"greeting": "Hello, {{ name }}!"
}
2. Update nest-cli.json to copy src/i18n as assets:
{
// ...
"compilerOptions": {
"assets": [
"i18n/**/*" // <-- Copies src/i18n to dist/i18n
],
"watchAssets": true
// ...
}
// ...
}
3. Update your I18nModule configuration:
import { join } from 'path';
import { Module } from '@nestjs/common';
import { I18nModule, AcceptLanguageResolver, join } from 'nestjs-i18n'; // join from nestjs-i18n is useful here
@Module({
imports: [
I18nModule.forRoot({
fallbackLanguage: 'en',
loaderOptions: {
// Using nestjs-i18n's join and process.cwd()
path: join(process.cwd(), 'dist/i18n'),
watch: process.env.NODE_ENV !== 'production', // Watch only in dev
},
// Use AcceptLanguageResolver for browser 'Accept-Language' header
resolvers: [AcceptLanguageResolver],
}),
// ... other modules
],
controllers: [AppController], // Assuming you still have AppController
providers: [AppService], // Assuming you still have AppService
})
export class AppModule {}
4. Run nest build and then run your application from the dist folder (e.g., node dist/main).
Now, the build process will copy your i18n folder into dist, and when the application starts, nestjs-i18n will correctly find your translation files by looking in dist/i18n via the process.cwd() based path.
This error, while frustrating, highlights the importance of understanding file paths and asset management within a build process. By configuring NestJS to handle your translation files as assets, you ensure they are right where the i18n module expects them to be when your compiled code runs.