Merge branch 'release/2.5.0'

This commit is contained in:
Sojan 2022-05-16 16:55:24 +05:30
commit 63b1013b45
878 changed files with 21085 additions and 6933 deletions

View file

@ -161,13 +161,15 @@ USE_INBOX_AVATAR_FOR_BOT=true
## NewRelic
# https://docs.newrelic.com/docs/agents/ruby-agent/configuration/ruby-agent-configuration/
# NEW_RELIC_LICENSE_KEY=
# Set this to true to allow newrelic apm to send logs.
# This is turned off by default.
# NEW_RELIC_APPLICATION_LOGGING_ENABLED=
## Datadog
## https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#environment-variables
# DD_TRACE_AGENT_URL=
## IP look up configuration
## ref https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md
## works only on accounts with ip look up feature enabled

View file

@ -42,7 +42,8 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- uses: ruby/setup-ruby@v1
with:

View file

@ -97,14 +97,14 @@ gem 'brakeman'
gem 'ddtrace'
gem 'newrelic_rpm'
gem 'scout_apm'
gem 'sentry-rails'
gem 'sentry-ruby'
gem 'sentry-sidekiq'
gem 'sentry-rails', '~> 5.3'
gem 'sentry-ruby', '~> 5.3'
gem 'sentry-sidekiq', '~> 5.3'
##-- background job processing --##
gem 'sidekiq', '~> 6.4.0'
# We want cron jobs
gem 'sidekiq-cron'
gem 'sidekiq-cron', '~> 1.3'
##-- Push notification service --##
gem 'fcm'

View file

@ -9,63 +9,63 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (6.1.4.7)
actionpack (= 6.1.4.7)
activesupport (= 6.1.4.7)
actioncable (6.1.5.1)
actionpack (= 6.1.5.1)
activesupport (= 6.1.5.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.4.7)
actionpack (= 6.1.4.7)
activejob (= 6.1.4.7)
activerecord (= 6.1.4.7)
activestorage (= 6.1.4.7)
activesupport (= 6.1.4.7)
actionmailbox (6.1.5.1)
actionpack (= 6.1.5.1)
activejob (= 6.1.5.1)
activerecord (= 6.1.5.1)
activestorage (= 6.1.5.1)
activesupport (= 6.1.5.1)
mail (>= 2.7.1)
actionmailer (6.1.4.7)
actionpack (= 6.1.4.7)
actionview (= 6.1.4.7)
activejob (= 6.1.4.7)
activesupport (= 6.1.4.7)
actionmailer (6.1.5.1)
actionpack (= 6.1.5.1)
actionview (= 6.1.5.1)
activejob (= 6.1.5.1)
activesupport (= 6.1.5.1)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.4.7)
actionview (= 6.1.4.7)
activesupport (= 6.1.4.7)
actionpack (6.1.5.1)
actionview (= 6.1.5.1)
activesupport (= 6.1.5.1)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.4.7)
actionpack (= 6.1.4.7)
activerecord (= 6.1.4.7)
activestorage (= 6.1.4.7)
activesupport (= 6.1.4.7)
actiontext (6.1.5.1)
actionpack (= 6.1.5.1)
activerecord (= 6.1.5.1)
activestorage (= 6.1.5.1)
activesupport (= 6.1.5.1)
nokogiri (>= 1.8.5)
actionview (6.1.4.7)
activesupport (= 6.1.4.7)
actionview (6.1.5.1)
activesupport (= 6.1.5.1)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_record_query_trace (1.8)
activejob (6.1.4.7)
activesupport (= 6.1.4.7)
activejob (6.1.5.1)
activesupport (= 6.1.5.1)
globalid (>= 0.3.6)
activemodel (6.1.4.7)
activesupport (= 6.1.4.7)
activerecord (6.1.4.7)
activemodel (= 6.1.4.7)
activesupport (= 6.1.4.7)
activemodel (6.1.5.1)
activesupport (= 6.1.5.1)
activerecord (6.1.5.1)
activemodel (= 6.1.5.1)
activesupport (= 6.1.5.1)
activerecord-import (1.3.0)
activerecord (>= 4.2)
activestorage (6.1.4.7)
actionpack (= 6.1.4.7)
activejob (= 6.1.4.7)
activerecord (= 6.1.4.7)
activesupport (= 6.1.4.7)
marcel (~> 1.0.0)
activestorage (6.1.5.1)
actionpack (= 6.1.5.1)
activejob (= 6.1.5.1)
activerecord (= 6.1.5.1)
activesupport (= 6.1.5.1)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (6.1.4.7)
activesupport (6.1.5.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -136,7 +136,7 @@ GEM
climate_control (1.0.1)
coderay (1.1.3)
commonmarker (0.23.4)
concurrent-ruby (1.1.9)
concurrent-ruby (1.1.10)
connection_pool (2.2.5)
crack (0.4.5)
rexml
@ -183,7 +183,7 @@ GEM
email_reply_trimmer (0.1.13)
erubi (1.10.0)
erubis (2.7.0)
et-orbi (1.2.6)
et-orbi (1.2.7)
tzinfo
execjs (2.8.1)
facebook-messenger (2.0.1)
@ -210,8 +210,8 @@ GEM
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
foreman (0.87.2)
fugit (1.5.2)
et-orbi (~> 1.1, >= 1.1.8)
fugit (1.5.3)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
gapic-common (0.3.4)
google-protobuf (~> 3.12, >= 3.12.2)
@ -349,7 +349,7 @@ GEM
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.14.0)
loofah (2.17.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -376,16 +376,16 @@ GEM
net-http-persistent (4.0.1)
connection_pool (~> 2.2)
netrc (0.11.0)
newrelic_rpm (8.4.0)
newrelic_rpm (8.7.0)
nio4r (2.5.8)
nokogiri (1.13.4)
nokogiri (1.13.5)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
nokogiri (1.13.4-arm64-darwin)
nokogiri (1.13.5-arm64-darwin)
racc (~> 1.4)
nokogiri (1.13.4-x86_64-darwin)
nokogiri (1.13.5-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.13.4-x86_64-linux)
nokogiri (1.13.5-x86_64-linux)
racc (~> 1.4)
oauth (0.5.8)
orm_adapter (0.5.0)
@ -419,31 +419,31 @@ GEM
rack-test (1.1.0)
rack (>= 1.0, < 3)
rack-timeout (0.6.0)
rails (6.1.4.7)
actioncable (= 6.1.4.7)
actionmailbox (= 6.1.4.7)
actionmailer (= 6.1.4.7)
actionpack (= 6.1.4.7)
actiontext (= 6.1.4.7)
actionview (= 6.1.4.7)
activejob (= 6.1.4.7)
activemodel (= 6.1.4.7)
activerecord (= 6.1.4.7)
activestorage (= 6.1.4.7)
activesupport (= 6.1.4.7)
rails (6.1.5.1)
actioncable (= 6.1.5.1)
actionmailbox (= 6.1.5.1)
actionmailer (= 6.1.5.1)
actionpack (= 6.1.5.1)
actiontext (= 6.1.5.1)
actionview (= 6.1.5.1)
activejob (= 6.1.5.1)
activemodel (= 6.1.5.1)
activerecord (= 6.1.5.1)
activestorage (= 6.1.5.1)
activesupport (= 6.1.5.1)
bundler (>= 1.15.0)
railties (= 6.1.4.7)
railties (= 6.1.5.1)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2)
loofah (~> 2.3)
railties (6.1.4.7)
actionpack (= 6.1.4.7)
activesupport (= 6.1.4.7)
railties (6.1.5.1)
actionpack (= 6.1.5.1)
activesupport (= 6.1.5.1)
method_source
rake (>= 0.13)
rake (>= 12.2)
thor (~> 1.0)
rainbow (3.1.1)
rake (13.0.6)
@ -533,16 +533,16 @@ GEM
activesupport (>= 4)
selectize-rails (0.12.6)
semantic_range (3.0.0)
sentry-rails (5.1.0)
sentry-rails (5.3.0)
railties (>= 5.0)
sentry-ruby-core (~> 5.1.0)
sentry-ruby (5.1.0)
sentry-ruby-core (~> 5.3.0)
sentry-ruby (5.3.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
sentry-ruby-core (= 5.1.0)
sentry-ruby-core (5.1.0)
sentry-ruby-core (= 5.3.0)
sentry-ruby-core (5.3.0)
concurrent-ruby
sentry-sidekiq (5.1.0)
sentry-ruby-core (~> 5.1.0)
sentry-sidekiq (5.3.0)
sentry-ruby-core (~> 5.3.0)
sidekiq (>= 3.0)
sexp_processor (4.16.0)
shoulda-matchers (5.1.0)
@ -551,8 +551,8 @@ GEM
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.2.0)
sidekiq-cron (1.2.0)
fugit (~> 1.1)
sidekiq-cron (1.4.0)
fugit (~> 1)
sidekiq (>= 4.2.1)
signet (0.16.0)
addressable (~> 2.8)
@ -726,12 +726,12 @@ DEPENDENCIES
rubocop-rspec
scout_apm
seed_dump
sentry-rails
sentry-ruby
sentry-sidekiq
sentry-rails (~> 5.3)
sentry-ruby (~> 5.3)
sentry-sidekiq (~> 5.3)
shoulda-matchers
sidekiq (~> 6.4.0)
sidekiq-cron
sidekiq-cron (~> 1.3)
simplecov (= 0.17.1)
slack-ruby-client
spring
@ -755,4 +755,4 @@ RUBY VERSION
ruby 3.0.2p107
BUNDLED WITH
2.3.8
2.3.10

View file

@ -1,5 +1,5 @@
class Campaigns::CampaignConversationBuilder
pattr_initialize [:contact_inbox_id!, :campaign_display_id!, :conversation_additional_attributes]
pattr_initialize [:contact_inbox_id!, :campaign_display_id!, :conversation_additional_attributes, :custom_attributes]
def perform
@contact_inbox = ContactInbox.find(@contact_inbox_id)
@ -21,7 +21,8 @@ class Campaigns::CampaignConversationBuilder
def message_params
ActionController::Parameters.new({
content: @campaign.message
content: @campaign.message,
campaign_id: @campaign.id
})
end
@ -32,7 +33,8 @@ class Campaigns::CampaignConversationBuilder
contact_id: @contact_inbox.contact_id,
contact_inbox_id: @contact_inbox.id,
campaign_id: @campaign.id,
additional_attributes: conversation_additional_attributes
additional_attributes: conversation_additional_attributes,
custom_attributes: custom_attributes || {}
}
end
end

View file

@ -15,11 +15,10 @@ class ContactBuilder
end
def create_contact_inbox(contact)
::ContactInbox.create!(
::ContactInbox.create_with(hmac_verified: hmac_verified || false).find_or_create_by!(
contact_id: contact.id,
inbox_id: inbox.id,
source_id: source_id,
hmac_verified: hmac_verified || false
source_id: source_id
)
end

View file

@ -29,7 +29,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
rescue Koala::Facebook::AuthenticationError
Rails.logger.error "Facebook Authorization expired for Inbox #{@inbox.id}"
rescue StandardError => e
Sentry.capture_exception(e)
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
true
end
@ -43,7 +43,7 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
return if contact.present?
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
@contact_inbox = ContactInbox.find_or_create_by!(contact: contact, inbox: @inbox, source_id: @sender_id)
end
def build_message
@ -128,10 +128,10 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
result = {}
# OAuthException, code: 100, error_subcode: 2018218, message: (#100) No profile available for this user
# We don't need to capture this error as we don't care about contact params in case of echo messages
Sentry.capture_exception(e) unless @outgoing_echo
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception unless @outgoing_echo
rescue StandardError => e
result = {}
Sentry.capture_exception(e)
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
end
process_contact_params_result(result)
end

View file

@ -24,7 +24,7 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
@inbox.channel.authorization_error!
raise
rescue StandardError => e
Sentry.capture_exception(e)
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
true
end

View file

@ -9,6 +9,7 @@ class Messages::MessageBuilder
@user = user
@message_type = params[:message_type] || 'outgoing'
@attachments = params[:attachments]
@automation_rule = @params&.dig(:content_attributes, :automation_rule_id)
return unless params.instance_of?(ActionController::Parameters)
@in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to)
@ -64,6 +65,14 @@ class Messages::MessageBuilder
@params[:external_created_at].present? ? { external_created_at: @params[:external_created_at] } : {}
end
def automation_rule_id
@automation_rule.present? ? { content_attributes: { automation_rule_id: @automation_rule } } : {}
end
def campaign_id
@params[:campaign_id].present? ? { additional_attributes: { campaign_id: @params[:campaign_id] } } : {}
end
def message_sender
return if @params[:sender_type] != 'AgentBot'
@ -82,6 +91,6 @@ class Messages::MessageBuilder
items: @items,
in_reply_to: @in_reply_to,
echo_id: @params[:echo_id]
}.merge(external_created_at)
}.merge(external_created_at).merge(automation_rule_id).merge(campaign_id)
end
end

View file

@ -53,16 +53,7 @@ class Messages::Messenger::MessageBuilder
def fetch_story_link(attachment)
message = attachment.message
begin
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
result = k.get_object(message.source_id, fields: %w[story from]) || {}
rescue Koala::Facebook::AuthenticationError
@inbox.channel.authorization_error!
raise
rescue StandardError => e
result = {}
Sentry.capture_exception(e)
end
result = get_story_object_from_source_id(message.source_id)
story_id = result['story']['mention']['id']
story_sender = result['from']['username']
message.content_attributes[:story_sender] = story_sender
@ -70,4 +61,15 @@ class Messages::Messenger::MessageBuilder
message.content = I18n.t('conversations.messages.instagram_story_content', story_sender: story_sender)
message.save!
end
def get_story_object_from_source_id(source_id)
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
k.get_object(source_id, fields: %w[story from]) || {}
rescue Koala::Facebook::AuthenticationError
@inbox.channel.authorization_error!
raise
rescue StandardError => e
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
{}
end
end

View file

