Многоязычность в Mithril приложениях

Основы локализации

Многоязычность (i18n) в Mithril требует организации текстового контента и управления переводами без нарушения реактивной природы компонентов. Основной принцип заключается в том, чтобы текстовые данные не захардкоживались в компонентах, а подгружались из внешних словарей или файлов перевода.

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

const translations = {
    en: {
        greeting: "Hello",
        farewell: "Goodbye",
        welcomeMessage: "Welcome to our application"
    },
    ru: {
        greeting: "Привет",
        farewell: "До свидания",
        welcomeMessage: "Добро пожаловать в наше приложение"
    }
};

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

Управление текущим языком

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

const i18n = {
    currentLang: "en",
    setLang(lang) {
        this.currentLang = lang;
        m.redraw(); // Перерисовка всех компонентов при смене языка
    },
    t(key) {
        return translations[this.currentLang][key] || key;
    }
};

Функция t возвращает перевод по ключу или сам ключ, если перевода нет, что предотвращает ошибки при недостающих строках.

Использование переводов в компонентах

В Mithril компоненты описываются через функцию view. Переводы интегрируются напрямую в шаблон с помощью вызова функции t:

const WelcomeComponent = {
    view() {
        return m("div", [
            m("h1", i18n.t("greeting")),
            m("p", i18n.t("welcomeMessage")),
            m("button", { onclick: () => i18n.setLang("ru") }, "RU"),
            m("button", { onclick: () => i18n.setLang("en") }, "EN")
        ]);
    }
};

Особенности перерисовки: Mithril автоматически реагирует на изменение состояния, но необходимо вручную вызвать m.redraw() при изменении языка, если смена происходит вне событий, связанных с компонентами.

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

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

/i18n
  en.js
  ru.js

Пример en.js:

export default {
    greeting: "Hello",
    farewell: "Goodbye",
    welcomeMessage: "Welcome to our application"
};

Загрузка осуществляется через импорт:

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

const translations = { en, ru };

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

Динамические строки и параметры

Часто тексты содержат переменные, например имя пользователя или число элементов. Для этого используют шаблоны с подстановкой:

const en = {
    greetingUser: "Hello, {name}!"
};

function t(key, params = {}) {
    let str = translations[i18n.currentLang][key] || key;
    for (const k in params) {
        str = str.replace(`{${k}}`, params[k]);
    }
    return str;
}

// Использование
m("p", t("greetingUser", { name: "Alice" }));

Поддержка параметров позволяет создавать универсальные переводы, которые не зависят от конкретного контекста.

Интеграция с внешними библиотеками i18n

Для более сложных задач можно использовать сторонние библиотеки, например i18next. Mithril позволяет интегрировать их с минимальными усилиями:

import i18next from "i18next";

i18next.init({
    lng: "en",
    resources: {
        en: { translation: { greeting: "Hello" } },
        ru: { translation: { greeting: "Привет" } }
    }
});

const WelcomeComponent = {
    view() {
        return m("div", i18next.t("greeting"));
    }
};

Подключение i18next упрощает работу с форматированием дат, чисел и поддержкой множественного числа.

Рекомендации по архитектуре

  1. Централизованное управление языками: хранение текущего языка и словарей в отдельном сервисе предотвращает дублирование.
  2. Ленивая загрузка переводов: для больших проектов полезно загружать языковые файлы по мере необходимости.
  3. Тестирование ключей перевода: автоматическая проверка наличия всех ключей во всех языках помогает избежать ошибок отображения.
  4. Поддержка fallback: при отсутствии перевода использовать английский или дефолтный ключ, чтобы интерфейс оставался читаемым.

Работа с компонентами и контекстом

Если приложение использует вложенные компоненты, важно передавать контекст языка через сервис или props. Пример с сервисом удобен, так как компоненты обращаются к нему напрямую, и изменение языка автоматически вызывает перерисовку всех дочерних элементов.

Международные форматы данных

Переводы текста — только часть задачи. Для полного мультиязычного опыта необходимо учитывать:

  • Форматы даты и времени (Intl.DateTimeFormat)
  • Форматы чисел и валют (Intl.NumberFormat)
  • Порядок элементов в интерфейсе (LTR/RTL)

Пример форматирования даты:

const date = new Date();
const formatted = new Intl.DateTimeFormat(i18n.currentLang, { dateStyle: "long" }).format(date);
m("p", formatted);

Использование Intl обеспечивает корректное отображение данных для любого языка без сторонних библиотек.

Поддержка множественного числа

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

function pluralize(key, count) {
    const base = translations[i18n.currentLang][key] || key;
    return count === 1 ? base.singular : base.plural;
}

// Пример словаря
const en = {
    item: { singular: "item", plural: "items" }
};

Для сложных правил лучше использовать готовые библиотеки типа i18next, которые учитывают правила конкретного языка.

Динамическая смена языка

При динамической смене языка важно:

  • Обновлять текст всех компонентов (m.redraw())
  • Перезагружать локализованные ресурсы, если они подгружаются асинхронно
  • Сохранять выбор пользователя (например, в localStorage)
i18n.setLang = function(lang) {
    this.currentLang = lang;
    localStorage.setItem("lang", lang);
    m.redraw();
};

Загрузка языка из localStorage позволяет сохранять предпочтения между сессиями.

Итоговые принципы

  • Словари и ключи должны быть централизованы
  • Компоненты используют функцию t для любых текстовых элементов
  • Переключение языка инициирует перерисовку всех компонентов
  • Динамические данные и множественное число обрабатываются через шаблоны или специализированные функции
  • Международные форматы даты, числа и валюты следует обрабатывать через Intl

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