Skip to content
better-i18n.com
En esta página

Client-side features allow instant locale switching without a full page reload or server round-trip. When BetterI18nProvider is present in the tree, locale changes trigger a CDN fetch and client re-render — no navigation needed.

BetterI18nProvider #

Wrap your Root Layout with BetterI18nProvider to enable instant client-side locale switching via useSetLocale.

app/[locale]/layout.tsx
import { BetterI18nProvider } from '@better-i18n/next/client';
import { i18n } from '@/i18n.config';

export default async function LocaleLayout({ children, params }) {
  const { locale } = await params;
  const messages = await i18n.getMessages(locale);

  return (
    <BetterI18nProvider config={i18n.config} locale={locale} messages={messages}>
      {children}
    </BetterI18nProvider>
  );
}

Props #

PropTypeDefaultDescription
configI18nConfigRequiredThe i18n config from createI18n()
localestringFrom cookieOverride active locale
messagesMessagesPre-loaded SSR messages
timeZonestringconfig.timeZoneIANA timezone
nowDatePin "now" for SSR hydration consistency

useSetLocale() #

Switches the active locale. Behavior depends on whether BetterI18nProvider is present in the tree.

With BetterI18nProvider (instant) #

When the provider is in the tree, calling setLocale triggers an instant CDN fetch and client re-render. No page navigation or router.refresh() is needed.

TSX
'use client';

import { useSetLocale } from '@better-i18n/next/client';

function LanguageSwitcher() {
  const setLocale = useSetLocale();

  return (
    <button onClick={() => setLocale('tr')}>
      Switch to Turkish
    </button>
  );
}

Without BetterI18nProvider (soft refresh) #

When called outside a provider, useSetLocale updates the locale cookie and calls router.refresh() to trigger a soft server re-render.

TSX
'use client';

import { useSetLocale } from '@better-i18n/next/client';
import { i18n } from '@/i18n.config';

function LanguageSwitcher() {
  // Pass config explicitly when no provider is in the tree
  const setLocale = useSetLocale({ config: i18n.config });

  return (
    <select onChange={(e) => setLocale(e.target.value)}>
      <option value="en">English</option>
      <option value="tr">Türkçe</option>
    </select>
  );
}

Behavior Comparison #

ModeTriggerResult
With BetterI18nProvidersetLocale('tr')Instant CDN fetch + client re-render
Without providersetLocale('tr')Cookie update + router.refresh()
With provider + localePrefix: "never"setLocale('tr')Instant CDN fetch + cookie update (URL unchanged)
Without provider + localePrefix: "never"setLocale('tr')Cookie update + router.refresh() (URL unchanged)

With localePrefix: "never", the URL never changes during locale switching — only the cookie and rendered content update. This makes it ideal for dashboards and SaaS apps where the URL should remain stable across languages.


useManifestLanguages(config) #

Fetches available languages with full metadata on the client. Includes request deduplication.

TSX
'use client';

import { useManifestLanguages } from '@better-i18n/next/client';
import { i18n } from '@/i18n.config';

function LanguageSwitcher() {
  const { languages, isLoading } = useManifestLanguages(i18n.config);
  const setLocale = useSetLocale();

  if (isLoading) return <select disabled><option>Loading...</option></select>;

  return (
    <select onChange={(e) => setLocale(e.target.value)}>
      {languages.map((lang) => (
        <option key={lang.code} value={lang.code}>
          {lang.nativeName || lang.name || lang.code}
        </option>
      ))}
    </select>
  );
}

Return Value #

PropertyTypeDescription
languagesLanguageOption[]Available languages from manifest
isLoadingbooleantrue while fetching
errorError | nullError if fetch failed

LocaleDropdown #

A fully-featured, accessible locale switcher dropdown for Next.js. Import from @better-i18n/next/client — the RSC-safe client entrypoint.

TSX
'use client';

import { LocaleDropdown } from '@better-i18n/next/client';
import { useLocale } from 'next-intl';
import { i18n } from '@/i18n.config';

function Header() {
  const locale = useLocale();

  return (
    <header>
      <LocaleDropdown config={i18n.config} locale={locale} />
    </header>
  );
}

Props #

PropTypeDefaultDescription
configI18nConfigRequiredConfig from createI18n()
localestringRequiredCurrent locale from useLocale()
languagesLanguageOption[]Pre-fetched languages (skips client CDN fetch)
variant"styled" | "unstyled""styled"Styled or unstyled mode
showFlagbooleantrueShow flag emoji/image
showNativeNamebooleantrueShow native language name
showLocaleCodebooleantrueShow locale code
renderTrigger(ctx) => ReactNodeCustom trigger renderer
renderItem(ctx) => ReactNodeCustom item renderer

Locale Switching Behavior #

ContextBehavior
Inside BetterI18nProviderInstant CDN fetch + client re-render — no navigation
Without providerCookie update + router.refresh() — soft server re-render

SSR-Optimized Example #

Pass languages from the server to skip the client-side CDN fetch:

app/[locale]/layout.tsx
import { BetterI18nProvider } from '@better-i18n/next/client';
import { i18n } from '@/i18n.config';

export default async function LocaleLayout({ children, params }) {
  const { locale } = await params;
  const [messages, languages] = await Promise.all([
    i18n.getMessages(locale),
    i18n.getLanguages(),
  ]);

  return (
    <BetterI18nProvider
      config={i18n.config}
      locale={locale}
      messages={messages}
    >
      <Header locale={locale} languages={languages} />
      {children}
    </BetterI18nProvider>
  );
}
components/header.tsx
'use client';

import { LocaleDropdown } from '@better-i18n/next/client';
import { i18n } from '@/i18n.config';
import type { LanguageOption } from '@better-i18n/core';

function Header({ locale, languages }: { locale: string; languages: LanguageOption[] }) {
  return (
    <header>
      <LocaleDropdown config={i18n.config} locale={locale} languages={languages} />
    </header>
  );
}

For full props reference, variants, CSS custom properties, and custom rendering — see LocaleDropdown.