Как проверить параметры локали в URL в React Router v7
Элегантная обработка неподдерживаемых кодов локали
Проблема
Когда идентификаторы локалей становятся частью структуры URL, они превращаются в пользовательский ввод, который может содержать любое произвольное значение. Пользователь может вручную ввести /xx/about, /gibberish/contact или любой другой недопустимый код локали в адресную строку. Без проверки приложение должно решить, как обрабатывать такие недопустимые данные. Если разрешить использование недопустимых локалей, это может привести к отсутствию переводов, нарушению форматирования или ошибкам выполнения, когда библиотеки i18n пытаются загрузить данные для несуществующей локали. Тихий переход на локаль по умолчанию без уведомления пользователя создает путаницу относительно того, на каком языке отображается контент. Отображение границ ошибок или пустых страниц оставляет пользователей в тупике без четкого пути для продолжения.
Эта задача усложняется тем, что проверка локали должна происходить на раннем этапе жизненного цикла запроса, до рендеринга компонентов и загрузки данных перевода. Если проверка выполняется слишком поздно, приложение может уже попытаться загрузить ресурсы, специфичные для локали, или инициализировать провайдеры i18n с недопустимой конфигурацией, что приведет к потере ресурсов и, возможно, к каскадным сбоям.
Решение
Проверяйте параметр локали в загрузчике маршрута до рендеринга страницы. Загрузчики в React Router выполняются до монтирования компонентов, что делает их идеальным местом для проверки, существует ли запрашиваемая локаль в списке поддерживаемых языков вашего приложения. Если локаль допустима, разрешите запрос продолжить выполнение в обычном режиме. Если локаль недопустима, немедленно перенаправьте пользователя на безопасный вариант — либо на тот же путь с допустимой локалью по умолчанию, либо на специальную страницу с объяснением проблемы.
Этот подход предотвращает попадание недопустимых локалей в ваши компоненты и провайдеры i18n. Возвращая ответ с перенаправлением из загрузчика, вы используете встроенную систему навигации React Router для аккуратной обработки ошибки. Перенаправление происходит на сервере во время SSR или на клиенте во время навигации, обеспечивая единообразное поведение для всех стратегий рендеринга. Пользователи получают немедленную обратную связь через изменение URL, а ваше приложение избегает попыток загрузки ресурсов для несуществующих локалей.
Шаги
1. Определите поддерживаемые локали
Создайте список допустимых кодов локалей, которые поддерживает ваше приложение. Этот список служит источником истины для проверки.
export const SUPPORTED_LOCALES = ["en", "es", "fr", "de", "ja"] as const;
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
export function isValidLocale(locale: string): locale is SupportedLocale {
return SUPPORTED_LOCALES.includes(locale as SupportedLocale);
}
Эта вспомогательная функция обеспечивает типобезопасную проверку и может быть повторно использована в загрузчиках и других частях вашего приложения.
2. Добавьте проверку в загрузчик маршрута с префиксом локали
В модуле маршрута для страниц с префиксом локали экспортируйте загрузчик, который проверяет параметр локали и перенаправляет, если он недействителен.
import type { Route } from "./+types/page";
import { redirect } from "react-router";
import { isValidLocale } from "~/i18n/locales";
export async function loader({ params }: Route.LoaderArgs) {
const { locale } = params;
if (!locale || !isValidLocale(locale)) {
return redirect("/en/not-found");
}
return { locale };
}
export default function Page({ loaderData }: Route.ComponentProps) {
return (
<div>
<h1>Контент на {loaderData.locale}</h1>
</div>
);
}
Загрузчик извлекает локаль из URL, проверяет её и перенаправляет на безопасный вариант, если проверка не пройдена. Если локаль действительна, он возвращает данные, которые могут использоваться компонентами.
3. Настройте маршруты с параметрами локали
В вашем файле routes.ts определите маршруты, которые включают локаль в качестве динамического сегмента.
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route(":locale/about", "./routes/about.tsx"),
route(":locale/contact", "./routes/contact.tsx"),
route(":locale/not-found", "./routes/not-found.tsx"),
] satisfies RouteConfig;
Каждый маршрут с параметром :locale вызовет свой загрузчик, где проверка выполняется до рендеринга компонента.
4. Создайте страницу "не найдено" для недопустимых локалей
Создайте специальную страницу, которая объясняет, что локаль не найдена, и предлагает варианты навигации.
import { Link } from "react-router";
import { SUPPORTED_LOCALES } from "~/i18n/locales";
export default function NotFound() {
return (
<div>
<h1>Язык не найден</h1>
<p>Запрошенный язык не поддерживается.</p>
<nav>
<p>Выберите язык:</p>
<ul>
{SUPPORTED_LOCALES.map((locale) => (
<li key={locale}>
<Link to={`/${locale}`}>{locale.toUpperCase()}</Link>
</li>
))}
</ul>
</nav>
</div>
);
}
Эта страница предоставляет четкую обратную связь и конкретные шаги, помогая пользователям восстановиться после ошибки, не покидая ваше приложение.
5. Добавьте маршрут для полностью недопустимых путей
Для URL-адресов, которые не соответствуют ни одному из определенных шаблонов маршрутов, добавьте маршрут-звездочку в конце вашей конфигурации маршрутов.
import { type RouteConfig, route } from "@react-router/dev/routes";
export default [
route(":locale/about", "./routes/about.tsx"),
route(":locale/contact", "./routes/contact.tsx"),
route(":locale/not-found", "./routes/not-found.tsx"),
route("*", "./routes/catch-all.tsx"),
] satisfies RouteConfig;
Маршрут-звездочка соответствует любому пути, который не совпадает с предыдущими маршрутами, позволяя вам обрабатывать полностью некорректные URL-адреса отдельно от недопустимых кодов локалей.
6. При необходимости перенаправляйте на локаль по умолчанию вместо страницы "не найдено"
Если вы предпочитаете молча исправлять недопустимые локали, а не показывать ошибку, перенаправляйте на тот же путь с локалью по умолчанию.
import type { Route } from "./+types/page";
import { redirect } from "react-router";
import { isValidLocale } from "~/i18n/locales";
const DEFAULT_LOCALE = "en";
export async function loader({ params, request }: Route.LoaderArgs) {
const { locale } = params;
if (!locale || !isValidLocale(locale)) {
const url = new URL(request.url);
const newPath = url.pathname.replace(/^\/[^/]+/, `/${DEFAULT_LOCALE}`);
return redirect(newPath);
}
return { locale };
}
Этот подход сохраняет остальную часть пути URL, заменяя только недопустимый сегмент локали, обеспечивая более плавный пользовательский опыт, если проблема заключается только в локали.