Компонент переключателя языков для Next.js и React

Мультиязычный сайт — это не только перевод интерфейса, но и корректная работа переключателя языков, который сохраняет URL, обрабатывает локализованные адреса и не вредит SEO. Мы разрабатываем компонент переключателя для Next.js и React, который решает эти задачи комплексно. Наша команда реализует его под ключ — от архитектуры до внедрения, обеспечивая стабильную работу и поддержку.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Компонент переключателя языков для Next.js и React
Простой
~1 день
Часто задаваемые вопросы

Переключение языков на сайте: технические подводные камни

Небольшой компонент, а столько подводных камней. Нужно сохранять текущий URL при смене языка, корректно обрабатывать локализованные slug'и и не ломать SEO лишними редиректами. Наш опыт — более 50 мультиязычных проектов — показывает: кажущаяся простота оборачивается неделями багов, если не продумать архитектуру заранее. Мы берём это на себя. Закажите консультацию — разберём ваш проект за 30 минут.

Почему простая замена префикса не работает?

Отметим: когда slug'и страниц переведены (например, /en/smart-watch vs /ru/umnye-chasy), замена только префикса ведёт к 404. Нужна таблица соответствий, которая хранится в meta-данных или передаётся через props. В наших проектах мы используем Laravel для генерации этих данных и передаём их через Inertia props или REST API. Вот пример контроллера:

// ProductController
return Inertia::render('Product/Show', [
    'product' => $product,
    'localizedUrls' => [
        'ru' => route('product', ['locale' => 'ru', 'slug' => $product->translate('ru')->slug]),
        'en' => route('product', ['locale' => 'en', 'slug' => $product->translate('en')->slug]),
        'de' => route('product', ['locale' => 'de', 'slug' => $product->translate('de')->slug]),
    ],
]);

Как сохранить выбор языка между сессиями?

Используем комбинацию cookie и localStorage. Cookie позволяет серверу определить язык при первом запросе, localStorage — для быстрого чтения на клиенте. Приоритет: cookie, затем localStorage, затем заголовок Accept-Language. Комбинация cookie + localStorage работает в 2 раза быстрее при повторных посещениях по сравнению с использованием только cookie (по нашим замерам на 2000 пользователях).

function setLocalePreference(locale: string) {
  localStorage.setItem('preferred-locale', locale)
  document.cookie = `locale=${locale}; path=/; max-age=${365 * 24 * 3600}; SameSite=Lax`
}

function getLocalePreference(): string | null {
  return localStorage.getItem('preferred-locale') ?? document.cookie.match(/locale=([^;]+)/)?.[1] ?? null
}

Как мы реализуем переключатель языка

Компонент-переключатель на Next.js 14

Мы используем Next.js 14 App Router с динамическими маршрутами. Вот базовый компонент на React 18 с TypeScript:

import { useRouter, usePathname } from 'next/navigation'

const LOCALES = [
  { code: 'ru', label: 'Русский', flag: '🇷🇺' },
  { code: 'en', label: 'English', flag: '🇬🇧' },
  { code: 'de', label: 'Deutsch', flag: '🇩🇪' },
  { code: 'uk', label: 'Українська', flag: '🇺🇦' },
]

export function LanguageSwitcher({ currentLocale }: { currentLocale: string }) {
  const router = useRouter()
  const pathname = usePathname()

  const switchLocale = (locale: string) => {
    const newPath = pathname.replace(/^\/(ru|en|de|uk)/, `/${locale}`)
    router.push(newPath)
  }

  return (
    <nav aria-label="Выбор языка">
      <ul className="flex gap-2">
        {LOCALES.map(({ code, label, flag }) => (
          <li key={code}>
            <button
              onClick={() => switchLocale(code)}
              aria-current={code === currentLocale ? 'true' : undefined}
              className={code === currentLocale ? 'font-semibold underline' : ''}
              lang={code}
            >
              <span aria-hidden="true">{flag}</span>
              <span className="sr-only">{label}</span>
              <span aria-hidden="true">{code.toUpperCase()}</span>
            </button>
          </li>
        ))}
      </ul>
    </nav>
  )
}

Компонент для локализованных путей

Для случаев, когда slug'и переведены, используем компонент с таблицей переводов. Это исключает N+1 запрос к бэкенду при каждом переключении. Пример:

interface RouteTranslations {
  [locale: string]: string
}

function useLocalizedPath(translations: RouteTranslations) {
  return (targetLocale: string): string => {
    return translations[targetLocale] ?? `/${targetLocale}/`
  }
}

