iachat/app/javascript/dashboard/i18n/locale/pt_BR/settings.json
Gabriel Jablonski 3aca86aa43
feat(internal-chat): implement internal chat system for agents (#247)
* feat(internal-chat): implement internal chat system for agents (Phase 1+2 MVP)

Add a Slack/Discord-style internal messaging system for Chatwoot agents with
text channels (public/private), direct messages, reactions, typing indicators,
and real-time updates via ActionCable.

Backend:
- 6 database migrations (categories, channels, members, messages, attachments, reactions)
- 6 models under InternalChat:: namespace with validations and associations
- API controllers for categories, channels, messages, members, and reactions
- Pundit policies for authorization (public/private/DM access control)
- MessageCreateService, TypingStatusManager, DefaultChannelSetupService
- InternalChatListener for real-time broadcasting to channel members
- Event types for internal chat events
- Default category/channel setup for new and existing accounts

Frontend:
- Vuex store modules for channels, messages, and typing status
- API clients for channels and messages
- Vue 3 components: InternalChatLayout, ChannelSidebar, ChannelView,
  ChannelHeader, MessageList, MessageBubble, MessageEditor,
  EmojiReactionPicker, ReactionDisplay, TypingIndicator
- Sidebar integration with "Internal Chat" menu item
- ActionCable handlers for real-time message/reaction/typing events
- Route definitions and i18n translations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(internal-chat): add comprehensive specs for models, controllers, policies, services, and listener

- 6 model specs (74 examples) covering associations, validations, scopes, methods
- 5 request specs for all API controllers (categories, channels, messages, members, reactions)
- 4 policy specs testing authorization rules for all actions
- 3 service specs (DefaultChannelSetupService, MessageCreateService, TypingStatusManager)
- 1 listener spec testing real-time broadcasting for all event types
- 6 FactoryBot factories with traits for all InternalChat models

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): fix dispatcher mock in service specs and cursor pagination test

- Allow dispatcher.dispatch in service specs to handle Account.created
  callbacks from factory setup before asserting specific event dispatch
- Fix after-cursor pagination test by adding 1 second offset to avoid
  timestamp precision issues with iso8601 rounding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): address CodeRabbit review — 7 critical security/correctness fixes

- Scope member creation through Current.account.users to prevent cross-account membership
- Scope member_ids in DM creation through Current.account to prevent cross-account invites
- Scope reaction message lookup through channel account to prevent cross-account access
- Fix Vuex store to commit messages array instead of response envelope
- Add UUID generation callback on Channel model (before_validation)
- Add channel access check to reaction deletion policy
- Validate parent_id belongs to same channel in MessageCreateService

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): address CodeRabbit round 2 + fix ChannelSidebar runtime error

- Re-throw error in fetchMessages instead of swallowing with empty array
- Wrap message + attachment creation in transaction for atomicity
- Fix factory to derive account from message (prevent cross-account fixtures)
- Guard listener against cross-account mismatch (not just missing records)
- Add cross-account regression tests to listener spec
- Fix ChannelSidebar computed properties to default to empty arrays

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): auto-setup default channels on account creation and migration

- Add after_create_commit :setup_internal_chat callback on Account model
- Add data migration to create default channels for existing accounts
- Make DefaultChannelSetupService convergent (find_or_create) instead of
  bail-on-exists, so it can sync new members on subsequent runs
- Fix specs to handle default category/channel created by callback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): avoid Vuex state mutation in sort + align muted styling in fallback section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): fix store module name mismatch — register as 'internalChat' not 'internalChatChannels'

Components dispatch to 'internalChat/get' but the module was registered
as 'internalChatChannels'. Also fix ActionCable handlers to use
'internalChat/messages/' nested module path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* i18n(internal-chat): add pt-BR translations for internal chat feature

Backend: default_category_name ('Canais') and default_channel_name ('Geral')
Frontend: all 40+ keys translated to Brazilian Portuguese

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): handle ISO 8601 timestamps in MessageBubble and MessageList

The API returns created_at as ISO strings but messageTimestamp() expects
Unix seconds and MessageList used `* 1000`. Now handles both formats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(internal-chat): build swagger output for internal chat API endpoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(internal-chat): register internal chat tags and paths in swagger index

Add tag definitions and path entries for all 5 internal chat resource
groups in swagger/index.yml and swagger/paths/index.yml. Rebuild output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* i18n(internal-chat): add SIDEBAR.INTERNAL_CHAT key to pt-BR settings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): comprehensive review fixes — backend and frontend

Backend:
- Add attachments to message API responses in both controllers
- Add internal_chat_channel_updated listener handler
- Include reactions in message event broadcast data

Frontend:
- Fix ActionCable dispatch paths to use correct action names
  (addMessageFromCable, updateMessageFromCable, deleteMessageFromCable)
- Connect typingUsers to internalChatTypingStatus store getter
- Fix message field references (edited → content_attributes.edited_at)
- Fix channel type comparisons (use 'private_channel'/'dm' strings)
- Add parent 'internal_chat' to sidebar activeOn array
- Increment unread_count on ActionCable message receive
- Add loadMore handler for cursor-based pagination
- Remove unused is-direct-message prop from InternalChatLayout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): implement phases 3-5 — threads, mentions, notifications, polls, drafts

Phase 3 — Threads, Mentions, Notifications:
- MentionService: parse @user mentions, @all (admin only), generate notifications
- NotificationService: create notifications for channel messages (respects mute)
- Add internal_chat_message/mention notification types to Notification model
- ThreadPanel.vue: slide-out panel for threaded replies
- Integrate mentions + notifications into MessageCreateService

Phase 4 — Polls:
- 3 new migrations: polls, poll_options, poll_votes tables
- 3 new models: Poll, PollOption, PollVote with validations
- PollsController: create poll, vote, unvote with routes
- PollService: voting logic with multiple choice + revote support
- PollCreator.vue: modal for creating polls with options
- PollDisplay.vue: vote UI with progress bars and results
- Polls Vuex store module
- INTERNAL_CHAT_POLL_VOTED event type

Phase 5 — Drafts:
- 1 new migration: drafts table
- Draft model with validations
- DraftsController: full CRUD (replace stub)
- DraftsList.vue: list all user drafts with navigation
- Drafts Vuex store module with auto-save
- Draft route and sidebar integration

Phase 6 — Feature Flag:
- Add INTERNAL_CHAT feature flag to features.yml and featureFlags.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): fix API routing for drafts and polls, add poll voting ActionCable handler

- Fix draft API client to use channel-scoped PATCH/DELETE endpoints
- Create dedicated polls API client with correct poll-based endpoints
- Update polls store to use InternalChatPollsAPI with pollId-based voting
- Add ActionCable handler for internal_chat.poll.voted events
- Add thread and drafts routes to sidebar activeOn array
- Fix drafts store to pass channelId to API calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): fix poll response format and API client routing (review round 2)

- Return message with embedded poll data instead of raw poll response
- Add poll data to message_response in messages controller
- Create dedicated InternalChatPollsAPI client with correct endpoints
- Update PollDisplay.vue to read from message.poll or content_attributes.poll
- Use option.voted flag instead of checking voters array
- Add missing PERCENTAGE i18n key to pt-BR
- Remove unused currentUserId prop from PollDisplay

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): fix poll voting and draft lookup bugs (review round 3)

- Fix draft getter to use internal_chat_channel_id field name
- Split poll set_poll into vote/unvote variants — unvote doesn't need option_id
- Unvote finds user's vote by user_id across all poll options
- Fix ChannelView to extract pollId from message.poll before dispatching
- Fix unvote handler to not require optionId

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 3)

- Expose is_dm, favorited, muted on channel API responses
- Normalize poll cable updates into message-shaped patch
- Add file presence validation to MessageAttachment
- Remove duplicate mention notifications from MentionService
- Make data migration rollback safe (IrreversibleMigration)
- Update factory to include file by default

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: return 201 for channel creation and optimize DM lookup

- Return :created (201) instead of :ok (200) for channel creation
- Replace O(n) Ruby scan with SQL-based DM lookup using ARRAY_AGG

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 5)

- Broadcast channel event after create for real-time notifications
- Separate create/update strong params to prevent channel_type transitions
- Use strong params for typing_status input
- Replace find_by with detect on preloaded collections to fix N+1
- Preload attachments with blobs in show response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 6)

- Serialize DM creation with advisory lock to prevent duplicates
- Broadcast channel deletion event for real-time UI updates
- Use strong params for mark_unread message_id
- Batch unread count computation to eliminate N+1 in index

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: eliminate N+1 in compute_unread_counts with single JOIN query

Replace per-membership COUNT loop with a single JOIN + GROUP BY query
that returns all unread counts in one database call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): quality fixes, missing tests, and Playwright E2E setup

Addresses quality issues found during review and fills test coverage gaps
for the internal chat feature.

Backend fixes:
- Return 201 for all create endpoints (messages, categories, polls, reactions, members)
- Fix N+1 queries: replies.size, poll votes, category channels.count, votes.exists?
- Fix pagination has_more logic to check page size instead of total count
- Scope poll vote/unvote to current account (security fix)
- Add internal_chat.messages.deleted i18n key
- Use find_by! in mark_unread for proper 404 on non-members
- Guard time param parsing with rescue ArgumentError
- Align message response format between channels and messages controllers
- Switch notification service to ActionCable-only (avoid push/email crashes)

Frontend fixes:
- Fix pinned message detection (content_attributes.pinned, not message.pinned)
- Fix thread reply count key (replies_count, not thread_replies_count)
- Fix markUnread to pass message_id parameter
- Fix pagination: PREPEND_MESSAGES mutation instead of overwriting
- Fix typing status to read Vuex reactive state, not stale closure
- Fix deleteDraft argument shape (pass { channelId, draftId })
- Fix DM channel filtering (check both is_dm and channel_type)
- Fix DraftsList navigation to use correct channel ID key
- Wire PollCreator to poll button in MessageEditor
- Wire settings event handler on ChannelHeader
- Reset PollCreator isSubmitting on timeout

