* Add workspace webhooks and drop the unused automation webhook node. Give workspaces HMAC-signed outgoing webhooks for the post lifecycle, with retry, auto-pause, replay, and live logs, and keep HTTP Request as the only outbound automation node. * Tighten webhook controller and validation after review. Drop the redundant workspace redirects, prune logs without counting, and validate events/status with Rule::enum. * Move leftover webhook UI copy behind i18n. HTTP status phrases, delete-cancel, and validation attribute names were still English literals. * Build the webhook-paused email through Maizzle. The hand-written Blade skipped the shared layout, header, and footer used by the other mail templates. * Cover real webhook dispatch paths and restyle the webhook pages. * Ask for the shared delete keyword when confirming a webhook delete. The endpoint URL is a poor confirm string; posts and assets already use the common "delete" keyword. * Fix webhook review blockers so CI can go green. Drop leftover French automation keys, stop mutating Inertia log props, and show delivered_at instead of created_at. * Close the remaining webhook review gaps. Keep Echo log updates across infinite scroll, align the channel with the policy, persist log ids across retries, and fail unknown automation nodes without throwing. * Stop webhook delivery after disable and record last sent only on success. Queued jobs now skip paused or disabled endpoints unless the user replays, and changing the URL re-pings it first. * Limit webhooks to owners and admins, and encrypt signing secrets. Members can no longer create or inspect outgoing integrations, and secrets stay encrypted at rest. * Cover webhook secret hiding, skip-ping, and failed-delivery edges. * Send the full post on webhooks after labels and platforms are saved. * Fix webhook payloads for integer media ids and type webhook status. * Split the webhook show page into focused components. * Reset live webhook logs when switching endpoints. * Keep the newest webhook logs at the top after live merges. * Cast media item ids to string without the extra scalar check. * Add post.unscheduled webhooks and put the log id on the envelope. Unscheduling is now a first-class event, and receivers can send the delivery id back so we can find the matching log. * Translate webhook event names in the UI. * Make the webhook show page full-width and stop stacking flash toasts. * Translate remaining webhook UI copy in every locale. * Sign webhook pings and drop author email from the payload. * Send signed webhook tests after create instead of pinging on save. Create and update only block private URLs so the receiver can copy the secret first. The show page then sends a signed webhook.test with an object data envelope. * Polish webhook test UX and always mint the dispatch log id in the job. Keep send-test in the actions menu (its own group) and drop the leftover constructor param so retries reuse the serialized id instead of a caller-supplied one.
402 lines
24 KiB
PHP
402 lines
24 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
return [
|
||
'mobile_notice' => 'Редактор автоматизаций лучше всего работает на большом экране. Откройте его на компьютере, чтобы создать рабочий процесс.',
|
||
'title' => 'Автоматизации',
|
||
'default_name' => 'Новая автоматизация',
|
||
|
||
'actions' => [
|
||
'new' => 'Новая автоматизация',
|
||
'edit' => 'Изменить',
|
||
'save' => 'Сохранить',
|
||
'activate' => 'Активировать',
|
||
'pause' => 'Приостановить',
|
||
'delete' => 'Удалить',
|
||
'retry' => 'Повторить',
|
||
'guide' => 'Узнать, как это работает',
|
||
],
|
||
|
||
'tabs' => [
|
||
'build' => 'Конструктор',
|
||
'variables' => 'Переменные',
|
||
'test' => 'Тест',
|
||
],
|
||
|
||
'nav' => [
|
||
'workflow' => 'Процесс',
|
||
'invocations' => 'Запуски',
|
||
'metrics' => 'Метрики',
|
||
'settings' => 'Настройки',
|
||
],
|
||
|
||
'settings' => [
|
||
'general' => 'Основные',
|
||
'general_description' => 'Переименуйте эту автоматизацию.',
|
||
'name_label' => 'Название',
|
||
'name_saved' => 'Автоматизация переименована.',
|
||
'status_title' => 'Статус',
|
||
'status_description' => 'Активируйте, чтобы запустить, или приостановите, чтобы остановить.',
|
||
'activated_at' => 'Активирована :date',
|
||
'paused_at' => 'Приостановлена :date',
|
||
'created_at' => 'Создана :date',
|
||
'danger_title' => 'Опасная зона',
|
||
'danger_description' => 'Необратимые действия.',
|
||
'delete_title' => 'Удалить эту автоматизацию',
|
||
'delete_description' => 'Безвозвратно удаляет автоматизацию и историю её запусков.',
|
||
],
|
||
|
||
'status_run' => [
|
||
'pending' => 'В ожидании',
|
||
'running' => 'Выполняется',
|
||
'waiting' => 'Ожидание',
|
||
'completed' => 'Завершено',
|
||
'failed' => 'Ошибка',
|
||
'cancelled' => 'Отменено',
|
||
],
|
||
|
||
'node_type' => [
|
||
'trigger' => 'Триггер',
|
||
'generate' => 'Генерация контента',
|
||
'delay' => 'Задержка',
|
||
'condition' => 'Условие',
|
||
'publish' => 'Публикация',
|
||
'end' => 'Конец',
|
||
'fetch_rss' => 'Получить RSS',
|
||
'http_request' => 'HTTP-запрос',
|
||
],
|
||
|
||
'invocations' => [
|
||
'empty' => 'Пока нет запусков.',
|
||
'refresh' => 'Обновить',
|
||
'search_placeholder' => 'Поиск по ID запуска…',
|
||
'copied' => 'ID запуска скопирован.',
|
||
'loading' => 'Загрузка шагов…',
|
||
'no_steps' => 'Шаги не записаны.',
|
||
'load_error' => 'Не удалось загрузить шаги.',
|
||
'steps' => '{0}Нет шагов|{1}:count шаг|[2,4]:count шага|[5,*]:count шагов',
|
||
'filter' => [
|
||
'all' => 'Все статусы',
|
||
],
|
||
'columns' => [
|
||
'timestamp' => 'Время',
|
||
'run' => 'Запуск',
|
||
'status' => 'Статус',
|
||
'message' => 'Последнее сообщение',
|
||
'duration' => 'Длительность',
|
||
],
|
||
'summary' => [
|
||
'completed' => 'Процесс завершён',
|
||
'failed' => 'Процесс завершился с ошибкой',
|
||
'running' => 'Процесс выполняется',
|
||
'cancelled' => 'Процесс отменён',
|
||
'pending' => 'Процесс в ожидании',
|
||
],
|
||
],
|
||
|
||
'metrics' => [
|
||
'overview' => 'Обзор',
|
||
'runs_over_time' => 'Запуски по времени',
|
||
'posts_by_platform' => 'Посты по платформам',
|
||
'no_posts' => 'В этот период не опубликовано ни одного поста.',
|
||
'cards' => [
|
||
'runs' => 'Всего запусков',
|
||
'completed' => 'Завершено',
|
||
'failed' => 'С ошибкой',
|
||
'in_progress' => 'В процессе',
|
||
'success_rate' => 'Доля успешных',
|
||
'avg_duration' => 'Средняя длительность',
|
||
'posts_created' => 'Создано постов',
|
||
],
|
||
'legend' => [
|
||
'started' => 'Запущено',
|
||
'completed' => 'Завершено',
|
||
'failed' => 'С ошибкой',
|
||
],
|
||
],
|
||
|
||
'categories' => [
|
||
'sources' => 'Источники',
|
||
'content' => 'Контент',
|
||
'flow' => 'Логика',
|
||
'output' => 'Вывод',
|
||
],
|
||
|
||
'variables' => [
|
||
'title' => 'Переменные процесса',
|
||
'hint' => 'Многократно используемые значения, доступные везде через {{ variables.KEY }}. Хранятся в зашифрованном виде.',
|
||
'empty' => 'Пока нет переменных.',
|
||
'key' => 'Ключ',
|
||
'value' => 'Значение',
|
||
'key_placeholder' => 'API_KEY',
|
||
'value_placeholder' => 'Значение',
|
||
'add' => 'Новая переменная',
|
||
],
|
||
|
||
'expr' => [
|
||
'trigger_event' => 'Название события-триггера',
|
||
'trigger_fired_at' => 'Когда сработал триггер',
|
||
'trigger_post_id' => 'ID поста-триггера',
|
||
'trigger_post_content' => 'Содержимое поста-триггера',
|
||
'trigger_post_status' => 'Статус поста-триггера',
|
||
'trigger_post_scheduled_at' => 'Когда запланирован пост',
|
||
'trigger_post_published_at' => 'Когда пост был опубликован',
|
||
'fetched_title' => 'Заголовок полученного элемента',
|
||
'fetched_link' => 'Ссылка полученного элемента',
|
||
'fetched_date' => 'Дата публикации полученного элемента',
|
||
'fetched_content' => 'Полное содержимое полученного элемента',
|
||
'fetched_description' => 'Краткое описание полученного элемента',
|
||
'fetched_author' => 'Автор полученного элемента',
|
||
'fetched_image' => 'URL изображения полученного элемента',
|
||
'fetched_categories' => 'Категории полученного элемента',
|
||
'fetched_enclosure' => 'Медиа полученного элемента (аудио/видео/файл)',
|
||
'fetched_pubdate' => 'Дата публикации полученного элемента',
|
||
'fetched_http' => 'Полученный HTTP-элемент (добавьте поле)',
|
||
'generated_content' => 'Сгенерированное ИИ содержимое поста',
|
||
'generated_post_url' => 'URL сгенерированного ИИ поста',
|
||
'variable' => 'Переменная процесса',
|
||
'now' => 'Текущие дата и время',
|
||
],
|
||
|
||
'test' => [
|
||
'description' => 'Выполняет автоматизацию от начала до конца, используя синтезированные данные триггера. Полезно для проверки каждого узла без ожидания реального расписания или ленты.',
|
||
'starting' => 'Запуск тестового прогона…',
|
||
'in_progress' => 'В процессе',
|
||
'completed' => 'Завершено',
|
||
'failed' => 'Ошибка',
|
||
'waiting' => 'Ожидание',
|
||
'close' => 'Закрыть',
|
||
'no_node_runs' => 'Ожидание запуска первого узла…',
|
||
'node_input' => 'Вход',
|
||
'node_output' => 'Выход',
|
||
'node_error' => 'Ошибка',
|
||
'no_new_items' => 'Нет новых элементов — последующие узлы не запускались.',
|
||
'error_starting' => 'Не удалось запустить тестовый прогон.',
|
||
'with_real_data' => 'С реальными данными',
|
||
'run' => 'Запустить тест',
|
||
'idle_hint' => 'Нажмите «Запустить тест», чтобы выполнить автоматизацию от начала до конца.',
|
||
'real_data_hint' => 'Этот тест опубликует посты, продвинет отметки опроса и вызовет внешние побочные эффекты.',
|
||
'dry_badge' => 'Тестовый прогон',
|
||
],
|
||
|
||
'status' => [
|
||
'draft' => 'Черновик',
|
||
'active' => 'Активна',
|
||
'paused' => 'Приостановлена',
|
||
],
|
||
|
||
'index' => [
|
||
'empty_title' => 'Пока нет автоматизаций',
|
||
'empty_description' => 'Создайте первую автоматизацию, чтобы публиковать на автопилоте.',
|
||
'columns' => [
|
||
'name' => 'Название',
|
||
'status' => 'Статус',
|
||
'created' => 'Создана',
|
||
],
|
||
],
|
||
|
||
'form' => [
|
||
'activate_error_fallback' => 'Не удалось активировать автоматизацию.',
|
||
'pause_error_fallback' => 'Не удалось приостановить автоматизацию.',
|
||
'save_error_fallback' => 'Не удалось сохранить автоматизацию.',
|
||
'save_success' => 'Автоматизация сохранена.',
|
||
'empty_canvas_title' => 'Начните создавать автоматизацию',
|
||
'empty_canvas_description' => 'Перетащите узел из левой панели, чтобы начать.',
|
||
'name_placeholder' => 'Автоматизация без названия',
|
||
],
|
||
|
||
'nodes' => [
|
||
'trigger' => 'Триггер',
|
||
'generate' => 'Генерация',
|
||
'delay' => 'Задержка',
|
||
'condition' => 'Условие',
|
||
'publish' => 'Публикация',
|
||
'end' => 'Конец',
|
||
'end_summary' => 'Останавливает автоматизацию здесь',
|
||
'fetch_rss' => 'Получить RSS',
|
||
'http_request' => 'HTTP-запрос',
|
||
'handles' => [
|
||
'items' => 'есть элементы',
|
||
'no_items' => 'нет элементов',
|
||
],
|
||
],
|
||
|
||
'config' => [
|
||
'select_placeholder' => 'Выберите…',
|
||
'invalid_json' => 'Это ещё не корректный JSON.',
|
||
'expand_editor' => 'Развернуть редактор',
|
||
'minimize_editor' => 'Свернуть',
|
||
|
||
'trigger' => [
|
||
'type' => 'Тип триггера',
|
||
'types' => [
|
||
'schedule' => 'Расписание',
|
||
'post_published' => 'Когда пост опубликован',
|
||
'post_scheduled' => 'Когда пост запланирован',
|
||
],
|
||
'post_published_hint' => 'Запускается при публикации любого поста в этом рабочем пространстве. Опубликованный пост доступен в {{ trigger.post }} для последующих узлов.',
|
||
'post_scheduled_hint' => 'Запускается при планировании любого поста в этом рабочем пространстве. Запланированный пост доступен в {{ trigger.post }}.',
|
||
|
||
'schedule' => [
|
||
'field' => 'Интервал срабатывания',
|
||
'fields' => [
|
||
'minutes' => 'Минуты',
|
||
'hours' => 'Часы',
|
||
'days' => 'Дни',
|
||
'weeks' => 'Недели',
|
||
'months' => 'Месяцы',
|
||
],
|
||
'minutes_interval' => 'Минут между срабатываниями',
|
||
'hours_interval' => 'Часов между срабатываниями',
|
||
'days_interval' => 'Дней между срабатываниями',
|
||
'hour' => 'Час срабатывания',
|
||
'minute' => 'Минута срабатывания',
|
||
'weekdays' => 'Дни недели срабатывания',
|
||
'day_of_month' => 'День месяца',
|
||
'weekday_names' => [
|
||
'sun' => 'Вс',
|
||
'mon' => 'Пн',
|
||
'tue' => 'Вт',
|
||
'wed' => 'Ср',
|
||
'thu' => 'Чт',
|
||
'fri' => 'Пт',
|
||
'sat' => 'Сб',
|
||
],
|
||
'summary' => [
|
||
'every_n_minutes' => 'Запускается каждую :count минуту|Запускается каждые :count минуты|Запускается каждые :count минут',
|
||
'every_n_hours' => 'Запускается каждый :count час на :minute-й минуте|Запускается каждые :count часа на :minute-й минуте|Запускается каждые :count часов на :minute-й минуте',
|
||
'every_n_days' => 'Запускается каждый :count день в :time|Запускается каждые :count дня в :time|Запускается каждые :count дней в :time',
|
||
'weekly' => 'Запускается по :days в :time',
|
||
'monthly' => 'Запускается :day числа каждого месяца в :time',
|
||
],
|
||
],
|
||
],
|
||
'generate' => [
|
||
'social_accounts' => 'Социальные аккаунты',
|
||
'social_accounts_empty' => 'Нет подключённых социальных аккаунтов. Сначала подключите один.',
|
||
'target_slide_count' => 'Сколько слайдов сгенерировать',
|
||
'prompt_template' => 'Шаблон запроса',
|
||
'prompt_template_hint' => 'Введите {{, чтобы вставить данные из предыдущих шагов.',
|
||
'image_count' => 'Сколько изображений сгенерировать',
|
||
'image_count_hint' => '0 = только текст (без изображения). 1 = одно изображение. 2+ = карусель.',
|
||
'use_brand_voice' => 'Использовать голос бренда',
|
||
'use_brand_voice_hint' => 'Применяйте описание и голос вашего бренда. Отключите для точной подачи сторонних источников (новости, RSS).',
|
||
'use_brand_visuals' => 'Использовать визуал бренда',
|
||
'use_brand_visuals_hint' => 'Направляйте ИИ-изображения цветами и айдентикой вашего бренда. Отключите для нейтральных изображений, основанных только на теме поста.',
|
||
'style' => 'Стиль',
|
||
'account_summary' => ':count аккаунт · :format|:count аккаунта · :format|:count аккаунтов · :format',
|
||
'formats' => [
|
||
'single' => 'один',
|
||
'carousel' => 'карусель',
|
||
],
|
||
],
|
||
'delay' => [
|
||
'duration' => 'Длительность',
|
||
'unit' => 'Единица',
|
||
'units' => [
|
||
'minutes' => 'Минуты',
|
||
'hours' => 'Часы',
|
||
'days' => 'Дни',
|
||
],
|
||
],
|
||
'condition' => [
|
||
'field' => 'Поле',
|
||
'operator' => 'Оператор',
|
||
'operators' => [
|
||
'contains' => 'содержит',
|
||
'not_contains' => 'не содержит',
|
||
'equals' => 'равно',
|
||
'not_equals' => 'не равно',
|
||
'matches' => 'соответствует (regex)',
|
||
'greater_than' => 'больше чем',
|
||
'less_than' => 'меньше чем',
|
||
],
|
||
'value' => 'Значение',
|
||
],
|
||
'publish' => [
|
||
'mode' => 'Режим',
|
||
'modes' => [
|
||
'now' => 'Опубликовать сейчас',
|
||
'scheduled' => 'Запланировать',
|
||
'draft' => 'Сохранить как черновик',
|
||
],
|
||
'scheduled_offset' => 'Смещение от триггера (минуты)',
|
||
'offset_summary' => ':mode · +:offset мин',
|
||
],
|
||
'end' => [
|
||
'reason' => 'Причина (необязательно)',
|
||
'reason_placeholder' => 'например, Отфильтровано условием',
|
||
],
|
||
'fetch_rss' => [
|
||
'feed_url' => 'URL ленты',
|
||
'feed_url_hint' => 'При первом запуске отметка устанавливается на «сейчас», чтобы старые элементы не хлынули в последующие узлы. Последующие запуски видят только элементы новее предыдущего опроса.',
|
||
'inspect' => 'Проверить ленту',
|
||
'inspecting' => 'Проверка…',
|
||
'inspect_hint' => 'Получите образец, чтобы узнать доступные поля для использования в последующих узлах.',
|
||
'inspect_error' => 'Не удалось прочитать эту ленту. Проверьте URL и попробуйте снова.',
|
||
'discovered_fields' => 'Доступные поля',
|
||
'discovered_empty' => 'В последнем элементе поля не найдены.',
|
||
],
|
||
'http_request' => [
|
||
'url' => 'URL',
|
||
'method' => 'Метод',
|
||
'auth_type' => 'Аутентификация',
|
||
'auth' => [
|
||
'none' => 'Нет (публичный)',
|
||
'bearer' => 'Bearer-токен',
|
||
'basic' => 'Basic-аутентификация',
|
||
'api_key' => 'Заголовок с API-ключом',
|
||
],
|
||
'bearer_token' => 'Bearer-токен',
|
||
'basic_username' => 'Имя пользователя',
|
||
'basic_password' => 'Пароль',
|
||
'api_key_header' => 'Имя заголовка',
|
||
'api_key_value' => 'API-ключ',
|
||
'body_template' => 'Шаблон тела (JSON)',
|
||
'headers' => 'Заголовки',
|
||
'header_name' => 'Имя заголовка',
|
||
'header_value' => 'Значение',
|
||
'add_header' => 'Добавить заголовок',
|
||
'polling_section' => 'Список и дедупликация (необязательно)',
|
||
'polling_hint' => 'Если ответ — список, каждый элемент запускает процесс отдельно. Один объект запускает его один раз.',
|
||
'items_path' => 'Путь к элементам',
|
||
'items_path_hint' => 'Оставьте пустым, если ответ уже является массивом. Используйте путь через точку (например, data.items) для вложенного массива или * для объекта с ключами по id.',
|
||
'item_key_path' => 'Путь к ключу элемента',
|
||
'item_key_path_hint' => 'JSON-путь к уникальному id (например, id). Уже встречавшиеся элементы пропускаются, поэтому лента без дат всё равно передаёт только новые записи.',
|
||
'item_date_path' => 'Путь к дате элемента',
|
||
'item_date_path_hint' => 'JSON-путь к отметке времени элемента (например, published_at). Предпочтительнее пути к ключу, когда доступен. Первый опрос фиксирует базовую отметку и ничего не передаёт, поэтому существующая лента не переполняет систему в первый день.',
|
||
],
|
||
],
|
||
|
||
'delete' => [
|
||
'title' => 'Удалить автоматизацию',
|
||
'description' => 'Вы уверены, что хотите удалить эту автоматизацию? Все запуски и элементы триггеров также будут удалены. Это действие нельзя отменить.',
|
||
'confirm' => 'Удалить',
|
||
'cancel' => 'Отмена',
|
||
],
|
||
|
||
'flash' => [
|
||
'deleted' => 'Автоматизация успешно удалена!',
|
||
],
|
||
|
||
'errors' => [
|
||
'no_active_social_accounts' => 'Для этой автоматизации не настроено ни одного активного социального аккаунта.',
|
||
'must_have_one_trigger' => 'Автоматизация должна содержать ровно один узел-триггер.',
|
||
'trigger_must_be_connected' => 'Узел-триггер должен быть связан хотя бы с одним узлом.',
|
||
'graph_contains_cycle' => 'Граф автоматизации содержит цикл.',
|
||
'only_failed_can_retry' => 'Повторить можно только запуски, завершившиеся с ошибкой.',
|
||
'no_generated_post' => 'В запуске не найдено сгенерированного поста.',
|
||
'url_not_allowed' => 'URL запроса указывает на приватный или недоступный адрес и был заблокирован.',
|
||
'node_no_longer_exists' => 'Узел :node_id больше не существует в автоматизации.',
|
||
'no_trigger_connection' => 'К узлу-триггеру не подключён ни один узел.',
|
||
'fetch_rss_missing_url' => 'В узле «Получить RSS» отсутствует URL ленты.',
|
||
'fetch_rss_request_failed' => 'Запрос RSS-ленты не удался.',
|
||
'fetch_rss_malformed' => 'RSS-лента имеет некорректный формат.',
|
||
'http_missing_url' => 'В узле HTTP-запроса отсутствует URL.',
|
||
'http_request_exception' => 'HTTP-запрос вызвал исключение.',
|
||
'http_request_failed' => 'HTTP-запрос не удался.',
|
||
'http_items_path_not_array' => 'Путь к элементам не привёл к списку.',
|
||
'generate_image_format_required' => 'ИИ генерирует только изображения. Выберите формат изображения (не видео).',
|
||
],
|
||
];
|