Интернационализация приложений

Интернационализация (i18n) — это процесс подготовки приложения к поддержке нескольких языков и региональных особенностей. В контексте Hyperapp подход к i18n строится на минимализме, реактивности и простой архитектуре состояния. Рассмотрим ключевые аспекты реализации интернационализации на примере реального приложения.


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

Hyperapp опирается на одно глобальное состояние, которое обновляется через actions. Для i18n удобнее всего хранить текущую локаль и словари переводов именно в состоянии.

import { h, app } from "hyperapp";

const state = {
  locale: "en",
  translations: {
    en: {
      greeting: "Hello",
      farewell: "Goodbye"
    },
    ru: {
      greeting: "Привет",
      farewell: "До свидания"
    }
  }
};

const actions = {
  setLocale: locale => state => ({ locale })
};

В этом примере locale определяет текущий язык приложения, а translations хранит переводы для каждого языка. Использование состояния Hyperapp обеспечивает автоматическое обновление UI при смене локали.


Функция для перевода текста

Чтобы избежать дублирования кода, создается функция-обертка, которая возвращает нужный перевод на основе текущей локали:

const t = (key) => (state) => state.translations[state.locale][key] || key;

Применение:

const view = (state, actions) => h("div", {}, [
  h("h1", {}, t("greeting")(state)),
  h("button", { onclick: () => actions.setLocale("ru") }, "RU"),
  h("button", { onclick: () => actions.setLocale("en") }, "EN")
]);

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


Организация словарей переводов

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

/i18n
  en.js
  ru.js

Пример en.js:

export default {
  greeting: "Hello",
  farewell: "Goodbye",
  menu: {
    home: "Home",
    about: "About"
  }
};

Импорт и объединение словарей:

import en from "./i18n/en";
import ru from "./i18n/ru";

const state = {
  locale: "en",
  translations: { en, ru }
};

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


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

В современных приложениях часто требуется ленивая загрузка локалей, чтобы не грузить все переводы сразу. В Hyperapp это можно реализовать через асинхронные actions:

const actions = {
  loadLocale: locale => async state => {
    const translations = await import(`./i18n/${locale}.js`);
    return { locale, translations: { ...state.translations, [locale]: translations.default } };
  }
};

Использование:

h("button", { onclick: () => actions.loadLocale("ru") }, "RU")

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


Форматирование чисел и дат

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

const formatDate = (date, locale) =>
  new Intl.DateTimeFormat(locale, { year: "numeric", month: "long", day: "numeric" }).format(date);

const formatCurrency = (amount, locale, currency) =>
  new Intl.NumberFormat(locale, { style: "currency", currency }).format(amount);

Пример использования в компоненте:

h("p", {}, `Сегодня: ${formatDate(new Date(), state.locale)}`)

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


Состояние и реактивность

Основной принцип Hyperapp — реактивное обновление UI при изменении состояния. При смене locale весь интерфейс, использующий функцию t, автоматически перерендеривается. Это позволяет создавать динамически переводимые интерфейсы без дополнительных библиотек для реактивности.


Поддержка множества языков

Для приложений с большим числом локалей полезно внедрять вспомогательные функции:

const availableLocales = ["en", "ru", "fr", "de"];

const isLocaleAvailable = (locale) => availableLocales.includes(locale);

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


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

Hyperapp позволяет строить локализованные компоненты следующим образом:

const Greeting = ({ state }) => h("h1", {}, t("greeting")(state));
const Farewell = ({ state }) => h("h2", {}, t("farewell")(state));

const view = (state, actions) => h("div", {}, [
  Greeting({ state }),
  Farewell({ state }),
  h("button", { onclick: () => actions.setLocale("ru") }, "RU"),
  h("button", { onclick: () => actions.setLocale("en") }, "EN")
]);

Компоненты остаются полностью переиспользуемыми, а локализация управляется через состояние.


Практические рекомендации

  • Хранить переводы в отдельной структуре, привязанной к ключам.
  • Использовать функции-обертки для перевода текста.
  • Применять стандартные объекты Intl для форматирования дат, времени и чисел.
  • Организовывать динамическую загрузку локалей для оптимизации бандла.
  • Проверять доступность локали перед установкой, чтобы избежать ошибок.
  • Структурировать переводы по модулям для масштабируемых приложений.

Hyperapp предоставляет минималистичный, но мощный каркас для реализации интернационализации. Реактивное состояние, простая архитектура actions и возможность динамической загрузки локалей позволяют создавать гибкие, легкие и поддерживаемые многоязычные приложения.