New RSpec tests (67 examples):
- Factories: polls, poll_options, poll_votes, drafts
- Model specs: Poll, PollOption, PollVote, Draft
- Controller specs: PollsController, DraftsController
- Service specs: PollService, NotificationService, MentionService

Playwright E2E setup (37 tests):
- Install Playwright with Chromium
- Auth helper with Devise Token Auth login flow
- 8 test suites: navigation, channels, messaging, DMs, reactions, threads, polls, mark-read-unread

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 7)

Backend:
- Use lambda for UUID default in channels migration
- Wrap poll creation in transaction for atomicity
- Preload replies in thread action to avoid N+1
- Broadcast replies_count + attachments in listener (match REST shape)
- Scope draft listing through accessible channels
- Key draft upserts/deletes by parent_id for thread drafts

Frontend:
- Remove duplicate poll methods from internalChatMessages.js (use internalChatPolls.js)
- Persist toggleMute/toggleFavorite to backend via updateMember API
- Clear active channel on DELETE_CHANNEL mutation
- Skip unread increment for active channel in ActionCable handler
- Filter archived channels from sidebar getters
- Fix ChannelHeader isArchived to check status === 'archived'
- Prevent duplicate reactions in ADD_REACTION mutation
- Merge poll data into existing content_attributes on cable updates
- Guard infinite scroll against duplicate loads
- Add response.ok() check in E2E auth helper, remove hardcoded account ID

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 8)

- Remove unused nested typingStatus module from internalChat store
- Add parent_id to draft uniqueness scope and migration index
- Exclude reaction creator from reaction_created broadcast
- Preload attachments and poll associations in thread/messages queries
- Handle `after` fetches with APPEND_MESSAGES mutation
- Wrap channel creation payloads under `channel` key in E2E helpers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rewrite Playwright E2E tests to use actual UI interactions

Completely rewrote all 8 E2E test suites to work with the live app:
- Test through actual UI interactions, not API bypass
- Use correct Portuguese (pt_BR) locale strings
- Use structural selectors matching real Vue component DOM
- Dynamic account ID from login response (no hardcoded values)
- 3 parallel workers, increased timeouts for reliability
- API calls only for preconditions (seeding test data)

29 tests passing across navigation, channels, messaging, DMs,
reactions, threads, polls, and mark-read-unread suites.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use partial unique indexes for draft uniqueness with NULL parent_id

PostgreSQL treats NULL as distinct in unique constraints, so a composite
index on (user_id, channel_id, parent_id) allows duplicate root drafts.
Split into two partial indexes: one for root drafts (WHERE parent_id IS
NULL) and one for thread drafts (WHERE parent_id IS NOT NULL).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 9)

- Remove duplicate index on internal_chat_polls.internal_chat_message_id
  (keep only unique index)
- Add options validation in polls create (return 400 instead of 500)
- Add expiration check to unvote action (match vote behavior)
- Use strong params in messages update action

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 10)

- Change channel associations from destroy_async to destroy (FK
  constraints are ON DELETE RESTRICT, blocking async deletion)
- Remove unused internal_chat notification types and PRIMARY_ACTORS
  entry (notification service uses ActionCable only, no DB records)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 11)

- Scope category_id to current account in channels controller (security)
- Defer message-created event in poll creation until after transaction
- Change message associations from destroy_async to destroy (FK compat)
- Validate option belongs to poll in poll_service
- Use strong params for emoji in reactions controller

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 12)

Backend (9 fixes):
- Gate message update/destroy by channel accessibility in policy
- Guard content_attributes nil before merge in polls controller
- Fix after-cursor pagination to use limit() instead of last()
- Wrap revote in transaction for atomicity in poll service
- Make unvote option-specific for multi-choice polls
- Exclude own messages from unread count
- Make channel activity update monotonic (only write if newer)
- Include actor in message/reaction broadcasts (multi-tab support)
- Return 400 for empty member create instead of 201

Frontend (8 fixes):
- Show uncategorized channels even when categories exist
- Clear editor on channel switch when no draft exists
- Soft-delete messages in store (update in place, don't remove)
- Guard ThreadPanel against out-of-order fetch responses
- Replace hardcoded channel label with i18n key in DraftsList
- Add accessible name to settings button in ChannelHeader
- Add aria-label to search field in ChannelSidebar
- Make MessageBubble actions keyboard-accessible via focus-within

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 13)

- Fix keyword argument mismatch in reactions dispatch_reaction_event
- Add user_id to reaction cable broadcast for shape consistency with REST

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): quality fixes, expanded RSpec + Playwright E2E tests

Fix isArchived computed (checked .archived instead of .status), fix
ReactionDisplay user identification (.user?.id vs .user_id), update
17 spec assertions from :success to :created on create endpoints,
add 32 new RSpec examples (polls, drafts, services), and rewrite
8 Playwright E2E test files with correct selectors, proper test
isolation, and dynamic user ID discovery.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 14)

Prevent duplicate votes on same option in multi-choice polls with
explicit BadRequest guard. Add internal_chat webhook events to
ALLOWED_WEBHOOK_EVENTS so users can subscribe to them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: include poll data in ActionCable broadcast for poll messages

Extract base_message_data helper and enrich message_event_data with
poll options when the message has an associated poll, ensuring
realtime subscribers receive the same poll data as REST API clients.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 15)

Backend: wrap single-choice revotes in transaction, capture member
tokens before channel destroy, exclude own messages from unread count,
strip attachments from deleted messages, enrich poll broadcast payload.

Frontend: use getCurrentRole getter, fix public-results poll display,
sync thread replies via store, add close button a11y, pass option_id
to unvote API, pass parent_id to deleteDraft API.

Models: handle nil last_read_at for new members, skip content
validation for attachment-only messages, align PollService guards
with controller, change category dependent to nullify.

Swagger: add attachments to message schema, fix create status to 201.
E2E: remove fragile waitForTimeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): fix 21 UX/functional issues

Address 21 UX gaps discovered during product testing:

Sidebar & Navigation:
- Fix search icon overlap, extend search to description + DM members
- Add create channel/DM/category buttons and modals
- Show DM member names instead of null
- Include members data in channel index API for DMs

Message Interactions:
- Add delete confirmation dialog
- Implement inline message editing with cancel support
- Toggle emoji reactions (add/remove)
- Support multiple pinned messages with click-to-scroll
- Prevent thread replies from appearing in main chat
- Fix reply count live updates
- Hide pin button on thread messages
- Improve deleted message styling with greyed-out card
- Replace spinner with skeleton loading
- Add markdown toolbar (bold/italic/code)
- Fix thread editing and add vote/unvote handlers

Features & Polish:
- Implement channel settings slide-over panel
- Fix thread loading not affecting main channel spinner
- Fix poll creation field name mismatch with backend API
- Fix drafts: show channel names, handle DM navigation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): use Dialog modal for delete confirmation

Replace window.confirm with the project's Dialog component for
message delete confirmation, providing a consistent UI experience.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 16)

- Require content field in message update OpenAPI schema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 17)

- Sanitize advisory lock SQL with sanitize_sql_array
- Use semantic button for pinned message banner
- Add aria-label to ChannelSettings close button
- Add type="button" to all ChannelSettings buttons
- Gate channel/DM/category creation to admins
- Replace hardcoded 'Direct Message' with i18n key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review feedback (round 18)

- Wrap DM creation payload in channel key for consistency
- Replace raw text in category select with i18n key
- Add IME composition guard to prevent premature send

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): UX round 2, rich editor, team members, drag-and-drop

- Reduce sidebar spacing between search bar and drafts
- Fix search icon overlapping placeholder text
- Replace inline category form with Dialog modal
- Add collapsible sidebar sections with localStorage persistence
- Add drag-and-drop channels across categories (admin-only, vuedraggable)
- Replace textarea editor with WootWriter ProseMirror rich text editor
- Replace regex markdown rendering with shared MessageFormatter
- Wire draft auto-save pipeline with WootWriter (3s debounce watcher)
- Add team + agent selection when creating private channels
- Auto-add all agents when creating public channels
- Sync team members to linked channels via TeamMember callback
- Fix member list not loading on first settings panel open
- Complete PT-BR translations for all internal chat strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): UX round 3, Enter-to-send, mentions, copy link, poll modal

- Send with Enter (not Cmd+Enter), Shift+Enter for newlines
- Enable @mentions via WootWriter suggestions plugin
- Refocus editor after sending a message
- Copy link to message button in hover toolbar
- Poll creator refactored to Dialog with confirm-discard on close
- Channel type uses Switch instead of dropdown
- Category uses components-next Select instead of native select
- Skeleton loading: only on initial load, spinner for pagination
- Scroll position preserved when loading older messages
- Mute/Favorite buttons fixed (store members updated after fetch)
- Add/remove channel members after creation (admin-only)
- Save draft immediately when switching channels
- Settings sidebar remembers open/closed state via localStorage
- Search icon overlap fixed (increased padding)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): DM settings, copy updates, input refocus, member UX

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): member edit for private only, emoji overflow, reaction tooltips

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): thread count sync, scroll loading, copy link, thread/settings exclusivity

- Fix thread reply count doubling (remove duplicate INCREMENT_REPLY_COUNT from sendThreadReply, cable handles it)
- Fix copy link button (use window.location.origin + pathname as fallback)
- Hide poll button in thread editor
- Add "Also send in #channel" checkbox for thread replies
- Increase scroll threshold for loading older messages (100px instead of 0)
- Track and stop loading when oldest message reached
- Thread and settings panels are mutually exclusive
- Refocus editor after send with delayed focus

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): scroll to linked message via ?messageId= query param

Read messageId from route query on mount, scroll to and highlight the
target message after messages load, then clean the query param.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): prevent editor from becoming unfocusable after send