@ -4,6 +4,7 @@ class V2::ReportBuilder
attr_reader :account, :params
DEFAULT_GROUP_BY = 'day'.freeze
AGENT_RESULTS_PER_PAGE = 25
def initialize(account, params)
@account = account
@ -45,7 +46,7 @@ class V2::ReportBuilder
if params[:type].equal?(:account)
conversations
else
agent_metrics
agent_metrics.sort_by { |hash| hash[:metric][:open] }.reverse
end
end
@ -79,20 +80,23 @@ class V2::ReportBuilder
end
def agent_metrics
users = @account.users
users = users.where(id: params[:user_id]) if params[:user_id].present?
users.each_with_object([]) do |user, arr|
@user = user
account_users = @account.account_users.page(params[:page]).per(AGENT_RESULTS_PER_PAGE)
account_users.each_with_object([]) do |account_user, arr|
@user = account_user.user
arr << {
user: { id: user.id, name: user.name, thumbnail: user.avatar_url },
id: @user.id,
name: @user.name,
email: @user.email,
thumbnail: @user.avatar_url,
availability: account_user.availability_status,
metric: conversations
}
end
end
def conversations
@open_conversations = scope.conversations.open
first_response_count = scope.reporting_events.where(name: 'first_response', conversation_id: @open_conversations.pluck('id')).count
@open_conversations = scope.conversations.where(account_id: @account.id).open
first_response_count = @account.reporting_events.where(name: 'first_response', conversation_id: @open_conversations.pluck('id')).count
metric = {
open: @open_conversations.count,
unattended: @open_conversations.count - first_response_count

View file

@ -45,6 +45,8 @@ class RoomChannel < ApplicationCable::Channel
end
def current_account
return if current_user.blank?
@current_account ||= if @current_user.is_a? Contact
@current_user.account
else

View file

@ -17,6 +17,16 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
@automation_rule
end
def attach_file
file_blob = ActiveStorage::Blob.create_and_upload!(
key: nil,
io: params[:attachment].tempfile,
filename: params[:attachment].original_filename,
content_type: params[:attachment].content_type
)
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
end
def show; end
def update
@ -25,6 +35,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
@automation_rule.actions = params[:actions] if params[:actions]
@automation_rule.save!
process_attachments
rescue StandardError => e
Rails.logger.error e
render json: { error: @automation_rule.errors.messages }.to_json, status: :unprocessable_entity
@ -43,17 +54,19 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
@automation_rule = new_rule
end
private
def process_attachments
return if params[:attachments].blank?
actions = @automation_rule.actions.filter_map { |k, _v| k if k['action_name'] == 'send_attachment' }
return if actions.blank?
params[:attachments].each do |uploaded_attachment|
@automation_rule.files.attach(uploaded_attachment)
actions.each do |action|
blob_id = action['action_params']
blob = ActiveStorage::Blob.find_by(id: blob_id)
@automation_rule.files.attach(blob)
end
@automation_rule
end
private
def automation_rules_permit
params.permit(
:name, :description, :event_name, :account_id, :active,

View file

@ -15,7 +15,7 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
set_instagram_id(page_access_token, facebook_channel)
set_avatar(@facebook_inbox, page_id)
rescue StandardError => e
Sentry.capture_exception(e)
ChatwootExceptionTracker.new(e).capture_exception
end
end
@ -60,7 +60,7 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
set_instagram_id(access_token, fb_page)
fb_page&.reauthorized!
rescue StandardError => e
Sentry.capture_exception(e)
ChatwootExceptionTracker.new(e).capture_exception
end
end

View file

@ -7,7 +7,6 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::V1::Accounts:
build_inbox
setup_webhooks if @twilio_channel.sms?
rescue StandardError => e
Sentry.capture_exception(e)
render_could_not_create_error(e.message)
end
end

View file

@ -5,7 +5,7 @@ class Api::V1::Accounts::Conversations::BaseController < Api::V1::Accounts::Base
private
def conversation
@conversation ||= Current.account.conversations.find_by(display_id: params[:conversation_id])
@conversation ||= Current.account.conversations.find_by!(display_id: params[:conversation_id])
authorize @conversation.inbox, :show?
end
end

View file

@ -4,6 +4,6 @@ class Api::V1::Accounts::Kbase::BaseController < Api::V1::Accounts::BaseControll
private
def portal
@portal ||= Current.account.kbase_portals.find_by(id: params[:portal_id])
@portal ||= Current.account.kbase_portals.find_by(slug: params[:portal_id])
end
end

View file

@ -1,10 +1,12 @@
class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::Kbase::BaseController
class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::BaseController
before_action :fetch_portal, except: [:index, :create]
def index
@portals = Current.account.kbase_portals
end
def show; end
def create
@portal = Current.account.kbase_portals.create!(portal_params)
end
@ -21,7 +23,11 @@ class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::Kbase::Ba
private
def fetch_portal
@portal = current_account.kbase_portals.find(params[:id])
@portal = Current.account.kbase_portals.find_by(slug: permitted_params[:id])
end
def permitted_params
params.permit(:id)
end
def portal_params

View file

@ -23,7 +23,7 @@ class Api::V1::Accounts::WebhooksController < Api::V1::Accounts::BaseController
private
def webhook_params
params.require(:webhook).permit(:inbox_id, :url)
params.require(:webhook).permit(:inbox_id, :url, subscriptions: [])
end
def fetch_webhook

View file

@ -10,7 +10,7 @@ class Api::V1::WebhooksController < ApplicationController
twitter_consumer.consume
head :ok
rescue StandardError => e
Sentry.capture_exception(e)
ChatwootExceptionTracker.new(e).capture_exception
head :ok
end

View file

@ -71,7 +71,7 @@ class Api::V1::Widget::BaseController < ApplicationController
end
def contact_email
permitted_params[:contact][:email].downcase if permitted_params[:contact].present?
permitted_params.dig(:contact, :email)&.downcase
end
def contact_name
@ -79,7 +79,7 @@ class Api::V1::Widget::BaseController < ApplicationController
end
def contact_phone_number
params[:contact][:phone_number]
permitted_params.dig(:contact, :phone_number)
end
def browser_params

View file

@ -69,7 +69,8 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
end
def permitted_params
params.permit(:id, :typing_status, :website_token, :email, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id],
params.permit(:id, :typing_status, :website_token, :email, contact: [:name, :email, :phone_number],
message: [:content, :referer_url, :timestamp, :echo_id],
custom_attributes: {})
end
end

View file

@ -82,7 +82,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
def conversation_params
{
type: params[:type].to_sym,
user_id: params[:user_id]
user_id: params[:user_id],
page: params[:page].presence || 1
}
end

View file

@ -9,8 +9,7 @@ module RequestExceptionHandler
def handle_with_exception
yield
rescue ActiveRecord::RecordNotFound => e
Sentry.capture_exception(e)
rescue ActiveRecord::RecordNotFound
render_not_found_error('Resource could not be found')
rescue Pundit::NotAuthorizedError
render_unauthorized('You are not authorized to do this action')

View file

@ -38,9 +38,12 @@ class DashboardController < ActionController::Base
end
def app_config
{ APP_VERSION: Chatwoot.config[:version],
{
APP_VERSION: Chatwoot.config[:version],
VAPID_PUBLIC_KEY: VapidService.public_key,
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'),
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', '') }
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
FACEBOOK_API_VERSION: 'v13.0'
}
end
end

View file

@ -21,6 +21,10 @@ class Platform::Api::V1::UsersController < PlatformController
def update
@resource.assign_attributes(user_update_params)
# We are using devise's reconfirmable flow for changing emails
# But in case of platform APIs we don't want user to go through this extra step
@resource.skip_reconfirmation! if user_update_params[:email].present?
@resource.save!
end

View file

@ -43,6 +43,6 @@ class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesCon
end
def permitted_params
params.permit(:identifier, :identifier_hash, :email, :name, :avatar_url, custom_attributes: {})
params.permit(:identifier, :identifier_hash, :email, :name, :avatar_url, :phone_number, custom_attributes: {})
end
end

View file

@ -22,7 +22,7 @@ class MessageFinder
def current_messages
if @params[:before].present?
messages.reorder('created_at desc').where('id < ?', @params[:before]).limit(20).reverse
messages.reorder('created_at desc').where('id < ?', @params[:before].to_i).limit(20).reverse
else
messages.reorder('created_at desc').limit(20).reverse
end

View file

@ -17,30 +17,30 @@ module ReportHelper
end
def conversations_count
(get_grouped_values scope.conversations).count
(get_grouped_values scope.conversations.where(account_id: account.id)).count
end
def incoming_messages_count
(get_grouped_values scope.messages.incoming.unscope(:order)).count
(get_grouped_values scope.messages.where(account_id: account.id).incoming.unscope(:order)).count
end
def outgoing_messages_count
(get_grouped_values scope.messages.outgoing.unscope(:order)).count
(get_grouped_values scope.messages.where(account_id: account.id).outgoing.unscope(:order)).count
end
def resolutions_count
(get_grouped_values scope.conversations.resolved).count
(get_grouped_values scope.conversations.where(account_id: account.id).resolved).count
end
def avg_first_response_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response'))
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'first_response', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
grouped_reporting_events.average(:value)
end
def avg_resolution_time
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved'))
grouped_reporting_events = (get_grouped_values scope.reporting_events.where(name: 'conversation_resolved', account_id: account.id))
return grouped_reporting_events.average(:value_in_business_hours) if params[:business_hours]
grouped_reporting_events.average(:value)
@ -48,7 +48,7 @@ module ReportHelper
def avg_resolution_time_summary
reporting_events = scope.reporting_events
.where(name: 'conversation_resolved', created_at: range)
.where(name: 'conversation_resolved', account_id: account.id, created_at: range)
avg_rt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
return 0 if avg_rt.blank?
@ -58,7 +58,7 @@ module ReportHelper
def avg_first_response_time_summary
reporting_events = scope.reporting_events
.where(name: 'first_response', created_at: range)
.where(name: 'first_response', account_id: account.id, created_at: range)
avg_frt = params[:business_hours] ? reporting_events.average(:value_in_business_hours) : reporting_events.average(:value)
return 0 if avg_frt.blank?

View file

@ -9,6 +9,14 @@ class AutomationsAPI extends ApiClient {
clone(automationId) {
return axios.post(`${this.url}/${automationId}/clone`);
}
attachment(file) {
return axios.post(`${this.url}/attach_file`, file, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
}
export default new AutomationsAPI();

View file

@ -44,6 +44,15 @@ class ReportsAPI extends ApiClient {
});
}
getConversationMetric(type = 'account', page = 1) {
return axios.get(`${this.url}/conversations`, {
params: {
type,
page,
},
});
}
getAgentReports(since, until) {
return axios.get(`${this.url}/agents`, {
params: { since, until },

View file

@ -97,5 +97,18 @@ describe('#Reports API', () => {
}
);
});
it('#getConversationMetric', () => {
reportsAPI.getConversationMetric('account');
expect(context.axiosMock.get).toHaveBeenCalledWith(
'/api/v2/reports/conversations',
{
params: {
type: 'account',
page: 1,
},
}
);
});
});
});

View file

@ -2,6 +2,10 @@
margin-right: var(--space-small);
}
.margin-bottom-small {
margin-bottom: var(--space-small);
}
.margin-right-smaller {
margin-right: var(--space-smaller);
}

View file

