Многоязычные приложения на Riot.js

Riot.js предоставляет возможность создавать динамичные интерфейсы с минимальным количеством кода. Для реализации многоязычных приложений важно правильно организовать структуру компонентов и механизм локализации. Основная идея заключается в разделении текстового контента от логики компонентов и использовании централизованного хранилища переводов.

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

Рекомендуемая структура проекта для многоязычного приложения на Riot.js:

src/
 ├─ components/
 │   ├─ header.riot
 │   ├─ footer.riot
 │   └─ language-selector.riot
 ├─ locales/
 │   ├─ en.json
 │   └─ ru.json
 ├─ app.js
 └─ i18n.js
  • components/ — папка с Riot-компонентами.
  • locales/ — папка с JSON-файлами переводов для каждого языка.
  • i18n.js — модуль локализации.

Модуль локализации

Централизованный модуль локализации позволяет управлять текущим языком и получать переводы по ключу. Пример реализации:

import en from './locales/en.json';
import ru from './locales/ru.json';

const translations = { en, ru };
let currentLanguage = 'en';

export const setLanguage = (lang) => {
  if (translations[lang]) currentLanguage = lang;
};

export const t = (key) => {
  return translations[currentLanguage][key] || key;
};
  • setLanguage(lang) — устанавливает текущий язык приложения.
  • t(key) — возвращает перевод для переданного ключа. Если ключ отсутствует, возвращает сам ключ.

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

В Riot-компонентах доступ к переводу осуществляется через импорт модуля i18n.js. Пример компонента:

<hello-world>
  <h1>{ t('welcome_message') }</h1>

  <script>
    import { t } from '../i18n.js';
  </script>
</hello-world>

Здесь { t('welcome_message') } автоматически подставляет текст на текущем языке.

Компонент выбора языка

Для динамической смены языка создается отдельный компонент:

<language-selector>
  <SELECT oncha nge={changeLanguage}>
    <option value="en">English</option>
    <option value="ru">Русский</option>
  </select>

  <script>
    import { setLanguage } FROM '../i18n.js';
    import { update } from 'riot';

    const changeLanguage = (event) => {
      setLanguage(event.target.value);
      update(); // Обновление всех компонентов Riot
    };
  </script>
</language-selector>
  • update() — глобальное обновление состояния компонентов, чтобы новый язык применился сразу.

Реактивное обновление текста

Riot.js поддерживает реактивность через методы update() и локальные состояния компонентов. Для многоязычности рекомендуется централизованно оповещать компоненты о смене языка:

import { observable } from 'riot';

export const i18nStore = observable({
  lang: 'en',
  setLang(newLang) {
    this.lang = newLang;
    this.trigger('change', newLang);
  }
});

Компоненты подписываются на событие change и обновляют свой интерфейс:

<greeting>
  <h1>{ message }</h1>

  <script>
    import { i18nStore, t } from '../i18n.js';
    let message = t('welcome_message');

    i18nStore.on('change', () => {
      message = t('welcome_message');
      update();
    });
  </script>
</greeting>

Работа с динамическими строками

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

{
  "greeting": "Привет, {name}!"
}

В Riot.js это реализуется через функцию форматирования:

export const t = (key, params = {}) => {
  let str = translations[currentLanguage][key] || key;
  for (const [k, v] of Object.entries(params)) {
    str = str.replace(`{${k}}`, v);
  }
  return str;
};

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

<h1>{ t('greeting', { name: userName }) }</h1>

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

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

Асинхронная загрузка переводов

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

export const loadLanguage = async (lang) => {
  const res = await fetch(`/locales/${lang}.json`);
  translations[lang] = await res.json();
  setLanguage(lang);
};

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

Рекомендации по организации

  • Все текстовые ключи должны быть в едином формате: section.key, например header.title.
  • Избегать дублирования текста в компонентах, использовать только ключи.
  • Для динамических компонентов использовать реактивный стор локализации.
  • Предусмотреть fallback на английский или базовый язык при отсутствии перевода.

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