Merge branch 'staging' of https://github.com/FarmBot/Farmbot-Web-App into plant_stages

pull/697/head
Rick Carlino 2018-03-05 11:16:04 -06:00
commit b259970e98
33 changed files with 1048 additions and 1095 deletions

View File

@ -63,7 +63,7 @@ FarmBot::Application.routes.draw do
get "/tos_update" => "dashboard#tos_update", as: :tos_update
post "/csp_reports" => "dashboard#csp_reports", as: :csp_report
get "/password_reset/:token" => "dashboard#password_reset", as: :password_reset
get "/password_reset/*token" => "dashboard#password_reset", as: :password_reset
get "/verify/:token" => "dashboard#verify", as: :verify_user
match "/app/*path", to: "dashboard#main_app", via: :all, constraints: { format: "html" }

View File

@ -93,7 +93,7 @@
"webpack-uglify-js-plugin": "^1.1.9",
"weinre": "^2.0.0-pre-I0Z7U9OV",
"which": "^1.3.0",
"yarn": "^1.2.1"
"yarn": "^1.5.1"
},
"devDependencies": {
"jscpd": "^0.6.15",

View File

@ -1,49 +1,58 @@
module.exports = {
" regimen": " режим",
" sequence": " функция",
"Fun":"Прикол",
"Warn":"Предупреждение",
"No Regimen selected. Click one in the Regimens panel to edit, or click \"+\" in the Regimens panel to create a new one.":"Режим не выбран. Выберите один из них для редактирования или кликните \"+\" на панели Режимы, чтобы создать новый.",
"Drag and drop commands here to create sequences for watering, planting seeds, measuring soil properties, and more. Press the Test button to immediately try your sequence with FarmBot. You can also edit, copy, and delete existing sequences; assign a color; and give your commands custom names.":"Перетаскивайте сюда команды для создания Функций полива, посадки семя, измерения влажности почвы и т.д. Нажмите кнопку \"Тест\", чтобы сразу-же проверить созданную Функцию. Вы также можете редактировать, копировать и удалять Функции; назначать цвет; и давать вашим командам произвольные имена.",
"Ok": "Ok",
"Updating...": "Обновляю...",
" regimen": " Режим",
" sequence": " Функция",
" unknown (offline)": " неизвестно (не в сети)",
"(Alpha) Enable use of rotary encoders during calibration and homing.": "(Альфа-версия) Задействовать линейные энкодеры при калибровке и поиске домашней позиции.",
"(Alpha) If encoders or end-stops are enabled, home axis (find zero).": "(Альфа-версия) Если энкодеры или концевые датчики задействованы, выполнить поиск домашней позиции.",
"(Alpha) If encoders or end-stops are enabled, home axis and determine maximum.": "(Альфа-версия) Если энкодеры или концевые датчики задействованы, выполнить поиск домашней позиции и максимума координат.",
"(Alpha) Number of steps missed (determined by encoder) before motor is considered to have stalled.": "(Альфа-версия) Число пропущенных шагов (определенных по энкодеру), при котором двигатель считается застопорившимся.",
"(Alpha) Reduction to missed step total for every good step.": "(Альфа-версия) Уменьшение счета пропущенных шагов при каждом хорошо отработанном шаге.",
"(Alpha) Reverse the direction of encoder position reading.": "(Альфа-версия) Инвертировать показания энкодера.",
"(No selection)": "(Нет выделения)",
"(unknown)": "(неизвестно)",
"AUTO SYNC": "Авто Синхр.",
"Accelerate for (steps)": "Ускоряться за (шагов)",
"ACCELERATE FOR (steps)": "УСКОРЕНИЕ (шаги)",
"Account Not Verified": "Аккаунт не верифицирован",
"Account Settings": "Настройки Аккаунта",
"active": "активн.",
"Add": "Добавить",
"Add Farm Event": "Добавить событие",
"Add to map": "Добавить на карту",
"Age": "Возраст",
"Agree to Terms of Service": "Я согласен с правилами и положениями",
"ALLOW NEGATIVES": "РАЗРЕШИТЬ ОТРИЦАТЕЛЬНЫЕ",
"Always Power Motors": "Питание на моторах всегда включено",
"Always Power Motors": "Питание на двигателях всегда включено",
"Amount of time to wait for a command to execute before stopping.": "Максимальное время ожидания завершения работы команды.",
"An error occurred during configuration.": "Произошла ошибка во время настройки.",
"Analog": "Аналоговый",
"App Settings": "Свойства приложения",
"App could not be fully loaded, we recommend you try refreshing the page.": "Приложению не удалось полностью загрузиться, мы рекомендуем вам перезагрузить страницу.",
"Are you sure you want to delete this first party farmware? Doing so will limit the functionality of your FarmBot and may cause unexpected behavior.": "Are you sure you want to delete this first party farmware? Doing so will limit the functionality of your FarmBot and may cause unexpected behavior.",
"Are you sure you want to delete this step?": "Вы уверены, что хотите удалить этот шаг?",
"AUTO SYNC": "Авто Синрх.",
"Assign a sequence to execute when a Raspberry Pi GPIO pin is activated.": "Назначьте команду, которая будет выполняться при активации пина GPIO Raspberry Pi.",
"Automatic Factory Reset": "Авто сброс к заводским настройкам",
"Automatically factory reset when the WiFi network cannot be detected. Useful for typos during FarmBot OS configuration or network changes.": "Автоматически делать полный сброс устройства при невозможности найти сеть WiFi. Может пригодиться при ошибках в настройке FarmBot OS или изменении настроек сети.",
"Axis Length (steps)": "Длина оси (шагов)",
"BACK": "НАЗАД",
"back": "назад",
"Back": "Назад",
"Begin": "Начало",
"Beta release Opt-In": "Beta release Opt-In",
"BIND": "Назначить",
"BLUR": "РАЗМЫТИЕ",
"BOOTING": "ЗАГРУЗКА",
"Bot ready": "Робот Готов",
"Back": "Назад",
"Beta release Opt-In": "Beta release Opt-In",
"Bottom Left": "Низ лево",
"Bottom Right": "Низ право",
"Browser": "Браузер",
"Busy": "Занят",
"Calibrate": "Калибровка",
"CALIBRATE {{axis}}": "КАЛИБРОВАТЬ {{axis}}",
"CALIBRATION": "КАЛИБРОВКА",
"CAMERA": "Камера",
"CLEAR WEEDS": "Уборка сорняков",
"CLICK anywhere within the grid": "Кликните в любом месте координатной сетки",
"Calibrate": "Калибровка",
"Calibrate FarmBot's camera for use in the weed detection software.": "Калибровка камеры FarmBot-а для процесса поиска сорняков.",
"Calibration Object Separation": "Расстояние между калибровочными объектами",
"Calibration Object Separation along axis": "Ось, вдоль которой расположены калибровочные объекты",
"calling FarmBot with credentials": "запрос данных",
"Camera": "Камера",
"CAMERA": "Камера",
"Camera Calibration": "Калибровка камеры",
"Camera Offset X": "Сдвиг камеры по X",
"Camera Offset Y": "Сдвиг камеры по Y",
@ -52,118 +61,103 @@ module.exports = {
"Can't connect to release server": "Не удалось подключиться к серверу",
"Cancel": "Отмена",
"Change Password": "Изменить пароль",
"Change settings of your FarmBot hardware with the fields below. Caution: Changing these settings to extreme values can cause hardware malfunction. Make sure to test any new settings before letting your FarmBot use them unsupervised. Tip: Recalibrate FarmBot after changing settings and test a few sequences to verify that everything works as expected.": "Аппаратные характеристики вашего робота настраиваются в представленных ниже полях. Предостережение: необдуманное внесение изменений в эти настройки может привести к повреждению аппаратной части вашего FarmBot-а. Протестируйте все внесенные изменения прежде, чем робот начнет работать на новых настройках без вашего контроля. Подсказка: после внесения изменений перекалибруйте FarmBot-а и протестируйте его работоспособность на нескольких последовательностях, чтобы убедиться, что все работает как нужно.",
"Change slot direction": "Изменить направление слота",
"Change the Farm Designer map size based on axis length. A value must be input in AXIS LENGTH and STOP AT MAX must be enabled in the HARDWARE widget.": "Изменить размер карты Farm Designer, основываясь на длинах осей. Значение в поле \"Длина оси\" должно быть задано и переключатель \"Останавливаться на максимуме\" должен быть включен в окне \"Аппаратная часть\".",
"Change the Farm Designer map size based on axis length. A value must be input in AXIS LENGTH and STOP AT MAX must be enabled in the HARDWARE widget.": "Изменить размер карты Дизайнера грядки, основываясь на длинах осей. Значение в поле \"Длина оси\" должно быть задано и переключатель \"Останавливаться на максимуме\" должен быть включен в окне \"Аппаратная часть\".",
"Check Again": "Проверить снова",
"Choose a crop": "Выберите рассаду",
"Choose a species": "Выберите вид",
"CLEAR WEEDS": "Уборка сорняков",
"CLICK anywhere within the grid": "Кликните в любом месте координатной сетки",
"Click the edit button to add or edit a feed URL.": "Нажмите кнопку \"Редактировать\", чтобы добавить или отредактировать URL видеопотока",
"Collapse All": "Свернуть все",
"color": "цвет",
"Color Range": "Диапазон цветов",
"Commands": "Команды",
"Complete": "Завершено",
"computer": "ПК",
"Confirm New Password": "Подтвердите новый пароль",
"Confirm Password": "Повторите пароль",
"Confirm Sequence step deletion": "Подтвердите удаление шага функции",
"Confirm Sequence step deletion": "Подтвердите удаление шага Функции",
"Connected.": "Подключено.",
"Connection Attempt Period": "Период ожидания попытки подключения",
"Connectivity": "Связь",
"CONTROLLER": "Контроллер",
"Controls": "Управление",
"Copy": "Копировать",
"Could not delete image.": "Не удалось удалить изображение.",
"Could not delete plant.": "Не удалось удалить растение.",
"Could not download FarmBot OS update information.": "Не удалось скачать информацию об обновлении FarmBot OS.",
"Could not download sync data": "Невозможно загрузить синхронизированную информацию",
"Create Account": "Создать Аккаунт",
"Create An Account": "Создать Аккаунт",
"Create logs for sequence:": "Создать логи для функции:",
"Create logs for sequence:": "Создать логи для Функции:",
"Create point": "Создать точку",
"Created At:": "Создано:",
"Crop Info": "Инфо об Урожае",
"Customize your web app experience.": "Настройте Web-приложение под себя.",
"DRAG COMMAND HERE": "ПЕРЕТАЩИТЕ КОМАНДУ СЮДА",
"Danger Zone": "Опасная зона (не нажимай, не подумав)",
"Data Label": "Название",
"Date": "Дата",
"Day {{day}}": "день {{day}}",
"Days": "Дни",
"days old": "дней",
"Debug": "Отладка",
"delete": "удалить",
"Delete": "Удалить",
"DELETE ACCOUNT": "УДАЛИТЬ АККАУНТ",
"Delete Account": "Удалить аккаунт",
"Delete Photo": "Удалить фото",
"Delete all created points": "Удалить все созданные точки",
"Delete all of the points created through this panel.": "Удалить все точки, созданные через эту панель.",
"Delete multiple": "Удалить несколько",
"Delete Photo": "Удалить фото",
"Delete selected": "Удалить выделенное",
"Delete this plant": "Удалить это растение",
"Deleted farm event.": "Удаленное событие.",
"Deselect all": "Снять выделение",
"Designer": "Дизайнер",
"DEVICE": "Робот",
"Device": "Робот",
"Detect weeds using FarmBot's camera and display them on the Farm Designer map.": "Находить сорняки при помощи камеры и отображать их на карте Дизайнера грядки.",
"Diagnose connectivity issues with FarmBot and the browser.": "Это окно позволяет выявить проблемы со связью между FarmBot-ом и браузером.",
"Diagnosis": "Диагноз",
"Digital": "Цифровой",
"DISCONNECTED": "ОТКЛЮЧЕНО",
"Disconnected.": "Отключено.",
"Display Encoder Data": "Отображать данные с энкодеров",
"Display plant animations": "Отображать анимацию растений",
"Documentation": "Документация",
"Don't allow movement past the maximum value provided in AXIS LENGTH.": "Запретить перемещения, выходящие за пределы, заданные параметром \"Длина оси\".",
"Done": "Готово",
"Double default map dimensions": "Удвоить значения карты по-умолчанию",
"Double default map dimensions": "Удвоить значения карты по умолчанию",
"Double the default dimensions of the Farm Designer map for a map with four times the area.": "Double the default dimensions of the Farm Designer map for a map with four times the area.",
"downloading device credentials": "загрузка данных устройства",
"Drag and drop": "Перетащите",
"Drag and drop into map": "Перетащи на карту",
"DRAG COMMAND HERE": "ПЕРЕТАЩИТЕ КОМАНДУ СЮДА",
"DRAG STEP HERE": "ПЕРЕТЯНИ СЮДА",
"Dynamic map size": "Динамический размер карты",
"E-Stop on Movement Error": "Активировать E-Stop ошибках в движении",
"EDIT": "РЕДАКТИРОВАТЬ",
"E-Stop on Movement Error": "Активировать E-Stop при ошибках в движении",
"ELSE...": "ИНАЧЕ...",
"ENCODER TYPE": "ТИП ЭНКОДЕРА",
"EXECUTE SEQUENCE": "ВЫПОЛНИТЬ ФУНКЦИЮ",
"Edit": "Редактировать",
"Edit Farm Event": "Редактировать событие",
"Edit on": "Редактирование включено",
"ELSE...": "ИНАЧЕ...",
"Email": "Email",
"Email has been sent.": "Email отправлен.",
"Email sent.": "Email отправлен.",
"Enable 2nd X Motor": "Активировать второй мотор по оси X",
"Emergency stop if movement is not complete after the maximum number of retries.": "Включать E-STOP, если движение не выполнено после максимального числа попыток.",
"Enable 2nd X Motor": "Активировать второй двигатель по оси X",
"Enable Encoders": "Включить энкодеры",
"ENABLE ENCODERS": "ВКЛЮЧИТЬ ЭНКОДЕРЫ",
"Enable Endstops": "Включить концевые датчики",
"Enable plant animations in the Farm Designer.": "Включить анимацию растений в Farm Designer.",
"Enable plant animations in the Farm Designer.": "Включить анимацию растений в Дизайнере грядки.",
"Enable use of a second x-axis motor. Connects to E0 on RAMPS.": "Использовать второй двигатель по оси X. Подключаться к E0 на RAMPS.",
"Enable use of electronic end-stops during calibration and homing.": "Включить использование концевых датчиков во время калибровки и поиска домашней позиции.",
"Encoder Missed Step Decay": "\"Забывание\" пропущенных шагов энкодера",
"Encoder Scaling": "Масштабный коэффициент энкодера",
"ENCODER TYPE": "ТИП ЭНКОДЕРА",
"Encoders and Endstops": "Энкодеры и концевые датчики",
"Enter a URL": "Введите URL",
"Enter Email": "Введите Email",
"Enter Password": "Введите пароль",
"Enter a URL": "Введите URL",
"Error": "Ошибка",
"Error establishing socket connection": "Ошибка нестабильное соеденение",
"Error saving device settings.": "Ошибка при сохранении настроек робота.",
"Error taking photo": "Ошибка при фотографировании",
"Every": "Каждый",
"EXECUTE SCRIPT": "ВЫПОЛНИТЬ СЦЕНАРИЙ",
"Execute Script": "Выполнить Скрипт",
"EXECUTE SEQUENCE": "ВЫПОЛНИТЬ ФУНКЦИЮ",
"Execute Sequence": "Выполнить функцию",
"Execute Sequence": "Выполнить Функцию",
"Execute a sequence if a condition is satisfied. If the condition is not satisfied, chose to do nothing or execute a different sequence.": "Выполнить Функцию, если условие выполнено. Если условие не выполнено, можно либо ничего не делать, либо выполнить другую Функцию.",
"Executes another sequence.": "Выполняет другую Функцию.",
"Expand All": "Развернуть все",
"FACTORY RESET": "СБРОС НА ЗАВОДСКИЕ НАСТРОЙКИ",
"FARMBOT OS": "FARMBOT OS",
"FARMBOT OS AUTO UPDATE": "АВТООБНОВЛЕНИЕ FARMBOT OS",
"FIRMWARE": "ПРОШИВКА",
"Factory Reset": "Сброс устройства",
"Farm Designer": "Дизайнер фермы",
"Factory resetting your FarmBot will destroy all data on the device, revoking your FarmBot's abilily to connect to your web app account and your home wifi. Upon factory resetting, your device will restart into Configurator mode. Factory resetting your FarmBot will not affect any data or settings from your web app account, allowing you to do a complete restore to your device once it is back online and paired with your web app account.": "Сброс на заводские настройки вашего FarmBot-а уничтожит на нем все данные, FarmBot забудет вашу домашнюю сеть WiFi и данные аккаунта в Web-приложении. После сброса, FarmBot перезагрузится в режиме конфигурирования. Сброс никак не повлияет на ваши данные и настройки в Web-приложении, что позволит полностью восстановить настройки FarmBot-а как только он подключится к Web-приложению.",
"Farm Events": "События",
"FarmBot Web App": "Web-приложение FarmBot",
"FarmBot forum.": "форум FarmBot.",
"FarmBot is at position ": "FarmBot на позиции ",
"FarmBot is not connected.": "FarmBot не подключен.",
"FARMBOT OS": "FARMBOT OS",
"FARMBOT OS AUTO UPDATE": "АВТООБНОВЛЕНИЕ FARMBOT OS",
"FarmBot Web App": "FarmBot Web-приложение",
"FarmBot sent a malformed message. You may need to upgrade FarmBot OS. Please upgrade FarmBot OS and log back in.": "FarmBot отправил некорректное сообщение. Возможно, вам нужно обновить FarmBot OS. Пожалуйста обновите FarmBot OS и снова зайдите в аккаунт.",
"FarmBot?": "FarmBot?",
"Feed Name": "Имя видеопотока",
"Filter logs": "Фильтровать логи",
@ -171,86 +165,77 @@ module.exports = {
"Find ": "Поиск ",
"Find Home": "Поиск домашней позиции",
"Find Home on Boot": "Поиск домашней позиции при включении",
"FIRMWARE": "ПРОШИВКА",
"Firmware Logs:": "Внутренние логи:",
"First-party Farmware": "Первичная прошивка",
"Forgot Password": "Забыл пароль",
"Forgot password?": "Забыли пароль?",
"from": "от",
"Full Name": "Полное имя",
"GO": "СТАРТ",
"Hardware": "Аппаратная часть",
"Hardware setting conflict": "Ошибка в настройках аппаратной части",
"Have the browser also read aloud log messages on the ": "Have the browser also read aloud log messages on the ",
"Hide Webcam widget": "Спрятать окно веб-камеры",
"HOME {{axis}}": "В начало {{axis}}",
"HOMING": "Поиск домашней позиции",
"Homing and Calibration": "Поиск домашней позиции и калибровка",
"Hotkeys": "Горячие клавишы",
"HUE": "Оттенок",
"Hardware": "Аппаратная часть",
"Hardware setting conflict": "Ошибка в настройках аппаратной части",
"Have the browser also read aloud log messages on the \"Speak\" channel that are spoken by FarmBot.": "Браузер будет зачитывать логи вслух.",
"Here is the list of all of your sequences. Click one to edit.": "Здесь представлен список всех ваших Функций. Нажмите на одну из них для редактирования.",
"Hide Webcam widget": "Спрятать окно Web-камеры",
"Home position adjustment travel speed (homing and calibration) in motor steps per second.": "Скорость перемещения при поиске домашней позиции (и калибровке), в шагах за секунду.",
"Homing and Calibration": "Поиск домашней позиции и калибровка",
"Hotkeys": "Горячие клавиши",
"I Agree to the Terms of Service": "Я согласен с правилами и положениями",
"I agree to the terms of use": "Я согласен с правилами использования",
"If not using a webcam, use this setting to remove the widget from the Controls page.": "Если Вы не используете веб-камеру, включите этот переключатель, чтобы спрятать соответствующее окно со страницы \"Управление\".",
"If Statement": "Оператор \"ЕСЛИ\"",
"IF STATEMENT": "ОПЕРАТОР \"ЕСЛИ\"",
"IF...": "ЕСЛИ...",
"image": "картинка",
"ITERATION": "Итерация",
"If Statement": "Оператор \"ЕСЛИ\"",
"If encoders or end-stops are enabled, find the home position when the device powers on. Warning! This will perform homing on all axes when the device powers on. Encoders or endstops must be enabled. It is recommended to make sure homing works properly before enabling this feature.": "Автоматически находить домашнюю позицию, если энкодеры или концевые датчики задействованы. Внимание! Эта команда выполнит поиск домашней позиции по всем осям. Энкодеры и концевые датчики должны работать. Рекомендуем убедиться, что поиск домашней позиции производится корректно, прежде чем включать эту команду.",
"If not using a webcam, use this setting to remove the widget from the Controls page.": "Если Вы не используете Web-камеру, включите этот переключатель, чтобы спрятать соответствующее окно со страницы \"Управление\".",
"If you are sure you want to delete your account, type in your password below to continue.": "Если вы уверены, что хотите удалить свой аккаунт, введите свой пароль в поле ниже.",
"If you have a webcam, you can view the video stream in this widget. Press the edit button to update and save your webcam URL.": "Если у вас есть Web-камера, вы можете просматривать видеопоток в этом окне. Нажмите кнопку \"Редактировать\", если требуется исправить URL вашей Web-камеры.",
"Image Deleted.": "Картинка удалена.",
"Image loading (try refreshing)": "Загрузка изображение (попробуйте обновить страницу)",
"Import coordinates from": "Импортировать координаты из",
"inactive": "неактивн.",
"Info": "Инфо",
"initiating connection": "установка соеденения",
"Install": "Установка",
"Internationalize Web App": "Internationalize Web App",
"Internet": "Интернет",
"Invert 2nd X Motor": "Инвертировать направление второго мотора оси X",
"Invert 2nd X Motor": "Инвертировать направление второго двигателя оси X",
"Invert Encoders": "Инвертировать энкодеры",
"INVERT ENDPOINTS": "ИНВЕНТИРОВАТЬ ENDSTOPS",
"Invert Endstops": "Инвертировать концевые датчики",
"Invert Hue Range Selection": "Инверсия выбора диапазона оттенков",
"Invert Jog Buttons": "Инвертировать кнопки перемещения",
"Invert Motors": "Инвертировать моторы",
"INVERT MOTORS": "ИНВЕНТИРОВАТЬ МОТОРЫ",
"is": "равно",
"is equal to": "равно",
"is greater than": "больше чем",
"is less than": "меньше чем",
"is not": "не равно",
"is not equal to": "не равно",
"is unknown": "неизвестно",
"ITERATION": "Итерация",
"Invert Motors": "Инвертировать двигатели",
"Invert direction of motor during calibration.": "Инвертировать направление двигателя во время калибровки.",
"Keep power applied to motors. Prevents slipping from gravity in certain situations.": "Удерживать питание на двигателях во время простоя. Эта функция помогает препятствовать сползанию оси Z вследствие гравитации, а также полезна при отсутствии энкодеров на осях X и Y.",
"LAST SEEN": "Замечен в последний раз",
"LENGTH (m)": "Длинна (м)",
"Lighting": "Освещение",
"Loading": "Загрузка",
"Loading...": "Загрузка...",
"Location": "Координаты",
"Log all commands sent to firmware (clears after refresh).": "Записывать в лог все команды, отправленные роботу (очищается после обновления).",
"Log all debug received from firmware (clears after refresh).": "Записывать в лог все отладочные сообщения от робота (очищается после обновления).",
"Log all responses received from firmware (clears after refresh). Warning: extremely verbose.": "Записывать в лог все ответные сообщения от робота (очищается после обновления). Внимание: лог очень подробный.",
"Login": "Войти",
"Login failed.": "Ошибка входа.",
"Logout": "Выход",
"Logs": "Логи",
"MAINTENANCE DOWNTIME": "ТЕХ. ОБСЛУЖИВАНИЕ",
"MORPH": "MORPH",
"MOVE ABSOLUTE": "АБСОЛЮТНОЕ ПЕРЕМЕЩЕНИЕ",
"MOVE AMOUNT (mm)": "Переместить на (мм)",
"MOVE RELATIVE": "ОТНОСИТЕЛЬНОЕ ПЕРЕМЕЩЕНИЕ",
"Manage Farmware (plugins).": "Управление Farmware (плагинами).",
"Manual input": "Ручной ввод",
"max": "максимум",
"Max Missed Steps": "Максимально разрешенное число пропущенных шагов",
"Max Retries": "Максимально число попыток",
"Max Speed (steps/s)": "Макс. скорость (шагов/мм)",
"Maximum travel speed after acceleration in motor steps per second.": "Максимальная скорость перемещения, в шагах за секунду.",
"Menu": "Меню",
"Message": "Сообщение",
"Message Broker": "Центр сообщений",
"Minimum Speed (steps/s)": "Мин. скорость (шагов/мм)",
"mm": "мм",
"MORPH": "MORPH",
"Minimum movement speed in motor steps per second. Also used for homing and calibration.": "Минимальная скорость перемещения, в шагах за секунду. Эта скорость используется для поиска домашней позиции и калибровки.",
"Motor Coordinates (mm)": "Координаты двигателя (мм)",
"Motors": "Двигатели",
"Move": "Перемещение",
"Move Absolute": "Абсолютное Перемещение",
"MOVE ABSOLUTE": "АБСОЛЮТНОЕ ПЕРЕМЕЩЕНИЕ",
"MOVE AMOUNT (mm)": "Переместить на (мм)",
"move mode": "режим движения",
"Move Relative": "Относительное Перемещение",
"MOVE RELATIVE": "ОТНОСИТЕЛЬНОЕ ПЕРЕМЕЩЕНИЕ",
"Move to location": "Двигаться к точке",
"Move to this coordinate": "Двигаться к данной координате",
"Must be a positive number. Rounding up to 0.": "Число должно быть положительным. Округляю до 0.",
@ -258,39 +243,36 @@ module.exports = {
"Negative Coordinates Only": "Только отрицательные координаты",
"Negative X": "X в минус",
"Negative Y": "Y в минус",
"NETWORK": "СЕТЬ",
"never connected to device": "никогда не подключалось к устройству",
"New Password": "Новый пароль",
"New Peripheral": "Новая периферия",
"New regimen ": "Новый режим ",
"new sequence {{ num }}": "новая функция {{ num }}",
"New regimen ": "Новый Режим ",
"Newer than": "Новее, чем",
"Next": "Следующий",
"no": "нет",
"No Executables": "Нет исполняемых файлов",
"No beta releases available": "Нет новых Beta-версий",
"No day(s) selected.": "Дни не выбраны.",
"No Executables": "Нет исполняемых файлов",
"No logs to display. Visit Logs page to view filters.": "Нет логов важных. Для детального просмотра, перейдите на вкладку \"Логи\"",
"No logs yet.": "Пока нет логов.",
"No logs yet.": "Пока логов нет.",
"No messages seen yet.": "Пока не было ни одного сообщения",
"No recent messages.": "Нет новых сообщений",
"No results.": "Не найдено.",
"No webcams yet. Click the edit button to add a feed URL.": "Веб-камеры не добавлены. Нажмите кнопку \"Редактировать\", чтобы добавить URL видеопотока.",
"None": "Нет",
"normal": "нормально",
"Not Connected to bot": "Нет соеденения с роботом",
"Not Set": "Не задано",
"OFF": "Выкл.",
"Note: The selected timezone for your FarmBot is different than your local browser time.": "Примечание: выбранная временная зона вашего FarmBot-а отличается от зоны вашего браузера.",
"Note: Times displayed according to FarmBot's local time, which is currently different from your browser's time. Timezone data is configurable on the Device page).": "Примечание: временные метки соответствуют локальному времени FarmBot-а, которое в данный момент отличается от времени вашего браузера. Временную зону можно настроить на странице \"Устройство\").",
"Number of steps used for acceleration and deceleration.": "Количество шагов, за которое будет происходить ускорение и замедление. Чем оно больше, тем плавнее FarmBot будет разгоняться и замедляться.",
"Number of times to retry a movement before stopping.": "Количество попыток повторить движение перед аварийной остановкой.",
"off": "выкл",
"on": "вкл",
"Old Password": "Текущий пароль",
"Older than": "Старше чем",
"ON": "Вкл.",
"Operator": "Оператор",
"Origin": "Начальная точка",
"Origin Location in Image": "Начальная точка на изображении",
"Outside of planting area. Plants must be placed within the grid.": "За границей зоны посадки. Растения должны быть расположены внутри зоны посадки.",
"Package Name": "Имя пакета",
"Page Not Found.": "Страница не найдена.",
"Parameters": "Параметры",
"Password": "Пароль",
"Password change failed.": "Смена пароля не удалась.",
"Peripheral ": "Периферия ",
@ -299,7 +281,6 @@ module.exports = {
"Photos?": "Фотографии?",
"Pin": "Пин",
"Pin ": "Пин ",
"Pin #": "Пин №",
"Pin Bindings": "Назначение команд пинам",
"Pin Guard": "Защита пинов (отключение по таймауту)",
"Pin Guard {{ num }}": "Защита пина {{ num }}",
@ -307,10 +288,7 @@ module.exports = {
"Pin Number": "Номер пина",
"Pin numbers are required and must be positive and unique.": "Номера пинов должны быть заданы и должны быть положительными и уникальными.",
"Pin numbers must be less than 1000.": "Номера пинов должны быть меньше 1000.",
"Pin {{num}}": "Пин {{num}}",
"Pins": "Пины",
"Pixel coordinate scale": "Размерность пикселей",
"plant icon": "иконка растения",
"Plant Info": "Инфо о растении",
"Plant Type": "Тип растения",
"Plants": "Растения",
@ -325,170 +303,177 @@ module.exports = {
"Presets:": "Предустановки:",
"Prev": "пред.",
"Privacy Policy": "Политика конфиденциальности",
"Problem Loading Terms of Service": "Проблема загрузки правил и соглашений",
"Processing Parameters": "Параметры обработки",
"Processing now. Results usually available in one minute.": "Обрабатываю. Результат обычно доступен в течение одной минуты.",
"Processing Parameters": "Обрабатываю параметры",
"radius": "радиус",
"READ PIN": "СЧИТАТЬ ПИН",
"RESET": "СБРОС",
"RESTART": "ПЕРЕЗАГРУЗКА",
"RESTART FARMBOT": "ПЕРЕЗАГРУЗИТЬ РОБОТА",
"Raspberry Pi Camera": "Камера Raspberry Pi",
"Raw Encoder data": "Импульсы энкодера",
"Raw encoder position": "Положение энкодера (в импульсах) ",
"Read Pin": "Считать пин",
"READ PIN": "СЧИТАТЬ ПИН",
"Read speak logs in browser": "Read speak logs in browser",
"Received": "Получено",
"Read speak logs in browser": "Читать логи вслух",
"Recursive condition.": "Рекурсивное условие.",
"Regimen": "Режим",
"Regimen Editor": "Редактор режимов",
"Regimen Name": "Название режима",
"Regimen Editor": "Редактор Режимов",
"Regimen Name": "Название Режима",
"Regimens": "Режимы",
"Regimens allow FarmBot to take care of a plant throughout its entire life. A regimen consists of many sequences that are scheduled to run based on the age of the plant. Regimens are applied to plants from the farm designer (coming soon) and can be re-used on many plants growing at the same or different times. Multiple regimens can be applied to any one plant.": "Режимы позволяют FarmBot-у заботиться о растении в течение всей его жизни. Режим состоит из нескольких Функций, которые запускаются по расписанию и в зависимости от возраста растения. Режимы можно применить к растению из Дизайнера грядки (скоро) и могут быть использованы на множестве растений, растущих в одно и то же или разные времена. Любое количество Режимов может быть применено к любому из растений.",
"Reinstall": "Переустановить",
"Release Notes": "Примечания к релизу",
"Remove": "Удалить",
"Repeats Every": "Повторять каждые",
"Repeats?": "Повторения?",
"Request sent": "Запрос отправлен",
"Resend Verification Email": "Повторная отправка письма для верификации",
"Reset": "Сброс",
"RESET": "СБРОС",
"Reset hardware parameter defaults": "Сброс настроек аппаратной части",
"Reset Password": "Сброс Пароля",
"Reset hardware parameter defaults": "Сброс настроек аппаратной части",
"Reset your password": "Сбросить ваш пароль",
"RESTART": "ПЕРЕЗАГРУЗКА",
"RESTART FARMBOT": "ПЕРЕЗАГРУЗИТЬ РОБОТА",
"Restoring hardware parameter defaults will destroy the current settings, resetting them to default values.": "Восстановление параметров робота по умолчанию уничтожит все текущие настройки, сбросив их на настройки по умолчанию.",
"Restrict travel to negative coordinate locations. Overridden by disabling STOP AT HOME.": "Запретить перемещение к отрицательным координатам. Этот параметр сбрасывается при отключении переключателя \"Остановка на домашней позиции\".",
"Run": "Запуск",
"Run Farmware": "Запуск Farmware",
"SATURATION": "Насыщенность",
"SAVE": "СОХРАНИТЬ",
"SEND MESSAGE": "ОТПРАВИТЬ СООБЩЕНИЕ",
"SET ZERO POSITION": "УСТ. НУЛЕВОЙ ПОЗИЦИИ",
"SHUTDOWN": "ВЫКЛЮЧИТЬ",
"SHUTDOWN FARMBOT": "ВЫКЛЮЧИТЬ РОБОТА",
"Save": "Сохранить",
"Save ": "Сохранить ",
"Save sequence and sync device before running.": "Сохраните функцию и сделайте синхронизацию перед запуском.",
"Saved ": "Сохранено ",
"Saving": "Сохраняю",
"Save sequence and sync device before running.": "Сохраните Функцию и сделайте синхронизацию перед запуском.",
"Scaled encoder position": "Положение энкодера (в миллиметрах)",
"Scan image": "Сканировать изображение",
"Scheduler": "Планировщик",
"Search OpenFarm...": "Искать на OpenFarm...",
"Search Regimens...": "Искать в Режимах...",
"Search Sequences...": "Искать в функциях...",
"Search Sequences...": "Искать в Функциях...",
"Search your plants...": "Искать в ваших растениях...",
"Seed Bin": "Семенной бункер",
"Seed Tray": "Семенной лоток",
"Seeder": "Сажалка",
"Select a regimen first or create one.": "Сначала выберите или создайте режим.",
"Select a sequence first": "Сначала выберите функцию",
"Select a sequence from the dropdown first.": "Выберите функцию из выпадающего списка.",
"Select a regimen first or create one.": "Сначала выберите или создайте Режим.",
"Select a sequence first": "Сначала выберите Функцию",
"Select a sequence from the dropdown first.": "Выберите Функцию из выпадающего списка.",
"Select all": "Выбрать все",
"Select none": "Отменить выбор",
"Select plants": "Выбрать растения",
"Send Message": "Отправить сообщение",
"SEND MESSAGE": "ОТПРАВИТЬ СООБЩЕНИЕ",
"Send Password reset": "Send Password reset",
"Send a log message for each sequence step.": "Записывать в лог каждый шаг Функции.",
"Send a log message upon the end of sequence execution.": "Записывать лог момент завершения Функции.",
"Send a log message upon the start of sequence execution.": "Записывать лог момент запуска Функции.",
"Sending camera configuration...": "Отправляю конфигурацию камеры...",
"Sending firmware configuration...": "Отправляю аппаратую конфигурацию...",
"Sensors": "Датчики",
"Sent": "Отправлено",
"Sending firmware configuration...": "Отправляю аппаратную конфигурацию...",
"Sequence": "Функция",
"Sequence Editor": "Редактор функций",
"Sequence or Regimen": "Функция или режим",
"Sequence Editor": "Редактор Функций",
"Sequence or Regimen": "Функция или Режим",
"Sequences": "Функции",
"Server Port": "Порт сервера",
"Server URL": "Сервер URL",
"SET ZERO POSITION": "УСТ. НУЛЕВОЙ ПОЗИЦИИ",
"Setup, customize, and control FarmBot from your": "Запустите, настройте, и контролируйте FarmBot с вашего",
"Show a confirmation dialog when the sequence delete step icon is pressed.": "Показывать диалог подтверждения при нажатии на иконку удаления шага функции.",
"Set device timezone here.": "Здесь настраивается временная зона робота.",
"Set the current location as zero.": "Задать текущую позицию как нулевую.",
"Set the length of each axis to provide software limits. Used only if STOP AT MAX is enabled.": "Задайте длину каждой оси для программного ограничения перемещения. Используется только если включен переключатель \"Остановка на максимуме\".",
"Setup, customize, and control FarmBot from your": "Запускайте, настраивайте и контролируйте FarmBot с вашего",
"Show a confirmation dialog when the sequence delete step icon is pressed.": "Показывать диалог подтверждения при нажатии на иконку удаления шага Функции.",
"Show in list": "Показывать в списке",
"SHUTDOWN": "ВЫКЛЮЧИТЬ",
"SHUTDOWN FARMBOT": "ВЫКЛЮЧИТЬ РОБОТА",
"SLOT": "СЛОТ",
"Slot": "Слот",
"smartphone": "смартфона",
"Socket Connection Established": "Соеденение не стабильно",
"Snaps a photo using the device camera. Select the camera type on the Device page.": "Захват фотографии с помощью камеры робота. Выберите тип камеры на странице \"Робот\".",
"Soil Sensor": "Датчик влажности почвы",
"Something went wrong while rendering this page.": "При обработке данной страницы что-то пошло не так.",
"Speak": "Речевое уведомление",
"Speed": "Скорость",
"Speed (%)": "Скорость (%)",
"Spread?": "Размах?",
"Started": "Запущено",
"Starts": "Запускается",
"Status": "Статус",
"STATUS": "СТАТУС",
"Steps": "Шаги",
"Steps per MM": "Шагов за мм",
"Stock Tools": "Стандартные насадки",
"Stop at Home": "Остановка на домашней позиции",
"Stop at Max": "Остановка на максимуме",
"Stop at the home location of the axis.": "Не двигаться дальше домашней позиции оси.",
"Success": "Успех",
"Successfully configured camera!": "Камера успешно сконфигурирована!",
"SYNC ERROR": "ОШИБКА СИНХР.",
"SYNC NOW": "СИНХРОНИЗИРОВАТЬ",
"Sync Required": "Требуется синхронизация",
"SYNCED": "СИНХРОНИЗИРОВАНО",
"SYNCING": "СИНХРОНИЗИРУЮ",
"tablet": "планшета",
"Take a Photo": "Сделать фото",
"Swap axis end-stops during calibration.": "Поменять местами концевые выключатели во время калибровки.",
"TAKE PHOTO": "СДЕЛАТЬ ФОТО",
"TEST": "ТЕСТ",
"THEN...": "ТО...",
"TIME ZONE": "ВРЕМЕННАЯ ЗОНА",
"Take Photo": "Сделать фото",
"Take a Photo": "Сделать фото",
"Take and view photos with your FarmBot's camera.": "Захватывайте и просматривайте фото с помощью камеры вашего FarmBot-а.",
"Terms of Service": "Условия обслуживания",
"Terms of Use": "Условия использования",
"TEST": "ТЕСТ",
"Test": "Тест",
"THEN...": "ТО...",
"The Find Home step instructs the device to perform a homing command to find and set zero for the chosen axis or axes.": "Шаг \"Поиск домашней позиции\" дает роботу команду найти начало координат для выбранной оси или осей.",
"The Move Absolute step instructs FarmBot to move to the specified coordinate regardless of the current position. For example, if FarmBot is currently at X=1000, Y=1000 and it receives a Move Absolute where X=0 and Y=3000, then FarmBot will move to X=0, Y=3000. If FarmBot must move in multiple directions, it will move diagonally. If you require straight movements along one axis at a time, use multiple Move Absolute steps. Offsets allow you to more easily instruct FarmBot to move to a location, but offset from it by the specified amount. For example moving to just above where a peripheral is located. Using offsets lets FarmBot do the math for you.": "Шаг \"Абсолютное перемещение\" дает FarmBot-у команду двигаться к указанным координатам независимо от текущей позиции. Например, если FarmBot сейчас на позиции X=1000, Y=1000, и он получает эту команду с координатами X=0 and Y=3000, он переместится к координатам X=0, Y=3000. Если перемещение выполняется по нескольким координатам, робот будет двигаться по диагонали. Если вам нужно, чтобы он двигался сначала по одной координате, а потом по другой - используйте несколько шагов \"Абсолютное перемещение\". Offsets allow you to more easily instruct FarmBot to move to a location, but offset from it by the specified amount. Например, подъехать на позицию, расположенную выше насадки. Использование смещений позволяет FarmBot-у посчитать всю математику за вас.",
"The Move Relative step instructs FarmBot to move the specified distance from its current location. For example, if FarmBot is currently at X=1000, Y=1000 and it receives a Move Relative where X=0 and Y=3000, then FarmBot will move to X=1000, Y=4000. If FarmBot must move in multiple directions, it will move diagonally. If you require straight movements along one axis at a time, use multiple Move Relative steps. Move Relative steps should be preceded by a Move Absolute step to ensure you are starting from a known location.": "Шаг \"Относительное перемещение\" дает FarmBot-у команду двигаться на указанное расстояние относительно текущей позиции. Например, если FarmBot сейчас на позиции X=1000, Y=1000, и он получает эту команду с координатами X=0 и Y=3000, он переместится на позицию X=1000, Y=4000. Если перемещение выполняется по нескольким координатам, робот будет двигаться по диагонали. Если вам нужно, чтобы он двигался сначала по одной координате, а потом по другой - используйте несколько шагов \"Относительное перемещение\". Шаги \"Относительное перемещение\" должны следовать после шага \"Абсолютное перемещение\", чтобы движение робота начиналось с известной позиции.",
"The Read Pin step instructs FarmBot to read the current value of the specified pin. Pin Mode: Use digital for a 0 (LOW) or 1 (HIGH) response, and analog for a voltage reading (0-1023 for 0-5V).": "Шаг \"Считать пин\" дает FarmBot-у команду считать текущее значение указанного пина. Режим пина: используйте цифровой режим для считывания 0 (Низкий уровень) или 1 (Высокий уровень), и аналоговый для считывания напряжения (значения 0-1023 соответствуют 0-5В).",
"The Run Farmware step runs a Farmware package. Visit the Farmware page to install and manage Farmware.": "Шаг Run Farmware запускает Farmware package. Откройте страницу Farmware для установки и управления Farmware.",
"The Wait step instructs FarmBot to wait for the specified amount of time. Use it in combination with the Pin Write step to water for a length of time.": "Шаг \"Wait\" дает FarmBot-у команду подождать указанное время. Как пример, используйте его совместно с командой \"Записать пин\" для полива растения в течение указанного количества времени.",
"The Write Pin step instructs FarmBot to set the specified pin on the Arduino to the specified mode and value. Use the digital pin mode for on (1) and off (0) control, and analog pin mode for PWM (pulse width modulation) (0-255).": "Шаг \"Записать пин\" дает FarmBot-у команду перевести пин Arduino в указанный режим и задать ему указанное значение. Используйте цифровой режим для задания состояния Вкл (1) или Выкл (0), и аналоговый режим для ШИМ (Широтно-импульсной модуляции) (0-255).",
"The device has never been seen. Most likely, there is a network connectivity issue on the device's end.": "Робот не был замечен ни разу. Вероятнее всего, есть проблемы подключения к сети на стороне робота",
"The number of motor steps required to move the axis one millimeter.": "Количество шагов двигателя, за которое ось перемещается на один миллиметр.",
"The number of the pin to guard. This pin will be set to the specified state after the duration specified by TIMEOUT.": "Номер пина для защиты. Значение этого пина будет переведено в заданное по прошествии времени, заданном в поле \"Таймаут\".",
"The terms of service have recently changed. You must accept the new terms of service to continue using the site.": "Условия обслуживания недавно изменились. Вы должны принять новые Условия обслуживания, чтобы продолжить пользоваться сайтом.",
"These are the most basic commands FarmBot can execute. Drag and drop them to create sequences for watering, planting seeds, measuring soil properties, and more.": "Это список базовых команд, которые может исполнять FarmBot. Перетаскивайте их, чтобы создавать Функции для полива, посадки семян, измерения параметров почвы и т.д.",
"This Farm Event does not appear to have a valid run time. Perhaps you entered bad dates?": "Для этого события, видимо, не задано корректное время для запуска. Возможно, вы ввели неверные даты?",
"This account did not have a timezone set. Farmbot requires a timezone to operate. We have updated your timezone settings based on your browser. Please verify these settings in the device settings panel. Device sync is recommended.": "На этом аккаунте не установлена временная зона. Farmbot-у для работы требуется задание временной зоны. Мы обновили настройку временной зоны, основываясь на данных вашего браузера. Пожалуйста проверьте эту настройку в окне \"Робот\". После этого рекомендуется синхронизация.",
"This command will not execute correctly because you do not have encoders or endstops enabled for the chosen axis. Enable endstops or encoders from the Device page for: ": "Эта команда не будет работать корректно, потому-что на выбранной оси не задействованы концевые датчики или энкодеры. Задействуйте концевые датчики или энкодеры на странице \"Робот\" для: ",
"This is a list of all of your regimens. Click one to begin editing it.": "Это список всех ваших Режимов. Кликните на один из них, чтобы начать его редактирование.",
"This is a list of all your FarmBot Tools. Click the Edit button to add, edit, or delete tools.": "Это список всех насадок вашего FarmBot-а. Нажмите на кнопку \"Редактировать\", чтобы добавить, отредактировать или удалить насадку.",
"This will restart FarmBot's Raspberry Pi and controller software.": "Это действие перезапустит Raspberry Pi и управляющий софт FarmBot-а.",
"This will shutdown FarmBot's Raspberry Pi. To turn it back on, unplug FarmBot and plug it back in.": "Это действие выключит Raspberry Pi FarmBot-а. Чтобы снова его включить, обесточьте и снова подайте питание на FarmBot.",
"Ticker Notification": "Бегущая строка",
"Time": "Время",
"Time in milliseconds": "Время в миллисекундах",
"TIME ZONE": "ВРЕМЕННАЯ ЗОНА",
"Time in minutes to attempt connecting to WiFi before a factory reset.": "Время в минутах на попытку подключения к WiFi перед сбросом к заводским настройкам.",
"Timeout (sec)": "Таймаут (сек)",
"Timeout after (seconds)": "Таймаут после (секунд)",
"TIMEOUT AFTER (seconds)": "ТАЙМАУТ ПОСЛЕ (секунд)",
"to": "к",
"to add the plant to the map. You can add the plant as many times as you need to before pressing DONE to finish.": "чтобы добавить растение на карту. Вы можете добавить сколько угодно растений, после чего нажмите \"Готово\", чтобы закончить.",
"To State": "Конечное состояние",
"Toast Pop Up": "Всплывающее сообщение",
"Tool": "Насадка",
"TOOL": "НАСАДКА",
"Tool ": "Насадка ",
"TOOL NAME": "НАЗВАНИЕ НАСАДКИ",
"Tool Name": "Название насадки",
"Tool Slots": "Слоты насадок",
"ToolBay ": "База насадок ",
"TOOLBAY NAME": "ДЕРЖАТЕЛЬ НАСАДОК",
"Toolbays are where you store your FarmBot Tools. Each Toolbay has Slots that you can put your Tools in, which should be reflective of your real FarmBot hardware configuration.": "База насадок - это место, где FarmBot хранит свои насадки. В каждой Базе насадок есть Слоты, в которые можно помещать насадки. Указанные здесь настройки обязательно должны совпадать с действительностью.",
"Tools": "Насадки",
"Top Left": "Верх лево",
"Top Right": "Верх право",
"Tried to delete Farm Event": "Попытка удаления события",
"Tried to delete plant": "Попытка удаления растения",
"Tried to save Farm Event": "Попытка сохранения события",
"Tried to save plant": "Попытка сохранения растения",
"Tried to update Farm Event": "Попытка обновления события",
"Turn off to set Web App to English.": "Выключите для перехода на Английский язык.",
"Type": "Тип",
"Unable to delete sequence": "Не удалось удалить функцию",
"Unable to download device credentials": "Не удалось загрузить настройки устройства",
"Unable to load webcam feed.": "Не удалось получить видеопоток с веб-камеры.",
"UP TO DATE": "UP TO DATE",
"UPDATE": "UPDATE",
"USB Camera": "USB-камера",
"Unable to load webcam feed.": "Не удалось получить видеопоток с Web-камеры.",
"Unable to save farm event.": "Не удалось сохранить событие.",
"Unable to send email.": "Не удалось отправить email.",
"Unexpected error occurred, we've been notified of the problem.": "Произошла непредвиденная ошибка. Разработчики были уведомлены",
"Until": "До",
"UP TO DATE": "UP TO DATE",
"Update": "Обновление",
"UPDATE": "UPDATE",
"USB Camera": "USB-камера",
"Use current location": "Использовать текущее положение",
"Use Encoders for Positioning": "Использовать энкодеры для позиционирования",
"Vacuum": "Вакуум",
"Use current location": "Использовать текущее положение",
"Use these manual control buttons to move FarmBot in realtime. Press the arrows for relative movements or type in new coordinates and press GO for an absolute movement. Tip: Press the Home button when you are done so FarmBot is ready to get back to work.": "Используйте эти кнопки ручного управления для движения FarmBot-а в реальном времени. Для задания движения нажимайте стрелки или введите желаемые координаты, после чего нажмите \"СТАРТ\", и FarmBot переместится к этим координатам. Подсказка: Нажмите на кнопку \"Дом\", когда закончите, чтобы FarmBot был готов приступить к своей дальнейшей работе.",
"Use these toggle switches to control FarmBot's peripherals in realtime. To edit and create new peripherals, press the EDIT button. Make sure to turn things off when you're done!": "Используйте эти переключатели для управления периферией FarmBot-а в реальном времени. Для редактирования и задания новой периферии, нажмите кнопку \"Редактировать\". Не забудьте выключить все ненужное, когда закончите!",
"VALUE": "ЗНАЧЕНИЕ",
"VERSION": "ВЕРСИЯ",
"Vacuum": "Вакуум",
"Value": "Значение",
"Variable": "Переменная",
"Verification email resent. Please check your email!": "Письмо для верификации было отправлено повторно. Пожалуйста проверьте свою почту!",
"Verify Password": "Повторите Пароль",
"Version": "Версия",
"VERSION": "ВЕРСИЯ",
"Version {{ version }}": "Версия {{ version }}",
"View": "Вид",
"View and change device settings.": "Просматривайте и изменяйте настройки робота.",
"View and filter log messages.": "Просмотр и поиск в сообщениях лога.",
"WAIT": "ОЖИДАНИЕ",
"WARNING! Deleting your account will permanently delete all of your Sequences , Regimens, Events, and Farm Designer data.Upon deleting your account, FarmBot will cease to function and become inaccessible until it is paired with another web app account. To do this, you will need to reboot your FarmBot so that is goes back into configuration mode for pairing with another user account. When this happens, all of the data on your FarmBot will be overwritten with the new account's data. If the account is brand new, then FarmBot will become a blank slate.": "ВНИМАНИЕ! Удаление вашего аккаунта навсегда удалит все созданные вами Функции, Режимы, События, и данные Дизайнера грядки. При удалении вашего аккаунта, FarmBot прекратит функционировать и будет недоступен до тех пор, пока не будет подключен к другому аккаунту Web-приложения. Чтобы это сделать, вам потребуется перезапустить вашего FarmBot-а, чтобы он перешел в режим настройки и мог быть привязан к другому аккаунту. Когда это произойдет, все данные на вашем Farmbot-е будут перезаписаны данными нового аккаунта. Если этот аккаунт полностью новый, FarmBot будет как чистый лист.",
"WRITE PIN": "ЗАПИСАТЬ ПИН",
"Wait": "Ожидание",
"Warning": "Предупреждение",
"Warning! Opting in to FarmBot OS beta releases may reduce FarmBot system stability. Are you sure?": "Предупреждение! Бета-версии FarmBot OS менее стабильны. Вы уверены?",
"Warning! This is an EXPERIMENTAL feature. This feature may be broken and may break or otherwise hinder your usage of the rest of the app. This feature may disappear or break at any time.": "Предупреждение! Это ЭКСПЕРИМЕНТАЛЬНАЯ возможность. Она может не работать или помешать нормальной работе остальной части приложения. Она может исчезнуть или перестать работать в любой момент.",
"Warning: Farmbot could not guess your timezone. We have defaulted your timezone to UTC, which is less than ideal for most users. Please select your timezone from the dropdown. Device sync is recommended.": "Предупреждене: Farmbot не смог определить вашу временную зону. Мы сбросили ее в UTC, что не подойдет большинству пользователей. Пожалуйста, выберите вашу временную зону из выпадающего списка. После этого рекомендуется синхронизация с роботом.",
"Warning: This will erase all data stored on your FarmBot's SD card, requiring you to reconfigure FarmBot so that it can reconnect to your WiFi network and a web app account. Factory resetting the device will not delete data stored in your web app account. Are you sure you wish to continue?": "Предупреждение: это действие удалит все данные с SD-карты вашего FarmBot-а, вам нужно будет заново настроить FarmBot-а, чтобы он смог подключиться к вашему WiFi и аккаунту в Web-приложении. Сброс к заводским настройкам не удалит данные, хранящиеся на вашем аккаунте Web-приложения. Вы уверены, что хотите продолжить?",
"Warning: This will reset all hardware settings to the default values. Are you sure you wish to continue?": "Предупреждение: это действие сбросит все настройки робота к значениям по умолчанию. Вы уверены, что хотите продолжить?",
"Water": "Подача воды",
"Watering Nozzle": "Поливочная насадка",
"Webcam Feeds": "Веб-камеры",
@ -496,14 +481,14 @@ module.exports = {
"Weeder": "Насадка для прополки",
"Week": "Неделя",
"Welcome to the": "Добро пожаловать в",
"When enabled, FarmBot OS will periodically check for, download, and install updates automatically.": "Если опция включена, FarmBot OS будет периодически проверять, скачивать и устанавливать обновления.",
"Widget load failed.": "Не удалось загрузить окно.",
"Will reboot device.": "Устройство будет перезапущено.",
"Write Pin": "Записать пин",
"WRITE PIN": "ЗАПИСАТЬ ПИН",
"X": "X",
"X (mm)": "X (мм)",
"X Axis": "Ось X",
"X AXIS": "Ось X",
"X Axis": "Ось X",
"X position": "Позиция X",
"X-Offset": "Сдвиг X",
"Y": "Y",
@ -512,12 +497,13 @@ module.exports = {
"Y Axis": "Ось Y",
"Y position": "Позиция Y",
"Y-Offset": "Сдвиг Y",
"yes": "да",
"You are running an old version of FarmBot OS.": "У Вас используется устаревшая версия FarmBot OS.",
"You are scheduling a regimen to run today. Be aware that running a regimen too late in the day may result in skipped regimen tasks. Consider rescheduling this event to tomorrow if this is a concern.": "Вы планируете запустить Режим сегодня. Помните, что Функции, запланированные на время, которое уже прошло, не будут выполнены. Если такие Функции есть, предлагаем перенести выполнение Режима на завтра.",
"You have been logged out.": "Был совершен выход из аккаунта.",
"You haven't made any regimens or sequences yet. Please create a": "Не создан не один режим или функция. Пожалуйста создайте",
"You haven't yet taken any photos with your FarmBot. Once you do, they will show up here.": "Робот FarmBot еще не сделал ни одно фото. Когда фото будут сделаны, они отобразятся сдесь.",
"You may click the button below to resend the email.": "Можно кликнуть на кнопку ниже, чтобы переотправить письмо.",
"You haven't made any regimens or sequences yet. Please create a": "Не создан не один Режим или Функция. Пожалуйста создайте",
"You haven't yet taken any photos with your FarmBot. Once you do, they will show up here.": "Робот FarmBot еще не сделал ни одно фото. Когда фото будут сделаны, они отобразятся здесь.",
"You may click the button below to resend the email.": "Можно кликнуть на кнопку ниже, чтобы отправить письмо заново.",
"You must set a timezone before using the FarmEvent feature.": "Вы должны выбрать временную зону, прежде чем работать с Событиями.",
"Your Name": "Ваше Имя",
"Your password is changed.": "Ваш пароль изменен.",
"Z": "Z",
@ -526,5 +512,121 @@ module.exports = {
"Z Axis": "Ось Z",
"Z position": "Позиция Z",
"Z-Offset": "Сдвиг Z",
"zero {{axis}}": "обнулить {{axis}}"
"active": "активн.",
"back": "назад",
"color": "цвет",
"computer": "ПК",
"days old": "дней",
"delete": "удалить",
"from": "от",
"image": "картинка",
"is": "равно",
"is equal to": "равно",
"is greater than": "больше чем",
"is less than": "меньше чем",
"is not": "не равно",
"is not equal to": "не равно",
"is unknown": "неизвестно",
"max": "максимум",
"mm": "мм",
"move mode": "режим движения",
"new sequence {{ num }}": "новая Функция {{ num }}",
"no": "нет",
"normal": "нормально",
"plant icon": "иконка растения",
"radius": "радиус",
"saved": "сохранено",
"smartphone": "смартфона",
"tablet": "планшета",
"to add the plant to the map. You can add the plant as many times as you need to before pressing DONE to finish.": "чтобы добавить растение на карту. Вы можете добавить сколько угодно растений, после чего нажмите \"Готово\", чтобы закончить.",
"zero {{axis}}": "обнулить {{axis}}",
" or": " или",
"ACCELERATE FOR (steps)": "УСКОРЕНИЕ (шаги)",
"ALLOW NEGATIVES": "РАЗРЕШИТЬ ОТРИЦАТЕЛЬНЫЕ",
"Add": "Добавить",
"Agree to Terms of Service": "Я согласен с условиями обслуживания",
"BOOTING": "ЗАГРУЗКА",
"Begin": "Начало",
"Bot ready": "Робот Готов",
"CONTROLLER": "Контроллер",
"Camera": "Камера",
"Choose a species": "Выберите вид",
"Complete": "Завершено",
"Confirm Password": "Повторите пароль",
"Controls": "Управление",
"Could not download sync data": "Невозможно загрузить синхронизированную информацию",
"Create An Account": "Создать Аккаунт",
"Crop Info": "Инфо об Урожае",
"DELETE ACCOUNT": "УДАЛИТЬ АККАУНТ",
"DEVICE": "Робот",
"DISCONNECTED": "ОТКЛЮЧЕНО",
"DRAG STEP HERE": "ПЕРЕТЯНИ СЮДА",
"Debug": "Отладка",
"Device": "Робот",
"Disconnected.": "Отключено.",
"EDIT": "РЕДАКТИРОВАТЬ",
"ENABLE ENCODERS": "ВКЛЮЧИТЬ ЭНКОДЕРЫ",
"EXECUTE SCRIPT": "ВЫПОЛНИТЬ СЦЕНАРИЙ",
"Error establishing socket connection": "Ошибка установки сетевого соединения",
"Execute Script": "Выполнить Скрипт",
"Farm Designer": "Дизайнер грядки",
"Farm designer": "Дизайнер грядки",
"Forgot Password": "Забыл пароль",
"INVERT ENDPOINTS": "ИНВЕНТИРОВАТЬ ENDSTOPS",
"INVERT MOTORS": "ИНВЕНТИРОВАТЬ ДВИГАТЕЛИ",
"LENGTH (m)": "Длинна (м)",
"MAINTENANCE DOWNTIME": "ТЕХ. ОБСЛУЖИВАНИЕ",
"Max Speed (steps/s)": "Макс. скорость (шагов/с)",
"Minimum Speed (steps/s)": "Мин. скорость (шагов/с)",
"NETWORK": "СЕТЬ",
"No Sequence selected. Click one in the Sequences panel to edit, or click \"+\" to create a new one.": "Функция не выбрана. Выберите одну из них для редактирования или кликните \"+\" чтобы создать новую.",
"Not Connected to bot": "Нет соединения с роботом",
"Parameters": "Параметры",
"Pin #": "Пин №",
"Pin {{num}}": "Пин {{num}}",
"Pins": "Пины",
"Problem Loading Terms of Service": "Проблема загрузки правил и соглашений",
"Received": "Получено",
"Repeats Every": "Повторять каждые",
"SLOT": "СЛОТ",
"STATUS": "СТАТУС",
"SYNC ERROR": "ОШИБКА СИНХР.",
"SYNC NOW": "СИНХРОНИЗИРОВАТЬ",
"SYNCED": "СИНХРОНИЗИРОВАНО",
"SYNCING": "СИНХРОНИЗИРУЮ",
"Save ": "Сохранить ",
"Saved ": "Сохранено ",
"Saving": "Сохраняю",
"Send Password reset": "Отправить сброс пароля",
"Sensors": "Датчики",
"Sent": "Отправлено",
"Socket Connection Established": "Сетевое соединение установлено",
"Speed": "Скорость",
"Speed (%)": "Скорость (%)",
"Steps": "Шаги",
"Sync Required": "Требуется синхронизация",
"TIMEOUT AFTER (seconds)": "ТАЙМАУТ ПОСЛЕ (секунд)",
"TOOL": "НАСАДКА",
"TOOL NAME": "НАЗВАНИЕ НАСАДКИ",
"TOOLBAY NAME": "ДЕРЖАТЕЛЬ НАСАДОК",
"Tried to delete Farm Event": "Попытка удаления события",
"Tried to delete plant": "Попытка удаления растения",
"Tried to save Farm Event": "Попытка сохранения события",
"Tried to save plant": "Попытка сохранения растения",
"Tried to update Farm Event": "Попытка обновления события",
"Unable to delete sequence": "Не удалось удалить Функцию",
"Unable to download device credentials": "Не удалось загрузить настройки устройства",
"Updating... ": "Обновляю... ",
"Verify Password": "Повторите Пароль",
"Version": "Версия",
"When enabled, device resources such as sequences and regimens will be sent to the device automatically. This removes the need to push \"SYNC\" after making changes in the web app. Changes to running sequences and regimens while auto sync is enabled will result in instantaneous change.": "Если включено, настройки, такие как список Функций и Режимов будут автоматически отправляться роботу. Это убирает необходимость нажимать кнопку \"СИНХРОНИЗИРОВАТЬ\" после внесения изменений в Web-приложении. Изменения, сделанные в Режимах и Функциях, отправляются роботу мгновенно.",
"Your web browser is unable to connect to the message broker. You might be behind a firewall or disconnected from the Internet. Check your network settings. View Device > Connectivity for more details.": "Вашему браузеру не удалось подключиться к центру сообщений. Причиной может быть обрыв соединения с интернетом или блокировка вашим Файерволом. Проверьте настройки вашего подключения. Ознакомьтесь с окном \"Робот\" > \"Связь\" для дополнительной информации.",
"calling FarmBot with credentials": "запрос данных",
"downloading device credentials": "загрузка данных устройства",
"first.": ".",
"inactive": "неактивн.",
"initiating connection": "установка соединения",
"never connected to device": "никогда не подключалось к устройству",
"to": "к",
"yes": "да"
}

View File

@ -0,0 +1,44 @@
jest.mock("fastclick", () => ({ attach: jest.fn() }));
import {
topLevelRoutes,
designerRoutes,
maybeReplaceDesignerModules
} from "../route_config";
import { RouterState, RedirectFunction } from "react-router";
async function makeSureTheyAreRoutes(input: typeof topLevelRoutes.childRoutes) {
const cb = jest.fn();
await Promise.all(input.map(route => route.getComponent(undefined, cb)));
expect(cb).toHaveBeenCalled();
expect(cb).toHaveBeenCalledTimes(input.length);
cb.mock.calls.map(x => expect(!!x[1]).toBeTruthy());
}
describe("top level routes", () => {
it("generates all of them",
() => makeSureTheyAreRoutes(topLevelRoutes.childRoutes));
});
describe("designer routes", () => {
it("generates all of them",
() => makeSureTheyAreRoutes(designerRoutes.childRoutes));
});
describe("maybeReplaceDesignerModules", () => {
it("does replace the route", () => {
const pathname = "/app/designer";
const next = { location: { pathname } } as RouterState;
const replace = jest.fn() as RedirectFunction;
maybeReplaceDesignerModules(next, replace);
expect(replace).toHaveBeenCalledWith(`${pathname}/plants`);
});
it("does not replace the route", () => {
const pathname = "/app/nope";
const next = { location: { pathname } } as RouterState;
const replace = jest.fn() as RedirectFunction;
maybeReplaceDesignerModules(next, replace);
expect(replace).not.toHaveBeenCalled();
});
});

View File

@ -10,7 +10,7 @@ import {
import { SpecialStatus } from "../../resources/tagged_resources";
import Axios from "axios";
import { API } from "../../api/index";
import { prettyPrintApiErrors } from "../../util";
import { prettyPrintApiErrors, equals } from "../../util";
import { success, error } from "farmbot-toastr/dist";
interface PasswordForm {
@ -19,26 +19,18 @@ interface PasswordForm {
password: string;
}
interface ChangePWState {
status: SpecialStatus;
form: PasswordForm
}
const EMPTY_FORM = {
new_password: "",
new_password_confirmation: "",
password: ""
};
interface ChangePWState { status: SpecialStatus; form: PasswordForm }
const EMPTY_FORM =
({ new_password: "", new_password_confirmation: "", password: "" });
export class ChangePassword extends React.Component<{}, ChangePWState> {
state: ChangePWState = {
status: SpecialStatus.SAVED,
form: EMPTY_FORM
};
state: ChangePWState = { status: SpecialStatus.SAVED, form: EMPTY_FORM };
/** Set the `status` flag to `undefined`, but only if the form is empty.
* Useful when the user manually clears the form. */
maybeClearForm = () => wowFixMe(EMPTY_FORM, this.state.form) ?
this.clearForm() : false;
maybeClearForm =
() => equals(EMPTY_FORM, this.state.form) ? this.clearForm() : false;
clearForm = () => this.setState({ status: SpecialStatus.SAVED, form: EMPTY_FORM });
@ -103,9 +95,3 @@ export class ChangePassword extends React.Component<{}, ChangePWState> {
</Widget>;
}
}
// TODO: Why does Object.is() not work when comparing EMPTY_FORM to
// this.state.form? Using this in the meantime. PRs and feedback welcome.
function wowFixMe<T>(l: T, r: T): boolean {
return (JSON.stringify(l) === JSON.stringify(r));
}

View File

@ -81,7 +81,7 @@ const MUST_LOAD: ResourceName[] = [
export class App extends React.Component<AppProps, {}> {
componentDidCatch(x: Error, y: React.ErrorInfo) { catchErrors(x, y); }
get isLoaded() {
private get isLoaded() {
return (MUST_LOAD.length ===
_.intersection(this.props.loaded, MUST_LOAD).length);
}

View File

@ -0,0 +1,48 @@
import { validBotLocationData } from "../util";
import { JogMovementControlsProps } from "./interfaces";
const _ = (nr_steps: number | undefined, steps_mm: number | undefined) => {
return (nr_steps || 0) / (steps_mm || 1);
};
function calculateAxialLengths(props: JogMovementControlsProps) {
const mp = props.bot.hardware.mcu_params;
return {
x: _(mp.movement_axis_nr_steps_x, mp.movement_step_per_mm_x),
y: _(mp.movement_axis_nr_steps_y, mp.movement_step_per_mm_y),
z: _(mp.movement_axis_nr_steps_z, mp.movement_step_per_mm_z),
};
}
export function buildDirectionProps(props: JogMovementControlsProps) {
const { location_data, mcu_params } = props.bot.hardware;
const botLocationData = validBotLocationData(location_data);
const lengths = calculateAxialLengths(props);
return {
x: {
isInverted: props.x_axis_inverted,
stopAtHome: !!mcu_params.movement_stop_at_home_x,
stopAtMax: !!mcu_params.movement_stop_at_max_x,
axisLength: lengths.x,
negativeOnly: !!mcu_params.movement_home_up_x,
position: botLocationData.position.x
},
y: {
isInverted: props.y_axis_inverted,
stopAtHome: !!mcu_params.movement_stop_at_home_y,
stopAtMax: !!mcu_params.movement_stop_at_max_y,
axisLength: lengths.y,
negativeOnly: !!mcu_params.movement_home_up_y,
position: botLocationData.position.y
},
z: {
isInverted: props.z_axis_inverted,
stopAtHome: !!mcu_params.movement_stop_at_home_z,
stopAtMax: !!mcu_params.movement_stop_at_max_z,
axisLength: lengths.z,
negativeOnly: !!mcu_params.movement_home_up_z,
position: botLocationData.position.z
},
};
}

View File

@ -3,41 +3,10 @@ import { DirectionButton } from "./direction_button";
import { homeAll } from "../devices/actions";
import { JogMovementControlsProps } from "./interfaces";
import { getDevice } from "../device";
import { validBotLocationData } from "../util";
import { buildDirectionProps } from "./direction_axes_props";
export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
render() {
const { location_data, mcu_params } = this.props.bot.hardware;
const botLocationData = validBotLocationData(location_data);
const directionAxesProps = {
x: {
isInverted: this.props.x_axis_inverted,
stopAtHome: !!mcu_params.movement_stop_at_home_x,
stopAtMax: !!mcu_params.movement_stop_at_max_x,
axisLength: (mcu_params.movement_axis_nr_steps_x || 0)
/ (mcu_params.movement_step_per_mm_x || 1),
negativeOnly: !!mcu_params.movement_home_up_x,
position: botLocationData.position.x
},
y: {
isInverted: this.props.y_axis_inverted,
stopAtHome: !!mcu_params.movement_stop_at_home_y,
stopAtMax: !!mcu_params.movement_stop_at_max_y,
axisLength: (mcu_params.movement_axis_nr_steps_y || 0)
/ (mcu_params.movement_step_per_mm_y || 1),
negativeOnly: !!mcu_params.movement_home_up_y,
position: botLocationData.position.y
},
z: {
isInverted: this.props.z_axis_inverted,
stopAtHome: !!mcu_params.movement_stop_at_home_z,
stopAtMax: !!mcu_params.movement_stop_at_max_z,
axisLength: (mcu_params.movement_axis_nr_steps_z || 0)
/ (mcu_params.movement_step_per_mm_z || 1),
negativeOnly: !!mcu_params.movement_home_up_z,
position: botLocationData.position.z
},
};
export function JogButtons(props: JogMovementControlsProps) {
const directionAxesProps = buildDirectionProps(props);
return <table className="jog-table" style={{ border: 0 }}>
<tbody>
<tr>
@ -53,8 +22,8 @@ export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
axis="y"
direction="up"
directionAxisProps={directionAxesProps.y}
steps={this.props.bot.stepSize || 1000}
disabled={this.props.disabled} />
steps={props.bot.stepSize || 1000}
disabled={props.disabled} />
</td>
<td />
<td />
@ -63,8 +32,8 @@ export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
axis="z"
direction="up"
directionAxisProps={directionAxesProps.z}
steps={this.props.bot.stepSize || 1000}
disabled={this.props.disabled} />
steps={props.bot.stepSize || 1000}
disabled={props.disabled} />
</td>
</tr>
<tr>
@ -72,7 +41,7 @@ export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
<button
className="i fa fa-home arrow-button fb-button"
onClick={() => homeAll(100)}
disabled={this.props.disabled || false} />
disabled={props.disabled || false} />
</td>
<td />
<td>
@ -80,24 +49,24 @@ export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
axis="x"
direction="left"
directionAxisProps={directionAxesProps.x}
steps={this.props.bot.stepSize || 1000}
disabled={this.props.disabled} />
steps={props.bot.stepSize || 1000}
disabled={props.disabled} />
</td>
<td>
<DirectionButton
axis="y"
direction="down"
directionAxisProps={directionAxesProps.y}
steps={this.props.bot.stepSize || 1000}
disabled={this.props.disabled} />
steps={props.bot.stepSize || 1000}
disabled={props.disabled} />
</td>
<td>
<DirectionButton
axis="x"
direction="right"
directionAxisProps={directionAxesProps.x}
steps={this.props.bot.stepSize || 1000}
disabled={this.props.disabled} />
steps={props.bot.stepSize || 1000}
disabled={props.disabled} />
</td>
<td />
<td>
@ -105,8 +74,8 @@ export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
axis="z"
direction="down"
directionAxisProps={directionAxesProps.z}
steps={this.props.bot.stepSize || 1000}
disabled={this.props.disabled} />
steps={props.bot.stepSize || 1000}
disabled={props.disabled} />
</td>
</tr>
<tr>
@ -115,4 +84,3 @@ export class JogButtons extends React.Component<JogMovementControlsProps, {}> {
</tbody>
</table>;
}
}

View File

@ -48,7 +48,7 @@ export class ToggleButton extends React.Component<ToggleButtonProps, {}> {
disabled={!!this.props.disabled}
className={this.css() + addCss}
onClick={cb}>
{this.caption()}
{t(this.caption())}
</button>;
}
}

View File

@ -5,12 +5,12 @@ import { Xyz, BotPosition } from "./devices/interfaces";
import { McuParams } from "farmbot";
import { getDevice } from "./device";
export interface State {
interface State {
isOpen: boolean;
stepSize: number;
}
export interface Props {
interface Props {
dispatch: Function;
axisInversion: Record<Xyz, boolean>;
botPosition: BotPosition;
@ -24,7 +24,7 @@ export class ControlsPopup extends React.Component<Props, Partial<State>> {
stepSize: 100
};
toggle = (property: keyof State) => () =>
private toggle = (property: keyof State) => () =>
this.setState({ [property]: !this.state[property] });
public render() {

View File

@ -46,8 +46,7 @@ export class LastSeen extends React.Component<LastSeenProps, {}> {
};
return t(text, data);
} else {
return t(" The device has never been seen. Most likely, " +
"there is a network connectivity issue on the device's end.");
return t("The device has never been seen. Most likely, there is a network connectivity issue on the device's end.");
}
}

View File

@ -31,8 +31,8 @@ export class HardwareSettings extends
<SaveBtn
status={bot.isUpdating ? SpecialStatus.SAVING : SpecialStatus.SAVED}
dirtyText={" "}
savingText={"Updating..."}
savedText={"saved"}
savingText={t("Updating...")}
savedText={t("saved")}
hidden={false} />
</MustBeOnline>
</WidgetHeader>

View File

@ -8,9 +8,11 @@ jest.mock("../../../../device", () => ({
import * as React from "react";
import { MotorsProps } from "../../interfaces";
import { bot } from "../../../../__test_support__/fake_state/bot";
import { Motors, StepsPerMmSettings } from "../motors";
import { Motors } from "../motors";
import { render, shallow, mount } from "enzyme";
import { McuParamName } from "farmbot";
import { StepsPerMmSettings } from "../steps_per_mm_settings";
import { NumericMCUInputGroup } from "../../numeric_mcu_input_group";
describe("<Motors/>", () => {
beforeEach(function () {
@ -88,12 +90,11 @@ describe("<StepsPerMmSettings/>", () => {
expect(firstInputProps.setting).toBe("steps_per_mm_x");
});
it("renders mcu settings", () => {
fit("renders mcu settings", () => {
const p = fakeProps();
p.bot.hardware.informational_settings.firmware_version = "5.0.5R";
const wrapper = shallow(<StepsPerMmSettings {...p} />);
const firstInputProps = wrapper.find("NumericMCUInputGroup")
.first().props();
const firstInputProps = wrapper.find(NumericMCUInputGroup).first().props();
expect(firstInputProps.x).toBe("movement_step_per_mm_x");
});
});

View File

@ -6,55 +6,13 @@ import { SpacePanelToolTip } from "../space_panel_tool_tip";
import { ToggleButton } from "../../../controls/toggle_button";
import { settingToggle } from "../../actions";
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
import { BotConfigInputBox } from "../bot_config_input_box";
import { MotorsProps } from "../interfaces";
import { Row, Col } from "../../../ui/index";
import { Header } from "./header";
import { Collapse } from "@blueprintjs/core";
import { McuInputBox } from "../mcu_input_box";
import { minFwVersionCheck } from "../../../util";
export function StepsPerMmSettings(props: MotorsProps) {
const { dispatch, bot, sourceFbosConfig } = props;
const { firmware_version } = bot.hardware.informational_settings;
if (minFwVersionCheck(firmware_version, "5.0.5")) {
return <NumericMCUInputGroup
name={t("Steps per MM")}
tooltip={ToolTips.STEPS_PER_MM}
x={"movement_step_per_mm_x"}
y={"movement_step_per_mm_y"}
z={"movement_step_per_mm_z"}
bot={bot}
dispatch={dispatch} />;
} else {
return <Row>
<Col xs={6}>
<label>
{t("Steps per MM")}
</label>
<SpacePanelToolTip tooltip={ToolTips.STEPS_PER_MM} />
</Col>
<Col xs={2}>
<BotConfigInputBox
setting="steps_per_mm_x"
sourceFbosConfig={sourceFbosConfig}
dispatch={dispatch} />
</Col>
<Col xs={2}>
<BotConfigInputBox
setting="steps_per_mm_y"
sourceFbosConfig={sourceFbosConfig}
dispatch={dispatch} />
</Col>
<Col xs={2}>
<BotConfigInputBox
setting="steps_per_mm_z"
sourceFbosConfig={sourceFbosConfig}
dispatch={dispatch} />
</Col>
</Row>;
}
}
import { StepsPerMmSettings } from "./steps_per_mm_settings";
export function Motors(props: MotorsProps) {
const { dispatch, bot, sourceFbosConfig } = props;

View File

@ -0,0 +1,57 @@
import * as React from "react";
import { BotConfigInputBox } from "../bot_config_input_box";
import { MotorsProps } from "../interfaces";
import { minFwVersionCheck } from "../../../util";
import { ToolTips } from "../../../constants";
import { NumericMCUInputGroup } from "../numeric_mcu_input_group";
import { Row, Col } from "../../../ui";
import { SpacePanelToolTip } from "../space_panel_tool_tip";
import { t } from "i18next";
function LegacyStepsPerMm(props: MotorsProps) {
const { dispatch, sourceFbosConfig } = props;
return <Row>
<Col xs={6}>
<label>
{t("Steps per MM")}
</label>
<SpacePanelToolTip tooltip={ToolTips.STEPS_PER_MM} />
</Col>
<Col xs={2}>
<BotConfigInputBox
setting="steps_per_mm_x"
sourceFbosConfig={sourceFbosConfig}
dispatch={dispatch} />
</Col>
<Col xs={2}>
<BotConfigInputBox
setting="steps_per_mm_y"
sourceFbosConfig={sourceFbosConfig}
dispatch={dispatch} />
</Col>
<Col xs={2}>
<BotConfigInputBox
setting="steps_per_mm_z"
sourceFbosConfig={sourceFbosConfig}
dispatch={dispatch} />
</Col>
</Row>;
}
export function StepsPerMmSettings(props: MotorsProps) {
const { dispatch, bot } = props;
const { firmware_version } = bot.hardware.informational_settings;
if (minFwVersionCheck(firmware_version, "5.0.5")) {
return <NumericMCUInputGroup
name={t("Steps per MM")}
tooltip={ToolTips.STEPS_PER_MM}
x={"movement_step_per_mm_x"}
y={"movement_step_per_mm_y"}
z={"movement_step_per_mm_z"}
bot={bot}
dispatch={dispatch} />;
} else {
return <LegacyStepsPerMm {...props} />;
}
}

View File

@ -18,7 +18,7 @@ export function Diagnosis(props: DiagnosisProps) {
const diagnosisStatus =
props.userMQTT && props.botAPI && props.botMQTT && props.botFirmware;
const diagnosisColor = diagnosisStatus ? "green" : "red";
const title = diagnosisStatus ? "Ok" : "Error";
const title = diagnosisStatus ? t("Ok") : t("Error");
return <div>
<div className={"connectivity-diagnosis"}>
<h4>{t("Diagnosis")}</h4>

View File

@ -247,17 +247,17 @@ describe("mapResourcesToCalendar(): regimen farm events", () => {
}
];
it("returns calendar rows", () => {
fit("returns calendar rows", () => {
const testTime = moment("2017-12-15T01:00:00.000Z");
const calendar = mapResourcesToCalendar(
fakeRegFEResources().index, testTime);
const calendar =
mapResourcesToCalendar(fakeRegFEResources().index, testTime);
expect(calendar.getAll()).toEqual(fakeRegimenFE);
});
it("doesn't return calendar row after event is over", () => {
fit("doesn't return calendar row after event is over", () => {
const testTime = moment("2017-12-27T01:00:00.000Z");
const calendar = mapResourcesToCalendar(
fakeRegFEResources().index, testTime);
const calendar =
mapResourcesToCalendar(fakeRegFEResources().index, testTime);
expect(calendar.getAll()).toEqual([]);
});
});

View File

@ -128,7 +128,6 @@ export function mapStateToPropsAddEdit(props: Everything): AddEditFarmEventProps
const regimensById = indexRegimenById(props.resources.index);
const sequencesById = indexSequenceById(props.resources.index);
const farmEventsById = indexFarmEventById(props.resources.index);
const farmEvents = selectAllFarmEvents(props.resources.index);
const getFarmEvent = (): TaggedFarmEvent | undefined => {

View File

@ -1,7 +1,7 @@
import { browserHistory } from "react-router";
export let history = browserHistory;
export let push = (url: string) => history.push(url);
export let pathname = history.getCurrentLocation().pathname;
export function getPathArray() {
return history.getCurrentLocation().pathname.split("/");
}

View File

@ -61,10 +61,10 @@ export class HotKeys extends React.Component<Props, Partial<State>> {
</div>;
}
toggle = (property: keyof State) => () =>
private toggle = (property: keyof State) => () =>
this.setState({ [property]: !this.state[property] });
hotkeys(dispatch: Function, slug: string) {
private hotkeys(dispatch: Function, slug: string) {
const idx = _.findIndex(links, { slug });
const right = "/app/" + (links[idx + 1] || links[0]).slug;
const left = "/app/" + (links[idx - 1] || links[links.length - 1]).slug;
@ -103,7 +103,7 @@ export class HotKeys extends React.Component<Props, Partial<State>> {
return hotkeyMap;
}
renderHotkeys() {
public renderHotkeys() {
const slug = getPathArray()[2];
return <Hotkeys>
{

View File

@ -1,6 +1,6 @@
import axios from "axios";
import { InitOptions } from "i18next";
/** @public */
export function generateUrl(langCode: string) {
const lang = langCode.slice(0, 2);
const url = "//" + location.host.split(":")

View File

@ -12,19 +12,8 @@ export let METHOD_MAP: Dictionary<DataChangeType> = {
};
export let METHODS = ["post", "put", "patch", "delete"];
export let RESOURCES: ResourceName[] = [
"Point",
"Regimen",
"Peripheral",
"Log",
"Sequence",
"FarmEvent",
"Point",
"Device"
];
/** Temporary stub until auto_sync rollout. TODO: Remove */
export const RESOURNCE_NAME_IN_URL = [
const RESOURNCE_NAME_IN_URL = [
"device",
"farm_events",
"logs",

View File

@ -26,6 +26,7 @@ export function responseFulfilled(input: AxiosResponse): AxiosResponse {
return input;
}
let ONLY_ONCE = true;
export function responseRejected(x: SafeError | undefined) {
if (x && isSafeError(x)) {
dispatchNetworkUp("user.api");
@ -50,7 +51,8 @@ export function responseRejected(x: SafeError | undefined) {
break;
case 451:
// DONT REFACTOR: I want to use alert() because it's blocking.
alert(t(Content.TOS_UPDATE));
ONLY_ONCE && alert(t(Content.TOS_UPDATE));
ONLY_ONCE = false;
window.location.href = "/tos_update";
break;
}

View File

@ -10,17 +10,6 @@ import { RestResources } from "./resources/interfaces";
in the UI. Only certain colors are valid. */
export type Color = FarmBotJsColor;
export interface SelectOptionsParams {
label: string;
value: string | number | undefined;
disabled?: boolean;
field?: string;
type?: string;
x?: number;
y?: number;
z?: number;
}
export interface PinBinding {
id?: number;
sequence_id: number;
@ -144,9 +133,3 @@ export type Point =
| PlantPointer;
export type PointerTypeName = Point["pointer_type"];
export const POINTER_NAMES: Readonly<PointerTypeName>[] = [
"Plant",
"GenericPointer",
"ToolSlot"
];

View File

@ -1,4 +1,5 @@
import * as React from "react";
import { t } from "i18next";
import { Session } from "./session";
import { BooleanSetting } from "./session_keys";
@ -73,7 +74,7 @@ export function LoadingPlant() {
fontSize={35}
textAnchor="middle"
fill="#434343">
Loading...
{t("Loading...")}
</text>
</svg>
</div>;

View File

@ -30,7 +30,7 @@ export const LogsFilterMenu = (props: LogsFilterMenuProps) => {
return <fieldset key={logType}>
<label>
<div className={`saucer ${logType}`} />
{_.startCase(logType)}
{t(_.startCase(logType))}
</label>
<button
className={"fb-button fb-toggle-button " + btnColor(logType)}

View File

@ -0,0 +1,42 @@
import { TaggedResource } from "./tagged_resources";
import { CowardlyDictionary } from "../util";
import { ResourceIndex } from "./interfaces";
import { assertUuid } from "./selectors";
interface IndexLookupDictionary<T extends TaggedResource>
extends CowardlyDictionary<T> { }
interface Indexer<T extends TaggedResource> {
(index: ResourceIndex): IndexLookupDictionary<T>;
}
interface MapperFn<T extends TaggedResource> {
(item: T): T | undefined;
}
/** Build a function,
* that returns a function,
* that returns a dictionary,
* that contains TaggedResource of kind T
* that uses the resource's id as the dictionary key.
* */
export const buildIndexer =
<T extends TaggedResource>(kind: T["kind"], mapper?: MapperFn<T>): Indexer<T> => {
return function (index: ResourceIndex, ) {
const noop: MapperFn<T> = (i) => i;
const output: CowardlyDictionary<T> = {};
const uuids = index.byKind[kind];
const m = mapper || noop;
uuids.map(uuid => {
assertUuid(kind, uuid);
const resource = index.references[uuid];
if (resource
&& (resource.kind === kind)
&& resource.body.id
&& m(resource as T)) {
output[resource.body.id] = resource as T;
}
});
return output;
};
};

View File

@ -29,10 +29,12 @@ import {
TaggedDevice,
TaggedFbosConfig,
TaggedWebAppConfig,
SpecialStatus
SpecialStatus,
TaggedPoint
} from "./tagged_resources";
import { CowardlyDictionary, betterCompact, sortResourcesById, bail } from "../util";
import { isNumber } from "util";
import { buildIndexer } from "./selector_support";
type StringMap = CowardlyDictionary<string>;
/** Similar to findId(), but does not throw exceptions. Do NOT use this method
@ -219,74 +221,17 @@ export function selectAllSequences(index: ResourceIndex) {
return findAll(index, "Sequence") as TaggedSequence[];
}
export function indexSequenceById(index: ResourceIndex) {
const output: CowardlyDictionary<TaggedSequence> = {};
const uuids = index.byKind.Sequence;
uuids.map(uuid => {
assertUuid("Sequence", uuid);
const sequence = index.references[uuid];
if (sequence && isTaggedSequence(sequence) && sequence.body.id) {
output[sequence.body.id] = sequence;
}
});
return output;
}
export function indexRegimenById(index: ResourceIndex) {
const output: CowardlyDictionary<TaggedRegimen> = {};
const uuids = index.byKind.Regimen;
uuids.map(uuid => {
assertUuid("Regimen", uuid);
const regimen = index.references[uuid];
if (regimen && isTaggedRegimen(regimen) && regimen.body.id) {
output[regimen.body.id] = regimen;
}
});
return output;
}
export function indexFarmEventById(index: ResourceIndex) {
const output: CowardlyDictionary<TaggedFarmEvent> = {};
const uuids = index.byKind.FarmEvent;
uuids.map(uuid => {
assertUuid("FarmEvent", uuid);
const farmEvent = index.references[uuid];
if (farmEvent && isTaggedFarmEvent(farmEvent) && farmEvent.body.id) {
output[farmEvent.body.id] = farmEvent;
}
});
return output;
}
export function indexByToolId(index: ResourceIndex) {
const output: CowardlyDictionary<TaggedTool> = {};
const uuids = index.byKind.Tool;
uuids.map(uuid => {
assertUuid("Tool", uuid);
const Tool = index.references[uuid];
if (Tool && isTaggedTool(Tool) && Tool.body.id) {
output[Tool.body.id] = Tool;
}
});
return output;
}
export function indexBySlotId(index: ResourceIndex) {
const output: CowardlyDictionary<TaggedToolSlotPointer> = {};
const uuids = index.byKind.Point;
uuids.map(uuid => {
assertUuid("Point", uuid);
const tool_slot = index.references[uuid];
if (tool_slot && isTaggedToolSlotPointer(tool_slot) && tool_slot.body.id) {
output[tool_slot.body.id] = tool_slot;
}
});
return output;
const mapper = (i: TaggedPoint): TaggedToolSlotPointer | undefined => {
if (i.kind == "Point" && (i.body.pointer_type === "ToolSlot")) {
return i as TaggedToolSlotPointer;
}
return undefined;
};
export const indexBySlotId = buildIndexer<TaggedToolSlotPointer>("Point", mapper);
export const indexSequenceById = buildIndexer<TaggedSequence>("Sequence");
export const indexRegimenById = buildIndexer<TaggedRegimen>("Regimen");
export const indexFarmEventById = buildIndexer<TaggedFarmEvent>("FarmEvent");
export const indexByToolId = buildIndexer<TaggedTool>("Tool");
export function assertUuid(expected: ResourceName, actual: string | undefined) {
if (actual && !actual.startsWith(expected)) {

View File

@ -0,0 +1,80 @@
import { App } from "./app";
import { crashPage } from "./crash_page";
import { RouterState, RedirectFunction } from "react-router";
/** These methods are a way to determine how to load certain modules
* based on the device (mobile or desktop) for optimization/css purposes.
*/
export function maybeReplaceDesignerModules(next: RouterState,
replace: RedirectFunction) {
if (next.location.pathname === "/app/designer") {
replace(`${next.location.pathname}/plants`);
}
}
function page(path: string, getter: () => Promise<React.ReactType>) {
return {
path,
getComponent(_: void, cb: Function) {
const ok = (component: React.ReactType) => cb(undefined, component);
const no = (e: object) => cb(undefined, crashPage(e));
return getter().then(ok, no);
}
};
}
const controlsRoute =
page("app/controls", async () => (await import("./controls/controls")).Controls);
export const designerRoutes = {
path: "app/designer",
onEnter: maybeReplaceDesignerModules,
getComponent(_discard: void, cb: Function) {
import("./farm_designer/index")
.then(module => cb(undefined, module.FarmDesigner))
.catch((e: object) => cb(undefined, crashPage(e)));
},
childRoutes: [
page("plants",
async () => (await import("./farm_designer/plants/plant_inventory")).Plants),
page("plants/crop_search",
async () => (await import("./farm_designer/plants/crop_catalog")).CropCatalog),
page("plants/crop_search/:crop",
async () => (await import("./farm_designer/plants/crop_info")).CropInfo),
page("plants/crop_search/:crop/add",
async () => (await import("./farm_designer/plants/add_plant")).AddPlant),
page("plants/select",
async () => (await import("./farm_designer/plants/select_plants")).SelectPlants),
page("plants/move_to", async () => (await import("./farm_designer/plants/move_to")).MoveTo),
page("plants/create_point",
async () => (await import("./farm_designer/plants/create_points")).CreatePoints),
page("plants/:plant_id",
async () => (await import("./farm_designer/plants/plant_info")).PlantInfo),
page("plants/:plant_id/edit",
async () => (await import("./farm_designer/plants/edit_plant_info")).EditPlantInfo),
page("farm_events",
async () => (await import("./farm_designer/farm_events/farm_events")).FarmEvents),
page("farm_events/add",
async () => (await import("./farm_designer/farm_events/add_farm_event")).AddFarmEvent),
page("farm_events/:farm_event_id",
async () => (await import("./farm_designer/farm_events/edit_farm_event")).EditFarmEvent),
]
};
export const topLevelRoutes = {
component: App,
indexRoute: controlsRoute,
childRoutes: [
page("app/account", async () => (await import("./account/index")).Account),
controlsRoute,
page("app/device", async () => (await import("./devices/devices")).Devices),
page("app/farmware", async () => (await import("./farmware/index")).FarmwarePage),
page("app/regimens", async () => (await import("./regimens/index")).Regimens),
page("app/regimens/:regimen", async () => (await import("./regimens/index")).Regimens),
page("app/sequences", async () => (await import("./sequences/sequences")).Sequences),
page("app/sequences/:sequence", async () => (await import("./sequences/sequences")).Sequences),
page("app/tools", async () => (await import("./tools/index")).Tools),
page("app/logs", async () => (await import("./logs/index")).Logs),
page("*", async () => (await import("./404")).FourOhFour),
designerRoutes,
]
};

View File

@ -1,8 +1,7 @@
import "./css/_index.scss";
import * as React from "react";
import { Provider } from "react-redux";
import { Router, RedirectFunction, RouterState } from "react-router";
import { App } from "./app";
import { Router } from "react-router";
import { store as _store } from "./redux/store";
import { history } from "./history";
import { Store } from "./redux/interfaces";
@ -10,27 +9,9 @@ import { ready } from "./config/actions";
import { Session } from "./session";
import { attachToRoot } from "./util";
import { Callback } from "i18next";
import { crashPage } from "./crash_page";
import { topLevelRoutes } from "./route_config";
const key = "Jan 20 23:52";
if (!localStorage[key]) {
localStorage[key] = JSON.stringify("X");
location.reload(true);
}
interface RootComponentProps {
store: Store;
}
const controlsRoute = {
path: "app/controls",
getComponent(_discard: void, cb: Function) {
import("./controls/controls")
.then((module) => cb(undefined, module.Controls))
.catch((e: object) => cb(undefined, crashPage(e)));
}
};
interface RootComponentProps { store: Store; }
export const attachAppToDom: Callback = (err, t) => {
attachToRoot(RootComponent, { store: _store });
@ -38,238 +19,6 @@ export const attachAppToDom: Callback = (err, t) => {
};
export class RootComponent extends React.Component<RootComponentProps, {}> {
requireAuth(_discard: RouterState, replace: RedirectFunction) {
const { store } = this.props;
if (Session.fetchStoredToken()) { // has a previous session in cache
if (store.getState().auth) { // Has session, logged in.
return;
} else { // Has session but not logged in (returning visitor).
store.dispatch(ready());
}
} else { // Not logged in yet.
Session.clear();
}
}
/** These methods are a way to determine how to load certain modules
* based on the device (mobile or desktop) for optimization/css purposes.
* Open to revision.
*/
maybeReplaceDesignerModules(next: RouterState, replace: RedirectFunction) {
if (next.location.pathname === "/app/designer") {
replace(`${next.location.pathname}/plants`);
}
}
/*
/app => App
/app/account => Account
/app/controls => Controls
/app/device => Devices
/app/designer?p1&p2 => FarmDesigner
/app/regimens => Regimens
/app/sequences => Sequences
/app/tools => Tools
/app/404 => 404
*/
routes = {
component: App,
indexRoute: controlsRoute,
childRoutes: [
{
path: "app/account",
getComponent(_discard: void, cb: Function) {
import("./account/index")
.then(module => cb(undefined, module.Account))
.catch((e: object) => cb(undefined, crashPage(e)));
}
},
controlsRoute,
{
path: "app/device",
getComponent(_discard: void, cb: Function) {
import("./devices/devices")
.then(module => cb(undefined, module.Devices))
.catch((e: object) => cb(undefined, crashPage(e)));
}
},
{
path: "app/farmware",
getComponent(_discard: void, cb: Function) {
import("./farmware/index")
.then(module => cb(undefined, module.FarmwarePage))
.catch((e: object) => cb(undefined, crashPage(e)));
}
},
{
path: "app/designer",
onEnter: this.maybeReplaceDesignerModules.bind(this),
getComponent(_discard: void, cb: Function) {
import("./farm_designer/index")
.then(module => cb(undefined, module.FarmDesigner))
.catch((e: object) => cb(undefined, crashPage(e)));
},
childRoutes: [
{
path: "plants",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/plant_inventory")
.then(module => cb(undefined, module.Plants))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "plants/crop_search",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/crop_catalog")
.then(module => cb(undefined, module.CropCatalog))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "plants/crop_search/:crop",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/crop_info")
.then(module => cb(undefined, module.CropInfo))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "plants/crop_search/:crop/add",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/add_plant")
.then(module => cb(undefined, module.AddPlant))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "plants/select",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/select_plants")
.then(module => cb(undefined, module.SelectPlants))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "plants/move_to",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/move_to")
.then(module => cb(undefined, module.MoveTo))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "plants/create_point",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/create_points")
.then(module => cb(undefined, module.CreatePoints))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "plants/:plant_id",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/plant_info")
.then(module => cb(undefined, module.PlantInfo))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "plants/:plant_id/edit",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/plants/edit_plant_info")
.then(module => cb(undefined, module.EditPlantInfo))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "farm_events",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/farm_events/farm_events")
.then(module => cb(undefined, module.FarmEvents))
.catch((e: object) => cb(undefined, crashPage(e)));
}
},
{
path: "farm_events/add",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/farm_events/add_farm_event")
.then(module => cb(undefined, module.AddFarmEvent))
.catch((e: object) => cb(undefined, crashPage(e)));
}
},
{
path: "farm_events/:farm_event_id",
getComponent(_discard: void, cb: Function) {
import("./farm_designer/farm_events/edit_farm_event")
.then(module => cb(undefined, module.EditFarmEvent))
.catch((e: object) => cb(undefined, crashPage(e)));
}
}
]
},
{
path: "app/regimens",
getComponent(_discard: void, cb: Function) {
import("./regimens/index")
.then(module => cb(undefined, module.Regimens))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "app/regimens/:regimen",
getComponent(_discard: void, cb: Function) {
import("./regimens/index")
.then(module => cb(undefined, module.Regimens))
.catch((e: object) => cb(undefined, crashPage(e)));
}
},
{
path: "app/sequences",
getComponent(_discard: void, cb: Function) {
import("./sequences/sequences")
.then(module => {
cb(undefined, module.Sequences);
})
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "app/sequences/:sequence",
getComponent(_discard: void, cb: Function) {
import("./sequences/sequences")
.then(module => cb(undefined, module.Sequences))
.catch((e: object) => cb(undefined, crashPage(e)));
},
},
{
path: "app/tools",
getComponent(_discard: void, cb: Function) {
import("./tools/index")
.then(module => cb(undefined, module.Tools))
.catch((e: object) => cb(undefined, crashPage(e)));
}
},
{
path: "app/logs",
getComponent(_discard: void, cb: Function) {
import("./logs/index")
.then(module => cb(undefined, module.Logs))
.catch((e: object) => cb(undefined, crashPage(e)));
}
},
{
path: "*",
getComponent(_discard: void, cb: Function) {
import("./404")
.then(module => cb(undefined, module.FourOhFour))
.catch((e: object) => cb(undefined, crashPage(e)));
}
}
]
};
render() {
// ==== TEMPORARY HACK. TODO: Add a before hook, if such a thing exists in
// React Router. Or switch routing libs.
@ -281,7 +30,7 @@ export class RootComponent extends React.Component<RootComponentProps, {}> {
// ==== END HACK ====
return <Provider store={_store}>
<Router history={history}>
{this.routes}
{topLevelRoutes}
</Router>
</Provider>;
}

View File

@ -32,7 +32,7 @@ export class Sequences extends React.Component<Props, {}> {
<h3>
<i>{t("Sequence Editor")}</i>
</h3>
<ToolTip helpText={ToolTips.SEQUENCE_EDITOR} />
<ToolTip helpText={t(ToolTips.SEQUENCE_EDITOR)} />
<SequenceEditorMiddle
syncStatus={this.props.syncStatus}
dispatch={this.props.dispatch}

View File

@ -39,7 +39,7 @@ export function stopIE() {
/** Dynamically change the meta title of the page. */
export function updatePageInfo(pageName: string) {
if (pageName === "designer") { pageName = "Farm Designer"; }
document.title = capitalize(pageName);
document.title = t(capitalize(pageName));
// Possibly add meta "content" here dynamically as well
}

View File

@ -7466,6 +7466,6 @@ yargs@~3.10.0:
decamelize "^1.0.0"
window-size "0.1.0"
yarn@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.3.2.tgz#5939762581b5b4ddcd3418c0f6be42df3aee195f"
yarn@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.5.1.tgz#e8680360e832ac89521eb80dad3a7bc27a40bab4"