AN.
  • Home
  • Experience
  • Projects
  • Blog
AN.

Built with Next.js, Tailwind v4, and Framer Motion.

GitHubLinkedInNPM
© 2026 Ahmed Nasser. All rights reserved.
Back to writing
December 27, 2024•
3 min read

Splitting next-intl Locales into Feature-Based JSON Files in Next.js

#Next.js#i18n#next-intl#TypeScript#Internationalization#Web Development
A
Ahmed NasserSenior Software Engineer

As your Next.js application grows, managing internationalization (i18n) becomes increasingly complex. A single en.json or ar.json file with thousands of lines is difficult to maintain, leads to frequent merge conflicts, and makes it hard for different teams to manage their own feature translations.

The solution is to split your translations into feature-based JSON files (namespaces) and compose them dynamically at request time.


Why Split Your Locales?

In a large-scale application, a monolithic translation file causes several pain points:

  • Merge Conflicts: Multiple developers editing the same file for different features.
  • Maintainability: Hard to find specific keys or remove unused ones.
  • Scalability: Teams cannot easily own their specific feature's translations (e.g., auth, dashboard, settings).

The Goal: Modular Structure

Instead of one giant file, we want a structure that reflects our features:

src/i18n/locales/
├── header/
│   ├── en.json
│   └── ar.json
├── signup/
│   ├── en.json
│   └── ar.json
└── project-report/
    ├── en.json
    └── ar.json

Then, at runtime, we "compose" these into a single nested object that next-intl understands:

{
  "header": { ... },
  "signup": { ... },
  "projectReport": { ... }
}

This allows you to use the standard useTranslations hook with namespaces:

const t = useTranslations("signup");
return <h1>{t("title")}</h1>;

1. Setting Up the Directory Structure

Pick a consistent convention. The most scalable approach is: src/i18n/locales/[namespace]/[locale].json

Example:

  • src/i18n/locales/auth/en.json
  • src/i18n/locales/auth/ar.json

2. Implementing the Dynamic Loader

In next-intl, the getRequestConfig function is where we define how messages are loaded. We can use Promise.all to load our namespaces in parallel for maximum performance.

// src/i18n/request.ts
import { getRequestConfig } from "next-intl/server";
import { routing } from "./routing";
 
// Define your namespaces here
const namespaces = ["common", "auth", "dashboard", "settings", "errors"];
 
export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale;
 
  // Ensure the locale is valid
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale;
  }
 
  // Load all namespaces in parallel
  const messages = {};
  await Promise.all(
    namespaces.map(async (namespace) => {
      try {
        const module = await import(`./locales/${namespace}/${locale}.json`);
        messages[namespace] = module.default;
      } catch (e) {
        // Fallback to default locale if a specific namespace translation is missing
        const fallback = await import(`./locales/${namespace}/${routing.defaultLocale}.json`);
        messages[namespace] = fallback.default;
      }
    })
  );
 
  return {
    locale,
    messages
  };
});

3. Benefits of This Pattern

✅ Parallel Loading

By using Promise.all and dynamic imports, Next.js can fetch all required translation fragments simultaneously, minimizing the impact on Server-Side Rendering (SSR) time.

✅ Built-in Fallback

The try/catch block ensures that if a specific translation file is missing for the current locale, the application gracefully falls back to the default locale (e.g., English), preventing broken UI.

✅ Type Safety

If you're using TypeScript, you can still get full autocomplete for your keys by defining a global IntlMessages interface based on your primary locale files.


Best Practices

  1. Keep Namespaces Small: Avoid giant "common" files. If a namespace exceeds 200 lines, consider splitting it further.
  2. Kebab-Case Folders: Use project-report instead of projectReport for folder names to stay consistent with web standards.
  3. Automate Namespace List: If you have many namespaces, you can use a Node.js script to automatically generate the namespaces array in request.ts by reading the directory names.

Conclusion

Splitting your locales into namespaces is a game-changer for large Next.js projects. It keeps your codebase organized, reduces friction in team environments, and ensures your internationalization strategy can scale alongside your product.

Share on TwitterShare on LinkedIn
A

Written by Ahmed Nasser

Senior Frontend Engineer

Passionate about building exceptional digital experiences. Specialized in React, Next.js, and modern web architectures. I love sharing my knowledge through technical writing and open-source contributions.

Social
TwitterLinkedInGitHub

Share

Share on TwitterShare on LinkedIn

Continue Reading

More articles you might find interesting

View all articles
Jan 30, 2026
6 min

Scaling Large Next.js Apps with Multi-Zones: The Complete Guide 🧩

Master Next.js Multi-Zones to split large applications into independent micro-frontends. Learn implementation patterns, routing strategies, and best practices for enterprise-scale architecture.

#Next.js#Architecture
Mar 5, 2024
5 min

Why You Should Not Use Index as Key in React Lists

Discover why using array indices as keys in React can lead to performance issues and bugs, and learn the best practices for proper list rendering.

#React#Next.js
Feb 10, 2024
3 min

How To Make Recursion: Use Case For new Map Syntax

Learn how to implement recursion in React components to manage nested radio button groups using JavaScript's Map object for efficient state management.

#React#JavaScript