Загрузка переводов

Fresh — это современный фреймворк на JavaScript/TypeScript, ориентированный на серверный рендеринг и высокую производительность. Одной из ключевых задач при разработке многоязычных приложений является организация системы переводов. В Fresh это достигается с помощью серверных модулей и асинхронной загрузки ресурсов.

Структура переводов

Переводы в Fresh обычно хранятся в виде JSON-файлов, где ключи представляют уникальные идентификаторы строк, а значения — переведённый текст. Типичная структура проекта может выглядеть так:

locales/
  en.json
  ru.json
  fr.json

Пример содержимого ru.json:

{
  "greeting": "Привет",
  "farewell": "До свидания",
  "welcome_message": "Добро пожаловать на наш сайт"
}

Ключи должны быть уникальными и интуитивно понятными, чтобы облегчить поддержку и расширение проекта.

Динамическая загрузка переводов

Для оптимизации загрузки страниц важно загружать переводы только для текущего языка. Fresh позволяет использовать динамический импорт JSON-файлов на сервере.

Пример функции для загрузки переводов:

import type { Handlers } from "$fresh/server.ts";

export const handler: Handlers = {
  async GET(req) {
    const url = new URL(req.url);
    const lang = url.searchParams.get("lang") || "en";
    
    let translations;
    try {
      translations = await import(`../locales/${lang}.json`);
    } catch {
      translations = await import("../locales/en.json");
    }

    return new Response(JSON.stringify(translations.default), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

Особое внимание уделяется обработке ошибок: если запрашиваемый язык не найден, используется fallback на английский. Это предотвращает появление пустых строк и сбоев интерфейса.

Интеграция с компонентами Fresh

Fresh использует Island Architecture, где часть компонентов рендерится на клиенте. Для передачи переводов из сервера в Island-компоненты можно использовать пропсы:

// components/Greeting.tsx
interface GreetingProps {
  translations: Record<string, string>;
}

export default function Greeting({ translations }: GreetingProps) {
  return <h1>{translations.greeting}</h1>;
}

На серверной странице:

import Greeting from "../components/Greeting.tsx";
import enTranslations from "../locales/en.json";

export default function Home() {
  return <Greeting translations={enTranslations} />;
}

Для динамических страниц язык можно передавать через URL или cookie, загружая соответствующий JSON на сервере.

Кэширование и производительность

При работе с переводами критически важно минимизировать повторные запросы к файловой системе. В Fresh можно использовать WeakMap или глобальные объекты для кэширования JSON:

const cache = new Map<string, Record<string, string>>();

async function loadTranslations(lang: string) {
  if (cache.has(lang)) return cache.get(lang)!;
  const translations = await import(`../locales/${lang}.json`);
  cache.set(lang, translations.default);
  return translations.default;
}

Такой подход снижает нагрузку на сервер и ускоряет отклик страницы.

Поддержка нескольких языков на одной странице

Иногда требуется выводить контент на разных языках одновременно. В этом случае каждое отдельное выражение загружается через универсальную функцию:

async function t(key: string, lang: string) {
  const translations = await loadTranslations(lang);
  return translations[key] || key;
}

Использование этой функции позволяет формировать многоязычные интерфейсы без дублирования логики рендеринга.

Автоматическое определение языка

Fresh не навязывает способ определения языка, но чаще всего используется:

  • Заголовок Accept-Language запроса
  • Параметры URL (?lang=ru)
  • Cookie с пользовательскими настройками

Пример обработки через заголовок:

const acceptLang = req.headers.get("accept-language")?.split(",")[0] || "en";
const translations = await loadTranslations(acceptLang);

Это обеспечивает удобную интеграцию с браузерами и повышает UX многоязычных приложений.

Структура и типизация

Для TypeScript рекомендуется описывать типы переводов:

interface Translations {
  greeting: string;
  farewell: string;
  welcome_message: string;
}

Затем JSON можно импортировать с типами:

import ruTranslations from "../locales/ru.json" assert { type: "json" };
const translations: Translations = ruTranslations;

Это предотвращает ошибки при использовании ключей и упрощает рефакторинг.

Итоговая схема загрузки

  1. Определение языка пользователя.
  2. Асинхронная загрузка соответствующего JSON-файла.
  3. Кэширование для повторного использования.
  4. Передача переводов в компоненты через пропсы.
  5. Использование fallback-языка при ошибках.

Такой подход позволяет создавать масштабируемые, производительные и легко поддерживаемые многоязычные приложения на Fresh.