Skip to content

Latest commit

 

History

History
239 lines (182 loc) · 6.72 KB

File metadata and controls

239 lines (182 loc) · 6.72 KB

Internationalization (i18n)

Overview

Nightwatch supports 14 languages using next-intl with cookie-based locale detection, 8 translation namespaces, RTL support for Arabic, and a server-side provider that pre-loads all messages before rendering.

Supported Languages

Code Language Direction
en English LTR
hi Hindi LTR
es Spanish LTR
fr French LTR
ja Japanese LTR
ko Korean LTR
de German LTR
pt Portuguese LTR
ar Arabic RTL
ru Russian LTR
zh Chinese LTR
it Italian LTR
tr Turkish LTR
th Thai LTR

Default locale: en

Translation Namespaces (8)

Each locale has 8 JSON files, one per namespace:

Namespace File Domain
common common.json Shared UI strings (navigation, buttons, errors, friends, settings)
auth auth.json Login, signup, password reset, verification
profile profile.json User profile, preferences, security settings
search search.json Search page, filters, content cards
watch watch.json VOD player, watchlist, content details
live live.json Livestream player, clips, chat
party party.json Watch party, room creation, invitations
music music.json Music player, playlists, lyrics

File Structure

src/i18n/
├── config.ts                    # Locale list, types, cookie name
├── request.ts                   # Server-side message loader (getRequestConfig)
└── messages/
    ├── en/                      # English (source language)
    │   ├── common.json
    │   ├── auth.json
    │   ├── profile.json
    │   ├── search.json
    │   ├── watch.json
    │   ├── live.json
    │   ├── party.json
    │   └── music.json
    ├── hi/                      # Hindi
    │   └── ... (same 8 files)
    ├── ar/                      # Arabic (RTL)
    │   └── ...
    └── ... (11 more locales)

Configuration

src/i18n/config.ts

export const locales = [
  'en', 'hi', 'es', 'fr', 'ja', 'ko', 'de', 'pt', 'ar', 'ru', 'zh', 'it', 'tr', 'th',
] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = 'en';
export const COOKIE_NAME = 'NEXT_LOCALE';

Cookie-Based Locale Detection

The active locale is stored in a cookie named NEXT_LOCALE. The server reads this cookie on every request:

// src/i18n/request.ts
const raw = cookieStore.get(COOKIE_NAME)?.value;
const locale = locales.includes(raw as Locale) ? raw : defaultLocale;

If the cookie is missing or contains an invalid value, the app falls back to en.

Server-Side Message Loading

src/i18n/request.ts uses getRequestConfig from next-intl/server with a static import map. Dynamic import() with template literals fails on Vercel Edge/serverless because the bundler can't resolve paths at build time.

const messageImports: Record<string, () => Promise[]> = {
  en: () => [
    import('./messages/en/common.json'),
    import('./messages/en/auth.json'),
    // ... all 8 namespaces
  ],
  // ... all 14 locales
};

All 8 namespace files are loaded in parallel via Promise.all() and merged into a single messages object:

const [common, auth, profile, search, watch, live, party, music] =
  await Promise.all(loader());

return {
  locale,
  messages: {
    common: common.default,
    auth: auth.default,
    // ...
  },
};

Provider Architecture

IntlProvider (Server Component)

src/providers/intl-provider.tsx — the top-level server-side provider:

  1. Calls getLocale() and getMessages() from next-intl/server
  2. Falls back to English if loading fails
  3. Wraps children with IntlClientWrapper and HtmlLangSetter
export async function IntlProvider({ children }) {
  const locale = await getLocale();
  const messages = await getMessages();
  return (
    <IntlClientWrapper locale={locale} messages={messages}>
      <HtmlLangSetter locale={locale} />
      {children}
    </IntlClientWrapper>
  );
}

IntlClientWrapper (Client Component)

src/providers/intl-client-wrapper.tsx — wraps NextIntlClientProvider with:

  • Error handler: dev-only console.warn for missing translations
  • Fallback strategy: renders the leaf key name when a message is missing (e.g., common.titletitle)

HtmlLangSetter (Client Component)

src/providers/html-lang-setter.tsx — headless component that synchronizes <html> attributes:

document.documentElement.lang = locale;       // e.g. "ar"
document.documentElement.dir = RTL_LOCALES.includes(locale) ? 'rtl' : 'ltr';

RTL locales: ['ar']

RTL Support

Arabic (ar) is the only RTL locale. When active:

  1. HtmlLangSetter sets dir="rtl" on <html>
  2. Tailwind's rtl: variant classes activate automatically
  3. Layout mirrors (sidebars, text alignment, icons) via CSS logical properties

Usage in Components

Client Components

'use client';
import { useTranslations } from 'next-intl';

function MyComponent() {
  const t = useTranslations('common');
  return <h1>{t('title')}</h1>;
}

Namespaced Access

const t = useTranslations('common');
t('friends.online');  // common.json → { "friends": { "online": "Online" } }

How to Add a New Language

  1. Add the locale code to src/i18n/config.ts:
export const locales = [
  'en', 'hi', 'es', 'fr', 'ja', 'ko', 'de', 'pt', 'ar', 'ru', 'zh', 'it', 'tr', 'th',
  'vi', // ← new locale
] as const;
  1. Create translation files — copy the en/ directory:
cp -r src/i18n/messages/en src/i18n/messages/vi
  1. Translate all 8 JSON files in the new directory.

  2. Add the static import entry in src/i18n/request.ts:

vi: () => [
  import('./messages/vi/common.json'),
  import('./messages/vi/auth.json'),
  import('./messages/vi/profile.json'),
  import('./messages/vi/search.json'),
  import('./messages/vi/watch.json'),
  import('./messages/vi/live.json'),
  import('./messages/vi/party.json'),
  import('./messages/vi/music.json'),
],
  1. If RTL, add the locale code to RTL_LOCALES in src/providers/html-lang-setter.tsx:
const RTL_LOCALES = ['ar', 'he']; // example
  1. Add a language option in the language selector UI (typically in profile/settings).

Translation Key Conventions

  • Keys use camelCase: pageTitle, noResults, searchPlaceholder
  • Nested objects group related strings: friends.online, errors.sessionExpired
  • Pluralization uses ICU message format where needed
  • Keep keys descriptive but concise