К содержимому

Выбор локали

Локаль определяется перебором списка источников — побеждает первое совпадение. Источники, неприменимые к текущей стороне, пропускаются, поэтому один и тот же список работает и на сервере, и в браузере.

Порядок по умолчанию #

strategy: ['url', 'cookie', 'lang', 'header', 'navigator', 'default'];

url что-то даёт только при включённом routing. header (Accept-Language) существует только на сервере. lang (<html lang>) и navigator — только в браузере, причём lang игнорируется, если совпадает с defaultLocale: на пререндеренной странице там всегда значение по умолчанию, а значит оно ничего не решает.

Изменение порядка #

Список можно сократить или переупорядочить — например, никогда не угадывать по браузеру, а брать только куки или значение по умолчанию:

createDialect({
	strategy: ['cookie', 'default'],
	// …
});

Кеширование #

Когда в списке участвует header, ответы получают Vary: Accept-Language, а когда cookie — Vary: Cookie, поэтому общий кеш не отдаёт язык одного посетителя другому.

Изоляция запросов #

На сервере каждый запрос обрабатывается в собственном контексте AsyncLocalStorage, поэтому параллельные запросы с разными языками не смешиваются: t() и getLocale() внутри рендера всегда видят локаль именно этого запроса. На рантаймах без async_hooks используется один общий слот на процесс.

Локали, определяемые в рантайме #

createDialect выполняется при импорте модуля — раньше, чем можно прочитать таблицу настроек оператора или переменную окружения на старте. configure заменяет набор локалей и локаль по умолчанию постфактум, и всё, что читает эти значения дальше — согласование, setLocale, reroute, localizeHref — подхватывает новые.

Вызовите его один раз на сервере при старте, до первого запроса, и ещё раз на клиенте во время инициализации приложения — со значениями, которые сервер передал через page data. configure идемпотентен, повторный вызов при каждой навигации ничего не стоит.

src/hooks.server.ts — top-level await runs before the first request
import { configure, handle as dialect } from '#lib/i18n.js';

const settings = await loadLocaleSettings();
configure({ defaultLocale: settings.defaultLocale, locales: settings.locales });

export const handle = dialect;
src/routes/+layout.ts
import { configure } from '#lib/i18n.js';
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = async ({ data }) => {
	configure(data.i18n);
	return data;
};

locales полностью заменяет набор, а не дополняет его, а defaultLocale обязан входить в итоговый набор; вызов, нарушающий любое из этих правил, выводит предупреждение и ничего не меняет. reroute — единственная часть, которая не может ждать: SvelteKit вызывает его синхронно до какого-либо load-функции, — поэтому префикс, убранный через configure, перестаёт быть маршрутом вообще.