Root cause: passing disabled prop to WootWriter applies pointer-events-none
and ProseMirror does not re-enable contenteditable when disabled returns to
false. Fix: never disable the WootWriter, use a local isSending guard to
prevent double-sends. Refocus 300ms after send for ProseMirror state reload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): simplify send guard, no artificial timeout

Content is cleared immediately before emit, so canSend naturally
returns false (empty content). No isSending guard needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): align poll option remove buttons vertically

Increase padding to p-1.5 and add flex-shrink-0 for consistent sizing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): close poll modal after creating poll

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): poll option X button alignment, discard modal on submit

- Button uses explicit 34px height matching input, no items-center
- Reset form before closing dialog so hasUnsavedChanges is false

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): close settings sidebar when clicking reply

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): update poll UI after voting, fix re-vote error

Vote/unvote actions now dispatch updateMessageFromCable with the API
response to update poll state locally. Pass channelId to enable this.
Clicking an already-voted option correctly triggers unvote.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): include poll data in message response, add timer and voters

Backend: message_response now includes poll data (options, votes, voted
status, voters for public polls) via eager-loaded poll association.
This fixes polls not rendering after page reload.

Frontend PollDisplay:
- Countdown timer showing time remaining until poll closes
- Read-only state when expired (div instead of button, no hover)
- Voter names shown below each option (public polls or admin)
- Prefer content_attributes.poll over message.poll for fresh data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): include channel_id in poll voted cable broadcast

The poll_event_data was missing internal_chat_channel_id, so the
frontend cable handler could not route the update to the correct
channel's message store.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): poll vote highlight, typing off, reaction broadcast, translations

- Preserve per-user voted flags when merging cable poll broadcast
- Send typing_off after 3s of no typing activity
- Include internal_chat_channel_id in reaction event broadcasts
- Fix reaction deleted handler to also check channel_id field
- Simplify "also send in" copy (works for both channels and DMs)
- Add PT-BR translation for ALSO_SEND_IN_CHANNEL

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): unified reaction popover with all emoji groups

Clicking any reaction badge opens a single popover showing all reactions
grouped by emoji with user names. Current user can remove their own
reaction via X button. Replaces per-reaction popover with unified view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): wire close DM button to archive and navigate home

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): close DM via hidden flag on channel membership

Add hidden boolean to channel_members table. Close DM sets hidden=true
via member update API. Sidebar filters out hidden DMs. New messages on
a DM channel automatically unhide all members via listener callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): reaction popover, user names, file upload support

- Include user name in reaction API response (was missing)
- Redesign reaction popover: flat list with emoji + name per row,
  aligned X button for removing own reactions
- Add file upload: paperclip button opens file picker, attached files
  shown as chips with remove, sent via FormData with message
- Store action and API client support files parameter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): reaction user names, unreact button, attachment rendering

- Include user name in reactions across all endpoints (messages_controller,
  listener base_message_data)
- Make unreact X button always visible (bg-n-alpha-2 background)
- Render message attachments as downloadable links with paperclip icon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): image preview for attachments in messages and editor

Messages: images render inline with max-h-60, non-images as download links.
Editor: image files show thumbnail preview, non-images show file icon + name.
Remove button as floating circle on top-right corner of each attachment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): attachment preview matches conversation pattern

- File previews show name + size (e.g. "2 MB") in a horizontal card
- Image thumbnails as 32px squares, non-images show document emoji
- Remove button is a visible X icon (not a floating circle)
- Layout matches AttachmentsPreview from conversation ReplyBox

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): auto-detect image file type from MIME on upload

MessageCreateService now detects file type from content_type instead of
defaulting to :file. Images are correctly tagged as :image so they
render inline in message bubbles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): include file_url in cable broadcast, fix filename display

Listener attachment_event_data now includes file_url so attachments
render correctly on real-time messages without page refresh.
MessageBubble extracts filename from URL or falls back to file_type+ext.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): pin attachments, edit members modal, settings persistence

- Skip content validation when pinning/unpinning (fixes pin on file-only messages)
- Add EditMembersModal with search, add, and remove members for private channels
- Fix settings sidebar always opening: @close now calls handleToggleSettings
  which updates localStorage, not just sets ref to false

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): fix X buttons for attachment remove and reaction unreact

Replace Icon component with inline SVG cross for reliable rendering.
Both attachment remove and reaction unreact buttons now show a visible
X icon at all times with proper vertical alignment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): allow any user to pin messages, not just sender/admin

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): restore Icon component for X buttons (size-4 in size-6 container)

SVG inline approach didn't render. Reverted to Icon i-lucide-x with
larger sizes (size-4 icon in size-6 button) which renders reliably.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): use p-1 + size-4 pattern for X buttons (matches message toolbar)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): thread indicator on messages from threads, allow pinning all

- Show "Thread" badge with icon on messages that have parent_id,
  clicking it opens the parent thread
- Remove parent_id restriction from canPin, any non-deleted message
  can be pinned

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): thread indicator, poll close loop, thread navigation

- Hide thread indicator inside thread panel (inThread prop)
- Open parent thread when clicking thread badge on messages with parent_id
- Fix PollCreator infinite close loop (handleClose no longer calls
  dialogRef.close, since Dialog already triggered the close)
- Look up parent message in store when opening thread from indicator

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(internal-chat): poll duration translations and clickable switch labels

- Duration options use i18n keys (EN + PT-BR: 24 horas, 7 dias, etc.)
- Multiple choice and Public results switches toggle by clicking label

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal_chat): enhance message handling and search functionality

- Added broadcasting of typing off events in InternalChatListener.
- Included member user IDs in channel data for better context.
- Updated message model to allow optional sender association.
- Implemented team mention expansion in MentionService to include team members.
- Enhanced message creation service to store mentioned user IDs in content attributes.
- Introduced a new SearchService for searching channels, DMs, and messages.
- Updated API responses to include has_unread_mention flag for channels.
- Added tests for user deletion behavior in internal chat, ensuring message preservation and reaction handling.
- Improved draft model to allow coexistence of root and thread drafts.
- Added unique indexes for drafts to prevent duplicate entries.
- Implemented foreign key constraints with appropriate delete behaviors for internal chat models.

* feat(internal-chat): swagger docs, webhook events, search UX improvements

Add Swagger documentation for drafts, polls, and search endpoints.
Wire internal_chat_message_deleted and internal_chat_channel_updated
webhook events to the UI and listener. Improve search empty state with
min-chars hint and friendly no-results message. Update CLAUDE.md to
include pt_BR translations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(internal-chat): add draft count display in channel sidebar

* chore: remove playwright config and dependencies

* feat(internal-chat): polish UX, swagger updates, and migration consolidation

