diff --git a/Dockerfile.web b/Dockerfile.web
new file mode 100644
index 00000000..3f85d9e7
--- /dev/null
+++ b/Dockerfile.web
@@ -0,0 +1,26 @@
+FROM node:20-slim AS base
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+RUN corepack enable
+
+FROM base AS prod
+
+WORKDIR /app
+COPY pnpm-lock.yaml /app
+RUN pnpm fetch --prod
+
+WORKDIR /app/packages/web
+COPY . /app
+RUN pnpm build
+####################################
+FROM nginx:1.29.1-alpine-slim as web
+RUN rm -r /usr/share/nginx/html \
+ && mkdir -p /usr/share/nginx/html \
+ && mkdir -p /etc/nginx/conf.d
+
+WORKDIR /usr/share/nginx/html
+COPY --from=prod /app/packages/web/dist ./
+#ADD ./packages/web/dist .
+COPY --from=prod /app/packages/web/infra/default.conf /etc/nginx/conf.d/default.conf
+EXPOSE 8080
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/packages/web/public/i18n/locales/ru-RU/channels.json b/packages/web/public/i18n/locales/ru-RU/channels.json
new file mode 100644
index 00000000..2a82f6ef
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/channels.json
@@ -0,0 +1,71 @@
+{
+ "page": {
+ "sectionLabel": "Каналы",
+ "channelName": "Канал: {{channelName}}",
+ "broadcastLabel": "Основной",
+ "channelIndex": "Кан {{index}}",
+ "import": "Импорт",
+ "export": "Экспорт"
+ },
+ "validation": {
+ "pskInvalid": "Пожалуйста, введите корректный {{bits}}-битный PSK."
+ },
+ "settings": {
+ "label": "Настройки канала",
+ "description": "Шифрование, MQTT и прочие настройки"
+ },
+ "role": {
+ "label": "Роль",
+ "description": "Телеметрия устройства отправляется через ОСНОВНОЙ канал. Разрешён только один ОСНОВНОЙ канал",
+ "options": {
+ "primary": "ОСНОВНОЙ",
+ "disabled": "ОТКЛЮЧЁН",
+ "secondary": "ВТОРИЧНЫЙ"
+ }
+ },
+ "psk": {
+ "label": "Предварительный общий ключ (PSK)",
+ "description": "Поддерживаемые длины PSK: 256-бит, 128-бит, 8-бит, Пустой (0-бит)",
+ "generate": "Сгенерировать"
+ },
+ "name": {
+ "label": "Название",
+ "description": "Уникальное имя канала <12 байт, оставьте пустым для значения по умолчанию"
+ },
+ "uplinkEnabled": {
+ "label": "Восходящая связь включена",
+ "description": "Отправлять сообщения из локальной сети в MQTT"
+ },
+ "downlinkEnabled": {
+ "label": "Нисходящая связь включена",
+ "description": "Отправлять сообщения из MQTT в локальную сеть"
+ },
+ "positionPrecision": {
+ "label": "Локация",
+ "description": "Точность передаваемой локации в канале. Можно отключить.",
+ "options": {
+ "none": "Не передавать локацию",
+ "precise": "Точная локация",
+ "metric_km23": "В радиусе 23 километров",
+ "metric_km12": "В радиусе 12 километров",
+ "metric_km5_8": "В радиусе 5,8 километров",
+ "metric_km2_9": "В радиусе 2,9 километров",
+ "metric_km1_5": "В радиусе 1,5 километров",
+ "metric_m700": "В радиусе 700 метров",
+ "metric_m350": "В радиусе 350 метров",
+ "metric_m200": "В радиусе 200 метров",
+ "metric_m90": "В радиусе 90 метров",
+ "metric_m50": "В радиусе 50 метров",
+ "imperial_mi15": "В радиусе 15 миль",
+ "imperial_mi7_3": "В радиусе 7,3 миль",
+ "imperial_mi3_6": "В радиусе 3,6 миль",
+ "imperial_mi1_8": "В радиусе 1,8 миль",
+ "imperial_mi0_9": "В радиусе 0,9 миль",
+ "imperial_mi0_5": "В радиусе 0,5 миль",
+ "imperial_mi0_2": "В радиусе 0,2 миль",
+ "imperial_ft600": "В радиусе 600 футов",
+ "imperial_ft300": "В радиусе 300 футов",
+ "imperial_ft150": "В радиусе 150 футов"
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/web/public/i18n/locales/ru-RU/commandPalette.json b/packages/web/public/i18n/locales/ru-RU/commandPalette.json
new file mode 100644
index 00000000..fc29806c
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/commandPalette.json
@@ -0,0 +1,51 @@
+{
+ "emptyState": "Ничего не найдено.",
+ "page": {
+ "title": "Меню команд"
+ },
+ "pinGroup": {
+ "label": "Закрепить группу команд"
+ },
+ "unpinGroup": {
+ "label": "Открепить группу команд"
+ },
+ "goto": {
+ "label": "Перейти",
+ "command": {
+ "messages": "Сообщения",
+ "map": "Карта",
+ "config": "Настройки",
+ "nodes": "Узлы"
+ }
+ },
+ "manage": {
+ "label": "Управление",
+ "command": {
+ "switchNode": "Сменить узел",
+ "connectNewNode": "Подключить новый узел"
+ }
+ },
+ "contextual": {
+ "label": "Контекстные",
+ "command": {
+ "qrCode": "QR-код",
+ "qrGenerator": "Генератор",
+ "qrImport": "Импорт",
+ "scheduleShutdown": "Запланировать выключение",
+ "scheduleReboot": "Перезагрузить устройство",
+ "resetNodeDb": "Сбросить базу узлов",
+ "dfuMode": "Режим DFU",
+ "factoryResetDevice": "Сброс устройства к заводским",
+ "factoryResetConfig": "Сброс конфигурации к заводским",
+ "disconnect": "Отключиться"
+ }
+ },
+ "debug": {
+ "label": "Отладка",
+ "command": {
+ "reconfigure": "Переконфигурировать",
+ "clearAllStoredMessages": "Очистить все сохранённые сообщения",
+ "clearAllStores": "Очистить всё локальное хранилище"
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/web/public/i18n/locales/ru-RU/common.json b/packages/web/public/i18n/locales/ru-RU/common.json
new file mode 100644
index 00000000..7a9f4436
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/common.json
@@ -0,0 +1,123 @@
+{
+ "button": {
+ "apply": "Применить",
+ "backupKey": "Сохранить ключ",
+ "cancel": "Отмена",
+ "clearMessages": "Очистить сообщения",
+ "close": "Закрыть",
+ "confirm": "Подтвердить",
+ "delete": "Удалить",
+ "dismiss": "Отклонить",
+ "download": "Скачать",
+ "export": "Экспортировать",
+ "generate": "Сгенерировать",
+ "regenerate": "Перегенерировать",
+ "import": "Импортировать",
+ "message": "Сообщение",
+ "now": "Сейчас",
+ "ok": "ОК",
+ "print": "Печать",
+ "remove": "Удалить",
+ "requestNewKeys": "Запросить новые ключи",
+ "requestPosition": "Запросить позицию",
+ "reset": "Сбросить",
+ "save": "Сохранить",
+ "scanQr": "Сканировать QR-код",
+ "traceRoute": "Трассировка маршрута",
+ "submit": "Отправить"
+ },
+ "app": {
+ "title": "Meshtastic",
+ "fullTitle": "Meshtastic Веб-клиент"
+ },
+ "loading": "Загрузка...",
+ "unit": {
+ "cps": "симв/с",
+ "dbm": "дБм",
+ "hertz": "Гц",
+ "hop": {
+ "one": "Передача",
+ "plural": "Передачи"
+ },
+ "hopsAway": {
+ "one": "{{count}} передача",
+ "plural": "{{count}} передач",
+ "unknown": "Неизвестное количество передач"
+ },
+ "megahertz": "МГц",
+ "raw": "исх.",
+ "meter": { "one": "Метр", "plural": "Метра", "suffix": "м" },
+ "kilometer": { "one": "Километр", "plural": "Километров", "suffix": "км" },
+ "minute": { "one": "Минута", "plural": "Минут" },
+ "hour": { "one": "Час", "plural": "Часов" },
+ "millisecond": {
+ "one": "Миллисекунда",
+ "plural": "Миллисекунд",
+ "suffix": "мс"
+ },
+ "second": { "one": "Секунда", "plural": "Секунд" },
+ "day": {
+ "one": "День",
+ "plural": "Дней",
+ "today": "Сегодня",
+ "yesterday": "Вчера"
+ },
+ "month": { "one": "Месяц", "plural": "Месяцев" },
+ "year": { "one": "Год", "plural": "Лет" },
+ "snr": "ОСШ",
+ "volt": { "one": "Вольт", "plural": "Вольт", "suffix": "В" },
+ "record": { "one": "Запись", "plural": "Записи" },
+ "degree": { "one": "Градус", "plural": "Градусов", "suffix": "°" }
+ },
+ "security": {
+ "0bit": "Пусто",
+ "8bit": "8 бит",
+ "128bit": "128 бит",
+ "256bit": "256 бит"
+ },
+ "unknown": {
+ "longName": "Неизвестно",
+ "shortName": "НЕИЗВ",
+ "notAvailable": "Н/Д",
+ "num": "??"
+ },
+ "nodeUnknownPrefix": "!",
+ "unset": "НЕ ЗАДАНО",
+ "fallbackName": "Meshtastic {{last4}}",
+ "node": "Узел",
+ "formValidation": {
+ "unsavedChanges": "Несохранённые изменения",
+ "tooBig": {
+ "string": "Слишком длинное, ожидалось не более {{maximum}} символов.",
+ "number": "Слишком большое, ожидалось число не более {{maximum}}.",
+ "bytes": "Слишком большой размер, ожидалось не более {{params.maximum}} байт."
+ },
+ "tooSmall": {
+ "string": "Слишком короткое, ожидалось не менее {{minimum}} символов.",
+ "number": "Слишком маленькое, ожидалось число не менее {{minimum}}."
+ },
+ "invalidFormat": {
+ "ipv4": "Неверный формат, ожидался IPv4-адрес.",
+ "key": "Неверный формат, ожидался ключ в формате Base64 (PSK)."
+ },
+ "invalidType": {
+ "number": "Неверный тип, ожидалось число."
+ },
+ "pskLength": {
+ "0bit": "Ключ должен быть пустым.",
+ "8bit": "Ключ должен быть 8-битным предварительно разделённым ключом (PSK).",
+ "128bit": "Ключ должен быть 128-битным предварительно разделённым ключом (PSK).",
+ "256bit": "Ключ должен быть 256-битным предварительно разделённым ключом (PSK)."
+ },
+ "required": {
+ "generic": "Это поле обязательно для заполнения.",
+ "managed": "Требуется хотя бы один административный ключ, если узел управляемый.",
+ "key": "Ключ обязателен."
+ },
+ "invalidOverrideFreq": {
+ "number": "Неверный формат, ожидалось значение в диапазоне 410–930 МГц или 0 (по умолчанию)."
+ }
+ },
+ "yes": "Да",
+ "no": "Нет"
+}
\ No newline at end of file
diff --git a/packages/web/public/i18n/locales/ru-RU/dashboard.json b/packages/web/public/i18n/locales/ru-RU/dashboard.json
new file mode 100644
index 00000000..484f9c19
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/dashboard.json
@@ -0,0 +1,12 @@
+{
+ "dashboard": {
+ "title": "Подключённые устройства",
+ "description": "Управление вашими подключёнными устройствами Meshtastic.",
+ "connectionType_ble": "BLE",
+ "connectionType_serial": "Последовательный порт",
+ "connectionType_network": "Сеть",
+ "noDevicesTitle": "Нет подключённых устройств",
+ "noDevicesDescription": "Подключите новое устройство, чтобы начать.",
+ "button_newConnection": "Новое подключение"
+ }
+}
\ No newline at end of file
diff --git a/packages/web/public/i18n/locales/ru-RU/deviceConfig.json b/packages/web/public/i18n/locales/ru-RU/deviceConfig.json
new file mode 100644
index 00000000..38282e7c
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/deviceConfig.json
@@ -0,0 +1,428 @@
+{
+ "page": {
+ "title": "Конфигурация",
+ "tabBluetooth": "Bluetooth",
+ "tabDevice": "Устройство",
+ "tabDisplay": "Дисплей",
+ "tabLora": "LoRa",
+ "tabNetwork": "Сеть",
+ "tabPosition": "Позиция",
+ "tabPower": "Питание",
+ "tabSecurity": "Безопасность"
+ },
+ "sidebar": {
+ "label": "Модули"
+ },
+ "device": {
+ "title": "Настройки устройства",
+ "description": "Параметры устройства",
+ "buttonPin": {
+ "description": "Переопределение контакта кнопки",
+ "label": "Контакт кнопки"
+ },
+ "buzzerPin": {
+ "description": "Переопределение контакта зуммера",
+ "label": "Контакт зуммера"
+ },
+ "disableTripleClick": {
+ "description": "Отключить тройное нажатие",
+ "label": "Отключить тройное нажатие"
+ },
+ "doubleTapAsButtonPress": {
+ "description": "Рассматривать двойное касание как нажатие кнопки",
+ "label": "Двойное касание как нажатие"
+ },
+ "ledHeartbeatDisabled": {
+ "description": "Отключить мигание светодиода по умолчанию",
+ "label": "Отключить мигание светодиода"
+ },
+ "nodeInfoBroadcastInterval": {
+ "description": "Как часто транслировать информацию об узле",
+ "label": "Интервал трансляции информации об узле"
+ },
+ "posixTimezone": {
+ "description": "Строка временной зоны POSIX для устройства",
+ "label": "Временная зона POSIX"
+ },
+ "rebroadcastMode": {
+ "description": "Как обрабатывать ретрансляцию",
+ "label": "Режим ретрансляции"
+ },
+ "role": {
+ "description": "Роль устройства в сети",
+ "label": "Роль"
+ }
+ },
+ "bluetooth": {
+ "title": "Настройки Bluetooth",
+ "description": "Параметры модуля Bluetooth",
+ "note": "Примечание: Некоторые устройства (ESP32) не могут использовать Bluetooth и Wi-Fi одновременно.",
+ "enabled": {
+ "description": "Включить или отключить Bluetooth",
+ "label": "Включено"
+ },
+ "pairingMode": {
+ "description": "Поведение при выборе PIN-кода.",
+ "label": "Режим сопряжения"
+ },
+ "pin": {
+ "description": "PIN-код для сопряжения",
+ "label": "PIN-код"
+ }
+ },
+ "display": {
+ "description": "Параметры дисплея устройства",
+ "title": "Настройки дисплея",
+ "headingBold": {
+ "description": "Выделение текста заголовка жирным",
+ "label": "Жирный заголовок"
+ },
+ "carouselDelay": {
+ "description": "Скорость переключения между окнами",
+ "label": "Задержка карусели"
+ },
+ "compassNorthTop": {
+ "description": "Фиксировать север вверху компаса",
+ "label": "Север вверху компаса"
+ },
+ "displayMode": {
+ "description": "Вариант компоновки экрана",
+ "label": "Режим отображения"
+ },
+ "displayUnits": {
+ "description": "Отображение метрических или имперских единиц",
+ "label": "Единицы измерения"
+ },
+ "flipScreen": {
+ "description": "Повернуть экран на 180 градусов",
+ "label": "Перевернуть экран"
+ },
+ "gpsDisplayUnits": {
+ "description": "Формат отображения координат",
+ "label": "Единицы отображения GPS"
+ },
+ "oledType": {
+ "description": "Тип OLED-экрана, подключённого к устройству",
+ "label": "Тип OLED"
+ },
+ "screenTimeout": {
+ "description": "Выключать дисплей через указанное время",
+ "label": "Таймаут экрана"
+ },
+ "twelveHourClock": {
+ "description": "Использовать 12-часовой формат времени",
+ "label": "12-часовой формат"
+ },
+ "wakeOnTapOrMotion": {
+ "description": "Пробуждать устройство по касанию или движению",
+ "label": "Пробуждение по касанию или движению"
+ }
+ },
+ "lora": {
+ "title": "Настройки сети LoRa",
+ "description": "Параметры сети LoRa",
+ "bandwidth": {
+ "description": "Полоса пропускания канала в МГц",
+ "label": "Полоса пропускания"
+ },
+ "boostedRxGain": {
+ "description": "Усиленное усиление приёма",
+ "label": "Усиленное усиление RX"
+ },
+ "codingRate": {
+ "description": "Знаменатель кодовой скорости",
+ "label": "Кодовая скорость"
+ },
+ "frequencyOffset": {
+ "description": "Смещение частоты для коррекции ошибок калибровки кварца",
+ "label": "Смещение частоты"
+ },
+ "frequencySlot": {
+ "description": "Номер частотного канала LoRa",
+ "label": "Частотный слот"
+ },
+ "hopLimit": {
+ "description": "Максимальное количество переприёмов",
+ "label": "Лимит переприёмов"
+ },
+ "ignoreMqtt": {
+ "description": "Не передавать MQTT-сообщения по сети",
+ "label": "Игнорировать MQTT"
+ },
+ "modemPreset": {
+ "description": "Используемый пресет модема",
+ "label": "Пресет модема"
+ },
+ "okToMqtt": {
+ "description": "Если установлено \"true\", это означает, что пользователь разрешает загрузку пакетов в MQTT. Если \"false\", удалённые узлы не должны передавать пакеты в MQTT",
+ "label": "Разрешить MQTT"
+ },
+ "overrideDutyCycle": {
+ "description": "Переопределение рабочего цикла",
+ "label": "Переопределить рабочий цикл"
+ },
+ "overrideFrequency": {
+ "description": "Переопределение частоты",
+ "label": "Переопределить частоту"
+ },
+ "region": {
+ "description": "Регион для вашего узла",
+ "label": "Регион"
+ },
+ "spreadingFactor": {
+ "description": "Количество чирпов на символ",
+ "label": "Коэффициент расширения"
+ },
+ "transmitEnabled": {
+ "description": "Включить/отключить передачу (TX) по радио LoRa",
+ "label": "Передача включена"
+ },
+ "transmitPower": {
+ "description": "Максимальная мощность передачи",
+ "label": "Мощность передачи"
+ },
+ "usePreset": {
+ "description": "Использовать один из заранее определённых пресетов модема",
+ "label": "Использовать пресет"
+ },
+ "meshSettings": {
+ "description": "Параметры сети LoRa",
+ "label": "Настройки сети"
+ },
+ "waveformSettings": {
+ "description": "Параметры формы сигнала LoRa",
+ "label": "Настройки формы сигнала"
+ },
+ "radioSettings": {
+ "label": "Настройки радио",
+ "description": "Параметры радио LoRa"
+ }
+ },
+ "network": {
+ "title": "Конфигурация Wi-Fi",
+ "description": "Настройки радиомодуля Wi-Fi",
+ "note": "Примечание: Некоторые устройства (ESP32) не могут использовать Bluetooth и Wi-Fi одновременно.",
+ "addressMode": {
+ "description": "Выбор способа назначения адреса",
+ "label": "Режим адресации"
+ },
+ "dns": {
+ "description": "DNS-сервер",
+ "label": "DNS"
+ },
+ "ethernetEnabled": {
+ "description": "Включить или отключить Ethernet-порт",
+ "label": "Включено"
+ },
+ "gateway": {
+ "description": "Основной шлюз",
+ "label": "Шлюз"
+ },
+ "ip": {
+ "description": "IP-адрес",
+ "label": "IP"
+ },
+ "psk": {
+ "description": "Пароль сети",
+ "label": "Пароль"
+ },
+ "ssid": {
+ "description": "Имя сети",
+ "label": "SSID"
+ },
+ "subnet": {
+ "description": "Маска подсети",
+ "label": "Подсеть"
+ },
+ "wifiEnabled": {
+ "description": "Включить или отключить радиомодуль Wi-Fi",
+ "label": "Включено"
+ },
+ "meshViaUdp": {
+ "label": "Сеть через UDP"
+ },
+ "ntpServer": {
+ "label": "NTP-сервер"
+ },
+ "rsyslogServer": {
+ "label": "Rsyslog-сервер"
+ },
+ "ethernetConfigSettings": {
+ "description": "Настройки Ethernet-порта",
+ "label": "Конфигурация Ethernet"
+ },
+ "ipConfigSettings": {
+ "description": "Конфигурация IP",
+ "label": "Конфигурация IP"
+ },
+ "ntpConfigSettings": {
+ "description": "Конфигурация NTP",
+ "label": "Конфигурация NTP"
+ },
+ "rsyslogConfigSettings": {
+ "description": "Конфигурация Rsyslog",
+ "label": "Конфигурация Rsyslog"
+ },
+ "udpConfigSettings": {
+ "description": "Конфигурация UDP через сеть",
+ "label": "Конфигурация UDP"
+ }
+ },
+ "position": {
+ "title": "Настройки позиции",
+ "description": "Параметры модуля позиции",
+ "broadcastInterval": {
+ "description": "Как часто ваша позиция отправляется по сети",
+ "label": "Интервал трансляции"
+ },
+ "enablePin": {
+ "description": "Переопределение контакта включения GPS-модуля",
+ "label": "Контакт включения"
+ },
+ "fixedPosition": {
+ "description": "Не передавать данные GPS, а использовать заданную вручную позицию",
+ "label": "Фиксированная позиция"
+ },
+ "gpsMode": {
+ "description": "Настройка режима GPS: включён, отключён или отсутствует",
+ "label": "Режим GPS"
+ },
+ "gpsUpdateInterval": {
+ "description": "Как часто обновлять данные GPS",
+ "label": "Интервал обновления GPS"
+ },
+ "positionFlags": {
+ "description": "Дополнительные поля для включения в сообщения о позиции. Чем больше полей выбрано, тем больше размер сообщения, что увеличивает время передачи и риск потери пакетов.",
+ "label": "Флаги позиции"
+ },
+ "receivePin": {
+ "description": "Переопределение контакта RX GPS-модуля",
+ "label": "Контакт приёма"
+ },
+ "smartPositionEnabled": {
+ "description": "Отправлять позицию только при значительном изменении местоположения",
+ "label": "Умная позиция"
+ },
+ "smartPositionMinDistance": {
+ "description": "Минимальное расстояние (в метрах), которое должно быть пройдено перед отправкой обновления позиции",
+ "label": "Минимальное расстояние"
+ },
+ "smartPositionMinInterval": {
+ "description": "Минимальный интервал (в секундах), который должен пройти перед отправкой обновления позиции",
+ "label": "Минимальный интервал"
+ },
+ "transmitPin": {
+ "description": "Переопределение контакта TX GPS-модуля",
+ "label": "Контакт передачи"
+ },
+ "intervalsSettings": {
+ "description": "Как часто отправлять обновления позиции",
+ "label": "Интервалы"
+ },
+ "flags": {
+ "placeholder": "Выберите флаги позиции...",
+ "altitude": "Высота",
+ "altitudeGeoidalSeparation": "Разделение высоты по геоиду",
+ "altitudeMsl": "Высота над уровнем моря",
+ "dop": "Показатель точности (DOP), по умолчанию PDOP",
+ "hdopVdop": "Если выбран DOP, использовать значения HDOP/VDOP вместо PDOP",
+ "numSatellites": "Количество спутников",
+ "sequenceNumber": "Порядковый номер",
+ "timestamp": "Временная метка",
+ "unset": "Сбросить",
+ "vehicleHeading": "Направление движения",
+ "vehicleSpeed": "Скорость движения"
+ }
+ },
+ "power": {
+ "adcMultiplierOverride": {
+ "description": "Используется для корректировки показаний напряжения батареи",
+ "label": "Коэффициент пересчёта АЦП"
+ },
+ "ina219Address": {
+ "description": "Адрес монитора батареи INA219",
+ "label": "Адрес INA219"
+ },
+ "lightSleepDuration": {
+ "description": "Продолжительность лёгкого сна устройства",
+ "label": "Длительность лёгкого сна"
+ },
+ "minimumWakeTime": {
+ "description": "Минимальное время бодрствования устройства после получения пакета",
+ "label": "Минимальное время бодрствования"
+ },
+ "noConnectionBluetoothDisabled": {
+ "description": "Если устройство не получает подключение по Bluetooth, модуль BLE будет отключён через указанное время",
+ "label": "Отключить Bluetooth при отсутствии подключения"
+ },
+ "powerSavingEnabled": {
+ "description": "Выберите, если устройство питается от источника с низким током (например, солнечная батарея), чтобы минимизировать потребление энергии.",
+ "label": "Включить режим энергосбережения"
+ },
+ "shutdownOnBatteryDelay": {
+ "description": "Автоматическое выключение узла через указанное время при питании от батареи, 0 — без ограничения",
+ "label": "Задержка выключения на батарее"
+ },
+ "superDeepSleepDuration": {
+ "description": "Продолжительность сверхглубокого сна устройства",
+ "label": "Длительность сверхглубокого сна"
+ },
+ "powerConfigSettings": {
+ "description": "Параметры модуля питания",
+ "label": "Конфигурация питания"
+ },
+ "sleepSettings": {
+ "description": "Настройки сна для модуля питания",
+ "label": "Настройки сна"
+ }
+ },
+ "security": {
+ "description": "Параметры конфигурации безопасности",
+ "title": "Настройки безопасности",
+ "button_backupKey": "Резервное копирование ключа",
+ "adminChannelEnabled": {
+ "description": "Разрешить входящее управление устройством через небезопасный устаревший административный канал",
+ "label": "Разрешить устаревший административный канал"
+ },
+ "enableDebugLogApi": {
+ "description": "Выводить живой отладочный лог через последовательный порт, просматривать и экспортировать логи устройства (без данных о позиции) через Bluetooth",
+ "label": "Включить API отладочного лога"
+ },
+ "managed": {
+ "description": "Если включено, параметры конфигурации устройства могут изменяться только удалённо узлом Remote Admin через административные сообщения. Не включайте эту опцию, если не настроен хотя бы один подходящий узел Remote Admin и его публичный ключ не сохранён в одном из полей выше.",
+ "label": "Управляемый режим"
+ },
+ "privateKey": {
+ "description": "Используется для создания общего ключа с удалённым устройством",
+ "label": "Закрытый ключ"
+ },
+ "publicKey": {
+ "description": "Отправляется другим узлам сети для вычисления общего секретного ключа",
+ "label": "Открытый ключ"
+ },
+ "primaryAdminKey": {
+ "description": "Основной публичный ключ, авторизованный для отправки административных сообщений на этот узел",
+ "label": "Основной административный ключ"
+ },
+ "secondaryAdminKey": {
+ "description": "Вторичный публичный ключ, авторизованный для отправки административных сообщений на этот узел",
+ "label": "Вторичный административный ключ"
+ },
+ "serialOutputEnabled": {
+ "description": "Серийная консоль через Stream API",
+ "label": "Вывод через последовательный порт"
+ },
+ "tertiaryAdminKey": {
+ "description": "Третичный публичный ключ, авторизованный для отправки административных сообщений на этот узел",
+ "label": "Третичный административный ключ"
+ },
+ "adminSettings": {
+ "description": "Параметры администратора",
+ "label": "Настройки администратора"
+ },
+ "loggingSettings": {
+ "description": "Параметры ведения лога",
+ "label": "Настройки лога"
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/web/public/i18n/locales/ru-RU/dialog.json b/packages/web/public/i18n/locales/ru-RU/dialog.json
new file mode 100644
index 00000000..54db3500
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/dialog.json
@@ -0,0 +1,221 @@
+{
+ "deleteMessages": {
+ "description": "Это действие удалит всю историю сообщений. Отменить это будет невозможно. Вы уверены, что хотите продолжить?",
+ "title": "Очистить все сообщения"
+ },
+ "deviceName": {
+ "description": "Устройство перезагрузится после сохранения настроек.",
+ "longName": "Длинное имя",
+ "shortName": "Короткое имя",
+ "title": "Изменить имя устройства",
+ "validation": {
+ "longNameMax": "Длинное имя не должно превышать 40 символов",
+ "shortNameMax": "Короткое имя не должно превышать 4 символов",
+ "longNameMin": "Длинное имя должно содержать хотя бы 1 символ",
+ "shortNameMin": "Короткое имя должно содержать хотя бы 1 символ"
+ }
+ },
+ "import": {
+ "description": "Импортировать набор каналов из Meshtastic URL.
Допустимые URL Meshtastic начинаются с \"https://meshtastic.org/e/...\"",
+ "error": {
+ "invalidUrl": "Некорректный Meshtastic URL"
+ },
+ "channelPrefix": "Канал ",
+ "primary": "Основной ",
+ "doNotImport": "Не импортировать",
+ "channelName": "Имя",
+ "channelSlot": "Слот",
+ "channelSetUrl": "URL набора каналов/QR-кода",
+ "useLoraConfig": "Импортировать конфигурацию LoRa",
+ "presetDescription": "Текущая конфигурация LoRa будет заменена.",
+ "title": "Импорт каналов"
+ },
+ "locationResponse": {
+ "title": "Местоположение: {{identifier}}",
+ "altitude": "Высота: ",
+ "coordinates": "Координаты: ",
+ "noCoordinates": "Координаты отсутствуют"
+ },
+ "pkiRegenerateDialog": {
+ "title": "Перегенерировать предварительно разделённый ключ?",
+ "description": "Вы уверены, что хотите перегенерировать предварительно разделённый ключ?",
+ "regenerate": "Перегенерировать"
+ },
+ "newDeviceDialog": {
+ "title": "Подключить новое устройство",
+ "https": "https",
+ "http": "http",
+ "tabHttp": "HTTP",
+ "tabBluetooth": "Bluetooth",
+ "tabSerial": "Последовательный порт",
+ "useHttps": "Использовать HTTPS",
+ "connecting": "Подключение...",
+ "connect": "Подключиться",
+ "connectionFailedAlert": {
+ "title": "Ошибка подключения",
+ "descriptionPrefix": "Не удалось подключиться к устройству. ",
+ "httpsHint": "При использовании HTTPS может потребоваться принять самоподписанный сертификат. ",
+ "openLinkPrefix": "Пожалуйста, откройте ",
+ "openLinkSuffix": " в новой вкладке",
+ "acceptTlsWarningSuffix": ", примите все предупреждения TLS, если они появятся, затем повторите попытку",
+ "learnMoreLink": "Узнать больше"
+ },
+ "httpConnection": {
+ "label": "IP-адрес/Имя хоста",
+ "placeholder": "000.000.000.000 / meshtastic.local"
+ },
+ "serialConnection": {
+ "noDevicesPaired": "Ещё нет сопряжённых устройств.",
+ "newDeviceButton": "Новое устройство",
+ "deviceIdentifier": "# {{index}} - {{vendorId}} - {{productId}}"
+ },
+ "bluetoothConnection": {
+ "noDevicesPaired": "Ещё нет сопряжённых устройств.",
+ "newDeviceButton": "Новое устройство",
+ "connectionFailed": "Ошибка подключения",
+ "deviceDisconnected": "Устройство отключено",
+ "unknownDevice": "Неизвестное устройство",
+ "errorLoadingDevices": "Ошибка загрузки устройств",
+ "unknownErrorLoadingDevices": "Неизвестная ошибка загрузки устройств"
+ },
+ "validation": {
+ "requiresWebBluetooth": "Этот тип подключения требует <0>Web Bluetooth0>. Пожалуйста, используйте поддерживаемый браузер, например Chrome или Edge.",
+ "requiresWebSerial": "Этот тип подключения требует <0>Web Serial0>. Пожалуйста, используйте поддерживаемый браузер, например Chrome или Edge.",
+ "requiresSecureContext": "Это приложение требует <0>защищённого контекста0>. Пожалуйста, подключайтесь по HTTPS или через localhost.",
+ "additionallyRequiresSecureContext": "Кроме того, требуется <0>защищённый контекст0>. Пожалуйста, подключайтесь по HTTPS или через localhost."
+ }
+ },
+ "nodeDetails": {
+ "message": "Сообщение",
+ "requestPosition": "Запросить местоположение",
+ "traceRoute": "Трассировка маршрута",
+ "airTxUtilization": "Использование эфира (TX)",
+ "allRawMetrics": "Все исходные метрики:",
+ "batteryLevel": "Уровень заряда батареи",
+ "channelUtilization": "Использование канала",
+ "details": "Детали:",
+ "deviceMetrics": "Метрики устройства:",
+ "hardware": "Оборудование: ",
+ "lastHeard": "Последний сигнал: ",
+ "nodeHexPrefix": "Hex-адрес узла: ",
+ "nodeNumber": "Номер узла: ",
+ "position": "Позиция:",
+ "role": "Роль: ",
+ "uptime": "Время работы: ",
+ "voltage": "Напряжение",
+ "title": "Детали узла для {{identifier}}",
+ "ignoreNode": "Игнорировать узел",
+ "removeNode": "Удалить узел",
+ "unignoreNode": "Перестать игнорировать узел",
+ "security": "Безопасность:",
+ "publicKey": "Публичный ключ: ",
+ "messageable": "Доступен для сообщений: ",
+ "KeyManuallyVerifiedTrue": "Публичный ключ подтверждён вручную",
+ "KeyManuallyVerifiedFalse": "Публичный ключ не подтверждён вручную"
+ },
+ "pkiBackup": {
+ "loseKeysWarning": "Если вы потеряете ключи, вам потребуется сбросить устройство.",
+ "secureBackup": "Важно сделать резервную копию ваших публичных и приватных ключей и хранить её в безопасности!",
+ "footer": "=== КОНЕЦ КЛЮЧЕЙ ===",
+ "header": "=== КЛЮЧИ MESHTASTIC ДЛЯ {{longName}} ({{shortName}}) ===",
+ "privateKey": "Приватный ключ:",
+ "publicKey": "Публичный ключ:",
+ "fileName": "meshtastic_keys_{{longName}}_{{shortName}}.txt",
+ "title": "Резервное копирование ключей"
+ },
+ "pkiBackupReminder": {
+ "description": "Рекомендуем регулярно делать резервную копию ключей. Хотите сделать резервную копию сейчас?",
+ "title": "Напоминание о резервном копировании",
+ "remindLaterPrefix": "Напомнить через",
+ "remindNever": "Больше не напоминать",
+ "backupNow": "Сделать резервную копию сейчас"
+ },
+ "pkiRegenerate": {
+ "description": "Вы уверены, что хотите перегенерировать пару ключей?",
+ "title": "Перегенерировать пару ключей"
+ },
+ "qr": {
+ "addChannels": "Добавить каналы",
+ "replaceChannels": "Заменить каналы",
+ "description": "Текущая конфигурация LoRa также будет включена.",
+ "sharableUrl": "Ссылка для совместного доступа",
+ "title": "Сгенерировать QR-код"
+ },
+ "reboot": {
+ "title": "Перезагрузка устройства",
+ "description": "Перезагрузить устройство сейчас или запланировать перезагрузку подключённого узла. При желании можно перезагрузиться в режим OTA (по воздуху).",
+ "ota": "Перезагрузиться в режим OTA",
+ "enterDelay": "Введите задержку",
+ "scheduled": "Перезагрузка запланирована",
+ "schedule": "Запланировать перезагрузку",
+ "now": "Перезагрузить сейчас",
+ "cancel": "Отменить запланированную перезагрузку"
+ },
+ "refreshKeys": {
+ "description": {
+ "acceptNewKeys": "Это удалит узел с устройства и запросит новые ключи.",
+ "keyMismatchReasonSuffix": ". Это связано с тем, что текущий публичный ключ удалённого узла не совпадает с ранее сохранённым ключом для этого узла.",
+ "unableToSendDmPrefix": "Ваш узел не может отправить прямое сообщение узлу: "
+ },
+ "acceptNewKeys": "Принять новые ключи",
+ "title": "Несовпадение ключей — {{identifier}}"
+ },
+ "removeNode": {
+ "description": "Вы уверены, что хотите удалить этот узел?",
+ "title": "Удалить узел?"
+ },
+ "shutdown": {
+ "title": "Запланировать выключение",
+ "description": "Выключить подключённый узел через X минут."
+ },
+ "traceRoute": {
+ "routeToDestination": "Маршрут до места назначения:",
+ "routeBack": "Обратный маршрут:"
+ },
+ "tracerouteResponse": {
+ "title": "Трассировка: {{identifier}}"
+ },
+ "unsafeRoles": {
+ "confirmUnderstanding": "Да, я знаю, что делаю",
+ "conjunction": " и статью в блоге о ",
+ "postamble": " и понимаю последствия изменения роли.",
+ "preamble": "Я прочитал ",
+ "choosingRightDeviceRole": "Выбор правильной роли устройства",
+ "deviceRoleDocumentation": "Документация по ролям устройств",
+ "title": "Вы уверены?"
+ },
+ "managedMode": {
+ "confirmUnderstanding": "Да, я знаю, что делаю",
+ "title": "Вы уверены?",
+ "description": "Включение управляемого режима блокирует клиентским приложениям (включая веб-клиент) возможность записывать конфигурации в радио. После включения конфигурации радио можно изменять только через сообщения удалённого администратора. Эта настройка не обязательна для удалённого администрирования узлов."
+ },
+ "clientNotification": {
+ "title": "Уведомление клиента",
+ "TraceRoute can only be sent once every 30 seconds": "TraceRoute можно отправлять не чаще одного раза в 30 секунд",
+ "Compromised keys were detected and regenerated.": "Обнаружены скомпрометированные ключи и они были перегенерарованы."
+ },
+ "resetNodeDb": {
+ "title": "Сбросить базу данных узлов",
+ "description": "Это удалит все узлы из базы данных подключённого устройства и очистит всю историю сообщений в клиенте. Это действие нельзя отменить. Вы уверены, что хотите продолжить?",
+ "confirm": "Сбросить базу данных узлов",
+ "failedTitle": "Произошла ошибка при сбросе базы данных узлов. Пожалуйста, попробуйте ещё раз."
+ },
+ "clearAllStores": {
+ "title": "Очистить всё локальное хранилище",
+ "description": "Это удалит все локально сохранённые данные, включая историю сообщений и базы данных узлов для всех ранее подключённых устройств. После этого вам потребуется заново подключиться к узлу. Это действие нельзя отменить. Вы уверены, что хотите продолжить?",
+ "confirm": "Очистить всё локальное хранилище",
+ "failedTitle": "Произошла ошибка при очистке локального хранилища. Пожалуйста, попробуйте ещё раз."
+ },
+ "factoryResetDevice": {
+ "title": "Сброс устройства к заводским настройкам",
+ "description": "Это выполнит сброс подключённого устройства к заводским настройкам, удалив все конфигурации и данные на устройстве, а также все узлы и сообщения, сохранённые в клиенте. Это действие нельзя отменить. Вы уверены, что хотите продолжить?",
+ "confirm": "Сбросить устройство к заводским настройкам",
+ "failedTitle": "Произошла ошибка при сбросе устройства. Пожалуйста, попробуйте ещё раз."
+ },
+ "factoryResetConfig": {
+ "title": "Сброс конфигурации к заводским настройкам",
+ "description": "Это выполнит сброс конфигурации подключённого устройства к заводским настройкам, удалив все конфигурации на устройстве. Это действие нельзя отменить. Вы уверены, что хотите продолжить?",
+ "confirm": "Сбросить конфигурацию к заводским настройкам",
+ "failedTitle": "Произошла ошибка при сбросе конфигурации. Пожалуйста, попробуйте ещё раз."
+ }
+}
diff --git a/packages/web/public/i18n/locales/ru-RU/map.json b/packages/web/public/i18n/locales/ru-RU/map.json
new file mode 100644
index 00000000..3d699819
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/map.json
@@ -0,0 +1,38 @@
+{
+ "maplibre": {
+ "GeolocateControl.FindMyLocation": "Найти моё местоположение",
+ "NavigationControl.ZoomIn": "Увеличить",
+ "NavigationControl.ZoomOut": "Уменьшить",
+ "CooperativeGesturesHandler.WindowsHelpText": "Используйте Ctrl + прокрутку для масштабирования карты",
+ "CooperativeGesturesHandler.MacHelpText": "Используйте ⌘ + прокрутку для масштабирования карты",
+ "CooperativeGesturesHandler.MobileHelpText": "Используйте два пальца для перемещения по карте"
+ },
+ "layerTool": {
+ "nodeMarkers": "Показать узлы",
+ "directNeighbors": "Показать прямые соединения",
+ "remoteNeighbors": "Показать удалённые соединения",
+ "positionPrecision": "Показать точность позиции",
+ "traceroutes": "Показать маршруты",
+ "waypoints": "Показать точки маршрута"
+ },
+ "mapMenu": {
+ "locateAria": "Найти мой узел",
+ "layersAria": "Изменить стиль карты"
+ },
+ "waypointDetail": {
+ "edit": "Редактировать",
+ "description": "Описание:",
+ "createdBy": "Отредактировано:",
+ "createdDate": "Создано:",
+ "updated": "Обновлено:",
+ "expires": "Истекает:",
+ "distance": "Расстояние:",
+ "bearing": "Абсолютный азимут:",
+ "lockedTo": "Заблокировано:",
+ "latitude": "Широта:",
+ "longitude": "Долгота:"
+ },
+ "myNode": {
+ "tooltip": "Это устройство"
+ }
+}
diff --git a/packages/web/public/i18n/locales/ru-RU/messages.json b/packages/web/public/i18n/locales/ru-RU/messages.json
new file mode 100644
index 00000000..b053dd3b
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/messages.json
@@ -0,0 +1,39 @@
+{
+ "page": {
+ "title": "Сообщения: {{chatName}}",
+ "placeholder": "Введите сообщение"
+ },
+ "emptyState": {
+ "title": "Выберите чат",
+ "text": "Сообщений пока нет."
+ },
+ "selectChatPrompt": {
+ "text": "Выберите канал или узел, чтобы начать обмен сообщениями."
+ },
+ "sendMessage": {
+ "placeholder": "Введите ваше сообщение здесь...",
+ "sendButton": "Отправить"
+ },
+ "actionsMenu": {
+ "addReactionLabel": "Добавить реакцию",
+ "replyLabel": "Ответить"
+ },
+ "deliveryStatus": {
+ "delivered": {
+ "label": "Сообщение доставлено",
+ "displayText": "Доставлено"
+ },
+ "failed": {
+ "label": "Не удалось доставить сообщение",
+ "displayText": "Доставка не удалась"
+ },
+ "unknown": {
+ "label": "Статус сообщения неизвестен",
+ "displayText": "Неизвестный статус"
+ },
+ "waiting": {
+ "label": "Отправка сообщения",
+ "displayText": "Ожидание доставки"
+ }
+ }
+}
diff --git a/packages/web/public/i18n/locales/ru-RU/moduleConfig.json b/packages/web/public/i18n/locales/ru-RU/moduleConfig.json
new file mode 100644
index 00000000..9ad42b52
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/moduleConfig.json
@@ -0,0 +1,448 @@
+{
+ "page": {
+ "tabAmbientLighting": "Подсветка",
+ "tabAudio": "Аудио",
+ "tabCannedMessage": "Шаблоны",
+ "tabDetectionSensor": "Датчик обнаружения",
+ "tabExternalNotification": "Внешние уведомления",
+ "tabMqtt": "MQTT",
+ "tabNeighborInfo": "Информация о соседах",
+ "tabPaxcounter": "Счётчик людей",
+ "tabRangeTest": "Тест дальности",
+ "tabSerial": "Последовательный порт",
+ "tabStoreAndForward": "Хранение и пересылка",
+ "tabTelemetry": "Телеметрия"
+ },
+ "ambientLighting": {
+ "title": "Настройки подсветки",
+ "description": "Настройки модуля подсветки",
+ "ledState": {
+ "label": "Состояние LED",
+ "description": "Включить или выключить LED"
+ },
+ "current": {
+ "label": "Ток",
+ "description": "Устанавливает ток для выхода LED. По умолчанию 10"
+ },
+ "red": {
+ "label": "Красный",
+ "description": "Уровень красного LED. Значения от 0 до 255"
+ },
+ "green": {
+ "label": "Зелёный",
+ "description": "Уровень зелёного LED. Значения от 0 до 255"
+ },
+ "blue": {
+ "label": "Синий",
+ "description": "Уровень синего LED. Значения от 0 до 255"
+ }
+ },
+ "audio": {
+ "title": "Настройки аудио",
+ "description": "Настройки модуля аудио",
+ "codec2Enabled": {
+ "label": "Codec 2 включён",
+ "description": "Включить кодирование аудио Codec 2"
+ },
+ "pttPin": {
+ "label": "PTT контакт",
+ "description": "Контакт GPIO для PTT"
+ },
+ "bitrate": {
+ "label": "Битрейт",
+ "description": "Битрейт для кодирования аудио"
+ },
+ "i2sWs": {
+ "label": "i2S WS",
+ "description": "Контакт GPIO для i2S WS"
+ },
+ "i2sSd": {
+ "label": "i2S SD",
+ "description": "Контакт GPIO для i2S SD"
+ },
+ "i2sDin": {
+ "label": "i2S DIN",
+ "description": "Контакт GPIO для i2S DIN"
+ },
+ "i2sSck": {
+ "label": "i2S SCK",
+ "description": "Контакт GPIO для i2S SCK"
+ }
+ },
+ "cannedMessage": {
+ "title": "Настройки шаблонов сообщений",
+ "description": "Настройки модуля шаблонов сообщений",
+ "moduleEnabled": {
+ "label": "Модуль включён",
+ "description": "Включить шаблоны сообщений"
+ },
+ "rotary1Enabled": {
+ "label": "Энкодер #1 включён",
+ "description": "Включить поворотный энкодер"
+ },
+ "inputbrokerPinA": {
+ "label": "Контакт энкодера A",
+ "description": "Значение контакта GPIO (1-39) для порта A энкодера"
+ },
+ "inputbrokerPinB": {
+ "label": "Контакт энкодера B",
+ "description": "Значение контакта GPIO (1-39) для порта B энкодера"
+ },
+ "inputbrokerPinPress": {
+ "label": "Контакт нажатия энкодера",
+ "description": "Значение контакта GPIO (1-39) для нажатия энкодера"
+ },
+ "inputbrokerEventCw": {
+ "label": "Событие по часовой",
+ "description": "Выберите событие."
+ },
+ "inputbrokerEventCcw": {
+ "label": "Событие против часовой",
+ "description": "Выберите событие."
+ },
+ "inputbrokerEventPress": {
+ "label": "Событие нажатия",
+ "description": "Выберите событие"
+ },
+ "updown1Enabled": {
+ "label": "Вверх/вниз включено",
+ "description": "Включить энкодер вверх/вниз"
+ },
+ "allowInputSource": {
+ "label": "Разрешить источник ввода",
+ "description": "Выберите из: '_any', 'rotEnc1', 'upDownEnc1', 'cardkb'"
+ },
+ "sendBell": {
+ "label": "Отправлять звонок",
+ "description": "Отправлять символ звонка с каждым сообщением"
+ }
+ },
+ "detectionSensor": {
+ "title": "Настройки датчика обнаружения",
+ "description": "Настройки модуля датчика обнаружения",
+ "enabled": {
+ "label": "Включено",
+ "description": "Включить или отключить модуль датчика обнаружения"
+ },
+ "minimumBroadcastSecs": {
+ "label": "Минимальный интервал передачи (сек)",
+ "description": "Интервал в секундах, как часто отправлять сообщение в сеть при обнаружении изменения состояния"
+ },
+ "stateBroadcastSecs": {
+ "label": "Интервал передачи состояния (сек)",
+ "description": "Интервал в секундах, как часто отправлять сообщение в сеть с текущим состоянием, независимо от изменений"
+ },
+ "sendBell": {
+ "label": "Отправлять звонок",
+ "description": "Отправлять ASCII-звонок с сообщением об оповещении"
+ },
+ "name": {
+ "label": "Дружественное имя",
+ "description": "Используется для форматирования сообщения, отправляемого в сеть, максимум 20 символов"
+ },
+ "monitorPin": {
+ "label": "Контакт мониторинга",
+ "description": "Контакт GPIO для мониторинга изменений состояния"
+ },
+ "detectionTriggerType": {
+ "label": "Тип триггера обнаружения",
+ "description": "Тип события триггера, который будет использоваться"
+ },
+ "usePullup": {
+ "label": "Использовать подтяжку",
+ "description": "Использовать режим INPUT_PULLUP для контакта GPIO"
+ }
+ },
+ "externalNotification": {
+ "title": "Настройки внешних уведомлений",
+ "description": "Настройка модуля внешних уведомлений",
+ "enabled": {
+ "label": "Модуль включён",
+ "description": "Включить внешние уведомления"
+ },
+ "outputMs": {
+ "label": "Длительность выхода (мс)",
+ "description": "Длительность выхода в миллисекундах"
+ },
+ "output": {
+ "label": "Выход",
+ "description": "Выход"
+ },
+ "outputVibra": {
+ "label": "Вибрационный выход",
+ "description": "Вибрационный выход"
+ },
+ "outputBuzzer": {
+ "label": "Звуковой выход",
+ "description": "Звуковой выход"
+ },
+ "active": {
+ "label": "Активен",
+ "description": "Активен"
+ },
+ "alertMessage": {
+ "label": "Сообщение оповещения",
+ "description": "Сообщение оповещения"
+ },
+ "alertMessageVibra": {
+ "label": "Вибрационное оповещение",
+ "description": "Вибрационное оповещение"
+ },
+ "alertMessageBuzzer": {
+ "label": "Звуковое оповещение",
+ "description": "Звуковое оповещение"
+ },
+ "alertBell": {
+ "label": "Оповещение звонком",
+ "description": "Следует ли срабатывать оповещение при получении входящего звонка?"
+ },
+ "alertBellVibra": {
+ "label": "Вибрация при звонке",
+ "description": "Вибрация при звонке"
+ },
+ "alertBellBuzzer": {
+ "label": "Звук при звонке",
+ "description": "Звук при звонке"
+ },
+ "usePwm": {
+ "label": "Использовать ШИМ",
+ "description": "Использовать ШИМ"
+ },
+ "nagTimeout": {
+ "label": "Таймаут напоминания",
+ "description": "Таймаут напоминания"
+ },
+ "useI2sAsBuzzer": {
+ "label": "Использовать I²S как зуммер",
+ "description": "Назначить контакт I²S как выход зуммера"
+ }
+ },
+ "mqtt": {
+ "title": "Настройки MQTT",
+ "description": "Настройки модуля MQTT",
+ "enabled": {
+ "label": "Включено",
+ "description": "Включить или отключить MQTT"
+ },
+ "address": {
+ "label": "Адрес сервера MQTT",
+ "description": "Адрес сервера MQTT для стандартных/пользовательских серверов"
+ },
+ "username": {
+ "label": "Имя пользователя MQTT",
+ "description": "Имя пользователя MQTT для стандартных/пользовательских серверов"
+ },
+ "password": {
+ "label": "Пароль MQTT",
+ "description": "Пароль MQTT для стандартных/пользовательских серверов"
+ },
+ "encryptionEnabled": {
+ "label": "Шифрование включено",
+ "description": "Включить или отключить шифрование MQTT. Примечание: все сообщения отправляются на брокер MQTT незашифрованными, если эта опция не включена, даже если ваши каналы связи зашифрованы. Это включает данные о местоположении."
+ },
+ "jsonEnabled": {
+ "label": "JSON включён",
+ "description": "Отправлять/принимать JSON-пакеты по MQTT"
+ },
+ "tlsEnabled": {
+ "label": "TLS включён",
+ "description": "Включить или отключить TLS"
+ },
+ "root": {
+ "label": "Корневой топик",
+ "description": "Корневой топик MQTT для стандартных/пользовательских серверов"
+ },
+ "proxyToClientEnabled": {
+ "label": "Прокси MQTT для клиента включено",
+ "description": "Использует сетевое подключение для проксирования сообщений MQTT клиенту."
+ },
+ "mapReportingEnabled": {
+ "label": "Отправка данных на карту включена",
+ "description": "Ваш узел будет периодически отправлять незашифрованный пакет отчёта на карту на настроенный сервер MQTT, это включает ID, короткое и длинное имя, приблизительное местоположение, модель оборудования, роль, версию прошивки, регион LoRa, пресет модема и имя основного канала."
+ },
+ "mapReportSettings": {
+ "publishIntervalSecs": {
+ "label": "Интервал публикации отчёта (сек)",
+ "description": "Интервал в секундах для публикации отчётов на карте"
+ },
+ "positionPrecision": {
+ "label": "Приблизительное местоположение",
+ "description": "Разделяемая позиция будет точной в пределах этого расстояния",
+ "options": {
+ "metric_km23": "В пределах 23 км",
+ "metric_km12": "В пределах 12 км",
+ "metric_km5_8": "В пределах 5,8 км",
+ "metric_km2_9": "В пределах 2,9 км",
+ "metric_km1_5": "В пределах 1,5 км",
+ "metric_m700": "В пределах 700 м",
+ "metric_m350": "В пределах 350 м",
+ "metric_m200": "В пределах 200 м",
+ "metric_m90": "В пределах 90 м",
+ "metric_m50": "В пределах 50 м",
+ "imperial_mi15": "В пределах 15 миль",
+ "imperial_mi7_3": "В пределах 7,3 миль",
+ "imperial_mi3_6": "В пределах 3,6 миль",
+ "imperial_mi1_8": "В пределах 1,8 миль",
+ "imperial_mi0_9": "В пределах 0,9 миль",
+ "imperial_mi0_5": "В пределах 0,5 миль",
+ "imperial_mi0_2": "В пределах 0,2 миль",
+ "imperial_ft600": "В пределах 600 футов",
+ "imperial_ft300": "В пределах 300 футов",
+ "imperial_ft150": "В пределах 150 футов"
+ }
+ }
+ }
+ },
+ "neighborInfo": {
+ "title": "Настройки информации о соседах",
+ "description": "Настройки модуля информации о соседах",
+ "enabled": {
+ "label": "Включено",
+ "description": "Включить или отключить модуль информации о соседах"
+ },
+ "updateInterval": {
+ "label": "Интервал обновления",
+ "description": "Интервал в секундах, как часто пытаться отправлять информацию о соседах в сеть"
+ }
+ },
+ "paxcounter": {
+ "title": "Настройки счётчика людей",
+ "description": "Настройки модуля счётчика людей",
+ "enabled": {
+ "label": "Модуль включён",
+ "description": "Включить счётчик людей"
+ },
+ "paxcounterUpdateInterval": {
+ "label": "Интервал обновления (сек)",
+ "description": "Как долго ждать между отправкой пакетов счётчика"
+ },
+ "wifiThreshold": {
+ "label": "Порог RSSI Wi-Fi",
+ "description": "При каком уровне RSSI Wi-Fi счётчик должен увеличиваться. По умолчанию -80."
+ },
+ "bleThreshold": {
+ "label": "Порог RSSI BLE",
+ "description": "При каком уровне RSSI BLE счётчик должен увеличиваться. По умолчанию -80."
+ }
+ },
+ "rangeTest": {
+ "title": "Настройки теста дальности",
+ "description": "Настройки модуля теста дальности",
+ "enabled": {
+ "label": "Модуль включён",
+ "description": "Включить тест дальности"
+ },
+ "sender": {
+ "label": "Интервал сообщений",
+ "description": "Как долго ждать между отправкой тестовых пакетов"
+ },
+ "save": {
+ "label": "Сохранить CSV в память",
+ "description": "Только для ESP32"
+ }
+ },
+ "serial": {
+ "title": "Настройки последовательного порта",
+ "description": "Настройки модуля последовательного порта",
+ "enabled": {
+ "label": "Модуль включён",
+ "description": "Включить вывод через последовательный порт"
+ },
+ "echo": {
+ "label": "Эхо",
+ "description": "Все отправленные пакеты будут возвращаться обратно на ваше устройство"
+ },
+ "rxd": {
+ "label": "Контакт приёма",
+ "description": "Установите контакт GPIO как RXD."
+ },
+ "txd": {
+ "label": "Контакт передачи",
+ "description": "Установите контакт GPIO как TXD."
+ },
+ "baud": {
+ "label": "Скорость (бод)",
+ "description": "Скорость передачи данных"
+ },
+ "timeout": {
+ "label": "Таймаут",
+ "description": "Время ожидания в секундах, после которого пакет считается завершённым"
+ },
+ "mode": {
+ "label": "Режим",
+ "description": "Выберите режим"
+ },
+ "overrideConsoleSerialPort": {
+ "label": "Переопределить консольный порт",
+ "description": "Если у вас подключён последовательный порт к консоли, это переопределит его."
+ }
+ },
+ "storeForward": {
+ "title": "Настройки хранения и пересылки",
+ "description": "Настройки модуля хранения и пересылки",
+ "enabled": {
+ "label": "Модуль включён",
+ "description": "Включить хранение и пересылку"
+ },
+ "heartbeat": {
+ "label": "Heartbeat включён",
+ "description": "Включить heartbeat для хранения и пересылки"
+ },
+ "records": {
+ "label": "Количество записей",
+ "description": "Количество записей для хранения"
+ },
+ "historyReturnMax": {
+ "label": "Максимум возвращаемых записей",
+ "description": "Максимальное количество возвращаемых записей"
+ },
+ "historyReturnWindow": {
+ "label": "Окно возвращаемых записей",
+ "description": "Максимальное количество возвращаемых записей"
+ }
+ },
+ "telemetry": {
+ "title": "Настройки телеметрии",
+ "description": "Настройки модуля телеметрии",
+ "deviceUpdateInterval": {
+ "label": "Интервал обновления устройства",
+ "description": "Интервал обновления метрик устройства (сек)"
+ },
+ "environmentUpdateInterval": {
+ "label": "Интервал обновления окружающей среды (сек)",
+ "description": ""
+ },
+ "environmentMeasurementEnabled": {
+ "label": "Модуль включён",
+ "description": "Включить телеметрию окружающей среды"
+ },
+ "environmentScreenEnabled": {
+ "label": "Отображать на экране",
+ "description": "Показывать модуль телеметрии на OLED"
+ },
+ "environmentDisplayFahrenheit": {
+ "label": "Отображать в Фаренгейтах",
+ "description": "Отображать температуру в Фаренгейтах"
+ },
+ "airQualityEnabled": {
+ "label": "Качество воздуха включено",
+ "description": "Включить телеметрию качества воздуха"
+ },
+ "airQualityInterval": {
+ "label": "Интервал обновления качества воздуха",
+ "description": "Как часто отправлять данные о качестве воздуха в сеть"
+ },
+ "powerMeasurementEnabled": {
+ "label": "Измерение мощности включено",
+ "description": "Включить телеметрию измерения мощности"
+ },
+ "powerUpdateInterval": {
+ "label": "Интервал обновления мощности",
+ "description": "Как часто отправлять данные о мощности в сеть"
+ },
+ "powerScreenEnabled": {
+ "label": "Экран мощности включён",
+ "description": "Включить экран телеметрии мощности"
+ }
+ }
+}
diff --git a/packages/web/public/i18n/locales/ru-RU/nodes.json b/packages/web/public/i18n/locales/ru-RU/nodes.json
new file mode 100644
index 00000000..3500fae2
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/nodes.json
@@ -0,0 +1,59 @@
+{
+ "nodeDetail": {
+ "publicKeyEnabled": {
+ "label": "Публичный ключ включён"
+ },
+ "noPublicKey": {
+ "label": "Нет публичного ключа"
+ },
+ "directMessage": {
+ "label": "Прямое сообщение {{shortName}}"
+ },
+ "favorite": {
+ "label": "Избранное",
+ "tooltip": "Добавить или убрать этот узел из избранного"
+ },
+ "notFavorite": {
+ "label": "Не в избранном"
+ },
+ "error": {
+ "label": "Ошибка",
+ "text": "Произошла ошибка при получении данных об узле. Пожалуйста, попробуйте позже."
+ },
+ "status": {
+ "heard": "Услышан",
+ "mqtt": "MQTT"
+ },
+ "elevation": {
+ "label": "Высота"
+ },
+ "channelUtil": {
+ "label": "Использование канала"
+ },
+ "airtimeUtil": {
+ "label": "Использование эфирного времени"
+ }
+ },
+ "nodesTable": {
+ "headings": {
+ "longName": "Полное имя",
+ "connection": "Подключение",
+ "lastHeard": "Последний контакт",
+ "encryption": "Шифрование",
+ "model": "Модель",
+ "macAddress": "MAC-адрес"
+ },
+ "connectionStatus": {
+ "direct": "Прямое",
+ "away": "недоступен",
+ "viaMqtt": ", через MQTT"
+ }
+ },
+ "actions": {
+ "added": "Добавлено",
+ "removed": "Удалено",
+ "ignoreNode": "Игнорировать узел",
+ "unignoreNode": "Перестать игнорировать узел",
+ "requestPosition": "Запросить позицию"
+ }
+}
\ No newline at end of file
diff --git a/packages/web/public/i18n/locales/ru-RU/ui.json b/packages/web/public/i18n/locales/ru-RU/ui.json
new file mode 100644
index 00000000..8ea02d95
--- /dev/null
+++ b/packages/web/public/i18n/locales/ru-RU/ui.json
@@ -0,0 +1,221 @@
+{
+ "navigation": {
+ "title": "Навигация",
+ "messages": "Сообщения",
+ "map": "Карта",
+ "config": "Настройки",
+ "channels": "Каналы",
+ "radioConfig": "Настройки радио",
+ "moduleConfig": "Настройки модуля",
+ "channelConfig": "Настройки канала",
+ "nodes": "Узлы"
+ },
+ "app": {
+ "title": "Meshtastic",
+ "logo": "Логотип Meshtastic"
+ },
+ "sidebar": {
+ "collapseToggle": {
+ "button": {
+ "open": "Открыть боковую панель",
+ "close": "Закрыть боковую панель"
+ }
+ },
+ "deviceInfo": {
+ "volts": "{{voltage}} вольт",
+ "firmware": {
+ "title": "Прошивка",
+ "version": "в{{version}}",
+ "buildDate": "Дата сборки: {{date}}"
+ },
+ "deviceName": {
+ "title": "Имя устройства",
+ "changeName": "Изменить имя устройства",
+ "placeholder": "Введите имя устройства"
+ },
+ "editDeviceName": "Редактировать имя устройства"
+ }
+ },
+ "batteryStatus": {
+ "charging": "{{level}}% зарядки",
+ "pluggedIn": "Подключено к сети",
+ "title": "Аккумулятор"
+ },
+ "search": {
+ "nodes": "Поиск узлов...",
+ "channels": "Поиск каналов...",
+ "commandPalette": "Поиск команд..."
+ },
+ "toast": {
+ "positionRequestSent": { "title": "Запрос позиции отправлен." },
+ "requestingPosition": { "title": "Запрос позиции, пожалуйста, подождите..." },
+ "sendingTraceroute": { "title": "Отправка трассировки, пожалуйста, подождите..." },
+ "tracerouteSent": { "title": "Трассировка отправлена." },
+ "savedChannel": { "title": "Сохранённый канал: {{channelName}}" },
+ "messages": {
+ "pkiEncryption": { "title": "Чат использует PKI-шифрование." },
+ "pskEncryption": { "title": "Чат использует PSK-шифрование." }
+ },
+ "configSaveError": {
+ "title": "Ошибка сохранения настроек",
+ "description": "Произошла ошибка при сохранении конфигурации."
+ },
+ "validationError": {
+ "title": "Ошибки в конфигурации",
+ "description": "Пожалуйста, исправьте ошибки в конфигурации перед сохранением."
+ },
+ "saveSuccess": {
+ "title": "Сохранение настроек",
+ "description": "Изменение конфигурации {{case}} сохранено."
+ },
+ "saveAllSuccess": {
+ "title": "Сохранено",
+ "description": "Все изменения конфигурации сохранены."
+ },
+ "favoriteNode": {
+ "title": "{{action}} {{nodeName}} {{direction}} избранное.",
+ "action": {
+ "added": "Добавлено",
+ "removed": "Удалено",
+ "to": "в",
+ "from": "из"
+ }
+ },
+ "ignoreNode": {
+ "title": "{{action}} {{nodeName}} {{direction}} список игнорирования",
+ "action": {
+ "added": "Добавлено",
+ "removed": "Удалено",
+ "to": "в",
+ "from": "из"
+ }
+ }
+ },
+ "notifications": {
+ "copied": {
+ "label": "Скопировано!"
+ },
+ "copyToClipboard": {
+ "label": "Скопировать в буфер обмена"
+ },
+ "hidePassword": {
+ "label": "Скрыть пароль"
+ },
+ "showPassword": {
+ "label": "Показать пароль"
+ },
+ "deliveryStatus": {
+ "delivered": "Доставлено",
+ "failed": "Доставка не удалась",
+ "waiting": "Ожидание",
+ "unknown": "Неизвестно"
+ }
+ },
+ "general": {
+ "label": "Общие"
+ },
+ "hardware": {
+ "label": "Аппаратное обеспечение"
+ },
+ "metrics": {
+ "label": "Метрики"
+ },
+ "role": {
+ "label": "Роль"
+ },
+ "filter": {
+ "label": "Фильтр"
+ },
+ "advanced": {
+ "label": "Дополнительно"
+ },
+ "clearInput": {
+ "label": "Очистить ввод"
+ },
+ "resetFilters": {
+ "label": "Сбросить фильтры"
+ },
+ "nodeName": {
+ "label": "Имя/номер узла",
+ "placeholder": "Meshtastic 1234"
+ },
+ "airtimeUtilization": {
+ "label": "Использование эфирного времени (%)",
+ "short": "Исп. эфира (%)"
+ },
+ "batteryLevel": {
+ "label": "Уровень заряда (%)",
+ "labelText": "Уровень заряда (%): {{value}}"
+ },
+ "batteryVoltage": {
+ "label": "Напряжение аккумулятора (В)",
+ "title": "Напряжение"
+ },
+ "channelUtilization": {
+ "label": "Использование канала (%)",
+ "short": "Исп. канала (%)"
+ },
+ "hops": {
+ "direct": "Прямое",
+ "label": "Количество переходов",
+ "text": "Количество переходов: {{value}}"
+ },
+ "lastHeard": {
+ "label": "Последний контакт",
+ "labelText": "Последний контакт: {{value}}",
+ "nowLabel": "Сейчас"
+ },
+ "snr": {
+ "label": "Отношение сигнал/шум (дБ)"
+ },
+ "favorites": {
+ "label": "Избранное"
+ },
+ "hide": {
+ "label": "Скрыть"
+ },
+ "showOnly": {
+ "label": "Показать только"
+ },
+ "viaMqtt": {
+ "label": "Подключено через MQTT"
+ },
+ "hopsUnknown": {
+ "label": "Неизвестное количество переходов"
+ },
+ "showUnheard": {
+ "label": "Неизвестно время последнего контакта"
+ },
+ "language": {
+ "label": "Язык",
+ "changeLanguage": "Изменить язык"
+ },
+ "theme": {
+ "dark": "Тёмная",
+ "light": "Светлая",
+ "system": "Автоматическая",
+ "changeTheme": "Изменить цветовую схему"
+ },
+ "errorPage": {
+ "title": "Это немного неловко...",
+ "description1": "Нам очень жаль, но в веб-клиенте произошла ошибка, из-за которой он завершил работу.
Такого не должно было произойти, и мы усердно работаем над исправлением.",
+ "description2": "Лучший способ предотвратить повторение этой ошибки — сообщить нам о проблеме.",
+ "reportInstructions": "Пожалуйста, укажите в отчёте следующую информацию:",
+ "reportSteps": {
+ "step1": "Что вы делали, когда произошла ошибка",
+ "step2": "Что вы ожидали увидеть",
+ "step3": "Что произошло на самом деле",
+ "step4": "Любая другая актуальная информация"
+ },
+ "reportLink": "Вы можете сообщить о проблеме на нашем <0>GitHub0>",
+ "dashboardLink": "Вернуться на <0>главную панель0>",
+ "detailsSummary": "Детали ошибки",
+ "errorMessageLabel": "Сообщение об ошибке:",
+ "stackTraceLabel": "Трассировка стека:",
+ "fallbackError": "{{error}}"
+ },
+ "footer": {
+ "text": "Работает на <0>▲ Vercel0> | Meshtastic® — зарегистрированный товарный знак Meshtastic LLC. | <1>Юридическая информация1>",
+ "commitSha": "Коммит SHA: {{sha}}"
+ }
+}
diff --git a/packages/web/src/i18n-config.ts b/packages/web/src/i18n-config.ts
index 8b8de390..c05113d9 100644
--- a/packages/web/src/i18n-config.ts
+++ b/packages/web/src/i18n-config.ts
@@ -18,6 +18,7 @@ export const supportedLanguages: Lang[] = [
{ code: "en", name: "English", flag: "🇺🇸" },
{ code: "fr", name: "Français", flag: "🇫🇷" },
{ code: "sv", name: "Svenska", flag: "🇸🇪" },
+ { code: "ru", name: "Русский", flag: "🇷🇺" },
];
export const FALLBACK_LANGUAGE_CODE: LangCode = "en";
@@ -45,6 +46,7 @@ i18next
fr: ["fr-FR", FALLBACK_LANGUAGE_CODE],
sv: ["sv-SE", FALLBACK_LANGUAGE_CODE],
de: ["de-DE", FALLBACK_LANGUAGE_CODE],
+ ru: ["ru-RU", FALLBACK_LANGUAGE_CODE],
},
fallbackNS: ["common", "ui", "dialog"],
debug: import.meta.env.MODE === "development",