@ -27,6 +27,16 @@
padding: 0 $space-small;
}
.video-js {
background: transparent;
// Override min-height : 50px in foundation
//
max-height: $space-mega * 2.4;
min-height: 4.8rem;
padding: var(--space-normal) 0 0;
resize: none;
}
>textarea {
@include ghost-input();
@include margin(0);

View file

@ -19,6 +19,7 @@
:menu-config="activeSecondaryMenu"
:current-role="currentRole"
@add-label="showAddLabelPopup"
@toggle-accounts="toggleAccountModal"
/>
</aside>
</template>

View file

@ -3,7 +3,8 @@ import { frontendURL } from '../../../../helper/URLHelper';
const reports = accountId => ({
parentNav: 'reports',
routes: [
'settings_account_reports',
'account_overview_reports',
'conversation_reports',
'csat_reports',
'agent_reports',
'label_reports',
@ -16,7 +17,14 @@ const reports = accountId => ({
label: 'REPORTS_OVERVIEW',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/reports/overview`),
toStateName: 'settings_account_reports',
toStateName: 'account_overview_reports',
},
{
icon: 'chat',
label: 'REPORTS_CONVERSATION',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/reports/conversation`),
toStateName: 'conversation_reports',
},
{
icon: 'emoji',

View file

@ -1,14 +1,34 @@
<template>
<div v-if="showShowCurrentAccountContext" class="account-context--group">
<div
v-if="showShowCurrentAccountContext"
class="account-context--group"
@mouseover="setShowSwitch"
@mouseleave="resetShowSwitch"
>
{{ $t('SIDEBAR.CURRENTLY_VIEWING_ACCOUNT') }}
<p class="account-context--name text-ellipsis">
{{ account.name }}
</p>
<transition name="fade">
<div v-if="showSwitchButton" class="account-context--switch-group">
<woot-button
variant="clear"
icon="arrow-swap"
class="cursor-pointer"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR.SWITCH') }}
</woot-button>
</div>
</transition>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return { showSwitchButton: false };
},
computed: {
...mapGetters({
account: 'getCurrentAccount',
@ -18,6 +38,14 @@ export default {
return this.userAccounts.length > 1 && this.account.name;
},
},
methods: {
setShowSwitch() {
this.showSwitchButton = true;
},
resetShowSwitch() {
this.showSwitchButton = false;
},
},
};
</script>
<style scoped lang="scss">
@ -27,10 +55,49 @@ export default {
font-size: var(--font-size-mini);
padding: var(--space-small);
margin-bottom: var(--space-small);
width: 100%;
position: relative;
&:hover {
background: var(--b-100);
}
.account-context--name {
font-weight: var(--font-weight-medium);
margin-bottom: 0;
}
}
.account-context--switch-group {
--overlay-shadow: linear-gradient(
to right,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 50%
);
align-items: center;
background-image: var(--overlay-shadow);
border-top-left-radius: 0;
border-top-right-radius: var(--border-radius-normal);
border-bottom-left-radius: 0;
border-bottom-right-radius: var(--border-radius-normal);
display: flex;
height: 100%;
justify-content: end;
opacity: 1;
position: absolute;
right: 0;
top: 0;
width: 100%;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 300ms ease;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

View file

@ -1,6 +1,6 @@
<template>
<div v-if="hasSecondaryMenu" class="main-nav secondary-menu">
<account-context />
<account-context @toggle-accounts="toggleAccountModal" />
<transition-group name="menu-list" tag="ul" class="menu vertical">
<secondary-nav-item
v-for="menuItem in accessibleMenuItems"
@ -224,6 +224,9 @@ export default {
showAddLabelPopup() {
this.$emit('add-label');
},
toggleAccountModal() {
this.$emit('toggle-accounts');
},
},
};
</script>

View file

@ -52,6 +52,11 @@
class="answer--text-input"
placeholder="Enter url"
/>
<automation-action-file-input
v-if="inputType === 'attachment'"
v-model="action_params"
:initial-file-name="initialFileName"
/>
</div>
</div>
<woot-button
@ -84,9 +89,11 @@
<script>
import AutomationActionTeamMessageInput from './AutomationActionTeamMessageInput.vue';
import AutomationActionFileInput from './AutomationFileInput.vue';
export default {
components: {
AutomationActionTeamMessageInput,
AutomationActionFileInput,
},
props: {
value: {
@ -109,6 +116,10 @@ export default {
type: Boolean,
default: true,
},
initialFileName: {
type: String,
default: '',
},
},
computed: {
action_name: {
@ -187,6 +198,7 @@ export default {
.filter__answer--wrap {
margin-right: var(--space-smaller);
flex-grow: 1;
max-width: 50%;
input {
margin-bottom: 0;

View file

@ -0,0 +1,117 @@
<template>
<label class="input-wrapper" :class="uploadState">
<input
v-if="uploadState !== 'processing'"
type="file"
name="attachment"
:class="uploadState === 'processing' ? 'disabled' : ''"
@change="onChangeFile"
/>
<spinner v-if="uploadState === 'processing'" />
<fluent-icon v-if="uploadState === 'idle'" icon="file-upload" />
<fluent-icon
v-if="uploadState === 'uploaded'"
icon="checkmark-circle"
type="outline"
class="success-icon"
/>
<fluent-icon
v-if="uploadState === 'failed'"
icon="dismiss-circle"
type="outline"
class="error-icon"
/>
<p class="file-button">{{ label }}</p>
</label>
</template>
<script>
import Spinner from 'shared/components/Spinner';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
Spinner,
},
mixins: [alertMixin],
props: {
value: {
type: Array,
default: () => [],
},
initialFileName: {
type: String,
default: '',
},
},
data() {
return {
uploadState: 'idle',
label: this.$t('AUTOMATION.ATTACHMENT.LABEL_IDLE'),
};
},
mounted() {
if (this.initialFileName) {
this.label = this.initialFileName;
this.uploadState = 'uploaded';
}
},
methods: {
async onChangeFile(event) {
this.uploadState = 'processing';
this.label = this.$t('AUTOMATION.ATTACHMENT.LABEL_UPLOADING');
try {
const file = event.target.files[0];
const formData = new FormData();
formData.append('attachment', file, file.name);
const id = await this.$store.dispatch(
'automations/uploadAttachment',
formData
);
this.$emit('input', [id]);
this.uploadState = 'uploaded';
this.label = this.$t('AUTOMATION.ATTACHMENT.LABEL_UPLOADED');
} catch (error) {
this.uploadState = 'failed';
this.label = this.$t('AUTOMATION.ATTACHMENT.LABEL_UPLOAD_FAILED');
this.showAlert(this.$t('AUTOMATION.ATTACHMENT.UPLOAD_ERROR'));
}
},
},
};
</script>
<style scoped>
input[type='file'] {
display: none;
}
.input-wrapper {
display: flex;
height: 39px;
background-color: var(--white);
border-radius: var(--border-radius-small);
border: 1px dashed var(--w-100);
padding: var(--space-smaller) var(--space-small);
align-items: center;
font-size: var(--font-size-mini);
cursor: pointer;
}
.success-icon {
margin-right: var(--space-small);
color: var(--g-500);
}
.error-icon {
margin-right: var(--space-small);
color: var(--r-500);
}
.processing {
cursor: not-allowed;
opacity: 0.9;
}
.file-button {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
</style>

View file

@ -0,0 +1,50 @@
<template>
<span>
{{ textToBeDisplayed }}
<button class="show-more--button" @click="toggleShowMore">
{{ buttonLabel }}
</button>
</span>
</template>
<script>
export default {
props: {
text: {
type: String,
default: '',
},
limit: {
type: Number,
default: 120,
},
},
data() {
return {
showMore: false,
};
},
computed: {
textToBeDisplayed() {
if (this.showMore) {
return this.text;
}
return this.text.slice(0, this.limit) + '...';
},
buttonLabel() {
const i18nKey = !this.showMore ? 'SHOW_MORE' : 'SHOW_LESS';
return this.$t(`COMPONENTS.SHOW_MORE_BLOCK.${i18nKey}`);
},
},
methods: {
toggleShowMore() {
this.showMore = !this.showMore;
},
},
};
</script>
<style scoped>
.show-more--button {
color: var(--w-500);
}
</style>

View file

@ -1,16 +1,29 @@
<template>
<div class="audio-wave-wrapper">
<div id="audio-wave"></div>
<audio id="audio-wave" class="video-js vjs-fill vjs-default-skin"></audio>
</div>
</template>
<script>
import WaveSurfer from 'wavesurfer.js';
import MicrophonePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.microphone.js';
import RecordRTC from 'recordrtc';
import 'video.js/dist/video-js.css';
import 'videojs-record/dist/css/videojs.record.css';
import videojs from 'video.js';
import inboxMixin from '../../../../shared/mixins/inboxMixin';
import alertMixin from '../../../../shared/mixins/alertMixin';
import Recorder from 'opus-recorder';
import encoderWorker from 'opus-recorder/dist/encoderWorker.min';
import WaveSurfer from 'wavesurfer.js';
import MicrophonePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.microphone.js';
import 'videojs-wavesurfer/dist/videojs.wavesurfer.js';
import 'videojs-record/dist/videojs.record.js';
import 'videojs-record/dist/plugins/videojs.record.opus-recorder.js';
import { format, addSeconds } from 'date-fns';
WaveSurfer.microphone = MicrophonePlugin;
export default {
@ -18,104 +31,116 @@ export default {
mixins: [inboxMixin, alertMixin],
data() {
return {
wavesurfer: false,
recorder: false,
recordingInterval: false,
recordingDateStarted: new Date().getTime(),
timeDuration: '00:00',
player: false,
recordingDateStarted: new Date(0),
initialTimeDuration: '00:00',
options: {
container: '#audio-wave',
backend: 'WebAudio',
interact: true,
cursorWidth: 1,
plugins: [
WaveSurfer.microphone.create({
bufferSize: 4096,
numberOfInputChannels: 1,
numberOfOutputChannels: 1,
constraints: {
video: false,
audio: true,
},
}),
],
},
optionsRecorder: {
type: 'audio',
mimeType: 'audio/wav',
disableLogs: true,
recorderType: RecordRTC.StereoAudioRecorder,
sampleRate: 44100,
numberOfAudioChannels: 2,
checkForInactiveTracks: true,
bufferSize: 4096,
recorderOptions: {
debug: true,
controls: true,
bigPlayButton: false,
fluid: false,
controlBar: {
deviceButton: false,
fullscreenToggle: false,
cameraButton: false,
volumePanel: false,
},
plugins: {
wavesurfer: {
backend: 'WebAudio',
waveColor: '#1f93ff',
progressColor: 'rgb(25, 118, 204)',
cursorColor: 'rgba(43, 51, 63, 0.7)',
backgroundColor: 'none',
barWidth: 1,
cursorWidth: 1,
hideScrollbar: true,
plugins: [
WaveSurfer.microphone.create({
bufferSize: 4096,
numberOfInputChannels: 1,
numberOfOutputChannels: 1,
constraints: {
video: false,
audio: true,
},
}),
],
},
record: {
audio: true,
video: false,
displayMilliseconds: false,
maxLength: 300,
audioEngine: 'opus-recorder',
audioWorkerURL: encoderWorker,
audioChannels: 1,
audioSampleRate: 48000,
audioBitRate: 128,
},
},
},
};
},
computed: {
isRecording() {
if (this.recorder) {
return this.recorder.getState() === 'recording';
}
return false;
return this.player && this.player.record().isRecording();
},
},
mounted() {
this.wavesurfer = WaveSurfer.create(this.options);
this.wavesurfer.on('play', this.playingRecorder);
this.wavesurfer.on('pause', this.pausedRecorder);
this.wavesurfer.microphone.on('deviceReady', this.startRecording);
this.wavesurfer.microphone.on('deviceError', this.deviceError);
this.wavesurfer.microphone.start();
this.fireStateRecorderTimerChanged(this.initialTimeDuration);
window.Recorder = Recorder;
this.fireProgressRecord(this.initialTimeDuration);
this.player = videojs('#audio-wave', this.recorderOptions, () => {
this.$nextTick(() => {
this.player.record().getDevice();
});
});
this.player.on('deviceReady', this.deviceReady);
this.player.on('deviceError', this.deviceError);
this.player.on('startRecord', this.startRecord);
this.player.on('stopRecord', this.stopRecord);
this.player.on('progressRecord', this.progressRecord);
this.player.on('finishRecord', this.finishRecord);
this.player.on('playbackFinish', this.playbackFinish);
},
beforeDestroy() {
if (this.recorder) {
this.recorder.destroy();
if (this.player) {
this.player.dispose();
}
if (this.wavesurfer) {
this.wavesurfer.destroy();
if (window.Recorder) {
window.Recorder = undefined;
}
},
methods: {
startRecording(stream) {
this.recorder = RecordRTC(stream, this.optionsRecorder);
this.recorder.onStateChanged = this.onStateRecorderChanged;
this.recorder.startRecording();
deviceReady() {
this.player.record().start();
},
startRecord() {
this.fireStateRecorderChanged('recording');
},
stopRecord() {
this.fireStateRecorderChanged('stopped');
},
finishRecord() {
const file = new File(
[this.player.recordedData],
this.player.recordedData.name,
{ type: this.player.recordedData.type }
);
this.fireRecorderBlob(file);
},
progressRecord() {
this.fireProgressRecord(this.formatTimeProgress());
},
stopAudioRecording() {
if (this.isRecording) {
this.recorder.stopRecording(() => {
this.wavesurfer.microphone.stopDevice();
this.wavesurfer.loadBlob(this.recorder.getBlob());
this.wavesurfer.stop();
this.fireRecorderBlob(this.getAudioFile());
});
}
this.player.record().stop();
},
getAudioFile() {
if (this.hasAudio()) {
return new File([this.recorder.getBlob()], this.getAudioFileName(), {
type: 'audio/wav',
});
}
return false;
},
hasAudio() {
return !(this.isRecording || this.wavesurfer.isPlaying());
},
playingRecorder() {
this.fireStateRecorderChanged('playing');
},
pausedRecorder() {
this.fireStateRecorderChanged('paused');
},
deviceError(err) {
deviceError() {
const deviceError = this.player.deviceErrorCode;
const deviceErrorName = deviceError?.name.toLowerCase();
if (
err?.name &&
(err.name.toLowerCase().includes('notallowederror') ||
err.name.toLowerCase().includes('permissiondeniederror'))
deviceErrorName?.includes('notallowederror') ||
deviceErrorName?.includes('permissiondeniederror')
) {
this.showAlert(
this.$t('CONVERSATION.REPLYBOX.TIP_AUDIORECORDER_PERMISSION')
@ -127,56 +152,37 @@ export default {
);
}
},
onStateRecorderChanged(state) {
// recording stopped inactive destroyed
switch (state) {
case 'recording':
this.timerDurationChanged();
break;
case 'stopped':
this.timerDurationChanged();
break;
default:
break;
}
this.fireStateRecorderChanged(state);
},
timerDurationChanged() {
if (this.isRecording) {
this.recordingInterval = setInterval(() => {
this.calculateTimeDuration(
(new Date().getTime() - this.recordingDateStarted) / 1000
);
this.fireStateRecorderTimerChanged(this.timeDuration);
}, 1000);
} else {
clearInterval(this.recordingInterval);
}
},
calculateTimeDuration(secs) {
let hr = Math.floor(secs / 3600);
let min = Math.floor((secs - hr * 3600) / 60);
let sec = Math.floor(secs - hr * 3600 - min * 60);
if (min < 10) {
min = '0' + min;
}
if (sec < 10) {
sec = '0' + sec;
}
if (hr <= 0) {
this.timeDuration = min + ':' + sec;
} else {
if (hr < 10) {
hr = '0' + hr;
}
this.timeDuration = hr + ':' + min + ':' + sec;
}
formatTimeProgress() {
return format(
addSeconds(
new Date(this.recordingDateStarted.getTimezoneOffset() * 1000 * 60),
this.player.record().getDuration()
),
'mm:ss'
);
},
playPause() {
this.wavesurfer.playPause();
if (this.player.wavesurfer().surfer.isPlaying()) {
this.fireStateRecorderChanged('paused');
} else {
this.fireStateRecorderChanged('playing');
}
this.player.wavesurfer().surfer.playPause();
},
play() {
this.fireStateRecorderChanged('playing');
this.player.wavesurfer().play();
},
pause() {
this.fireStateRecorderChanged('paused');
this.player.wavesurfer().pause();
},
playbackFinish() {
this.fireStateRecorderChanged('paused');
this.player.wavesurfer().pause();
},
fireRecorderBlob(blob) {
this.$emit('recorder-blob', {
this.$emit('finish-record', {
name: blob.name,
type: blob.type,
size: blob.size,
@ -186,29 +192,8 @@ export default {
fireStateRecorderChanged(state) {
this.$emit('state-recorder-changed', state);
},
fireStateRecorderTimerChanged(duration) {
this.$emit('state-recorder-timer-changed', duration);
},
getAudioFileName() {
const d = new Date();
return `audio-${d.getFullYear()}-${d.getMonth()}-${d.getDate()}-${this.getRandomString()}.wav`;
},
getRandomString() {
if (
window.crypto &&
window.crypto.getRandomValues &&
navigator.userAgent.indexOf('Safari') === -1
) {
let a = window.crypto.getRandomValues(new Uint32Array(3));
let token = '';
for (let i = 0, l = a.length; i < l; i += 1) {
token += a[i].toString(36);
}
return token.toLowerCase();
}
return (Math.random() * new Date().getTime())
.toString(36)
.replace(/\./g, '');
fireProgressRecord(duration) {
this.$emit('state-recorder-progress-changed', duration);
},
},
};
@ -217,7 +202,9 @@ export default {
<style lang="scss">
.audio-wave-wrapper {
min-height: 8rem;
max-height: 12rem;
overflow: hidden;
height: 8rem;
}
.video-js .vjs-control-bar {
background-color: transparent;
}
</style>

View file

@ -60,6 +60,7 @@
:readable-time="readableTime"
:source-id="data.source_id"
:inbox-id="data.inbox_id"
:message-read="showReadTicks"
/>
</div>
<spinner v-if="isPending" size="tiny" />
@ -153,6 +154,14 @@ export default {
type: Boolean,
default: false,
},
hasUserReadMessage: {
type: Boolean,
default: false,
},
isWebWidgetInbox: {
type: Boolean,
default: false,
},
},
data() {
return {
@ -268,6 +277,14 @@ export default {
isOutgoing() {
return this.data.message_type === MESSAGE_TYPE.OUTGOING;
},
showReadTicks() {
return (
(this.isOutgoing || this.isTemplate) &&
this.hasUserReadMessage &&
this.isWebWidgetInbox &&
!this.data.private
);
},
isTemplate() {
return this.data.message_type === MESSAGE_TYPE.TEMPLATE;
},

View file

@ -48,6 +48,10 @@
:data="message"
:is-a-tweet="isATweet"
:has-instagram-story="hasInstagramStory"
:has-user-read-message="
hasUserReadMessage(message.created_at, getLastSeenAt)
"
:is-web-widget-inbox="isAWebWidgetInbox"
/>
<li v-show="getUnreadCount != 0" class="unread--toast">
<span class="text-uppercase">
@ -66,6 +70,10 @@
:data="message"
:is-a-tweet="isATweet"
:has-instagram-story="hasInstagramStory"
:has-user-read-message="
hasUserReadMessage(message.created_at, getLastSeenAt)
"
:is-web-widget-inbox="isAWebWidgetInbox"
/>
</ul>
<div
@ -141,6 +149,7 @@ export default {
listLoadingStatus: 'getAllMessagesLoaded',
getUnreadCount: 'getUnreadCount',
loadingChatList: 'getChatListLoadingStatus',
conversationLastSeen: 'getConversationLastSeen',
}),
inboxId() {
return this.currentChat.inbox_id;
@ -241,6 +250,11 @@ export default {
}
return 'arrow-chevron-left';
},
getLastSeenAt() {
if (this.conversationLastSeen) return this.conversationLastSeen;
const { contact_last_seen_at: contactLastSeenAt } = this.currentChat;
return contactLastSeenAt;
},
},
watch: {

View file

@ -36,9 +36,9 @@
<woot-audio-recorder
v-if="showAudioRecorderEditor"
ref="audioRecorderInput"
@state-recorder-timer-changed="onStateRecorderTimerChanged"
@state-recorder-progress-changed="onStateProgressRecorderChanged"
@state-recorder-changed="onStateRecorderChanged"
@recorder-blob="onRecorderBlob"
@finish-record="onFinishRecorder"
/>
<resizable-text-area
v-else-if="!showRichContentEditor"
@ -80,8 +80,8 @@
>
<p
v-if="isSignatureAvailable"
v-dompurify-html="formatMessage(messageSignature)"
class="message-signature"
v-html="formatMessage(messageSignature)"
/>
<p v-else class="message-signature">
{{ $t('CONVERSATION.FOOTER.MESSAGE_SIGNATURE_NOT_CONFIGURED') }}
@ -103,7 +103,7 @@
:show-emoji-picker="showEmojiPicker"
:on-send="sendMessage"
:is-send-disabled="isReplyButtonDisabled"
:recording-audio-duration-text="recordingAudioDuration"
:recording-audio-duration-text="recordingAudioDurationText"
:recording-audio-state="recordingAudioState"
:is-recording-audio="isRecordingAudio"
:set-format-mode="setFormatMode"
@ -193,7 +193,7 @@ export default {
attachedFiles: [],
isRecordingAudio: false,
recordingAudioState: '',
recordingAudioDuration: '',
recordingAudioDurationText: '',
isUploading: false,
replyType: REPLY_EDITOR_MODES.REPLY,
mentionSearchKey: '',
@ -585,11 +585,13 @@ export default {
}
},
toggleAudioRecorderPlayPause() {
if (this.isRecordingAudio && !this.isRecorderAudioStopped) {
this.isRecorderAudioStopped = true;
this.$refs.audioRecorderInput.stopAudioRecording();
} else if (this.isRecordingAudio && this.isRecorderAudioStopped) {
this.$refs.audioRecorderInput.playPause();
if (this.isRecordingAudio) {
if (!this.isRecorderAudioStopped) {
this.isRecorderAudioStopped = true;
this.$refs.audioRecorderInput.stopAudioRecording();
} else if (this.isRecorderAudioStopped) {
this.$refs.audioRecorderInput.playPause();
}
}
},
hideEmojiPicker() {
@ -612,19 +614,17 @@ export default {
onFocus() {
this.isFocused = true;
},
onStateRecorderTimerChanged(time) {
this.recordingAudioDuration = time;
onStateProgressRecorderChanged(duration) {
this.recordingAudioDurationText = duration;
},
onStateRecorderChanged(state) {
this.recordingAudioState = state;
if (state.includes('notallowederror')) {
if (state && 'notallowederror'.includes(state)) {
this.toggleAudioRecorder();
}
},
onRecorderBlob(file) {
if (file) {
this.onFileUpload(file);
}
onFinishRecorder(file) {
return file && this.onFileUpload(file);
},
toggleTyping(status) {
const conversationId = this.currentChat.id;

View file

@ -1,6 +1,8 @@
<template>
<div class="message-text--metadata">
<span class="time">{{ readableTime }}</span>
<span class="time" :class="{ delivered: messageRead }">{{
readableTime
}}</span>
<span v-if="showSentIndicator" class="time">
<fluent-icon
v-tooltip.top-start="$t('CHAT_LIST.SENT')"
@ -8,6 +10,13 @@
size="16"
/>
</span>
<fluent-icon
v-if="messageRead"
v-tooltip.top-start="$t('CHAT_LIST.MESSAGE_READ')"
icon="checkmark-double"
class="action--icon read-tick"
size="12"
/>
<fluent-icon
v-if="isEmail"
v-tooltip.top-start="$t('CHAT_LIST.RECEIVED_VIA_EMAIL')"
@ -120,6 +129,10 @@ export default {
type: [String, Number],
default: 0,
},
messageRead: {
type: Boolean,
default: false,
},
},
computed: {
inbox() {
@ -173,6 +186,10 @@ export default {
}
.action--icon {
&.read-tick {
color: var(--v-100);
margin-top: calc(var(--space-micro) + var(--space-micro) / 2);
}
color: var(--white);
}
@ -237,6 +254,15 @@ export default {
position: absolute;
right: var(--space-small);
white-space: nowrap;
&.delivered {
right: var(--space-medium);
line-height: 2;
}
}
.read-tick {
position: absolute;
bottom: var(--space-small);
right: var(--space-small);
}
}
}

View file

@ -6,7 +6,7 @@
'hide--quoted': !showQuotedContent,
}"
>
<div class="text-content" v-html="message"></div>
<div v-dompurify-html="message" class="text-content"></div>
<button
v-if="displayQuotedButton"
class="quoted-text--button"

View file

@ -23,6 +23,8 @@ class ActionCableConnector extends BaseActionCableConnector {
'contact.updated': this.onContactUpdate,
'conversation.mentioned': this.onConversationMentioned,
'notification.created': this.onNotificationCreated,
'first.reply.created': this.onFirstReplyCreated,
'conversation.read': this.onConversationRead,
};
}
@ -64,6 +66,11 @@ class ActionCableConnector extends BaseActionCableConnector {
this.fetchConversationStats();
};
onConversationRead = data => {
const { contact_last_seen_at: lastSeen } = data;
this.app.$store.dispatch('updateConversationRead', lastSeen);
};
onLogout = () => AuthAPI.logout();
onMessageCreated = data => {
@ -122,6 +129,7 @@ class ActionCableConnector extends BaseActionCableConnector {
fetchConversationStats = () => {
bus.$emit('fetch_conversation_stats');
bus.$emit('fetch_overview_reports');
};
onContactDelete = data => {
@ -139,6 +147,10 @@ class ActionCableConnector extends BaseActionCableConnector {
onNotificationCreated = data => {
this.app.$store.dispatch('notifications/addNotification', data);
};
onFirstReplyCreated = () => {
bus.$emit('fetch_overview_reports');
};
}
export default {

View file

@ -1,7 +1,15 @@
const allElementsString = arr => {
return arr.every(elem => typeof elem === 'string');
};
const allElementsNumbers = arr => {
return arr.every(elem => typeof elem === 'number');
};
const formatArray = params => {
if (params.length <= 0) {
params = [];
} else if (params.every(elem => typeof elem === 'string')) {
} else if (allElementsString(params) || allElementsNumbers(params)) {
params = [...params];
} else {
params = params.map(val => val.id);

View file

@ -0,0 +1,100 @@
import i18n from 'widget/i18n/index';
const defaultTranslations = Object.fromEntries(
Object.entries(i18n).filter(([key]) => key.includes('en'))
).en;
export const standardFieldKeys = {
emailAddress: {
key: 'EMAIL_ADDRESS',
label: 'Email Id',
placeholder: 'Please enter your email address',
},
fullName: {
key: 'FULL_NAME',
label: 'Full Name',
placeholder: 'Please enter your full name',
},
phoneNumber: {
key: 'PHONE_NUMBER',
label: 'Phone Number',
placeholder: 'Please enter your phone number',
},
};
export const getLabel = ({ key, label }) => {
return defaultTranslations.PRE_CHAT_FORM.FIELDS[key]
? defaultTranslations.PRE_CHAT_FORM.FIELDS[key].LABEL
: label;
};
export const getPlaceHolder = ({ key, placeholder }) => {
return defaultTranslations.PRE_CHAT_FORM.FIELDS[key]
? defaultTranslations.PRE_CHAT_FORM.FIELDS[key].PLACEHOLDER
: placeholder;
};
export const getCustomFields = ({ standardFields, customAttributes }) => {
let customFields = [];
const { pre_chat_fields: preChatFields } = standardFields;
customAttributes.forEach(attribute => {
const itemExist = preChatFields.find(
item => item.name === attribute.attribute_key
);
if (!itemExist) {
customFields.push({
label: attribute.attribute_display_name,
placeholder: attribute.attribute_display_name,
name: attribute.attribute_key,
type: attribute.attribute_display_type,
values: attribute.attribute_values,
field_type: attribute.attribute_model,
required: false,
enabled: false,
});
}
});
return customFields;
};
export const getFormattedPreChatFields = ({ preChatFields }) => {
return preChatFields.map(item => {
return {
...item,
label: getLabel({
key: standardFieldKeys[item.name]
? standardFieldKeys[item.name].key
: item.name,
label: item.label ? item.label : item.name,
}),
placeholder: getPlaceHolder({
key: standardFieldKeys[item.name]
? standardFieldKeys[item.name].key
: item.name,
placeholder: item.placeholder ? item.placeholder : item.name,
}),
};
});
};
export const getPreChatFields = ({
preChatFormOptions = {},
customAttributes = [],
}) => {
const { pre_chat_message, pre_chat_fields } = preChatFormOptions;
let customFields = {};
let preChatFields = {};
const formattedPreChatFields = getFormattedPreChatFields({
preChatFields: pre_chat_fields,
});
customFields = getCustomFields({
standardFields: { pre_chat_fields: formattedPreChatFields },
customAttributes,
});
preChatFields = [...formattedPreChatFields, ...customFields];
return {
pre_chat_message,
pre_chat_fields: preChatFields,
};
};

View file

@ -1,25 +0,0 @@
export const createMessengerScript = pageId => `
<script>
window.fbAsyncInit = function() {
FB.init({
appId: "${window.chatwootConfig.fbAppId}",
xfbml: true,
version: "v4.0"
});
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) { return; }
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<div class="fb-messengermessageus"
messenger_app_id="${window.chatwootConfig.fbAppId}"
page_id="${pageId}"
color="blue"
size="standard" >
</div>
`;

View file

@ -0,0 +1,47 @@
export default {
customFields: {
pre_chat_message: 'Share your queries or comments here.',
pre_chat_fields: [
{
label: 'Email Address',
name: 'emailAddress',
type: 'email',
field_type: 'standard',
required: false,
enabled: false,
placeholder: 'Please enter your email address',
},
{
label: 'Full Name',
name: 'fullName',
type: 'text',
field_type: 'standard',
required: false,
enabled: false,
placeholder: 'Please enter your full name',
},
{
label: 'Phone Number',
name: 'phoneNumber',
type: 'text',
field_type: 'standard',
required: false,
enabled: false,
placeholder: 'Please enter your phone number',
},
],
},
customAttributes: [
{
id: 101,
attribute_description: 'Order Identifier',
attribute_display_name: 'Order Id',
attribute_display_type: 'number',
attribute_key: 'order_id',
attribute_model: 'conversation_attribute',
attribute_values: Array(0),
created_at: '2021-11-29T10:20:04.563Z',
},
],
};

View file

@ -0,0 +1,76 @@
import {
getPreChatFields,
getFormattedPreChatFields,
getCustomFields,
} from '../preChat';
import inboxFixture from './inboxFixture';
const { customFields, customAttributes } = inboxFixture;
describe('#Pre chat Helpers', () => {
describe('getPreChatFields', () => {
it('should return correct pre-chat fields form options passed', () => {
expect(getPreChatFields({ preChatFormOptions: customFields })).toEqual(
customFields
);
});
});
describe('getFormattedPreChatFields', () => {
it('should return correct custom fields', () => {
expect(
getFormattedPreChatFields({
preChatFields: customFields.pre_chat_fields,
})
).toEqual([
{
label: 'Email Address',
name: 'emailAddress',
placeholder: 'Please enter your email address',
type: 'email',
field_type: 'standard',
required: false,
enabled: false,
},
{
label: 'Full Name',
name: 'fullName',
placeholder: 'Please enter your full name',
type: 'text',
field_type: 'standard',
required: false,
enabled: false,
},
{
label: 'Phone Number',
name: 'phoneNumber',
placeholder: 'Please enter your phone number',
type: 'text',
field_type: 'standard',
required: false,
enabled: false,
},
]);
});
});
describe('getCustomFields', () => {
it('should return correct custom fields', () => {
expect(
getCustomFields({
standardFields: { pre_chat_fields: customFields.pre_chat_fields },
customAttributes,
})
).toEqual([
{
enabled: false,
label: 'Order Id',
placeholder: 'Order Id',
name: 'order_id',
required: false,
field_type: 'conversation_attribute',
type: 'number',
values: [],
},
]);
});
});
});

View file

@ -89,7 +89,9 @@
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ"
},
"ACTION": {
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ"
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ",
"TEAM_MESSAGE_INPUT_PLACEHOLDER": "اكتب رسالتك هنا",
"TEAM_DROPDOWN_PLACEHOLDER": "اختيار فريق"
},
"TOGGLE": {
"ACTIVATION_TITLE": "تفعيل قاعدة الأتمتة",
@ -102,6 +104,13 @@
"DEACTIVATION_ERROR": "تعذر إلغاء تنشيط قاعدة الأتمتة، الرجاء المحاولة مرة أخرى لاحقاً",
"CONFIRMATION_LABEL": "نعم",
"CANCEL_LABEL": "لا"
},
"ATTACHMENT": {
"UPLOAD_ERROR": "تعذر تحميل المرفق، الرجاء المحاولة مرة أخرى",
"LABEL_IDLE": "ارفع المرفق",
"LABEL_UPLOADING": "جاري الرفع...",
"LABEL_UPLOADED": "تم الرفع بنجاح",
"LABEL_UPLOAD_FAILED": "فشل الرفع"
}
}
}

View file

@ -81,6 +81,7 @@
"NO_MESSAGES": "لا توجد رسائل",
"NO_CONTENT": "لم يتم العثور على محتوى",
"HIDE_QUOTED_TEXT": "Hide Quoted Text",
"SHOW_QUOTED_TEXT": "Show Quoted Text"
"SHOW_QUOTED_TEXT": "Show Quoted Text",
"MESSAGE_READ": "قرائة"
}
}

View file

@ -70,6 +70,14 @@
"SUCCESS_MESSAGE": "تم حفظ جهة الاتصال بنجاح",
"ERROR_MESSAGE": "حدث خطأ، الرجاء المحاولة مرة أخرى"
},
"DELETE_NOTE": {
"CONFIRM": {
"TITLE": "تأكيد الحذف",
"MESSAGE": "هل أنت متأكد من حذف هذه الملاحظة؟",
"YES": "نعم، احذف",
"NO": "لا، احتفظ به"
}
},
"DELETE_CONTACT": {
"BUTTON_LABEL": "حذف جهة الاتصال",
"TITLE": "حذف جهة الاتصال",

View file

@ -60,6 +60,13 @@
"NOTIFICATIONS_PAGE": {
"HEADER": "الإشعارات",
"MARK_ALL_DONE": "وضع علامة على جميع المنجز",
"DELETE_TITLE": "تم الحذف",
"UNREAD_NOTIFICATION": {
"TITLE": "إشعارات غير مقروءة",
"ALL_NOTIFICATIONS": "عرض جميع الإشعارات",
"LOADING_UNREAD_MESSAGE": "تحميل الإشعارات الغير مقروءة...",
"EMPTY_MESSAGE": "ليس لديك إشعارات غير مقروءة"
},
"LIST": {
"LOADING_MESSAGE": "جاري تحميل الإشعارات...",
"404": "لا يوجد إشعارات",
@ -101,6 +108,7 @@
"GO_TO_CONVERSATION_DASHBOARD": "الذهاب إلى لوحة المحادثة",
"GO_TO_CONTACTS_DASHBOARD": "الذهاب إلى لوحة جهات الاتصال",
"GO_TO_REPORTS_OVERVIEW": "الذهاب إلى نظرة التقارير",
"GO_TO_CONVERSATION_REPORTS": "الذهاب إلى تقارير المحادثات",
"GO_TO_AGENT_REPORTS": "الذهاب إلى تقارير الوكيل",
"GO_TO_LABEL_REPORTS": "انتقل إلى تقارير التسمية",
"GO_TO_INBOX_REPORTS": "الذهاب إلى تقارير صندوق الوارد",

View file

@ -341,10 +341,6 @@
"AUTO_ASSIGNMENT_SUCCESS_MESSAGE": "تم تحديث إعدادات الإسناد التلقائي بنجاح",
"ERROR_MESSAGE": "تعذر تحديث لون صندوق الدردشة. الرجاء المحاولة مرة أخرى لاحقاً."
},
"AUTO_ASSIGNMENT": {
"ENABLED": "مفعل",
"DISABLED": "معطّل"
},
"EMAIL_COLLECT_BOX": {
"ENABLED": "مفعل",
"DISABLED": "معطّل"
@ -394,13 +390,16 @@
"FEATURES": {
"LABEL": "الخصائص",
"DISPLAY_FILE_PICKER": "عرض أداة انتقاء الملفات في الـ widget",
"DISPLAY_EMOJI_PICKER": "عرض منتقي الرموز التعبيرية على الـ widget"
"DISPLAY_EMOJI_PICKER": "عرض منتقي الرموز التعبيرية على الـ widget",
"ALLOW_END_CONVERSATION": "السماح للمستخدمين بإنهاء المحادثة من عنصر واجهة المستخدم"
},
"SETTINGS_POPUP": {
"MESSENGER_HEADING": "كود \"الماسنجر\"",
"MESSENGER_SUB_HEAD": "ضع هذا الكود داخل وسم الـ body في موقعك",
"INBOX_AGENTS": "موظف الدعم",
"INBOX_AGENTS_SUB_TEXT": "إضافة أو إزالة موظفين من قناة التواصل هذه",
"AGENT_ASSIGNMENT": "إسناد المحادثات",
"AGENT_ASSIGNMENT_SUB_TEXT": "تحديث إعدادات إسناد المحادثات",
"UPDATE": "تحديث",
"ENABLE_EMAIL_COLLECT_BOX": "تفعيل صندوق جمع البريد الإلكتروني",
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "تمكين أو تعطيل مربع جمع البريد الإلكتروني في محادثة جديدة",
@ -431,6 +430,15 @@
},
"PRE_CHAT_FORM": {
"DESCRIPTION": "نماذج ما قبل الدردشة تمكنك من التقاط معلومات المستخدم قبل بدء المحادثة معك.",
"SET_FIELDS": "حقول نموذج الدردشة السابقة",
"SET_FIELDS_HEADER": {
"FIELDS": "الحقول",
"LABEL": "الوسم",
"PLACE_HOLDER": "المحتوى",
"KEY": "المفتاح",
"TYPE": "النوع",
"REQUIRED": "مطلوب"
},
"ENABLE": {
"LABEL": "تمكين نموذج الدردشة السابقة",
"OPTIONS": {
@ -468,6 +476,7 @@
"IMAP": {
"TITLE": "IMAP",
"SUBTITLE": "تعيين تفاصيل IMAP الخاصة بك",
"NOTE_TEXT": "لتمكين SMTP ، الرجاء تكوين IMAP.",
"UPDATE": "تحديث الإعدادات",
"TOGGLE_AVAILABILITY": "تمكين تكوين IMAP لهذا البريد الوارد",
"TOGGLE_HELP": "تمكين IMAP سيساعد المستخدم على تلقي البريد الإلكتروني",
@ -483,9 +492,9 @@
"LABEL": "المنفذ",
"PLACE_HOLDER": "المنفذ"
},
"EMAIL": {
"LABEL": "البريد الإلكتروني",
"PLACE_HOLDER": "البريد الإلكتروني"
"LOGIN": {
"LABEL": "تسجيل الدخول",
"PLACE_HOLDER": "تسجيل الدخول"
},
"PASSWORD": {
"LABEL": "كلمة المرور",
@ -511,9 +520,9 @@
"LABEL": "المنفذ",
"PLACE_HOLDER": "المنفذ"
},
"EMAIL": {
"LABEL": "البريد الإلكتروني",
"PLACE_HOLDER": "البريد الإلكتروني"
"LOGIN": {
"LABEL": "تسجيل الدخول",
"PLACE_HOLDER": "تسجيل الدخول"
},
"PASSWORD": {
"LABEL": "كلمة المرور",
@ -526,7 +535,9 @@
"ENCRYPTION": "التشفير",
"SSL_TLS": "SSL/TLS",
"START_TLS": "STARTTLS",
"OPEN_SSL_VERIFY_MODE": "فتح وضع التحقق من SSL"
}
"OPEN_SSL_VERIFY_MODE": "فتح وضع التحقق من SSL",
"AUTH_MECHANISM": "المصادقة"
},
"NOTE": "ملاحظة: "
}
}

View file

@ -2,6 +2,29 @@
"INTEGRATION_SETTINGS": {
"HEADER": "خيارات الربط",
"WEBHOOK": {
"SUBSCRIBED_EVENTS": "الأحداث المشتركة",
"FORM": {
"CANCEL": "إلغاء",
"DESC": "أحداث Webhook توفر لك معلومات في الوقت الحقيقي حول ما يحدث في حساب Chatwoot الخاص بك. الرجاء إدخال عنوان URL صالح لتكوين callback.",
"SUBSCRIPTIONS": {
"LABEL": "الأحداث",
"EVENTS": {
"CONVERSATION_CREATED": "تم إنشاء المحادثة",
"CONVERSATION_STATUS_CHANGED": "تم تغيير حالة المحادثة",
"CONVERSATION_UPDATED": "تم تحديث المحادثة",
"MESSAGE_CREATED": "تم إنشاء رسالة",
"MESSAGE_UPDATED": "تم تحديث الرسالة",
"WEBWIDGET_TRIGGERED": "أداة الدردشة المباشرة مفتوحة من قبل المستخدم"
}
},
"END_POINT": {
"LABEL": "رابط Webhook",
"PLACEHOLDER": "مثال: https://example/api/webhook",
"ERROR": "الرجاء إدخال عنوان URL صالح"
},
"EDIT_SUBMIT": "تحديث الويبهوك",
"ADD_SUBMIT": "إنشاء webhook"
},
"TITLE": "Webhook",
"CONFIGURE": "تهيئة",
"HEADER": "إعدادات الـ Webhook",
@ -20,35 +43,16 @@
"EDIT": {
"BUTTON_TEXT": "تعديل",
"TITLE": "تعديل webhook",
"CANCEL": "إلغاء",
"DESC": "أحداث Webhook توفر لك معلومات في الوقت الحقيقي حول ما يحدث في حساب Chatwoot الخاص بك. الرجاء إدخال عنوان URL صالح لتكوين callback.",
"FORM": {
"END_POINT": {
"LABEL": "رابط Webhook",
"PLACEHOLDER": "مثال: https://example/api/webhook",
"ERROR": "الرجاء إدخال عنوان URL صالح"
},
"SUBMIT": "تعديل webhook"
},
"API": {
"SUCCESS_MESSAGE": "تم تحديث رابط Webhook بنجاح",
"SUCCESS_MESSAGE": "تم تحديث تكوين ويبهوك بنجاح",
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
}
},
"ADD": {
"CANCEL": "إلغاء",
"TITLE": "إضافة webhook جديد",
"DESC": "أحداث Webhook توفر لك معلومات في الوقت الحقيقي حول ما يحدث في حساب Chatwoot الخاص بك. الرجاء إدخال عنوان URL صالح لتكوين callback.",
"FORM": {
"END_POINT": {
"LABEL": "رابط Webhook",
"PLACEHOLDER": "مثال: https://example/api/webhook",
"ERROR": "الرجاء إدخال عنوان URL صالح"
},
"SUBMIT": "إنشاء webhook"
},
"API": {
"SUCCESS_MESSAGE": "تم إضافة Webhook بنجاح",
"SUCCESS_MESSAGE": "تم إضافة إعدادات Webhook بنجاح",
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
}
},
@ -60,7 +64,7 @@
},
"CONFIRM": {
"TITLE": "تأكيد الحذف",
"MESSAGE": "هل أنت متأكد من الحذف ",
"MESSAGE": "هل أنت متأكد من حذف webhook؟ (%{webhookURL})",
"YES": "نعم، احذف ",
"NO": "لا، احتفظ به"
}

View file

@ -1,6 +1,6 @@
{
"REPORT": {
"HEADER": "نظرة عامة",
"HEADER": "المحادثات",
"LOADING_CHART": "تحميل بيانات الرسم البياني...",
"NO_ENOUGH_DATA": "لم يتم جمع بيانات بقدر كافي لإنشاء التقرير، الرجاء المحاولة مرة أخرى لاحقاً.",
"DOWNLOAD_AGENT_REPORTS": "تنزيل تقارير الوكيل",
@ -19,11 +19,15 @@
},
"FIRST_RESPONSE_TIME": {
"NAME": "وقت الاستجابة الأولى",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الرد الأول هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_TIME": {
"NAME": "وقت إغلاق المحادثات",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الحل هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_COUNT": {
"NAME": "عدد مرات الإغلاق",
@ -108,7 +112,8 @@
"id": 4,
"groupBy": "السنة"
}
]
],
"BUSINESS_HOURS": "ساعات العمل"
},
"AGENT_REPORTS": {
"HEADER": "نظرة عامة للوكلاء",
@ -131,11 +136,15 @@
},
"FIRST_RESPONSE_TIME": {
"NAME": "وقت الاستجابة الأولى",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الرد الأول هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_TIME": {
"NAME": "وقت إغلاق المحادثات",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الحل هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_COUNT": {
"NAME": "عدد مرات الإغلاق",
@ -194,11 +203,15 @@
},
"FIRST_RESPONSE_TIME": {
"NAME": "وقت الاستجابة الأولى",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الرد الأول هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_TIME": {
"NAME": "وقت إغلاق المحادثات",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الحل هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_COUNT": {
"NAME": "عدد مرات الإغلاق",
@ -257,11 +270,15 @@
},
"FIRST_RESPONSE_TIME": {
"NAME": "وقت الاستجابة الأولى",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الرد الأول هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_TIME": {
"NAME": "وقت إغلاق المحادثات",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الحل هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_COUNT": {
"NAME": "عدد مرات الإغلاق",
@ -320,11 +337,15 @@
},
"FIRST_RESPONSE_TIME": {
"NAME": "وقت الاستجابة الأولى",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الرد الأول هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_TIME": {
"NAME": "وقت إغلاق المحادثات",
"DESC": "(متوسط)"
"DESC": "(متوسط)",
"INFO_TEXT": "العدد الإجمالي للمحادثات المستخدمة في الحساب:",
"TOOLTIP_TEXT": "وقت الحل هو %{metricValue} (على أساس %{conversationCount} محادثات)"
},
"RESOLUTION_COUNT": {
"NAME": "عدد مرات الإغلاق",
@ -392,5 +413,33 @@
"TOOLTIP": "العدد الإجمالي للردود / العدد الإجمالي لرسائل الاستقصاء التي أرسلتها CSAT * 100"
}
}
},
"OVERVIEW_REPORTS": {
"HEADER": "نظرة عامة",
"LIVE": "مباشر",
"ACCOUNT_CONVERSATIONS": {
"HEADER": "المحادثات المفتوحة",
"LOADING_MESSAGE": "جارٍ تحميل مقاييس المحادثات...",
"OPEN": "فتح",
"UNATTENDED": "بدون حضور",
"UNASSIGNED": "غير مسند"
},
"AGENT_CONVERSATIONS": {
"HEADER": "المحادثات من قبل الوكلاء",
"LOADING_MESSAGE": "جاري تحميل مقاييس الوكيل...",
"NO_AGENTS": "لا توجد أي محادثات من قبل الوكلاء",
"TABLE_HEADER": {
"AGENT": "موظف الدعم",
"OPEN": "افتتحت",
"UNATTENDED": "بدون حضور",
"STATUS": "الحالة"
}
},
"AGENT_STATUS": {
"HEADER": "حالة الوكيل",
"ONLINE": "متصل",
"BUSY": "مشغول",
"OFFLINE": "غير متصل"
}
}
}
}

View file

@ -15,6 +15,9 @@
"SUCCESS_MESSAGE": "تم تغيير كلمة المرور بنجاح",
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
},
"CAPTCHA": {
"ERROR": "انتهت صلاحية التحقق. الرجاء حل كلمة التحقق مرة أخرى."
},
"SUBMIT": "إرسال"
}
}

View file

@ -131,6 +131,10 @@
"BUTTON_TEXT": "نسخ",
"COPY_SUCCESSFUL": "تم نسخ الكود إلى الحافظة بنجاح"
},
"SHOW_MORE_BLOCK": {
"SHOW_MORE": "إظهار المزيد",
"SHOW_LESS": "إظهار أقل"
},
"FILE_BUBBLE": {
"DOWNLOAD": "تنزيل",
"UPLOADING": "جاري الرفع..."
@ -147,6 +151,7 @@
},
"SIDEBAR": {
"CURRENTLY_VIEWING_ACCOUNT": "مشاهدة حاليا:",
"SWITCH": "تبديل",
"CONVERSATIONS": "المحادثات",
"ALL_CONVERSATIONS": "كل المحادثات",
"MENTIONED_CONVERSATIONS": "الإشارات",
@ -173,8 +178,8 @@
"NEW_LABEL": "علامة جديدة",
"NEW_TEAM": "فريق جديد",
"NEW_INBOX": "صندوق الوارد الجديد",
"REPORTS_OVERVIEW": "نظرة عامة",
"CSAT": "CSAT",
"REPORTS_CONVERSATION": "المحادثات",
"CSAT": "تقييم رضاء العملاء",
"CAMPAIGNS": "الحملات",
"ONGOING": "جارية",
"ONE_OFF": "إيقاف واحد",
@ -183,7 +188,8 @@
"REPORTS_INBOX": "صندوق الوارد",
"REPORTS_TEAM": "الفريق",
"SET_AVAILABILITY_TITLE": "تعيين نفسك كـ",
"BETA": "تجريبي"
"BETA": "تجريبي",
"REPORTS_OVERVIEW": "نظرة عامة"
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "أوه! لم نتمكن من العثور على الحساب. الرجاء إنشاء حساب جديد للمتابعة.",

View file

@ -21,7 +21,8 @@
"PASSWORD": {
"LABEL": "كلمة المرور",
"PLACEHOLDER": "كلمة المرور",
"ERROR": "كلمة المرور قصيرة جداً"
"ERROR": "كلمة المرور قصيرة جداً",
"IS_INVALID_PASSWORD": "يجب أن تحتوي كلمة المرور على الأقل على حرف كبير واحد وحرف صغير واحد ورقم واحد وحرف خاص واحد"
},
"CONFIRM_PASSWORD": {
"LABEL": "تأكيد كلمة المرور",

View file

@ -89,7 +89,9 @@
"DELETE_MESSAGE": "You need to have atleast one condition to save"
},
"ACTION": {
"DELETE_MESSAGE": "You need to have atleast one action to save"
"DELETE_MESSAGE": "You need to have atleast one action to save",
"TEAM_MESSAGE_INPUT_PLACEHOLDER": "Enter your message here",
"TEAM_DROPDOWN_PLACEHOLDER": "Select teams"
},
"TOGGLE": {
"ACTIVATION_TITLE": "Activate Automation Rule",
@ -102,6 +104,13 @@
"DEACTIVATION_ERROR": "Could not Deactivate Automation, Please try again later",
"CONFIRMATION_LABEL": "Yes",
"CANCEL_LABEL": "No"
},
"ATTACHMENT": {
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
"LABEL_IDLE": "Upload Attachment",
"LABEL_UPLOADING": "Качване...",
"LABEL_UPLOADED": "Succesfully Uploaded",
"LABEL_UPLOAD_FAILED": "Upload Failed"
}
}
}

View file

@ -81,6 +81,7 @@
"NO_MESSAGES": "Няма съобщения",
"NO_CONTENT": "Няма налично съдържание",
"HIDE_QUOTED_TEXT": "Скриване на цитирания текст",
"SHOW_QUOTED_TEXT": "Показване на цитирания текст"
"SHOW_QUOTED_TEXT": "Показване на цитирания текст",
"MESSAGE_READ": "Read"
}
}

View file

@ -70,6 +70,14 @@
"SUCCESS_MESSAGE": "Успешно запазване на контактите",
"ERROR_MESSAGE": "Възникна грешка, моля опитайте отново"
},
"DELETE_NOTE": {
"CONFIRM": {
"TITLE": "Потвърди изтриването",
"MESSAGE": "Are you want sure to delete this note?",
"YES": "Yes, Delete it",
"NO": "No, Keep it"
}
},
"DELETE_CONTACT": {
"BUTTON_LABEL": "Изтриване на контакта",
"TITLE": "Изтриване на контакта",

View file

@ -60,6 +60,13 @@
"NOTIFICATIONS_PAGE": {
"HEADER": "Notifications",
"MARK_ALL_DONE": "Mark All Done",
"DELETE_TITLE": "deleted",
"UNREAD_NOTIFICATION": {
"TITLE": "Unread Notifications",
"ALL_NOTIFICATIONS": "View all notifications",
"LOADING_UNREAD_MESSAGE": "Loading unread notifications...",
"EMPTY_MESSAGE": "You have no unread notifications"
},
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
@ -101,6 +108,7 @@
"GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard",
"GO_TO_CONTACTS_DASHBOARD": "Go to Contacts Dashboard",
"GO_TO_REPORTS_OVERVIEW": "Go to Reports Overview",
"GO_TO_CONVERSATION_REPORTS": "Go to Conversation Reports",
"GO_TO_AGENT_REPORTS": "Go to Agent Reports",
"GO_TO_LABEL_REPORTS": "Go to Label Reports",
"GO_TO_INBOX_REPORTS": "Go to Inbox Reports",

View file

@ -341,10 +341,6 @@
"AUTO_ASSIGNMENT_SUCCESS_MESSAGE": "Auto assignment updated successfully",
"ERROR_MESSAGE": "Could not update widget color. Please try again later."
},
"AUTO_ASSIGNMENT": {
"ENABLED": "Enabled",
"DISABLED": "Disabled"
},
"EMAIL_COLLECT_BOX": {
"ENABLED": "Enabled",
"DISABLED": "Disabled"
@ -394,13 +390,16 @@
"FEATURES": {
"LABEL": "Features",
"DISPLAY_FILE_PICKER": "Display file picker on the widget",
"DISPLAY_EMOJI_PICKER": "Display emoji picker on the widget"
"DISPLAY_EMOJI_PICKER": "Display emoji picker on the widget",
"ALLOW_END_CONVERSATION": "Allow users to end conversation from the widget"
},
"SETTINGS_POPUP": {
"MESSENGER_HEADING": "Messenger Script",
"MESSENGER_SUB_HEAD": "Place this button inside your body tag",
"INBOX_AGENTS": "Агенти",
"INBOX_AGENTS_SUB_TEXT": "Add or remove agents from this inbox",
"AGENT_ASSIGNMENT": "Conversation Assignment",
"AGENT_ASSIGNMENT_SUB_TEXT": "Update conversation assignment settings",
"UPDATE": "Update",
"ENABLE_EMAIL_COLLECT_BOX": "Enable email collect box",
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Enable or disable email collect box on new conversation",
@ -431,6 +430,15 @@
},
"PRE_CHAT_FORM": {
"DESCRIPTION": "Pre chat forms enable you to capture user information before they start conversation with you.",
"SET_FIELDS": "Pre chat form fields",
"SET_FIELDS_HEADER": {
"FIELDS": "Fields",
"LABEL": "Label",
"PLACE_HOLDER": "Placeholder",
"KEY": "Ключ",
"TYPE": "Тип",
"REQUIRED": "Required"
},
"ENABLE": {
"LABEL": "Enable pre chat form",
"OPTIONS": {
@ -439,7 +447,7 @@
}
},
"PRE_CHAT_MESSAGE": {
"LABEL": "Pre Chat Message",
"LABEL": "Pre chat message",
"PLACEHOLDER": "This message would be visible to the users along with the form"
},
"REQUIRE_EMAIL": {
@ -468,6 +476,7 @@
"IMAP": {
"TITLE": "IMAP",
"SUBTITLE": "Set your IMAP details",
"NOTE_TEXT": "To enable SMTP, please configure IMAP.",
"UPDATE": "Update IMAP settings",
"TOGGLE_AVAILABILITY": "Enable IMAP configuration for this inbox",
"TOGGLE_HELP": "Enabling IMAP will help the user to recieve email",
@ -483,9 +492,9 @@
"LABEL": "Port",
"PLACE_HOLDER": "Port"
},
"EMAIL": {
"LABEL": "Email",
"PLACE_HOLDER": "Email"
"LOGIN": {
"LABEL": "Login",
"PLACE_HOLDER": "Login"
},
"PASSWORD": {
"LABEL": "Password",
@ -511,9 +520,9 @@
"LABEL": "Port",
"PLACE_HOLDER": "Port"
},
"EMAIL": {
"LABEL": "Email",
"PLACE_HOLDER": "Email"
"LOGIN": {
"LABEL": "Login",
"PLACE_HOLDER": "Login"
},
"PASSWORD": {
"LABEL": "Password",
@ -526,7 +535,9 @@
"ENCRYPTION": "Encryption",
"SSL_TLS": "SSL/TLS",
"START_TLS": "STARTTLS",
"OPEN_SSL_VERIFY_MODE": "Open SSL Verify Mode"
}
"OPEN_SSL_VERIFY_MODE": "Open SSL Verify Mode",
"AUTH_MECHANISM": "Authentication"
},
"NOTE": "Note: "
}
}

View file

@ -2,6 +2,29 @@
"INTEGRATION_SETTINGS": {
"HEADER": "Integrations",
"WEBHOOK": {
"SUBSCRIBED_EVENTS": "Subscribed Events",
"FORM": {
"CANCEL": "Отмени",
"DESC": "Webhook events provide you the realtime information about what's happening in your Chatwoot account. Please enter a valid URL to configure a callback.",
"SUBSCRIPTIONS": {
"LABEL": "Events",
"EVENTS": {
"CONVERSATION_CREATED": "Conversation Created",
"CONVERSATION_STATUS_CHANGED": "Conversation Status Changed",
"CONVERSATION_UPDATED": "Conversation Updated",
"MESSAGE_CREATED": "Message created",
"MESSAGE_UPDATED": "Message updated",
"WEBWIDGET_TRIGGERED": "Live chat widget opened by the user"
}
},
"END_POINT": {
"LABEL": "Webhook URL",
"PLACEHOLDER": "Example: https://example/api/webhook",
"ERROR": "Please enter a valid URL"
},
"EDIT_SUBMIT": "Update webhook",
"ADD_SUBMIT": "Create webhook"
},
"TITLE": "Webhook",
"CONFIGURE": "Configure",
"HEADER": "Webhook settings",
@ -20,35 +43,16 @@
"EDIT": {
"BUTTON_TEXT": "Редактирай",
"TITLE": "Edit webhook",
"CANCEL": "Отмени",
"DESC": "Webhook events provide you the realtime information about what's happening in your Chatwoot account. Please enter a valid URL to configure a callback.",
"FORM": {
"END_POINT": {
"LABEL": "Webhook URL",
"PLACEHOLDER": "Example: https://example/api/webhook",
"ERROR": "Please enter a valid URL"
},
"SUBMIT": "Edit webhook"
},
"API": {
"SUCCESS_MESSAGE": "Webhook URL updated successfully",
"SUCCESS_MESSAGE": "Webhook configuration updated successfully",
"ERROR_MESSAGE": "Не можа да се свърже с Woot сървър. Моля, опитайте отново по-късно"
}
},
"ADD": {
"CANCEL": "Отмени",
"TITLE": "Add new webhook",
"DESC": "Webhook events provide you the realtime information about what's happening in your Chatwoot account. Please enter a valid URL to configure a callback.",
"FORM": {
"END_POINT": {
"LABEL": "Webhook URL",
"PLACEHOLDER": "Example: https://example/api/webhook",
"ERROR": "Please enter a valid URL"
},
"SUBMIT": "Create webhook"
},
"API": {
"SUCCESS_MESSAGE": "Webhook added successfully",
"SUCCESS_MESSAGE": "Webhook configuration added successfully",
"ERROR_MESSAGE": "Не можа да се свърже с Woot сървър. Моля, опитайте отново по-късно"
}
},
@ -60,7 +64,7 @@
},
"CONFIRM": {
"TITLE": "Потвърди изтриването",
"MESSAGE": "Сигурни ли сте за изтриването ",
"MESSAGE": "Are you sure to delete the webhook? (%{webhookURL})",
"YES": "Да, изтрий ",
"NO": "No, Keep it"
}

View file

@ -1,6 +1,6 @@
{
"REPORT": {
"HEADER": "Overview",
"HEADER": "Разговори",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
@ -18,12 +18,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "First response time",
"DESC": "( Avg )"
"NAME": "First Response Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Resolution Time",
"DESC": "( Avg )"
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@ -108,7 +112,8 @@
"id": 4,
"groupBy": "Year"
}
]
],
"BUSINESS_HOURS": "Business Hours"
},
"AGENT_REPORTS": {
"HEADER": "Agents Overview",
@ -130,12 +135,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "First response time",
"DESC": "( Avg )"
"NAME": "First Response Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Resolution Time",
"DESC": "( Avg )"
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@ -193,12 +202,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "First response time",
"DESC": "( Avg )"
"NAME": "First Response Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Resolution Time",
"DESC": "( Avg )"
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@ -256,12 +269,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "First response time",
"DESC": "( Avg )"
"NAME": "First Response Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Resolution Time",
"DESC": "( Avg )"
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@ -319,12 +336,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "First response time",
"DESC": "( Avg )"
"NAME": "First Response Time",
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Resolution Time",
"DESC": "( Avg )"
"DESC": "( Avg )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Resolution Count",
@ -392,5 +413,33 @@
"TOOLTIP": "Total number of responses / Total number of CSAT survey messages sent * 100"
}
}
},
"OVERVIEW_REPORTS": {
"HEADER": "Overview",
"LIVE": "Live",
"ACCOUNT_CONVERSATIONS": {
"HEADER": "Open Conversations",
"LOADING_MESSAGE": "Loading conversation metrics...",
"OPEN": "Отворен",
"UNATTENDED": "Unattended",
"UNASSIGNED": "Неназначен"
},
"AGENT_CONVERSATIONS": {
"HEADER": "Conversations by agents",
"LOADING_MESSAGE": "Loading agent metrics...",
"NO_AGENTS": "There are no conversations by agents",
"TABLE_HEADER": {
"AGENT": "Агент",
"OPEN": "OPEN",
"UNATTENDED": "Unattended",
"STATUS": "Статус"
}
},
"AGENT_STATUS": {
"HEADER": "Agent status",
"ONLINE": "Online",
"BUSY": "Busy",
"OFFLINE": "Offline"
}
}
}
}

View file

@ -15,6 +15,9 @@
"SUCCESS_MESSAGE": "Successfully changed the password",
"ERROR_MESSAGE": "Не можа да се свърже с Woot сървър. Моля, опитайте отново по-късно"
},
"CAPTCHA": {
"ERROR": "Verification expired. Please solve captcha again."
},
"SUBMIT": "Изпращане"
}
}

View file

@ -21,7 +21,7 @@
},
"MESSAGE_SIGNATURE_SECTION": {
"TITLE": "Personal message signature",
"NOTE": "Create a personal message signature that would be added to all the messages you send from the platform. Use the rich content editor to create a highly personalised signature.",
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
"BTN_TEXT": "Save message signature",
"API_ERROR": "Couldn't save signature! Try again",
"API_SUCCESS": "Signature saved successfully"
@ -131,6 +131,10 @@
"BUTTON_TEXT": "Copy",
"COPY_SUCCESSFUL": "Code copied to clipboard successfully"
},
"SHOW_MORE_BLOCK": {
"SHOW_MORE": "Show More",
"SHOW_LESS": "Show Less"
},
"FILE_BUBBLE": {
"DOWNLOAD": "Download",
"UPLOADING": "Качване..."
@ -147,6 +151,7 @@
},
"SIDEBAR": {
"CURRENTLY_VIEWING_ACCOUNT": "Currently viewing:",
"SWITCH": "Switch",
"CONVERSATIONS": "Разговори",
"ALL_CONVERSATIONS": "All Conversations",
"MENTIONED_CONVERSATIONS": "Споменавания",
@ -173,7 +178,7 @@
"NEW_LABEL": "New label",
"NEW_TEAM": "New team",
"NEW_INBOX": "New inbox",
"REPORTS_OVERVIEW": "Overview",
"REPORTS_CONVERSATION": "Разговори",
"CSAT": "CSAT",
"CAMPAIGNS": "Campaigns",
"ONGOING": "Ongoing",
@ -183,7 +188,8 @@
"REPORTS_INBOX": "Входяща кутия",
"REPORTS_TEAM": "Team",
"SET_AVAILABILITY_TITLE": "Set yourself as",
"BETA": "Beta"
"BETA": "Beta",
"REPORTS_OVERVIEW": "Overview"
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",

View file

@ -21,7 +21,8 @@
"PASSWORD": {
"LABEL": "Password",
"PLACEHOLDER": "Password",
"ERROR": "Password is too short"
"ERROR": "Password is too short",
"IS_INVALID_PASSWORD": "Password should contain atleast 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character"
},
"CONFIRM_PASSWORD": {
"LABEL": "Confirm Password",

View file

@ -83,7 +83,7 @@
"SELECT_ALL": "select all agents",
"SELECTED_COUNT": "%{selected} out of %{total} agents selected.",
"BUTTON_TEXT": "Add agents",
"AGENT_VALIDATION_ERROR": "Select atleaset one agent."
"AGENT_VALIDATION_ERROR": "Select at least one agent."
},
"FINISH": {
"TITLE": "Your team is ready!",

View file

@ -89,7 +89,9 @@
"DELETE_MESSAGE": "You need to have atleast one condition to save"
},
"ACTION": {
"DELETE_MESSAGE": "You need to have atleast one action to save"
"DELETE_MESSAGE": "You need to have atleast one action to save",
"TEAM_MESSAGE_INPUT_PLACEHOLDER": "Enter your message here",
"TEAM_DROPDOWN_PLACEHOLDER": "Select teams"
},
"TOGGLE": {
"ACTIVATION_TITLE": "Activate Automation Rule",
@ -102,6 +104,13 @@
"DEACTIVATION_ERROR": "Could not Deactivate Automation, Please try again later",
"CONFIRMATION_LABEL": "Si",
"CANCEL_LABEL": "No"
},
"ATTACHMENT": {
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
"LABEL_IDLE": "Upload Attachment",
"LABEL_UPLOADING": "S'està carregant...",
"LABEL_UPLOADED": "Succesfully Uploaded",
"LABEL_UPLOAD_FAILED": "Upload Failed"
}
}
}

View file

@ -81,6 +81,7 @@
"NO_MESSAGES": "Cap Missatge",
"NO_CONTENT": "No content available",
"HIDE_QUOTED_TEXT": "Hide Quoted Text",
"SHOW_QUOTED_TEXT": "Show Quoted Text"
"SHOW_QUOTED_TEXT": "Show Quoted Text",
"MESSAGE_READ": "Read"
}
}

View file

@ -70,6 +70,14 @@
"SUCCESS_MESSAGE": "Contacts saved successfully",
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
},
"DELETE_NOTE": {
"CONFIRM": {
"TITLE": "Confirma l'esborrat",
"MESSAGE": "Are you want sure to delete this note?",
"YES": "Yes, Delete it",
"NO": "No, manten-la"
}
},
"DELETE_CONTACT": {
"BUTTON_LABEL": "Delete Contact",
"TITLE": "Delete contact",

View file

@ -60,6 +60,13 @@
"NOTIFICATIONS_PAGE": {
"HEADER": "Notificacions",
"MARK_ALL_DONE": "Marca Tot Fet",
"DELETE_TITLE": "deleted",
"UNREAD_NOTIFICATION": {
"TITLE": "Unread Notifications",
"ALL_NOTIFICATIONS": "View all notifications",
"LOADING_UNREAD_MESSAGE": "Loading unread notifications...",
"EMPTY_MESSAGE": "You have no unread notifications"
},
"LIST": {
"LOADING_MESSAGE": "Carregant notificacions...",
"404": "Cap Notificació",
@ -101,6 +108,7 @@
"GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard",
"GO_TO_CONTACTS_DASHBOARD": "Go to Contacts Dashboard",
"GO_TO_REPORTS_OVERVIEW": "Go to Reports Overview",
"GO_TO_CONVERSATION_REPORTS": "Go to Conversation Reports",
"GO_TO_AGENT_REPORTS": "Go to Agent Reports",
"GO_TO_LABEL_REPORTS": "Go to Label Reports",
"GO_TO_INBOX_REPORTS": "Go to Inbox Reports",

View file

@ -341,10 +341,6 @@
"AUTO_ASSIGNMENT_SUCCESS_MESSAGE": "Assignació automàtica actualitzada correctament",
"ERROR_MESSAGE": "No s'ha pogut actualitzar el color del widget. Torneu-ho a provar més endavant."
},
"AUTO_ASSIGNMENT": {
"ENABLED": "Habilita",
"DISABLED": "Inhabilita"
},
"EMAIL_COLLECT_BOX": {
"ENABLED": "Habilita",
"DISABLED": "Inhabilita"
@ -394,13 +390,16 @@
"FEATURES": {
"LABEL": "Característiques",
"DISPLAY_FILE_PICKER": "Mostra el selector de fitxers al widget",
"DISPLAY_EMOJI_PICKER": "Mostra el selector d'emoji al widget"
"DISPLAY_EMOJI_PICKER": "Mostra el selector d'emoji al widget",
"ALLOW_END_CONVERSATION": "Allow users to end conversation from the widget"
},
"SETTINGS_POPUP": {
"MESSENGER_HEADING": "Script del missatger",
"MESSENGER_SUB_HEAD": "Col·loca aquest botó dins de l'etiqueta body",
"INBOX_AGENTS": "Agents",
"INBOX_AGENTS_SUB_TEXT": "Afegir o eliminar agents d'aquesta safata d'entrada",
"AGENT_ASSIGNMENT": "Conversation Assignment",
"AGENT_ASSIGNMENT_SUB_TEXT": "Update conversation assignment settings",
"UPDATE": "Actualitza",
"ENABLE_EMAIL_COLLECT_BOX": "Enable email collect box",
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Enable or disable email collect box on new conversation",
@ -431,6 +430,15 @@
},
"PRE_CHAT_FORM": {
"DESCRIPTION": "Pre chat forms enable you to capture user information before they start conversation with you.",
"SET_FIELDS": "Pre chat form fields",
"SET_FIELDS_HEADER": {
"FIELDS": "Fields",
"LABEL": "Label",
"PLACE_HOLDER": "Placeholder",
"KEY": "Key",
"TYPE": "Type",
"REQUIRED": "Required"
},
"ENABLE": {
"LABEL": "Enable pre chat form",
"OPTIONS": {
@ -439,7 +447,7 @@
}
},
"PRE_CHAT_MESSAGE": {
"LABEL": "Pre Chat Message",
"LABEL": "Pre chat message",
"PLACEHOLDER": "This message would be visible to the users along with the form"
},
"REQUIRE_EMAIL": {
@ -468,6 +476,7 @@
"IMAP": {
"TITLE": "IMAP",
"SUBTITLE": "Set your IMAP details",
"NOTE_TEXT": "To enable SMTP, please configure IMAP.",
"UPDATE": "Update IMAP settings",
"TOGGLE_AVAILABILITY": "Enable IMAP configuration for this inbox",
"TOGGLE_HELP": "Enabling IMAP will help the user to recieve email",
@ -483,9 +492,9 @@
"LABEL": "Port",
"PLACE_HOLDER": "Port"
},
"EMAIL": {
"LABEL": "Correu electrònic",
"PLACE_HOLDER": "Correu electrònic"
"LOGIN": {
"LABEL": "Inicia la sessió",
"PLACE_HOLDER": "Inicia la sessió"
},
"PASSWORD": {
"LABEL": "Contrasenya",
@ -511,9 +520,9 @@
"LABEL": "Port",
"PLACE_HOLDER": "Port"
},
"EMAIL": {
"LABEL": "Correu electrònic",
"PLACE_HOLDER": "Correu electrònic"
"LOGIN": {
"LABEL": "Inicia la sessió",
"PLACE_HOLDER": "Inicia la sessió"
},
"PASSWORD": {
"LABEL": "Contrasenya",
@ -526,7 +535,9 @@
"ENCRYPTION": "Encryption",
"SSL_TLS": "SSL/TLS",
"START_TLS": "STARTTLS",
"OPEN_SSL_VERIFY_MODE": "Open SSL Verify Mode"
}
"OPEN_SSL_VERIFY_MODE": "Open SSL Verify Mode",
"AUTH_MECHANISM": "Authentication"
},
"NOTE": "Note: "
}
}

View file

@ -2,6 +2,29 @@
"INTEGRATION_SETTINGS": {
"HEADER": "Integracions",
"WEBHOOK": {
"SUBSCRIBED_EVENTS": "Subscribed Events",
"FORM": {
"CANCEL": "Cancel·la",
"DESC": "Els esdeveniments de Webhook us proporcionen informació en temps real sobre el que passa al vostre compte de Chatwoot. Introduïu una URL vàlid per configurar un callback.",
"SUBSCRIPTIONS": {
"LABEL": "Events",
"EVENTS": {
"CONVERSATION_CREATED": "Conversation Created",
"CONVERSATION_STATUS_CHANGED": "Conversation Status Changed",
"CONVERSATION_UPDATED": "Conversation Updated",
"MESSAGE_CREATED": "Message created",
"MESSAGE_UPDATED": "Message updated",
"WEBWIDGET_TRIGGERED": "Live chat widget opened by the user"
}
},
"END_POINT": {
"LABEL": "URL del webhook",
"PLACEHOLDER": "Exemple: https://example/api/webhook",
"ERROR": "Introduïu una URL vàlid"
},
"EDIT_SUBMIT": "Update webhook",
"ADD_SUBMIT": "Crear webhook"
},
"TITLE": "Webhook",
"CONFIGURE": "Configura",
"HEADER": "Configuració Webhook",
@ -20,35 +43,16 @@
"EDIT": {
"BUTTON_TEXT": "Edita",
"TITLE": "Edit webhook",
"CANCEL": "Cancel·la",
"DESC": "Els esdeveniments de Webhook us proporcionen informació en temps real sobre el que passa al vostre compte de Chatwoot. Introduïu una URL vàlid per configurar un callback.",
"FORM": {
"END_POINT": {
"LABEL": "URL del webhook",
"PLACEHOLDER": "Exemple: https://example/api/webhook",
"ERROR": "Introduïu una URL vàlid"
},
"SUBMIT": "Edit webhook"
},
"API": {
"SUCCESS_MESSAGE": "Webhook URL updated successfully",
"SUCCESS_MESSAGE": "Webhook configuration updated successfully",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
}
},
"ADD": {
"CANCEL": "Cancel·la",
"TITLE": "Afegir un nou webhook",
"DESC": "Els esdeveniments de Webhook us proporcionen informació en temps real sobre el que passa al vostre compte de Chatwoot. Introduïu una URL vàlid per configurar un callback.",
"FORM": {
"END_POINT": {
"LABEL": "URL del webhook",
"PLACEHOLDER": "Exemple: https://example/api/webhook",
"ERROR": "Introduïu una URL vàlid"
},
"SUBMIT": "Crear webhook"
},
"API": {
"SUCCESS_MESSAGE": "S'ha afegit el Webhook correctament",
"SUCCESS_MESSAGE": "Webhook configuration added successfully",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
}
},
@ -60,7 +64,7 @@
},
"CONFIRM": {
"TITLE": "Confirma l'esborrat",
"MESSAGE": "N'estàs segur ",
"MESSAGE": "Are you sure to delete the webhook? (%{webhookURL})",
"YES": "Si, esborra ",
"NO": "No, manten-la"
}

View file

@ -1,6 +1,6 @@
{
"REPORT": {
"HEADER": "Overview",
"HEADER": "Converses",
"LOADING_CHART": "S'estan carregant dades del gràfic...",
"NO_ENOUGH_DATA": "No hem rebut suficients punts de dades per generar l'informe. Torneu-ho a provar més endavant.",
"DOWNLOAD_AGENT_REPORTS": "Descarregar Informes d'Agent",
@ -18,12 +18,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Primer temps de resposta",
"DESC": "( Promig )"
"NAME": "First Response Time",
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Temps de resolució",
"DESC": "( Promig )"
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Total de resolucions",
@ -108,7 +112,8 @@
"id": 4,
"groupBy": "Year"
}
]
],
"BUSINESS_HOURS": "Business Hours"
},
"AGENT_REPORTS": {
"HEADER": "Agents Overview",
@ -130,12 +135,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Primer temps de resposta",
"DESC": "( Promig )"
"NAME": "First Response Time",
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Temps de resolució",
"DESC": "( Promig )"
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Total de resolucions",
@ -193,12 +202,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Primer temps de resposta",
"DESC": "( Promig )"
"NAME": "First Response Time",
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Temps de resolució",
"DESC": "( Promig )"
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Total de resolucions",
@ -256,12 +269,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Primer temps de resposta",
"DESC": "( Promig )"
"NAME": "First Response Time",
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Temps de resolució",
"DESC": "( Promig )"
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Total de resolucions",
@ -319,12 +336,16 @@
"DESC": "( Total )"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Primer temps de resposta",
"DESC": "( Promig )"
"NAME": "First Response Time",
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Temps de resolució",
"DESC": "( Promig )"
"DESC": "( Promig )",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Total de resolucions",
@ -392,5 +413,33 @@
"TOOLTIP": "Total number of responses / Total number of CSAT survey messages sent * 100"
}
}
},
"OVERVIEW_REPORTS": {
"HEADER": "Overview",
"LIVE": "Live",
"ACCOUNT_CONVERSATIONS": {
"HEADER": "Open Conversations",
"LOADING_MESSAGE": "Loading conversation metrics...",
"OPEN": "Obrir",
"UNATTENDED": "Unattended",
"UNASSIGNED": "Sense assignar"
},
"AGENT_CONVERSATIONS": {
"HEADER": "Conversations by agents",
"LOADING_MESSAGE": "Loading agent metrics...",
"NO_AGENTS": "There are no conversations by agents",
"TABLE_HEADER": {
"AGENT": "Agent",
"OPEN": "OPEN",
"UNATTENDED": "Unattended",
"STATUS": "Estat"
}
},
"AGENT_STATUS": {
"HEADER": "Agent status",
"ONLINE": "En línia",
"BUSY": "Ocupat",
"OFFLINE": "Fora de línia"
}
}
}
}

View file

@ -15,6 +15,9 @@
"SUCCESS_MESSAGE": "S'ha canviat la contrasenya correctament",
"ERROR_MESSAGE": "No s'ha pogut connectar amb Woot Server. Torneu-ho a provar més endavant"
},
"CAPTCHA": {
"ERROR": "Verification expired. Please solve captcha again."
},
"SUBMIT": "Envia"
}
}

View file

@ -131,6 +131,10 @@
"BUTTON_TEXT": "Copia",
"COPY_SUCCESSFUL": "El codi s'ha copiat al porta-retalls amb èxit"
},
"SHOW_MORE_BLOCK": {
"SHOW_MORE": "Show More",
"SHOW_LESS": "Show Less"
},
"FILE_BUBBLE": {
"DOWNLOAD": "Descarrega",
"UPLOADING": "S'està carregant..."
@ -147,6 +151,7 @@
},
"SIDEBAR": {
"CURRENTLY_VIEWING_ACCOUNT": "Currently viewing:",
"SWITCH": "Switch",
"CONVERSATIONS": "Converses",
"ALL_CONVERSATIONS": "All Conversations",
"MENTIONED_CONVERSATIONS": "Mentions",
@ -173,7 +178,7 @@
"NEW_LABEL": "New label",
"NEW_TEAM": "New team",
"NEW_INBOX": "New inbox",
"REPORTS_OVERVIEW": "Overview",
"REPORTS_CONVERSATION": "Converses",
"CSAT": "CSAT",
"CAMPAIGNS": "Campaigns",
"ONGOING": "Ongoing",
@ -183,7 +188,8 @@
"REPORTS_INBOX": "Inbox",
"REPORTS_TEAM": "Team",
"SET_AVAILABILITY_TITLE": "Set yourself as",
"BETA": "Beta"
"BETA": "Beta",
"REPORTS_OVERVIEW": "Overview"
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",

View file

@ -21,7 +21,8 @@
"PASSWORD": {
"LABEL": "Contrasenya",
"PLACEHOLDER": "Contrasenya",
"ERROR": "La contrasenya és massa curta"
"ERROR": "La contrasenya és massa curta",
"IS_INVALID_PASSWORD": "Password should contain atleast 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character"
},
"CONFIRM_PASSWORD": {
"LABEL": "Confirma la contrasenya",

View file

@ -83,7 +83,7 @@
"SELECT_ALL": "select all agents",
"SELECTED_COUNT": "%{selected} out of %{total} agents selected.",
"BUTTON_TEXT": "Afegir agents",
"AGENT_VALIDATION_ERROR": "Select atleaset one agent."
"AGENT_VALIDATION_ERROR": "Select at least one agent."
},
"FINISH": {
"TITLE": "Your team is ready!",

View file

@ -89,7 +89,9 @@
"DELETE_MESSAGE": "You need to have atleast one condition to save"
},
"ACTION": {
"DELETE_MESSAGE": "You need to have atleast one action to save"
"DELETE_MESSAGE": "You need to have atleast one action to save",
"TEAM_MESSAGE_INPUT_PLACEHOLDER": "Enter your message here",
"TEAM_DROPDOWN_PLACEHOLDER": "Select teams"
},
"TOGGLE": {
"ACTIVATION_TITLE": "Activate Automation Rule",
@ -102,6 +104,13 @@
"DEACTIVATION_ERROR": "Could not Deactivate Automation, Please try again later",
"CONFIRMATION_LABEL": "Ano",
"CANCEL_LABEL": "Ne"
},
"ATTACHMENT": {
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
"LABEL_IDLE": "Upload Attachment",
"LABEL_UPLOADING": "Nahrávání...",
"LABEL_UPLOADED": "Succesfully Uploaded",
"LABEL_UPLOAD_FAILED": "Upload Failed"
}
}
}

View file

@ -81,6 +81,7 @@
"NO_MESSAGES": "Žádné zprávy",
"NO_CONTENT": "Žádný obsah k dispozici",
"HIDE_QUOTED_TEXT": "Hide Quoted Text",
"SHOW_QUOTED_TEXT": "Show Quoted Text"
"SHOW_QUOTED_TEXT": "Show Quoted Text",
"MESSAGE_READ": "Read"
}
}

View file

@ -70,6 +70,14 @@
"SUCCESS_MESSAGE": "Contacts saved successfully",
"ERROR_MESSAGE": "Došlo k chybě, zkuste to prosím znovu"
},
"DELETE_NOTE": {
"CONFIRM": {
"TITLE": "Potvrdit odstranění",
"MESSAGE": "Are you want sure to delete this note?",
"YES": "Yes, Delete it",
"NO": "No, Keep it"
}
},
"DELETE_CONTACT": {
"BUTTON_LABEL": "Delete Contact",
"TITLE": "Delete contact",

View file

@ -60,6 +60,13 @@
"NOTIFICATIONS_PAGE": {
"HEADER": "Oznámení",
"MARK_ALL_DONE": "Označit vše dokončeno",
"DELETE_TITLE": "deleted",
"UNREAD_NOTIFICATION": {
"TITLE": "Unread Notifications",
"ALL_NOTIFICATIONS": "View all notifications",
"LOADING_UNREAD_MESSAGE": "Loading unread notifications...",
"EMPTY_MESSAGE": "You have no unread notifications"
},
"LIST": {
"LOADING_MESSAGE": "Načítání upozornění...",
"404": "Žádná upozornění",
@ -101,6 +108,7 @@
"GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard",
"GO_TO_CONTACTS_DASHBOARD": "Go to Contacts Dashboard",
"GO_TO_REPORTS_OVERVIEW": "Go to Reports Overview",
"GO_TO_CONVERSATION_REPORTS": "Go to Conversation Reports",
"GO_TO_AGENT_REPORTS": "Go to Agent Reports",
"GO_TO_LABEL_REPORTS": "Go to Label Reports",
"GO_TO_INBOX_REPORTS": "Go to Inbox Reports",

View file

@ -341,10 +341,6 @@
"AUTO_ASSIGNMENT_SUCCESS_MESSAGE": "Automatické přiřazení bylo úspěšně aktualizováno",
"ERROR_MESSAGE": "Nelze aktualizovat barvu widgetu. Opakujte akci později."
},
"AUTO_ASSIGNMENT": {
"ENABLED": "Povoleno",
"DISABLED": "Zakázáno"
},
"EMAIL_COLLECT_BOX": {
"ENABLED": "Povoleno",
"DISABLED": "Zakázáno"
@ -394,13 +390,16 @@
"FEATURES": {
"LABEL": "Funkce",
"DISPLAY_FILE_PICKER": "Display file picker on the widget",
"DISPLAY_EMOJI_PICKER": "Display emoji picker on the widget"
"DISPLAY_EMOJI_PICKER": "Display emoji picker on the widget",
"ALLOW_END_CONVERSATION": "Allow users to end conversation from the widget"
},
"SETTINGS_POPUP": {
"MESSENGER_HEADING": "Messenger skript",
"MESSENGER_SUB_HEAD": "Umístěte toto tlačítko dovnitř vašeho tělesného štítku",
"INBOX_AGENTS": "Agenti",
"INBOX_AGENTS_SUB_TEXT": "Přidat nebo odebrat agenty z této složky doručené pošty",
"AGENT_ASSIGNMENT": "Conversation Assignment",
"AGENT_ASSIGNMENT_SUB_TEXT": "Update conversation assignment settings",
"UPDATE": "Aktualizovat",
"ENABLE_EMAIL_COLLECT_BOX": "Enable email collect box",
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Enable or disable email collect box on new conversation",
@ -431,6 +430,15 @@
},
"PRE_CHAT_FORM": {
"DESCRIPTION": "Pre chat forms enable you to capture user information before they start conversation with you.",
"SET_FIELDS": "Pre chat form fields",
"SET_FIELDS_HEADER": {
"FIELDS": "Fields",
"LABEL": "Label",
"PLACE_HOLDER": "Placeholder",
"KEY": "Key",
"TYPE": "Type",
"REQUIRED": "Required"
},
"ENABLE": {
"LABEL": "Enable pre chat form",
"OPTIONS": {
@ -439,7 +447,7 @@
}
},
"PRE_CHAT_MESSAGE": {
"LABEL": "Zpráva před chatem",
"LABEL": "Pre chat message",
"PLACEHOLDER": "This message would be visible to the users along with the form"
},
"REQUIRE_EMAIL": {
@ -468,6 +476,7 @@
"IMAP": {
"TITLE": "IMAP",
"SUBTITLE": "Set your IMAP details",
"NOTE_TEXT": "To enable SMTP, please configure IMAP.",
"UPDATE": "Update IMAP settings",
"TOGGLE_AVAILABILITY": "Enable IMAP configuration for this inbox",
"TOGGLE_HELP": "Enabling IMAP will help the user to recieve email",
@ -483,9 +492,9 @@
"LABEL": "Port",
"PLACE_HOLDER": "Port"
},
"EMAIL": {
"LABEL": "E-mailová adresa",
"PLACE_HOLDER": "E-mailová adresa"
"LOGIN": {
"LABEL": "Přihlásit se",
"PLACE_HOLDER": "Přihlásit se"
},
"PASSWORD": {
"LABEL": "Heslo",
@ -511,9 +520,9 @@
"LABEL": "Port",
"PLACE_HOLDER": "Port"
},
"EMAIL": {
"LABEL": "E-mailová adresa",
"PLACE_HOLDER": "E-mailová adresa"
"LOGIN": {
"LABEL": "Přihlásit se",
"PLACE_HOLDER": "Přihlásit se"
},
"PASSWORD": {
"LABEL": "Heslo",
@ -526,7 +535,9 @@
"ENCRYPTION": "Encryption",
"SSL_TLS": "SSL/TLS",
"START_TLS": "STARTTLS",
"OPEN_SSL_VERIFY_MODE": "Open SSL Verify Mode"
}
"OPEN_SSL_VERIFY_MODE": "Open SSL Verify Mode",
"AUTH_MECHANISM": "Authentication"
},
"NOTE": "Note: "
}
}

View file

@ -2,6 +2,29 @@
"INTEGRATION_SETTINGS": {
"HEADER": "Integrace",
"WEBHOOK": {
"SUBSCRIBED_EVENTS": "Subscribed Events",
"FORM": {
"CANCEL": "Zrušit",
"DESC": "Události webhooku vám poskytují reálné informace o tom, co se děje ve vašem Chatwoot účtu. Zadejte prosím platnou URL pro nastavení hovoru.",
"SUBSCRIPTIONS": {
"LABEL": "Events",
"EVENTS": {
"CONVERSATION_CREATED": "Conversation Created",
"CONVERSATION_STATUS_CHANGED": "Conversation Status Changed",
"CONVERSATION_UPDATED": "Conversation Updated",
"MESSAGE_CREATED": "Message created",
"MESSAGE_UPDATED": "Message updated",
"WEBWIDGET_TRIGGERED": "Live chat widget opened by the user"
}
},
"END_POINT": {
"LABEL": "URL webového háčku",
"PLACEHOLDER": "Příklad: https://example/api/webhook",
"ERROR": "Zadejte prosím platnou URL"
},
"EDIT_SUBMIT": "Update webhook",
"ADD_SUBMIT": "Create webhook"
},
"TITLE": "Webový háček",
"CONFIGURE": "Konfigurace",
"HEADER": "Nastavení webhooku",
@ -20,35 +43,16 @@
"EDIT": {
"BUTTON_TEXT": "Upravit",
"TITLE": "Edit webhook",
"CANCEL": "Zrušit",
"DESC": "Události webhooku vám poskytují reálné informace o tom, co se děje ve vašem Chatwoot účtu. Zadejte prosím platnou URL pro nastavení hovoru.",
"FORM": {
"END_POINT": {
"LABEL": "URL webového háčku",
"PLACEHOLDER": "Příklad: https://example/api/webhook",
"ERROR": "Zadejte prosím platnou URL"
},
"SUBMIT": "Edit webhook"
},
"API": {
"SUCCESS_MESSAGE": "Webhook URL updated successfully",
"SUCCESS_MESSAGE": "Webhook configuration updated successfully",
"ERROR_MESSAGE": "Nelze se připojit k Woot serveru, opakujte akci později"
}
},
"ADD": {
"CANCEL": "Zrušit",
"TITLE": "Přidat nový webový háček",
"DESC": "Události webhooku vám poskytují reálné informace o tom, co se děje ve vašem Chatwoot účtu. Zadejte prosím platnou URL pro nastavení hovoru.",
"FORM": {
"END_POINT": {
"LABEL": "URL webového háčku",
"PLACEHOLDER": "Příklad: https://example/api/webhook",
"ERROR": "Zadejte prosím platnou URL"
},
"SUBMIT": "Vytvořit webový háček"
},
"API": {
"SUCCESS_MESSAGE": "Webový háček byl úspěšně přidán",
"SUCCESS_MESSAGE": "Webhook configuration added successfully",
"ERROR_MESSAGE": "Nelze se připojit k Woot serveru, opakujte akci později"
}
},
@ -60,7 +64,7 @@
},
"CONFIRM": {
"TITLE": "Potvrdit odstranění",
"MESSAGE": "Opravdu chcete odstranit ",
"MESSAGE": "Are you sure to delete the webhook? (%{webhookURL})",
"YES": "Ano, odstranit ",
"NO": "No, Keep it"
}

View file

@ -1,6 +1,6 @@
{
"REPORT": {
"HEADER": "Overview",
"HEADER": "Konverzace",
"LOADING_CHART": "Načítání dat mapy...",
"NO_ENOUGH_DATA": "Pro vytvoření hlášení jsme neobdrželi dostatek dat, zkuste to prosím později.",
"DOWNLOAD_AGENT_REPORTS": "Stáhnout reporty agentů",
@ -18,12 +18,16 @@
"DESC": "( celkem)"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Čas první odpovědi",
"DESC": "(Průměrný)"
"NAME": "First Response Time",
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Čas rozlišení",
"DESC": "(Průměrný)"
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Počet rozlišení",
@ -108,7 +112,8 @@
"id": 4,
"groupBy": "Year"
}
]
],
"BUSINESS_HOURS": "Pracovní doba"
},
"AGENT_REPORTS": {
"HEADER": "Agents Overview",
@ -130,12 +135,16 @@
"DESC": "( celkem)"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Čas první odpovědi",
"DESC": "(Průměrný)"
"NAME": "First Response Time",
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Čas rozlišení",
"DESC": "(Průměrný)"
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Počet rozlišení",
@ -193,12 +202,16 @@
"DESC": "( celkem)"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Čas první odpovědi",
"DESC": "(Průměrný)"
"NAME": "First Response Time",
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Čas rozlišení",
"DESC": "(Průměrný)"
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Počet rozlišení",
@ -256,12 +269,16 @@
"DESC": "( celkem)"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Čas první odpovědi",
"DESC": "(Průměrný)"
"NAME": "First Response Time",
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Čas rozlišení",
"DESC": "(Průměrný)"
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Počet rozlišení",
@ -319,12 +336,16 @@
"DESC": "( celkem)"
},
"FIRST_RESPONSE_TIME": {
"NAME": "Čas první odpovědi",
"DESC": "(Průměrný)"
"NAME": "First Response Time",
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_TIME": {
"NAME": "Čas rozlišení",
"DESC": "(Průměrný)"
"DESC": "(Průměrný)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "Resolution Time is %{metricValue} (based on %{conversationCount} conversations)"
},
"RESOLUTION_COUNT": {
"NAME": "Počet rozlišení",
@ -392,5 +413,33 @@
"TOOLTIP": "Total number of responses / Total number of CSAT survey messages sent * 100"
}
}
},
"OVERVIEW_REPORTS": {
"HEADER": "Overview",
"LIVE": "Live",
"ACCOUNT_CONVERSATIONS": {
"HEADER": "Open Conversations",
"LOADING_MESSAGE": "Loading conversation metrics...",
"OPEN": "Otevřít",
"UNATTENDED": "Unattended",
"UNASSIGNED": "Nepřiřazeno"
},
"AGENT_CONVERSATIONS": {
"HEADER": "Conversations by agents",
"LOADING_MESSAGE": "Loading agent metrics...",
"NO_AGENTS": "There are no conversations by agents",
"TABLE_HEADER": {
"AGENT": "Agent",
"OPEN": "OPEN",
"UNATTENDED": "Unattended",
"STATUS": "Stav"
}
},
"AGENT_STATUS": {
"HEADER": "Agent status",
"ONLINE": "Online",
"BUSY": "Zaneprázdněn",
"OFFLINE": "Offline"
}
}
}
}

View file

@ -15,6 +15,9 @@
"SUCCESS_MESSAGE": "Heslo bylo úspěšně změněno",
"ERROR_MESSAGE": "Nelze se připojit k Woot serveru, opakujte akci později"
},
"CAPTCHA": {
"ERROR": "Verification expired. Please solve captcha again."
},
"SUBMIT": "Odeslat"
}
}

View file

@ -131,6 +131,10 @@
"BUTTON_TEXT": "Kopírovat",
"COPY_SUCCESSFUL": "Kód byl úspěšně zkopírován do schránky"
},
"SHOW_MORE_BLOCK": {
"SHOW_MORE": "Show More",
"SHOW_LESS": "Show Less"
},
"FILE_BUBBLE": {
"DOWNLOAD": "Stáhnout",
"UPLOADING": "Nahrávání..."
@ -147,6 +151,7 @@
},
"SIDEBAR": {
"CURRENTLY_VIEWING_ACCOUNT": "Currently viewing:",
"SWITCH": "Switch",
"CONVERSATIONS": "Konverzace",
"ALL_CONVERSATIONS": "All Conversations",
"MENTIONED_CONVERSATIONS": "Mentions",
@ -173,7 +178,7 @@
"NEW_LABEL": "New label",
"NEW_TEAM": "New team",
"NEW_INBOX": "New inbox",
"REPORTS_OVERVIEW": "Overview",
"REPORTS_CONVERSATION": "Konverzace",
"CSAT": "CSAT",
"CAMPAIGNS": "Kampaně",
"ONGOING": "Ongoing",
@ -183,7 +188,8 @@
"REPORTS_INBOX": "Inbox",
"REPORTS_TEAM": "Team",
"SET_AVAILABILITY_TITLE": "Set yourself as",
"BETA": "Beta"
"BETA": "Beta",
"REPORTS_OVERVIEW": "Overview"
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",

View file

@ -21,7 +21,8 @@
"PASSWORD": {
"LABEL": "Heslo",
"PLACEHOLDER": "Heslo",
"ERROR": "Heslo je příliš krátké"
"ERROR": "Heslo je příliš krátké",
"IS_INVALID_PASSWORD": "Password should contain atleast 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character"
},
"CONFIRM_PASSWORD": {
"LABEL": "Potvrzení hesla",

View file

@ -83,7 +83,7 @@
"SELECT_ALL": "select all agents",
"SELECTED_COUNT": "%{selected} out of %{total} agents selected.",
"BUTTON_TEXT": "Přidat agenty",
"AGENT_VALIDATION_ERROR": "Select atleaset one agent."
"AGENT_VALIDATION_ERROR": "Select at least one agent."
},
"FINISH": {
"TITLE": "Your team is ready!",

View file

@ -89,7 +89,9 @@
"DELETE_MESSAGE": "You need to have atleast one condition to save"
},
"ACTION": {
"DELETE_MESSAGE": "You need to have atleast one action to save"
"DELETE_MESSAGE": "You need to have atleast one action to save",
"TEAM_MESSAGE_INPUT_PLACEHOLDER": "Enter your message here",
"TEAM_DROPDOWN_PLACEHOLDER": "Select teams"
},
"TOGGLE": {
"ACTIVATION_TITLE": "Activate Automation Rule",
@ -102,6 +104,13 @@
"DEACTIVATION_ERROR": "Could not Deactivate Automation, Please try again later",
"CONFIRMATION_LABEL": "Yes",
"CANCEL_LABEL": "No"
},
"ATTACHMENT": {
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
"LABEL_IDLE": "Upload Attachment",
"LABEL_UPLOADING": "Uploader...",
"LABEL_UPLOADED": "Succesfully Uploaded",
"LABEL_UPLOAD_FAILED": "Upload Failed"
}
}
}

