i18n-l10n

star 0

Internationalization and localization for the boilerplate monorepo. Covers backend (i18next), frontend (next-intl), and mobile (react-i18next) with shared types.

atakan-urey By atakan-urey schedule Updated 3/28/2026

name: i18n-l10n description: Internationalization and localization for the boilerplate monorepo. Covers backend (i18next), frontend (next-intl), and mobile (react-i18next) with shared types.

Internationalization & Localization (i18n/L10n)

Complete i18n toolkit for the monorepo boilerplate - backend, frontend, and mobile.

Overview

Default: Türkçe (tr) | Supported: tr, en

Layer Library Locale Detection Storage
Backend i18next + i18next-fs-backend Accept-Language header In-memory (JSON files)
Frontend next-intl URL segment (/tr/..., /en/...) Cookie + URL
Mobile react-i18next Device locale + AsyncStorage AsyncStorage

Shared Types

All i18n types are centralized in @boilerplate/types:

import {
  SupportedLocale, // 'tr' | 'en'
  SUPPORTED_LOCALES, // ['tr', 'en'] as const
  DEFAULT_LOCALE, // 'tr'
  FALLBACK_LOCALE, // 'tr'
  LOCALE_NAMES, // { tr: 'Turkish', en: 'English' }
  LOCALE_NATIVE_NAMES, // { tr: 'Türkçe', en: 'English' }
  ErrorCode, // API error codes
  LocalizedApiError, // Localized API error response type
  isLocaleSupported, // Type guard function
} from '@boilerplate/types';

File Structure

packages/
├── shared/
│   ├── types/src/i18n.ts                    # Shared type definitions
│   ├── backend/src/i18n/
│   │   ├── index.ts                         # Exports
│   │   ├── i18n.service.ts                  # Singleton service
│   │   ├── i18n.middleware.ts               # Express middleware
│   │   └── locales/
│   │       ├── tr/
│   │       │   ├── common.json
│   │       │   ├── errors.json
│   │       │   └── validation.json
│   │       └── en/
│   │           ├── common.json
│   │           ├── errors.json
│   │           └── validation.json
│   └── mobile/src/lib/
│       ├── i18n.ts                          # Mobile i18n config
│       └── locales/
│           ├── tr.json
│           └── en.json
├── frontend/nextjs-template/
│   ├── src/i18n/
│   │   ├── config.ts                        # Locale config
│   │   ├── request.ts                       # Server request config
│   │   └── navigation.ts                    # Locale-aware navigation
│   ├── src/messages/
│   │   ├── tr.json
│   │   └── en.json
│   ├── src/middleware.ts                    # Locale routing
│   └── src/app/[locale]/                    # Locale-aware routes
└── mobile/expo-template/
    └── src/components/LanguageSwitcher.tsx  # Mobile language picker

Backend Usage

Initialize i18n Service

// app.ts
import { i18nService, createI18nMiddleware } from '@boilerplate/shared-backend';

export async function createApp() {
  const app = express();

  // Initialize i18n
  await i18nService.init({
    debug: process.env.NODE_ENV === 'development',
    localesPath: path.join(__dirname, '../src/i18n/locales'),
  });

  // Add middleware
  app.use(cookieParser());
  app.use(createI18nMiddleware());

  return app;
}

Translate in Controllers/Services

// Request object has t() function attached by middleware
app.get('/api/greeting', (req, res) => {
  // req.locale - detected locale ('tr' | 'en')
  // req.localeSource - how locale was detected ('header' | 'query' | 'cookie' | 'default')
  // req.t() - translation function

  res.json({
    message: req.t('common.welcome'),
    greeting: req.t('common.hello', { name: 'Atakan' }),
  });
});

Localized API Errors

import { ApiError } from '@boilerplate/shared-backend';

// Throw error with error code for localized message
throw ApiError.notFoundWithCode('USER_NOT_FOUND', { resource: 'User' });

// Error middleware returns localized response:
// {
//   "success": false,
//   "error": {
//     "message": "Kullanıcı bulunamadı",  // Localized message
//     "messageKey": "USER_NOT_FOUND",      // Key for client override
//     "statusCode": 404,
//     "status": "Not Found"
//   }
// }

Frontend Usage (Next.js)

In Server Components

import { useTranslations } from 'next-intl';

export default function HomePage() {
  const t = useTranslations('common');

  return (
    <div>
      <h1>{t('welcome')}</h1>
      <p>{t('greeting', { name: 'Atakan' })}</p>
    </div>
  );
}

In Client Components

'use client';

import { useTranslations, useLocale } from 'next-intl';

export function ClientComponent() {
  const t = useTranslations('auth');
  const locale = useLocale();

  return (
    <button>{t('login')}</button>
  );
}

Locale-Aware Navigation

// Use locale-aware Link from navigation.ts
import { Link, useRouter, usePathname, redirect } from '@/i18n/navigation';

export function Navigation() {
  const router = useRouter();
  const pathname = usePathname();

  return (
    <nav>
      <Link href="/dashboard">{t('dashboard')}</Link>
      <Link href="/settings">{t('settings')}</Link>
    </nav>
  );
}

Language Switcher

'use client';

import { useLocale } from 'next-intl';
import { usePathname, useRouter } from '@/i18n/navigation';
import { SUPPORTED_LOCALES, LOCALE_NATIVE_NAMES, type SupportedLocale } from '@boilerplate/types';

