* 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
17 KiB
PHP
402 lines
17 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
return [
|
|
'mobile_notice' => 'O editor de automações funciona melhor em uma tela maior. Abra no desktop para montar seu fluxo de trabalho.',
|
|
'title' => 'Automações',
|
|
'default_name' => 'Nova automação',
|
|
|
|
'actions' => [
|
|
'new' => 'Nova automação',
|
|
'edit' => 'Editar',
|
|
'save' => 'Salvar',
|
|
'activate' => 'Ativar',
|
|
'pause' => 'Pausar',
|
|
'delete' => 'Excluir',
|
|
'retry' => 'Tentar novamente',
|
|
'guide' => 'Aprenda como funciona',
|
|
],
|
|
|
|
'tabs' => [
|
|
'build' => 'Montar',
|
|
'variables' => 'Variáveis',
|
|
'test' => 'Testar',
|
|
],
|
|
|
|
'nav' => [
|
|
'workflow' => 'Workflow',
|
|
'invocations' => 'Invocações',
|
|
'metrics' => 'Métricas',
|
|
'settings' => 'Configurações',
|
|
],
|
|
|
|
'settings' => [
|
|
'general' => 'Geral',
|
|
'general_description' => 'Renomeie esta automação.',
|
|
'name_label' => 'Nome',
|
|
'name_saved' => 'Automação renomeada.',
|
|
'status_title' => 'Status',
|
|
'status_description' => 'Ative para começar a rodar, ou pause para parar.',
|
|
'activated_at' => 'Ativada em :date',
|
|
'paused_at' => 'Pausada em :date',
|
|
'created_at' => 'Criada em :date',
|
|
'danger_title' => 'Zona de perigo',
|
|
'danger_description' => 'Ações irreversíveis.',
|
|
'delete_title' => 'Excluir esta automação',
|
|
'delete_description' => 'Remove permanentemente a automação e o histórico de execuções.',
|
|
],
|
|
|
|
'status_run' => [
|
|
'pending' => 'Pendente',
|
|
'running' => 'Executando',
|
|
'waiting' => 'Aguardando',
|
|
'completed' => 'Concluído',
|
|
'failed' => 'Falhou',
|
|
'cancelled' => 'Cancelado',
|
|
],
|
|
|
|
'node_type' => [
|
|
'trigger' => 'Gatilho',
|
|
'generate' => 'Gerar conteúdo',
|
|
'delay' => 'Espera',
|
|
'condition' => 'Condição',
|
|
'publish' => 'Publicar',
|
|
'end' => 'Fim',
|
|
'fetch_rss' => 'Buscar RSS',
|
|
'http_request' => 'Requisição HTTP',
|
|
],
|
|
|
|
'invocations' => [
|
|
'empty' => 'Nenhuma invocação ainda.',
|
|
'refresh' => 'Atualizar',
|
|
'search_placeholder' => 'Buscar por ID do run…',
|
|
'copied' => 'ID do run copiado.',
|
|
'loading' => 'Carregando passos…',
|
|
'no_steps' => 'Nenhum passo registrado.',
|
|
'load_error' => 'Não foi possível carregar os passos.',
|
|
'steps' => '{0}Nenhum passo|{1}:count passo|[2,*]:count passos',
|
|
'filter' => [
|
|
'all' => 'Todos os status',
|
|
],
|
|
'columns' => [
|
|
'timestamp' => 'Data',
|
|
'run' => 'Run',
|
|
'status' => 'Status',
|
|
'message' => 'Última mensagem',
|
|
'duration' => 'Duração',
|
|
],
|
|
'summary' => [
|
|
'completed' => 'Workflow concluído',
|
|
'failed' => 'Workflow falhou',
|
|
'running' => 'Workflow em execução',
|
|
'cancelled' => 'Workflow cancelado',
|
|
'pending' => 'Workflow pendente',
|
|
],
|
|
],
|
|
|
|
'metrics' => [
|
|
'overview' => 'Visão geral',
|
|
'runs_over_time' => 'Execuções ao longo do tempo',
|
|
'posts_by_platform' => 'Posts por plataforma',
|
|
'no_posts' => 'Nenhum post publicado neste período.',
|
|
'cards' => [
|
|
'runs' => 'Total de execuções',
|
|
'completed' => 'Concluídas',
|
|
'failed' => 'Falhas',
|
|
'in_progress' => 'Em progresso',
|
|
'success_rate' => 'Taxa de sucesso',
|
|
'avg_duration' => 'Duração média',
|
|
'posts_created' => 'Posts criados',
|
|
],
|
|
'legend' => [
|
|
'started' => 'Iniciadas',
|
|
'completed' => 'Concluídas',
|
|
'failed' => 'Falhas',
|
|
],
|
|
],
|
|
|
|
'categories' => [
|
|
'sources' => 'Fontes',
|
|
'content' => 'Conteúdo',
|
|
'flow' => 'Fluxo',
|
|
'output' => 'Saída',
|
|
],
|
|
|
|
'variables' => [
|
|
'title' => 'Variáveis do workflow',
|
|
'hint' => 'Valores reutilizáveis referenciados em qualquer lugar com {{ variables.KEY }}. Armazenados encriptados.',
|
|
'empty' => 'Nenhuma variável ainda.',
|
|
'key' => 'Chave',
|
|
'value' => 'Valor',
|
|
'key_placeholder' => 'API_KEY',
|
|
'value_placeholder' => 'Valor',
|
|
'add' => 'Nova variável',
|
|
],
|
|
|
|
'expr' => [
|
|
'trigger_event' => 'Nome do evento do gatilho',
|
|
'trigger_fired_at' => 'Quando o gatilho disparou',
|
|
'trigger_post_id' => 'ID do post que disparou',
|
|
'trigger_post_content' => 'Conteúdo do post que disparou',
|
|
'trigger_post_status' => 'Status do post que disparou',
|
|
'trigger_post_scheduled_at' => 'Quando o post está agendado',
|
|
'trigger_post_published_at' => 'Quando o post foi publicado',
|
|
'fetched_title' => 'Título do item buscado',
|
|
'fetched_link' => 'Link do item buscado',
|
|
'fetched_date' => 'Data de publicação do item buscado',
|
|
'fetched_content' => 'Conteúdo completo do item buscado',
|
|
'fetched_description' => 'Resumo do item buscado',
|
|
'fetched_author' => 'Autor do item buscado',
|
|
'fetched_image' => 'URL da imagem do item buscado',
|
|
'fetched_categories' => 'Categorias do item buscado',
|
|
'fetched_enclosure' => 'Mídia do item (áudio/vídeo/arquivo)',
|
|
'fetched_pubdate' => 'Data de publicação do item buscado',
|
|
'fetched_http' => 'Item HTTP buscado (adicione um campo)',
|
|
'generated_content' => 'Conteúdo do post gerado por IA',
|
|
'generated_post_url' => 'URL do post gerado por IA',
|
|
'variable' => 'Variável do fluxo',
|
|
'now' => 'Data e hora atuais',
|
|
],
|
|
|
|
'test' => [
|
|
'description' => 'Executa a automação ponta a ponta usando um payload de gatilho sintético. Útil pra validar cada nó sem esperar o agendamento ou o feed real.',
|
|
'starting' => 'Iniciando execução de teste…',
|
|
'in_progress' => 'Em andamento',
|
|
'completed' => 'Concluído',
|
|
'failed' => 'Falhou',
|
|
'waiting' => 'Aguardando',
|
|
'close' => 'Fechar',
|
|
'no_node_runs' => 'Aguardando o primeiro nó começar…',
|
|
'node_input' => 'Entrada',
|
|
'node_output' => 'Saída',
|
|
'node_error' => 'Erro',
|
|
'no_new_items' => 'Nenhum item novo — nada foi executado adiante.',
|
|
'error_starting' => 'Não foi possível iniciar a execução de teste.',
|
|
'with_real_data' => 'Com dados reais',
|
|
'run' => 'Rodar teste',
|
|
'idle_hint' => 'Clique em Rodar teste para executar a automação de ponta a ponta.',
|
|
'real_data_hint' => 'Este teste vai publicar posts, avançar watermarks e disparar efeitos colaterais externos.',
|
|
'dry_badge' => 'Teste seco',
|
|
],
|
|
|
|
'status' => [
|
|
'draft' => 'Rascunho',
|
|
'active' => 'Ativa',
|
|
'paused' => 'Pausada',
|
|
],
|
|
|
|
'index' => [
|
|
'empty_title' => 'Nenhuma automação ainda',
|
|
'empty_description' => 'Crie sua primeira automação para começar a publicar no piloto automático.',
|
|
'columns' => [
|
|
'name' => 'Nome',
|
|
'status' => 'Status',
|
|
'created' => 'Criada em',
|
|
],
|
|
],
|
|
|
|
'form' => [
|
|
'activate_error_fallback' => 'Não foi possível ativar a automação.',
|
|
'pause_error_fallback' => 'Não foi possível pausar a automação.',
|
|
'save_error_fallback' => 'Não foi possível salvar a automação.',
|
|
'save_success' => 'Automação salva.',
|
|
'empty_canvas_title' => 'Comece a construir sua automação',
|
|
'empty_canvas_description' => 'Arraste um nó do painel esquerdo para começar.',
|
|
'name_placeholder' => 'Automação sem título',
|
|
],
|
|
|
|
'nodes' => [
|
|
'trigger' => 'Trigger',
|
|
'generate' => 'Gerar',
|
|
'delay' => 'Esperar',
|
|
'condition' => 'Condição',
|
|
'publish' => 'Publicar',
|
|
'end' => 'Encerrar',
|
|
'end_summary' => 'Encerra a automação aqui',
|
|
'fetch_rss' => 'Buscar RSS',
|
|
'http_request' => 'Requisição HTTP',
|
|
'handles' => [
|
|
'items' => 'tem itens',
|
|
'no_items' => 'sem itens',
|
|
],
|
|
],
|
|
|
|
'config' => [
|
|
'select_placeholder' => 'Selecione…',
|
|
'invalid_json' => 'Isto ainda não é um JSON válido.',
|
|
'expand_editor' => 'Expandir editor',
|
|
'minimize_editor' => 'Minimizar',
|
|
|
|
'trigger' => [
|
|
'type' => 'Tipo de trigger',
|
|
'types' => [
|
|
'schedule' => 'Agendamento',
|
|
'post_published' => 'Quando um post é publicado',
|
|
'post_scheduled' => 'Quando um post é agendado',
|
|
],
|
|
'post_published_hint' => 'Roda toda vez que algum post nesta workspace é publicado. O post fica disponível em {{ trigger.post }} pros próximos nós.',
|
|
'post_scheduled_hint' => 'Roda toda vez que algum post nesta workspace é agendado. O post fica disponível em {{ trigger.post }}.',
|
|
|
|
'schedule' => [
|
|
'field' => 'Intervalo de disparo',
|
|
'fields' => [
|
|
'minutes' => 'Minutos',
|
|
'hours' => 'Horas',
|
|
'days' => 'Dias',
|
|
'weeks' => 'Semanas',
|
|
'months' => 'Meses',
|
|
],
|
|
'minutes_interval' => 'Minutos entre disparos',
|
|
'hours_interval' => 'Horas entre disparos',
|
|
'days_interval' => 'Dias entre disparos',
|
|
'hour' => 'Disparar na hora',
|
|
'minute' => 'Disparar no minuto',
|
|
'weekdays' => 'Disparar nos dias',
|
|
'day_of_month' => 'Dia do mês',
|
|
'weekday_names' => [
|
|
'sun' => 'Dom',
|
|
'mon' => 'Seg',
|
|
'tue' => 'Ter',
|
|
'wed' => 'Qua',
|
|
'thu' => 'Qui',
|
|
'fri' => 'Sex',
|
|
'sat' => 'Sáb',
|
|
],
|
|
'summary' => [
|
|
'every_n_minutes' => 'Roda a cada minuto|Roda a cada :count minutos',
|
|
'every_n_hours' => 'Roda a cada hora no minuto :minute|Roda a cada :count horas no minuto :minute',
|
|
'every_n_days' => 'Roda todo dia às :time|Roda a cada :count dias às :time',
|
|
'weekly' => 'Roda :days às :time',
|
|
'monthly' => 'Roda no dia :day de cada mês às :time',
|
|
],
|
|
],
|
|
],
|
|
'generate' => [
|
|
'social_accounts' => 'Contas sociais',
|
|
'social_accounts_empty' => 'Nenhuma conta social conectada. Conecte uma primeiro.',
|
|
'target_slide_count' => 'Slides a gerar',
|
|
'prompt_template' => 'Template do prompt',
|
|
'prompt_template_hint' => 'Digite {{ para inserir dados das etapas anteriores.',
|
|
'image_count' => 'Imagens a gerar',
|
|
'image_count_hint' => '0 = post só texto (sem imagem). 1 = imagem única. 2+ = carrossel.',
|
|
'use_brand_voice' => 'Usar voz da marca',
|
|
'use_brand_voice_hint' => 'Aplica a descrição e a voz da sua marca. Desligue para curadoria fiel de fontes de terceiros (notícias, RSS).',
|
|
'use_brand_visuals' => 'Usar visual da marca',
|
|
'use_brand_visuals_hint' => 'Guia as imagens de IA com as cores e identidade da sua marca. Desligue para imagens neutras, guiadas só pelo tema do post.',
|
|
'style' => 'Estilo',
|
|
'account_summary' => ':count conta · :format|:count contas · :format',
|
|
'formats' => [
|
|
'single' => 'único',
|
|
'carousel' => 'carrossel',
|
|
],
|
|
],
|
|
'delay' => [
|
|
'duration' => 'Duração',
|
|
'unit' => 'Unidade',
|
|
'units' => [
|
|
'minutes' => 'Minutos',
|
|
'hours' => 'Horas',
|
|
'days' => 'Dias',
|
|
],
|
|
],
|
|
'condition' => [
|
|
'field' => 'Campo',
|
|
'operator' => 'Operador',
|
|
'operators' => [
|
|
'contains' => 'contém',
|
|
'not_contains' => 'não contém',
|
|
'equals' => 'igual a',
|
|
'not_equals' => 'diferente de',
|
|
'matches' => 'corresponde (regex)',
|
|
'greater_than' => 'maior que',
|
|
'less_than' => 'menor que',
|
|
],
|
|
'value' => 'Valor',
|
|
],
|
|
'publish' => [
|
|
'mode' => 'Modo',
|
|
'modes' => [
|
|
'now' => 'Publicar agora',
|
|
'scheduled' => 'Agendar',
|
|
'draft' => 'Salvar como rascunho',
|
|
],
|
|
'scheduled_offset' => 'Atraso a partir do trigger (minutos)',
|
|
'offset_summary' => ':mode · +:offset min',
|
|
],
|
|
'end' => [
|
|
'reason' => 'Motivo (opcional)',
|
|
'reason_placeholder' => 'ex: Filtrado pela condição',
|
|
],
|
|
'fetch_rss' => [
|
|
'feed_url' => 'URL do feed',
|
|
'feed_url_hint' => 'Na primeira execução, o watermark é setado pra "agora" pra não inundar os próximos nós com items históricos. Execuções seguintes só veem items novos.',
|
|
'inspect' => 'Inspecionar feed',
|
|
'inspecting' => 'Inspecionando…',
|
|
'inspect_hint' => 'Busca uma amostra pra descobrir os campos disponíveis pra usar nos próximos nós.',
|
|
'inspect_error' => 'Não foi possível ler esse feed. Confira a URL e tente de novo.',
|
|
'discovered_fields' => 'Campos disponíveis',
|
|
'discovered_empty' => 'Nenhum campo encontrado no último item.',
|
|
],
|
|
'http_request' => [
|
|
'url' => 'URL',
|
|
'method' => 'Método',
|
|
'auth_type' => 'Autenticação',
|
|
'auth' => [
|
|
'none' => 'Nenhuma (público)',
|
|
'bearer' => 'Bearer token',
|
|
'basic' => 'Basic auth',
|
|
'api_key' => 'Header de API key',
|
|
],
|
|
'bearer_token' => 'Bearer token',
|
|
'basic_username' => 'Usuário',
|
|
'basic_password' => 'Senha',
|
|
'api_key_header' => 'Nome do header',
|
|
'api_key_value' => 'API key',
|
|
'body_template' => 'Template do body (JSON)',
|
|
'headers' => 'Headers',
|
|
'header_name' => 'Nome do header',
|
|
'header_value' => 'Valor',
|
|
'add_header' => 'Adicionar header',
|
|
'polling_section' => 'Lista e deduplicação (opcional)',
|
|
'polling_hint' => 'Quando a resposta é uma lista, cada item roda o fluxo separadamente. Um objeto único roda uma vez.',
|
|
'items_path' => 'Caminho dos itens',
|
|
'items_path_hint' => 'Deixe vazio se a resposta já é um array. Use um caminho com ponto (ex: data.items) para um array aninhado, ou * para um objeto com chaves por id.',
|
|
'item_key_path' => 'Caminho da chave do item',
|
|
'item_key_path_hint' => 'Caminho JSON pra um id único (ex: id). Itens já vistos são ignorados, então um feed sem datas ainda encaminha só os novos.',
|
|
'item_date_path' => 'Caminho da data do item',
|
|
'item_date_path_hint' => 'Caminho JSON pro timestamp do item (ex: published_at). Preferido sobre o caminho da chave quando existe. A primeira busca registra o ponto de partida e não encaminha nada, então um feed existente nunca inunda no primeiro dia.',
|
|
],
|
|
],
|
|
|
|
'delete' => [
|
|
'title' => 'Excluir automação',
|
|
'description' => 'Tem certeza que deseja excluir esta automação? Todas as execuções e itens de gatilho também serão removidos. Esta ação não pode ser desfeita.',
|
|
'confirm' => 'Excluir',
|
|
'cancel' => 'Cancelar',
|
|
],
|
|
|
|
'flash' => [
|
|
'deleted' => 'Automação excluída com sucesso!',
|
|
],
|
|
|
|
'errors' => [
|
|
'no_active_social_accounts' => 'Nenhuma conta social ativa configurada para esta automação.',
|
|
'must_have_one_trigger' => 'A automação precisa ter exatamente um nó de trigger.',
|
|
'trigger_must_be_connected' => 'O nó de trigger precisa estar conectado a pelo menos um nó.',
|
|
'graph_contains_cycle' => 'O grafo da automação contém um ciclo.',
|
|
'only_failed_can_retry' => 'Apenas execuções que falharam podem ser repetidas.',
|
|
'no_generated_post' => 'Nenhum post gerado encontrado para esta execução.',
|
|
'url_not_allowed' => 'A URL da requisição aponta para um endereço privado ou inacessível e foi bloqueada.',
|
|
'node_no_longer_exists' => 'O nó :node_id não existe mais nesta automação.',
|
|
'no_trigger_connection' => 'Nenhum nó conectado ao nó de trigger.',
|
|
'fetch_rss_missing_url' => 'O nó Buscar RSS está sem a URL do feed.',
|
|
'fetch_rss_request_failed' => 'A requisição do feed RSS falhou.',
|
|
'fetch_rss_malformed' => 'O feed RSS está malformado.',
|
|
'http_missing_url' => 'O nó de requisição HTTP está sem a URL.',
|
|
'http_request_exception' => 'A requisição HTTP lançou uma exceção.',
|
|
'http_request_failed' => 'A requisição HTTP falhou.',
|
|
'http_items_path_not_array' => 'O items path não resultou em uma lista.',
|
|
'generate_image_format_required' => 'A geração por IA só cria imagens. Escolha um formato de imagem (não vídeo).',
|
|
],
|
|
];
|