Menu

Internationalization Usage

Nexty.dev leverages next-intl to provide comprehensive multilingual support, delivering a robust internationalization solution that includes route-level language switching, automatic language detection, and seamless locale management.

This guide covers the fundamentals of next-intl usage, demonstrates how to add new languages and translation content, and explores the advanced internationalization capabilities that Nexty provides.

File Structure

i18n/
├── messages/                # Translation files directory
│   ├── en/                  # English translations
│   │   └── common.json
│   ├── zh/                  # Chinese translations
│   │   └── common.json
│   └── ja/                  # Japanese translations
│       └── common.json
├── request.ts               # next-intl request configuration
└── routing.ts               # Routing and language configuration
 
components/
├── LocaleSwitcher.tsx       # Language switcher component
└── LanguageDetectionAlert.tsx  # Language detection alert component
 
stores/
└── localeStore.ts           # Language state management
 
middleware.ts                # Middleware configuration
next.config.mjs              # Next.js configuration

Basic Usage

Using Translations in Components

In React components, use the useLocale hook to retrieve the current locale and the useTranslations hook to access translation content.

import { useLocale, useTranslations } from 'next-intl';
 
export default function MyComponent() {
  const locale = useLocale();
  const t = useTranslations('Home');
  
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

Good to know

In APP Router mode, useLocale and useTranslations work in both client and server components. For server components, simply avoid using the async keyword in the component definition to ensure these hooks function properly while maintaining server-side rendering.

Using in Server Components

For async server components, use getLocale to retrieve the current locale and getTranslations to access translation content.

import { getLocale, getTranslations } from 'next-intl/server';
 
export default async function ServerComponent() {
  const locale = await getLocale();
  const t = await getTranslations('Home');
  
  return (
    <div>
      <h1>{t('title')}</h1>
    </div>
  );
}

Using Parameterized Translations

In JSON file:

{
  "welcome": "Welcome, {name}!",
  "countdown": "Closing in {countdown} seconds"
}

In component:

const message = t('welcome', { name: 'John' });
const timer = t('countdown', { countdown: countdown.toString() });

Using Raw Data

For complex data structures like arrays or objects, use the t.raw() method to access the raw translation data:

const headerLinks = t.raw('Header.links') as HeaderLink[];
const features = t.raw('Landing.Features.items');

Use the Link component exposed from @/i18n/routing. The href prop doesn't require language prefixes, as next-intl will automatically handle locale routing based on the current language.

import { Link as I18nLink } from '@/i18n/routing';
 
<I18nLink href="/about">
  {t('aboutUs')}
</I18nLink>

Internationalized useRouter

Use the useRouter hook from @/i18n/routing for programmatic navigation. Like the Link component, routes don't need language prefixes as next-intl automatically handles locale routing.

import { useRouter } from '@/i18n/routing';
 
const router = useRouter();
 
const handleClick = () => {
  router.push('/dashboard');
};

Steps to Add New Languages

Step 1. Update Language Configuration

Add the new language to your locale configuration in i18n/routing.ts:

i18n/routing
export const LOCALES = ['en', 'zh', 'ja', 'ko'] // Add Korean
export const DEFAULT_LOCALE = 'en'
export const LOCALE_NAMES: Record<string, string> = {
  'en': "English",
  'zh': "中文", 
  'ja': "日本語",
  'ko': "한국어", // Add Korean display name
};

Step 2. Create Translation Files

Create a new directory for the language and copy existing translation files as a template:

mkdir i18n/messages/ko
cp i18n/messages/en/common.json i18n/messages/ko/common.json

Step 3. Translate Content

Translate all content in the new language files. Edit i18n/messages/ko/common.json and replace English content with Korean translations.

Step 4. Update Middleware Configuration

Update the middleware matcher to include the new locale:

middleware.ts
export const config = {
  matcher: [
    '/',
 
    '/(en|zh|ja|ko)/:path*', // Add Korean
 
    '/((?!api|_next|_vercel|auth|.*\\.|favicon.ico).*)'
  ]
};

Step 5. Update Route Redirects (Optional)

If needed, add corresponding redirect rules in next.config.mjs:

next.config.mjs
{
  source: "/ko/dashboard", 
  destination: "/ko/dashboard/settings",
  permanent: true,
}

Good to know

To remove a language, reverse these steps: remove the locale from configuration, delete translation files, and update the middleware matcher.

Adding New Translation Namespaces

When adding new pages or features, it's recommended to create separate JSON files to organize translations into logical namespaces.

For example, alongside the existing common.json, you can add a new Landing.json for landing page translations:

i18n/messages/en/
├── common.json
├── Landing.json         # New Landing addition

Update the Request Configuration:

i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
 
export default getRequestConfig(async ({ requestLocale }) => {
  let locale = await requestLocale;
 
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale;
  }
 
  const common = (await import(`./messages/${locale}/common.json`)).default;
  const Landing = (await import(`./messages/${locale}/Landing.json`)).default; // Import Landing
 
  return {
    locale,
    messages: {
      ...common,
      Landing, // Import Landing
    }
  };
});

