Выбор локали
Локаль определяется перебором списка источников — побеждает первое совпадение. Источники, неприменимые к текущей стороне, пропускаются, поэтому один и тот же список работает и на сервере, и в браузере.
Порядок по умолчанию #
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 идемпотентен, повторный вызов при каждой навигации ничего не стоит.
import { configure, handle as dialect } from '#lib/i18n.js';
const settings = await loadLocaleSettings();
configure({ defaultLocale: settings.defaultLocale, locales: settings.locales });
export const handle = dialect;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, перестаёт быть маршрутом вообще.