- Editor toolbar shortcuts (@ and #) with instant popover trigger,
  including accent-insensitive matching and wider conversation popover
- Localized last activity time and inbox name on conversation preview cards
- Thread + main interplay: also-send-in-channel mirror, parent_id filter,
  per-message conversation link, hidden buttons inside thread view,
  reactions update across both lists, scroll-to-message behavior
- Search service uses f_unaccent for messages, channel names and user names
  via dedicated GIN trigram functional indexes
- Renamed InternalChat::ProGating to InternalChat::Limits with neutral
  semantics
- Consolidated 17 internal chat migrations into 3 (tables, default channels,
  unaccent search) and added a rake task to ensure the f_unaccent function
  exists before db:schema:load
- Swagger paths and definitions updated to match the current state of the
  feature (also_send_in_channel, status codes, pro-required responses,
  hidden member flag, search meta fields, etc.)

* fix(internal-chat): use Rake task augmentation for db:schema:load hook

The previous `Rake::Task['db:schema:load'].enhance(...)` guarded by
`task_defined?` silently no-op'd in CI when the rake file loaded before
ActiveRecord's rake_tasks block ran. Re-opening `db:schema:load` via
Rake's `task name => deps` DSL augments the existing task regardless of
load order, ensuring the f_unaccent function is created before schema.rb
references it.

* fix(internal-chat): enhance db:schema:load from Rakefile after load_tasks

Adding the prereq inside lib/tasks/internal_chat_search.rake (via either
`Rake::Task#enhance` or task DSL augmentation) was being silently dropped
in CI, presumably due to load order between application rake files and
ActiveRecord's `rake_tasks` block. Moving the `enhance` to the Rakefile
itself, after `Rails.application.load_tasks`, guarantees both
`db:schema:load` and `db:internal_chat:ensure_search_functions` are
defined before the prereq is added.

Also leaves a debug `puts` in the task body so future regressions are
visible from CI logs.

* chore(internal-chat): add diagnostic logging to f_unaccent hook

* fix(internal-chat): install f_unaccent on all envs iterated by db:schema:load

Rails' `db:schema:load` in development env iterates over BOTH the
development and test databases (see
`ActiveRecord::Tasks::DatabaseTasks.each_current_environment`), but our
hook was only installing the function on the currently-connected
database. CI defaults to development env (no `RAILS_ENV` set), so the
function landed on `chatwoot_dev` while `chatwoot_test` remained
without it, causing the schema load to fail when creating the functional
indexes against the test DB.

The hook now mirrors the same iteration logic and installs the function
on every relevant config, restoring the original AR connection
afterwards.

* fix(internal-chat): align listener spec with current broadcast payload

- internal_chat_message_created now emits two broadcasts (the message
  itself plus an automatic typing_off), so the spec switches to
  `allow`/`have_received` to assert the message broadcast without caring
  about the additional typing_off call.
- internal_chat_reaction_created payload uses `message_id`, not
  `internal_chat_message_id`. Update the spec expectation to match.

* chore(internal-chat): remove redundant DSL augmentation in rake task

* fix(internal-chat): harden gates, kill N+1s and reduce race risk

Closes review findings raised on the internal chat PR:

- Restrict role mass-assignment in ChannelMembersController so only
  account administrators can promote new members to channel admin.
- Wrap private-channel create/unarchive in a Postgres advisory lock per
  account so concurrent requests can no longer bypass the freemium limit.
- Replace `replies.size` and `votes.size` (per-broadcast queries) with
  `replies_count` / `votes_count` counter caches.
- Make `update_channel_activity` an atomic compare-and-set update so
  concurrent message creates can never regress `last_activity_at`.
- Optimize `Poll#total_votes_count` to use the cached column and eager-
  loaded options instead of a per-poll `votes.count` query.
- Add `internal_chat_messages.account_id` foreign key (`ON DELETE
  CASCADE`) to prevent orphan rows.
- Escape HTML in `ChannelSidebar.highlightMatch` to close a v-html XSS
  via incomplete tags in message search snippets.
- Cleanup `typingOffTimer` on `ChannelView` unmount.
- Add stable sort to `getChannelsByCategory` (alphabetical) and
  `getDMChannels` (last activity) to prevent UI reorder thrash.
- Localize `PollDisplay` time-remaining strings (en + pt-BR).
- Add specs covering the 90-day search history filter and the search
  controller endpoint, plus regenerate the consolidated migration
  with the new columns and FK.

* docs(swagger): note role mass-assignment restriction on channel members

Document that the `role` field on the channel member create payload is
silently coerced to `member` for callers that are not account
administrators, matching the controller behavior introduced in the
previous commit.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 13:50:15 -03:00

1006 lines
45 KiB
JSON

{
"PROFILE_SETTINGS": {
"LINK": "Configurações do Perfil",
"TITLE": "Configurações do Perfil",
"BTN_TEXT": "Atualizar o Perfil",
"DELETE_AVATAR": "Apagar Avatar",
"AVATAR_DELETE_SUCCESS": "Avatar apagado com sucesso",
"AVATAR_DELETE_FAILED": "Ocorreu um erro ao excluir o avatar. Tente novamente",
"UPDATE_SUCCESS": "Seu perfil foi atualizado com sucesso",
"PASSWORD_UPDATE_SUCCESS": "A sua senha foi alterada com sucesso",
"AFTER_EMAIL_CHANGED": "Seu perfil foi atualizado com sucesso. Faça login novamente, pois suas credenciais de login foram alteradas",
"FORM": {
"PICTURE": "Foto do perfil",
"AVATAR": "Imagem de Perfil",
"ERROR": "Por favor, corrija os erros",
"REMOVE_IMAGE": "Excluir",
"UPLOAD_IMAGE": "Carregar imagem",
"UPDATE_IMAGE": "Atualizar Imagem",
"PROFILE_SECTION": {
"TITLE": "Perfil",
"NOTE": "Seu endereço de e-mail é sua identidade e é usado para fazer login."
},
"SEND_MESSAGE": {
"TITLE": "Tecla de atalho para enviar mensagens",
"NOTE": "Você pode selecionar uma tecla de atalho (Enter ou Cmd/Ctrl+Enter) com base na sua preferência de escrita.",
"UPDATE_SUCCESS": "Suas configurações foram atualizadas com sucesso",
"CARD": {
"ENTER_KEY": {
"HEADING": "Enter (↵)",
"CONTENT": "Enviar mensagens pressionando a tecla Enter em vez de clicar no botão de enviar."
},
"CMD_ENTER_KEY": {
"HEADING": "Cmd/Ctrl + Enter (⌘ + ↵)",
"CONTENT": "Pressione a tecla Cmd/Ctrl + Enter ao invés de clicar no botão enviar."
}
}
},
"INTERFACE_SECTION": {
"TITLE": "Interface",
"NOTE": "Personalize a aparência do seu painel do Chatwoot.",
"FONT_SIZE": {
"TITLE": "Tamanho da fonte",
"NOTE": "Ajuste o tamanho do texto do painel com base na sua preferência.",
"UPDATE_SUCCESS": "As configurações de sua fonte foram atualizadas com sucesso",
"UPDATE_ERROR": "Ocorreu um erro ao atualizar as configurações de fonte, por favor, tente novamente",
"OPTIONS": {
"SMALLER": "Menor",
"SMALL": "Pequeno",
"DEFAULT": "Padrão",
"LARGE": "Grande",
"LARGER": "Maior",
"EXTRA_LARGE": "Muito Grande"
}
},
"LANGUAGE": {
"TITLE": "Idioma preferido",
"NOTE": "Escolha o idioma que deseja usar.",
"UPDATE_SUCCESS": "Suas configurações de idioma foram atualizadas com sucesso",
"UPDATE_ERROR": "Ocorreu um erro ao atualizar as configurações de idioma, por favor, tente novamente",
"USE_ACCOUNT_DEFAULT": "Usar padrão da conta"
}
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Assinatura de mensagens pessoais",
"NOTE": "Crie uma assinatura de mensagem única para aparecer no final de cada mensagem que você enviar de qualquer caixa de entrada. Você também pode incluir uma imagem anexada, suportada em live-chat, e-mail e caixas de entrada de API.",
"BTN_TEXT": "Salvar assinatura da mensagem",
"API_ERROR": "Não foi possível salvar a assinatura! Tente novamente",
"API_SUCCESS": "Assinatura salva com sucesso",
"RESET_TO_DEFAULT": "Restaurar para padrão",
"RESET_SUCCESS": "Assinatura da caixa de entrada removida, usando assinatura padrão",
"INBOX_SELECTOR": {
"LABEL": "Caixa de entrada",
"DEFAULT": "Padrão (todas as caixas de entrada)",
"CUSTOM": "Personalizada"
},
"IMAGE_UPLOAD_ERROR": "Não foi possível fazer o upload da imagem! Tente novamente",
"IMAGE_UPLOAD_SUCCESS": "Imagem adicionada com sucesso. Por favor clique em salvar para salvar a assinatura",
"IMAGE_UPLOAD_SIZE_ERROR": "O tamanho da imagem deve ser menor que {size}MB",
"SIGNATURE_POSITION": {
"LABEL": "Posição da assinatura",
"OPTIONS": {
"TOP": "Início da mensagem",
"BOTTOM": "Final da mensagem"
}
},
"SIGNATURE_SEPARATOR": {
"LABEL": "Separador da assinatura",
"OPTIONS": {
"BLANK": "Linha em branco",
"HORIZONTAL_LINE": "Linha horizontal (--)"
}
},
"PREVIEW": {
"TITLE": "Pré-visualização da Assinatura",
"NOTE": "Esta é a aparência da sua assinatura nas mensagens",
"EMPTY": "Digite uma assinatura acima para ver a pré-visualização",
"SAMPLE_MESSAGE": "Olá! Obrigado por entrar em contato. Como posso ajudá-lo hoje?"
}
},
"MESSAGE_SIGNATURE": {
"LABEL": "Assinatura da mensagem",
"ERROR": "Assinatura da mensagem não pode estar vazia",
"PLACEHOLDER": "Insira aqui a assinatura de sua mensagem pessoal."
},
"PASSWORD_SECTION": {
"TITLE": "Senha",
"NOTE": "A atualização da sua senha redefiniria o seu login em vários dispositivos.",
"BTN_TEXT": "Mudar Senha"
},
"SECURITY_SECTION": {
"TITLE": "Segurança",
"NOTE": "Gerencie recursos adicionais de segurança para sua conta.",
"MFA_BUTTON": "Gerenciar autenticação de dois fatores "
},
"ACCESS_TOKEN": {
"TITLE": "Token de acesso",
"NOTE": "Esse token pode ser usado se você estiver criando uma integração baseada em API",
"COPY": "Copiar",
"RESET": "Reiniciar",
"CONFIRM_RESET": "Você tem certeza?",
"CONFIRM_HINT": "Clique novamente para confirmar",
"RESET_SUCCESS": "Token de acesso gerado novamente com sucesso",
"RESET_ERROR": "Não foi possível regerar o token de acesso. Por favor, tente novamente"
},
"AUDIO_NOTIFICATIONS_SECTION": {
"TITLE": "Alertas de áudio",
"NOTE": "Habilitar notificações de áudio no painel para novas mensagens e conversas.",
"PLAY": "Reproduzir áudio",
"ALERT_TYPES": {
"NONE": "Nenhuma",
"MINE": "Atribuído",
"ALL": "Todos",
"ASSIGNED": "Conversas atribuídas a mim",
"UNASSIGNED": "Conversas não atribuídas",
"NOTME": "Conversas abertas atribuídas a outras pessoas"
},
"ALERT_COMBINATIONS": {
"NONE": "Você não selecionou nenhuma opção, você não receberá nenhum alerta de áudio.",
"ASSIGNED": "Você receberá alertas para conversas atribuídas a você.",
"UNASSIGNED": "Você receberá alertas para quaisquer conversas não atribuídas.",
"NOTME": "Você receberá alertas para conversas atribuídas a outras pessoas.",
"ASSIGNED+UNASSIGNED": "Receberá alertas de conversas atribuídas e de quaisquer conversas não atendidas.",
"ASSIGNED+NOTME": "Você receberá alertas para conversas atribuídas a você e a outros, mas não para conversas não atribuídas.",
"NOTME+UNASSIGNED": "Você receberá alertas de conversas não atendidas e aquelas atribuídas a outros.",
"ASSIGNED+NOTME+UNASSIGNED": "Você receberá alertas para quaisquer conversas não atribuídas."
},
"ALERT_TYPE": {
"TITLE": "Eventos de alerta para conversas",
"NONE": "Nenhuma",
"ASSIGNED": "Conversas atribuídas",
"ALL_CONVERSATIONS": "Todas as conversas"
},
"DEFAULT_TONE": {
"TITLE": "Titulo:"
},
"CONDITIONS": {
"TITLE": "Condições:",
"CONDITION_ONE": "Enviar alertas de áudio apenas se a janela do navegador não estiver ativa",
"CONDITION_TWO": "Enviar alertas a cada 30 segundos até que todas as conversas atribuídas sejam lidas"
},
"SOUND_PERMISSION_ERROR": "A reprodução automática está desativada no seu navegador. Para ouvir alertas automaticamente, habilite a permissão de som nas configurações do seu navegador ou interaja com a página.",
"READ_MORE": "Ler mais"
},
"EMAIL_NOTIFICATIONS_SECTION": {
"TITLE": "Notificações por e-mail",
"NOTE": "Atualize suas preferências de notificação por e-mail aqui",
"CONVERSATION_ASSIGNMENT": "Enviar notificações por email quando uma conversa for atribuída a mim",
"CONVERSATION_CREATION": "Enviar notificações por email quando uma nova conversa for criada",
"CONVERSATION_MENTION": "Enviar notificações por email quando você for mencionado em uma conversa",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envie notificações por e-mail quando uma nova mensagem for criada numa conversa já atribuída",
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Enviar notificações por email quando uma nova mensagem é criada em uma conversa que você participa",
"SLA_MISSED_FIRST_RESPONSE": "Enviar notificações por e-mail quando uma conversa perder a primeira resposta SLA",
"SLA_MISSED_NEXT_RESPONSE": "Enviar notificações por e-mail quando uma conversa perder o próximo SLA de resposta",
"SLA_MISSED_RESOLUTION": "Enviar notificações por e-mail quando uma conversa perder o SLA de resolução"
},
"NOTIFICATIONS": {
"TITLE": "Preferências de notificação",
"TYPE_TITLE": "Tipo de notificação",
"EMAIL": "E-mail",
"PUSH": "Notificação ",
"TYPES": {
"CONVERSATION_CREATED": "Uma nova conversa foi criada",
"CONVERSATION_ASSIGNED": "Uma conversa foi atribuída a você",
"CONVERSATION_MENTION": "Você foi mencionado em uma conversa",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Uma nova mensagem foi criada e atribuída",
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Uma nova mensagem foi criada em uma conversa que você participa",
"SLA_MISSED_FIRST_RESPONSE": "Uma conversa perde o SLA de primeira resposta",
"SLA_MISSED_NEXT_RESPONSE": "Uma conversa perde o SLA da próxima resposta",
"SLA_MISSED_RESOLUTION": "Uma conversa perde o SLA de resolução"
},
"BROWSER_PERMISSION": "Ative notificações push em seu navegador para poder recebê-las"
},
"API": {
"UPDATE_SUCCESS": "Suas preferências de notificação foram atualizadas com sucesso",
"UPDATE_ERROR": "Ocorreu um erro ao atualizar as preferências, por favor, tente novamente"
},
"PUSH_NOTIFICATIONS_SECTION": {
"TITLE": "Notificações via Push",
"NOTE": "Atualize suas preferências de notificação Push aqui",
"CONVERSATION_ASSIGNMENT": "Enviar notificações push quando uma conversa é atribuída a mim",
"CONVERSATION_CREATION": "Enviar notificações push quando uma nova conversa é criada",
"CONVERSATION_MENTION": "Enviar notificações push quando você for mencionado em uma conversa",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envie notificações quando uma nova mensagem for criada numa conversa já atribuída",
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Enviar notificações push quando uma nova mensagem é criada em uma conversa que você participa",
"HAS_ENABLED_PUSH": "Você ativou push para este navegador.",
"REQUEST_PUSH": "Habilitar notificações push",
"SLA_MISSED_FIRST_RESPONSE": "Enviar notificações quando uma conversa perder a primeira resposta SLA",
"SLA_MISSED_NEXT_RESPONSE": "Enviar notificações quando uma conversa perder a próxima resposta SLA",
"SLA_MISSED_RESOLUTION": "Enviar notificações quando uma conversa perder resolução de resolução SLA"
},
"PROFILE_IMAGE": {
"LABEL": "Imagem do Perfil"
},
"NAME": {
"LABEL": "Seu nome completo",
"ERROR": "Por favor, insira um nome completo válido",
"PLACEHOLDER": "Por favor, digite seu nome completo"
},
"DISPLAY_NAME": {
"LABEL": "Nome para exibição",
"ERROR": "Por favor, insira um nome de exibição válido",
"PLACEHOLDER": "Por favor, insira um nome de exibição para ser exibido em conversas"
},
"AVAILABILITY": {
"LABEL": "Disponibilidade",
"STATUS": {
"ONLINE": "Online",
"BUSY": "Ocupado",
"OFFLINE": "Offline"
},
"SET_AVAILABILITY_SUCCESS": "Disponibilidade foi definida com sucesso",
"SET_AVAILABILITY_ERROR": "Não foi possível definir a disponibilidade, por favor tente novamente",
"IMPERSONATING_ERROR": "Não é possível alterar a disponibilidade enquanto personifica um usuário"
},
"EMAIL": {
"LABEL": "Seu e-mail",
"ERROR": "Por favor, insira um endereço de e-mail válido",
"PLACEHOLDER": "Por favor, insira seu endereço de e-mail, que será exibido em conversas"
},
"CURRENT_PASSWORD": {
"LABEL": "Senha atual",
"ERROR": "Por favor, digite a senha atual",
"PLACEHOLDER": "Por favor, digite a senha atual"
},
"PASSWORD": {
"LABEL": "Nova senha",
"ERROR": "Por favor, digite uma senha de comprimento 6 ou mais",
"PLACEHOLDER": "Por favor, digite uma nova senha"
},
"PASSWORD_CONFIRMATION": {
"LABEL": "Confirme a nova senha",
"ERROR": "A confirmação da senha é diferente. Favor digitar novamente",
"PLACEHOLDER": "Por favor, digite sua nova senha "
}
}
},
"SIDEBAR_ITEMS": {
"CHANGE_AVAILABILITY_STATUS": "Trocar",
"CHANGE_ACCOUNTS": "Alterar conta",
"SWITCH_ACCOUNT": "Alterar conta",
"CONTACT_SUPPORT": "Contate o suporte",
"SELECTOR_SUBTITLE": "Selecione uma conta da lista a seguir",
"PROFILE_SETTINGS": "Configurações do Perfil",
"YEAR_IN_REVIEW": "Retrospectiva do Ano",
"KEYBOARD_SHORTCUTS": "Atalhos do teclado",
"APPEARANCE": "Alterar Tema",
"SUPER_ADMIN_CONSOLE": "Console de Super Admin",
"DOCS": "Ler documentação",
"CHANGELOG": "Notas de versão",
"LOGOUT": "Encerrar sessão"
},
"APP_GLOBAL": {
"TRIAL_MESSAGE": "dias de teste restantes.",
"TRAIL_BUTTON": "Comprar agora",
"DELETED_USER": "Deletar Usuário",
"EMAIL_VERIFICATION_PENDING": "Parece que você ainda não verificou seu endereço de e-mail. Por favor, verifique sua caixa de entrada pelo e-mail de verificação.",
"RESEND_VERIFICATION_MAIL": "Reenviar e-mail de verificação",
"EMAIL_VERIFICATION_SENT": "O e-mail de verificação foi enviado. Por favor, verifique sua caixa de entrada.",
"ACCOUNT_SUSPENDED": {
"TITLE": "Conta Suspensa",
"MESSAGE": "Sua conta está suspensa. Entre em contato com a equipe de suporte para obter mais informações."
},
"NO_ACCOUNTS": {
"TITLE": "Nenhuma conta encontrada",
"MESSAGE_CLOUD": "Você não faz parte de nenhuma conta no momento. Se acreditar que isso é um engano, entre em contato com nossa equipe de suporte.",
"MESSAGE_SELF_HOSTED": "Você não faz parte de nenhuma conta no momento. Entre em contato com o seu administrador.",
"LOGOUT": "Encerrar sessão"
}
},
"COMPONENTS": {
"CODE": {
"BUTTON_TEXT": "Copiar",
"CODEPEN": "Abrir em CodePen",
"COPY_SUCCESSFUL": "Código copiado para área de transferência com sucesso"
},
"SHOW_MORE_BLOCK": {
"SHOW_MORE": "Mostrar Mais",
"SHOW_LESS": "Mostrar Menos"
},
"FILE_BUBBLE": {
"DOWNLOAD": "Baixar",
"UPLOADING": "Enviando...",
"INSTAGRAM_STORY_UNAVAILABLE": "Este story não está mais disponível.",
"INSTAGRAM_STORY_REPLY": "Respondido ao seu story:"
},
"LOCATION_BUBBLE": {
"SEE_ON_MAP": "Ver Localização"
},
"FORM_BUBBLE": {
"SUBMIT": "Enviar"
},
"MEDIA": {
"IMAGE_UNAVAILABLE": "Esta imagem não está mais disponível.",
"AUDIO_UNAVAILABLE": "Este áudio não está mais disponível.",
"LOADING_FAILED": "Falha no carregamento"
}
},
"CONFIRM_EMAIL": "Verificando...",
"SETTINGS": {
"INBOXES": {
"NEW_INBOX": "Adicionar Caixa de Entrada"
}
},
"SIDEBAR": {
"NO_ITEMS": "Nenhum item",
"CURRENTLY_VIEWING_ACCOUNT": "Visualização atual:",
"SWITCH": "Trocar",
"INBOX_VIEW": "Caixa de entrada",
"CONVERSATIONS": "Conversas",
"INBOX": "Caixa de Entrada",
"ALL_CONVERSATIONS": "Todas as conversas",
"MENTIONED_CONVERSATIONS": "Menções",
"PARTICIPATING_CONVERSATIONS": "Participantes",
"UNATTENDED_CONVERSATIONS": "Não atendidas",
"REPORTS": "Relatórios",
"SETTINGS": "Configurações",
"CONTACTS": "Contatos",
"ACTIVE": "Ativo",
"COMPANIES": "Empresas",
"ALL_COMPANIES": "Todas as empresas",
"INTERNAL_CHAT": "Chat Interno",
"KANBAN": "Kanban",
"CAPTAIN": "Capitão",
"CAPTAIN_ASSISTANTS": "Assistentes",
"CAPTAIN_DOCUMENTS": "Documentos",
"CAPTAIN_RESPONSES": "FAQs",
"CAPTAIN_TOOLS": "Ferramentas",
"CAPTAIN_SCENARIOS": "Cenários",
"CAPTAIN_PLAYGROUND": "Playground",
"CAPTAIN_INBOXES": "Caixas de Entrada",
"CAPTAIN_SETTINGS": "Configurações",
"HOME": "Principal",
"AGENTS": "Agentes",
"AGENT_BOTS": "Robôs",
"APPS": "Apps",
"AUDIT_LOGS": "Auditoria",
"INBOXES": "Caixas de Entrada",
"NOTIFICATIONS": "Notificações",
"CANNED_RESPONSES": "Respostas Prontas",
"INTEGRATIONS": "Integrações",
"PROFILE_SETTINGS": "Configurações do Perfil",
"ACCOUNT_SETTINGS": "Conta",
"APPLICATIONS": "Aplicações",
"LABELS": "Etiquetas",
"CUSTOM_ATTRIBUTES": "Atributos Personalizados",
"AUTOMATION": "Automação",
"MACROS": "Macros",
"TEAMS": "Times",
"BILLING": "Cobrança",
"CUSTOM_VIEWS_FOLDER": "Pastas",
"CUSTOM_VIEWS_SEGMENTS": "Segmentos",
"ALL_CONTACTS": "Todos os Contatos",
"TAGGED_WITH": "Marcado com",
"NEW_LABEL": "Nova etiqueta",
"NEW_TEAM": "Novo time",
"NEW_INBOX": "Nova caixa de entrada",
"REPORTS_CONVERSATION": "Conversas",
"CSAT": "CSAT",
"LIVE_CHAT": "Chat ao vivo",
"SMS": "SMS",
"WHATSAPP": "WhatsApp",
"CAMPAIGNS": "Campanhas",
"ONGOING": "Em andamento",
"ONE_OFF": "Única",
"REPORTS_SLA": "SLA",
"REPORTS_BOT": "Robôs",
"REPORTS_AGENT": "Agentes",
"REPORTS_LABEL": "Etiquetas",
"REPORTS_INBOX": "Caixa de Entrada",
"REPORTS_TEAM": "Time",
"AGENT_ASSIGNMENT": "Atribuição de Agentes",
"SET_AVAILABILITY_TITLE": "Defina como",
"SET_YOUR_AVAILABILITY": "Disponibilidade",
"SLA": "SLA",
"CUSTOM_ROLES": "Funções Personalizadas",
"BETA": "Beta",
"REPORTS_OVERVIEW": "Visão geral",
"REAUTHORIZE": "A conexão com a sua caixa de entrada expirou. Por favor, reconecte para continuar recebendo e enviando mensagens",
"HELP_CENTER": {
"TITLE": "Central de Ajuda",
"ARTICLES": "Artigos",
"CATEGORIES": "Categorias",
"LOCALES": "Localidades",
"SETTINGS": "Configurações"
},
"CHANNELS": "Canais",
"SET_AUTO_OFFLINE": {
"TEXT": "Marcar offline automaticamente",
"INFO_TEXT": "Deixe o sistema marcar você automaticamente quando você não estiver usando o app ou o painel de controle.",
"INFO_SHORT": "Marcar off-line automaticamente quando não estiver usando o aplicativo."
},
"DOCS": "Ler documentos",
"SECURITY": "Segurança",
"CAPTAIN_AI": "Capitão",
"CONVERSATION_WORKFLOW": "Fluxo de Conversa"
},
"CAPTAIN_SETTINGS": {
"TITLE": "Configurações do Capitão",
"DESCRIPTION": "Configure seus modelos e recursos de IA para o Capitão. O Capitão utiliza um modelo de cobrança baseado em créditos; você será cobrado em créditos por cada ação que o Capitão realizar, dependendo do modelo selecionado.",
"LOADING": "Carregando configurações do Capitão...",
"LINK_TEXT": "Saiba mais sobre os Créditos do Capitão",
"NOT_ENABLED": "O Capitão não está habilitado para a sua conta. Atualize seu plano para acessar os recursos do Capitão.",
"MODEL_CONFIG": {
"TITLE": "Configuração do Modelo",
"DESCRIPTION": "Selecione modelos de IA para diferentes recursos.",
"SELECT_MODEL": "Selecionar modelo",
"CREDITS_PER_MESSAGE": "{credits} crédito/mensagem",
"COMING_SOON": "Em breve",
"EDITOR": {
"TITLE": "Recursos do Editor",
"DESCRIPTION": "Potencializa a escrita inteligente, correções gramaticais, ajustes de tom e aprimoramento de conteúdo no editor de mensagens."
},
"ASSISTANT": {
"TITLE": "Assistente",
"DESCRIPTION": "Gerencia respostas automatizadas, resumos de conversas e sugestões inteligentes de resposta para interações com clientes."
},
"COPILOT": {
"TITLE": "Copiloto",
"DESCRIPTION": "Fornece sugestões contextuais em tempo real, recomendações da base de conhecimento e insights proativos durante as conversas."
}
},
"FEATURES": {
"TITLE": "Funcionalidades",
"DESCRIPTION": "Ative ou desative recursos com tecnologia de IA.",
"AUDIO_TRANSCRIPTION": {
"TITLE": "Transcrição de Áudio",
"DESCRIPTION": "Converte automaticamente mensagens de voz e gravações de chamadas em transcrições de texto pesquisáveis."
},
"HELP_CENTER_SEARCH": {
"TITLE": "Indexação de Pesquisa da Central de Ajuda",
"DESCRIPTION": "Use IA para realizar buscas com reconhecimento de contexto nos artigos da sua central de ajuda."
},
"LABEL_SUGGESTION": {
"TITLE": "Sugestão de Etiquetas",
"DESCRIPTION": "Sugere automaticamente rótulos e etiquetas relevantes para as conversas, com base na análise de conteúdo e no contexto.",
"MODEL_TITLE": "Modelo de Sugestão de Etiquetas",
"MODEL_DESCRIPTION": "Selecione o modelo de IA a ser utilizado para analisar as conversas e sugerir etiquetas apropriadas"
}
},
"API": {
"SUCCESS": "Configurações do Capitão atualizadas com sucesso.",
"ERROR": "Falha ao atualizar as configurações do Capitão. Por favor, tente novamente."
}
},
"BILLING_SETTINGS": {
"TITLE": "Cobrança",
"DESCRIPTION": "Gerencie sua assinatura aqui, faça o upgrade do seu plano e obtenha mais para seu time.",
"CURRENT_PLAN": {
"TITLE": "Plano Atual",
"PLAN_NOTE": "Você está atualmente inscrito no plano **{plan}** com **{quantity}** licenças",
"SEAT_COUNT": "Número de assentos",
"RENEWS_ON": "Renovações em"
},
"VIEW_PRICING": "Ver Preços",
"MANAGE_SUBSCRIPTION": {
"TITLE": "Gerencie sua assinatura",
"DESCRIPTION": "Veja suas faturas anteriores, edite seus detalhes de cobrança, ou cancele sua assinatura.",
"BUTTON_TXT": "Ir para o portal de cobrança"
},
"CAPTAIN": {
"TITLE": "Capitão",
"DESCRIPTION": "Gerenciar uso e créditos para o Captain AI.",
"BUTTON_TXT": "Comprar mais créditos",
"DOCUMENTS": "Documentos",
"RESPONSES": "Respostas",
"UPGRADE": "O capitão não está disponível no plano gratuito, faça o upgrade para ter acesso aos assistentes, co-piloto e muito mais.",
"REFRESH_CREDITS": "Atualizar"
},
"CHAT_WITH_US": {
"TITLE": "Precisa de ajuda?",
"DESCRIPTION": "Você está com algum problema de cobrança? Nós estamos aqui para ajudar.",
"BUTTON_TXT": "Fale conosco no chat"
},
"NO_BILLING_USER": "A sua conta de cobrança está sendo configurada. Atualize a página e tente novamente.",
"TOPUP": {
"BUY_CREDITS": "Comprar mais créditos",
"MODAL_TITLE": "Comprar Créditos de IA",
"MODAL_DESCRIPTION": "Comprar créditos adicionais para o Capitão IA.",
"CREDITS": "CRÉDITOS",
"ONE_TIME": "único",
"POPULAR": "Mais populares",
"NOTE_TITLE": "Nota:",
"NOTE_DESCRIPTION": "Créditos são adicionados imediatamente e expiram em 6 meses. Uma assinatura ativa é necessária para usar créditos. Créditos adquiridos são consumidos após seus créditos do plano mensal.",
"CANCEL": "Cancelar",
"PURCHASE": "Comprar Créditos",
"LOADING": "Carregando opções...",
"FETCH_ERROR": "Falha ao carregar opções de crédito. Por favor, tente novamente.",
"PURCHASE_ERROR": "Falha ao processar a compra. Por favor, tente novamente.",
"PURCHASE_SUCCESS": "Foram adicionados {credits} créditos com sucesso à sua conta",
"CONFIRM": {
"TITLE": "Confirmar Compra",
"DESCRIPTION": "Você está prestes a comprar {credits} créditos por {amount}.",
"INSTANT_DEDUCTION_NOTE": "Seu cartão salvo será cobrado imediatamente após a confirmação.",
"GO_BACK": "Voltar",
"CONFIRM_PURCHASE": "Confirmar Compra"
}
}
},
"SECURITY_SETTINGS": {
"TITLE": "Segurança",
"DESCRIPTION": "Gerencie as configurações de segurança da sua conta.",
"LINK_TEXT": "Saiba mais sobre o SAML SSO",
"SAML_DISABLED_MESSAGE": "O SSO via SAML está desativado no momento. Entre em contato com o administrador para habilitar esse recurso.",
"SAML": {
"TITLE": "SAML SSO",
"NOTE": "Configure o login único via SAML para sua conta. Os usuários se autenticarão por meio do seu provedor de identidade em vez de usar e-mail e senha.",
"ACS_URL": {
"LABEL": "ACS URL",
"TOOLTIP": "URL do Assertion Consumer Service - Configure esta URL no seu IdP como o destino para as respostas SAML"
},
"SSO_URL": {
"LABEL": "SSO URL",
"HELP": "A URL para onde as solicitações de autenticação SAML serão enviadas",
"PLACEHOLDER": "https://your-idp.com/saml/sso"
},
"CERTIFICATE": {
"LABEL": "Certificado de assinatura no formato PEM",
"HELP": "O certificado público do seu provedor de identidade, usado para verificar as respostas SAML",
"PLACEHOLDER": "-----BEGIN CERTIFICATE-----\nMIIC..."
},
"FINGERPRINT": {
"LABEL": "Impressão digital",
"TOOLTIP": "Impressão digital SHA-1 do certificado - use-a para verificar o certificado na configuração do seu IdP"
},
"COPY_SUCCESS": "Código copiado para área de transferência com sucesso",
"SP_ENTITY_ID": {
"LABEL": "SP Entity ID",
"HELP": "Identificador exclusivo desta aplicação como provedor de serviço (gerado automaticamente).",
"TOOLTIP": "Identificador exclusivo do Chatwoot como Provedor de Serviço - configure-o nas configurações do seu IdP"
},
"IDP_ENTITY_ID": {
"LABEL": "ID da Entidade do Provedor de Identidade",
"HELP": "Identificador exclusivo do seu provedor de identidade (geralmente encontrado na configuração do IdP)",
"PLACEHOLDER": "https://seu-idp.com/saml"
},
"UPDATE_BUTTON": "Atualizar configurações de SAML",
"API": {
"SUCCESS": "Configurações de SAML atualizadas com sucesso",
"ERROR": "Falha ao atualizar as configurações de SAML",
"ERROR_LOADING": "Falha ao carregar as configurações de SAML",
"DISABLED": "Configurações de SAML desativadas com sucesso"
},
"VALIDATION": {
"REQUIRED_FIELDS": "A URL de SSO, o ID da Entidade do Provedor de Identidade e o Certificado são campos obrigatórios",
"SSO_URL_ERROR": "Por favor, insira uma URL de SSO válida",
"CERTIFICATE_ERROR": "O certificado é necessário",
"IDP_ENTITY_ID_ERROR": "O ID da Entidade do Provedor de Identidade é obrigatório"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "O recurso de SSO via SAML está disponível apenas nos planos Enterprise.",
"UPGRADE_PROMPT": "Atualize para um plano Enterprise para ter acesso ao login único via SAML e outros recursos avançados de segurança.",
"ASK_ADMIN": "Entre em contato com seu administrador para fazer a atualização."
},
"PAYWALL": {
"TITLE": "Atualizar para habilitar o SSO via SAML",
"AVAILABLE_ON": "O recurso de SSO via SAML está disponível apenas nos planos Enterprise.",
"UPGRADE_PROMPT": "Atualize seu plano para ter acesso ao login único via SAML e outros recursos avançados.",
"UPGRADE_NOW": "Atualizar agora",
"CANCEL_ANYTIME": "Você pode alterar ou cancelar seu plano a qualquer momento"
},
"ATTRIBUTE_MAPPING": {
"TITLE": "Configuração de Atributos SAML",
"DESCRIPTION": "Os seguintes mapeamentos de atributos devem ser configurados no seu provedor de identidade"
},
"INFO_SECTION": {
"TITLE": "Informações do Provedor de Serviço",
"TOOLTIP": "Copie esses valores e configure-os no seu Provedor de Identidade para estabelecer a conexão SAML"
}
}
},
"CONVERSATION_WORKFLOW": {
"INDEX": {
"HEADER": {
"TITLE": "Fluxos de Conversa",
"DESCRIPTION": "Configure regras e campos obrigatórios para a resolução de conversas."
}
},
"REQUIRED_ATTRIBUTES": {
"TITLE": "Atributos obrigatórios para resolução",
"DESCRIPTION": "Ao resolver uma conversa, os agentes serão solicitados a preencher esses atributos caso ainda não o tenham feito.",
"NO_ATTRIBUTES": "Nenhum atributo adicionado ainda",
"ADD": {
"TITLE": "Adicionar Atributos",
"SEARCH_PLACEHOLDER": "Pesquisar atributos"
},
"SAVE": {
"SUCCESS": "Atributos obrigatórios atualizados",
"ERROR": "Não foi possível atualizar os atributos obrigatórios. Por favor, tente novamente"
},
"MODAL": {
"TITLE": "Resolver conversa",
"DESCRIPTION": "Por favor, preencha os seguintes atributos personalizados antes de resolver esta conversa",
"ACTIONS": {
"RESOLVE": "Resolver conversa",
"CANCEL": "Cancelar"
},
"PLACEHOLDERS": {
"TEXT": "Escreva uma nota...",
"NUMBER": "Insira um número",
"LINK": "Adicione um link",
"DATE": "Selecione uma data",
"LIST": "Selecione uma opção"
},
"CHECKBOX": {
"YES": "Sim",
"NO": "Não"
}
},
"PAYWALL": {
"TITLE": "Atualize para usar os atributos obrigatórios",
"AVAILABLE_ON": "O recurso de atributos obrigatórios da conversa está disponível nos planos Business e Enterprise.",
"UPGRADE_PROMPT": "Atualize seu plano para solicitar que os agentes preencham os atributos obrigatórios antes da resolução da conversa.",
"UPGRADE_NOW": "Atualizar agora",
"CANCEL_ANYTIME": "Você pode alterar ou cancelar seu plano a qualquer momento"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "O recurso de atributos obrigatórios de conversa está disponível nos planos pagos.",
"UPGRADE_PROMPT": "Atualize para um plano pago para exigir atributos obrigatórios antes da resolução da conversa.",
"ASK_ADMIN": "Entre em contato com seu administrador para fazer a atualização."
}
}
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "Ah oh! Não conseguimos encontrar nenhuma conta. Por favor, crie uma nova conta para continuar.",
"NEW_ACCOUNT": "Nova conta",
"SELECTOR_SUBTITLE": "Criar nova conta",
"API": {
"SUCCESS_MESSAGE": "Conta criada com sucesso",
"EXIST_MESSAGE": "Esta conta já existe",
"ERROR_MESSAGE": "Não foi possível conectar ao servidor Woot, por favor tente novamente mais tarde"
},
"FORM": {
"NAME": {
"LABEL": "Nome da empresa",
"PLACEHOLDER": "Informe o nome da conta"
},
"SUBMIT": "Enviar",
"CANCEL": "Cancelar"
}
},
"KEYBOARD_SHORTCUTS": {
"TOGGLE_MODAL": "Alternar Modal",
"TITLE": {
"OPEN_CONVERSATION": "Abrir conversa",
"RESOLVE_AND_NEXT": "Resolver e ir para o próximo",
"NAVIGATE_DROPDOWN": "Navegar pelos itens suspensos",
"RESOLVE_CONVERSATION": "Resolver Conversa",
"GO_TO_CONVERSATION_DASHBOARD": "Ir para Painel de Conversação",
"ADD_ATTACHMENT": "Adicionar anexo",
"GO_TO_CONTACTS_DASHBOARD": "Ir para Painel de Contatos",
"TOGGLE_SIDEBAR": "Alternar barra lateral",
"GO_TO_REPORTS_SIDEBAR": "Ir para a barra lateral de Relatórios",
"MOVE_TO_NEXT_TAB": "Mover para a próxima aba da lista de conversas",
"GO_TO_SETTINGS": "Ir para Configurações",
"SWITCH_TO_PRIVATE_NOTE": "Mudar para Nota Privada",
"SWITCH_TO_REPLY": "Mudar para resposta",
"TOGGLE_SNOOZE_DROPDOWN": "Ativar/desativar soneca"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Atribuição de agentes",
"DESCRIPTION": "Defina políticas para gerenciar a carga de trabalho de forma eficaz e encaminhar conversas com base nas necessidades das caixas de entrada e dos agentes. Saiba mais aqui"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Política de atribuição",
"DESCRIPTION": "Gerencie como as conversas são atribuídas nas caixas de entrada.",
"FEATURES": [
"Atribuir conversas de forma uniforme ou com base na capacidade disponível",
"Adicione regras de distribuição justa para evitar sobrecarregar qualquer agente",
"Adicione caixas de entrada a uma política - uma política por caixa de entrada"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Política de capacidade dos agentes",
"DESCRIPTION": "Gerencie a carga de trabalho dos agentes.",
"FEATURES": [
"Defina o número máximo de conversas por caixa de entrada",
"Criar exceções com base em rótulos e tempo",
"Adicione agentes a uma política - uma política por agente"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Política de atribuição",
"CREATE_POLICY": "Nova política"
},
"CARD": {
"ORDER": "Ordem",
"PRIORITY": "Prioridade",
"ACTIVE": "Ativo",
"INACTIVE": "Inativa",
"POPOVER": "Caixas de entrada adicionadas",
"EDIT": "Alterar"
},
"NO_RECORDS_FOUND": "Nenhuma política de atribuição encontrada"
},
"CREATE": {
"HEADER": {
"TITLE": "Criar política de atribuição"
},
"CREATE_BUTTON": "Criar política",
"API": {
"SUCCESS_MESSAGE": "Política de atribuição criada com sucesso",
"ERROR_MESSAGE": "Falha ao criar a política de atribuição",
"INBOX_LINKED": "A caixa de entrada foi vinculada à política"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Editar política de atribuição"
},
"EDIT_BUTTON": "Atualizar política",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Adicionar caixa de entrada",
"DESCRIPTION": "A caixa de entrada {inboxName} já está vinculada a outra política. Tem certeza de que deseja vinculá-la a esta política? Ela será desvinculada da outra.",
"CONFIRM_BUTTON_LABEL": "Continuar",
"CANCEL_BUTTON_LABEL": "Cancelar"
},
"INBOX_LINK_PROMPT": {
"TITLE": "Vincular caixa de entrada à política",
"DESCRIPTION": "Deseja vincular esta caixa de entrada à política de atribuição?",
"LINK_BUTTON": "Vincular caixa de entrada",
"CANCEL_BUTTON": "Pular"
},
"API": {
"SUCCESS_MESSAGE": "Política de atribuição atualizada com sucesso",
"ERROR_MESSAGE": "Falha ao atualizar a política de atribuição"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Caixa de entrada adicionada à política com sucesso",
"ERROR_MESSAGE": "Falha ao adicionar a caixa de entrada à política"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Caixa de entrada removida da política com sucesso",
"ERROR_MESSAGE": "Caixa de entrada removida da política com sucesso"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Nome da política:",
"PLACEHOLDER": "Informe o nome da política"
},
"DESCRIPTION": {
"LABEL": "Descrição:",
"PLACEHOLDER": "Insira a descrição"
},
"STATUS": {
"LABEL": "Status:",
"PLACEHOLDER": "Selecionar status",
"ACTIVE": "Política ativa",
"INACTIVE": "Política inativa"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Ordem de atribuição",
"ROUND_ROBIN": {
"LABEL": "Rodízio",
"DESCRIPTION": "Distribuir as conversas de forma uniforme entre os agentes."
},
"BALANCED": {
"LABEL": "Equilibrado",
"DESCRIPTION": "Atribuir conversas com base na capacidade disponível.",
"PREMIUM_MESSAGE": "Atualize o plano para acessar a atribuição equilibrada e o gerenciamento de capacidade dos agentes.",
"PREMIUM_BADGE": "Premium"
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Prioridade de atribuição",
"EARLIEST_CREATED": {
"LABEL": "Criado recentemente",
"DESCRIPTION": "A conversa criada primeiro será atribuída primeiro."
},
"LONGEST_WAITING": {
"LABEL": "Maior tempo de espera",
"DESCRIPTION": "A conversa que está aguardando há mais tempo é atribuída primeiro."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Política de distribuição justa",
"DESCRIPTION": "Defina o número máximo de conversas que podem ser atribuídas por agente dentro de um período, para evitar sobrecarregar qualquer agente. Este campo obrigatório tem como padrão 100 conversas por hora.",
"INPUT_MAX": "Máximo de atribuições",
"DURATION": "Conversas por agente a cada"
},
"INBOXES": {
"LABEL": "Caixas de entrada adicionadas",
"DESCRIPTION": "Adicionar caixas de entrada às quais esta política será aplicada.",
"ADD_BUTTON": "Adicionar caixa de entrada",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Pesquisar e selecionar caixas de entrada para adicionar",
"ADD_BUTTON": "Adicionar"
},
"EMPTY_STATE": "Nenhuma caixa de entrada adicionada a esta política. Adicione uma caixa de entrada para começar",
"API": {
"SUCCESS_MESSAGE": "Caixa de entrada adicionada à política com sucesso",
"ERROR_MESSAGE": "Falha ao adicionar a caixa de entrada à política"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Política de atribuição excluída com sucesso",
"ERROR_MESSAGE": "Falha ao excluir a política de atribuição"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Capacidade do agente",
"CREATE_POLICY": "Nova política"
},
"CARD": {
"POPOVER": "Agentes adicionados",
"EDIT": "Alterar"
},
"NO_RECORDS_FOUND": "Nenhuma política de capacidade dos agentes encontrada"
},
"CREATE": {
"HEADER": {
"TITLE": "Criar política de capacidade dos agentes"
},
"CREATE_BUTTON": "Criar política",
"API": {
"SUCCESS_MESSAGE": "Política de capacidade dos agentes criada com sucesso",
"ERROR_MESSAGE": "Falha ao criar a política de capacidade dos agentes"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Editar política de capacidade dos agentes"
},
"EDIT_BUTTON": "Atualizar política",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Adicionar agente",
"DESCRIPTION": "{agentName} já está vinculado a outra política. Tem certeza de que deseja vinculá-lo a esta política? Ele será desvinculado da outra.",
"CONFIRM_BUTTON_LABEL": "Continuar",
"CANCEL_BUTTON_LABEL": "Cancelar"
},
"API": {
"SUCCESS_MESSAGE": "Política de capacidade dos agentes atualizada com sucesso",
"ERROR_MESSAGE": "Falha ao atualizar a política de capacidade dos agentes"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agente adicionado à política com sucesso",
"ERROR_MESSAGE": "Falha ao adicionar o agente à política"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agente removido da política com sucesso",
"ERROR_MESSAGE": "Falha ao remover o agente da política"
}
},
"INBOX_LIMIT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Limite de caixa de entrada adicionado com sucesso",
"ERROR_MESSAGE": "Falha ao adicionar limite de caixa de entrada"
},
"UPDATE": {
"SUCCESS_MESSAGE": "Limite de caixa de entrada atualizado com sucesso",
"ERROR_MESSAGE": "Falha ao atualizar limite da caixa de entrada"
},
"DELETE": {
"SUCCESS_MESSAGE": "Limite da caixa de entrada excluído com sucesso",
"ERROR_MESSAGE": "Falha ao excluir o limite da caixa de entrada"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Nome da política:",
"PLACEHOLDER": "Informe o nome da política"
},
"DESCRIPTION": {
"LABEL": "Descrição:",
"PLACEHOLDER": "Insira a descrição"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Limites de capacidade da caixa de entrada",
"ADD_BUTTON": "Adicionar caixa de entrada",
"FIELD": {
"SELECT_INBOX": "Selecionar caixa de entrada",
"MAX_CONVERSATIONS": "Máximo de conversas",
"SET_LIMIT": "Definir limite"
},
"EMPTY_STATE": "Nenhum limite de caixa de entrada definido"
},
"EXCLUSION_RULES": {
"LABEL": "Regras de exclusão",
"DESCRIPTION": "Conversas que atendam às seguintes condições não serão contabilizadas na capacidade do agente",
"TAGS": {
"LABEL": "Excluir conversas marcadas com rótulos específicos",
"ADD_TAG": "adicionar etiqueta",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Pesquise e selecione etiquetas para adicionar"
},
"EMPTY_STATE": "Nenhuma etiqueta adicionada a esta política."
},
"DURATION": {
"LABEL": "Excluir conversas mais antigas que um período especificado",
"PLACEHOLDER": "Definir duração"
}
},
"USERS": {
"LABEL": "Agentes atribuídos",
"DESCRIPTION": "Adicione os agentes aos quais esta política será aplicada.",
"ADD_BUTTON": "Adicionar agente",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Pesquise e selecione agentes para adicionar",
"ADD_BUTTON": "Adicionar"
},
"EMPTY_STATE": "Nenhum agente adicionado",
"API": {
"SUCCESS_MESSAGE": "Agente adicionado à política com sucesso",
"ERROR_MESSAGE": "Falha ao adicionar o agente à política"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Política de capacidade de agentes excluída com sucesso",
"ERROR_MESSAGE": "Falha ao excluir a política de capacidade de agentes"
}
},
"DELETE_POLICY": {
"TITLE": "Excluir política",
"DESCRIPTION": "Tem certeza de que deseja excluir esta política? Essa ação não pode ser desfeita.",
"CONFIRM_BUTTON_LABEL": "Excluir",
"CANCEL_BUTTON_LABEL": "Cancelar"
}
},
"CONVERSATION_WORKFLOW": {
"INDEX": {
"HEADER": {
"TITLE": "Fluxos de Conversa",
"DESCRIPTION": "Configure regras e campos obrigatórios para resolução de conversas."
}
},
"REQUIRED_ATTRIBUTES": {
"TITLE": "Atributos obrigatórios na resolução",
"DESCRIPTION": "Ao resolver uma conversa, os agentes serão solicitados a preencher esses atributos se ainda não o fizeram.",
"NO_ATTRIBUTES": "Nenhum atributo adicionado ainda",
"ADD": {
"TITLE": "Adicionar Atributos",
"SEARCH_PLACEHOLDER": "Buscar atributos"
},
"SAVE": {
"SUCCESS": "Atributos obrigatórios atualizados",
"ERROR": "Não foi possível atualizar os atributos obrigatórios, tente novamente"
},
"MODAL": {
"TITLE": "Resolver conversa",
"DESCRIPTION": "Por favor, preencha os seguintes atributos personalizados antes de resolver esta conversa",
"ACTIONS": {
"RESOLVE": "Resolver conversa",
"CANCEL": "Cancelar"
},
"PLACEHOLDERS": {
"TEXT": "Escreva uma nota...",
"NUMBER": "Insira um número",
"LINK": "Adicione um link",
"DATE": "Escolha uma data",
"LIST": "Selecione uma opção"
},
"CHECKBOX": {
"YES": "Sim",
"NO": "Não"
}
},
"PAYWALL": {
"TITLE": "Faça upgrade para usar atributos obrigatórios",
"AVAILABLE_ON": "O recurso de atributos obrigatórios de conversa está disponível nos planos Business e Enterprise.",
"UPGRADE_PROMPT": "Faça upgrade do seu plano para solicitar que os agentes preencham atributos obrigatórios antes da resolução da conversa.",
"UPGRADE_NOW": "Fazer upgrade agora",
"CANCEL_ANYTIME": "Você pode alterar ou cancelar seu plano a qualquer momento"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "O recurso de atributos obrigatórios de conversa está disponível nos planos pagos.",
"UPGRADE_PROMPT": "Faça upgrade para um plano pago para exigir atributos obrigatórios antes da resolução da conversa.",
"ASK_ADMIN": "Entre em contato com seu administrador para o upgrade."
}
}
}
}