export function LocaleSwitcher() {
  const locale = useLocale();
  const pathname = usePathname();
  const router = useRouter();

  const switchLocale = (newLocale: SupportedLocale) => {
    router.replace(pathname, { locale: newLocale });
  };

  return (
    <select value={locale} onChange={(e) => switchLocale(e.target.value as SupportedLocale)}>
      {SUPPORTED_LOCALES.map((loc) => (
        <option key={loc} value={loc}>{LOCALE_NATIVE_NAMES[loc]}</option>
      ))}
    </select>
  );
}

Mobile Usage (React Native)

Initialize on App Start

// app/_layout.tsx
import { initI18n } from '@boilerplate/shared-mobile';

export default function RootLayout() {
  const [isI18nReady, setIsI18nReady] = useState(false);

  useEffect(() => {
    initI18n()
      .then(() => setIsI18nReady(true))
      .catch((error) => {
        console.error('i18n init failed:', error);
        setIsI18nReady(true); // Continue with defaults
      });
  }, []);

  if (!isI18nReady) {
    return <LoadingScreen />;
  }

  return <Stack />;
}

Translate in Components

import { useTranslation } from 'react-i18next';

export function MyScreen() {
  const { t } = useTranslation();

  return (
    <View>
      <Text>{t('common.welcome')}</Text>
      <Text>{t('auth.login')}</Text>
    </View>
  );
}

Change Language

import { changeLanguage, getCurrentLanguage } from '@boilerplate/shared-mobile';
import { type SupportedLocale } from '@boilerplate/types';

// Get current language
const current = getCurrentLanguage(); // 'tr' | 'en'

// Change language (persists to AsyncStorage)
await changeLanguage('en');

Adding a New Language

Step 1: Update Types

// packages/shared/types/src/i18n.ts
export type SupportedLocale = 'tr' | 'en' | 'de'; // Add new locale

export const SUPPORTED_LOCALES: readonly SupportedLocale[] = ['tr', 'en', 'de'] as const;

export const LOCALE_NAMES: Record<SupportedLocale, string> = {
  tr: 'Turkish',
  en: 'English',
  de: 'German', // Add name
};

export const LOCALE_NATIVE_NAMES: Record<SupportedLocale, string> = {
  tr: 'Türkçe',
  en: 'English',
  de: 'Deutsch', // Add native name
};

Step 2: Add Backend Translations

Create packages/shared/backend/src/i18n/locales/de/ directory with:

  • common.json
  • errors.json
  • validation.json

Step 3: Add Frontend Translations

Create packages/frontend/nextjs-template/src/messages/de.json

Step 4: Add Mobile Translations

Create packages/shared/mobile/src/lib/locales/de.json

Then update packages/shared/mobile/src/lib/i18n.ts:

import de from './locales/de.json';

const resources = {
  tr: { translation: tr },
  en: { translation: en },
  de: { translation: de }, // Add new locale
};

Step 5: Update Frontend Middleware

// packages/frontend/nextjs-template/src/i18n/config.ts
export const locales = ['tr', 'en', 'de'] as const;

Step 6: Rebuild Types

pnpm --filter @boilerplate/types build

Translation Key Structure

Minimal boilerplate keys - projects add their own:

{
  "common": {
    "loading": "Yükleniyor...",
    "error": "Bir hata oluştu",
    "save": "Kaydet",
    "cancel": "İptal",
    "delete": "Sil",
    "edit": "Düzenle",
    "search": "Ara",
    "noResults": "Sonuç bulunamadı",
    "welcome": "Hoş geldiniz",
    "getStarted": "Başla"
  },
  "auth": {
    "login": "Giriş Yap",
    "logout": "Çıkış Yap",
    "register": "Kayıt Ol",
    "email": "E-posta",
    "password": "Şifre",
    "createAccount": "Hesap Oluştur"
  },
  "errors": {
    "unauthorized": "Yetkilendirme gerekli",
    "forbidden": "Bu işlemi yapmaya yetkiniz yok",
    "notFound": "{{resource}} bulunamadı",
    "serverError": "Sunucu hatası oluştu"
  },
  "validation": {
    "required": "Bu alan zorunludur",
    "invalidEmail": "Geçerli bir e-posta adresi girin",
    "minLength": "En az {{min}} karakter olmalıdır"
  }
}

Best Practices

Key Naming

  • Use namespaced keys: auth.login.button
  • Keep keys semantic, not content-based
  • Use camelCase for consistency
  • Group related translations

Type Safety

  • Always import types from @boilerplate/types
  • Use SupportedLocale type, not string
  • Use isLocaleSupported() for runtime checks

API Responses

  • Include messageKey for client-side override
  • Let backend handle translation based on Accept-Language
  • Support query param ?lang=en for testing

Performance

  • Backend: Translations loaded once at startup
  • Frontend: Per-locale bundle splitting via Next.js
  • Mobile: AsyncStorage caches language preference

Scripts

Check Missing Translations

# Python script to find missing keys
python scripts/check-translations.py packages/frontend/nextjs-template/src/messages

Sync Translation Files

# Sync missing keys from base locale (tr) to others
python scripts/sync-translations.py packages/frontend/nextjs-template/src/messages tr

Checklist

Initial Setup (Done in Boilerplate)

  • Shared types in @boilerplate/types
  • Backend i18n service and middleware
  • Frontend next-intl with locale routing
  • Mobile react-i18next with AsyncStorage

For New Project

  • Review/customize default translations
  • Add project-specific namespaces
  • Add additional languages if needed
  • Configure error codes for domain-specific errors

For New Feature

  • Extract all user-facing strings
  • Add keys to all locale files
  • Test both locales
  • Verify formatting (dates, numbers, pluralization)
Install via CLI
npx skills add https://github.com/atakan-urey/monorepo-full-boilerplate --skill i18n-l10n
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator