Riot.js предоставляет возможность создавать динамичные интерфейсы с минимальным количеством кода. Для реализации многоязычных приложений важно правильно организовать структуру компонентов и механизм локализации. Основная идея заключается в разделении текстового контента от логики компонентов и использовании централизованного хранилища переводов.
Рекомендуемая структура проекта для многоязычного приложения на Riot.js:
src/
├─ components/
│ ├─ header.riot
│ ├─ footer.riot
│ └─ language-selector.riot
├─ locales/
│ ├─ en.json
│ └─ ru.json
├─ app.js
└─ 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.Этот подход обеспечивает гибкость, централизованное управление языками и возможность расширения интерфейса без изменения логики компонентов.