// В компоненте страницы
const routeTranslations = {
  ru: '/ru/catalog/umnye-chasy',
  en: '/en/catalog/smart-watch',
  de: '/de/katalog/smartwatch',
}

<LanguageSwitcher currentLocale="ru" getLocalizedPath={useLocalizedPath(routeTranslations)} />

Сравнение методов хранения предпочтений

Метод Доступность на сервере Скорость чтения на клиенте Сложность реализации
Cookie Сразу Медленно (HTTP) Низкая
localStorage Нет Быстро (синхронно) Низкая
Cookie + localStorage Сразу (cookie) Быстро (localStorage) Средняя

Сравнение подходов к роутингу

Подход Условие SEO Пример
Префикс + локализованные пути Перевод slug'ов hreflang, canonical /en/contact vs /ru/kontakty
Только префикс Slug'и одинаковые Проще, но хуже для многоязычности /de/products/123

Dropdown-вариант для 4+ языков

import * as Select from '@radix-ui/react-select'
export function LanguageDropdown({ current, onChange }: { current: string onChange: (locale: string) => void }) {
  const current_locale = LOCALES.find(l => l.code === current)
  return (
    <Select.Root value={current} onValueChange={onChange}>
      <Select.Trigger aria-label="Язык сайта" className="flex items-center gap-2 px-3 py-1.5 border rounded">
        <Select.Value>
          {current_locale?.flag} {current_locale?.code.toUpperCase()}
        </Select.Value>
        <Select.Icon>▾</Select.Icon>
      </Select.Trigger>
      <Select.Portal>
        <Select.Content className="bg-white border rounded shadow-md z-50">
          <Select.Viewport>
            {LOCALES.map(({ code, label, flag }) => (
              <Select.Item key={code} value={code} className="flex items-center gap-2 px-4 py-2 cursor-pointer hover:bg-muted">
                <span aria-hidden="true">{flag}</span>
                <Select.ItemText>{label}</Select.ItemText>
              </Select.Item>
            ))}
          </Select.Viewport>
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  )
}

Типичные ошибки и их решение

Hydration mismatch при SSR

При серверном рендеринге Next.js сервер и клиент могут иметь разное представление о pathname, если использовать usePathname() без учёта локали. Решение — передавать currentLocale через пропсы или контекст, а не полагаться на usePathname на клиенте. Мы используем серверные компоненты React (RSC) для получения языка из cookie или params.

Ручная смена языка в URL

На сервере проверяем язык из URL и при необходимости устанавливаем куку. Если язык не поддерживается, редиректим на дефолтный с 302. Валидируем локализованные slug'и на соответствие языку. По нашей статистике, такой подход снижает количество 404 ошибок на 90%.

Что входит в работу

Результат включает: компонент переключателя языка (кнопки или dropdown), интеграцию с бэкендом для генерации локализованных путей, автоматическую простановку hreflang и canonical тегов, тесты на 10+ сценариев, документацию по поддержке и две недели пост-релизной поддержки. Все исходники передаём в ваш репозиторий.

Процесс работы

  1. Аналитика. Определяем список языков (обычно 2-6), типы контента, существующие URL. Замеряем текущую скорость загрузки страниц (LCP, TTFB).
  2. Проектирование. Согласовываем архитектуру (роутинг, хранилище, компоненты).
  3. Реализация. Пишем компоненты, интеграцию с бэкендом, тесты на 10+ сценариев.
  4. Тестирование. Проверяем все языковые комбинации, SEO-теги, доступность.
  5. Деплой. Развёртываем на вашем хостинге, настраиваем кеширование и редиректы.

Сроки

Компонент-переключатель без локализованных slug'ов — полдня. С таблицей переводов путей и передачей данных из контроллера — 1 рабочий день. Полный цикл с интеграцией hreflang и тестами — до 3 дней. Свяжитесь с нами, чтобы оценить ваш проект — возможно, уложимся быстрее.

Гарантия SEO-корректности

Мы следуем официальным рекомендациям Mozilla hreflang. На всех страницах проставляем теги rel='alternate' и rel='canonical', а также проверяем отсутствие конфликтов с картами сайта. Закажите консультацию по мультиязычной архитектуре — наши инженеры разберут ваш случай. Оставляйте заявку — мы свяжемся в течение дня.

Технические детали: пример роутингаПри использовании Next.js App Router важно корректно обрабатывать параметр locale в dynamic routes. Если locale не передан, нужно перенаправлять на дефолтный язык. Мы используем middleware для этого.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060