View file

@ -81,6 +81,7 @@
"NO_MESSAGES": "No Messages",
"NO_CONTENT": "No content available",
"HIDE_QUOTED_TEXT": "Hide Quoted Text",
"SHOW_QUOTED_TEXT": "Show Quoted Text"
"SHOW_QUOTED_TEXT": "Show Quoted Text",
"MESSAGE_READ": "Read"
}
}

View file

@ -70,6 +70,14 @@
"SUCCESS_MESSAGE": "Contacts saved successfully",
"ERROR_MESSAGE": "Der opstod en fejl. Prøv venligst igen"
},
"DELETE_NOTE": {
"CONFIRM": {
"TITLE": "Bekræft Sletning",
"MESSAGE": "Are you want sure to delete this note?",
"YES": "Yes, Delete it",
"NO": "Nej, behold det"
}
},
"DELETE_CONTACT": {
"BUTTON_LABEL": "Delete Contact",
"TITLE": "Delete contact",

View file

@ -60,6 +60,13 @@
"NOTIFICATIONS_PAGE": {
"HEADER": "Notifications",
"MARK_ALL_DONE": "Mark All Done",
"DELETE_TITLE": "deleted",
"UNREAD_NOTIFICATION": {
"TITLE": "Unread Notifications",
"ALL_NOTIFICATIONS": "View all notifications",
"LOADING_UNREAD_MESSAGE": "Loading unread notifications...",
"EMPTY_MESSAGE": "You have no unread notifications"
},
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
@ -101,6 +108,7 @@
"GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard",
"GO_TO_CONTACTS_DASHBOARD": "Go to Contacts Dashboard",
"GO_TO_REPORTS_OVERVIEW": "Go to Reports Overview",
"GO_TO_CONVERSATION_REPORTS": "Go to Conversation Reports",
"GO_TO_AGENT_REPORTS": "Go to Agent Reports",
"GO_TO_LABEL_REPORTS": "Go to Label Reports",
"GO_TO_INBOX_REPORTS": "Go to Inbox Reports",

Some files were not shown because too many files have changed in this diff Show more