Интернационализация (i18n) в приложениях на Fresh направлена на разделение контента, языка и логики отображения, сохраняя при этом ключевую особенность фреймворка — серверный рендеринг с минимальным JavaScript на клиенте. Fresh не навязывает встроенную систему i18n, что делает концепции особенно важными: архитектура локализации проектируется на уровне приложения.
i18n в Fresh строится вокруг следующих принципов:
Основной источник информации о языке — HTTP-запрос. В Fresh доступны все стандартные механизмы:
Accept-LanguageHTTP-заголовок Accept-Language позволяет определить
предпочтительный язык пользователя:
Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
На сервере Fresh этот заголовок читается через
Request:
const langHeader = req.headers.get("accept-language");
Обычно применяется разбор приоритетов и сопоставление с поддерживаемыми локалями.
Наиболее распространённый и предсказуемый подход — явное указание языка в маршруте:
/ru/blog
/en/blog
/de/blog
В Fresh это естественно реализуется через файловую структуру:
routes/
├─ [lang]/
│ ├─ index.tsx
│ ├─ blog.tsx
Параметр lang автоматически доступен в обработчике
маршрута.
Cookies используются как вторичный источник, если язык не указан в URL. Типовой порядок определения локали:
Accept-Language;Наиболее прозрачная модель — хранение переводов в виде статических объектов:
export const ru = {
title: "Главная страница",
menu: {
home: "Главная",
blog: "Блог",
},
};
Каждая локаль оформляется как отдельный модуль:
locales/
├─ ru.ts
├─ en.ts
├─ de.ts
Импорт выполняется на сервере, без участия клиента.
Fresh активно использует TypeScript, что позволяет строго типизировать словари:
type Dictionary = typeof ru;
const dictionaries: Record<string, Dictionary> = {
ru,
en,
};
Это исключает расхождения ключей между языками на этапе компиляции.
Для передачи переводов в компоненты используется контекст рендеринга Fresh.
HandlerContextВ обработчике маршрута определяется язык и словарь:
export const handler = {
GET(req, ctx) {
const lang = ctx.params.lang;
const dict = dictionaries[lang] ?? dictionaries.ru;
return ctx.render({ lang, dict });
},
};
Данные передаются в компонент страницы как props.
Компоненты Fresh — это чистые функции без клиентского состояния. Переводы передаются явно:
export default function Page(props) {
const { dict } = props.data;
return (
<h1>{dict.title}</h1>
);
}
Ключевые особенности подхода:
Для уменьшения шаблонного кода применяется функция-доступ к ключам:
function t(dict, path: string) {
return path.split(".").reduce((o, k) => o?.[k], dict);
}
Использование:
<h1>{t(dict, "menu.home")}</h1>
В крупных проектах применяются типобезопасные генераторы ключей или enum-подход.
Переводы часто содержат переменные:
welcome: "Добро пожаловать, {name}"
Простая интерполяция:
function format(str: string, vars: Record<string, string>) {
return str.replace(/\{(\w+)\}/g, (_, k) => vars[k]);
}
Использование:
{format(dict.welcome, { name: user.name })}
Такой подход полностью совместим с серверным рендерингом Fresh.
Текст — лишь часть i18n. Форматирование значений выполняется через
стандартный Intl API:
new Intl.DateTimeFormat(lang).format(date);
new Intl.NumberFormat(lang).format(number);
new Intl.NumberFormat(lang, {
style: "currency",
currency: "EUR",
}).format(price);
Intl работает нативно в Deno и не требует сторонних
библиотек.
Помимо текста, локализуются сами пути:
/ru/o-nas
/en/about
Реализуется через таблицу маршрутов:
const routes = {
about: {
ru: "o-nas",
en: "about",
},
};
URL генерируются на сервере с учётом языка, что сохраняет SEO и кэшируемость.
Fresh отлично подходит для многоязычных сайтов благодаря SSR:
<html lang="ru"> задаётся на сервере;<link rel="alternate" hreflang="..."> формируется
динамически;Поисковые системы индексируют страницы как независимые документы.
Переводы — статические данные, что позволяет:
i18n не увеличивает размер hydration-бандла, поскольку логика полностью серверная.
Хранение языка в состоянии клиента Нарушает модель Fresh и усложняет рендеринг.
Глобальный singleton-переводчик Приводит к ошибкам при параллельных запросах.
Смешивание текста и логики Усложняет поддержку и добавление новых языков.
Такой подход соответствует философии Fresh: простота, серверная предсказуемость и контроль над архитектурой без избыточных абстракций.