Using the new namespace

const t = useTranslations('Landing');

SSG (Static Site Generation) Best Practices

Good to know

  • Before v2.3.0, dynamic methods in the layout prevented true SSG implementation. Pages were still server-side rendered (SSR), which maintained SEO benefits but didn't provide static generation performance gains.
  • For v2.3.0 and later, please follow the implementation approach below for SSG.

For static content pages, such as blog detail pages, Privacy Policy, and Terms of Service pages, it's recommended to use SSG to reduce server CPU consumption and improve page loading performance.

Scenario 1: Pages with locale as the only dynamic parameter

Applicable pages: URLs like /privacy-policy or /zh/privacy-policy, where the only dynamic parameter is the locale prefix.

Implementation approach:

  1. Extract the locale parameter from params (avoid using getLocale() for SSG compatibility)
  2. Implement generateStaticParams to pre-generate pages for all supported locales
app/[locale]/privacy-policy/page.tsx
export default async function Page({ params }: { params: Params }) {
  // ✅ Extract locale from params for SSG compatibility
  // ❌ Don't use getLocale() as it prevents static generation
  const { locale } = await params;
 
  // ... other code ...
 
  return (
    // ... page content ...
  );
}
 
export async function generateStaticParams() {
  return LOCALES.map((locale) => ({
    locale,
  }));
}

Scenario 2: Pages with multi-level dynamic routes

Applicable pages: URLs like /blogs/nexty-dev-stand-out or /zh/blogs/nexty-dev-stand-out, where dynamic parameters include both language prefix and content identifier (such as blog slugs).

Implementation approach: Follow the same principles, but generateStaticParams must handle more complex parameter combinations.

app/[locale]/blogs/[slug]/page.tsx
export default async function BlogPage({ params }: { params: Params }) {
  const { locale, slug } = await params;
  
  // ... other code ...
 
  return (
    // ... page content ...
  );
}
 
export async function generateStaticParams() {
  // Generate all possible combinations of locale and slug
  const allParams: { locale: string; slug: string }[] = [];
 
  for (const locale of LOCALES) {
    // Fetch all blog slugs for the current locale from your data source
    const blogSlugs = await getBlogSlugs(locale); // Your data fetching logic
    
    for (const slug of blogSlugs) {
      allParams.push({ locale, slug });
    }
  }
 
  return allParams;
}

Verifying SSG Implementation

After running npm run build, verify that static HTML files are generated in the .next/server/app/en directory:

ssg result

The presence of .html files confirms that your pages are being statically generated at build time.

Learn more: Check out the Next.js documentation on dynamic routes for additional details.

Nexty's Advanced Features

Automatic Language Detection and User Prompts

The system automatically detects the user's browser language on the user's first visit. If the detected language differs from the current website language, a language switch prompt is displayed with the following behavior:

  • Auto-dismiss: Automatically closes after 10 seconds
  • Manual control: Users can dismiss the prompt manually
  • Persistent preference: Dismissal state is saved for 30 days using cookies

SEO Optimization

Each language version generates independent URLs and localized metadata to ensure optimal search engine indexing:

import { constructMetadata } from "@/lib/metadata";
 
export async function generateMetadata({ params }: MetadataProps): Promise<Metadata> {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: "Home" });
 
  return constructMetadata({
    page: "Home",
    title: t("title"),
    description: t("description"),
    locale: locale as Locale,
    path: `/`,
  });
}